Fix ESP32 Full idle radio teardown and compact AP startup
This commit is contained in:
1 parent
f1ffe53e8a
commit
d0859f925d
29 files changed
+1724
-144
No files matched your search
@@ -25,6 +25,9 @@ void companionPairingUiHilProbe();
|
||||
#include "esp_pm.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_system.h"
|
||||
#if defined(CONFIG_BLUEDROID_ENABLED) && !defined(MESH_USE_NIMBLE_ARDUINO)
|
||||
#include <esp_bt_main.h>
|
||||
#endif
|
||||
#if defined(CONFIG_PM_ENABLE) && CONFIG_PM_ENABLE
|
||||
#define COMPANION_IDF_PM_AVAILABLE 1
|
||||
#else
|
||||
@@ -3001,6 +3004,37 @@ public:
|
||||
};
|
||||
static CompanionWirelessBackend companion_wireless;
|
||||
|
||||
#if defined(ESP32_PLATFORM) && defined(ENABLE_USB_INTERFACE) \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT \
|
||||
&& !defined(ETHERNET_ENABLED) && !defined(SERIAL_RX)
|
||||
static bool companionNativeUsbLightSleepReady() {
|
||||
// Full compiles every transport, but a compiled driver is not an active
|
||||
// receiver. Preserve live services and pending replies while permitting
|
||||
// the same short sleep slice when those services have been turned off.
|
||||
if (companion_wireless.enabled() != 0 || interface_manager.hasPendingIO()
|
||||
|| board.isOTAUpdateRunning() || board.isRadioTestActive()
|
||||
|| sensors.gpsUsesSerialUart(1)) return false;
|
||||
#if defined(BLE_PIN_CODE) && !CONFIG_IDF_TARGET_ESP32C6
|
||||
// Stopped advertising is not a stopped controller. Manual light sleep
|
||||
// requires the RF driver to be disabled, including during BLE teardown.
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED)
|
||||
return false;
|
||||
#if defined(CONFIG_BLUEDROID_ENABLED) && !defined(MESH_USE_NIMBLE_ARDUINO)
|
||||
if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_ENABLED) return false;
|
||||
#endif
|
||||
#endif
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
// Catch an unfinished teardown even if the logical WiFi owner is off.
|
||||
wifi_mode_t mode = WIFI_MODE_NULL;
|
||||
const esp_err_t result = esp_wifi_get_mode(&mode);
|
||||
if (result == ESP_OK) return mode == WIFI_MODE_NULL;
|
||||
return result == ESP_ERR_WIFI_NOT_INIT || result == ESP_ERR_WIFI_NOT_STARTED;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool handleCompanionWirelessCommand(const char* command, char* reply, size_t size,
|
||||
CompanionWirelessSource source) {
|
||||
#if defined(BLE_PIN_CODE)
|
||||
@@ -3698,8 +3732,10 @@ void loop() {
|
||||
// The native-USB-only light-sleep path below bypasses ESP32Board::sleep.
|
||||
can_sleep = can_sleep && !mesh::isUsbLoggingEnabled();
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM) \
|
||||
|| (defined(ESP32_PLATFORM) && defined(ENABLE_USB_INTERFACE))
|
||||
#if defined(ESP32_PLATFORM) && defined(ENABLE_USB_INTERFACE)
|
||||
const bool usb_sleep_held = board.isUsbSleepHeld();
|
||||
can_sleep = can_sleep && !usb_sleep_held;
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
can_sleep = can_sleep && !board.isUsbHostConnected();
|
||||
#endif
|
||||
#if defined(MOMENTARY_BUTTON_WAKE_FROM_SLEEP) \
|
||||
@@ -3714,22 +3750,22 @@ void loop() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
|
||||
#elif defined(ESP32_PLATFORM)
|
||||
#if COMPANION_IDF_PM_AVAILABLE
|
||||
// Yield long enough for ESP-IDF automatic light sleep to enter when no
|
||||
// driver holds a power-management lock.
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
#elif defined(ENABLE_USB_INTERFACE) \
|
||||
#if defined(ENABLE_USB_INTERFACE) \
|
||||
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT \
|
||||
&& !defined(BLE_PIN_CODE) && !defined(WIFI_SSID) \
|
||||
&& !defined(ETHERNET_ENABLED) && !defined(SERIAL_RX)
|
||||
// The stock Arduino core has no automatic light sleep. A short timer
|
||||
// slice gives native-USB-only battery builds real light sleep without
|
||||
// Automatic light sleep is disabled for native USB, even in a PM-enabled
|
||||
// core. A short timer slice gives idle battery builds real light sleep without
|
||||
// delaying radio, GPS, button, or newly attached USB work by more than the
|
||||
// normal 10 ms loop cadence. can_sleep already proved no USB host is up.
|
||||
if (esp_sleep_enable_timer_wakeup(10000ULL) != ESP_OK
|
||||
if (!companionNativeUsbLightSleepReady()
|
||||
|| esp_sleep_enable_timer_wakeup(10000ULL) != ESP_OK
|
||||
|| esp_light_sleep_start() != ESP_OK) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
#elif COMPANION_IDF_PM_AVAILABLE
|
||||
// Yield long enough for ESP-IDF automatic light sleep to enter when no
|
||||
// driver holds a power-management lock.
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
#else
|
||||
// Connected transports need their own modem sleep and must retain the
|
||||
// normal FreeRTOS idle behavior.
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include "MyMesh.h"
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/esp32/BootFileSystem.h>
|
||||
#if defined(WITH_WEBCONFIG) || defined(WITH_MQTT_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| defined(LIGHTWEIGHT_WIFI_OTA) || (defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA))
|
||||
#include <WiFi.h>
|
||||
#include <esp_wifi.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <helpers/UsbLogging.h>
|
||||
#include <helpers/HilStartupTrace.h>
|
||||
@@ -10920,6 +10925,29 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::suspendUnconfiguredSetupBridges() {
|
||||
#if defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (_cli.getObserverPrefs()->wifi_ssid[0]) return;
|
||||
#else
|
||||
char ssid[33] = {};
|
||||
WebConfigServer::loadStandaloneWiFi(ssid, sizeof(ssid), nullptr, 0);
|
||||
if (ssid[0]) return;
|
||||
#endif
|
||||
_unconfigured_setup_espnow_suspended = true;
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
#if defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE)
|
||||
if (espnow_bridge.isRunning()) espnow_bridge.end();
|
||||
#else
|
||||
if (bridge.isRunning()) bridge.end();
|
||||
#endif
|
||||
#endif
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (mqtt_bridge && mqtt_bridge->isRunning()) mqtt_bridge->end();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MyMesh::startWebConfig(bool force_ap, char* reply) {
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) {
|
||||
strcpy(reply, "Err: OTA server is running - 'stop ota' first");
|
||||
@@ -10930,6 +10958,23 @@ bool MyMesh::startWebConfig(bool force_ap, char* reply) {
|
||||
: "Err: webconfig already running");
|
||||
return true;
|
||||
}
|
||||
if (mesh::wireless::control().blocked(mesh::wireless::WiFi)) {
|
||||
strcpy(reply, "Error: WiFi disabled; use set wifi on or set 2.4ghz on");
|
||||
return true;
|
||||
}
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
// A live MQTT worker cannot be rolled back immediately if its stop times
|
||||
// out. Require an explicit stop before a portal that would take its radio.
|
||||
if ((isMqttBridgeRunning() || isMqttBridgeStopping()) && (force_ap
|
||||
#if defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
|| _cli.getObserverPrefs()->wifi_ssid[0] == 0
|
||||
#endif
|
||||
)) {
|
||||
strcpy(reply, isMqttBridgeStopping() ? "Err: MQTT bridge is stopping - retry shortly"
|
||||
: "Err: MQTT bridge is running - 'set bridge off' first");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (!_webconfig) {
|
||||
void* mqtt_prefs = nullptr;
|
||||
bool owns_wifi = true;
|
||||
@@ -10947,21 +10992,38 @@ bool MyMesh::startWebConfig(bool force_ap, char* reply) {
|
||||
}
|
||||
}
|
||||
|
||||
const bool setup_was_suspended = _unconfigured_setup_espnow_suspended;
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
const bool espnow_was_running = isEspNowBridgeRunning();
|
||||
#endif
|
||||
suspendUnconfiguredSetupBridges();
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_webconfig->updateWiFiOwnership(!mqtt_bridge
|
||||
|| (!mqtt_bridge->isRunning() && !mqtt_bridge->isStopping()));
|
||||
#endif
|
||||
|
||||
bool started;
|
||||
if (force_ap) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (mqtt_bridge && mqtt_bridge->isRunning()) {
|
||||
strcpy(reply, "Err: MQTT bridge is running - 'set bridge off' first");
|
||||
return true;
|
||||
started = _webconfig->startSetupMode(reply);
|
||||
} else {
|
||||
started = _webconfig->startAutoMode(reply);
|
||||
}
|
||||
if (!started) {
|
||||
// A failed portal must not consume a live operator-started transport.
|
||||
// Restore actual pre-start state, never the saved first-boot defaults.
|
||||
_unconfigured_setup_espnow_suspended = setup_was_suspended;
|
||||
bool restored = true;
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (espnow_was_running && !isEspNowBridgeRunning()) {
|
||||
restored = setEspNowBridgeState(true) && restored;
|
||||
}
|
||||
#endif
|
||||
_webconfig->startSetupMode(reply);
|
||||
} else {
|
||||
_webconfig->startAutoMode(reply);
|
||||
_unconfigured_setup_espnow_suspended = setup_was_suspended;
|
||||
if (!restored) {
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, "; bridge resume failed");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -12918,6 +12980,16 @@ void __attribute__((noinline)) MyMesh::servicePostMeshLoop() {
|
||||
// the loop until reboot - otherwise a packet still queued here (busy /
|
||||
// duty-limited channel) is lost when the flash spins the loop and reboots.
|
||||
drainOutbound(OTA_TX_DRAIN_TIMEOUT_MS);
|
||||
const bool mqtt_was_running = mqtt_bridge && mqtt_bridge->isRunning();
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
const bool espnow_was_running = espnow_bridge.isRunning();
|
||||
#endif
|
||||
auto resume_paused_bridges = [&]() {
|
||||
if (mqtt_was_running) setMqttBridgeState(true);
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (espnow_was_running) setEspNowBridgeState(true);
|
||||
#endif
|
||||
};
|
||||
setBridgeState(false);
|
||||
// TODO: Replace this timed settle with a task-exit/join barrier once MQTT
|
||||
// teardown can prove that the idle task has reclaimed the worker resources.
|
||||
@@ -12930,13 +13002,13 @@ void __attribute__((noinline)) MyMesh::servicePostMeshLoop() {
|
||||
if (mqtt_bridge && !mqtt_bridge->canFlashAfterStop()) {
|
||||
mesh::usbConsolePort().printf("OTA: aborted, MQTT stop did not complete cleanly - resuming bridge\r\n");
|
||||
otaAlert("OTA aborted: MQTT stop unclean, bridge resumed");
|
||||
setBridgeState(true);
|
||||
resume_paused_bridges();
|
||||
} else if (!_cli.getBoard()->otaFromManifest(ota_resolve_base(_ota_update_channel), getFirmwareVer(), false, ota_reply)) {
|
||||
mesh::usbConsolePort().printf("OTA: aborted, resuming bridge - %s\r\n", ota_reply);
|
||||
char ota_alert_msg[160];
|
||||
snprintf(ota_alert_msg, sizeof(ota_alert_msg), "OTA aborted: %s", ota_reply);
|
||||
otaAlert(ota_alert_msg);
|
||||
setBridgeState(true);
|
||||
resume_paused_bridges();
|
||||
}
|
||||
// Success path: otaFromManifest() flashes and reboots into the new image
|
||||
// (never returns), so there is no in-boot "success" alert - the START alert
|
||||
@@ -12964,6 +13036,9 @@ void __attribute__((noinline)) MyMesh::servicePostMeshLoop() {
|
||||
#endif
|
||||
|
||||
// is pending dirty contacts write needed?
|
||||
#if defined(ESP32_PLATFORM)
|
||||
serviceIdleWiFi();
|
||||
#endif
|
||||
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||
const bool saved = acl.save(_fs);
|
||||
if (saved) {
|
||||
@@ -13546,9 +13621,59 @@ bool MyMesh::startNeighborDiscover(char* reply) {
|
||||
#endif // WITH_MQTT_NEIGHBORS
|
||||
|
||||
// To check if there is pending work
|
||||
#if defined(ESP32_PLATFORM)
|
||||
void MyMesh::serviceIdleWiFi() {
|
||||
#if (defined(WITH_WEBCONFIG) || defined(WITH_MQTT_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| defined(LIGHTWEIGHT_WIFI_OTA) || (defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA))) \
|
||||
&& (!defined(MESH_PRIMARY_ESPNOW) || !MESH_PRIMARY_ESPNOW) \
|
||||
&& (!defined(MESH_ESPNOW_RADIO) || !MESH_ESPNOW_RADIO)
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) return;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (isWebConfigActive()) return;
|
||||
#endif
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (_ota_update_at) return;
|
||||
if (mqtt_bridge && (mqtt_bridge->isRunning() || mqtt_bridge->isStopping())) return;
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (isEspNowBridgeRunning()) return;
|
||||
#endif
|
||||
// MQTT shutdown and WebConfig-to-OTA handoff retain STA intentionally.
|
||||
// Reclaim it only after the last live owner is gone, using Arduino first
|
||||
// so its private initialized/started cache agrees with the SDK driver.
|
||||
if (WiFi.getMode() == WIFI_OFF && WiFi.channel() > 0
|
||||
&& !WiFi.mode(WIFI_STA)) return;
|
||||
wifi_mode_t sdk_mode = WIFI_MODE_NULL;
|
||||
if (WiFi.getMode() == WIFI_OFF && esp_wifi_get_mode(&sdk_mode) != ESP_OK) return;
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.disconnect(false, false);
|
||||
if (!WiFi.mode(WIFI_OFF)) return;
|
||||
// A pure IDF ESP-NOW owner never created an Arduino facade, making OFF a
|
||||
// no-op there. The ownership checks above also protect that startup path.
|
||||
if (esp_wifi_get_mode(&sdk_mode) == ESP_OK) {
|
||||
esp_wifi_stop();
|
||||
esp_wifi_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool MyMesh::hasPendingWork() const {
|
||||
if (isDualRadioActive()) return true;
|
||||
if (hasPendingOtaApply()) return true;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (isWebConfigActive()) return true;
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM) \
|
||||
&& (defined(WITH_WEBCONFIG) || defined(WITH_MQTT_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| defined(LIGHTWEIGHT_WIFI_OTA) || (defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA))) \
|
||||
&& (!defined(MESH_PRIMARY_ESPNOW) || !MESH_PRIMARY_ESPNOW) \
|
||||
&& (!defined(MESH_ESPNOW_RADIO) || !MESH_ESPNOW_RADIO)
|
||||
// Cleanup may fail while the Arduino facade already reports OFF. Require
|
||||
// the live SDK driver to be gone before permitting manual light sleep.
|
||||
wifi_mode_t sdk_mode = WIFI_MODE_NULL;
|
||||
if (esp_wifi_get_mode(&sdk_mode) != ESP_ERR_WIFI_NOT_INIT) return true;
|
||||
#endif
|
||||
#if defined(WITH_WEBCONFIG) || defined(ETHERNET_ENABLED)
|
||||
if (_local_cli_output.busy()) return true;
|
||||
#endif
|
||||
@@ -13556,6 +13681,9 @@ bool MyMesh::hasPendingWork() const {
|
||||
#if defined(WITH_BRIDGE)
|
||||
const AbstractBridge* active_bridge = activeBridge();
|
||||
if (active_bridge && active_bridge->isRunning()) return true;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (mqtt_bridge && mqtt_bridge->isStopping()) return true;
|
||||
#endif
|
||||
#if defined(WITH_MQTT_BRIDGE) && defined(WITH_RS232_BRIDGE)
|
||||
if (isRs232BridgeRunning()) return true;
|
||||
#endif
|
||||
|
||||
@@ -621,6 +621,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
// Retry ESP-NOW without taking over an active OTA session. MQTT creates its
|
||||
// WiFi task asynchronously, so it must first associate on the shared channel.
|
||||
bool startSharedEspNowBridgeIfReady() {
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (_unconfigured_setup_espnow_suspended) return false;
|
||||
#endif
|
||||
#if defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE)
|
||||
ESPNowBridge& transport = espnow_bridge;
|
||||
if (!_prefs.espnow_bridge_enabled) return false;
|
||||
@@ -650,10 +653,17 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
#endif
|
||||
#ifdef WITH_WEBCONFIG
|
||||
WebConfigServer* _webconfig = nullptr;
|
||||
// A first-boot setup session may pause saved bridge intent for this boot.
|
||||
// Its retry loop must not revive ESP-NOW after the AP window ends.
|
||||
bool _unconfigured_setup_espnow_suspended = false;
|
||||
void suspendUnconfiguredSetupBridges();
|
||||
bool _wc_batch_active = false;
|
||||
bool _wc_restart_pending = false;
|
||||
uint8_t _wc_slot_restart_mask = 0;
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM)
|
||||
void serviceIdleWiFi();
|
||||
#endif
|
||||
|
||||
#if defined(WITH_MQTT_NEIGHBORS)
|
||||
// Neighbor-scope discovery: a snapshot of the neighbor table overlaid with an
|
||||
@@ -1393,6 +1403,9 @@ public:
|
||||
}
|
||||
if (espnow_bridge.isRunning()) return true;
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) return false;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
_unconfigured_setup_espnow_suspended = false;
|
||||
#endif
|
||||
shared_espnow_retry_at = 0;
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (mqtt_bridge && mqtt_bridge->isRunning()) {
|
||||
@@ -1450,6 +1463,9 @@ public:
|
||||
// A standalone bridge shares its WiFi driver with the browser uploader.
|
||||
// Preserve saved intent, but do not restart it until OTA has stopped.
|
||||
if (enable && _cli.getBoard()->isOTAUpdateRunning()) return false;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (enable) _unconfigured_setup_espnow_suspended = false;
|
||||
#endif
|
||||
shared_espnow_retry_at = 0;
|
||||
#endif
|
||||
// Disabling an already-absent heap-backed bridge is successful and must
|
||||
@@ -1547,6 +1563,9 @@ public:
|
||||
#else
|
||||
#if defined(WITH_ESPNOW_BRIDGE) && !defined(WITH_RS232_BRIDGE)
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) return false;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
_unconfigured_setup_espnow_suspended = false;
|
||||
#endif
|
||||
shared_espnow_retry_at = 0;
|
||||
#endif
|
||||
#if defined(WITH_RS232_BRIDGE) && !defined(WITH_MQTT_BRIDGE)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include "MyMesh.h"
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/esp32/BootFileSystem.h>
|
||||
#if defined(WITH_WEBCONFIG) || defined(WITH_MQTT_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| defined(LIGHTWEIGHT_WIFI_OTA) || (defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA))
|
||||
#include <WiFi.h>
|
||||
#include <esp_wifi.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <helpers/UsbLogging.h>
|
||||
#if defined(ENABLE_OTA)
|
||||
@@ -1948,6 +1953,25 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::suspendUnconfiguredSetupBridges() {
|
||||
#if defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (_cli.getObserverPrefs()->wifi_ssid[0]) return;
|
||||
#else
|
||||
char ssid[33] = {};
|
||||
WebConfigServer::loadStandaloneWiFi(ssid, sizeof(ssid), nullptr, 0);
|
||||
if (ssid[0]) return;
|
||||
#endif
|
||||
_unconfigured_setup_espnow_suspended = true;
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (espnow_bridge.isRunning()) espnow_bridge.end();
|
||||
#endif
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (bridge && bridge->isRunning()) bridge->end();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MyMesh::startWebConfig(bool force_ap, char* reply) {
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) {
|
||||
strcpy(reply, "Err: OTA server is running - 'stop ota' first");
|
||||
@@ -1958,6 +1982,21 @@ bool MyMesh::startWebConfig(bool force_ap, char* reply) {
|
||||
: "Err: webconfig already running");
|
||||
return true;
|
||||
}
|
||||
if (mesh::wireless::control().blocked(mesh::wireless::WiFi)) {
|
||||
strcpy(reply, "Error: WiFi disabled; use set wifi on or set 2.4ghz on");
|
||||
return true;
|
||||
}
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if ((isMqttBridgeRunning() || isMqttBridgeStopping()) && (force_ap
|
||||
#if defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
|| _cli.getObserverPrefs()->wifi_ssid[0] == 0
|
||||
#endif
|
||||
)) {
|
||||
strcpy(reply, isMqttBridgeStopping() ? "Err: MQTT bridge is stopping - retry shortly"
|
||||
: "Err: MQTT bridge is running - 'set bridge off' first");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (!_webconfig) {
|
||||
void* mqtt_prefs = nullptr;
|
||||
bool owns_wifi = true;
|
||||
@@ -1974,21 +2013,36 @@ bool MyMesh::startWebConfig(bool force_ap, char* reply) {
|
||||
}
|
||||
}
|
||||
|
||||
const bool setup_was_suspended = _unconfigured_setup_espnow_suspended;
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
const bool espnow_was_running = isEspNowBridgeRunning();
|
||||
#endif
|
||||
suspendUnconfiguredSetupBridges();
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
_webconfig->updateWiFiOwnership(!bridge
|
||||
|| (!bridge->isRunning() && !bridge->isStopping()));
|
||||
#endif
|
||||
|
||||
bool started;
|
||||
if (force_ap) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (bridge && bridge->isRunning()) {
|
||||
strcpy(reply, "Err: MQTT bridge is running - 'set bridge off' first");
|
||||
return true;
|
||||
started = _webconfig->startSetupMode(reply);
|
||||
} else {
|
||||
started = _webconfig->startAutoMode(reply);
|
||||
}
|
||||
if (!started) {
|
||||
_unconfigured_setup_espnow_suspended = setup_was_suspended;
|
||||
bool restored = true;
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (espnow_was_running && !isEspNowBridgeRunning()) {
|
||||
restored = setEspNowBridgeState(true) && restored;
|
||||
}
|
||||
#endif
|
||||
_webconfig->startSetupMode(reply);
|
||||
} else {
|
||||
_webconfig->startAutoMode(reply);
|
||||
_unconfigured_setup_espnow_suspended = setup_was_suspended;
|
||||
if (!restored) {
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, "; bridge resume failed");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2780,6 +2834,9 @@ void MyMesh::loop() {
|
||||
// had deliberately stopped.
|
||||
mesh::usbConsolePort().printf("OTA: starting update\r\n");
|
||||
const bool bridge_was_running = bridge && bridge->isRunning();
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
const bool espnow_was_running = espnow_bridge.isRunning();
|
||||
#endif
|
||||
drainOutbound(OTA_TX_DRAIN_TIMEOUT_MS);
|
||||
|
||||
// A previously timed-out stop leaves the lifecycle dirty even though the
|
||||
@@ -2787,11 +2844,15 @@ void MyMesh::loop() {
|
||||
// later request must not bypass the teardown barrier merely because there
|
||||
// is no active task left to stop this time.
|
||||
bool may_flash = !bridge || bridge->canFlashAfterStop();
|
||||
if (bridge_was_running) {
|
||||
if (bridge_was_running
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
|| espnow_was_running
|
||||
#endif
|
||||
) {
|
||||
setBridgeState(false);
|
||||
// OTA must not write after a forced/timed-out MQTT shutdown: its TLS/heap
|
||||
// ownership is uncertain until a subsequent clean start/stop cycle.
|
||||
may_flash = bridge && bridge->canFlashAfterStop();
|
||||
may_flash = !bridge || bridge->canFlashAfterStop();
|
||||
if (!may_flash) {
|
||||
mesh::usbConsolePort().printf("OTA: aborted, MQTT stop did not complete cleanly\r\n");
|
||||
} else {
|
||||
@@ -2811,9 +2872,12 @@ void MyMesh::loop() {
|
||||
// Successful otaFromManifest() reboots and never returns. Restore only a
|
||||
// bridge that was running before this attempt; leave an intentionally
|
||||
// stopped bridge stopped after any OTA refusal or download failure.
|
||||
if (!may_flash && bridge_was_running) {
|
||||
if (!may_flash) {
|
||||
mesh::usbConsolePort().printf("OTA: resuming bridge\r\n");
|
||||
setBridgeState(true);
|
||||
if (bridge_was_running) setMqttBridgeState(true);
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (espnow_was_running) setEspNowBridgeState(true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2837,6 +2901,10 @@ void MyMesh::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
serviceIdleWiFi();
|
||||
#endif
|
||||
|
||||
// TODO: periodically check for OLD/inactive entries in known_clients[], and evict
|
||||
|
||||
// update uptime
|
||||
@@ -2981,14 +3049,62 @@ uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const {
|
||||
}
|
||||
|
||||
// To check if there is pending work
|
||||
#if defined(ESP32_PLATFORM)
|
||||
void MyMesh::serviceIdleWiFi() {
|
||||
#if (defined(WITH_WEBCONFIG) || defined(WITH_MQTT_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| defined(LIGHTWEIGHT_WIFI_OTA) || (defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA))) \
|
||||
&& (!defined(MESH_PRIMARY_ESPNOW) || !MESH_PRIMARY_ESPNOW) \
|
||||
&& (!defined(MESH_ESPNOW_RADIO) || !MESH_ESPNOW_RADIO)
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) return;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (isWebConfigActive()) return;
|
||||
#endif
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (_ota_update_at) return;
|
||||
if (bridge && (bridge->isRunning() || bridge->isStopping())) return;
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
if (isEspNowBridgeRunning()) return;
|
||||
#endif
|
||||
if (WiFi.getMode() == WIFI_OFF && WiFi.channel() > 0
|
||||
&& !WiFi.mode(WIFI_STA)) return;
|
||||
wifi_mode_t sdk_mode = WIFI_MODE_NULL;
|
||||
if (WiFi.getMode() == WIFI_OFF && esp_wifi_get_mode(&sdk_mode) != ESP_OK) return;
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.disconnect(false, false);
|
||||
if (!WiFi.mode(WIFI_OFF)) return;
|
||||
if (esp_wifi_get_mode(&sdk_mode) == ESP_OK) {
|
||||
esp_wifi_stop();
|
||||
esp_wifi_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool MyMesh::hasPendingWork() const {
|
||||
if (isDualRadioActive()) return true;
|
||||
if (hasPendingOtaApply()) return true;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (isWebConfigActive()) return true;
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM) \
|
||||
&& (defined(WITH_WEBCONFIG) || defined(WITH_MQTT_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| defined(LIGHTWEIGHT_WIFI_OTA) || (defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA))) \
|
||||
&& (!defined(MESH_PRIMARY_ESPNOW) || !MESH_PRIMARY_ESPNOW) \
|
||||
&& (!defined(MESH_ESPNOW_RADIO) || !MESH_ESPNOW_RADIO)
|
||||
// Never sleep over an unowned driver whose cleanup failed or whose Arduino
|
||||
// facade no longer reflects the live SDK state.
|
||||
wifi_mode_t sdk_mode = WIFI_MODE_NULL;
|
||||
if (esp_wifi_get_mode(&sdk_mode) != ESP_ERR_WIFI_NOT_INIT) return true;
|
||||
#endif
|
||||
#if defined(WITH_WEBCONFIG) || defined(ETHERNET_ENABLED)
|
||||
if (_local_cli_output.busy()) return true;
|
||||
#endif
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (bridge && bridge->isRunning()) return true; // bridge needs WiFi radio, can't sleep
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
if (bridge && bridge->isStopping()) return true;
|
||||
#endif
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
if (espnow_bridge.isRunning()) return true;
|
||||
#endif
|
||||
|
||||
@@ -320,10 +320,15 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks,
|
||||
#endif
|
||||
#ifdef WITH_WEBCONFIG
|
||||
WebConfigServer* _webconfig = nullptr;
|
||||
bool _unconfigured_setup_espnow_suspended = false;
|
||||
void suspendUnconfiguredSetupBridges();
|
||||
bool _wc_batch_active = false;
|
||||
bool _wc_restart_pending = false;
|
||||
uint8_t _wc_slot_restart_mask = 0;
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM)
|
||||
void serviceIdleWiFi();
|
||||
#endif
|
||||
|
||||
void addPost(ClientInfo* client, const char* postData);
|
||||
bool applySavedRadioParams();
|
||||
@@ -579,6 +584,9 @@ public:
|
||||
// MQTT's WiFi task associates asynchronously. ESP-NOW must wait for that
|
||||
// station interface so it can share the AP-selected channel safely.
|
||||
bool startSharedEspNowBridgeIfReady() {
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (_unconfigured_setup_espnow_suspended) return false;
|
||||
#endif
|
||||
if (espnow_bridge.isRunning()) return true;
|
||||
if (!_prefs.espnow_bridge_enabled) return false;
|
||||
// Keep the browser uploader's bridge pause in effect until OTA stops.
|
||||
@@ -661,6 +669,10 @@ public:
|
||||
return !espnow_bridge.isRunning();
|
||||
}
|
||||
if (espnow_bridge.isRunning()) return true;
|
||||
if (_cli.getBoard()->isOTAUpdateRunning()) return false;
|
||||
#ifdef WITH_WEBCONFIG
|
||||
_unconfigured_setup_espnow_suspended = false;
|
||||
#endif
|
||||
shared_espnow_retry_at = 0;
|
||||
if (bridge && bridge->isRunning()) {
|
||||
startSharedEspNowBridgeIfReady();
|
||||
|
||||
Reference in new issue
Block a user