Http response

Hey guys, looking for some help. Pretty new to imp, but I’m a seasoned iOS developer. Just testing the waters and I have a basic app turning an LED ON or OFF and it’s working fine.

Looking for some help, I want to query a pin and return the status to my iOS app.

I wrote the iOS app Pitchfork and there is some Imp code on my github page that will likely be helpful in doing what you would like. Here is sample code that I use with the Control Panel.

i’m a pretty amatuer programmer, and if anyone such as yourself would be interested in helping to develop Pitchfork or apps like it for use with the Electric Imp, I would be quite happy to work with them. There are a lot of updates that I would like to make to it, but just don’t have the time.

Pitchfork has been downloaded 861 times. It would be pretty cool to know what folks are doing with it… perhaps someone will read this and share. :slight_smile:

This is something I’m struggling with - well, I mean to investigate thoroughly, but have only looked at in a cursory fashion so far. That is, how to get the Agent to return useful data back to the phone in response to a basic Agent URL query, ie. keep the link alive while I get information from the Device, package it up and respond to the query with it.

Here we go:

`function request_handler(request, response)
{
if (“qry” in request.query)
{
device.send(“ts.clock.switch.qresponse”, 1);
// response.send(200, “OK”);
response_out = response; // Save the response to send later
}
}

function send_mode(value)
{
local string = “24”;
if (value == 12) string = “12”;
response_out.send(200, string);
}

response_out <- null;
http.onrequest(request_handler);
device.on(“ts.clock.mode.resp”, send_mode);`

If the http request handler gets the right type of query, it requests a value from the imp. The imp replies, triggering function set_mode() which appends the data to the existing httpresponse and returns it to the iOS app.

@Diaonic & @smittytone - you two want to look at this thread.

The general principal looks something like this:

  1. When a request comes in, store the HTTPResponse object it in a table with some kind of key you can reference later
  2. Send a message from the agent to the device to get the data (and include the response key)
  3. Send a message back to the agent that includes the data you’re looking for, plus the key.
  4. Use the key to retrieve the response, and respond with the data.

I’m having trouble with this still, anyone have a full example of this working. With Agent and Device code?

Still trying to work through this.

If I have a table assigned in my agent code that looks like:

Led
{
state=0;
}

When I’m accessing that table from the device side via Led.state, is this just a pointer?

Can I literally change the value by Led.state=1;
?

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];

Sorry for the long post - hopefully the description makes sense. There’s lots of places to optimize / cleanup this code… but I tried to code in a way that makes it as clear as possible in terms of what’s going on.

Let me know if you have questions!

Working through this now, thank you very much. This is just what I needed to get me kicked started into this platform.