In this example you will learn how to use a web service not just to save the data your device generates — as we did in the Plotly example — but to retrieve data and make use of it. In this case, we will be acquiring weather information for a specified location and we’ll use it to indicate the current temperature at that location by setting the imp003/LBWA1ZV1CD EVB’s red-green-blue (RGB) multi-color LED.
If you are having problems getting the EVB online, please consult the BlinkUp Troubleshooting Guide.
A successful BlinkUp operation is indicated by a slowly flashing green LED on the EVB. The EVB will be listed in impCentral’s ‘My Development Devices’ list (accessed via the devices menu in the black bar at the top). The number presented in impCentral is the EVB’s unique Device ID. You can, if you wish, give it a more friendly name as follows:
One of the most important features of agents is that they allow you to easily integrate third-party web services into your project. We touched on this ability in an earlier example, but now we’re going to explore it in more depth. The agent’s http object allows you to construct and send arbitrary HTTP requests, and so tailor them to the API requirements of almost any web service.
In this example, the agent sends a simple request to Weather Underground, a weather service API. Weather Underground responds with the current weather conditions at a provided location, and the agent code extracts the temperature from that data. It then calculates what mix of red, green and blue hues the EVB’s multi-color LED needs to be programmed with to indicate the local ambient temperature. We’ll use the following chart as the basis for our temperature-to-color calculation:
It is generally good practice to use the agent to perform calculations rather than the device because the less work the device does, the less power it consumes. This is an important consideration for battery powered devices in particular.
Once we have the right color, appropriate data is sent to the EVB, which then sets its LED accordingly. Here is the flow of data between the example’s constituents, with the agent acting as mediator between device and web service:
The key part of the example code is its getConditions() function. This builds the request to be sent to Weather Underground and parses the response to extract the current temperature.
Before getConditions() starts building and sending the HTTP request, it handles some update scheduling:
// Prevent double-scheduled updates (if device and agent restart)
if (updatehandle) { imp.cancelwakeup(updatehandle); }
// schedule next update
updatehandle = imp.wakeup(UPDATEINTERVAL, getConditions);
The imp API method imp.wakeup() is used to set a timer and specify a function that will be called when the timer fires. When called, in line 4 above, the method returns a reference to the new active timer object, which is stored in the variable updatehandle
. This allows us to cancel the timer before it fires, and this we do in line 2 above to ensure we only ever have one timer in operation at any given time. We use the imp.cancelwakeup() API method.
Why might there be multiple timers in operation? Both device and agent are programmed to request an update when they restart — the agent to make the first of its periodic data requests, the device to get the information it needs to initialize its LED. If both were to restart at the very nearly the same time, this would cause getConditions() to called twice in rapid succession — and thus schedule two updates after the value specified by the constant UPDATEINTERVAL.
To avoid this, when getConditions() is called, the first thing it does is check for an existing timer in the queue. If it finds one, it shuts the timer down. This ensures only the latest of the two close calls results in the the function being called again. Just like we saw in the ’Hello World’ example, this is an instance of the imp’s event-based programming style.
Housekeeping done, getConditions() builds and sends an HTTP GET request to Weather Underground:
// request the current conditions for our zipcode
local url = format("%s/%s/conditions/q/%s.json", WUNDERGROUND_URL, WUNDERGROUND_KEY, ZIPCODE);
local res = http.get(url, {}).sendsync();
The first line here uses the base URL, your Weather Underground API key and the zip code to build a request URL in the format that Weather Underground’s server expects. The second line creates an HTTP GET request object with http.get(), and this object is sent using the imp API’s httprequest.sendsync() method.
This call is synchronous; it blocks until the request returns. In most cases, the asynchronous equivalent, httprequest.sendasync(), is recommended to prevent this blocking behavior. But in this example there are no other tasks the agent needs to perform so the blocking call is fine.
Because we used the synchronous send, we receive the response to our Weather Underground request as a return value. Responses to asynchronous sends are handled with callback functions triggered when the receipt event takes place.
Now we’re ready to parse the return value to get the weather.
if (res.statuscode != 200)
{
server.log("Wunderground error: " + res.statuscode + " => " + res.body);
}
else
{
// response parsing is in a try-catch to prevent runtime errors if our request does not return valid JSON
try
{
local response = http.jsondecode(res.body);
local weather = response.current_observation;
// Calculate the color that corresponds to the current temperature, and send it to the device
device.send("setcolor", tempToColor(weather.temp_c));
}
catch (e)
{
throw "Wunderground error: " + e;
}
}
First, we check the response code for our request; if our request wasn’t successful, there’s no sense in attempting to parse the response. Once we know the response code indicates a successful request, the agent attempts to parse the JSON returned by the Weather Underground service: we use the imp API method http.jsondecode(). The HTTP response object, res
, is structured data; we use res.body
to zero in on the body of the response.
Decoding the response with http.jsondecode
yields a Squirrel table — a collection object comprises key-value pairs, which Squirrel calls ’slots’. The values in this table are dictated by Weather Underground’s API, so we can refer to Weather Underground’s API docs to see how to navigate this table. The parameter we’re looking for is response.current_observation.temp_c
, which holds the current temperature in degrees Celsius.
We now use our tempToColor() function to turn this temperature into a color, using the chart at the beginning of this example as a guide:
local color = {};
// scale red proportionally to temp, from 20 to 40 C
color.red <- (255.0/20.0) * (temp - 20.0);
if (color.red < 0) { color.red = 0; }
if (color.red > 255) { color.red = 255; }
// scale green proportionally to temp from 1 to 20, inversely from 20 to 39
color.green <- 255 - ((255.0 / 19.0) * (math.abs(temp - 19.0)));
if (color.green < 0) { color.green = 0; }
if (color.green > 255) { color.green = 255; }
// scale blue inversely to temp, from 0 to 20 C
color.blue <- 255 - (255.0/20.0) * (temp);
if (color.blue < 0) { color.blue = 0; }
if (color.blue > 255) { color.blue = 255; }
server.log(format("Temp: %0.2f -> [%d,%d,%d]", temp, color.red, color.green, color.blue));
return color;
This function returns a table with three keys: ‘red’, ‘green’, and ‘blue’, each paired with a value between 0 and 255. The table — an object in its own right — is created at the start: the braces mark the variable color
as a table. As we can see at the end of getConditions(), this table is sent to the device:
device.send("setcolor", tempToColor(weather.temp_c));
The agent-only device.send() call sends an object to the device. It takes two parameters, the first of which is a message identification string. The second parameter is the data we want to send to the device, in this case the value returned by tempToColor(). The device code must include code to indicate its interest in messages of this kind and this is done using the device-only method agent.on(). It takes as parameters the same message identification string we used above and a function with a single parameter of its own to take the data handed over by the agent and process it.
The device is even simpler than the agent; it’s entire job is just to receive new tables with red, green and blue values, and to set the LED appropriately. First, the pins are assigned and configured, one for each of the three one-color LEDs that make up the multi-color LED on the EVB.
// pin assignments
redled <- hardware.pinE;
greenled <- hardware.pinF;
blueled <- hardware.pinK;
redled.configure(PWM_OUT, 1.0/PWMFREQ, 1.0);
greenled.configure(PWM_OUT, 1.0/PWMFREQ, 1.0);
blueled.configure(PWM_OUT, 1.0/PWMFREQ, 1.0);
Each pin is configured as a pulse-width modulation (PWM) output, indicated by the constant PWM_OUT. PWM allows us to control the apparent brightness of the LED’s red, green and blue emitters by varying the amount of time each is turned on relative to the time it is turned off. Human persistence of vision ensures the LED doesn’t flash or flicker but appears to grow bright or dim as this ’duty cycle’ is altered.
The second parameter to pin.configure() is the PWM period, here the inverse of a frequency value. The third parameter is the initial duty cycle; setting the duty cycle to 1.0 (100%) means the pin will always be high, and the LED always off. Later in the code, we use pin.write() to change the duty cycle of the pin, thus changing the apparent brightness of the LED.
Next, a function is defined to take in a color (as a table, with ‘red’, ‘green’, and ‘blue’ keys, to match the data coming from the agent) and set the pin values to tune the LED to that color:
function setColor(color)
{
// pins sink, rather than source, current (active-low)
redled.write((255.0 - color.red) / 255.0);
greenled.write((255.0 - color.green) / 255.0);
blueled.write((255.0 - color.blue) / 255.0);
}
Because the pins act as the current sink for each LED, setting the pin low (rather than high) turns the LED on. Therefore, to increase the duty cycle of the LED, a lower value is written to the pin.
Now that we have a function to change the color, we can register it as a callback for the "setcolor"
message the agent will send.
// register a callback for the "newcolor" event from the agent
agent.on("setcolor", setColor);
As we saw in the agent code section above, this code registers the function setColor() with the imp OS as the one to call if and when messages of type "setcolor"
arrive. When setColor() is called in response to such events, it is passed the the message’s data payload and, as we saw above, extracts the red, green and blue color component values from the supplied table.
You may have noticed that neither the sending method nor the receiving method and its callback specify the type of data being passed. Squirrel doesn’t require variables be declared as one type or another. Instead it sets them on assignment, and is happy to modify their type should they be later assigned a value of a different kind.
In the next section, we’ll use the technique learned above and in the previous sections to build a simple connected security system based on the imp003/LBWA1ZV1CD EVB.