Support for ESP32, WebThing API and PlatformIO-style projects.

ESP8266 has been the first board we've been implementing code generation modules for. It can be programmed using Espressifs own ESP-IDF framework, or the Arduino Framework - which makes it really easy to get started quickly. Luckily, the ESP8266 core for Arduino includes an HTTP server, so generating a REST API for it leads to very compact code. Unfortunately, this HTTP server is not included in its successor module, the ESP32. And one cannot simply take the code from the ESP8266 library and throw it on an ESP32, so we had to look for alternatives.

Luckily there's me-no-dev/ESPAsyncWebServer, and the main README speaks about the reason why it's better suited for the task, e.g. because of speed, asynchronous processing, more functionality etc.

I'm really a fan of PlatformIO because it makes it really easy to create for a lot of different boards and frameworks. (Ok, and a dozen or so reasons more). The first generator modules for ESP8266 and Arduino MKR created a single .INO sketch, now it was time for some refactoring and a different approach using PlatformIO.

As an example i have the NodeMCU-ESP32 (with the WROOM32 module), a Grove Base Shield and a small Grove LED Let's see how to put the WebThing API onto it :)

Thngstruction.online-nodemcu-esp32-grove

Creating the model ...

Start by opening the THNG:STRUCTION App in a new tab. At "Create New Thing Description", give it a name and select OnOffLight as type. Hit "Next >".

Create Thing

From the list of available code generation modules, select ESP32 and you should see the ESP32 generator. You can click on "more" or the documentation button to see a more detailed description for this module. Click on the right arrow to select this module and continue.

Select ESP32 generator

The model already contains a property named on, as a boolean property. This is part of the OnOffLight definition, so we do not need to add more at this point. Click "Actions >".

At the "Actions" tab, i've chosen to add a custom action called "flashOnce", which should make the LED flash on/off for a short timem every time this action is triggered:

Add Custom action

Hit "Events" to continue. I did not want to add events here, so a click to "Generate >" takes to a review page and finally to the code generation page.

It shows a bunch of generated files. Instead of putting all code in a single .INO sketch, this module separates it into different layers of functionality. Good news is: you only have to touch two files to make it work, the rest can be used as-is.

Create PlatformIO project

Let's start by creating a project using PlatformIO. My board is named nodemcu-32s. In case you have a different ESP32-board you can easily look up the board id using pio boards esp32 which at the time of writing returns 33 boards.

$ pio init -b nodemcu-32s --ide vscode

This creates among other files a src/ folder where we can put all the generated files. They can be downloaded as ZIP, TAR or TAR.GZ. I choose a tarball and unpacked it in the src/ folder:

$ cd src
$ tar xfv ~/Downloads/f9694791-4119-432a-b40d-2e18325b9978.tar
$ cd .. 

Up and running

The code snippets contain a HTTP+JSON REST API for the model created above, but without the specific application behaviour. We have to make three kinds of modifications to this project to make it work:

  • add libraries; it won't compile otherwise
  • set WiFi credentials
  • add application logic.
Libraries

README.md contains instructions about the libraries (e.g. ESPAsyncWebServer) and how to add them to the project. Open platformio.ini in an editor and add the following lines to it:

lib_deps =
    ESP Async WebServer
    ArduinoJson
    CircularBuffer
WiFi

Most of the configurable items have been moved to config.h. Open in an editor, locate the lines with WIFI_SSID and WIFI_PASSWORD, and place in your credentials.

Add Application logic

The generator cannot know what we want to do within the API endpoints, so we have to implement this. Open app.cpp in an editor. It contains function definitions for all properties, actions and events from the model, plus a application-level setup and loop function.

I connected my LED to GPIO18, so in app_setup i need to set pinMode for this pin to OUTPUT.

void app_setup() {
  pinMode(18, OUTPUT);
}

In the getter and setter of my On property, i'm using digitalRead and digitalWrite to get/set the LED:

bool app_get_on() {
    return digitalRead(18);
}

void app_set_on(bool v) {
  digitalWrite(18, v);
}

Testing

Ready to go!

$ pio run -t upload && pio device monitor -b 115200

Compiles and flashes the code, directly opening the serial monitor afterwards. The setup code prints out the IP address, so i can give it a try.

......
WiFi connected
IP address: 192.168.0.101
signal strength (RSSI):-26

The main entrypoint URI to the WebThing API is /wot (as defined in config.h, see URIPATH_PREFIX). It returns additional endpoints as json:

$ curl -s http://192.168.0.101/wot | jq
{
  "name": "esp 32 led test",
  "type": "onOffLight",
  "href": "/wot",
  "description": "",
  "properties": {
    "on": {
      "type": "Boolean",
      "description": "undefined",
      "href": "/wot/properties/on"
    }
  },
  "actions": {
    "flashOnce": {
      "description": ""
    }
  },
  "events": {}
}

/wot/properties/on can be used to read the LED status (GET) and turn it on or off (PUT):

$ curl -X PUT -H 'Content-Type: application/json' --data '{"on":true}' http://192.168.0.101/wot/properties/on

$ curl http://192.168.0.101/wot/properties/on
{"on":true}

This device can be connected to Mozilla's Things Gateway as-is. Starting from gateway version 0.4, the gateway allows for adding devices by IP address, so adding http://192.168.0.101/wot/ makes the LED accessible to the gateway.