Skip to content

Instantly share code, notes, and snippets.

@ElectricImpSampleCode
Last active May 12, 2022 13:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ElectricImpSampleCode/a77000c6a33e61c2e402e7eff229ac4a to your computer and use it in GitHub Desktop.
Save ElectricImpSampleCode/a77000c6a33e61c2e402e7eff229ac4a to your computer and use it in GitHub Desktop.
Effective Internet <-> Agent <-> Device communications with the Rocky and MessageManager libraries
/*
* Load the libraries that we need:
* Rocky to serve the API,
* MessageManager to handle the agent-device interaction
*/
#require "Rocky.agent.lib.nut:3.0.0"
#require "MessageManager.lib.nut:2.4.0"
/*
* Instantiate instances of Rocky and MessageManager
* NOTE Rocky is a singleton
*/
api <- Rocky.init();
mm <- MessageManager();
/*
* This is the handler called in response to replies
* made to "request.reading" that the agent sends
*/
function readingRequestReplyHandler(message, response) {
// Retrieve the stored Rocky context
local rockyContext = message.metadata;
// Assemble the table of data that we will return
// to the remote caller
local returnedData = {};
if (typeof response == "array") {
returnedData.readings <- response;
} else {
returnedData.reading <- response;
}
// Use the Rocky context to respond with JSON
rockyContext.send(200, http.jsonencode(returnedData));
}
/*
* Register a Rocky middleware function which we'll chain to
* all incoming requests with Rocky's 'use()' method.
* It also demonstrates some of the request info accessible
* via a Rocky context.
* NOTE Make sure you call the supplied 'next()' function to
* maintain the chain of calls
*/
api.use(function(aRockyContext, next) {
server.log("API received a " + aRockyContext.req.method.toupper() + " request at " + time() + " from " + aRockyContext.getHeader("x-forwarded-for") + " at path " + aRockyContext.req.path.tolower());
next();});
/*
* Register a Rocky handler for incoming GET requests
* made to /readings.
* NOTE Rocky provides a lot of flexibility for defining
* endpoint paths, including regular expressions
*/
api.get("/readings", function(theRockyContext) {
// Use a try...catch to trap JSON decode errors
try {
// Decode the incoming request body
local requestData = http.jsondecode(theRockyContext.req.rawbody);
// Check it's the right kind of request
if ("sensor" in requestData) {
// The request contains the key field, sensor,
// so use MessageManager to contact the device:
// We pass:
// 1. The message name
// 2. The data payload
// 3. A table of event handlers
// 4. A timeout
// 5. Message metadata
mm.send("request.reading",
requestData,
{"onReply": readingRequestReplyHandler},
30,
theRockyContext
);
} else {
theRockyContext.send(500, "Request JSON lacks a \'sensor\' field");
}
} catch (error) {
// The JSON decode failed so report the error
// NOTE We provide a single line of text, but this could
// be a complex JSON report - it's up to you
theRockyContext.send(500, "Bad data posted: " + error);
}
});
/*
* Load the libraries that we need:
* HTS221 to manage the board's thermal sensor
* MessageManager to handle the agent-device interaction
*/
#require "HTS221.device.lib.nut:2.0.2"
#require "MessageManager.lib.nut:2.4.0"
/*
* Instantiate a MessageManager instance
*/
local mm = MessageManager();
/*
* Configure the I2C bus, then instantiate
* and configure the thermal sensor
*/
hardware.i2cLM.configure(CLOCK_SPEED_400_KHZ);
sensor <- HTS221(hardware.i2cLM);
sensor.setMode(HTS221_MODE.ONE_SHOT);
/*
* Register a handler for incoming "request.reading" messages
* NOTE Here we're using an inline function definition
* cf. 'readingRequestReplyHandler()' in the agent code
*/
mm.on( "request.reading",
function(message, reply) {
// Are we beging asked to read from the thermal sensor?
if (message.data.sensor == "temperature") {
// Get the number of readings requested, but use a default (1) if
// the 'readings' field was not included in the request
local numberOfReadings = "readings" in message.data ? message.data.readings : 1;
// Set up a store for multiple readings (may not be needed)
local readings = [];
// Get the required number of readings, pausing one second
// between each.
// NOTE Readings are received asynchronously through the callback
// passed into 'sensor.read()'. We bind the callback to the
// environment 'this' to ensure surrounding local variables,
// eg. 'numberOfReadings' are in the scope of the callback
for (local i = 0 ; i < numberOfReadings ; i ++) {
sensor.read(function(reading) {
if (!("error" in reading)) {
// No errors encountered while taking the reading so
// store the reading (if we want several) or return the
// single requested reading
if (numberOfReadings > 1) {
readings.append(reading.temperature);
if (readings.len() == numberOfReadings) {
// We've got all the requested readings so return them
reply(readings);
}
} else {
// Return the single reading requested
reply(reading.temperature);
}
}
}.bindenv(this));
imp.sleep(1.0);
}
return;
}
// No other sensor type supported... yet
// For now, reply with an invalid reading
reply(999.99);
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment