MessageManager is framework for asynchronous bidirectional agent to device communication. It is the successor to Bullwinkle.
The library uses ConnectionManager on the device side to receive notifications of connection and disconnection events, and to monitor connection status (ie. so that no attempt it made to send messages when the device is disconnected).
To include this library in your project, add #require "MessageManager.lib.nut:2.4.0"
at the top of your agent and device code.
Note MessageManager is designed to run over reliable (ie. TCP/TLS) connections. Retries only occur in the case of dropped connections or lost packets, or if called manually from beforeSend() or beforeRetry().
- MessageManager — The core library. It is used to add/remove message callbacks, and to send messages
- MessageManager.send() — Sends the data message
- MessageManager.on() — Sets the callback which will be executed when a message with the specified name is received (name-specific or generic)
- MessageManager.defaultOn() — Sets the generic callback, which will be executed when a message doesn’t match any of the name-specific callbacks
- MessageManager.beforeSend() — Sets the callback which will be called before a message is sent
- MessageManager.beforeRetry() — Sets the callback which will be called before a message is retried
- MessageManager.onFail() — Sets the callback which will be called when an error occurs
- MessageManager.onTimeout() — Sets the callback which will be called when an message times out
- MessageManager.onAck() — Sets the callback which will be called on the message acknowledgement
- MessageManager.onReply() — Sets the callback which will be called when a sent message receives a reply
- MessageManager.getPendingCount — Returns the overall number of pending messages (either waiting for acknowledgement or waiting in the retry queue)
- MessageManager.DataMessage — The data message object, consisting of the payload
to be send over the air and meta-information used to control the message lifecycle
- MessageManager.DataMessage.onFail() — Sets the message-local callback to be executed when an error occurs
- MessageManager.DataMessage.onTimeout() — Sets the message-local callback to be executed when a transmission times out
- MessageManager.DataMessage.onAck() — Sets the message-local callback to be executed when a sent message is acknowledged
- MessageManager.DataMessage.onReply() — Sets the message-local callback to be executed when a sent message receives a reply
Calling the MessageManager constructor creates a new MessageManager instance. An optional table can be passed into the constructor (as options) to override default behaviours. options can contain any of the following keys:
Key | Data Type | Description |
---|---|---|
debug | Boolean | The flag that enables debug library mode, which turns on extended logging. Default: false |
retryInterval | Integer | Changes the default timeout parameter passed to the retry method. Default: 0s |
messageTimeout | Integer | Changes the default timeout required before a message is considered failed (to be acknowledged or replied to). Default: 10s |
autoRetry | Boolean | If set to true , MessageManager will automatically continue to retry sending a message until maxAutoRetries has been reached when no onFail() callback is supplied. Please note that if maxAutoRetries is set to 0, autoRetry will have no limit to the number of times it will retry. Default: false |
maxAutoRetries | Integer | Changes the default number of automatic retries to be performed by the library. After this number is reached the message will be dropped. Please note that the message will automatically be retried if there is no onFail() callback registered by the user. Default: 0 |
connectionManager | ConnectionManager | Optional instance of the ConnectionManager library that helps MessageManager track connectivity status. Default: null |
nextIdGenerator | Function | User-defined callback that generates the next message ID. The function has no parameters. Default: null |
onPartnerConnected | Function | Sets the callback to be triggered when the partner is known to be connected. The callback’s signature is: callbackName(reply), where reply(data) is a callback to respond to the “connected” event. Default: null |
onConnectedReply | Function | Sets the callback to be triggered when the partner responds to the connected status. The callback’s signature is: callbackName(response), where response is the response data. Default: null |
maxMessageRate | Integer | Maximum message send rate, which defines the maximum number of messages the library allows to send per second. If the application exceeds the limit, the onFail callback is triggered. Default: 10 messages/s Note please don’t change the value unless absolutely necessary |
firstMessageId | Integer | Initial value for the auto-incrementing message ID. Default: 0 |
// Initialize using default settings
local mm = MessageManager();
// Create ConnectionManager instance
local cm = ConnectionManager({
"blinkupBehavior": CM_BLINK_ALWAYS,
"stayConnected": true
});
imp.setsendbuffersize(8096);
// MessageManager options
local options = { "debug": true,
"retryInterval": 15,
"messageTimeout": 2,
"autoRetry": true,
"maxAutoRetries": 10,
"connectionManager": cm
};
local mm = MessageManager(options);
This method sends a named message to the partner side and returns the MessageManager.DataMessage object created. The data parameter can be a basic Squirrel type (1
, true
, "A String"
) or more complex data structures such as an array or table, but it must be a serializable Squirrel value.
mm.send("lights", true); // Turn on the lights
callbacks is a table containing the local message event callback functions. The library uses the key name to determine which callback-registration method to use. For example, if the table contains the key onAck, the key’s value (which should be a function) will be passed into MessageManager.DataMessage.onAck() in order to register it as the acknowledgement callback.
Key | Registration Method | Description |
---|---|---|
onAck | MessageManager.DataMessage.onAck | Acknowledgement |
onFail | MessageManager.DataMessage.onFail | Failure |
onReply | MessageManager.DataMessage.onReply | Reply |
onTimeout | MessageManager.DataMessage.onTimeout | Timeout |
Note Message-local callbacks override the global ones, ie. if a message-local callback is triggered, the global one, if set, will not be executed.
If the messageName parameter’s argument is not null
, this method sets the name-specific message callback. Otherwise it uses the generic message callback. The generic message callback captures any messages not handled by any name-specific callback. The callback function takes two parameters: message (the message) and reply (a function that can be called to reply to the message).
// Set a name-specific message callback
mm.on("lights", function(message, reply) {
led.write(message.data);
reply("Got it!");
});
// Set a generic message callback
mm.on(null, function(message, reply) {
log(message.data);
reply("Got it!");
});
This method sets a callback which is executed only for those messages that are not matched and handled by any name-specific callbacks set via MessageManager.on. The callback function takes two parameters: message (the message) and reply (a function that can be called to reply to the message).
// Set a generic message callback
mm.defaultOn(function(message, reply) {
log(message.data);
reply("Got it!");
});
This method sets the callback which will be called before a message is sent. The callback has the following parameters:
Parameter | Description |
---|---|
message | An instance of DataMessage to be sent |
enqueue | A function with no parameters which appends the message to the retry queue for later processing |
drop | A function which disposes of the message. It takes two optional parameters: silently, which defaults to true and which governs whether the disposal takes place silently or through the onFail callbacks, and error which if silently is false , specifies the error message to be provided to the onFail callback |
The enqueue and drop functions must be called synchronously, if they are called at all.
mm.beforeSend(
function(msg, enqueue, drop) {
if (runningOutOfMemory()) {
drop();
}
if (needToPreserveMessageOrder() && previousMessagesFailed()) {
enqueue();
}
}
);
This method sets the callback for retry operations. It will be called before the library attempts to re-send the message and has the following parameters:
Parameter | Description |
---|---|
message | An instance of DataMessage to be re-sent |
skip | A function with a single parameter, duration, which postpones the retry attempt and leaves the message in the retry queue for the specified amount of time. If duration is not specified, it defaults to the retryInterval provided for MessageManager constructor |
drop | A function which disposes of the message. It takes two optional parameters: silently, which defaults to true and which governs whether the disposal takes place silently or through the onFail callbacks, and error which if silently is false , specifies the error message to be provided to the onFail callback |
The skip and drop functions must be called synchronously, if they are called at all.
mm.beforeRetry(
function(msg, skip, drop) {
if (runningOutOfMemory()) {
drop();
}
if (needToWaitForSomeReasonBeforeRetry()) {
skip(duration);
}
}
);
This method sets the function to be called when a message error occurs. The callback has the following parameters:
Parameter | Description |
---|---|
message | An instance of DataMessage that caused the error |
reason | The error description string |
retry | A function that can be invoked to retry sending the message in a specified period of time. This function must be called synchronously, if it is called at all. It takes one parameter, interval. If there is no interval parameter specified, the retryInterval value provided for MessageManager constructor is used. If the function is not called, the message will expire |
mm.onFail(
function(msg, error, retry) {
// Always retry to send the message
retry();
}
);
This method sets the callback to be executed when a message timeout occurs. The callback has the following parameters:
Parameter | Description |
---|---|
message | An instance of DataMessage that caused the timeout |
wait | A function which resets the acknowledgement timeout for the message, which means the message will not raise a timeout error for the interval of time specified by the function’s interval parameter. This function must be called synchronously |
fail | A function which makes the message fall through the onFail callbacks |
If neither wait nor fail are called, the message will expire.
mm.onTimeout(
function(msg, wait, fail) {
if (isStillValid(msg)) {
wait(10);
} else {
// Fail otherwise
fail();
}
}
);
This method sets the callback to be executed when the message’s receipt is acknowledged. The callback has the following parameters:
Parameter | Description |
---|---|
message | An instance of DataMessage that was acknowledged |
mm.onAck(
function(msg) {
// Just log the ACK event
server.log("ACK received for " + msg.payload.data);
}
);
Note A message is only acknowledged if the receiving party has defined a callback for this message.
This method sets the callback to be executed when the message is replied to. The callback has the following parameters:
Parameter | Description |
---|---|
message | An instance of DataMessage that was replied to |
response | The response from the partner |
mm.onReply(
function(msg, response) {
processResponseFor(msg.payload.data, response);
}
);
Note Setting the callback doesn’t necessarily imply that the other side must reply to the message. It just registers a function which is executed if and when the message is replied to.
This method returns the overall number of pending messages (either waiting for acknowledgement or waiting in the retry queue).
if (mm.getPendingCount() < SOME_MAX_PENDING_COUNT) {
mm.send("temp", temp);
} else {
// Do something else
}
MessageManager.DataMessage instances are not intended to be created by users manually — they are always returned from the MessageManager.send() method.
This method sets a message-local version of the MessageManager.onFail() callback.
This method sets a message-local version of the MessageManager.onTimeout() callback.
This method sets a message-local version of the MessageManager.onAck() callback.
This method sets a message-local version of the MessageManager.onReply() callback.
Integration with ConnectionManager
// Device code
#require "ConnectionManager.lib.nut:3.0.0"
#require "MessageManager.lib.nut:2.4.0"
local cm = ConnectionManager({
"blinkupBehavior": CM_BLINK_ALWAYS,
"stayConnected": true
});
// Set the recommended buffer size
// (see https://github.com/electricimp/ConnectionManager for details)
imp.setsendbuffersize(8096);
local config = {
"messageTimeout": 2,
"connectionManager": cm
};
local counter = 0;
local mm = MessageManager(config);
mm.onFail(
function(msg, error, retry) {
server.log("Error occurred: " + error);
retry();
}
);
mm.onReply(
function(msg, response) {
server.log("Response for " + msg.payload.data + " received: " + response);
}
);
function sendData() {
mm.send("name", counter++);
imp.wakeup(1, sendData);
}
sendData();
// Agent code
#require "MessageManager.lib.nut:2.4.0"
local mm = MessageManager();
mm.on("name", function(message, reply) {
server.log("Message received: " + message.data);
reply("Got it!");
});
This library is licensed under the MIT License.