Skip to main content

Display Local Weather Conditions

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.

Peripherals Used

  • RGB LED

APIs Used

Setup

Create a Developer Account and add your EVB to impCentral™

  1. Create a free Developer Account to access Electric Imp impCentral and log in.
  2. Download the Electric Imp app from the App Store or Google Play store on your iOS or Android device, and log in using your Developer Account credentials.
  3. On the Connect an Imp page, select your WiFi network.
    • The imp only operates using 2.4GHz 802.11b, g or n. 5GHz networks are not supported.
  4. Use BlinkUp™ to configure your EVB for Internet access. Power up the EVB then press Send BlinkUp in the Electric Imp app. As soon as you hear the countdown tones, place your tablet or phone’s screen against the EVB’s imp003/LBWA1ZV1CD breakout board until the screen stops flashing and you hear a second set of tones.

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:

  1. Click ‘Settings’ in the ‘MANAGE’ column.
  2. In the ‘Device Settings’ panel, enter a new name, eg. ‘LBWA1ZV1CD EVB’, ‘imp003 EVB’ or ‘My EVB’, and click ‘Update’.

Create a New Product and Device Group

  1. Click on your account name in the grey bar at the top of the window to view a list of your Products — this will be empty initially. If it isn’t, select the “EVB” Product, click ‘Create New Device Group’, and go to Step 4.
  2. Click ‘Create New Product’.
  3. Enter a name for the Product: “EVB”.
  4. Enter a name for the first Device Group: “Weather”.

Add Code

  1. Copy the device and agent code from our GitHub repo and paste it into the impCentral’s code editor ‘Device Code’ and ‘Agent Code’ panes, respectively.
  2. Visit the Weather Underground website to create an account and obtain your API key.
  3. Enter your API key in the marked place in the agent code: look for the constant WUNDERGROUND_URL.
  4. Enter your zip code where you see the constant ZIPCODE.
  5. If you’re not based in the US, enter 94022 — you’ll see the temperature at the Electric Imp HQ.
  6. Click ‘Build and Force Restart’ to transfer the device code to the imp003/LBWA1ZV1CD and the agent code to Electric Imp impCloud™, and to run them.

What’s Going On

imp003 EVB weather colors cool

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.

Weather Underground

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:

Color gradient

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:

Application architecture

What the Agent Does

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.

What the Device does

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.