---
title: Arduino Sensor Network
teaser: Write Arduino software for a low-power sensor board.
tags: arduino,iot,hardware
author: Tony DiPasquale
published_on: 2014-03-05
---

Previously, we looked at creating a [Low Power Custom Arduino Sensor Board]
for use in a sensor network. Now, let's look at writing the software for our
sensor network using that custom board. We will revisit the bathroom occupancy
sensor as an example.

[Low Power Custom Arduino Sensor Board]: https://thoughtbot.com/blog/low-power-custom-arduino-sensor-board

## Low Power Using Interrupts

Last time we looked at optimizing power by sleeping the Arduino and waking it
only when a door's state changed. To do this, we used the pin change interrupt
on pins 2 and 3. Unfortunately, we can only monitor change on those pins when
the processor is in idle mode which doesn't offer us max power savings. It
would be nice to put the processor into its highest power savings mode: deep
sleep. In deep sleep, those pins can still use interrupts but instead of
interrupting on change, it would interrupt on a single state of `HIGH` or
`LOW`. To use this type of interrupt we would have to remove the interrupt
after it fires and then add it back to trigger on the opposite state every time
the processor was woken. This is doable but there is a simpler solution.

The [Watchdog Timer (WDT)] is a timer that runs on microcontrollers as a
safety feature. Its purpose is to notify the processor if a fault or exception
occurs. Say for instance, that you have code in the main execution loop that
you know takes no more than 100ms to execute. You could set the WDT to
interrupt just over 100ms. Then at the end of every execution loop reset the
WDT. Now if the WDT interrupt is ever reached, you know that the WDT timed out
meaning the code took longer than expected to execute. You can then handle the
failure accordingly in the interrupt callback.

[Watchdog Timer (WDT)]: http://en.wikipedia.org/wiki/Watchdog_timer

We are going to use the WDT a little differently than its intended use. If we
set the WDT to interrupt every second, then we can put the processor into deep
sleep and it will wake up in second intervals. Every time it wakes up, we can
check the doors and transmit their state if it has changed. This may not be
ideal for optimal power savings but it doesn't cost much more power and it
allows us to have a more general application for sensing and reporting
anything. Our Arduino will wakeup every second, check some sensors, and then
report their state if they changed. Using this model, we can have the sensor
board be more than just a bathroom door detector. It could be placed around the
office and report temperature, humidity, brightness, motion, etc.

Let's create a new Arduino project and setup the WDT.

    // Import the interrupt library
    #include <avr/interrupt.h>

    volatile int __watch_dog_timer_flag = 1;

    // Define WDT interrupt callback
    ISR(WDT_vect)
    {
      __watch_dog_timer_flag = 1;
    }

    void setup()
    {
      // Disable processor reset on WDT time-out
      MCUSR &= ~(1<<WDRF);

      // Tell WDT we're going to change its prescaler
      WDTCSR |= (1<<WDCE);

      // Set prescaler to 1 second
      WDTCSR = 1 << WDP1 | 1 << WDP2;

      // Turn on the WDT
      WDTCSR |= (1 << WDIE);
    }

    void loop()
    {
      if (__watch_dog_timer_flag == 1) {
        __watch_dog_timer_flag = 0;
        // do things here ...
      }
    }

First, we create a flag variable that we use to know which interrupt was fired.
We use the `volatile` keyword to let the compiler know that this variable might
change at any time. This is important for variables being modified within
interrupt callbacks and the main execution loop. Next, we define the interrupt
callback using the `ISR`, [Interrupt Service Routine], function. We tell the
ISR which interrupt callback we're defining, `WDT_vect` is for the Watchdog
Timer Interrupt Vector. The only thing we need to do Inside the interrupt
callback is set the flag.

[Interrupt Service Routine]: http://en.wikipedia.org/wiki/Interrupt_handler

Next, we setup the WDT using some register bit manipulation. The `MCUSR`
register is the processor's status register and allows us to reset the
processor when the WDT times out. We don't want this to happen so we use the
bitwise `&` operator to set the `WDRF`, Watchdog Reset Flag, bit to `0`. Then,
we configure the WDT Control Register, `WDTCSR`. Setting the `WDCE` bit tells
the processor that we are going to change the timer prescaler.  Then, we set
the timer prescaler with the `WDP1` and `WDP2` bits so the WDT will time-out
around 1 second. Now, we can enable the WDT by setting the `WDIE` bit. You can
find out more about these registers in the [datasheet]. Finally, in the
execution loop, we check to see if the flag is set, meaning the WDT has
triggered. If it has triggered, we reset the flag and execute the application
specific code.

[datasheet]: http://www.atmel.com/Images/doc8161.pdf

Revisiting the [bathroom occupancy detector], the sensor board in charge of
monitoring the downstairs bathrooms has to sense the input from 2 reed switches
on the doors. We will use pins D2 and D3 for the reed switches. We also want to
activate the internal pull-up resistor which adds a resistor to power inside
the chip. This gives our door pins a default state if the door is not closed.

