REMOTE OTA
Over The Air Device Updates
Over-the-Air (OTA) Programming is a method that allows the remote sending of new firmware to IoT devices over the Internet, regardless of their location. This process works with the Thinger.io Cloud to deliver the new firmware binaries.
This feature enables us to keep devices updated with new security patches and code improvements quickly and on a large scale. It is an essential tool for the maintenance of IoT products, preventing the need to travel to the devices' locations.
The update process is performed using a Visual Studio Code extension that integrates with the Thinger.io cloud to upload new firmware binaries to the target device. This integration with the IDE streamlines the development process, allowing developers to compile and upload firmware as if the device were connected to the computer.
IDE Configuration
Before working with this tool, it is necessary to install and configure Visual Studio Code and the PlatformIO extension as explained in the SDK SETUP section of the documentation. Then, install the Thinger.io VSCode extension directly from the Extension Manager. Search for "Thinger.io" or Checkout on Microsoft Marketplace.

This extension will manage the OTA processes with several interesting features such as:
OTA updates directly from the Internet over Thinger.io
Device and Product switcher to search and select the target device(s) for the update
Real-time device connection status and input/output indicators
Compatibility with multiple PlatformIO configuration environments within a project
Automatic build and upload over the Internet with a single click
OTA with compression support for ESP8266, ESP32, and Arduino Portenta devices
MD5 checksum verification for firmware binaries, both compressed and uncompressed
Extension Configuration
Before running the OTA, it is necessary to configure the extension by accessing the VS Code Extensions Manager and selecting the Extensions Settings option.

Thinger.io Host: The URL of the Thinger.io instance being used should be placed here (if using a private instance, otherwise by default it will be
backend.thinger.io
).Thinger.io Port: Specifies the connection port (443 by default).
Thinger.io Secure: Verify SSL/TLS connection (enabled by default).
Thinger.io SSL: Use SSL/TLS encryption (enabled by default).
Thinger.io Token: Place here a Thinger.io Access Token with these permissions:
ListDevices
AccessDeviceResources
ReadDeviceStatistics
ListProducts

Firmware Upload via OTA
The OTA feature is implemented on the default Thinger.io Arduino IOTMP client libraries required for connectivity with the Thinger.io cloud.
The boards that support OTA updates over the Visual Studio Code extension are:
Espressif ESP8266
Espressif ESP32
Arduino Nano 33 IOT
Arduino MKR WiFi 1010
Arduino RPI2040 Connect
Arduino MKR NB 1500
Arduino Portenta
Arduino Opta
The general requirements to start working with OTA updates for a specific device are:
Have a PlatformIO project on Visual Studio Code for the target device. More details here.
A basic Thinger.io firmware should be prepared for the device, ensuring its ability to connect to the cloud.
Modify the sketch to include the OTA functionality. More details in each specific device section.
Flash the initial firmware over a serial communication port on the computer.
The initial firmware can then be flashed over a serial communication port on the computer.
Subsequently, the Thinger.io Visual Studio Code toolbar buttons can be used to select the device and flash new firmware. If the configuration is correct, it will be possible to begin working with the Thinger.io extension through the new elements added to the bottom toolbar. Two buttons are available to select the target device for flashing, and then to compile and upload new firmware binaries.

Select Target Device 🚀
This button is a device selector. Upon activation, a prompt will appear for the selection of a target device from the user's Thinger.io account.

Compile and Update ▶️
This button compiles and uploads the code to the selected device. In the process, it will show a window with the OTA progress.

Clean Target Device 🗑️
It is possible to clean the selected target device by accessing the Visual Studio command Palette with Ctrl
+ Shift
+ P
, and searching for Thinger.io
:

