Add Wi-Fi commissioning: SoftAP captive portal with IPv6 support

Implements first-boot Wi-Fi setup with no companion app required: a
SoftAP + captive portal (DNS wildcard redirect, DHCP option 114, HTTP
form) lets the user submit Wi-Fi credentials from any browser. On
submit, credentials are saved to NVS and the device switches to STA
mode; on subsequent boots it reconnects automatically via stored
credentials. dns_server.c/.h are vendored from ESP-IDF's official
captive_portal example (Unlicense/CC0).

Requests both link-local and global IPv6 addresses once the STA
connection has an IPv4 address (not on WIFI_EVENT_STA_CONNECTED --
esp_netif_create_ip6_linklocal() reliably fails there because
esp-netif's own handler, which marks the lwIP netif "up", hasn't run
yet due to event-handler registration order). Verified end-to-end on
real ESP32-C6 hardware: SoftAP comes up, credential submission works,
STA reconnects on reboot, and both a link-local and a router-assigned
global IPv6 address are obtained via SLAAC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ronny Eia
2026-07-09 21:24:38 +02:00
parent f1be175dc2
commit af0c2da146
9 changed files with 796 additions and 1 deletions

View File

@@ -6,6 +6,17 @@ Also owns Wi-Fi/MQTT configuration storage for now (not broken out into its own
Future transports (Zigbee, Thread, Matter) should implement the same publish API as alternate backends behind this component's interface, without changing callers.
## Wi-Fi commissioning
`include/commissioning.h` / `src/commissioning.cpp` implement first-time Wi-Fi setup, no companion app required:
- On boot, `try_stored_credentials()` attempts to connect using credentials saved in NVS from a previous session.
- If none exist (or the caller chooses to), `start_commissioning()` opens a SoftAP (`Kvida-XXXXXX`, derived from the MAC) with a captive portal: a DNS server (`src/dns_server.c`/`.h`, vendored from ESP-IDF's official `captive_portal` example, Unlicense/CC0) answers every query with the AP's own IP, and DHCP option 114 plus a 404-redirects-to-`/` HTTP handler get most phones/laptops to auto-open the setup page (`src/portal.html`) in a plain browser.
- Submitting the form (`POST /connect`) saves the credentials to NVS and switches Wi-Fi to STA mode. The AP is open (no password) by design -- see the comment above `ap_config.ap.authmode` in `commissioning.cpp` for the reasoning and the tradeoff.
- A 5-minute `esp_timer` closes the commissioning window automatically if nobody submits credentials.
- **IPv6**: once STA gets its IPv4 address (`IP_EVENT_STA_GOT_IP` -- not `WIFI_EVENT_STA_CONNECTED`, see the comment in `commissioning.cpp` for why that timing matters), `esp_netif_create_ip6_linklocal()` requests a link-local address (`CONFIG_LWIP_IPV6=y` is set explicitly in `sdkconfig.defaults`); `IP_EVENT_GOT_IP6` is logged when it arrives. Verified end-to-end on real ESP32-C6 hardware.
- Not yet wired up: re-entering commissioning on an already-configured device needs a physical trigger (the user button) per AGENTS.md's "configuration mode requires physical user interaction" principle -- blocked on `drivers` having a real button implementation. Network scanning (SSID dropdown instead of free text) was left out of this first pass to keep scope tight.
TODO: built on ESP-IDF's own `esp_wifi` and `mqtt_client` (`esp-mqtt`) components once real code lands — both ship with ESP-IDF, no extra registry dependency needed.
TODO: A/B OTA update-checking belongs here, driven by ESP-IDF's native `esp_ota_ops` against the `ota_0`/`ota_1` partitions in `partitions.csv` — no extra dependency needed either.