FelipeMdeO opened a new pull request, #19725:
URL: https://github.com/apache/nuttx/pull/19725
# arch/[risc-v|xtensa]: fix Wi-Fi station reconnection after AP-side
disconnect
## Summary
An ESP32 Wi-Fi station running NuttX never recovers from an AP-initiated
disconnection. Once the access point goes away — a reboot, a brief outage, a
deauthentication — the station drops the link and stays down forever. The
interface remains `UP` without `RUNNING` and only a manual
`wapi psk` + `wapi essid` brings it back. Nothing is logged, so in the field
this looks like the board "losing the network" for no reason.
This is a regression. The disconnect handler in `esp_wifi_event_handler.c`
reconnects only when the reported reason is `WIFI_REASON_ASSOC_LEAVE`:
```c
case WIFI_EVENT_STA_DISCONNECTED:
wlinfo("Wi-Fi station disconnected, reason: %u\n", reason);
esp_wlan_sta_disconnect_hook();
if (reason == WIFI_REASON_ASSOC_LEAVE)
{
work_queue(LPWORK, &g_wifi_reconnect_work, esp_reconnect_work_cb,
NULL, 0);
}
break;
```
The `reason` field carries an 802.11 reason code, extended by ESP-IDF with
vendor values above 200 for locally detected conditions
(`wifi_err_reason_t` in `esp_wifi_types_generic.h`):
```c
WIFI_REASON_AUTH_EXPIRE = 2, /* Authentication expired */
WIFI_REASON_AUTH_LEAVE = 3, /* Deauthentication due to leaving */
WIFI_REASON_ASSOC_EXPIRE = 4, /* Association expired */
WIFI_REASON_ASSOC_LEAVE = 8, /* Deassociated due to leaving */
WIFI_REASON_BEACON_TIMEOUT = 200, /* Beacon timeout */
WIFI_REASON_NO_AP_FOUND = 201, /* No AP found */
```
`WIFI_REASON_ASSOC_LEAVE` is the value the stack reports for a *locally
initiated* disconnect — confirmed on hardware, since running `wapi essid`
logs
`reason: 8` because the tool disconnects before associating. So the surviving
branch covers the one case where reconnecting is not needed, and every
AP-initiated reason is left unhandled.
Before the Wi-Fi driver refactors the same handler used an explicit intent
flag, which is the rule ESP-IDF documents — reconnect on any reason unless
the
disconnection was requested locally:
```c
case WIFI_ADPT_EVT_STA_DISCONNECT:
g_sta_connected = false;
esp_wlan_sta_set_linkstatus(false);
if (g_sta_reconnect)
{
ret = esp_wifi_connect();
}
break;
```
`g_sta_reconnect` was set true by `esp_wifi_sta_connect()` and false by
`esp_wifi_sta_disconnect()`, so the driver always knew whether it had asked
to
leave. The refactors replaced that flag with the reason-code test:
| Commit | Date | Chips |
|---|---|---|
| `1f7c3a32e5` | 2025-08-21 | ESP32-C3 / C6 |
| `20ff68bd650` | 2025-09-01 | ESP32 / S2 / S3 |
The intent cannot be derived from the reason code: the reason is what the
*other* end reports, while the intent is state this driver owns. The correct
logic is still present in tree today, in
`arch/risc-v/src/esp32c3-legacy/esp32c3_wifi_adapter.c`, so the same chip
currently ships two drivers with opposite behaviour.
This PR restores the intent flag on both architectures.
`esp_reconnect_work_cb`
re-checks the flag because the user may request `ifdown` between the work
being
queued and the work running. Retry cadence is left to the event loop,
matching
ESP-IDF: a failed attempt raises another `STA_DISCONNECTED` (reason 201)
which
schedules the next one, self-throttled by the radio's scan time (~2.4 s
measured).
The `failure_retry_cnt` writes in `esp_wifi_sta_disconnect()` are left
untouched, so this PR only adds the flag there. A comment marks them as
having no documented effect: ESP-IDF states the field applies only when
`scan_method` is `WIFI_ALL_CHANNEL_SCAN`, and NuttX never selects it — the
only assignment of `scan_method` anywhere in the tree is
`WIFI_FAST_SCAN` in the legacy ESP32-C3 driver, and neither the common driver
nor the bundled HAL ever writes the field. Removing the now-redundant writes,
and revisiting whether that field is the right mechanism at all, is better
handled as a follow-up PR so this one stays focused on the regression.
## Impact
* Impact on user: yes, and this is the point. A station that loses its AP now
reconnects by itself instead of staying offline until rebooted or manually
re-associated.
* Impact on hardware: all Espressif chips with Wi-Fi station support, on both
`arch/risc-v` and `arch/xtensa`. Validated on ESP32-C3.
* Impact on compatibility: a user-requested disconnect (`ifdown`,
`esp_wifi_sta_disconnect(false)` from the ioctl path) still does not
reconnect — that path is what the flag protects. No call sites changed:
`esp_wlan_netdev.c` already passes `false` for the user disconnect and the
four internal disconnects in `esp_wifi_api.c` already pass `true`.
## Testing
Two boards on the same access point, in the same time window, so the trigger
is
identical for both:
| | Device under test | Reference |
|---|---|---|
| Board | ESP32-C3-DevKit | ESP32-C6-DevKit |
| Firmware | NuttX, stock `esp32c3-devkit:wifi` defconfig | ESP-IDF v6.0.1,
`examples/wifi/getting_started/station` |
| Only changes | `CONFIG_DEBUG_WIRELESS_INFO=y` + Wi-Fi credentials | log
the reason code; retry without a limit |
The NuttX side runs the unmodified board defconfig — no application on top,
so
the Wi-Fi driver is the only thing under test.
**Reproduction: power the access point off for 5 minutes, then back on.**
NuttX, ESP32-C3 — the AP disappears:
```
I (14465) wifi:bcn_timeout,ap_probe_send_start
I (14482) wifi:ap_probe_send over, reset wifi status to disassoc
I (14482) wifi:state: run -> init (0xc800)
esp_wifi_event_handler: Wi-Fi station disconnected, reason: 200
I (14483) wifi:<ba-del>idx:0, tid:0
I (14483) wifi:<ba-del>idx:1, tid:7
```
Nothing follows. No scan, no association attempt, no further log line — the
remaining entries are the radio tearing down block-ack state at the same
millisecond. The AP came back a few minutes later; 21 minutes after that the
board was still offline:
```
nsh> ifconfig
wlan0 Link encap:Ethernet HWaddr 80:65:99:2d:4e:3c at UP mtu 1500
inet addr:192.168.15.144 DRaddr:192.168.15.1 Mask:255.255.255.0
```
`UP` without `RUNNING`: carrier down, no recovery.
ESP-IDF, ESP32-C6, same outage — note the **same reason 200**:
```
W (1121797) wifi station: STA_DISCONNECTED, reason: 200
I (1121797) wifi station: retry to connect to the AP (attempt 1)
W (1124217) wifi station: STA_DISCONNECTED, reason: 201
I (1124217) wifi station: retry to connect to the AP (attempt 2)
...
W (1473207) wifi station: STA_DISCONNECTED, reason: 201
I (1473207) wifi station: retry to connect to the AP (attempt 147)
I (1494777) wifi station: got ip:192.168.15.161
```
147 attempts spaced ~2.4 s apart, then an address as soon as the AP was
serving
again. Both stacks received the same event; only the handling differs.
**With this patch applied** — this is why the PR is a draft. The patched
firmware is built and running on the ESP32-C3, and manual association,
`ifdown` and user-requested disconnect all still behave as before, but the
access-point outage has not been repeated yet against the fixed build. That
run will be added here before the PR leaves draft. Expected result: the same
`reason: 200`, followed by reconnection attempts and a restored link.
Reviewers who want to reproduce either side need only two boards and a power
switch on their access point.
**Related reports that may share this root cause, though neither was
diagnosed:**
https://github.com/apache/nuttx/issues/19137
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]