ESP32 OTA
To add OTA functionality for ESP32, it is only required to include the ThingerESP32OTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#define THINGER_SERIAL_DEBUG
#include <ThingerESP32.h>
#include <ThingerESP32OTA.h>
#include "arduino_secrets.h"
ThingerESP32 thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerESP32OTA ota(thing);
void setup() {
// open serial for monitoring
Serial.begin(115200);
pinMode(16, OUTPUT);
thing.add_wifi(SSID, SSID_PASSWORD);
// digital pin control example (i.e. turning on/off a light, a relay, configuring a parameter, etc)
thing["GPIO_16"] << digitalPin(16);
// resource output example (i.e. reading a sensor value)
thing["millis"] >> outputValue(millis());
// more details at http://docs.thinger.io/arduino/
}
void loop() {
thing.handle();
}
ESP8266 OTA
To add OTA functionality for ESP8266, it is only required to include the ThingerESP8266OTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#define THINGER_SERIAL_DEBUG
#include <ThingerESP8266.h>
#include <ThingerESP8266OTA.h>
#include "arduino_secrets.h"
ThingerESP8266 thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerESP8266OTA ota(thing);
void setup() {
// open serial for monitoring
Serial.begin(115200);
// set builtin led as output
pinMode(LED_BUILTIN, OUTPUT);
// add WiFi credentials
thing.add_wifi(SSID, SSID_PASSWORD);
// digital pin control example (i.e. turning on/off a light, a relay, configuring a parameter, etc)
thing["led"] << digitalPin(LED_BUILTIN);
// resource output example (i.e. reading a sensor value)
thing["millis"] >> outputValue(millis());
// more details at http://docs.thinger.io/arduino/
}
void loop() {
thing.handle();
}
Arduino Portenta/Opta
To add OTA functionality for Arduino Portenta and Opta devices, it is required to include the ThingerPortentaOTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#define THINGER_SERIAL_DEBUG
#include <ThingerMbed.h>
#include <ThingerPortentaOTA.h>
#include "arduino_secrets.h"
ThingerMbed thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerPortentaOTA ota(thing);
void setup() {
// configure LED_BUILTIN for output
pinMode(LED_BUILTIN, OUTPUT);
// open serial for debugging
Serial.begin(115200);
// configure wifi network
thing.add_wifi(SSID, SSID_PASSWORD);
// pin control example (i.e. turning on/off a light, a relay, etc)
thing["led"] << digitalPin(LED_BUILTIN);
// resource output example (i.e. reading a sensor value, a variable, etc)
thing["millis"] >> outputValue(millis());
// start thinger on its own task
thing.start();
// more details at http://docs.thinger.io/arduino/
}
void loop() {
// use loop as in normal Arduino Sketch
// use thing.lock() thing.unlock() when using/modifying variables exposed on thinger resources
delay(1000);
}
It may be required to update the bootloader of the Portenta/Opta device to enable OTA updates.
Update the Bootloader of the Arduino Portenta/Opta: Run the sketch 'STM32H747_System_manage_Bootloader'
Format the QSPI flash memory: Run the sketch 'QSPIFormat'.
Run WiFiFirmwareUpdater
Arduino Nano 33 IOT OTA
To add OTA functionality for Arduino Nano 33 IOT, it is required to include the ThingerWiFiNINAOTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#define THINGER_SERIAL_DEBUG
#include <ThingerWiFiNINA.h>
#include <ThingerWiFiNINAOTA.h>
#include "arduino_secrets.h"
ThingerWiFiNINA thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerWiFiNINAOTA ota(thing);
void setup() {
// configure LED_BUILTIN for output
pinMode(LED_BUILTIN, OUTPUT);
// open serial for debugging
Serial.begin(115200);
// configure wifi network
thing.add_wifi(SSID, SSID_PASSWORD);
// pin control example (i.e. turning on/off a light, a relay, etc)
thing["led"] << digitalPin(LED_BUILTIN);
// resource output example (i.e. reading a sensor value, a variable, etc)
thing["millis"] >> outputValue(millis());
// more details at http://docs.thinger.io/arduino/
}
void loop() {
thing.handle();
}
Note: it is required to include lib_archive = no
to platformio.ini
configuration file.
[env:nano_33_iot]
platform = atmelsam
board = nano_33_iot
framework = arduino
lib_archive = no
lib_deps = thinger.io
Arduino MKR WiFi 1010 OTA
To add OTA functionality for Arduino MKR WiFi 1010, it is required to include the ThingerWiFiNINAOTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#include <ThingerWiFiNINA.h>
#include <ThingerWiFiNINAOTA.h>
#include "arduino_secrets.h"
ThingerWiFiNINA thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerWiFiNINAOTA ota(thing);
void setup() {
// configure LED_BUILTIN for output
pinMode(LED_BUILTIN, OUTPUT);
// open serial for debugging
Serial.begin(115200);
// configure wifi network
thing.add_wifi(SSID, SSID_PASSWORD);
// pin control example (i.e. turning on/off a light, a relay, etc)
thing["led"] << digitalPin(LED_BUILTIN);
// resource output example (i.e. reading a sensor value, a variable, etc)
thing["millis"] >> outputValue(millis());
// more details at http://docs.thinger.io/arduino/
}
void loop() {
thing.handle();
}
Note: it is required to include lib_archive = no
to platformio.ini
configuration file.
[env:mkrwifi1010]
platform = atmelsam
board = mkrwifi1010
framework = arduino
lib_archive = no
lib_deps = thinger.io
Arduino RPI2040 Connect OTA
To add OTA functionality for Arduino RPI2040 Connect, it is only required to include the ThingerMbedOTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#define THINGER_SERIAL_DEBUG
#include <ThingerMbed.h>
#include <ThingerMbedOTA.h>
#include "arduino_secrets.h"
ThingerMbed thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerMbedOTA ota(thing);
void setup() {
// open serial for debugging
Serial.begin(115200);
// configure LED_BUILTIN for output
pinMode(LED_BUILTIN, OUTPUT);
// configure wifi network
thing.add_wifi(SSID, SSID_PASSWORD);
// pin control example (i.e. turning on/off a light, a relay, etc)
thing["led"] << digitalPin(LED_BUILTIN);
// resource output example (i.e. reading a sensor value, a variable, etc)
thing["millis"] >> outputValue(millis());
// start thinger task
thing.start();
// more details at http://docs.thinger.io/arduino/
}
void loop() {
// your code here
}
Arduino MKR NB 1500 OTA
To add OTA functionality for MKR NB 1500, it is only required to include the ThingerMKRNBOTA.h
header and create an instance of it. A complete example for a basic firmware with OTA support:
#define THINGER_SERIAL_DEBUG
#include <ThingerMKRNB.h>
#include <ThingerMKRNBOTA.h>
#include "arduino_secrets.h"
ThingerMKRNB thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
// OTA seems not to work if no reset button is pressed
ThingerMKRNBOTA ota(thing);
void setup() {
// enable serial for debugging
Serial.begin(115200);
// optional set pin number
thing.set_pin(PIN_NUMBER);
// set APN
thing.set_apn(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD);
// set builtin led to output
pinMode(LED_BUILTIN, OUTPUT);
// pin control example over the Internet (i.e. turning on/off a light, a relay, etc)
thing["led"] << digitalPin(LED_BUILTIN);
// resource output example (i.e. reading a sensor value, a variable, etc)
thing["time"] >> [&](pson& out){
out = thing.getNB().getTime();
};
// more details at http://docs.thinger.io/arduino/
}
void loop() {
thing.handle();
}
Note: it is required to include lib_archive = no
to platformio.ini
configuration file.
[env:mkrnb1500]
platform = atmelsam
board = mkrnb1500
framework = arduino
lib_archive = no
lib_deps = thinger.io
At this moment, it seems that MKR NB 1500 requires pressing the reset button to apply the OTA update.
Firmware Versioning
Firmware versioning is essential for managing updates and maintaining the security of IoT devices. As IoT deployments grow in complexity and scale, proper versioning ensures that devices operate reliably and securely.
Our recommended versioning system follows the Semantic Versioning (SemVer) approach, which uses a Major.Minor.Patch format:
Major: Incremented for incompatible API changes.
Minor: Incremented for added functionality in a backwards-compatible manner.
Patch: Incremented for backwards-compatible bug fixes.
To define the firmware version, it is required to define a preprocessor definition called THINGER_OTA_VERSION
. This is used inside the firmware and VS Code to decide when a device requires an update.
Each time an OTA update process is started, the system will display a confirmation dialog showing the details of the firmware to be uploaded. This dialog includes the device name, firmware version, and the environment. It ensures that the user is informed about the firmware update specifics before proceeding, providing an additional layer of verification.

