Skip to content

Releases

OTAmpy v4.9.3

Choose a tag to compare

@simonl65 simonl65 released this 14 Sep 10:07
Immutable release. Only release title and notes can be modified.

OTAmpy 4.9.3 — 2026-09-14

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • None.

Patch (backward compatible bug fixes)

  • Fixed footprint_boot.py, footprint_update.py, and footprint_stress.py (device RAM-footprint probes under src/otampy/device/tools/) importing OTA/boot/manager from a non-existent on-device path (src.otampy...) instead of the real deployed package (otampy) — these would have failed immediately if actually run on a device.
  • Fixed the project's Pyright configuration replacing the entire standard-library typeshed with the MicroPython stub set instead of overlaying it, which silently broke type-checking for host-only code (dataclasses, pathlib, os.environ, etc.) and had caused every DeployArgs(...) construction to misreport as invalid.
  • Fixed cli.py's init command reusing one variable (path) across two incompatible types (the raw str | None argument and the resolved Path), which the previous # type: ignore comments were silently papering over.

OTAmpy v4.9.2

Choose a tag to compare

@simonl65 simonl65 released this 13 Sep 21:23
Immutable release. Only release title and notes can be modified.

OTAmpy 4.9.2 — 2026-09-13

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • None.

Patch (backward compatible bug fixes)

  • Fixed mux._write() allocating two extra bytes objects (~256 B) on every channel-1 send by building the channel-prefixed frame in a pre-sized bytearray and writing the COBS-encoded frame and delimiter in a single call.

OTAmpy v4.9.1

Choose a tag to compare

@simonl65 simonl65 released this 13 Sep 19:20
Immutable release. Only release title and notes can be modified.

OTAmpy 4.9.1 — 2026-09-13

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • None.

Patch (backward compatible bug fixes)

  • Fixed --minify shipping an f-string conversion (!r/!s/!a) that MicroPython's parser rejects and mpy-cross refuses to compile, so otampy upd/cp/deploy --minify no longer ships a device library file that fails on the next boot.

OTAmpy v4.9.0

Choose a tag to compare

@simonl65 simonl65 released this 13 Sep 14:15
Immutable release. Only release title and notes can be modified.

OTAmpy 4.9.0 — 2026-09-13

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • Added OTA.mark_application_alive(), which clears the boot marker without polling the transport. An application that only calls poll() when frame_ready() is true (because poll() can block for up to ACK_TIMEOUT_MS on an idle link) never cleared the marker in a boot where no OTA command arrived. The next boot then paid the wide OTA_BOOT_RECOVERY_LISTEN_MS window, which does not answer PING, and otampy rollback could miss its post-commit-ready-timeout. Call it once, immediately before the main loop. It never touches the transport, never raises, and a second call does nothing. Applications that poll unconditionally, including both shipped scaffolds, need no change.

Patch (backward compatible bug fixes)

  • docs/protocol.md §2.4 and docs/architecture.md no longer say that reaching poll() is the only proof the application is alive. Both now describe mark_application_alive() and the frame_ready() case that needs it.
  • The examples/shared-uart/main.py scaffold has a new comment pointing real-time loops at frame_ready() plus mark_application_alive(). The scaffold code is unchanged.
  • Updated the README with trial-boot updates, rollback and recovery details.
  • CHANGELOG.md now has separate, dated 4.7.0 and 4.8.0 sections instead of an Unreleased block. It also adds the missing 4.8.0 entries for otampy cp device:<path> (device-to-host copy) and otampy upd --all-files skipping __pycache__ and *.example.*.

OTAmpy v4.8.0

Choose a tag to compare

@simonl65 simonl65 released this 11 Sep 21:52
Immutable release. Only release title and notes can be modified.

OTAmpy 4.8.0 — 2026-09-11

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • Shared-UART (channel-mux) mode, end to end. A project whose own code needs the same physical UART as OTAmpy can wrap it in otampy.mux.SerialMux on the device and have the host CLI speak the matching framing, so one radio link carries OTAmpy traffic alongside the application's own stream. New wire contract below URST (docs/protocol.md §1.3, PROTOCOL_VERSION unchanged), a host otampy.channel.ChannelCodec/ChannelSerial codec, a --mux/--no-mux CLI flag and mux config key, and an otampy init --mux scaffold. Opt-in; existing direct-mode devices and CLIs are unaffected.
  • Boot-time recovery window — recover a device stranded before ota.poll(), over the radio, no USB. boot.py now listens briefly on every boot for UPDATE_REQUEST or ROLLBACK, widening to an ~8 s window after a boot that never reached poll() or still carries an unconfirmed candidate. New device settings OTA_BOOT_RECOVERY_LISTEN_MS/OTA_BOOT_MARK_FILE; new host retry loops via otampy upd --recover and otampy rollback --recover with a configurable recovery-wait. Honors OTA_REQUIRE_AUTH when set. Optional OTA(...).boot(heartbeat=wdt.feed) lets a custom boot.py feed a watchdog across both blocking stretches. PROTOCOL_VERSION unchanged.
  • A committed OTA update is now on trial until it is confirmed. boot.py tracks unconfirmed candidates in a retain-previous journal and auto-restores the previous generation if the device reboots within OTA_TRIAL_BOOTS (default 3) boots without being confirmed. New channel-0 commands CONFIRM/UPDATE_STATE; new CLI commands otampy confirm and otampy state; otampy upd now auto-confirms after a healthy PING. PROTOCOL_VERSION unchanged.
  • otampy rollback reverts a confirmed (or trialling) update over the radio, restoring the one retained previous generation via a new ROLLBACK channel-0 command. Refuses safely when nothing is retained or a commit is mid-flight.
  • Boot-time UPDATE_COMMIT is now all-or-nothing and retains the previous generation, via a new commit journal (OTA_JOURNAL_FILE) and restore.repair() run on every boot. UPDATE_START discards the previously retained generation, keeping at most one prior generation on device.
  • otampy cp can now copy device-to-host. A device:<path>[:<host target>] argument downloads a single file from the device, reusing the existing CAT command rather than adding new device-side protocol.

