From 4c8ddbd29f948d1ada90eeb36a004150d57e913c Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Sun, 9 Aug 2026 16:56:24 +1000 Subject: [PATCH] feat(companion-wifi): pin-derived recovery AP password with on-screen pin Mirror the BLE pin rules on WiFi builds so the recovery AP always has a knowable password: devices with a display get a random per-session pin shown as Pin:NNNNNN on the home screen, headless devices default to 123456, and 'set pin' overrides both persistently. The AP password is the active pin zero-padded to 8 digits (WPA2 minimum), so a fresh device is never an open AP. Co-Authored-By: Claude Fable 5 --- eastmesh-docs/custom-cli.md | 43 +++++++++++++++++++++-------- examples/companion_radio/MyMesh.cpp | 22 +++++++++++++-- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/eastmesh-docs/custom-cli.md b/eastmesh-docs/custom-cli.md index d661a9c8..e8688108 100644 --- a/eastmesh-docs/custom-cli.md +++ b/eastmesh-docs/custom-cli.md @@ -265,15 +265,36 @@ Companion Wi-Fi builds also still support the existing rescue commands such as: When a companion Wi-Fi device has no Wi-Fi credentials configured, or cannot connect to its configured network for 60 seconds, it broadcasts its own `EastMesh-WiFi` -access point so it can be rescued without a serial cable: +access point so it can be rescued without a serial cable. -- the AP password is the device's 6-digit pin (`set pin ...`) zero-padded to 8 digits, - e.g. pin `123456` gives Wi-Fi password `00123456` -- if no pin has been set, the AP is **open** — set a pin first if the device is in a - public area -- connect to the AP, then open the rescue CLI with `telnet 192.168.4.1` (or - `nc 192.168.4.1 23`); all rescue commands above are available -- typical recovery: `set wifi.ssid `, `set wifi.pwd `, `reboot` -- while the AP is up the device keeps retrying its configured network; as soon as the - station connection succeeds the recovery AP shuts down automatically -- the MeshCore app can also reach the device through the AP on the usual TCP port +**The AP password is the device pin, zero-padded to 8 digits** (WPA2 requires at +least 8 characters). The pin follows the same rules as the Bluetooth pairing pin on +BLE builds: + +| Device | Active pin | `EastMesh-WiFi` password | +| --- | --- | --- | +| Has a screen, no pin set | random 6-digit pin each boot, shown as `Pin:NNNNNN` on the home screen | `00NNNNNN` — read it off the screen | +| No screen, no pin set | `123456` (default) | `00123456` | +| Pin set via `set pin ` | your configured pin (any device) | your pin zero-padded to 8 digits, e.g. pin `4242` → `00004242` | + +Notes on the pin: + +- on devices with a screen, the pin is only random while no pin has been saved; run + `set pin ` for a fixed password (takes effect next boot) +- headless devices in public areas should always get a custom pin — `00123456` is a + documented default, so treat it like a default router password + +Recovery steps: + +1. join the `EastMesh-WiFi` network with the password from the table above +2. open the rescue CLI with `telnet 192.168.4.1` (or `nc 192.168.4.1 23`) — all + rescue commands above are available +3. `set wifi.ssid `, then `set wifi.pwd ` — the device immediately + retries the network with the new credentials +4. `reboot` (or just wait — see below) + +While the AP is up the device keeps retrying its configured network in the +background; as soon as the station connection succeeds, the recovery AP shuts down +automatically (this also drops your rescue session — that's the sign it worked). +The MeshCore app can also reach the device through the AP on the usual companion +TCP port. diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6c1ed2ee..6e075d19 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1041,6 +1041,24 @@ void MyMesh::begin(bool has_display) { } else { _active_ble_pin = _prefs.ble_pin; } +#elif defined(ESP32) && defined(WIFI_SSID) + // WiFi builds: the pin doubles as the recovery AP password (zero-padded to 8 digits), + // mirroring the BLE pin rules: shown on the display when there is one (random per + // session), static default 123456 on headless devices, 'set pin' overrides both. + if (_prefs.ble_pin == 0) { +#ifdef DISPLAY_CLASS + if (has_display) { + StdRNG rng; + _active_ble_pin = rng.nextInt(100000, 999999); // random pin each session + } else { + _active_ble_pin = 123456; + } +#else + _active_ble_pin = 123456; +#endif + } else { + _active_ble_pin = _prefs.ble_pin; + } #else _active_ble_pin = 0; #endif @@ -2370,8 +2388,8 @@ void MyMesh::startRecoveryAP() { char ap_pwd[12]; const char* pwd = NULL; - if (_prefs.ble_pin != 0) { // WPA2 needs >= 8 chars, so zero-pad the pin - sprintf(ap_pwd, "%08lu", (unsigned long) _prefs.ble_pin); + if (_active_ble_pin != 0) { // WPA2 needs >= 8 chars, so zero-pad the pin + sprintf(ap_pwd, "%08lu", (unsigned long) _active_ble_pin); pwd = ap_pwd; }