Ok, I just whipped up a quick example that you should be able to follow + modify to your needs. This example has an LED hooked up to pin1, and a “temperature sensor” that always returns a nice 20.0 degrees Celsius (because I didn’t feel like including the NTC thermistor code).
The agent url has the following end points:
Turn LED Off: /led?state=0
Turn LED On: /led?state=1
Get LED State: /asyncdata/led
Get Temp: /asyncdata/temp
Here’s the code (explanation at the bottom):
Agent Code
`//agent code
const TIMEOUT = 15; // close hanging async requests after 15 seconds
responses <- {};
// create unique keys for responses table
function generateKey() {
local key = math.rand();
while (key in responses) key = math.rand();
return key.tostring();
}
function responseWatchdog() {
imp.wakeup(1, responseWatchdog);
local t = time();
foreach(key, response in responses) {
if (t - response.t > TIMEOUT) {
responses[key].resp.send(408, "Agent Timeout");
delete responses[key];
}
}
} responseWatchdog();
device.on(“asyncdata”, function(data) {
if (!(data.k in responses)) {
server.log(format(“response %s already timed-out.”, data.t));
return;
}
responses[data.k].resp.send(200, http.jsonencode(data.d));
delete responses[data.k];
});
http.onrequest(function(req, resp) {
try {
local path = req.path.tolower();
// if user sends response to /asyncdata/...
if (path.find("/asyncdata") == 0) {
// generate key, and store the response object
local responseKey = generateKey();
responses[responseKey] <- { resp = resp, t = time() };
// this is what we're asking the agent for
local requestedData = "unknown";
// if they want the LED state
if (path == "/asyncdata/led" || path == "/asyncdata/led/") requestedData = "led";
// request asyncdata from the device:
// k is the response key - we'll use this in the response
// r is what we're looking for from the device
// d is an object that will store the data
device.send("asyncdata", { k = responseKey, r = requestedData, d = null });
return;
}
// if they're trying to set the LED
if (path == "/led" || path == "/led/") {
if (!("state" in req.query)) {
resp.send(400, "Missing query parameter: state");
return;
} else {
device.send("setled", req.query.state.tointeger());
resp.send(200, "OK");
}
}
// if they send a response anywhere else, just send a 200 back
resp.send(404, "Unknown");
} catch(ex) {
// if there was an error, send a 500 back
resp.send(500, "Internal Agent Error: " + ex);
}
});`
Device Code
`//device code
// configure LED
led <- hardware.pin1;
led.configure(DIGITAL_OUT);
led.write(0);
function getTemp() {
return 20.0; // it’s always 20 degrees celcius in Los Altos
}
function getLed() {
return led.read();
}
agent.on(“asyncdata”, function(data) {
// do stuff to get data you’re looking for
if (data.r == “temp”) data.d = { temp=getTemp() };
if (data.r == “led”) data.d = { led=getLed() };
else data.d = { error = "Unknown async request to device" };
agent.send("asyncdata", data);
});
agent.on(“setled”, function(state) {
led.write(state);
})`
What’s going on:
There are a couple utility functions and objects in the agent code:
-
responses: this is a table of response object that are currently fetching data from the device
-
generateKey(): this is a function which generates a unique key for the responses table
-
responseWatchdog(): this is a self-calling function that runs every second to see if there are any responses objects that have been hanging out for longer than we want. If there are, it sends back a 408, Timeout message. This is just in case there’s an uncaught error in the device and fail to send a message back to the agent.
Next, let’s look at the http.onrequest handler - the part we care about is the bits inside the if (path.find("/asyncdata") == 0) {:
`// if user sends response to /asyncdata/…
if (path.find("/asyncdata") == 0) {
// generate key, and store the response object
local responseKey = generateKey();
responses[responseKey] <- { resp = resp, t = time() };
// this is what we're asking the agent for
local requestedData = "unknown";
// if they want the LED state
if (path == "/asyncdata/led" || path == "/asyncdata/led/") requestedData = "led";
// if they want the temperature
if (path == "/asyncdata/temp" || path == "/asyncdata/temp/") requestedData = "temp";
// request asyncdata from the device:
// k is the response key - we'll use this in the response
// r is what we're looking for from the device
// d is an object that will store the data
device.send("asyncdata", { k = responseKey, r = requestedData, d = null });
return;
}`
If the path of the request starts with /asyncdata then we fire off an async request to the device. To do this, we generate a unique key for the response table, and then stash our response object in it. This allows us to keep a reference to it, while we go off and do other things:
local responseKey = generateKey(); responses[responseKey] <- { resp = resp, t = time() };
Next, we figure out what data (or task) the user is wanting to do - we do this in a really lazy way by exactly matching the path. If they sent the request to /asyncdata/led, we’re going to look for “led” data, if they sent the request to /asyncdata/temp we’re going to look for “temp” data.
local requestedData = "unknown"; if (path == "/asyncdata/led" || path == "/asyncdata/led/") requestedData = "led"; if (path == "/asyncdata/temp" || path == "/asyncdata/temp/") requestedData = "temp";
Finally, we fire off a request to the device with an object attached (the device will respond with the same object). The object has three bits of data:
k is the response key - which we’ll use once the device responds to fetch the response object and send the data passed back.
r is a key for what data we’re requesting. We’ll use it in the device code to figure out what code we should run.
d is where the device will put it’s data.
device.send("asyncdata", { k = responseKey, r = requestedData, d = null });
The device code does it’s thing, and sends back the same object with data populated.
`agent.on(“asyncdata”, function(data) {
// do stuff to get data you’re looking for
if (data.r == “temp”) data.d = { temp=getTemp() };
if (data.r == “led”) data.d = { led=getLed() };
else data.d = { error = "Unknown async request to device" };
agent.send("asyncdata", data);
});`
When we catch the response message from the device, we check to see if a response with the key exists in the responses table (it may have already returned with a timeout message):
device.on("asyncdata", function(data) { if (!(data.k in responses)) { server.log(format("response %s already timed-out.", data.t)); return; }
If the response does exists, we jsonencode the returned data, send the response, then delete the response object from our response table:
responses[data.k].resp.send(200, http.jsonencode(data.d)); delete responses[data.k];