OTA firmware updates: complete guide
OTA (over the air) means updating a device's firmware without touching it: no USB cable, no visit. You upload the new firmware in the Organization Portal, choose the devices, and start a shipment. Each device downloads the new firmware, installs it and restarts into it.
This guide covers both sides: what the firmware needs (developers) and how to ship an update (organizations).
How it works
ThingsLinker does not make thousands of devices ask "is there an update?" every few minutes. Instead it rings a doorbell: the moment you start a shipment, each of its devices gets a small message over its existing MQTT connection. Only then does the device ask the server what to install.
Why it is built this way:
- Instant and light. Devices hear about an update at once, without polling the server. That keeps working with thousands of devices.
- Offline devices are not missed. The doorbell is kept on the broker, so a device that is off when you start hears it as soon as it reconnects.
- Safe. The doorbell carries no firmware and no instructions. The device always asks the server over HTTPS, and the server alone decides what it gets. A fake message can only make a device ask; it cannot make it install anything.
- A safety net. The device also checks once a few minutes after it starts and then every 12 hours, in case it ever missed a doorbell.
1. Prepare the firmware
Turn on automatic updates
Add one line after iot.begin():
#include <ThingsLinker.h>
ThingsLinker iot("YOUR_DEVICE_AUTH_TOKEN", "YOUR_BLUEPRINT_KEY");
// Printed at start-up, so you can see which version is running after an update
static const char* FIRMWARE_VERSION = "1.0";
void setup() {
Serial.begin(115200);
Serial.printf("My product v%s\n", FIRMWARE_VERSION);
iot.begin();
iot.enableOTA(); // listen for the doorbell, plus a safety-net check every 12 hours
}
void loop() {
iot.run(); // installs an update when one is waiting
// your code
}
That is all. The complete sketch is the library example 19_OTA_Auto.
ThingsLinkerGSM does not have enableOTA(). Call iot.checkOTA() yourself on a schedule, for example every few hours, to save mobile data. See Check for updates yourself.
Choose the partition scheme (important)
To update itself, the ESP32 keeps two copies of the firmware: the one running and the one being downloaded. The flash must be divided to make room for both.
In Arduino IDE:
- Tools → Flash Size: your chip's real size, for example 4MB for most ESP32 DevKits or 16MB for an ESP32-S3 N16R8.
- Tools → Partition Scheme → Minimal SPIFFS (1.9MB APP with OTA).
Upload the first firmware over USB with these settings, and build every later .bin with exactly the same flash size and partition scheme. A firmware built differently can fail to install, or install and not start. "Default with OTA" (1.3 MB per slot) is usually too small for a sketch with WiFi and OTA.
Build the .bin file
- Raise the version in your code, for example
"1.0"→"1.1". - Choose Sketch → Export Compiled Binary.
- Arduino IDE saves the file in the sketch's
buildfolder. Use the one named after your sketch, ending in.ino.bin, not thebootloaderorpartitionsfiles.
2. Test on one device first
Always ship a new version to one device on your desk before shipping to everyone:
- Flash version
1.0over USB and add the device to an account so it is online. - Ship version
1.1to that device only (next section). - Watch the Serial Monitor: you see the download progress, then the device restart and print
v1.1.
If anything goes wrong, you can always reflash over USB.
3. How versions are chosen
- A device installs the newest live shipment it is part of. It is never offered an older one, so there are no accidental downgrades.
- When a device installs an update it reports completed, and the portal shows its new firmware version.
- A device that is inactive (not using a plan slot) gets no updates.
4. Ship the update
In the Organization Portal, go to OTA / Firmware and choose New Shipment. The full walkthrough with pictures is in Firmware updates. In short:
- Shipment Details: a name and the blueprint.
- Devices: choose them, or Select all.
- Firmware: the version and the
.binfile (drag it onto the upload area). - Retry Settings: how many tries each device gets.
- Start Shipping, then Start the shipment.
5. When an update fails
Downloads can fail: weak WiFi, a power cut, a restart in the middle. ThingsLinker handles this for you.
| Setting | Meaning | Example |
|---|---|---|
| Attempts Limit | How many tries each device gets. | 3: up to three tries, about 5 minutes apart |
| Attempts Reset Period | After using all its tries, how long a device waits before getting a fresh set. | 24 hours: if all three fail, try again tomorrow |
A device that restarts in the middle of an update counts as a failed attempt, so it is retried instead of being marked as updated. A failed update never breaks the device: it keeps running the firmware it had.
Check for updates yourself
If you prefer to decide when to check (for example on 4G), call checkOTA() instead of enableOTA():
void loop() {
iot.run();
static unsigned long lastCheck = 0;
if (millis() - lastCheck >= 6UL * 60 * 60 * 1000) { // every 6 hours
lastCheck = millis();
OTAResult r = iot.checkOTA();
if (r == OTA_NO_UPDATE) Serial.println("Firmware up to date");
if (r == OTA_FAILED) Serial.println("Update failed; the server will retry");
if (r == OTA_ERROR) Serial.println("Could not reach the server");
// On success the device restarts into the new firmware, so OTA_SUCCESS is not returned here
}
}
Troubleshooting
| What you see | Cause and fix |
|---|---|
| The serial log mentions a partition or "not enough space" | The .bin was built with a different flash size or partition scheme than the device. Rebuild it with the same settings as the firmware on the device, or reflash the device over USB with the right settings once. |
| The device downloads part of the file, then stops | Weak WiFi or power. The server retries automatically; check the device's signal and power supply. |
| Nothing happens after Start | The firmware must call iot.enableOTA() (or checkOTA()), the device must be online, and it must be active on your plan. |
| The update installs but the device keeps the old version | You uploaded the wrong .bin (for example the bootloader). Use <sketch>.ino.bin. |
| The device shows Offline for a moment during an update | Normal: it closes its MQTT connection while downloading, then restarts. |
| A device used all its attempts | Wait for the Attempts reset period, or fix the cause and start a new shipment. |
Good practice
- Version every release and print it at start-up.
- Test on one device, then a few, then all.
- Keep the same partition scheme for the life of the product.
- Do not change the credentials in a new version: the update must keep the same auth token and blueprint key.
- Keep the
.binfiles of released versions, so you can ship a fix quickly.