fix: stabilize primary ESP-NOW WiFi coexistence
This commit is contained in:
1 parent
fefd1a9577
commit
11e70a5b32
11 files changed
+77
-21
No files matched your search
@@ -42,5 +42,13 @@ inline uint8_t validEspNowChannelOrDefault(uint8_t stored,
|
||||
return isValidEspNowChannel(stored) ? stored : fallback;
|
||||
}
|
||||
|
||||
// Rewriting an ESP32's current WiFi channel is not a harmless no-op. The
|
||||
// driver briefly reconfigures the shared RF path, which can interrupt SoftAP
|
||||
// beacons and ESP-NOW receive when a caller repeats the write from a fast main
|
||||
// loop. Only request a restore when the primary channel actually moved.
|
||||
inline bool espNowChannelRestoreRequired(uint8_t current, uint8_t target) {
|
||||
return current != target;
|
||||
}
|
||||
|
||||
} // namespace wifi
|
||||
} // namespace mesh
|
||||
@@ -334,10 +334,7 @@ bool WiFiSetupPortal::begin(const char* ap_name, SaveCallback save_callback, voi
|
||||
if (!WiFi.softAPConfig(SETUP_IP, SETUP_IP, SETUP_MASK)
|
||||
|| !WiFi.softAP(impl->ap_name, nullptr,
|
||||
mesh::wifi::accessPointChannel())
|
||||
|| esp_wifi_set_protocol(
|
||||
WIFI_IF_AP,
|
||||
mesh::wifi::kProtocolMask)
|
||||
!= ESP_OK
|
||||
|| mesh::wifi::applyAccessPointProtocolMask() != ESP_OK
|
||||
|| esp_wifi_set_protocol(
|
||||
WIFI_IF_STA,
|
||||
mesh::wifi::kProtocolMask)
|
||||
|
||||
@@ -129,6 +129,19 @@ void ESPNOWRadio::init() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
|
||||
// Modem sleep is still required when BLE and infrastructure WiFi coexist,
|
||||
// but unicast/broadcast ESP-NOW frames are not buffered by an access point.
|
||||
// Hold the driver's documented wake reference for the lifetime of this
|
||||
// primary mesh radio so receive stays continuous without disabling the
|
||||
// WiFi/Bluetooth coexistence policy globally.
|
||||
if (esp_wifi_force_wakeup_acquire() != ESP_OK) {
|
||||
ESPNOW_DEBUG_PRINTLN("Error keeping primary ESP-NOW receive awake");
|
||||
esp_now_deinit();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_wifi_set_max_tx_power(80); // should be 20dBm
|
||||
|
||||
esp_now_register_send_cb(OnDataSent);
|
||||
|
||||
@@ -774,8 +774,8 @@ bool WebConfigServer::startSetupMode(char reply[]) {
|
||||
// LR bit set. The driver then reports a healthy SoftAP, but ordinary
|
||||
// phones and laptops cannot discover its beacon. A WiFi companion must
|
||||
// advertise using the interoperable 2.4 GHz protocol set.
|
||||
const esp_err_t ap_protocol_result = esp_wifi_set_protocol(
|
||||
WIFI_IF_AP, mesh::wifi::kProtocolMask);
|
||||
const esp_err_t ap_protocol_result =
|
||||
mesh::wifi::applyAccessPointProtocolMask();
|
||||
const esp_err_t sta_protocol_result = esp_wifi_set_protocol(
|
||||
WIFI_IF_STA, mesh::wifi::kProtocolMask);
|
||||
if (ap_protocol_result == ESP_OK && sta_protocol_result == ESP_OK) break;
|
||||
@@ -812,7 +812,10 @@ bool WebConfigServer::startSetupMode(char reply[]) {
|
||||
_initial_setup = _wifi_ssid[0] == 0 && node.admin_password[0] != 0;
|
||||
_setup_started_at = millis();
|
||||
_last_activity = _setup_started_at;
|
||||
WiFi.scanNetworks(true); // pre-populate the SSID picker
|
||||
// A primary ESP-NOW radio cannot leave its selected channel while scanning.
|
||||
// Ordinary WiFi targets retain the zero-channel all-band scan.
|
||||
WiFi.scanNetworks(true, false, false, 300,
|
||||
mesh::wifi::stationScanChannel());
|
||||
|
||||
sprintf(reply, "WebConfig AP started: join '%s' then open http://%s/", _ap_ssid, ip.toString().c_str());
|
||||
return true;
|
||||
@@ -2329,7 +2332,8 @@ void WebConfigServer::handleScan(AsyncWebServerRequest* req) {
|
||||
n = WIFI_SCAN_FAILED;
|
||||
}
|
||||
if (n == WIFI_SCAN_FAILED) {
|
||||
WiFi.scanNetworks(true);
|
||||
WiFi.scanNetworks(true, false, false, 300,
|
||||
mesh::wifi::stationScanChannel());
|
||||
req->send(200, "application/json", "{\"state\":\"scanning\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ static constexpr uint8_t kProtocolMask = WIFI_PROTOCOL_11B
|
||||
| WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N;
|
||||
#endif
|
||||
|
||||
// LR is private to ESP-NOW on the station interface. A setup SoftAP must keep
|
||||
// an ordinary b/g/n protocol bitmap so phones and laptops can discover it.
|
||||
static constexpr uint8_t kAccessPointProtocolMask = WIFI_PROTOCOL_11B
|
||||
| WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N;
|
||||
|
||||
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
|
||||
struct EspNowBootChannelState {
|
||||
bool loaded;
|
||||
@@ -98,6 +103,10 @@ inline esp_err_t applyProtocolMask(wifi_interface_t interface_id) {
|
||||
return esp_wifi_set_protocol(interface_id, kProtocolMask);
|
||||
}
|
||||
|
||||
inline esp_err_t applyAccessPointProtocolMask() {
|
||||
return esp_wifi_set_protocol(WIFI_IF_AP, kAccessPointProtocolMask);
|
||||
}
|
||||
|
||||
// An ESP-NOW radio and an associated station cannot occupy different channels.
|
||||
// Full Companion therefore keeps both its setup AP and any infrastructure-WiFi
|
||||
// association on the mesh channel. Passing zero on ordinary targets preserves
|
||||
@@ -118,12 +127,18 @@ inline uint8_t stationScanChannel() {
|
||||
|
||||
inline esp_err_t restoreEspNowChannel() {
|
||||
if (!kKeepEspNowRadioRunning) return ESP_OK;
|
||||
const uint8_t target = activeEspNowChannel();
|
||||
uint8_t current = 0;
|
||||
wifi_second_chan_t secondary = WIFI_SECOND_CHAN_NONE;
|
||||
if (esp_wifi_get_channel(¤t, &secondary) == ESP_OK
|
||||
&& !espNowChannelRestoreRequired(current, target)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
// Fail closed instead of silently moving to the build default. A failure can
|
||||
// be transient (scan/connect in progress), and changing the cached boot
|
||||
// channel here would split this node from peers which still use the selected
|
||||
// channel. Callers retry through their normal reconnect/tick paths.
|
||||
return esp_wifi_set_channel(
|
||||
activeEspNowChannel(), WIFI_SECOND_CHAN_NONE);
|
||||
return esp_wifi_set_channel(target, WIFI_SECOND_CHAN_NONE);
|
||||
}
|
||||
|
||||
} // namespace wifi
|
||||
|
||||
@@ -76,13 +76,10 @@ inline wl_status_t beginStation(const char* ssid, const char* password) {
|
||||
// follow their existing disconnected/retry path.
|
||||
inline bool enforceStationChannel() {
|
||||
if (!kPrimaryEspNowRadio) return true;
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
// A disconnect and scan/association completion are asynchronous. Keep
|
||||
// reasserting the boot channel so a first ESP_ERR_WIFI_STATE cannot leave
|
||||
// the mesh parked on the router's former channel.
|
||||
restoreEspNowChannel();
|
||||
return true;
|
||||
}
|
||||
// beginStation(), disconnect handling, and setup-AP startup already restore
|
||||
// the selected channel. Avoid polling the driver and attempting a channel
|
||||
// write from every WebConfig and Companion loop while it is disconnected.
|
||||
if (WiFi.status() != WL_CONNECTED) return true;
|
||||
if (WiFi.channel() == activeEspNowChannel()) return true;
|
||||
WiFi.disconnect(false, false);
|
||||
restoreEspNowChannel();
|
||||
|
||||
Reference in new issue
Block a user