Building a Web Of Things REST-API on an Arduino MKR1000 with PlatformIO

This short blog post introduces to programming a HTTP+JSON REST API for embedded devices, in this case the MKR1000 from Arduino. Open APIs are becoming more and more relevant to interconnect IoT devices, and REST as an architectural style is one valid way to do that. Instead of diving into the details of HTTP and JSON libraries on Arduino (which are definitely interesting topics for embedded developers), this step by step tutorial shows how to use THNG:STRUCTION to model such an API, generate code for a REST implementation, and actually use it. Let’s get started.

Thngstruction.online-arduino-mkr100-bme280

Prerequisites

As a basis we’re using

These steps can also be applied to an Arduino MEGA and the WiFi101 Shield, possibly with minor adaptions also to ESP8266 boards.

Connect Sensors

We’re using the BME280 from Seeedstudio’s Grove Kit, which can be easily connected via I2C. Using the Grove Connector:

  • red cable goes to VCC,
  • black cable to GND,
  • yellow is SCL (Pin 12),
  • white is SDA (Pin 11) on the MKR100. (See pinout here). For other boards, please check the reference documentation to find the right pins.

Use Case

Although the BME280 delivery three environmental values, we’d like to focus on humidity only to make this tutorial more to the point. The Web Of Things API/Thing Description contains three major elements that apply for interactions with a WoT device. If you want to know more about WoT and the architecture behind, you might want to check out this post for a short intro. The interactions are Properties, Actions and Events. As such, we're using ..

  • Humidity as a property. It’s a percentage value in the range 0..100, and can be queried for.
  • An action that makes the on-board LED flash for a short interval.
  • An event that is triggered every time the humidity above a fixed threshold.

Creating the Web Thing Model

Open https://thngstruction.online/app in a separate tab or click „Get started“ on the main page. This will take you to the first step to „create a new Thing Description“. At this point, our Web Thing needs a name, i set it to „HumidityAlerter“. It’s a device of type „Thing“, which starts with a blank template. The Description is optional. 
Create thing description

We stored this Thing Description as a template! So you can also select it from the Box and click "Create", which will prefill all input fields, so you can directly follow along this tutorial.

Click „Next“, which takes you to the Code Generator selection. It shows available code generators and let’s you find a suitable one according the some filters. If you’re interested in generators for other pieces of Hardware or other frameworks, please let me know, you’ll find a button below the list. For this tutorial, i choose „Arduino-compatible JSON/REST-API, for use with WiFiServer and WiFi101“.

Choose Code Generation Module

Next up is „Properties“. Clicking on the round „+/Add“ button opens a new input tab. Mandatory inputs are the property’s name (Humidity) and a type (set to Number from the type tab, then Type=Integer). Other values are optional. Hit the right check button to save the property, it should look like so:

Properties Properties

Click on „Actions“. Actions are much simpler for the moment, click on „+“ to add a new action. All that is necessary here is the Action Name:

Actions

Click on „Events“ to save the action and move on to the next part: Events, which are simple as well. Add a single event named „HumidityAboveThreshold“. No further information necessary here.

Events

Hit „Generate“. On the next page you can see all information you modeled so far by clicking through the tab. If you need to change something you can jump back to the detail page and do so. Now it’s time to generate the code, tick the checkbox in the grey conditions section and hit „Generate!“.

If all goes well you should see a download page, which a table showing a bunch of created files. In line of „sketch.ino“, click on the download or view link. If you view the code, a modal pops up where you can copy the whole text to the clipboard, please to so.

Download Code

At THNG:STRUCTION’s download page, you can also download or view a README, with detailed information on the sketch as well as the license file (it’s CC0..) and the Thing Description according to Mozilla’s Web Thing API spec draft.

Transfer sketch to local environment

Let’s create a new project using PlatformIO. You can do this within a PIO-supported IDE, i’ll go with the command line:

$ pio init --board mkr1000USB --ide vscode

Copy&Paste the sketch code to the file src/main.ino.

The sketch already contains a lot of code regarding handling of HTTP requests and serialization/deserialization of JSON data. What it cannot know though is:

  • WiFi credentials
  • How to add libraries to your development environment
  • What to do exactly when e.g. humidity is queried

Let’s add this now!

First to the hardware basics. Connect the sensor to the board, the board to USB. Try to locate it:

$ pio device list
/dev/cu.usbmodem1421
--------------------
Hardware ID: USB VID:PID=2341:804E SER=3D4A8BFF514D4D335A202020FF101E0D LOCATION=20-2
Description: Arduino MKR1000

A bunch of libararies need to be included and installed (using pio lib install):

$ pio lib install ArduinoHttpServer
ArduinoHttpServer @ 5eb3878aed has been successfully installed!
$ pio lib install ArduinoJSON
ArduinoJson @ 5.13.1 has been successfully installed!
$ pio lib install WiFi101
WiFi101 @ 0.15.2 has been successfully installed!

Add custom logic to the sketch

