MessageManager is framework for asynchronous bidirectional agent to device communication. The library 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 add this library to your project, add #require "MessageManager.lib.nut:2.2.0"
to 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 handlers, and to send messages
- MessageManager.send() — Sends the data message
- MessageManager.on() — Sets the callback which will be called when a message with the specified name is received
- 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 handler to be called when an error occurs
- MessageManager.DataMessage.onTimeout() — Sets the message-local handler to be called when a transmission times out
- MessageManager.DataMessage.onAck() — Sets the message-local handler to be called when a sent message is acknowledged
- MessageManager.DataMessage.onReply() — Sets the message-local handler to be called 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 | Default Value | Description |
---|---|---|---|
debug | Boolean | false |
The flag that enables debug library mode, which turns on extended logging |
retryInterval | Integer | 0 | Changes the default timeout parameter passed to the retry method |
messageTimeout | Integer | 10 | Changes the default timeout required before a message is considered failed (to be acknowledged or replied to) |
autoRetry | Boolean | false |
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 |
maxAutoRetries | Integer | 0 | Changes the default number of automatic retries to be peformed 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() handler registered by the user |
connectionManager | ConnectionManager | null |
Optional instance of the ConnectionManager library that helps MessageManager to track the connectivity status |
nextIdGenerator | Function | null |
User-defined callback that generates the next message ID. The function has no parameters |
onPartnerConnected | Function | null |
Sets the handler to be called when the partner is known to be connected. The handler’s signature is: handler(reply), where reply(data) is the callback to respond to the “connected” event |
onConnectedReply | Function | null |
Sets the handler to be called when the partner responds to the connected status. The handler’s signature is: handler(response), where response is the response data |
maxMessageRate | Integer | 10 | Maximum message send rate, which defines the maximum number of messages the library allows to send per second. If application exceeds the limit, the onFail handler is called. Note please don’t change the value unless absolutely necessary. |
firstMessageId | Integer | 0 | Initial value for the auto-incrementing message ID |
// Initialize using default settings
local mm = MessageManager();
// Create ConnectionManager instance
local cm = ConnectionManager({
"blinkupBehavior": ConnectionManager.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);
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
handlers is a table containing the message-local message event handlers:
Note Message-local handlers override the global ones. I.e. if a message-local handler is triggered, the global one, if set, is not executed.
Key | Description | Handler |
---|---|---|
onAck | Acknowledgement handler | MessageManager.DataMessage.onAck |
onFail | Failure handler | MessageManager.DataMessage.onFail |
onReply | Reply handler | MessageManager.DataMessage.onReply |
onTimeout | Timeout handler | MessageManager.DataMessage.onTimeout |
Sets a message listener function (callback) for the specified messageName. The callback function takes two parameters: message (the message) and reply (a function that can be called to reply to the message).
// Get a message, and do something with it
mm.on("lights", function(message, reply) {
led.write(message.data);
reply("Got it!");
});
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();
}
}
)
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);
}
}
)
Sets the callack 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();
}
)
Sets the callback to be called 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();
}
}
);
Sets the callback to be called 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 acked if the receiving party has a handler for this message defined.
Sets the callback to be called 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 sets a handler, which is executed when/if the message is replied.
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.
Sets a message-local version of the MessageManager.onFail() handler.
Sets a message-local version of the MessageManager.onTimeout() handler.
Sets a message-local version of the MessageManager.onAck() handler.
Sets a message-local version of the MessageManager.onReply() handler.
Integration with ConnectionManager
// Device code
#require "ConnectionManager.class.nut:1.0.2"
#require "MessageManager.lib.nut:2.2.0"
local cm = ConnectionManager({
"blinkupBehavior": ConnectionManager.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.2.0"
local mm = MessageManager();
mm.on("name", function(data, reply) {
server.log("message received: " + data);
reply("Got it!");
});
MessageManager is licensed under the MIT License.