Honor saved WebUI off during unconfigured boot setup
An enabled MQTT bridge without a saved SSID forced WebConfig back on at boot, even after set webui off. Its setup AP also suspended an enabled ESP-NOW bridge. Use first-time MQTT setup as the default only when no WebUI choice is saved. Apply the saved on/off setting afterward in Repeater and Room Server. Preserve first-time provisioning and explicit starts. Add executable boot regressions for both roles with unset, on and off WebUI settings and configured/unconfigured WiFi. Update setup and picker guidance.
This commit is contained in:
1 parent
16399a019d
commit
af69918094
7 files changed
+87
-20
No files matched your search
+3
-2
@@ -194,8 +194,9 @@ absolute 30-minute window. Browser activity or a phone left associated with the
|
||||
AP does not extend it. If no SSID has been saved when the window expires,
|
||||
WebConfig closes and the ESP32 WiFi radio remains off automatically for the
|
||||
remainder of that boot. A reboot or power cycle starts a new 30-minute setup
|
||||
window; the timeout is deliberately not written to preferences. An
|
||||
administrator can still override the automatic cutoff with an explicit
|
||||
window unless `set webui off` has been saved. That choice prevents automatic
|
||||
setup even without WiFi credentials. The timeout itself is not saved.
|
||||
An administrator can still override the automatic cutoff with an explicit
|
||||
`start webconfig` command.
|
||||
|
||||
Once an SSID is saved, the provisioning cutoff no longer applies. When WiFi is
|
||||
|
||||
@@ -2175,7 +2175,7 @@
|
||||
"Use get logging.output and set logging.output off|usb|wifi|both to choose the saved output mode. Avoid both when two consumers publish the same packets to one broker."
|
||||
);
|
||||
extra.push(
|
||||
"With no saved SSID, the setup AP stays available for 30 minutes after each boot, then Wi-Fi powers off automatically until reboot or an explicit start webconfig command. A configured Wi-Fi mode keeps reconnecting instead."
|
||||
"With no saved SSID, automatic setup offers a 30-minute AP window per boot. Use set webui off to disable automatic setup after reboot. An explicit start webconfig command can reopen it. A configured Wi-Fi mode keeps reconnecting instead."
|
||||
);
|
||||
} else if (profile.logging === "usb-runtime") {
|
||||
if (!isFullCompanion(profile)) {
|
||||
|
||||
@@ -326,7 +326,10 @@ folder transport.
|
||||
again after future reboots; `set webui off` closes it and disables that boot
|
||||
start. `get webui` reports the saved on/off state plus whether the portal is
|
||||
inactive, joining WiFi, serving a setup AP, or serving a LAN URL. The default is
|
||||
`off` on repeater and room-server builds. On display-equipped repeaters, an
|
||||
`off` on repeater and room-server builds. An unconfigured MQTT node can open
|
||||
first-time setup while no WebUI choice has been saved. An explicit
|
||||
`set webui off` prevents that AP from returning after reboot and pausing
|
||||
ESP-NOW. On display-equipped repeaters, an
|
||||
otherwise-unused triple click toggles the same saved setting.
|
||||
Consequently, `> off, http://192.168.1.130/` means the saved boot setting is
|
||||
off while a temporary WebConfig session is currently active at that URL.
|
||||
@@ -355,9 +358,10 @@ saved `webui on`.)
|
||||
LAN mode otherwise remains active until reboot. On expanded FULL builds, an
|
||||
unconfigured automatic setup AP receives one absolute 30-minute window per
|
||||
boot, then powers WiFi off even if a client remains attached; rebooting starts
|
||||
a new automatic window. An administrator can explicitly run `start webconfig`
|
||||
again without rebooting. Once an SSID is saved, the cutoff no longer applies
|
||||
and the selected WiFi/MQTT mode keeps reconnecting. Other setup sessions retain
|
||||
a new automatic window unless `webui off` is saved. An administrator can
|
||||
explicitly run `start webconfig` again without rebooting. Once an SSID is saved,
|
||||
the cutoff no longer applies and the selected WiFi/MQTT mode keeps reconnecting.
|
||||
Other setup sessions retain
|
||||
their profile's idle timeout.
|
||||
|
||||
Full Repeater and Room Server suspend unconfigured network bridges during
|
||||
|
||||
@@ -305,7 +305,9 @@ the no-output and USB settings because `set usb.logging off|on` is persistent.
|
||||
On a fresh unified FULL install with no saved SSID, the setup AP and WiFi radio
|
||||
remain available for 30 minutes per boot, then turn off automatically until the
|
||||
next reboot or power cycle. An explicit administrator `start webconfig` remains
|
||||
available as an override. A saved SSID switches to the normal indefinite
|
||||
available as an override. Save `set webui off` to close the portal and prevent
|
||||
its automatic return after reboot, including on an ESP-NOW node without WiFi
|
||||
credentials. A saved SSID switches to the normal indefinite
|
||||
reconnect behavior instead.
|
||||
|
||||
Fresh ESP32 Full infrastructure defaults to USB logging off. Existing saved
|
||||
|
||||
@@ -4160,12 +4160,15 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
mesh::hilStartupTrace("mesh_webconfig_begin");
|
||||
#if defined(WITH_WEBCONFIG) && !defined(WEBCONFIG_NO_AUTO_AP)
|
||||
bool start_webui = WebConfigServer::loadEnabled(false);
|
||||
bool start_webui = false;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Offer first-boot MQTT setup only as the default. A saved WebUI choice,
|
||||
// including off on an unconfigured ESP-NOW node, must take precedence.
|
||||
start_webui = _prefs.bridge_enabled
|
||||
&& !WebConfigServer::hasConfiguredWiFi(_cli.getObserverPrefs());
|
||||
#endif
|
||||
start_webui = WebConfigServer::loadEnabled(start_webui);
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// Preserve the MQTT observer's first-boot setup experience even though the
|
||||
// persistent WebUI master switch defaults off on infrastructure roles.
|
||||
start_webui = start_webui || (_prefs.bridge_enabled
|
||||
&& !WebConfigServer::hasConfiguredWiFi(_cli.getObserverPrefs()));
|
||||
if (start_webui && !WebConfigServer::hasConfiguredWiFi(_cli.getObserverPrefs())) {
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
if (espnow_bridge.isRunning()) espnow_bridge.end();
|
||||
|
||||
@@ -2828,10 +2828,14 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
#endif
|
||||
|
||||
#if defined(WITH_WEBCONFIG) && !defined(WEBCONFIG_NO_AUTO_AP)
|
||||
bool start_webui = WebConfigServer::loadEnabled(false);
|
||||
bool start_webui = false;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// First-boot MQTT setup is a default, never an override of saved WebUI off.
|
||||
start_webui = _prefs.bridge_enabled
|
||||
&& !WebConfigServer::hasConfiguredWiFi(_cli.getObserverPrefs());
|
||||
#endif
|
||||
start_webui = WebConfigServer::loadEnabled(start_webui);
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
start_webui = start_webui || (_prefs.bridge_enabled
|
||||
&& !WebConfigServer::hasConfiguredWiFi(_cli.getObserverPrefs()));
|
||||
if (start_webui && !WebConfigServer::hasConfiguredWiFi(_cli.getObserverPrefs())) {
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
if (espnow_bridge.isRunning()) espnow_bridge.end();
|
||||
|
||||
@@ -158,7 +158,7 @@ struct MQTTBridge : AbstractBridge {
|
||||
};
|
||||
@STOP_OWNED_WIFI@
|
||||
struct WebConfigServer {
|
||||
static bool enabled;
|
||||
static int saved_enabled;
|
||||
static bool allow_start;
|
||||
bool running = false, stopping = false, owns;
|
||||
uint32_t _setup_started_at = 0;
|
||||
@@ -170,7 +170,10 @@ struct WebConfigServer {
|
||||
template<class... Args> WebConfigServer(void*, void* prefs, bool owner, Args...) : owns(owner) {
|
||||
if (prefs) strcpy(_wifi_ssid, static_cast<ObserverPrefs*>(prefs)->wifi_ssid);
|
||||
}
|
||||
static bool loadEnabled(bool) { return enabled; }
|
||||
static bool loadEnabled(bool default_value) {
|
||||
return saved_enabled < 0 ? default_value : saved_enabled != 0;
|
||||
}
|
||||
static bool saveEnabled(bool enabled) { saved_enabled = enabled ? 1 : 0; return true; }
|
||||
static bool hasConfiguredWiFi(const void* prefs) { return static_cast<const ObserverPrefs*>(prefs)->wifi_ssid[0] != 0; }
|
||||
bool isRunning() const { return running; }
|
||||
bool isStopping() const { return stopping; }
|
||||
@@ -200,7 +203,7 @@ struct WebConfigServer {
|
||||
@SETUP_WINDOW@
|
||||
}
|
||||
};
|
||||
bool WebConfigServer::enabled = false;
|
||||
int WebConfigServer::saved_enabled = -1;
|
||||
bool WebConfigServer::allow_start = true;
|
||||
constexpr unsigned OTA_TX_DRAIN_TIMEOUT_MS = 1, OTA_MQTT_STOP_SETTLE_MS = 1;
|
||||
const char* ota_resolve_base(int) { return "test"; }
|
||||
@@ -405,7 +408,7 @@ class ESP32FullPowerLifecycleTest(unittest.TestCase):
|
||||
methods += "\n" + wifi_owner
|
||||
defaults_start = source.index(" // bridge defaults")
|
||||
defaults = source[defaults_start:source.index(" _prefs.bridge_delay", defaults_start)]
|
||||
startup = source[source.index(" bool start_webui = WebConfigServer::loadEnabled(false);"):]
|
||||
startup = source[source.index(" bool start_webui ="):]
|
||||
startup = startup[:startup.index("#endif\n\n", startup.index(" if (start_webui)"))]
|
||||
tick = source.index("_webconfig->tick(millis());")
|
||||
portal_start = source.rfind(" if (_webconfig) {", 0, tick)
|
||||
@@ -436,6 +439,56 @@ class ESP32FullPowerLifecycleTest(unittest.TestCase):
|
||||
with self.subTest(role=role):
|
||||
self.compile_and_run(self.fixture(role), *FLAGS)
|
||||
|
||||
def test_saved_webui_off_survives_reboot_without_stopping_espnow(self):
|
||||
checks = r'''
|
||||
int main() {
|
||||
for (bool configured : {false, true}) {
|
||||
for (int saved : {-1, 0, 1}) {
|
||||
// NVS survives a reboot; each node starts with fresh runtime state.
|
||||
WebConfigServer::saved_enabled = -1;
|
||||
if (saved >= 0) assert(WebConfigServer::saveEnabled(saved != 0));
|
||||
WiFi.reset(); espnow_owners = 0; now = 0;
|
||||
MyMesh node; node.defaults();
|
||||
if (configured) strcpy(node._cli.prefs.wifi_ssid, "saved-network");
|
||||
assert(node._prefs.bridge_enabled && node._prefs.espnow_bridge_enabled);
|
||||
assert(node.setBridgeState(true)); WiFi.connected = true;
|
||||
assert(node.startSharedEspNowBridgeIfReady());
|
||||
assert(node.isMqttBridgeRunning() && node.espnow_bridge.isRunning());
|
||||
const auto mqtt_stops = node.@WORKER@->stops;
|
||||
const auto espnow_stops = node.espnow_bridge.stops;
|
||||
node.startupSetup();
|
||||
const bool setup_expected = saved == 1 || (saved < 0 && !configured);
|
||||
assert(node.isWebConfigActive() == setup_expected);
|
||||
assert(node._prefs.bridge_enabled && node._prefs.espnow_bridge_enabled);
|
||||
if (setup_expected && !configured) {
|
||||
assert(node._unconfigured_setup_espnow_suspended);
|
||||
assert(!node.isMqttBridgeRunning() && !node.espnow_bridge.isRunning());
|
||||
} else {
|
||||
assert(!node._unconfigured_setup_espnow_suspended);
|
||||
assert(node.isMqttBridgeRunning() && node.espnow_bridge.isRunning());
|
||||
assert(node.@WORKER@->stops == mqtt_stops);
|
||||
assert(node.espnow_bridge.stops == espnow_stops);
|
||||
}
|
||||
if (!setup_expected) {
|
||||
assert(node._webconfig == nullptr && sdk_mode == WIFI_STA);
|
||||
for (unsigned retry = 0; retry < 3; ++retry) {
|
||||
now += 10000;
|
||||
node.servicePortal(); node.serviceIdleWiFi();
|
||||
assert(node.startSharedEspNowBridgeIfReady());
|
||||
assert(!node.isWebConfigActive() && sdk_mode == WIFI_STA);
|
||||
assert(sdk_started && node.isMqttBridgeRunning());
|
||||
assert(node.espnow_bridge.isRunning());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
for role in ("simple_repeater", "simple_room_server"):
|
||||
with self.subTest(role=role):
|
||||
body = "#include <initializer_list>\n" + self.fixture(role, checks)
|
||||
self.compile_and_run(body, *FLAGS)
|
||||
|
||||
def test_suspended_mqtt_idle_portal_and_foreground_ownership(self):
|
||||
checks = r'''
|
||||
int main() {
|
||||
|
||||
Reference in new issue
Block a user