For a BME280, an additional library is necessary. We’re using the one supplied by Seeedstudio, which can be found at https://github.com/Seeed-Studio/Grove_BME280:

$ cd lib && git clone https://github.com/Seeed-Studio/Grove_BME280.git

Add to the top of the sketch:

#include <Seeed_BME280.h>
BME280 bme280;

which will include the library and instantiate an object of the BME280 class.

Next, in the sketch, locate these constants (or search for string TODO):

/* TODO: place your WiFi credentials here */
const char* WIFI_SSID =         "YOURSSID";
const char* WIFI_PASSWORD =     "YOURPASSWORD";

Put in your own WiFi credentials. Make sure to use the same WiFi that your PC is connected to, otherwise connection might not be possible :) Scroll down to approx. line #120, that’s where the application functions are located. Find the snippet:

int app_get_Humidity() {
}

This function is called every time the Humidity property is requested via the API. We change it to:

int app_get_Humidity() {
 return bme280.getHumidity(); 
}

The BME280 needs initialization during setup. Locate the setup() function at the end of the sketch, add:

(…)

    // Additional setup code goes here
    if(!bme280.init()){
      Serial.println("Device error!");
    }
}

Compile & upload the sketch using:

$ pio run -t upload && pio device monitor

Opening the Serial monitor, it should come up with something like this:

$ WiFi connected
IP address: >>>>>IP IN YOUR NETWORK<<<<<<
signal strength (RSSI):-51

Great, we can ping the device and actually issue the first HTTP request. The following snippets show this using curl, but wget or a browser is good, too:

$ curl http://$YOURIP/wot
{"name":"HumidityAlerter","type":"thing","description":"Measures Humidity and compares it against defined thresholds.","properties":{"Humidity":{"type":"Integer","description":"Humidity in [%]"}},"actions":{"FlashLED":{"description":"Flashes the onboard LED."}},"events":{"HumidityAboveThreshold":{"description":"Triggered when humidity goes above certain threshold"}}}

That just returned the Thing Description in JSON. If you happen to have jq installed, try:


$ curl http://$YOURIP/wot | jq
{
  "name": "HumidityAlerter",
  "type": "thing",
  "description": "Measures Humidity and compares it against defined thresholds.",
  "properties": {
    "Humidity": {
      "type": "Integer",
      "description": "Humidity in [%]"
    }
  },
…
…

Where does the /wot come from in the URL? Search the sketch for this #define and change if you like:

#define URIPATH_PREFIX      "/wot"

Querying humidity is easy:

$ curl http://$YOURIP/wot/properties/Humidity
{"Humidity":42}

Success, there it is, the sensor’s humidity value, as JSON, queried via HTTP. Now, 42 can be everything, even the answer to life and everything, so how can we test this? Breathe against the sensor, the moisture will make the value go up. If you happen to be on Linux or MacOS, try

$ watch 'curl http://$YOURIP/wot/properties/Humidity'

which will execute the HTTP call every 2 seconds. Remember, URLs are case-sensitive, please make sure to have upper/lower case as specified in the model.

Triggering an event

Next: Firing an event every time humidity goes above 70 percent. For this we need to locate the function app_loop() which is called regularly by the sketch’s loop(). In app_loop(), we measure humidity as well, but now in every call. We need a counter so that humidity is read every 100.000 calls, then compared to the threshold. If condition is met, an event is triggered using wot_events_push, a helper function from the sketch.

Change app_loop() as follows (ca. around lines 120..)

int query_counter = 0;

void app_loop() {
  if (++query_counter >= 100000) {
    query_counter = 0;
    if (bme280.getHumidity() > 70) {
      wot_events_push("HumidityAboveThreshold", "", "");
    }
  }
}

as a first, very simple version of implementing this. After compiling and uploading the sketch, breathe against the sensor you can see the event(s) when querying:

$ curl http://$YOURIP/wot/events
[{"name":"HumidityAboveThreshold","time":""},{"name":"HumidityAboveThreshold","time":""},{"name":"HumidityAboveThreshold","time":""},{"name":"HumidityAboveThreshold","time":""},{"name":"HumidityAboveThreshold","time":""}]

So much for now and the basics of THNG:STRUCTION, Web Of Things and REST-APIs for embedded devices.

TODOs left :)

This demo is not yet complete. If you like - as an exercise, or just playing around with the API, implement some of these:

  • flash the led when app_request_FlashLED is triggered (by /wot/actions/FlashLED)
  • modify the event code from above, so that new events are only triggered after the humidity has dropped below the threshold once it’s been above it.
  • make the treshold configurable by adding a property for it. Properties can be writting using HTTP’s PUT method, which will be understood by the sketch.
  • add another event such as HumidityBelowThreshold to the model
  • Add other sensor data from the BME280 class as individual properties

Conclusion

THNG:STRUCTION frees you from the hassle of implementing b0ring API stuff, which is highly repetivite code. Additionally, for upcoming generators such as MQTT or generators for other boards, the model can be reused. Happy modeling :-)