[bathroom occupancy detector]: https://thoughtbot.com/blog/arduino-bathroom-occupancy-detector

    byte leftDoorStatus = 0;
    byte rightDoorStatus = 0;

    void setup()
    {
      // WDT init ...

      pinMode(2, INPUT);
      digitalWrite(2, HIGH);

      pinMode(3, INPUT);
      digitalWrite(3, HIGH);
    }

    void loop()
    {
      if (__watch_dog_timer_flag == 1) {
        __watch_dog_timer_flag = 0;

        byte left = digitalRead(2);

        if (leftDoorStatus != left) {
          leftDoorStatus = left;
        }

        byte right = digitalRead(3);

        if (rightDoorStatus != right) {
          rightDoorStatus = right;
        }
      }
    }

Here, we added two global status variables. Then, we set the pins 2 and 3 as
`INPUT` and turn on their internal pull-up resistors using `digitalWrite(x,
HIGH);`. In the loop function, we check and compare the doors' status with the
global status. If the status has changed we set the global variable. Now, we
can use the [nRF24 board] to communicate these changes to the hub.

[nRF24 board]: http://www.amazon.com/nRF24L01-Wireless-Transceiver-Arduino-Compatible/dp/B00E594ZX0

    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>

    // ...

    void setup()
    {
      // ...

      Mirf.csnPin = 10;
      Mirf.cePin = 9;
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();
      Mirf.setRADDR((byte *)"bath1");
      Mirf.payload = 32;
      Mirf.config();
    }

Make sure to include the proper libraries. We can download the [Mirf] library
and place it into our Arduino libraries folder. Setup the Mirf by setting the
`csnPin` and `cePin`, which are on pins 10 and 9 respectively, telling it to
use the hardware SPI, setting the address as `bath1`, and the payload as 32
bytes. Now in the execution loop we can transmit the data when a status has
changed.

[Mirf]: http://playground.arduino.cc/InterfacingWithHardware/Nrf24L01#.UwOn6UJdXig

    const String rightDoorID = "E1MLhY2yhH";
    const String leftDoorID = "bEOr5qhMHY";

    void loop()
    {
      if (__watch_dog_timer_flag == 1) {
        __watch_dog_timer_flag = 0;

        byte left = digitalRead(2);

        if (leftDoorStatus != left) {
          leftDoorStatus = left;
          sendDataWithIDAndStatus(leftDoorID, leftDoorStatus);
        }

        byte right = digitalRead(3);

        if (rightDoorStatus != right) {
          rightDoorStatus = right;
          sendDataWithIDAndStatus(rightDoorID, rightDoorStatus);
        }
      }
    }

    void sendDataWithIDAndStatus(String id, byte status)
    {
      byte doorStatus[12];
      id.getBytes(doorStatus, 11);
      doorStatus[11] = status;

      Mirf.setTADDR((byte *)"tbhub");
      Mirf.send(doorStatus);
      while(Mirf.isSending()) ;
      Mirf.powerDown();

      free(doorStatus);
    }

First, we add two IDs for our door sensors. These IDs correspond to their
respective ID in the cloud storage service we are using to store the data (more
on this later). When the status has changed we call
`sendDataWithIDAndStatus(id, status)` which combines the ID and status of the
door into a byte array and uses Mirf to transmit the array to the hub, `tbhub`.
We wait for transmission to finish and then tell the Mirf board to sleep.

The last thing we have to do is sleep the processor after the application code
has executed.

    #include <avr/power.h>
    #include <avr/sleep.h>

    void loop()
    {
      if (__watch_dog_timer_flag == 1) {
        __watch_dog_timer_flag = 0;

        // Application code ...

        enterSleepMode();
      }
    }

    void enterSleepMode()
    {
      set_sleep_mode(SLEEP_MODE_PWR_DOWN);
      sleep_enable();
      sleep_mode();

      sleep_disable();
      power_all_enable();
    }

Call `enterSleepMode()` after our application code in the main loop. This
function sets and enables the sleep mode then tells the processor to enter
sleep mode with `sleep_mode()`. When the processor wakes up from the
interrupt, code execution begins where it left off, disabling sleep and
turning on power for all peripherals.

## A Library to Simplify

We have provided an [Arduino library] that we can use to make this much
simpler. Add the `thoughtbot` directory into the Arduino libraries directory
and restart the Arduino software.

[Arduino library]: https://github.com/thoughtbot/arduino/tree/master/libraries

The library provides the `TBClient` class and a wrapper file `TBWrapper`.

The TBClient class abstracts the communication to the hub. Initialize a client
class by calling `TBClient client((byte *)"cname", 32);`. This will initialize
the Mirf software. The first parameter is the name of the client device. This
name will be used to receive transmissions meant just for this board. It's very
important that this name be 5 characters long or else the wireless library
won't work. The second parameter is the size of the transmission payload in
bytes. The max is 32 bytes which we set above even though we might not use all
that. TBClient also provides a `sendData(byte *address, byte *data)` function
for transmitting. It takes in the 5 character address of the device to transmit
to and the byte array of data to transmit.

