ModBee OTA
Firmware updates for ModBee nodes over MQTT and Sparkplug B. The node pulls
the image, verifies it, and rolls it back if the new image doesn't come up.
Nothing waits: update() does a bounded slice of work, so it runs inside a
PLC scan.
#include <WiFi.h>
#include <ModBeeMqtt.h>
#include <ModBeeOta.h>
#include <links/OtaMqttLink.h>
WiFiClient net;
MqttDevice mqtt(net);
OtaEsp32Target flash; // the ESP32's A/B slots
OtaAsyncTarget target(flash); // flash work off the caller...
OtaEsp32Worker worker(target); // ...on a lowest-priority task
OtaEsp32Store store; // the job, across reboots
OtaUpdater ota(target);
OtaMqttDeviceLink otaLink(mqtt, "modbee/node-1/ota");
void setup() {
// ... WiFi, mqtt.connect(BROKER, "node-1") ...
OtaSettings settings;
settings.board = "my-board";
settings.version = "1.0.0";
worker.begin();
ota.store(&store);
ota.addLink(otaLink);
ota.begin(settings);
}
void loop() {
mqtt.poll();
ota.update(); // a bounded slice of work; never waits
}
What it does
- Pulls. The node asks for a byte range; the server answers with
CRC-checked chunks. A lost message costs one request, a dropped link resumes
where it stopped, and the server can't overrun the node. - Verifies before it switches. It checks each chunk's CRC-32, the whole
image's SHA-256, an optional Ed25519 or ECDSA P-256 signature, board and
version, and the chip's own image check. - Rolls back. The new image boots on trial and confirms itself once it
can reach MQTT again. If it crashes, hangs or never connects, the old one
comes back, and it reportsrolled_backfor the job. - Safe beside a PLC. On the ESP32 all flash work runs on a lowest-priority
worker, andupdate()returns in microseconds. Measured on the Node UIO
with probe tasks on both cores, the only stall left is ~50 ms once per
update, when the new image confirms. Every event is also polled state. - Made for slow links. The timeout learns the link's round trip, a stall
waits instead of failing, and a power cut resumes where it stopped.
NB-IoT-like links cost 1.01× the image. - Isn't tied to the chip. ESP32 uses its own OTA. Other chips implement an
OtaStorageand anOtaBootControl, andOtaStagedTargetdoes the rest. - Isn't tied to the transport. Links exist for
MqttDevice, a bare
MqttClient(as OpenPLC'sMQTT_CLIENTblock holds it), and Sparkplug B.
One binary frame format runs on all of them, so a gateway can relay frames
without decoding them.
Requirements
- ESP32 (any variant) with the Arduino-ESP32
3.x core, and a partition table with two OTA slots. The core's default and
"Minimal SPIFFS" schemes both have them. - ModBee MQTT for the MQTT and
Sparkplug links. The core library builds without it. - The
NodeUio_MQTTexample also needs
ModBee Node UIO.
The core (src/*.cpp, src/staged/) is plain C++14 with no Arduino or
ESP-IDF code, so it builds for any target and on a desktop.
Installation
Arduino IDE: download this repository as a ZIP and use Sketch → Include
Library → Add .ZIP Library, or clone it into your libraries folder. Install
ModBee MQTT the same way.
PlatformIO: add both to platformio.ini:
lib_deps =
https://github.com/mod-bee/modbee-mqtt
https://github.com/mod-bee/modbee-ota
Sending an update
The library is the node side. The server is anything that speaks the protocol
in docs/PROTOCOL.md over the same broker:
- Publish a JSON offer (size, SHA-256, board, version, optional signature)
to<base>/offer, retained. - Answer each Request frame the node publishes on
<base>/reqwith Chunk
frames on<base>/data. - Watch
<base>/statusfor progress and the outcome.
That's a few dozen lines in any language with an MQTT client. On a device,
OtaServer with an OtaImageSource is the same server side in C++, for a
gateway feeding the nodes behind it.
To accept only signed images, give the updater a verifier with
ota.verifier(&verifier): OtaEd25519Verifier on the ESP32, or
OtaEcdsaP256Verifier where wolfSSL is available. Unsigned offers are then
refused. If the verifier is attached after begin(), set
settings.requireSignature so nothing unsigned gets in before it. What is
signed, and how, is in docs/PROTOCOL.md.
Examples
NodeUio_MQTT: a Node UIO over
MQTT, with the flash work on a background worker and optional signed images.Esp32_Sparkplug: an ESP32
Sparkplug B edge node updated through its own metrics.
Docs
- Architecture: the parts, the job lifecycle, why
pull, PLC timing (measured), slow links, and the safety layers - Protocol: frames, topics, JSON and signatures, which is
everything a server implements - Porting: adding STM32, RP2040 and other chips
- Roadmap: the BeeBus gateway relay and what comes next
Layout
src/ core: updater, protocol, hashes, server (no platform code)
src/staged/ OtaStagedTarget - the kit for chips without native OTA
src/esp32/ ESP32 target, NVS store, Ed25519 verifier, background worker
src/links/ MQTT and Sparkplug links (header-only, opt-in)
src/verifiers/ ECDSA P-256 verifier over wolfSSL (header-only, opt-in)
examples/ Node UIO over MQTT; ESP32 Sparkplug edge node
Configuration
Limits are compile-time macros in src/OtaConfig.h, each
behind #ifndef, so a build overrides them with -D without editing the
library: chunk size, buffer and peer counts, string lengths. Run-time settings
(buffer size, timeouts, confirm window, accept and apply policy) are fields of
OtaSettings in src/OtaUpdater.h.
Building the core on a desktop
The core builds with CMake on Linux, macOS and Windows, for host-side tools
or to link into another project:
cmake -S . -B build
cmake --build build
License
GPL-3.0. See LICENSE.