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. .. list-table:: :header-rows: 1 :widths: 8 8 84 * - ID - Ops - Meaning * - 51 - W - Package name on the server (``/repo/``) * - 55 - E - Download the package (:meth:`findylab.findyIoT.findyIoT.downloadUpdate`) * - 52 - E - Apply the downloaded package (:meth:`findylab.findyIoT.findyIoT.update`) * - 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 ``deflate`` support), 7 invalid size / URI, 8 apply failed or the new firmware does not boot 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/`` for the total size and fetches ``/repo//1460//`` chunks of 1460 bytes into ``/update.zip`` (:meth:`findylab.modem.eg915.downloadChunk`, up to three attempts per chunk). ``/update.meta`` pins 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 (:func:`findylab.ota.ota_zip_ok`); only then 53 = 2, 54 = 0 and the command is acknowledged. The acknowledgement is processed with ``execute=True``, so a ``*SET,52`` queued on the server runs immediately. #. Command **52** - ``update()`` re-verifies the archive, sets 53 = 3, extracts it into the ``/next`` staging directory (:func:`findylab.ota.ota_extract`, CRC-checked file by file), writes the ``/update.commit`` marker, deletes the archive, acknowledges the command and resets. #. ``boot.py`` finds ``/update.commit`` **before** importing the application, copies ``/next`` onto the live filesystem, writes ``/update.applied``, removes ``/next`` and 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 fw`` succeeds, the first successful report calls ``fw._notifyUpdateResult()``: 53 = 0, 54 = 1, marker removed. If the import fails, ``boot.py`` falls back to ``base.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. Related pages ------------- * :doc:`/api/ota`, :doc:`/api/findyiot`, :doc:`/api/modem` * :doc:`/datatypes/system-ids`