Skip to main content

MessageManager

Latest Version: 2.2.1

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).

You can view the latest version of the library’s source code on GitHub. Click here to see information on other versions of this library.

To add this library to your project, add #require "MessageManager.lib.nut:2.2.1" 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().

API Overview

MessageManager Usage

Constructor: MessageManager([options])

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 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 when 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.

Examples

// 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);

MessageManager.send(name[, data][, handlers][, timeout][, metadata])

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:

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

Note Message-local handlers override the global ones, ie. if a message-local handler is triggered, the global one, if set, is not executed.

MessageManager.on(messageName, callback)

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).

Example

// Get a message, and do something with it
mm.on("lights", function(message, reply) {
  led.write(message.data);
  reply("Got it!");
});

MessageManager.beforeSend(callback)

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.

Example

mm.beforeSend(
  function(msg, enqueue, drop) {
    if (runningOutOfMemory()) {
      drop();
    }

    if (needToPreserveMessageOrder() && previousMessagesFailed()) {
      enqueue();
    }
  }
);

MessageManager.beforeRetry(callback)

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.

Example

mm.beforeRetry(
  function(msg, skip, drop) {
    if (runningOutOfMemory()) {
      drop();
    }

    if (needToWaitForSomeReasonBeforeRetry()) {
      skip(duration);
    }
  }
);

MessageManager.onFail(callback)

Sets the callback 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

Example

mm.onFail(
  function(msg, error, retry) {
    // Always retry to send the message
    retry();
  }
);

MessageManager.onTimeout(callback)

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.

Example

mm.onTimeout(
  function(msg, wait, fail) {
    if (isStillValid(msg)) {
      wait(10);
    } else {
      // Fail otherwise
      fail();
    }
  }
);

MessageManager.onAck(callback)

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

Example

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 a handler for the defined message.

MessageManager.onReply(callback)

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

Example

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 if and when the message is replied.

MessageManager.getPendingCount()

Returns the overall number of pending messages (either waiting for acknowledgement or waiting in the retry queue).

Example

if (mm.getPendingCount() < SOME_MAX_PENDING_COUNT) {
  mm.send("temp", temp);
} else {
  // do something else
}

MessageManager.DataMessage

MessageManager.DataMessage instances are not intended to be created by users manually — they are always returned from the MessageManager.send() method.

MessageManager.DataMessage.onFail()

Sets a message-local version of the MessageManager.onFail() handler.

MessageManager.DataMessage.onTimeout()

Sets a message-local version of the MessageManager.onTimeout() handler.

MessageManager.DataMessage.onAck()

Sets a message-local version of the MessageManager.onAck() handler.

MessageManager.DataMessage.onReply()

Sets a message-local version of the MessageManager.onReply() handler.

Other Usage Examples

Integration with ConnectionManager

// Device code
#require "ConnectionManager.class.nut:1.0.2"
#require "MessageManager.lib.nut:2.2.1"

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.1"

local mm = MessageManager();

mm.on("name", function(data, reply) {
  server.log("message received: " + data);
  reply("Got it!");
});

Release History

The Electric Imp Dev Center documents the latest version of the library. For past versions, please see the Electric Imp public GitHub repos listed below.

Version Source Code Notes
0.9.0 GitHub Initial release
1.0.0 GitHub Add firstMessageId key to constructor options table; add error parameter to beforeRetry() and beforeSend() callback’s drop() parameter list
1.0.1 GitHub Bug fix for the “end of statement expected (; or lf) in file 'messagemanager.class.nut:1.0.0' row 299 col 120” issue
1.0.2 GitHub Fixed maxMessageRate issue
2.0.0 GitHub Bug fixes; Updated library name to meet requirements of new naming scheme
2.1.0 GitHub Message-local handlers overwrite the library-global ones
2.2.0 GitHub Send ACK only if there is a handler for the message registered (send NACK otherwise)
2.2.1 GitHub Fixed the "undefined handler" issue on startup

License

The MessageManager class is licensed under the MIT License.