Patch (backward compatible bug fixes)

  • otampy upd --all-files no longer uploads __pycache__ artifacts or *.example.* template files, which were never meant to be flashed to the device.
  • Session-scoped settings (ports/log-level/config/device-dir/mux) no longer silently revert under automation that forks a fresh subprocess per command. A new OTAMPY_SESSION_ID env var lets such callers opt into a fixed session identifier instead of relying on process ancestry, which changed on every invocation with no persistent parent shell.
  • Every successful otampy upd no longer reports failure due to the post-commit health check racing the new recovery window. Post-commit and rollback health checks now use their own post-commit-ready-timeout (default 30) instead of sharing the shorter update-ready-timeout.
  • The shipped boot.py/main.py example scaffold is direct mode again, fixing a regression where it wired SerialMux unconditionally and silently broke plain (non-mux) host CLI commands.

OTAmpy v4.7.0

Choose a tag to compare

@simonl65 simonl65 released this 11 Sep 20:27
Immutable release. Only release title and notes can be modified.

OTAmpy 4.7.0 — 2026-09-11

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • Added optional channel-mux mode for sharing one serial port between OTAmpy and other host/device traffic: outer frame protocol, ChannelCodec/ChannelSerial, otampy mux command,
    --mux flag, OTAMPY_MUX setting, and an otampy init --mux scaffold.
  • Added fail-safe, all-or-nothing update commits with a retain-previous journal, automatic sweep of orphaned .bck files, and restore.repair() run on every boot.
  • Added a trial-boot lifecycle: boots are counted with automatic rollback on repeated failure, plus otampy confirm / otampy state commands and a device-side ota.confirm() facade.
  • Added otampy rollback for a user-initiated revert to the previous firmware generation.
  • Added a boot-time recovery window so a stranded device can be reached over the radio after a failed or unpolled boot, via otampy upd --recover and otampy rollback --recover.
  • Replaced the old watchdog guidance with a heartbeat threaded through boot.run() and OTA.boot().

Patch (backward compatible bug fixes)

  • Fixed main.py to run recover() so a lost boot.py self-heals (F-06).
  • Fixed the orphan sweep so it no longer deletes retained .bck files (F-04).
  • Fixed the one-shot RTC helper so it is no longer retained or journaled (F-05).
  • Fixed post-reboot waits to use their own timeout instead of inheriting an unrelated one (F-11).
  • Fixed the boot-time recovery window so it is actually reachable in practice, including a two-tier window and boot marker (F-10, F-14).
  • Fixed the host-side recovery handshake to hold one port open and retry at stock timings (F-15).
  • Fixed --recover to skip the bytecode-shadow preflight, which doesn't apply during recovery (F-16).
  • Fixed otampy rollback --recover to refuse to run against an already-healthy device (F-17).
  • Fixed ChannelSerial so it no longer blocks on an empty serial port.
  • Fixed the CLI to ignore a malformed OTAMPY_MUX value instead of aborting every command.

OTAmpy v4.6.0

Choose a tag to compare

@simonl65 simonl65 released this 03 Sep 20:18
Immutable release. Only release title and notes can be modified.

OTAmpy 4.6.0 — 2026-09-03

Major (breaking changes)

  • None. Every new setting below is opt-in: an existing device with an unchanged configota.py, driven by a CLI with no OTAMPY_COMMAND_AUTH_KEY, behaves exactly as it did in 4.5.0.

