Latest Version: 0.2.0
The Messenger library provides basic asynchronous bidirectional agent and device communication. It includes the following features:
The ReplayMessenger library can be used alongside Messenger if persistent storage and retry features are required. It adds the following features:
ReplayMessenger is only supported on the device, where messages are able to be persisted in SPI flash storage. ReplayMessenger extends the base Messenger class, therefore Messenger must be included before and in addition to ReplayMessenger, as shown in the library inclusion statements below.
You can view the library’s source code on GitHub. Click here to see information on other versions of this library.
To include the Messenger library in your project, add the following at the top of your agent and device code:
#require "Messenger.lib.nut:0.2.0"
To include the ReplayMessenger library in your project, add the following at the top of your device code:
#require "Serializer.class.nut:1.0.0"
#require "SPIFlashLogger.device.lib.nut:2.2.0"
#require "ConnectionManager.lib.nut:3.1.1"
#require "Messenger.lib.nut:0.2.0"
#require "ReplayMessenger.device.lib.nut:0.2.0"
Note In the following discussion, the term ‘partner’ refers to either the agent or the device: the device’s partner is its agent; the agent’s partner is the device.
Parameter | Type | Required? | Description |
---|---|---|---|
options | Table | No | See Messenger Options below for details and default values |
Key | Data Type | Description |
---|---|---|
ackTimeout | Integer | The time after which an unacknowledged message is considered to have failed to be sent. Default: 10s |
maxRate | Integer | The maximum message send rate, ie. the maximum number of messages the library allows to be sent in a second. If the application exceeds the limit, the onFail callback is triggered. Default: 10 messages/s Note Please don’t change this value unless absolutely necessary |
firstMsgId | Integer | An initial value for the auto-incrementing message ID. Default: 0 |
debug | Boolean | A flag that enables or disables extended logging for debugging purposes. Default: false |
// Initialize using the default settings
local msngr = Messenger();
This method sends a named message to the partner and returns an instance of the Messenger.Message class.
Parameter | Type | Required? | Description |
---|---|---|---|
name | String | Yes | The name of the message to be sent |
data | Any serializable type | No | The data to be sent. It can be a basic Squirrel type (integer, float, boolean or string) or an array or table, but it must be serializable. Default: null |
ackTimeout | Integer | No | An individual message timeout. Default: No timeout |
metadata | Any serializable type | No | Message metadata. It will not be sent to the partner. Default: null |
Messenger.Message instance.
// Turn on the lights
msngr.send("lights", true);
This method registers a name-specific message callback. The callback function is triggered when a message with the specified name is received from the partner.
Parameter | Type | Required? | Description |
---|---|---|---|
name | String | Yes | The name of the message with which the registered function will be associated |
onMsg | Function | Yes | A callback that is triggered when a message with the specified name is received. See The onMsg Callback, below, for the function’s details |
Nothing.
Your onMsg function should include the following parameters:
Parameter | Type | Required? | Description |
---|---|---|---|
message | Table | Yes | The received message’s payload. See Message Payload Table, below, for details |
customAck | Function | Yes | A function that your onMsg code can call to customize message acknowledgement. This function has no parameters. See Custom Acknowledgement, below, for details |
If your onMsg callback code does not call the function it receives through its customAck parameter, an automatic message acknowledgement will be sent by the library to the partner. However, if you wish to add data to and/or delay the message acknowledgement, your code should call the customAck function and store the return value, itself a function, that can be called later when you wish to acknowledge the original message.
When called, the acknowledgement function sends an acknowledgement message to the partner. If you wish to add data to this acknowledgement, pass it into the acknowledgement function. The data can be any serializable type and defaults to null
. The acknowledgement function does not return a value.
Here is a typical custom acknowledgement flow:
This flow is illustrated in the example below.
Note This example is meant to illustrate usage and incorporates variables that are defined outside of the scope of these few lines.
// Automatically Acknowledge Messages
function lxHandler(payload, customAck) {
// Do NOT call the received custom acknowledgement function
// to ensure that automatic message acknowledgement takes place
local lightsOn = payload.data;
(lightsOn) ? led.write(ON) : led.write(OFF);
}
// Create Custom Acknowledgement Messages
function tempHandler(payload, customAck) {
// Call the received custom acknowledgement function to prevent
// auto-acknowledgement, and store the returned acknowledgement function
local ack = customAck();
// Take a reading
tempHumid.read(function(result) {
if ("error" in result) {
server.error(result.error);
// Acknowledge the message by calling the stored acknowledgement function
// The reading failed, so don't pass in a data value
ack();
return;
}
// Acknowledge the message by calling the stored acknowledgement function
// Reading was successful, so pass in the reading result
ack(result);
}.bindenv(this));
}
// Register 'onMsg' callbacks for given names
msngr.on("lights", lxHandler.bindenv(this));
msngr.on("temp", tempHandler.bindenv(this));
This method sets the default callback, which will be executed when a received message’s name doesn't match any of the name-specific handlers registered with on.
Parameter | Type | Required? | Description |
---|---|---|---|
onMsg | Function | Yes | Callback that is triggered when a message without an on handler is received. See onMsg Callback, above, for the function’s details |
Nothing.
function onUnknownMsg(payload, customAck) {
local data = payload.data;
// Log message name and payload data
server.error("Received unknown message: " + payload.name);
if (typeof data == "table" || typeof data == "array") {
server.log(http.jsonencode(data));
} else {
server.log(data);
}
// Allow automatic msg ack, so msg is not retried.
}
msngr.defaultOn(onUnknownMsg.bindenv(this));
This method registers a global callback that will be executed when a message is acknowledged.
Parameter | Type | Required? | Description |
---|---|---|---|
onAckCallback | Function | Yes | Callback that is triggered when a message is acknowledged. See The onAck Callback, below, for the function’s details |
Nothing.
Your onAck function should include the following parameters:
Parameter | Type | Required? | Description |
---|---|---|---|
message | A Messenger.Message instance | Yes | The Messenger.Message instance created and returned when the message was sent |
ackData | Any serializable type | Yes | The data sent in the partner’s custom acknowledgement, or null if no data was sent |
function onAck(message, ackData) {
// Store message info in variables
local payload = message.payload;
local id = payload.id;
local name = payload.name;
local msgData = payload.data;
local metadata = message.metadata;
// Log message info
server.log("Received ack for message " + name + " with id: " + id);
// Log message ack data if any
if (ackData != null) server.log(ackData);
// TODO Create switch statement to handle ACKs for specific messages based on id, name, data or metadata
}
msngr.onAck(onAck.bindenv(this));
This method registers a global callback that will be executed when message transmission was not acknowledged, ie. the message is not considered to have been sent.
Parameter | Type | Required? | Description |
---|---|---|---|
onFailCallback | Function | Yes | Callback that is triggered when a message fails. See The onFail Callback, below, for the function’s details |
Nothing.
Your onFail function should include the following parameters:
Parameter | Type | Required? | Description |
---|---|---|---|
message | A Messenger.Message instance | Yes | The Messenger.Message instance created and returned when message was sent |
reason | String | Yes | A description of the message failure |
function onFail(message, reason) {
// Store message info in variables
local payload = message.payload;
local id = payload.id;
local name = payload.name;
local msgData = payload.data;
local metadata = message.metadata;
// Log message info
server.error("Message " + name + " with id " + id + " send failure reason: " + reason);
// TODO: Create switch statement to handle failures for specific messages based on id, name, data or metadata
}
msngr.onFail(onFail.bindenv(this));
Messages should only be created by calling the Messenger.send() or ReplayMessenger.send() methods, never by your application. These methods will return an instance of this class. The message instance will also be passed to the global onAck and onFail callbacks, and ReplayMessenger’s confirmResend() function.
The Message class doesn’t contain any getters and setters. Instead it provides public properties that may you may wist to access directly. Please see the onAck() and onFail() examples for details.
Property | Data Type | Value |
---|---|---|
payload | Table | A table that is sent between partners when a send method is called. See Message Payload Table below for table details |
metadata | Any serializable type | Optional application-defined data containing additional message properties. This data will not be sent to the partner, but can be accessed when the onAck or onFail callbacks are triggered |
Key | Data Type | Description |
---|---|---|
id | Integer | A message identifier, an auto-incrementing value |
name | String | The name of the message |
data | Any serializable type | The message data |
ReplayMessenger extends Messenger by adding features for resending and persisting messages. ReplayMessenger also depends upon the SpiFlashLogger and ConnectionManager libraries.
Parameter | Type | Required? | Description |
---|---|---|---|
spiFlashLogger | A SpiFlashLogger instance | Yes | Used to store messages in the imp’s SPI Flash. Must be instantiated from library version 2.2.0 or above |
connectionManager | A ConnectionManager instance | Yes | Used to check the connection state. Must be instantiated from library version 3.1.0 or above |
options | Table | No | Settings that override default behaviors. See ReplayMessenger Options, below, for details and defaults values |
Key | Data Type | Description |
---|---|---|
ackTimeout | Integer | The time after which an unacknowledged message is considered to have failed to be sent. Default: 10s |
maxRate | Integer | The maximum message send rate, ie. the maximum number of messages the library allows to be sent in a second. If the application exceeds the limit, the onFail callback is triggered. Default: 10 messages/s Note Please don’t change this value unless absolutely necessary |
firstMsgId | Integer | An initial value for the auto-incrementing message ID. When the default is used, the library will use persisted messages to determine a safe starting value. When overriding the default, the application must define this value in such a way that it will not interfere with the currently persisted messages. Default: 0, or value based on persisted message IDs |
resendLimit | Integer | The maximum number of messages to queue at any given time when replaying. Default: 20 |
debug | Boolean | A flag that enables or disables extended logging for debugging purposes. Default: false |
#require "Serializer.class.nut:1.0.0"
#require "SPIFlashLogger.device.lib.nut:2.2.0"
#require "ConnectionManager.lib.nut:3.1.1"
#require "Messenger.lib.nut:0.2.0"
#require "ReplayMessenger.device.lib.nut:0.2.0"
local sfl = SPIFlashLogger();
local cm = ConnectionManager({ "retryOnTimeout" : false });
local rm = ReplayMessenger(sfl, cm);
All methods documented in the Messenger Class Methods section above are also available to ReplayMessenger instances, with the exception of one minor update to the send() method and an additional method, confirmResend().
This method sends a named message to the partner and returns an instance of the Messenger.Message class.
Parameter | Type | Required? | Description |
---|---|---|---|
name | String | Yes | The name of the message to be sent |
data | Any serializable type | No | The data to be sent. It can be a basic Squirrel type (integer, float, boolean or string) or an array or table, but it must be serializable. Default: null |
importance | Integer | No | A message priority rating. See Message Priority, below. Default: RM_IMPORTANCE_LOW |
ackTimeout | Integer | No | An individual message timeout. Default: No timeout |
metadata | Any serializable type | No | Message metadata. It will not be sent to the partner. Default: null |
Every message created by this method has an additional property called importance which takes one of the three following values:
Every persisted message will be resent if this is confirmed by the application. Every persisted message is removed from the storage once it has been sent and acknowledged.
Messenger.Message class instance.
// Turn on the lights
rm.send("lights", true, RM_IMPORTANCE_HIGH);
This method sets a global callback that will be triggered when ReplayMessenger is about to replay a persisted message. This allows the application to decide whether the message should be resent or dropped (and thus removed from storage).
The callback function should return true
to confirm that the message should be resent, or false
to drop the message (and remove it from storage).
Parameter | Type | Required? | Description |
---|---|---|---|
confirmResendCallback | Function | Yes | Callback that is triggered when a message is replayed after it has been persisted. See The confirmResend Callback, below, for the function’s details |
Nothing.
Your confirmResend function should include the following parameters:
Parameter | Type | Required? | Description |
---|---|---|---|
message | A Messenger.Message instance | Yes | The Messenger.Message instance created and returned when the message was sent |
It should return true
to confirm that the message should be resent, or false
to cause the message to be dropped.
rm.confirmResend(function(message) {
// Resend all messages until they are acknowledged
return true;
});
rm.confirmResend(function(message) {
// Filter for messages sending readings
if (message.payload.name == "reading") {
local now = time();
local dataTimestamp = message.payload.data.ts;
// Drop messages that are older than 5min
if (now - dataTimestamp > 300) return false;
}
// Resend all other messages
return true;
});
Tests for the library are provided in the GitHub repo’s tests
directory.
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.1.0 | GitHub | Initial pre-release |
0.2.0 | GitHub | Initial pre-release |
These libraries are licensed under the MIT License.