Restore ordinary WiFi protocols for browser OTA hotspots

This commit is contained in:
mikecarper committed 2026-10-05 11:23:01 -07:00
1 parent 64a09cbada
commit 8fbf948d68
4 files changed
+48 -1

No files matched your search

+12
View File
@@ -59,6 +59,7 @@ bool ESP32Board::isUserGpioAvailable(uint8_t pin) const {
#if defined(LIGHTWEIGHT_WIFI_OTA) && \
(defined(ADMIN_PASSWORD) || defined(COMPANION_RADIO_FULL))
#include <WiFi.h>
#include <helpers/esp32/WiFiRadioPolicy.h>
#include <helpers/esp32/StaticHtml.h>
#include <Update.h>
#include <esp_ota_ops.h>
@@ -304,6 +305,12 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
ota_started_ap = WiFi.softAPConfig(ap_ip, ap_ip, ap_mask)
&& WiFi.softAP("MeshCore-OTA", nullptr);
}
// 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;
}
if (!ota_started_ap) {
inhibit_sleep = ota_server != nullptr;
strcpy(reply, "ERR: OTA WiFi failed");
@@ -355,6 +362,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 <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
@@ -387,6 +395,10 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[], bool force_ap) {
ota_started_ap = WiFi.softAPConfig(ap_ip, ap_ip, ap_mask)
&& WiFi.softAP("MeshCore-OTA", NULL);
}
if (ota_started_ap && mesh::wifi::applyAccessPointProtocolMask() != ESP_OK) {
WiFi.softAPdisconnect(true);
ota_started_ap = false;
}
if (!ota_started_ap) {
inhibit_sleep = ota_server != nullptr;
strcpy(reply, "ERR: OTA WiFi failed");
+22
View File
@@ -11,6 +11,11 @@
constexpr int WL_CONNECTED = 3;
constexpr int HTTP_GET = 0;
constexpr int WIFI_AP = 2;
constexpr int ESP_OK = 0, WIFI_IF_AP = 1;
constexpr uint8_t WIFI_PROTOCOL_11B = 1, WIFI_PROTOCOL_11G = 2;
constexpr uint8_t WIFI_PROTOCOL_11N = 4, WIFI_PROTOCOL_LR = 8;
using esp_err_t = int;
bool allow_protocol_reset = true;
struct IPAddress {
std::string value;
IPAddress() = default;
@@ -24,6 +29,7 @@ struct IPAddress {
struct WiFiFake {
bool connected = true, ap = false, allow_ap = true;
unsigned starts = 0;
uint8_t ap_protocol = 15; // Valid driver state, but LR beacons hide the AP from ordinary clients.
int status() const { return connected ? WL_CONNECTED : 0; }
int getMode() const { return ap ? WIFI_AP : 0; }
IPAddress localIP() const { return IPAddress(10, 20, 30, 40); }
@@ -39,6 +45,15 @@ struct WiFiFake {
}
void softAPdisconnect(bool) { ap = false; }
} WiFi;
esp_err_t esp_wifi_set_protocol(int interface_id, uint8_t protocols) {
assert(interface_id == WIFI_IF_AP);
if (!allow_protocol_reset) return -1;
WiFi.ap_protocol = protocols;
return ESP_OK;
}
namespace mesh { namespace wifi {
@AP_PROTOCOL_POLICY@
} }
static unsigned server_starts = 0;
struct AsyncWebServerRequest {
void send(int, const char*, const char*) {}
@@ -101,6 +116,7 @@ int main() {
+ "/update";
assert(strncmp(reply.text, url.c_str(), url.size()) == 0);
assert(strstr(reply.text, instruction));
if (ap || !WiFi.connected) assert(WiFi.ap_protocol == 7);
assert(strlen(reply.text) < 160 && reply.guard == 0x12345678);
};
// Normal start reports the actual LAN address and does not disconnect it.
@@ -119,6 +135,7 @@ int main() {
start(true, "192.168.4.1", "Join WiFi MeshCore-OTA");
assert(WiFi.ap && WiFi.connected && server_starts == 1);
const auto ap_starts = WiFi.starts;
WiFi.ap_protocol = 15; // Repair an existing AP without dropping its clients.
start(true, "192.168.4.1", "Join WiFi MeshCore-OTA");
assert(WiFi.starts == ap_starts); // Healthy repeats retain associated clients.
WiFi.ap = false; // An external driver stop invalidates cached AP ownership.
@@ -136,6 +153,11 @@ int main() {
assert(!board.inhibit_sleep && !board.ota_server && !WiFi.ap && WiFi.connected);
WiFi.connected = false;
allow_protocol_reset = false;
assert(!board.startOTAUpdate("test node", reply.text, false));
assert(strcmp(reply.text, "ERR: OTA WiFi failed") == 0);
assert(!board.inhibit_sleep && !board.ota_server && !WiFi.ap);
allow_protocol_reset = true;
start(false, "192.168.4.1", "Join WiFi MeshCore-OTA");
assert(WiFi.ap && !WiFi.connected);
assert(board.stopOTAUpdate(reply.text));
+8 -1
View File
@@ -38,6 +38,7 @@ STUBS = r'''
#define MESH_DEBUG_PRINTLN(...) ((void)0)
constexpr int WL_CONNECTED = 3, WL_DISCONNECTED = 6, HTTP_GET = 0;
constexpr int WIFI_AP = 2;
constexpr int ESP_OK = 0;
struct IPAddress {
uint32_t value = 0;
@@ -61,6 +62,7 @@ struct MockWiFi {
bool ap_config_ok = true, ap_start_ok = true;
bool initialized_cache = false, started_cache = false;
IPAddress station_ip, ap_ip, configured_ap_ip;
int ap_protocol = 15;
int status() const { return station_status; }
int getMode() const { return sdk_ap_active ? WIFI_AP : 0; }
IPAddress localIP() const { return station_ip; }
@@ -78,7 +80,11 @@ struct MockWiFi {
}
return ap_start_ok;
}
void softAPdisconnect(bool) { sdk_ap_active = false; ap_ip = IPAddress(); }
} WiFi;
namespace mesh { namespace wifi {
int applyAccessPointProtocolMask() { WiFi.ap_protocol = 7; return ESP_OK; }
} }
int wifi_stop_calls = 0, wifi_deinit_calls = 0;
int esp_wifi_stop() {
@@ -148,7 +154,7 @@ void seedStation() {
bool endpointUsable() {
return sdk_initialized && sdk_started
&& ((WiFi.station_status == WL_CONNECTED && WiFi.station_ip.value != 0)
|| (sdk_ap_active && WiFi.ap_ip.value != 0));
|| (sdk_ap_active && WiFi.ap_ip.value != 0 && WiFi.ap_protocol == 7));
}
int main(int argc, char** argv) {
assert(argc == 2);
@@ -162,6 +168,7 @@ int main(int argc, char** argv) {
the_mesh.web = true;
const IPAddress ip(192, 168, 4, 1), mask(255, 255, 255, 0);
assert(WiFi.softAPConfig(ip, ip, mask) && WiFi.softAP("setup", nullptr));
mesh::wifi::applyAccessPointProtocolMask();
assert(endpointUsable());
} else if (scenario == "owners_mqtt" || scenario == "cleanup_mqtt") {
the_mesh.mqtt = true;
+6
View File
@@ -2,6 +2,7 @@
"""Exercise the actual browser OTA start/stop methods with fake WiFi and servers."""
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
@@ -34,6 +35,11 @@ class WiFiOtaStartTest(unittest.TestCase):
source = (ROOT / "src/helpers/ESP32Board.cpp").read_text()
lightweight, other = source.split("#elif defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA)", 1)
fixture = (ROOT / "test/fixtures/wifi_ota_start.cpp").read_text()
policy = (ROOT / "src/helpers/esp32/WiFiRadioPolicy.h").read_text()
mask = re.search(r"static constexpr uint8_t kAccessPointProtocolMask =.*?;",
policy, re.DOTALL).group(0)
fixture = fixture.replace("@AP_PROTOCOL_POLICY@", mask + "\n" + extract_braced(
policy, "inline esp_err_t applyAccessPointProtocolMask()"))
for name, implementation, flags in (
("lightweight infrastructure", lightweight, ["-DLIGHTWEIGHT_WIFI_OTA=1"]),
("lightweight companion", lightweight, ["-DLIGHTWEIGHT_WIFI_OTA=1", "-DCOMPANION_RADIO_FULL=1"]),