Disabling the AutoAP fallback while no WiFi SSID is configured leaves the device
with no network interface at all. WEB_Utils::setup() still starts the web
server, and lwip aborts. The device then reboots in a loop and can only be
recovered over USB.
On a remote or hard-to-reach installation (a hilltop digipeater, for example)
this means physically retrieving the device.
Reproduction
Leave the WiFi SSID empty (normal for a digipeater with no network on site).
Uncheck AutoAP in the web configuration — a natural thing to do to save power
on a solar-powered site.
In WIFI_Utils::startWiFi(), with no SSID and AutoAP disabled, neither STA nor AP
is started:
if(!networkManager->hasWiFiNetworks()){Serial.println("WiFi SSID not set!");if(Config.wifiAutoAP.enabled){Serial.println("Starting AP fallback...");startAutoAP();}return;}
setup() then continues with the network-dependent modules. Three of them guard
on connectivity, one does not:
module
guard
NTP_Utils::setup()
networkManager->isConnected() && ecoMode == 0
SYSLOG_Utils::setup()
networkManager->isConnected()
WX_Utils::setup()
wxsensor.active (not network)
WEB_Utils::setup()
ecoMode == 0 only
So WEB_Utils::setup() registers its handlers, calls OTA_Utils::setup(&server)
and then server.begin() with no netif initialised — hence the lwip assert.
This also explains why ecoMode = 2 (WiFi off) does not crash: the same ecoMode == 0 condition skips the web server. Only the autoAP = false + ecoMode = 0 combination reaches server.begin() without a network.
A second safeguard worth considering: refuse to disable AutoAP from the web form
when no SSID is set, since that combination leaves no way back in.
Tested on a TTGO LoRa32 v2.1.6, V3.2.4.
## Summary
Disabling the AutoAP fallback while no WiFi SSID is configured leaves the device
with **no network interface at all**. `WEB_Utils::setup()` still starts the web
server, and lwip aborts. The device then reboots in a loop and can only be
recovered over USB.
On a remote or hard-to-reach installation (a hilltop digipeater, for example)
this means physically retrieving the device.
## Reproduction
1. Leave the WiFi SSID empty (normal for a digipeater with no network on site).
2. Uncheck AutoAP in the web configuration — a natural thing to do to save power
on a solar-powered site.
3. Reboot.
## Serial output
```
SPIFFS Mounted
Reading config..
Config read successfuly
[NM] Initializing Networking...
Starting Station: <CALL> Version: 2026-04-21
(DigiEcoMode: OFF)
init : LoRa Module ... done!
WiFi SSID not set!
assert failed: tcpip_api_call IDF/components/lwip/lwip/src/api/tcpip.c:497 (Invalid mbox)
Backtrace: 0x40083e2d:0x3ffcd1c0 0x40094df5:0x3ffcd1e0 0x4009a765:0x3ffcd200 ...
Rebooting...
```
…and the same sequence repeats indefinitely.
## Cause
In `WIFI_Utils::startWiFi()`, with no SSID and AutoAP disabled, neither STA nor AP
is started:
```cpp
if (!networkManager->hasWiFiNetworks()) {
Serial.println("WiFi SSID not set!");
if (Config.wifiAutoAP.enabled) {
Serial.println("Starting AP fallback...");
startAutoAP();
}
return;
}
```
`setup()` then continues with the network-dependent modules. Three of them guard
on connectivity, one does not:
| module | guard |
|---|---|
| `NTP_Utils::setup()` | `networkManager->isConnected() && ecoMode == 0` |
| `SYSLOG_Utils::setup()` | `networkManager->isConnected()` |
| `WX_Utils::setup()` | `wxsensor.active` (not network) |
| **`WEB_Utils::setup()`** | **`ecoMode == 0` only** |
So `WEB_Utils::setup()` registers its handlers, calls `OTA_Utils::setup(&server)`
and then `server.begin()` with no netif initialised — hence the lwip assert.
This also explains why `ecoMode = 2` (WiFi off) does *not* crash: the same
`ecoMode == 0` condition skips the web server. Only the `autoAP = false` +
`ecoMode = 0` combination reaches `server.begin()` without a network.
## Suggested fix
Align the guard with the other three modules:
```cpp
void setup() {
if (Config.digi.ecoMode == 0 && networkManager->isConnected()) {
...
server.begin();
}
}
```
A second safeguard worth considering: refuse to disable AutoAP from the web form
when no SSID is set, since that combination leaves no way back in.
Tested on a TTGO LoRa32 v2.1.6, V3.2.4.
<!-- github-sync-id:4949737265 -->
nice findings.... should this fix just be added on web_utils.cpp or elsewhere?
will check this out also as a validation of both ways you mentioned
nice findings.... should this fix just be added on web_utils.cpp or elsewhere?
will check this out also as a validation of both ways you mentioned
<!-- github-comment-id:5050421989 -->
web_utils.cpp — setup() calls server.begin() without checking that a
network interface exists. A guard there stops the lwip assert. That fixes the
crash.
network_manager.cpp — that fixes the actual problem. A unit with no valid
SSID and no AP is unreachable: no configuration page, no way back without
reflashing. That is what happened to me. The AP should start unconditionally when
no usable SSID is configured, whatever the autoAP setting says.
The first prevents the panic, the second prevents a configuration mistake from
turning into a dead device.
## Where the #445 fix belongs
Both places, for two different reasons.
**`web_utils.cpp`** — `setup()` calls `server.begin()` without checking that a
network interface exists. A guard there stops the lwip assert. That fixes the
crash.
**`network_manager.cpp`** — that fixes the actual problem. A unit with no valid
SSID and no AP is unreachable: no configuration page, no way back without
reflashing. That is what happened to me. The AP should start unconditionally when
no usable SSID is configured, whatever the `autoAP` setting says.
The first prevents the panic, the second prevents a configuration mistake from
turning into a dead device.
<!-- github-comment-id:5056033456 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Disabling the AutoAP fallback while no WiFi SSID is configured leaves the device
with no network interface at all.
WEB_Utils::setup()still starts the webserver, and lwip aborts. The device then reboots in a loop and can only be
recovered over USB.
On a remote or hard-to-reach installation (a hilltop digipeater, for example)
this means physically retrieving the device.
Reproduction
on a solar-powered site.
Serial output
…and the same sequence repeats indefinitely.
Cause
In
WIFI_Utils::startWiFi(), with no SSID and AutoAP disabled, neither STA nor APis started:
setup()then continues with the network-dependent modules. Three of them guardon connectivity, one does not:
NTP_Utils::setup()networkManager->isConnected() && ecoMode == 0SYSLOG_Utils::setup()networkManager->isConnected()WX_Utils::setup()wxsensor.active(not network)WEB_Utils::setup()ecoMode == 0onlySo
WEB_Utils::setup()registers its handlers, callsOTA_Utils::setup(&server)and then
server.begin()with no netif initialised — hence the lwip assert.This also explains why
ecoMode = 2(WiFi off) does not crash: the sameecoMode == 0condition skips the web server. Only theautoAP = false+ecoMode = 0combination reachesserver.begin()without a network.Suggested fix
Align the guard with the other three modules:
A second safeguard worth considering: refuse to disable AutoAP from the web form
when no SSID is set, since that combination leaves no way back in.
Tested on a TTGO LoRa32 v2.1.6, V3.2.4.
nice findings.... should this fix just be added on web_utils.cpp or elsewhere?
will check this out also as a validation of both ways you mentioned
Where the #445 fix belongs
Both places, for two different reasons.
web_utils.cpp—setup()callsserver.begin()without checking that anetwork interface exists. A guard there stops the lwip assert. That fixes the
crash.
network_manager.cpp— that fixes the actual problem. A unit with no validSSID and no AP is unreachable: no configuration page, no way back without
reflashing. That is what happened to me. The AP should start unconditionally when
no usable SSID is configured, whatever the
autoAPsetting says.The first prevents the panic, the second prevents a configuration mistake from
turning into a dead device.
this has been solved for V4.0.1 please test as it will be available soon (had to do a few changes though)