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
+50
-21
@@ -773,7 +773,14 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
// contain its appended byte, so they safely inherit the enabled default.
|
||||
_prefs->system_watchdog_enabled = 1;
|
||||
memset(_prefs->extra_sf, 0, sizeof(_prefs->extra_sf));
|
||||
#if defined(ESP32_PLATFORM) && defined(MESHCORE_EXPANDED_PARTITION_PROFILE)
|
||||
// Full carries diagnostics as an optional runtime service. Merely adding
|
||||
// that capability must not hold an otherwise idle battery node awake.
|
||||
// A saved logging preference below still takes precedence.
|
||||
_prefs->usb_logging_enabled = 0;
|
||||
#else
|
||||
_prefs->usb_logging_enabled = 1;
|
||||
#endif
|
||||
_prefs->usb_debug_enabled = 0;
|
||||
_prefs->trace_when_repeat_off = 0;
|
||||
_prefs->ota_channel = 0;
|
||||
@@ -2941,6 +2948,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
strcpy(reply, "ERR: usage start ota [ap]");
|
||||
return;
|
||||
}
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
const bool ota_was_running = _board->isOTAUpdateRunning();
|
||||
// Keep an outstanding pause if an earlier resume failed. Only a
|
||||
// successful resume or an explicitly disabled saved intent clears it.
|
||||
#endif
|
||||
bool webconfig_stopped = false;
|
||||
#if defined(ESP_PLATFORM) && defined(ADMIN_PASSWORD) && !defined(WEBCONFIG_DISABLED)
|
||||
if (_callbacks->isWebConfigActive()) {
|
||||
@@ -2951,7 +2963,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
webconfig_stopped = true;
|
||||
}
|
||||
#endif
|
||||
#if defined(WITH_ESPNOW_BRIDGE) && !defined(WITH_MQTT_BRIDGE)
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
// Browser OTA owns the shared WiFi driver. An independent UART bridge
|
||||
// can keep running throughout the upload.
|
||||
const bool espnow_paused = _callbacks->isEspNowBridgeRunning();
|
||||
@@ -2959,21 +2971,28 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
strcpy(reply, "ERR: could not pause ESP-NOW for OTA");
|
||||
return;
|
||||
}
|
||||
_wifi_ota_resume_espnow = _wifi_ota_resume_espnow || espnow_paused;
|
||||
#endif
|
||||
reply[0] = 0;
|
||||
if (!_board->startOTAUpdate(_prefs->node_name, reply, force_ap)) {
|
||||
if (!reply[0]) strcpy(reply, "Error");
|
||||
#if defined(WITH_ESPNOW_BRIDGE) && !defined(WITH_MQTT_BRIDGE)
|
||||
if (espnow_paused && !_callbacks->setEspNowBridgeState(true)) {
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, "; ESP-NOW resume failed");
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
if (espnow_paused && !ota_was_running) {
|
||||
if (_callbacks->setEspNowBridgeState(true)) {
|
||||
_wifi_ota_resume_espnow = false;
|
||||
} else {
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, "; ESP-NOW resume failed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if defined(WITH_MQTT_BRIDGE) && defined(LIGHTWEIGHT_WIFI_OTA)
|
||||
else {
|
||||
// Keep WiFi up, but release MQTT/TLS heap while the browser uploader runs.
|
||||
_callbacks->setBridgeState(false);
|
||||
const bool mqtt_paused = _callbacks->isMqttBridgeRunning();
|
||||
_wifi_ota_resume_mqtt = _wifi_ota_resume_mqtt || mqtt_paused;
|
||||
if (mqtt_paused) _callbacks->setMqttBridgeState(false);
|
||||
}
|
||||
#endif
|
||||
#if defined(WITH_ESPNOW_BRIDGE) && !defined(WITH_MQTT_BRIDGE)
|
||||
@@ -2987,30 +3006,40 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
snprintf(reply + used, 160 - used, "; WebConfig stopped");
|
||||
}
|
||||
} else if (memcmp(command, "stop ota", 8) == 0 && (command[8] == 0 || command[8] == ' ')) {
|
||||
if (!_board->stopOTAUpdate(reply)) {
|
||||
const bool ota_stopped = _board->stopOTAUpdate(reply);
|
||||
if (!ota_stopped) {
|
||||
if (!reply[0]) strcpy(reply, "Error");
|
||||
}
|
||||
#if defined(WITH_MQTT_BRIDGE) && defined(LIGHTWEIGHT_WIFI_OTA)
|
||||
else if (_prefs->bridge_enabled
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
|| _prefs->espnow_bridge_enabled
|
||||
#endif
|
||||
) {
|
||||
_callbacks->setBridgeState(true);
|
||||
else {
|
||||
if (_wifi_ota_resume_mqtt) {
|
||||
if (!_prefs->bridge_enabled || _callbacks->setMqttBridgeState(true)) {
|
||||
_wifi_ota_resume_mqtt = false;
|
||||
} else {
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, "; MQTT resume failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if defined(WITH_ESPNOW_BRIDGE) && !defined(WITH_MQTT_BRIDGE)
|
||||
else if (
|
||||
#if defined(WITH_RS232_BRIDGE) || defined(ESPNOW_BRIDGE_MERGED)
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
if (ota_stopped && !_board->isOTAUpdateRunning() && _wifi_ota_resume_espnow) {
|
||||
const bool still_enabled =
|
||||
#if defined(WITH_MQTT_BRIDGE) || defined(WITH_RS232_BRIDGE) || defined(ESPNOW_BRIDGE_MERGED)
|
||||
_prefs->espnow_bridge_enabled
|
||||
#else
|
||||
_prefs->bridge_enabled
|
||||
#endif
|
||||
) {
|
||||
const bool resumed = _callbacks->setEspNowBridgeState(true);
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, resumed
|
||||
? "; ESP-NOW resumed" : "; ESP-NOW resume failed");
|
||||
!= 0;
|
||||
if (!still_enabled) {
|
||||
_wifi_ota_resume_espnow = false;
|
||||
} else {
|
||||
const bool resumed = _callbacks->setEspNowBridgeState(true);
|
||||
if (resumed) _wifi_ota_resume_espnow = false;
|
||||
const size_t used = strlen(reply);
|
||||
snprintf(reply + used, 160 - used, resumed
|
||||
? "; ESP-NOW resumed" : "; ESP-NOW resume failed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else if (memcmp(command, "clock", 5) == 0) {
|
||||
|
||||
@@ -793,6 +793,13 @@ class CommonCLI {
|
||||
NodePrefs* _prefs;
|
||||
CommonCLICallbacks* _callbacks;
|
||||
mesh::MainBoard* _board;
|
||||
#if defined(WITH_ESPNOW_BRIDGE) \
|
||||
|| (defined(WITH_MQTT_BRIDGE) && defined(LIGHTWEIGHT_WIFI_OTA))
|
||||
// Browser OTA restores only transports it actually paused, not saved
|
||||
// defaults suspended by an unconfigured first-boot setup session.
|
||||
bool _wifi_ota_resume_mqtt = false;
|
||||
bool _wifi_ota_resume_espnow = false;
|
||||
#endif
|
||||
#if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL)
|
||||
UserGpio _user_gpio;
|
||||
#endif
|
||||
|
||||
+34
-26
@@ -60,6 +60,7 @@ bool ESP32Board::isUserGpioAvailable(uint8_t pin) const {
|
||||
(defined(ADMIN_PASSWORD) || defined(COMPANION_RADIO_FULL))
|
||||
#include <WiFi.h>
|
||||
#include <helpers/esp32/WiFiRadioPolicy.h>
|
||||
#include <helpers/esp32/WiFiAccessPointPolicy.h>
|
||||
#include <helpers/esp32/StaticHtml.h>
|
||||
#include <Update.h>
|
||||
#include <esp_ota_ops.h>
|
||||
@@ -298,22 +299,14 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
if (!use_ap) {
|
||||
ip = WiFi.localIP();
|
||||
} else {
|
||||
if (!ota_started_ap || !(WiFi.getMode() & WIFI_AP)
|
||||
|| static_cast<uint32_t>(WiFi.softAPIP()) == 0) {
|
||||
const IPAddress ap_ip(192, 168, 4, 1);
|
||||
const IPAddress ap_mask(255, 255, 255, 0);
|
||||
ota_started_ap = WiFi.softAPConfig(ap_ip, ap_ip, ap_mask)
|
||||
&& WiFi.softAP("MeshCore-OTA", nullptr,
|
||||
mesh::wifi::accessPointChannel());
|
||||
}
|
||||
// A valid IP does not imply a discoverable hotspot. An LR protocol bit
|
||||
// left by ESP-NOW makes AP beacons incompatible with ordinary clients.
|
||||
if (ota_started_ap && mesh::wifi::applyAccessPointProtocolMask() != ESP_OK) {
|
||||
WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
}
|
||||
ota_started_radio = ota_started_radio || WiFi.getMode() == WIFI_OFF;
|
||||
ota_started_ap = mesh::wifi::startOpenAccessPoint("MeshCore-OTA", ota_started_ap);
|
||||
if (!ota_started_ap) {
|
||||
inhibit_sleep = ota_server != nullptr;
|
||||
if (!inhibit_sleep) {
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
}
|
||||
strcpy(reply, "ERR: OTA WiFi failed");
|
||||
return false;
|
||||
}
|
||||
@@ -324,6 +317,8 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
if (!lightweight_ota_server.begin(this)) {
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
strcpy(reply, "ERR: OTA server failed");
|
||||
return false;
|
||||
@@ -344,6 +339,11 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
|
||||
bool ESP32Board::stopOTAUpdate(char reply[]) {
|
||||
if (ota_server == nullptr) {
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
strcpy(reply, "OK - OTA not running");
|
||||
return true;
|
||||
}
|
||||
@@ -355,6 +355,8 @@ bool ESP32Board::stopOTAUpdate(char reply[]) {
|
||||
ota_server = nullptr;
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
strcpy(reply, "OK - OTA stopped");
|
||||
MESH_DEBUG_PRINTLN("stopOTAUpdate: %s", reply);
|
||||
@@ -364,6 +366,7 @@ bool ESP32Board::stopOTAUpdate(char reply[]) {
|
||||
#elif defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only
|
||||
#include <WiFi.h>
|
||||
#include <helpers/esp32/WiFiRadioPolicy.h>
|
||||
#include <helpers/esp32/WiFiAccessPointPolicy.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include "../../arch/esp32/AsyncElegantOTA/src/AsyncElegantOTA.h"
|
||||
@@ -394,20 +397,14 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
if (!use_ap) {
|
||||
ip = WiFi.localIP();
|
||||
} else {
|
||||
if (!ota_started_ap || !(WiFi.getMode() & WIFI_AP)
|
||||
|| static_cast<uint32_t>(WiFi.softAPIP()) == 0) {
|
||||
const IPAddress ap_ip(192, 168, 4, 1);
|
||||
const IPAddress ap_mask(255, 255, 255, 0);
|
||||
ota_started_ap = WiFi.softAPConfig(ap_ip, ap_ip, ap_mask)
|
||||
&& WiFi.softAP("MeshCore-OTA", NULL,
|
||||
mesh::wifi::accessPointChannel());
|
||||
}
|
||||
if (ota_started_ap && mesh::wifi::applyAccessPointProtocolMask() != ESP_OK) {
|
||||
WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
}
|
||||
ota_started_radio = ota_started_radio || WiFi.getMode() == WIFI_OFF;
|
||||
ota_started_ap = mesh::wifi::startOpenAccessPoint("MeshCore-OTA", ota_started_ap);
|
||||
if (!ota_started_ap) {
|
||||
inhibit_sleep = ota_server != nullptr;
|
||||
if (!inhibit_sleep) {
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
}
|
||||
strcpy(reply, "ERR: OTA WiFi failed");
|
||||
return false;
|
||||
}
|
||||
@@ -439,6 +436,8 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
if (async_ota_host == nullptr) {
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
strcpy(reply, "ERR: OTA server allocation failed");
|
||||
return false;
|
||||
@@ -462,6 +461,8 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
ota_server = nullptr;
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
strcpy(reply, "ERR: OTA server failed; retry start ota");
|
||||
return false;
|
||||
@@ -472,6 +473,11 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
|
||||
|
||||
bool ESP32Board::stopOTAUpdate(char reply[]) {
|
||||
if (ota_server == nullptr) {
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
strcpy(reply, "OK - OTA not running");
|
||||
return true;
|
||||
}
|
||||
@@ -484,6 +490,8 @@ bool ESP32Board::stopOTAUpdate(char reply[]) {
|
||||
ota_server = nullptr;
|
||||
if (ota_started_ap) WiFi.softAPdisconnect(true);
|
||||
ota_started_ap = false;
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(ota_started_radio);
|
||||
ota_started_radio = false;
|
||||
inhibit_sleep = false;
|
||||
|
||||
strcpy(reply, "OK - OTA stopped");
|
||||
|
||||
@@ -40,6 +40,7 @@ protected:
|
||||
uint8_t startup_reason;
|
||||
bool inhibit_sleep = false;
|
||||
bool ota_started_ap = false;
|
||||
bool ota_started_radio = false;
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
mesh::UsbHostSleepPolicy usb_host_sleep_policy;
|
||||
#endif
|
||||
@@ -117,8 +118,8 @@ public:
|
||||
// after a Pi closes its serial port). Guard every caller here.
|
||||
// Sample even when another blocker is active, so an open console/OTA does
|
||||
// not leave the last positive native USB host signal stale.
|
||||
const bool usb_host_connected = isUsbHostConnected();
|
||||
bool keep_awake = inhibit_sleep || usb_host_connected || isRadioTestActive();
|
||||
const bool usb_sleep_held = isUsbSleepHeld();
|
||||
bool keep_awake = inhibit_sleep || usb_sleep_held || isRadioTestActive();
|
||||
#if !defined(ARDUINO_USB_CDC_ON_BOOT) || !ARDUINO_USB_CDC_ON_BOOT
|
||||
// External USB-to-UART chips cannot report host attachment here. Light
|
||||
// sleep loses their first input bytes; UART wake loses the wake character
|
||||
@@ -126,13 +127,6 @@ public:
|
||||
// Keep an enabled console available even before its first command.
|
||||
keep_awake = keep_awake || static_cast<bool>(Serial);
|
||||
#endif
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
// HWCDC loses its SOF host signal after about 5 ms. A warm Pi reboot,
|
||||
// especially on a USB 1.1 bus, must not put us in a 30-second light sleep
|
||||
// while the host is returning and trying to enumerate this same radio.
|
||||
keep_awake = keep_awake || usb_host_sleep_policy.shouldKeepAwake(
|
||||
millis(), MESH_ESP32_USB_HOST_LOSS_SLEEP_GRACE_MS);
|
||||
#endif
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
// A live logging stream must also remain available before a host opens
|
||||
// it and across host disconnects. Compiled-out logging is not a blocker.
|
||||
@@ -295,6 +289,18 @@ public:
|
||||
inhibit_sleep = inhibit;
|
||||
}
|
||||
|
||||
bool isUsbSleepHeld() {
|
||||
// Share the native host-loss grace with short Companion sleep slices.
|
||||
// Sample even when another inhibitor is active, preserving reconnects.
|
||||
const bool host_connected = isUsbHostConnected();
|
||||
#if MESH_ESP32_USB_CONSOLE_COOPERATIVE
|
||||
return host_connected || usb_host_sleep_policy.shouldKeepAwake(
|
||||
millis(), MESH_ESP32_USB_HOST_LOSS_SLEEP_GRACE_MS);
|
||||
#else
|
||||
return host_connected;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t getResetReason() const override {
|
||||
return esp_reset_reason();
|
||||
}
|
||||
|
||||
@@ -72,10 +72,12 @@
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#if defined(ENABLE_USB_INTERFACE)
|
||||
#if defined(ENABLE_USB_INTERFACE) \
|
||||
|| (defined(ESP32_PLATFORM) && defined(MESHCORE_EXPANDED_PARTITION_PROFILE))
|
||||
// A USB Companion owns its primary stream for framed traffic until saved
|
||||
// preferences are loaded. Starting disabled prevents early boot diagnostics
|
||||
// from corrupting that stream before single-TTY builds can enter terminal mode.
|
||||
// Full infrastructure likewise waits for its saved runtime logging preference.
|
||||
static std::atomic<bool> usb_logging_enabled{false};
|
||||
#else
|
||||
static std::atomic<bool> usb_logging_enabled{true};
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
#include <helpers/esp32/WiFiRadioPolicy.h>
|
||||
#include <helpers/esp32/WiFiAccessPointPolicy.h>
|
||||
#include <helpers/WirelessControl.h>
|
||||
#include <helpers/esp32/WiFiStationPolicy.h>
|
||||
|
||||
namespace {
|
||||
|
||||
static const IPAddress SETUP_IP(192, 168, 4, 1);
|
||||
static const IPAddress SETUP_MASK(255, 255, 255, 0);
|
||||
static const uint32_t CONNECT_TIMEOUT_MS = 20000;
|
||||
// The rendered success page remains in the browser after the setup SSID
|
||||
// disappears; allow one second for the HTTP response to flush first.
|
||||
@@ -29,6 +29,7 @@ struct PortalImpl {
|
||||
WiFiServer server{80};
|
||||
DNSServer dns;
|
||||
TaskHandle_t task = nullptr;
|
||||
bool started_radio = false;
|
||||
WiFiSetupPortal::SaveCallback save_callback = nullptr;
|
||||
void* callback_context = nullptr;
|
||||
volatile bool* active = nullptr;
|
||||
@@ -302,6 +303,8 @@ static void portalTask(void* arg) {
|
||||
impl->dns.stop();
|
||||
impl->server.stop();
|
||||
WiFi.softAPdisconnect(true);
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(impl->started_radio);
|
||||
impl->started_radio = false;
|
||||
if (impl->active) *impl->active = false;
|
||||
impl->task = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
@@ -333,20 +336,17 @@ bool WiFiSetupPortal::begin(const char* ap_name, SaveCallback save_callback, voi
|
||||
sizeof(impl->ap_name) - 1);
|
||||
impl->ap_name[sizeof(impl->ap_name) - 1] = 0;
|
||||
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
if (!WiFi.softAPConfig(SETUP_IP, SETUP_IP, SETUP_MASK)
|
||||
|| !WiFi.softAP(impl->ap_name, nullptr,
|
||||
mesh::wifi::accessPointChannel())
|
||||
|| mesh::wifi::applyAccessPointProtocolMask() != ESP_OK
|
||||
|| esp_wifi_set_protocol(
|
||||
WIFI_IF_STA,
|
||||
mesh::wifi::kProtocolMask)
|
||||
!= ESP_OK) {
|
||||
impl->started_radio = WiFi.getMode() == WIFI_OFF;
|
||||
if (!mesh::wifi::startOpenAccessPoint(impl->ap_name, false)) {
|
||||
WiFi.softAPdisconnect(true);
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(impl->started_radio);
|
||||
impl->started_radio = false;
|
||||
return false;
|
||||
}
|
||||
if (!impl->dns.start(53, "*", SETUP_IP)) {
|
||||
WiFi.softAPdisconnect(true);
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(impl->started_radio);
|
||||
impl->started_radio = false;
|
||||
return false;
|
||||
}
|
||||
impl->server.begin();
|
||||
@@ -358,6 +358,8 @@ bool WiFiSetupPortal::begin(const char* ap_name, SaveCallback save_callback, voi
|
||||
impl->dns.stop();
|
||||
impl->server.stop();
|
||||
WiFi.softAPdisconnect(true);
|
||||
mesh::wifi::stopTemporaryAccessPointRadio(impl->started_radio);
|
||||
impl->started_radio = false;
|
||||
impl->task = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ static_assert(sizeof(WEBCONFIG_AP_PREFIX) <= 28,
|
||||
#include "helpers/WebConfigKeys.h"
|
||||
#include "helpers/WiFiPowerSave.h"
|
||||
#include "helpers/esp32/WiFiRadioPolicy.h"
|
||||
#include "helpers/esp32/WiFiAccessPointPolicy.h"
|
||||
#include "helpers/esp32/WiFiStationPolicy.h"
|
||||
|
||||
// ESPAsyncWebServer closes every ordinary response, so splitting the UI into
|
||||
@@ -819,28 +820,14 @@ static bool setupAccessPointReady(const char* ssid, int channel) {
|
||||
static bool scanSetupNetworks() {
|
||||
// Populate the picker before the AP starts, while off-channel scanning
|
||||
// cannot interrupt discovery or a client's first connection.
|
||||
WiFi.scanDelete();
|
||||
const uint8_t channel = mesh::wifi::stationScanChannel();
|
||||
const int16_t scan_result = WiFi.scanNetworks(true, false, false, 200, channel);
|
||||
const uint32_t scan_started = millis();
|
||||
while (WiFi.scanComplete() == WIFI_SCAN_RUNNING
|
||||
&& millis() - scan_started < 3600) delay(20);
|
||||
const bool prepared = mesh::wifi::scanBeforeAccessPoint();
|
||||
mesh::usbDebugPort().printf(
|
||||
"WebConfig pre-AP scan: channel=%u start=%d result=%d elapsed=%u\n",
|
||||
(unsigned)channel, (int)scan_result, (int)WiFi.scanComplete(),
|
||||
"WebConfig pre-AP scan: channel=%u result=%d elapsed=%u\n",
|
||||
(unsigned)channel, (int)WiFi.scanComplete(),
|
||||
(unsigned)(millis() - scan_started));
|
||||
// Arduino's scanComplete() can clear its running flag on a facade timeout
|
||||
// before the SDK scanner stops. Also cancel FAILED scans before enabling AP.
|
||||
if (WiFi.scanComplete() < 0) {
|
||||
if (esp_wifi_scan_stop() != ESP_OK) return false;
|
||||
const uint32_t stop_started = millis();
|
||||
while (WiFi.scanComplete() == WIFI_SCAN_RUNNING
|
||||
&& millis() - stop_started < 100) delay(20);
|
||||
WiFi.scanDelete();
|
||||
}
|
||||
// A failed scan still allows manual SSID entry and a browser rescan. An
|
||||
// active scanner must stop before we enable the AP interface.
|
||||
return WiFi.scanComplete() != WIFI_SCAN_RUNNING;
|
||||
return prepared;
|
||||
}
|
||||
|
||||
bool WebConfigServer::startSetupMode(char reply[]) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <helpers/esp32/WiFiRadioPolicy.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace wifi {
|
||||
|
||||
inline bool accessPointStarted() {
|
||||
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
||||
return WiFi.AP.started();
|
||||
#else
|
||||
return (WiFi.getStatusBits() & AP_STARTED_BIT) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Arduino setters and a configured netif IP can outlive the AP_START event.
|
||||
// Read the live driver before telling an operator that the hotspot is ready.
|
||||
inline bool openAccessPointReady(const char* ssid, int channel) {
|
||||
wifi_mode_t mode = WIFI_MODE_NULL;
|
||||
wifi_config_t config = {};
|
||||
uint8_t protocol = 0;
|
||||
const size_t length = strlen(ssid);
|
||||
return accessPointStarted()
|
||||
&& esp_wifi_get_mode(&mode) == ESP_OK && (mode & WIFI_MODE_AP)
|
||||
&& esp_wifi_get_config(WIFI_IF_AP, &config) == ESP_OK
|
||||
&& config.ap.ssid_len == length
|
||||
&& memcmp(config.ap.ssid, ssid, length) == 0
|
||||
&& config.ap.ssid_hidden == 0 && config.ap.authmode == WIFI_AUTH_OPEN
|
||||
&& config.ap.channel == channel
|
||||
&& esp_wifi_get_protocol(WIFI_IF_AP, &protocol) == ESP_OK
|
||||
&& protocol == kAccessPointProtocolMask
|
||||
&& static_cast<uint32_t>(WiFi.softAPIP()) != 0;
|
||||
}
|
||||
|
||||
inline bool finishAccessPointScan() {
|
||||
// A failed Arduino scan can still have an active SDK scanner. scanDelete()
|
||||
// only releases results; cancel that scanner before enabling AP beacons.
|
||||
if (WiFi.scanComplete() < 0) {
|
||||
if (esp_wifi_scan_stop() != ESP_OK) return false;
|
||||
const uint32_t started = millis();
|
||||
while (WiFi.scanComplete() == WIFI_SCAN_RUNNING
|
||||
&& millis() - started < 100) delay(20);
|
||||
WiFi.scanDelete();
|
||||
}
|
||||
return WiFi.scanComplete() != WIFI_SCAN_RUNNING;
|
||||
}
|
||||
|
||||
inline bool scanBeforeAccessPoint() {
|
||||
// Use the same STA-only scan-first startup as WebConfig, including compact
|
||||
// OTA images without WebConfig. Shared ESP-NOW stays on its home channel.
|
||||
WiFi.scanDelete();
|
||||
WiFi.scanNetworks(true, false, false, 200, stationScanChannel());
|
||||
const uint32_t started = millis();
|
||||
while (WiFi.scanComplete() == WIFI_SCAN_RUNNING
|
||||
&& millis() - started < 3600) delay(20);
|
||||
return finishAccessPointScan();
|
||||
}
|
||||
|
||||
inline bool startOpenAccessPoint(const char* ssid, bool reuse_existing) {
|
||||
const int channel = accessPointChannel();
|
||||
// Repeated OTA commands retain joined clients when the AP is already live.
|
||||
if (reuse_existing && applyAccessPointProtocolMask() == ESP_OK
|
||||
&& openAccessPointReady(ssid, channel)) return true;
|
||||
|
||||
const bool connected = WiFi.status() == WL_CONNECTED
|
||||
&& static_cast<uint32_t>(WiFi.localIP()) != 0;
|
||||
// Removing only AP keeps a station association and any ESP-NOW owner. It
|
||||
// also initializes a cold, previously disabled radio through Arduino.
|
||||
if (!WiFi.mode(WIFI_STA)) return false;
|
||||
const uint32_t stopped = millis();
|
||||
while (accessPointStarted() && millis() - stopped < 300) delay(20);
|
||||
if (accessPointStarted() || applyProtocolMask(WIFI_IF_STA) != ESP_OK)
|
||||
return false;
|
||||
// Do not scan away from a live LAN connection. An offline radio needs the
|
||||
// scan-first preparation before its first AP, rather than AP-only startup.
|
||||
if (!(connected ? finishAccessPointScan() : scanBeforeAccessPoint()))
|
||||
return false;
|
||||
|
||||
const IPAddress ip(192, 168, 4, 1);
|
||||
const IPAddress mask(255, 255, 255, 0);
|
||||
bool ready = WiFi.softAPConfig(ip, ip, mask)
|
||||
&& WiFi.softAP(ssid, nullptr, channel)
|
||||
&& applyAccessPointProtocolMask() == ESP_OK;
|
||||
if (ready) {
|
||||
ready = false;
|
||||
const uint32_t started = millis();
|
||||
do {
|
||||
ready = openAccessPointReady(ssid, channel);
|
||||
if (!ready) delay(20);
|
||||
} while (!ready && millis() - started < 300);
|
||||
}
|
||||
if (!ready) WiFi.softAPdisconnect(true);
|
||||
return ready;
|
||||
}
|
||||
|
||||
inline void stopTemporaryAccessPointRadio(bool started_radio) {
|
||||
// Only restore OFF when this AP initialized the radio. A station inherited
|
||||
// from WebConfig/MQTT belongs to the caller's service ownership policy.
|
||||
if (started_radio && !espNowChannelConstrained()
|
||||
&& WiFi.status() != WL_CONNECTED) WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace mesh
|
||||
Reference in new issue
Block a user