Skip to main content

Boot sequence

  • The bootloader validates the appended image SHA-256 and the OTA-data partition, then jumps into the selected app slot. Failed or rolled-back updates land on the previous good slot (see OTA).
  • _boot.py mounts the filesystem through flashbdev.py (the flash block device).
  • inisetup.py performs first-boot filesystem setup.
  • boot.py (a VFS file on flash, distinct from the frozen _boot.py) runs on every boot, including wake from deep sleep, immediately before main.py.
  • main.py / project_main.py bring up the application: event bus, task manager, LEDs, radios, GNSS, and the compass state machine. In v5.0.3 compass.start launches the ESP-NOW comms task via enow_v2.communicate_supervised, which restarts it on a crash (v5.0.2: communicate_v2 directly).

Power / boot modes

device_power.py tracks an application-level boot_mode and a battery-health state. Observed states and transitions (from log strings and symbols):

Reset cause vs. boot-loop protection

The app boot_mode above is separate from the hardware reset cause the ESP reports (Reset Cause: {} — e.g. WDT_RESET, DEEPSLEEP_RESET). A boot-failure / crash-counter mechanism guards against boot loops: it tracks boot_count, boots_failed (with boots_failed_max / boots_failed_min) and boots_wdt, and on a bad boot runs a WDT recovery path (check_bootsec, calc_recovery_risk, check_recovery_cache, Performing immediate WDT Recovery) before rebooting (Rebooting from: {} in 3sec, Is Reboot a Continued Session: {}).

Remote power control (demigod)

Power state can be commanded remotely over the mesh. A peer can send a demigod power-control command (Power control demigod command received, demigod_gen_pwr_control) that changes the power mode or triggers power-down (Sending demigod command to power down), subject to a qualification gate (Device disqualified from demigod command).

Sleep

The device uses both light and deep sleep aggressively to save power.

Light sleep

Periodic wake with duty tracking: Awoke from lightsleep | Slept for: {} of {} | Total Sleep: {} | sleep duty: {:.3f}. GNSS RTC sync can block sleep (Block sleep for GNSS RTC Sync).

Deep sleep

State is preserved in RTC memory (f_lib/rtc_mem.py, rtc_v2.py) — notably BLE pairing secrets (BLE secrets found in RTC memory, saving to VFS, later flushed to VFS) and a device snapshot (is_rtc_snapshot, snapshot_coords / snapshot_ticks / snapshot_travel) so navigation state survives sleep. On wake: Awake from Deep Sleep | {} | Free mem: {}.
Touch sensitivity adapts to the power state — [touch] Low battery mode ON/OFF adjusts the capacitive baseline so the Touch Crystal still works as the battery drains. This low-battery touch state (constant BATT_POOR_TOUCH) is part of the touch subsystem, not a value of the battery-health enum above.

Watchdog

wdt_manager.py is not a plain task-WDT wrapper but a condition/blocker engine (WdtManager / WdtConditions / WdtBlockers): an async feed loop (Starting WDT feed loop) feeds the native task watchdog (mpy_machine_wdt / task_wdt), and is disabled while any condition or blocker is active — _should_enable returns False ([WDT {}] cond=[{}] blk=[{}]). The v5.0.3 blockers are ('log rotate', 'vfs write', 'wlan kick'); WLAN_KICK = 2 is new, set and cleared by ble_manager around its WiFi-driver “kick” (v5.0.2 had only the first two). It supports dynamic threshold changes (Updating WDT threshold from: {} to {}) and drives a recovery/reboot path (Performing immediate WDT Recovery, restore_wdt_reboot) that ties into the boot-failure counters above. A separate BLE handoff watchdog (ble_handoff_watchdog, [BLE Watchdog] Handoff stall detected) arbitrates BLE TX-priority ownership between the phone app and the ESP (last_handoff_to_app / last_handoff_to_esp); when a handoff stalls it reclaims TX priority.