TBWrapper is a file that wraps the standard Arduino `setup()` and `loop()`
functions to setup the WDT and put the processor in deep sleep. If we wanted custom
sleep and interrupt logic other than what we did above, we could remove this file.
Keeping this file will simplify the code so that we can concern ourselves only
with our application. With TBWrapper, use `clientSetup()` and `clientLoop()`
instead of `setup()` and `loop()` respectively. Inside `clientSetup()`, we can setup
any pins or modules we need for our sensing application. `clientLoop()` will
be executed about every second when the processor comes out of sleep. In here,
we should check our sensors and transmit their data if any have changed.

To use this library, create a new file with the Arduino software. In the menu,
under `Sketch` select `Import Library...` and pick `thoughtbot`.  Also import
the Mirf and SPI libraries. The final code after refactoring the above
code to use the libraries will look like this:

    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <MirfSpiDriver.h>

    #include <TBClient.h>
    #include <TBWrapper.h>

    const String rightDoorID = "E1MLhY2yhH";
    const String leftDoorID = "bEOr5qhMHY";

    TBClient client((byte *) "bath1", 32);

    byte leftDoorStatus = 0;
    byte rightDoorStatus = 0;

    void clientSetup()
    {
      pinMode(2, INPUT);
      digitalWrite(2, HIGH);

      pinMode(3, INPUT);
      digitalWrite(3, HIGH);
    }

    void clientLoop()
    {
      byte left = digitalRead(2);

      if (leftDoorStatus != left) {
        leftDoorStatus = left;
        sendDataWithIDAndStatus(leftDoorID, leftDoorStatus);
      }

      byte right = digitalRead(3);

      if (rightDoorStatus != right) {
        rightDoorStatus = right;
        sendDataWithIDAndStatus(rightDoorID, rightDoorStatus);
      }
    }

    void sendDataWithIDAndStatus(String id, byte status)
    {
      byte doorStatus[12];
      id.getBytes(doorStatus, 11);
      doorStatus[11] = status;
      client.sendData((byte *)"tbhub", (byte *)doorStatus);
      free(doorStatus);
    }

## The Hub

The hub, our [Arduino Yún], also has an nRF24 board attached and is receiving
the transmissions. It will post the sensor data to an internet service so we
can access that data from anywhere. We decided to use [Parse] as the internet
service because of its ease to use and large data capacity for the free tier.

[Arduino Yún]: http://arduino.cc/en/Main/ArduinoBoardYun
[Parse]: https://parse.com/

Let's look at how we can receive data from our sensor board and post it to the
cloud.

    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <MirfSpiDriver.h>

    #include <Bridge.h>
    #include <Process.h>

    void setup()
    {
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();

      Mirf.setRADDR((byte *) "tbhub");
      Mirf.payload = 32;

      Mirf.config();

      Bridge.begin();
    }

Here, we are setting up the Mirf library by giving it the name of our device,
`tbhub`, and the payload size, 32 bytes.  The `Bridge.begin();` call is setting
up the Arduino to be able to talk to the on-board Linux computer. Now we can
monitor for received data in the `loop()` function.

    void loop()
    {
      if (Mirf.dataReady()) {
        byte data[32];
        Mirf.getData((byte *) &data);
        String id = String((char *)data);
        sendData(id, data[11]);
      }
    }

When we receive data, we extract the sensor ID from the first bytes of the
string and send it along with the status byte to the Parse API.

    void sendData(String id, byte value)
    {
      Process curl;
      curl.begin("curl");
      curl.addParameter("-k");
      curl.addParameter("-X");
      curl.addParameter("POST");
      curl.addParameter("-H");
      curl.addParameter("X-Parse-Application-Id:YOUR-APPLICATION-ID");
      curl.addParameter("-H");
      curl.addParameter("X-Parse-REST-API-Key:YOUR-PARSE-API-KEY");
      curl.addParameter("-H");
      curl.addParameter("Content-Type:application/json");
      curl.addParameter("-d");

      String data = "{\"sensor\":{\"__type\":\"Pointer\",\"className\":\"Sensor\",\"objectId\":\"";
      data += id;
      data += "\"},\"value\":";
      data += value;
      data += "}";

      curl.addParameter(data);
      curl.addParameter("https://api.parse.com/1/classes/SensorValue");
      curl.run();
    }

`Process` is a class available on the Arduino Yún that sends a command to the
Linux computer for execution. Unfortunately, the string parameter in the
`addParameter(String)` method, must not contain any spaces, leaving the code to
look messy and repetitive. We are using `curl` to `POST` the new sensor status
to a Parse object called `SensorValue`. The string identifiers for each door on
the sensor board correspond to a `Sensor` object on Parse. Above, we are
creating a new `SensorValue` object in Parse that points to the appropriate
`Sensor` object.

This code and the code for the client can be found in the [GitHub repository].

[GitHub repository]: https://github.com/thoughtbot/arduino/tree/master/examples

## Conclusion

Now we have the code to make our [sensor board] run, and with that we can
start sensing and reporting anything we can imagine. The hardware and software
is all [open source], so make a sensor network at your office or home and
report back to us with your awesome creations!

[sensor board]: https://thoughtbot.com/blog/low-power-custom-arduino-sensor-board
[open source]: https://github.com/thoughtbot/arduino