Minor (functionality added in a backward compatible manner)

  • Optional authentication for the runtime command surface. Until now, any station that could reach the device's UART could read, overwrite, delete or reboot it with no credential. On a wired link that is the same trust boundary as physical access; on a radio link — where OTAmpy is most useful — it means anyone in range.
    • Setting OTA_REQUIRE_AUTH = True on the device makes it require every command (RB, SR, UPDATE_REQUEST, RM, CAT, LS, MEM, RTC*, CP_*) to arrive as AUTH:<counter>:<hex-mac>:<command>. The host signs when OTAMPY_COMMAND_AUTH_KEY is set.
    • The MAC is HMAC-SHA256 truncated to 8 bytes over a domain-separated, counter-prefixed copy of the command. The inner command is dispatched through the existing parser unchanged, so no command grammar moves.
    • Counters are strictly increasing and seeded from the wall clock, so a command captured off the air cannot be replayed — including after a reboot, since an accepted RB/SR/UPDATE_REQUEST saves the counter as a boot floor before resetting.
    • The MAC sits above URST, so URST's retransmission is unchanged and unaware of it: a legitimate retry is never mistaken for a replay.
    • New device settings: OTA_REQUIRE_AUTH, COMMAND_AUTH_KEY, OTA_REPLAY_FLOOR_FILE. New host environment variables: OTAMPY_COMMAND_AUTH_KEY, OTAMPY_COUNTER_FILE.
    • Documented in docs/protocol.md section 1.2, with all five settings shown commented out in configota.example.py.
  • OTA_ALLOWED_PATH_PREFIXES optionally restricts which paths CP_START, CAT and RM may target. Unset, any non-traversing path is still allowed.

Patch (backward compatible bug fixes)

  • Fixed an arbitrary file write in CP_START (path traversal / persistent remote code execution). CP_START:<target>:<size>:<sha256> took its target straight from the command with no directory restriction and no .. rejection — and since the SHA-256 field is attacker-supplied too, the checksum gate constrained nothing. A crafted CP_START:main.py:… (or boot.py, or any lib/ path) overwrote device code, which then ran on the next reboot.
    • .. is now always refused for CP_START, CAT and RM, whether or not an allowlist is configured. There is no legitimate traversal on a filesystem rooted at /.
    • Prefix matching respects path-segment boundaries, so an allowed prefix of data does not also admit database.db.
    • The refusal happens before any directory or staging file is created, so a rejected copy leaves nothing behind.

OTAmpy v4.5.0

Choose a tag to compare

@simonl65 simonl65 released this 20 Aug 18:53
Immutable release. Only release title and notes can be modified.

OTAmpy 4.5.0 — 2026-08-20

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • OTA.poll() / manager.poll() accept a new optional heartbeat callback, called once per fragment during a large CAT/LS response's transfer. A large response streams entirely
    inside one poll() call, one fragment at a time, each its own reliable send-and-wait-for-ACK — on a slow or lossy link this can legitimately take far longer than a single poll() call
    normally would. A caller feeding a hardware watchdog only once per poll() call would otherwise see no progress for the whole transfer and could mistake it for a hang. Distinct from
    the existing callback parameter, which remains a one-shot "about to reset" hook used only by RB/UPDATE_REQUEST. Exceptions raised from heartbeat are swallowed — a caller's
    watchdog feed must never be able to abort an in-progress transfer. Defaults to None (no-op) at every layer, so existing callers are unaffected.

Patch (backward compatible bug fixes)

  • None.

OTAmpy v4.4.0

Choose a tag to compare

@simonl65 simonl65 released this 18 Aug 19:38
Immutable release. Only release title and notes can be modified.

OTAmpy 4.4.0 — 2026-08-18

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • upd now reports transfer progress within each file, not only at its start. An update is dominated by its largest file: at the default 128-byte chunk size a ~34 kB main.py is ~268 chunks, each an ack round-trip over the link, so the longest stretch of an update previously produced no output at all and was indistinguishable from a hang.
  • On a terminal, each file gets a live progress bar showing its position in the manifest, percentage, transfer rate and estimated time remaining.
  • When output is not a terminal (piped to a dashboard, a log, or CI), progress is emitted as plain lines at 25% steps — for example main.py: 50% (17152/34188 bytes). A redrawn bar is meaningless once captured and its control codes are litter in whatever reads it. The rendering is selected from the real stream, so redirecting is enough; there is nothing to configure.
  • The per-file announcement now carries its position in the manifest: [2/13] main.py (34188 bytes).
  • Progress advances only once the device has acknowledged a chunk, so it reflects bytes the device accepted rather than bytes written to the wire.
  • New upd --no-progress flag restores exactly the previous output — one plain Transferring <path> (<n> bytes)... line per file, with no counter and no percentages — for scripts and captured runs where the extra lines are noise.

Patch (backward compatible bug fixes)

  • None.

OTAmpy v4.3.1

Choose a tag to compare

@simonl65 simonl65 released this 17 Aug 16:09
Immutable release. Only release title and notes can be modified.

OTAmpy 4.3.1 — 2026-08-17

Major (breaking changes)

  • None.

Minor (functionality added in a backward compatible manner)

  • None.

Patch (backward compatible bug fixes)

  • Fixed otampy cat printing its "Showing content of specified file: ..." status line to stdout instead of stderr, which always prepended itself to any piped/captured output.
  • Fixed otampy cat running raw device file content through Rich console markup, which silently swallowed any literal [...] in the file (list literals, dict/subscript syntax, bracketed comments) as an unrecognized style tag.
  • Fixed otampy cat appending an extra trailing newline even when the file content already ended in one.