Serving forms from agents

Anyone seeking a little guidance on serving HTML forms from agents and dealing with results might like to check out our latest blog post.

Very useful and handy explanation on forms. Thanks.

I thought to share this tricky problem I’ve just solved as it relates to getting data from a device back to an html form based on when a user clicks on a form button. I found this quite problematic to solve due to async callback functions between device and agent.

I am now wondering whether others have found a different / possibly simpler / better method.

The key challenge I found was finding a way to wait for the data on agent side once the device sent it. I finally solved this by using javascript on the html page and then using a double GET (or POST) request with the second request delayed using the “setTimeout” method.

This essentially frees up the agent and the device to carry on handling events as per normal while the user / client side (i.e. html page) handles the delay to wait to receive data from device using this double GET request method).

In my case I was requesting the device to scan for wifi networks and return this data via an array. Generally (rather the Imp specific) scanning for wifi details is not immediate.

Anyway, so method (this is a rough outline) is as follows - welcome comment:

  1. User clicks button on form and this triggers a javascript function call based on onclick=functionName() method.

  2. Then within functionName() script I make a double GET request using the following:

$.get(document.URL, {--- place a keyword here which will be used by Agent httpHandler function to request device to get data --- e.g. triggerDevice ---}, (function(data) {setTimeout(function() {$.get(document.URL, {--- place another keyword here which will be used to get the data from agent once the agent has received the data --- e.g. getAgentData ---}, (function(data) { --- now place your javascript output functionality to display data back on html page ---); })) .fail(function(data) {alert('ERROR: ' + data.responseText);}); }, 1000); })) .fail(function(data) {alert('ERROR: ' + data.responseText);});

Note document.URL refers to agent URL.

Note that I used a 1000 millisecond delay to wait before requesting data from agent. Unfortunately this is guess work and does not guarantee data will be ready in time. You could of course repeat function again should data not be ready.

  1. Then within the Agent httpHandler function I use the following which looks out for 2 GET requests:

`try {
if (“triggerDevice” in req.query) {
If a match I first update this global variable which I named aDate using time(); // this value is used for checking that the data matches when data comes back via second asyn call.
Then I send a device.send(“scanSSID”, aDate); request to the device
Then I return back to html/javascript using resp.send(200, “OK”);
}

// Then handler to look after second function call
if ("getAgentData " in req.query) { check global data for aDate and array contain data received by device. I then create a json object to return the data back to html page
htmlTable = {scArr=scanArray, scTime=aDate}; // See point 5 below which updates the scanArray and the aDate global data
jvars = http.jsonencode(htmlTable);}

I then return back to html / javascript using resp.send(200, jvars);
}`

  1. Within Device code I use:
    `agent.on(“scanSSID”, getSSIDdetails);

function getSSIDdetails(refDate) {
local wlans = imp.scanwifinetworks();
local SSIDS = [];
foreach (hotspot in wlans)
{
SSIDS.append(hotspot.ssid); // you can add signal strength and wifi channel data here too if you wish
}
local sensorpoint = {“ssids”:SSIDS, “refDate”:refDate};
agent.send(“ssidData”, sensorpoint);
}
5. Then within Agent code I use:device.on(“ssidData”, SSID_Handler);

function SSID_Handler(datapoint) {
aDate= datapoint.refDate; // use this as a reference against original data timestamp for checking
scanArray.extend(datapoint.ssids); // The scanArray is a global array so can be used by other functions. This just updates the data array
}`

Looks like there is an alternative method after all…

https://discourse.electricimp.com/discussion/3400/device-as-part-of-httprequest-and-response-arduino

Did not realise that app/html form-agent link stays open until response received and that this response does not have to be within the function attached to the http.onrequest(function) method… HOWEVER, as I have now learnt from applying above example, one must first save the response in a global variable (e.g. object or array) for later reference, otherwise the function as referenced below will not work…

Looks like you can use http://electricimp.com/docs/api/httpresponse/send/ to handle the response back to the html form, which can be within another agent function attached to a device.on(function) method.

(UPDATE) However, I found the description and the code in the above reference slightly misleading (or I simply misunderstood it) in that the “httpresponse” part as described in the httpresponse.send(integer, string) function was not a predefined global object that could be referenced anywhere.

Thus if you wanted to use “httpresponse” as a reference elsewhere in your agent code you would have to declare this as a global using something like this: httpresponse <- {}; // create as an object (or httpresponse <- [] for array)

Then within the function “handler(request, response)” as described in the example you would then add this line of code: httpresponse = {response};

Then when you require to send a 200 response back to form with a text string “OK” or other json data you would send:

httpresponse.response.send(200, “OK”);

This then frees you up to grab data from Imp and place inside a form (note this may require javascript to avoid a screen refresh) upon user request through html page. Alternatively instead of “setpage()” function, as per blog’s description, I can now reference another function altogether which will display the new data on an html update post button press, which has come from my Imp device.

This looks so much simpler… unless of course I still have got this wrong.