Skip to content

fix(ota): working rollback, encrypted writes, real refusal reasons - #132

Open
PaulDWhite wants to merge 1 commit into
masterfrom
fix/ota-hardening
Open

PaulDWhite wants to merge 1 commit into
masterfrom
fix/ota-hardening

Conversation

@PaulDWhite

Copy link
Copy Markdown
Collaborator

Makes Bluetooth firmware updates safer and lets the app show the controller's real state. Everything is in ota_service.cpp, plus the CI build-number fix.

Changes

  • Rollback works now. Arduino's initArduino() marks a freshly updated image valid before setup() runs unless the app overrides the weak verifyRollbackLater() hook. We never did, so the PENDING_VERIFY check at the end of setup() never fired, and a bad image couldn't roll back (8.0 too). This adds extern "C" bool verifyRollbackLater() { return true; }. I checked firmware.elf: the strong symbol replaces the weak default and returns 1.

  • OTA writes require an encrypted link. The command and data characteristics get WRITE_ENC, matching every config write, so only a paired phone can flash firmware. Notifications and indications are unchanged.

  • START refusal reasons:

    Code Meaning
    0x0002 Armed
    0x0003 Image too large for the slot
    0x0004 Flash or partition error
    0x0001 Generic / CRC failure (unchanged)

    8.0 sent 0x0001 for all of these. Existing apps still treat any non-zero code as a failure.

  • No arming during START. The session is claimed (otaInProgress) before the arm check, so toggleArm() can't arm during the multi-second esp_ota_begin() erase. The OTA connection parameters are now requested before the erase instead of after it.

  • Reboot after END is deferred. Rebooting about 1 s later from checkOtaTimeout() (bleNotifyTask, 50 Hz) lets the END write response and ACK go out. Before, ESP.restart() ran inside the write callback, so the write never completed. A disconnect in that window no longer aborts the session, and arming stays blocked until the reboot.

  • CI build number. pio-build checks out full history (fetch-depth: 0). VERSION_BUILD is git rev-list --count HEAD, which a shallow clone reports as 1. extra_script.py now fails a shallow CI build and warns on a shallow local one.

Testing

  • pio run -e OpenPPG-CESP32S3-CAN-SP140 passes: flash 36.3%, RAM 36.8%. cpplint is clean.
  • The marker in firmware.bin reads 8.1, build 1225.
  • Not yet tested on hardware. Checklist:
    • Bonded Android phone, 8.0 → this build over BLE: update completes and the app sees the END ACK.
    • Phone that was never bonded: START is refused with an insufficient-encryption error, or a pairing prompt appears.
    • START while armed returns 0x0002, and arming fails while an update runs.
    • Rollback: OTA to a build that panics in setup() and confirm it boots back into the previous image. Requires a rollback-capable bootloader, which comes from an 8.x full-flash install.

- Rollback: Arduino's initArduino() marks a new OTA image valid before
  setup() unless verifyRollbackLater() is overridden, and nothing
  overrode it, so the main.cpp check never ran and a bad image could
  not roll back. Override it so validation happens at the end of setup().
- Require an encrypted link (WRITE_ENC) for the OTA command and data
  characteristics, matching every config write, so only a paired phone
  can flash firmware.
- CMD_START refusals now report why: 2 = armed, 3 = image too large,
  4 = flash/partition error. 1 stays the generic/CRC failure. 8.0 sent
  1 for everything.
- Claim the OTA session before checking the arm state, so arming can't
  slip in during the multi-second esp_ota_begin() erase. Request the OTA
  connection parameters before the erase rather than after it.
- After END, reboot from checkOtaTimeout() about 1 s later instead of
  inside the write callback, so the END write response and ACK actually
  reach the app. A disconnect in that window no longer aborts; arming
  stays blocked until the reboot.
- CI: fetch full history for the firmware build, so VERSION_BUILD (the
  commit count) isn't 1. extra_script.py fails a shallow CI build and
  warns on a shallow local one.
@github-actions github-actions Bot added the fix label Oct 5, 2026
PaulDWhite added a commit that referenced this pull request Oct 5, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant