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 <noreply@anthropic.com>
This commit is contained in:
Jared Dohrman
2026-08-09 16:56:24 +10:00
co-authored by Claude Fable 5
parent a5c9ba3e01
commit 4c8ddbd29f
2 changed files with 52 additions and 13 deletions
+32 -11
View File
@@ -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 <ssid>`, `set wifi.pwd <password>`, `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 <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 <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 <ssid>`, then `set wifi.pwd <password>` — 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.
+20 -2
View File
@@ -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;
}