Skip to content

PR-07: WiFi provisioning & station management#13

Merged
cryptotomte merged 7 commits into
mainfrom
007-wifi-provisioning
Jul 4, 2026
Merged

PR-07: WiFi provisioning & station management#13
cryptotomte merged 7 commits into
mainfrom
007-wifi-provisioning

Conversation

@cryptotomte

Copy link
Copy Markdown
Owner

PR-07 — WiFi provisioning & station management

Implements FR9 (provisioning) + the WiFi reliability NFR from the master PRD. Spec-kit
artifacts in specs/007-wifi-provisioning/.

What it does

  • First-boot provisioning via a custom SoftAP portal (FR9 decision D1): unconfigured
    device (empty SSID) boots into a WPA2 AP at 192.168.4.1 serving a minimal setup page on a
    standalone esp_http_server; valid SSID/password is persisted to NVS and the device restarts
    (~3 s) to apply. wifi_provisioning is the documented re-escalation fallback (research R1).
  • Station management: STA connect from NVS credentials with parity fixed-interval reconnect
    (10 s retry, +60 s pause after 5 consecutive failures, 5 s health monitor). Never reboots
    (FR-013 — no boot loop).
  • Recovery: config button (GPIO18) held ≥5 s at boot forces provisioning and clears stored
    credentials; a wrong password keeps retrying and stays provisionable.
  • Isolation (FR-014): WifiManager's constructor takes only
    IWifiDriver/IConfigStore/ITimeProvider/ReconnectPolicy — no watering reference — and
    runs on its own FreeRTOS task with no shared mutex; WiFi outages never touch watering.

Architecture

New network component, split at the IWifiDriver seam (same pattern as the sensor drivers):

  • Pure, host-tested: WifiManager state machine, validateWifiCredentials, decideBootMode.
  • Hardware, target-only (excluded from the linux build): EspWifiDriver
    (esp_wifi/esp_netif/esp_eventWifiEvent queue) and ProvisioningPortal.
  • Reuses PR-06 IConfigStore for credentials (empty SSID = unconfigured; values never logged).
  • app_main adds esp_netif_init() + esp_event_loop_create_default() after NVS;
    pumps_force_off() stays the first action. diag_console gains a wifi status command.

Verification

  • Host tests: 178/0 (linux target) — reconnect schedule (exact 10 s / 60 s boundaries),
    boot-mode truth table, credential validation, no-boot-loop (800 rounds), FR-014 isolation,
    synchronous-staConnect-failure cadence, paused-ignores-stray-disconnect.
  • Both board targets build + board-config verified; app ~1.01 MB (35–36 % free in the
    1.5 MB OTA slot). dependencies.lock and both esp-modbus pins unchanged. No frozen-legacy edits.

Review (CP3)

Five specialist agents reviewed; all core invariants PASS. Findings F1–F6 + M1/M2/M3 fixed and
re-verified (see docs/checkpoints/cp3-007-wifi-provisioning.md).

Deferred (by design)

  • LockedWifiManager (cross-task snapshot) and type nice-to-haves → PR-09 (with the HTTP
    status reader).
  • Known minor: STA/AP netifs not destroyed on the init failure path (bounded, once-called static).
  • HIL checklist (specs/007-wifi-provisioning/checklists/hil.md) runs on the rev1 rig —
    includes config-button polarity (active-LOW, bench-verify) and the brownout watch (QUIRK 4).

🤖 Generated with Claude Code

cryptotomte and others added 7 commits July 4, 2026 10:38
Spec-kit artifacts for PR-07 (WiFi station management + first-boot SoftAP
provisioning), approved at Checkpoint 2. Records the pre-made FR9 decision
(custom SoftAP portal), parity reconnect timing, and the host-testable
IWifiDriver seam. No implementation yet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…vent-loop init

Phase 1+2 of PR-07 (WiFi provisioning). Adds:
- network component (linux-guarded CMake; header-only for now)
- IWifiDriver pure interface (interfaces/) + WifiState types + MockWifiDriver
- Kconfig: WS_PROV_AP_SSID/PASSWORD + WS_WIFI_* reconnect constants (parity defaults)
- app_main: esp_netif_init() + esp_event_loop_create_default() after NVS,
  pumps_force_off() preserved as first action; no WiFi objects constructed yet
- host wifi test suite registered (run_wifi_tests stub)

Verified: host tests 164/0, both board targets build + board-config verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lidation

Phase 3 / User Story 1 of PR-07:
- WifiCredentialValidation (pure): SSID 1-32, password empty-or-8..64, typed reasons
- decideBootMode (pure): button-held or no-credentials => Provisioning
- ProvisioningPortal (target-only): standalone esp_http_server, GET / setup page,
  POST /wifi/config -> validate -> IConfigStore::setWifiCredentials -> 200 + deferred
  esp_restart (~3s, injectable RestartHook); never logs credential values
- app_main: boot-mode decision + portal start in Provisioning mode
  (SoftAP radio bring-up left as TODO(US2/T018); config-button read TODO(US3/T024))

Verified: host tests 168/0 (+4 wifi cases), both board targets build + verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4 / US2 (host-testable core):
- WifiManager (pure, no IDF): STA connect + fixed-interval reconnect (10s retry,
  +60s pause after 5 fails, 5s rssi monitor), Connecting/Connected/Reconnecting/
  ReconnectPaused; GotIp=connected. Constructor injects only IWifiDriver/
  IConfigStore/ITimeProvider/ReconnectPolicy (FR-014: no watering dependency).
  Overflow-guarded failure counter; never reboots (FR-013 no boot loop).
- 6 host tests (MockWifiDriver + FakeTimeProvider): happy path, 10s retry boundary,
  60s pause after 5 fails, disconnect->reconnect, AP-mode suspends, no-block isolation.

Hardware wiring (EspWifiDriver, wifi_task, app_main STA, LED) is the next mission.
Verified: host tests 174/0, both board targets build + verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing-up

Phase 4 / US2 hardware layer (target-only):
- EspWifiDriver: esp_wifi/esp_netif/esp_event; WIFI_EVENT/IP_EVENT handlers push
  WifiEvents onto a FreeRTOS queue (lock-free task hand-off, no watering coupling);
  non-blocking staConnect/staStop/apStart/apStop/pollEvent/rssi; STA/AP exclusive;
  WIFI_STORAGE_RAM (real creds stay in wscfg NVS)
- wifi_task: own FreeRTOS task (mirrors sensor_task) ticking WifiManager @250ms;
  drives BOARD_PIN_STATUS_LED (steady=connected, 500ms toggle=connecting/reconnecting)
- app_main: builds ReconnectPolicy from CONFIG_WS_WIFI_*; provisioning branch apStart
  + portal; station branch WifiManager.begin(Station) + wifi_task_start; resolves the
  US1 apStart and US2 station TODOs. Reuses existing EspTimeProvider.
- diag console: 'wifi' status line from snapshot (no credential values)

Verified: host tests 174/0, both board targets build + verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…boot-loop

Phase 5 / US3 + polish docs:
- app_main: read BOARD_PIN_BTN_CONFIG (GPIO18, active-LOW/pull-up) at boot; held >=5s
  forces provisioning with 100ms status-LED blink; clears WiFi credentials first on an
  already-configured device (emergency reset). pumps_force_off() still first; hold loop
  bounded and only entered when pressed at boot.
- WifiBootMode: pure shouldClearCredentialsOnBoot(credsPresent, buttonHeld)
- host tests: no-boot-loop under permanent failure (800 rounds, bounded counters,
  never Connected/Provisioning), emergency-reset decision -> 176/0
- firmware/CLAUDE.md: reference the FR9 SoftAP-portal decision (specs/007-*)
- HIL checklist specs/007-wifi-provisioning/checklists/hil.md for the rev1 rig

Verified: host 176/0; rev1 1011536B (36% slot free) + rev2 1013920B (35% free),
both board-config verified; dependencies.lock + esp-modbus pins unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agnostics

Addresses CP3 review findings (all verified, host 178/0, both boards green):
- F1: WifiManager routes synchronous staConnect() failure through handleFailure
  so the reconnect cadence advances instead of wedging in Connecting forever
- F4: handleFailure ignores stray failure events while ReconnectPaused (counter
  stays <= failuresBeforePause; 60s deadline not re-armed)
- F3: EspWifiDriver::init() unwinds cleanly on event-handler registration failure
  (unregister handler, esp_wifi_deinit, delete queue) — no leak/dangling handler
- F2: app_main gates provisioning AND station bring-up on WiFi-init success;
  wifi_manager stays null on failure (console reports 'not available')
- F5: correct the WifiConnectionSnapshot concurrency comment (no mutex exists;
  single-writer + benign cross-task diagnostic read); TODO(PR-09) LockedWifiManager
- F6: qualify credential-never-logged citations to (PR-06 FR-004)
- M1: count + WARN dropped WiFi events (queue full drops the newest, corrected)
- M2: log a terminal 'provisioning unavailable' line if AP/portal start fails
- M3: distinct 400 for a missing ssid form field (vs length error)
- host tests: synchronous-staConnect-failure cadence + paused-ignores-stray-disconnect

Known minor (out of scope, pre-existing, bounded): STA/AP netifs are not destroyed
on the init failure paths (idempotent once-called static; WiFi-unavailable outcome).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cryptotomte cryptotomte merged commit 6c4ffd7 into main Jul 4, 2026
3 checks passed
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