The Internet of Things (IoT) is already all around us – from voice‑activated devices to lights, coffee machines, and fridges – and we interact with it everyday in ways we don’t always realize. One of the biggest challenges in scaling IoT devices is device management, particularly over-the-air updates, commonly called OTA.
OTA is critical to deploying new firmware to IoT devices; without it user intervention is required, either by the consumer or by a technician visiting (possibly literally) the device in the field.
This post demonstrates how to use NGINX Open Source or NGINX Plus as an API gateway to deploy OTA updates to devices automatically. (For ease of reading, I’ll refer to NGINX throughout.) I’m using a simple API to handle the firmware versioning (so the devices know which version is the latest) and to deliver the file itself.
For the purposes of this post I’m using an ESP32‑DevkitC device, which uses WiFi for communication. The setup described below applies just as easily to most device types, depending only on the device capabilities and how the firmware is written.
Setting Up the Device
First off, if you haven’t already set up and connected the ESP32‑DevkitC to WiFi, set that up now using the tutorial from Espressif.
Once your device is rocking WiFi, the next step is to load an Arduino sketch with the updated firmware on it. To do this, you need the Arduino IDE, so if you don’t already have it:
- Download the Arduino IDE from the Arduino website, install it, and launch it.
- Navigate to Tools > Manage Libraries, and wait for the Arduino IDE to update.
- Search for and select esp32FOTA to install the software for updating ESP32 firmware over the air.
Using the Arduino IDE, connect to the device via either WiFi or USB and load the following sketch. Be sure to replace <domain-url>
on line 11 with your domain name:
Setting Up an NGINX Instance
Now we have the board set up, it’s time to set up NGINX as an API gateway to handle versioning and delivery to device.
-
Add the following
include
directive to thehttp
block in nginx.conf: -
Create /etc/nginx/conf.d/api.conf with the following contents. I totally recommend that you use HTTPS and have included the directives for configuring it on lines 18–31. You need to generate a certificate and key in this case, and specify the paths to them with the
ssl_certificate
andssl_certificate_key
directives (lines 18–19).As in the sketch in the previous section, be sure to replace
<domain-name>
with your domain domain name on lines 8 and 11.The
location
block (lines 10–12) specifies the URI for the API (/ota) and the response that NGINX returns: status code200
plus the data that the sketch uses, in JSON format. For now it’s hardcoded but in the next section we will make that a little more dynamic, so read on. -
Run these commands to validate the configuration syntax and reload the config.
$ nginx -t $ nginx -s reload
-
Before powering on the device, run a check to make sure the API is working as expected and returns the JSON you’ve loaded into the
location
block in api.conf:$ curl -X GET https://domain-url/ota {"type":"esp32-fota-http", "version": 100, "host": "domain-url", "port": 80, "bin": "/esp32-fota-http-100.bin"}
If you run into any issues, check the api_access.log file and retrace your steps; depending on the device you are using, HTTPS might not be supported.
Automating OTA with the NGINX JavaScript Module
Now we intro a little NGINX JavaScript magic: we are going to write JavaScript code that reads from a folder and updates the output of the API when a new OTA update file is ready for devices to load.
[Editor – This post is one of several that explore use cases for the NGINX JavaScript module. For a complete list, see Use Cases for the NGINX JavaScript Module.
This section is updated to use the js_import
directive, which replaces the js_include
directive in NGINX Plus R23 and later. For more information, see the reference documentation for the NGINX JavaScript module – the Example Configuration section shows the correct syntax for NGINX configuration and JavaScript files.]
- Follow the instructions in the NGINX Plus Admin Guide to install the NGINX JavaScript module on the NGINX host, if you haven’t already.
-
To enable the module, edit nginx.conf and add the following
load_module
directives in the top‑level (“main”) context: -
Edit api.conf and make the following modifications. (For the purposes of this post, we instead create a copy called api_njs.conf with the modifications in it.)
-
Add this
js_import
directive above theserver
block which begins on line 4 of api.conf (see above; with the addition of this directive to api_njs.conf theserver
block now begins on line 6). -
In the
location
/ota
block, remove (or comment out) thereturn
directive and insert the followingjs_content
directive:
-
-
Create ota.js with the following contents. Be sure to:
- Replace
</path/to/firmware>
on line 5 with the path to the directory where you are storing the firmware files to be updated over the air. It must be a publicly accessible Internet location. - Replace
<domain-url>
on line 21 with your domain name.
On line 12, we invoke the Node.js fs module‘s
readFileSync
function to find the latest version of the OTA update file in the directory named byotaFolder
. We loop through the filenames in the directory to find the highest version number, usingreadFileSync
to check if the next file version in the sequence exists. When a version doesn’t exist, the previous (found) version is determined to be the latest.For this to work, filenames must be in the following format:
esp32-fota-http-version.bin
where version is 100 on the first version of the file. Increment the version number sequentially for subsequent versions (101, 102, 103, and so on).
Note that we’re using
readFileSync
because at the time of writing, the NGINX JavaScript module does not support thereaddir
function, but only operations on individual files. [Editor – Support forreaddir()
was added in July 2020 in NGINX JavaScript version 0.4.2.] - Replace
-
Run these commands to validate the configuration syntax and reload the config.
$ nginx -t $ nginx -s reload
-
Repeat the
curl
command from the previous section to verify the API is working as expected:$ curl -X GET https://domain-url/ota {"type":"esp32-fota-http", "version": 100, "host": "domain-url", "port": 80, "bin": "/esp32-fota-http-100.bin"}
Taking Advantage of Advanced Features
There’s a bunch of handy other features in NGINX and devices that we can use too.
For enhanced security with better authentication between device and server, many newer devices come equipped with crypto chips (like the Arduino MKR series) that allow for on‑device x.509 certificate generation.
NGINX’s advanced load balancing means you can deploy OTA update files to different server‑based regions (handy if you have country‑specific firmware) or multiple firmware servers or locations.
It’s up to each IoT device to decide when it does updates (or checks for them), so you need to keep older versions of OTA updates available. Devices are sometimes inactive or temporarily unable to get Internet access for a period of time, so make sure you keep track of your firmware versions and their dependencies.
OTA increases the longevity of device builds, as it allows you to refine and update device functionality (on the software side anyway). Of course OTA is even more important for things like security upgrades and patches, as it helps create more secure devices.
Just remember that 1 device can turn into 10 can turn into 10,000 before you know it, so it’s critical to have a solid OTA update plan in place before you scale.
To try NGINX Plus, start your free 30-day trial today or contact us to discuss your use cases.