Over-the-air update
Firmware is updated remotely through three protocol commands and two status parameters. The mechanism is the same on every device; only the contents of the package differ.
ID |
Ops |
Meaning |
|---|---|---|
51 |
W |
Package name on the server ( |
55 |
E |
Download the package
( |
52 |
E |
Apply the downloaded package
( |
53 |
R |
State: 0 idle, 1 downloading, 2 downloaded / ready, 3 updating |
54 |
R |
Result: 0 none, 1 updated successfully, 4 connection lost (partial
download kept, resumable), 5 integrity check failed (download
discarded), 6 unsupported package (compressed entries without
|
Building a package
The package is a ZIP archive stored without compression. Its entries are copied onto the root of the device filesystem, so build it from a directory laid out exactly like the device:
cd package/ # contains e.g. last.mpy
zip -0 -r -X ../fw2609061200.zip .
-0 disables DEFLATE (the device build has no deflate module),
-X strips extra fields and streaming data descriptors. Upload the
file to the server repository; the server refers to packages without
the .zip suffix.
Warning
Keep firmware_core.mpy and boot.py out of routine packages.
The recovery image base.py imports firmware_core - if the
framework itself is broken by an update, nothing is left to receive
the fix. If you must ship them, test the exact package on a bench
device first.
Update flow
The server writes the package name to 51 and sends command 55.
downloadUpdate()sets 53 = 1, asks/repo/<name>for the total size and fetches/repo/<name>/1460/<n>/<IMEI>chunks of 1460 bytes into/update.zip(findylab.modem.eg915.downloadChunk(), up to three attempts per chunk)./update.metapins the partial file to the package name, size and chunk size, so an interrupted download resumes from the last complete chunk on the next 55 instead of restarting. When the file is complete every ZIP entry’s CRC32 is verified (findylab.ota.ota_zip_ok()); only then 53 = 2, 54 = 0 and the command is acknowledged. The acknowledgement is processed withexecute=True, so a*SET,52queued on the server runs immediately.Command 52 -
update()re-verifies the archive, sets 53 = 3, extracts it into the/nextstaging directory (findylab.ota.ota_extract(), CRC-checked file by file), writes the/update.commitmarker, deletes the archive, acknowledges the command and resets.boot.pyfinds/update.commitbefore importing the application, copies/nextonto the live filesystem, writes/update.applied, removes/nextand finally the marker. The copy is idempotent - a power loss in the middle simply re-runs it on the next boot.If
from last import fwsucceeds, the first successful report callsfw._notifyUpdateResult(): 53 = 0, 54 = 1, marker removed. If the import fails,boot.pyfalls back tobase.py, which reports 54 = 8, keeps the device reachable (one report every 300 s) and waits for a corrected package via the same 51 / 55 / 52 commands.
Files used on the device: /update.zip, /update.meta, /next/,
/update.commit, /update.applied. All of them are transient.
Recovery image
base.py must stay tiny: it imports only firmware_core, registers
the OTA parameters plus the identity/network ones, sends one
samplings report per cycle (with 53/54 in its datalist so the
server can drive the update from its normal telemetry view) and deep
sleeps for 300 s. Never add application logic to it.