Devices that are already running the current firmware version will not be updated, ensuring that only devices with outdated firmware receive the new update. This helps to optimize the update process, reducing unnecessary data transfer and minimizing downtime for devices that are already up-to-date.
It is possible to define the firmware version in different places, such as:
In the source code
Through external build flags
Based on version control, like git tags
Different alternatives are described in detail below.
Fixed on Code
Define THINGER_OTA_VERSION
in the code before Thinger.io includes. This method requires hardcoding the firmware version directly in the source files. Each firmware version update necessitates a manual change of the version number in the code and subsequent redeployment of the firmware.
#define THINGER_OTA_VERSION "1.0.0"
// Thinger.io includes
#include <ThingerMbed.h>
#include <ThingerPortentaOTA.h>
Fixed Build Flag
Create a build flag in platformio.ini
with a static version definition. This approach sets the firmware version through build configuration. To update the firmware version, modify the version number in the platformio.ini
file and rebuild the firmware.
build_flags =
-D THINGER_OTA_VERSION=1.0.0
Dynamic Build Flag
This is the recommended approach and consists of dynamically setting the firmware version based on version control information, such as git tags. This method ensures the firmware version is automatically updated based on the latest git tag, reducing manual intervention. Each time a new version in git is tagged, the build process will automatically use this tag as the firmware version.
To achieve this configuration, it is required to modify the platformio.ini
file and include a build flag that will be dynamically composed using the git describe
command. This command retrieves the latest git tag and uses it to set the THINGER_OTA_VERSION
preprocessor definition.
build_flags =
!echo '-D THINGER_OTA_VERSION='$(git describe --tags)''
Last updated
Was this helpful?