The is an AI-fueled fever dream mash-up of the the WiFi and BLE OTA updaters for ESP-32
- BLE Updater: https://github.com/meshtastic/firmware-ota
- WiFi Updater: https://github.com/meshtastic/firmware-ota-wifi
The protocol uses a text-based command-response handshake followed by a raw binary stream. It is designed for constrained devices, minimizing RAM usage by streaming data directly to flash memory.
Key Security Feature: The device strictly enforces firmware integrity. The SHA-256 hash of the intended firmware must be pre-provisioned in the device's Non-Volatile Storage (NVS) before the update begins. The client must provide this exact hash during the handshake, and the final downloaded binary must match it.
Meshtastic's design implicitly trusts the network. This NVS hashing system is intended for future compatibility. Should the firmware TCP connection be secured in some way, then the NVS hash can only be applied by a trusted device. The OTA loader will then only accept a firmware with the proper hash.
If there is a hash mismatch, the OTA loader will intentionally corrupt the header of the partition to prevent untrusted or bad code from executing.
- Discovery: The device listens for UDP broadcasts on port
3232. It responds with its device name and version. - Connection: The Client initiates a TCP connection to the device IP on port
3232. - Flow: Synchronous. The client sends a packet and waits for a response (or TCP ACK).
- Service UUID:
4FAFC201-1FB5-459E-8FCC-C5C9C331914B - Characteristics:
- OTA (Write / Write No Response):
62ec0272-3ec5-11eb-b378-0242ac130005- Used by Client to send Commands and Binary Firmware.
- TX (Notify):
62ec0272-3ec5-11eb-b378-0242ac130003- Used by Device to send responses (
OK,ERR,ACK,ERASING).
- Used by Device to send responses (
- OTA (Write / Write No Response):
- Flow: Asynchronous with application-level ACK. The client writes a chunk, then waits for an
ACKnotification before sending the next.
- Client connects (TCP) or Subscribes to TX Characteristic (BLE).
- Client sends:
VERSION\n - Device responds:
OK <hw_ver> <fw_ver> <reboot_count> <git_hash>\n- Example:
OK 1 1.0.0 45 v1.2-5-g8a2b3c
- Example:
- Client sends:
OTA <size_in_bytes> <sha256_hex_string>\n- Example:
OTA 1048576 a1b2c3d4...(64 char hex string)
- Example:
- Device performs Validation:
- Parses size and hash.
- Check: Compares the Client-provided hash against the
ota_hashstored in the device's NVS.
- Device responds (on success):
- Sends:
ERASING(This indicates flash erasure has started. Client should wait). - ...Time passes (Flash Erase)...
- Sends:
OK\n(Device is now ready to receive binary stream).
- Sends:
- Device responds (on failure):
- Sends:
ERR <Reason>\n(e.g.,ERR Hash Rejected,ERR Invalid Format). - The connection remains open, but OTA state is reset.
- Sends:
Once the OK\n is received after the OTA command, the device enters STATE_DOWNLOADING.
- Client splits the firmware binary into chunks (Recommended: 256 to 512 bytes for BLE, up to 1024 for WiFi).
- Loop until all bytes are sent:
- Client sends binary chunk.
- Device writes chunk to OTA partition and updates running SHA-256 calculation.
- (BLE ONLY) Device sends:
ACKvia notification. - (BLE ONLY) Client waits for
ACKbefore sending next chunk. - (WiFi) Standard TCP flow control applies; no application-level ACK is sent per chunk.
- Device detects that
total_received_bytes == size_in_bytes. - Device calculates the final SHA-256 hash of the written partition.
- Device compares Calculated Hash vs. NVS Expected Hash.
- Device sets the new partition as the Boot Partition.
- Device resets internal reboot counters (if applicable).
- Device sends:
OK\n - Device waits 2 seconds, then reboots.
- Device detects mismatch.
- Device executes Partition Corruption:
- Overwrites the first sector of the target OTA partition with garbage data (
0xFF). - This ensures the bootloader will not attempt to boot this corrupt firmware.
- Overwrites the first sector of the target OTA partition with garbage data (
- Device sends:
ERR Hash Mismatch\n - Device aborts OTA and resets state.
All commands must be terminated with \n (newline).
| Command | Arguments | Description | Response Success | Response Fail |
|---|---|---|---|---|
VERSION |
None | Get device info | OK <hw> <fw> <cnt> <ver> |
ERR |
REBOOT |
None | Force restart | OK (then reboots) |
ERR |
OTA |
<size> <hash> |
Start update | ERASING ... OK |
ERR <Msg> |
If the device replies with ERR, the remainder of the line describes the error.
ERR Unknown Command: The command string was not recognized.ERR Invalid Format: Arguments forOTAcommand were malformed.ERR Hash Rejected (NVS Mismatch): The hash provided by the client does not match the hash pinned in the device NVS.ERR No Partition: Could not find a valid OTA_0 or OTA_1 partition.ERR OTA Begin Failed: Hardware error initializing flash write.ERR Hash Update: Internal crypto engine error.ERR Flash Write: Hardware error writing to flash.ERR Size Mismatch: Client sent more bytes than declared inOTAcommand.ERR Hash Mismatch: The downloaded binary did not match the expected hash.ERR Set Boot: Failed to configure bootloader to use new partition.
-
PlatformIO should build with an esp32 or esp32s3 environment.
pio run -e esp32s3 -t uploador
pio run -e esp32 -t upload -
A custom
uplaod_commandin the ini file will upload only the OTA loader into the proper partion.
-
Install esp-idf and activate the environment
-
Clean build with the following commands:
rm sdkconfig
rm -rf build
idf.py set-target esp32s3
idf.py build
-
Install it on a 4MB board:
esptool.py --port /dev/tty.usbmodem21101 write_flash 0x260000 ./build/OTA-WiFi.bin -
Install it on a 8MB board:
esptool.py --port /dev/tty.usbmodem21101 write_flash 0x5D0000 ./build/OTA-WiFi.bin -
Install it on a 16MB board:
esptool.py --port /dev/tty.usbmodem21101 write_flash 0x650000 ./build/OTA-WiFi.bin
-
Apply merge the changes in the firmware
meshtastic-otabranch into your firmware version of choice. -
Build and install this version on your device
-
Buind and install this OTA firmware into the OTA_1 partition. Be careful only write the OTA_1 partition and not any of the supporting partitions. The platformio.ini file has a custom update step to do this using esptool.py
-
Build and install the
firmware-updatesbranch of the iOS app -
Good luck