Skip to content

python3-improv: emit Device-Status JSON superset (net/time/sec) for imx93-jaguar-eink#41

Merged
ajlennon merged 1 commit into
mainfrom
feature/improv-device-status-superset
Jul 20, 2026
Merged

python3-improv: emit Device-Status JSON superset (net/time/sec) for imx93-jaguar-eink#41
ajlennon merged 1 commit into
mainfrom
feature/improv-device-status-superset

Conversation

@ajlennon

Copy link
Copy Markdown
Member

Summary

Extends the imx93-jaguar-eink vendor Device-Status characteristic (e5f10002) from the flat network payload to the full Device-Status JSON superset defined in the app-side contract (active-esl-onboard docs/BLE-BOARD-PROFILE.md §5 / §5.1). This implements roadmap P0-2 (net-nest + time) and the Phase-0 slice of P0-1 (sec block).

  • net — network fields moved under a nested net block (bearer/state/ssid/ipv4/rssi/iface). The legacy flat top-level keys are mirrored for one release for backward compatibility; the app prefers the net block.
  • time — clock-sync status (epoch/iso/source/synced) from the systemd-timesyncd stamp file. Reports ntp/none instead of trusting the flaky PCF2131 RTC.
  • sec — self-reported security posture: secure_element:"ele", dm-crypt storage_encrypted, bonded:false, attested:false. secure_boot is deliberately "unknown" — the raw ELE-OCOTP0 shadow can't be safely interpreted (every SoC has non-zero fuses), so authoritative fused/unfused state is a tracked follow-up (ELE lifecycle query; roadmap P0-1/P2-1). We never emit a value that could be wrong.

All blocks are computed off the BLE event loop (same executor path as before), so reads never block on nmcli.

Compatibility

The app (device_status.dart) already parses this superset and falls back to the flat keys, so this is a forward-compatible rollout. Field names are locked against the app in BLE-BOARD-PROFILE.md §5.1.

Test plan

  • py_compile clean
  • Hot-patched on eink-0167 (transient systemd unit) and read back over BLE with improv_provision.py info --json: confirmed nested net + flat mirror + time{source:ntp,synced:true} + sec{secure_element:ele,...}
  • Board restored to packaged improv.service after verification
  • Lands on device via a new eink factory build + OTA (not in this PR)

Notes

Branched off main (PR #38 watchdog). Independent, parallel "ghost-advertising bounce" watchdog WIP exists on another branch and is not included here.

Made with Cursor

Extend the imx93-jaguar-eink vendor Device-Status characteristic
(e5f10002) from the flat network payload to the full Device-Status
document defined in active-esl-onboard BLE-BOARD-PROFILE.md §5:

- net: network fields nested under a `net` block (bearer/state/ssid/
  ipv4/rssi/iface). Legacy flat top-level keys are mirrored for one
  release so older app builds keep working; the app prefers `net`.
- time: clock-sync status (epoch/iso/source/synced) from the
  systemd-timesyncd stamp file. Reports ntp/none rather than trusting
  the flaky PCF2131 RTC.
- sec: self-reported security posture (secure_element=ele, dm-crypt
  storage_encrypted, bonded/attested=false). secure_boot is reported
  `unknown` for now: the raw ELE-OCOTP0 shadow cannot be interpreted
  safely (every SoC has non-zero fuses) — authoritative fused/unfused
  state needs the ELE lifecycle query (tracked follow-up, roadmap
  P0-1/P2-1). We never emit a value that could be wrong.

Computed off the BLE event loop (same executor path as before) so
reads never block. Verified over BLE on eink-0167 (info --json).

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4b93e6d. Configure here.

"""Blocking; call off the BLE loop. Full Device-Status superset."""
return build_device_status(compute_net_status())


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clock fields trigger periodic notifies

Medium Severity

Each refresh now embeds a fresh time block (epoch/iso) in the payload passed to _publish_net_status, but change detection still compares the full serialized JSON. When link state is unchanged, the document still differs every cycle, so _publish_net_status treats every refresh as a change and sends BLE notifications on each ~5s loop iteration instead of only on network updates.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4b93e6d. Configure here.

@ajlennon
ajlennon merged commit e6202ac into main Jul 20, 2026
2 of 3 checks passed
@ajlennon
ajlennon deleted the feature/improv-device-status-superset branch July 20, 2026 09:41
ajlennon pushed a commit that referenced this pull request Jul 20, 2026
Hot-patching the ota block onto eink-0167 showed the characteristic value
truncatedating mid-JSON at 512 bytes (full payload with the legacy flat mirror
was ~580). Drop the flat top-level state/ssid/ipv4/rssi/iface mirror (the
app has preferred nested net since #41) and add a progressive shrink
(time.iso → ota.os_version/hwid) before publish so a long SSID can't
silently corrupt the JSON. Re-verified over BLE: complete ota block with
registered:false/factory:eink/target:25/daemon:false.

Co-authored-by: Cursor <cursoragent@cursor.com>
ajlennon added a commit that referenced this pull request Jul 20, 2026
… (ota block) (#45)

* python3-improv: report Foundries enrolment/OTA state in Device-Status (ota block)

Add an `ota` block to the Improv Device-Status superset (vendor char
e5f10002) so the app/cloud can see, during onboarding, whether the board is
enrolled to a Foundries factory and running the OTA client:

  - registered: /var/sota/sota.toml present (the config lmp-device-register
    writes and aktualizr-lite.service's ConditionPathExists gate); absent =>
    not enrolled to any factory
  - factory/tag/hwid/target/os_version: from /etc/os-release
  - daemon: aktualizr-lite.service active
  - up_to_date: null on the board on purpose — authoritative "on latest target"
    comes from the cloud->Foundries API, not a possibly-OFFLINE device

All signals are root-free/best-effort and self-reported (untrusted until
attested, roadmap P2-1). Verified on eink-0167 (currently un-enrolled):
{"registered":false,"factory":"eink","tag":"main-imx93-jaguar-eink",
"hwid":"imx93-jaguar-eink","target":25,"os_version":"5.0.11-25-96",
"daemon":false,"up_to_date":null}.

Supports the app/admin onboard/offboard-to-Foundries feature (BLE-triggered
on-device registration + cloud offboard via Foundries API).

Co-authored-by: Cursor <cursoragent@cursor.com>

* python3-improv: fit Device-Status+ota under the 512-byte ATT ceiling

Hot-patching the ota block onto eink-0167 showed the characteristic value
truncatedating mid-JSON at 512 bytes (full payload with the legacy flat mirror
was ~580). Drop the flat top-level state/ssid/ipv4/rssi/iface mirror (the
app has preferred nested net since #41) and add a progressive shrink
(time.iso → ota.os_version/hwid) before publish so a long SSID can't
silently corrupt the JSON. Re-verified over BLE: complete ota block with
registered:false/factory:eink/target:25/daemon:false.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Alex J Lennon <ajlenon@dynamicdevices.co.uk>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant