Commit graph

18 commits

Author SHA1 Message Date
jedarden
e676694fdc feat(provisioning): carry ms_ip in payload for mDNS-less networks
Add optional mothership IP override to the provisioning flow so nodes
on networks where mDNS is blocked (enterprise WiFi, mesh, VLANs) can
connect on first boot without manual intervention.

- Add ms_ip field to provisioning Payload and request structs
- Firmware writes ms_ip to both NVS_KEY_MS_IP and NVS_KEY_MS_IP_PROV
- Discovery prefers provisioned IP on first attempt, falls back to
  mDNS, then cached IP
- Web Serial wizard adds Mothership IP field in Network Troubleshooting
- Auto-populates IP when browser accesses dashboard by IP address
- Document when/how to use the override in docs/notes/mdns-override.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 01:06:05 -04:00
jedarden
982f038e75 fix(firmware): report actual flash chip size in hello message via esp_flash_get_size()
Replaces the hardcoded flash_mb:16 with a runtime query so the mothership
receives the correct physical flash size regardless of the hardware variant.
On the 4MB XMC board this will now report 4; on a 16MB board it reports 16.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:19:45 -04:00
jedarden
a3dbbf1170 fix: support special characters in WiFi credentials
- SSID input: add autocorrect=off, autocapitalize=none, spellcheck=false to
  prevent mobile browsers from silently altering SSIDs with special chars
- Password input: same attrs for consistency
- Firmware: accept WPA/WPA2 mixed mode (WIFI_AUTH_WPA_WPA2_PSK) so networks
  with special characters in the password connect regardless of WPA version
- Firmware: detect open networks (empty password) and use WIFI_AUTH_OPEN so
  passwordless networks are not rejected by the auth threshold

JSON encoding path (JSON.stringify → TextEncoderStream → cJSON) already
handles all characters correctly; these changes prevent browser-side mangling
and firmware-side connection rejection.
2026-04-17 08:42:20 -04:00
jedarden
460dac8f4d fix(firmware): use %u cast for uint32_t in ESP_LOGI to fix -Wformat build error 2026-04-17 00:31:09 -04:00
jedarden
cc84a6eea8 Add WiFi provisioning observability and extend firmware window
- Show real-time status messages and a collapsible log panel during WiFi provisioning
- Thread addProvLog/setProvStatus callbacks through provisionAndSend and sendPayloadOverSerial
- Log every stage: mothership fetch, payload assembly, port open retries, serial send/response
- All log lines also go to browser console.log/warn/error
- Firmware: extend provisioning window from 10s to 120s for fresh boards (15s for re-provisioning)
- Firmware: include MAC address in SPAXEL READY message for display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:35:39 -04:00
jedarden
d7c1adc260 Fix ESP-IDF 5.x firmware compilation for ESP32-S3 build
Port firmware/main to ESP-IDF 5.2 API:
- Add idf_component.yml with mdns and esp_websocket_client managed deps
- Rename esp_ota → app_update, esp_wifi_csi_info_t → wifi_csi_info_t
- Fix freertos/semphr.h include path rename
- Add missing headers: esp_mac.h, esp_netif.h, driver/temperature_sensor.h, string.h, math.h
- Add led.c to SRCS, fix xTaskCreate arg count (7→6)
- Use IDF 5.x temperature_sensor API in place of stub
- Fix mdns_query_ptr signature (add max_results arg)
- Fix url_decode isxdigit unsigned char cast
- Add flash size config (16MB) to sdkconfig.defaults
- Pin managed component versions in dependencies.lock
- Add sdkconfig (generated) to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 15:35:03 -04:00
jedarden
c79f7ffee4 fix(firmware): declare mdns as managed component dependency
mdns was moved out of the built-in ESP-IDF tree in v5.x and must be
declared via idf_component.yml for the component manager to resolve it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 15:21:37 -04:00
jedarden
ab47adc88a fix(firmware): add led.c to CMakeLists SRCS
led.c was missing from the build, causing undefined reference errors
for led_init, led_stop_blink, and led_blink_identify at link time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 15:15:06 -04:00
jedarden
0aa6046f82 feat: implement firmware LED blink handler for identify command
- Add led_init() call during firmware initialization in app_main()
- Add led_stop_blink() call on WebSocket disconnect
- LED GPIO configurable via CONFIG_SPAXEL_LED_GPIO (default GPIO8)
- Blink runs in FreeRTOS task at ~5Hz (100ms on/100ms off)
- Any running blink is cancelled when new identify message received

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 07:01:42 -04:00
jedarden
a42c5e7ea1 feat: wire NTP client into firmware build and initialization
- Add ntp.c to CMakeLists.txt SRCS so it's compiled and linked
- Load ntp_server from NVS in load_nvs_config() (default: pool.ntp.org)
- Add ntp_server field to spaxel_state_t
- Initialize NTP after WiFi connects with 10s sync timeout, WARN on failure
- Re-sync NTP after WiFi reconnect (WIFI_LOST state)
- Start periodic 10-minute resync timer via esp_timer
- Add ntp_synced boolean to health JSON message
- Handle ntp_server field in downstream config message
- Fix periodic resync callback to properly stop/restart SNTP

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 13:19:07 -04:00
jedarden
e44dd345f6 feat: implement comprehensive /healthz endpoint
Add complete health check implementation for Docker HEALTHCHECK and
Traefik health routing with:

Response fields:
- status: "ok" or "degraded"
- uptime_s: seconds since mothership boot
- version: mothership version string
- nodes_online: count of connected nodes
- db: "ok" or "failing" (SELECT 1 with 100ms timeout)
- load_level: 0-3 from load shedding state
- reason: human-readable explanation (only when degraded)

HTTP status codes:
- 200 for healthy (status="ok")
- 503 for degraded (status="degraded")

Degraded conditions:
- Database unreachable
- Load level 3 sustained for >60 seconds
- No nodes connected after 5 minutes uptime

Docker HEALTHCHECK updated to verify status="ok" response.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 11:09:36 -04:00
jedarden
391ed884e4 feat: implement NVS schema migration on boot
Implement versioned NVS key migration on ESP32-S3 firmware so
OTA-updated firmware gracefully handles NVS written by older versions.

- Add nvs_migration.c/h with migration framework
- On boot, read schema_ver from NVS; initialize to 1 if missing
- Run migrations sequentially if schema_ver < COMPILED_NVS_VERSION
- Each migration commits after each write for durability
- Log all migration steps to UART for debugging
- Example migration v1→v2: rename 'ms_ip' to 'mothership_ip',
  add 'ntp_server' with default 'pool.ntp.org'
- Migration failure leaves NVS in consistent state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 10:27:38 -04:00
jedarden
ee1caf6ed8 feat: dual-partition OTA rollback validation with 60s role timeout
Update partition layout to 4MB factory/ota_0/ota_1 partitions for larger
firmware images. Add a 60-second validation timer that starts after the
hello message is sent on boot from an OTA partition. The partition is only
marked valid (via esp_ota_mark_app_valid_cancel_rollback) after receiving
a role message from the mothership. If the timer expires without role
receipt, the partition stays unconfirmed and the bootloader rolls back to
the previous firmware on next reset.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 01:43:29 -04:00
jedarden
d2e5b4d4a0 feat: implement passive radar auto-detection of router AP
Automatically detect the home router as a passive radar TX source,
eliminating need for a dedicated active TX node.

Firmware changes:
- During hello message, include ap_bssid and ap_channel from esp_wifi_sta_get_ap_info()

Mothership changes:
- On hello: extract ap_bssid; if >=80% of nodes report same BSSID create virtual node entry with virtual=1
- OUI lookup: embedded IEEE OUI registry as Go map compiled via go:embed; display router brand
- Detect AP BSSID change (router reboot/replacement) and emit system alert
- SQLite nodes table: add virtual BOOL, node_type TEXT, ap_bssid TEXT, ap_channel INT columns

Dashboard changes:
- Show "I detected your router (ASUS). Place it on the floor plan..." notification
- Render virtual AP nodes with distinct router icon in 3D view
- Drag-to-place virtual node (distinct router icon) in 3D editor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:03:03 -04:00
jedarden
f76ab62698 feat(firmware): OTA SHA-256 verification and captive portal URL decoding
- Add git-based version header generation for firmware builds
- Implement SHA-256 hash verification for OTA downloads with mbedtls
- Add URL decoding for captive portal form parsing (spaces, special chars)
- Add mbedtls dependency for SHA-256 verification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 19:56:11 -04:00
jedarden
bcfd1e3f16 feat(protocol): full bidirectional node protocol over WebSocket
- firmware: wire on-device motion hints to websocket_send_motion_hint()
  with 1s rate-limit; csi_set_rate() now writes to g_state.packet_rate
- firmware: websocket_send_motion_hint() sends variance + MAC + timestamp
  to mothership so rate controller ramps ahead of server-side detection
- mothership: RateController.OnMotionHint() preemptively ramps adjacent
  nodes via SetAdjacentNodesFn callback (topology-aware burst propagation)
- mothership: idle timeout extended to 30s; variance_threshold=0 in active
  mode (server handles detection), DefaultVarianceThreshold=1.0 in idle
- mothership: SendRoleToMAC() exposes dynamic role changes post-connect
- mothership: SendOTAToMAC() enables pushing firmware updates to nodes
- mothership: OTA status events are now logged with state and progress %

Protocol is backward-compatible: binary CSI frames work as in Phase 1;
JSON control messages are additive on the same single WebSocket per node.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 23:14:19 -04:00
jedarden
fb691904c6 feat(dashboard): per-link motion presence indicator with amplitude time series
- Dashboard hub broadcasts motion state changes immediately on transition
  (idle↔motion) via BroadcastMotionState; periodic state snapshots include
  motion_states for new client init
- Per-link presence badge (green CLEAR / red MOTION) rendered in link list
  alongside global presence indicator in status bar
- Amplitude mean time-series chart (60 s rolling window) for selected link,
  line segments colored by motion state at each sample
- Fix: links created from JSON link_active/state events now initialize
  ampHistory and lastAmpSample so time-series accumulates from first frame

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:55:33 -04:00
jedarden
f178496c83 feat(firmware): ESP32-S3 firmware skeleton with CSI/BLE/WebSocket
- State machine: 7-state lifecycle (BOOT→WIFI→DISCOVERY→CONNECTED,
  degraded modes WIFI_LOST/MOTHERSHIP_UNAVAILABLE/CAPTIVE_PORTAL)
- WiFi: STA connection with exponential backoff, mDNS discovery with
  cached IP fallback, captive portal AP for provisioning
- WebSocket: bidirectional comms, binary CSI frames (24B header + I/Q),
  JSON hello/health/ble/ota_status upstream, role/config/ota/reboot
  downstream, OTA task with progress reporting
- CSI: promiscuous mode capture, queue-based processing, passive BSSID
  filter, on-device variance tracking (Welford's) for motion hints
- BLE: passive scanning on Core 0, 60-device cache, 5s reporting
- NVS: 15-key schema with versioning, role/rate persistence

Complete: ESP32 firmware skeleton, passive radar, BLE scanning
Remaining: Dashboard skeleton, Docker packaging (Phase 1 items 5-6)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 07:20:55 -04:00