From 52f2fa9c435e0698b9f556f0dccdf99c27d58a13 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 21 Jul 2024 21:15:49 +0200 Subject: [PATCH 01/16] Search for master by hopping channels - attempt at solving #4063 - sync master transmits beacon/heartbeat every 5s - slave hops WiFi channels until master is heard - master can be connected to WiFi, slave cannot --- wled00/remote.cpp | 6 ---- wled00/udp.cpp | 14 +++++--- wled00/wled.cpp | 92 ++++++++++++++++++++++++++++++++++++----------- wled00/wled.h | 2 ++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/wled00/remote.cpp b/wled00/remote.cpp index 9c8d67d0d9..d81f53f949 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -179,12 +179,6 @@ static bool remoteJson(int button) void handleRemote(uint8_t *incomingData, size_t len) { message_structure_t *incoming = reinterpret_cast(incomingData); - if (strcmp(last_signal_src, linked_remote) != 0) { - DEBUG_PRINT(F("ESP Now Message Received from Unlinked Sender: ")); - DEBUG_PRINTLN(last_signal_src); - return; - } - if (len != sizeof(message_structure_t)) { DEBUG_PRINT(F("Unknown incoming ESP Now message received of length ")); DEBUG_PRINTLN(len); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index c2221e2cf6..0152e30609 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -969,10 +969,14 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs DEBUG_PRINTLN(); #endif -#ifndef WLED_DISABLE_ESPNOW // usermods hook can override processing if (usermods.onEspNowMessage(address, data, len)) return; -#endif + + // only handle messages from linked master/remote + if (strlen(linked_remote) < 12 || strcmp(last_signal_src, linked_remote) != 0) { + DEBUG_PRINTLN(F("ESP-NOW unpaired remote sender.")); + return; + } // handle WiZ Mote data if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) { @@ -980,8 +984,10 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs return; } - if (strlen(linked_remote) == 12 && strcmp(last_signal_src, linked_remote) != 0) { - DEBUG_PRINTLN(F("ESP-NOW unpaired remote sender.")); + // is received packet a master's heartbeat + if (len == 12 && strncmp_P((const char *)data, PSTR("WLED MASTER."), 12) == 0) { + DEBUG_PRINTLN(F("ESP-NOW master heartbeat heard.")); + scanESPNow = millis(); // disable scanning for next 10s and do no futrher processing return; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index f8aa94c491..b9ab377bbb 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -633,6 +633,22 @@ void WLED::initAP(bool resetAP) dnsServer.setErrorReplyCode(DNSReplyCode::NoError); dnsServer.start(53, "*", WiFi.softAPIP()); + +#ifndef WLED_DISABLE_ESPNOW + if (enableESPNow) { + // we have opened AP (on desired channel) so we need to initialise ESP-NOW on the same channel + // it is going to be used as for sending AND receiving (assuming slave device has no WiFi configured) + // if slave has WiFi configured it needs to set TEMPORARY AP to be able to search for master + quickEspNow.onDataSent(espNowSentCB); // see udp.cpp + quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp + DEBUG_PRINTLN(F("ESP-NOW initing in AP mode.")); + #ifdef ESP32 + quickEspNow.setWiFiBandwidth(WIFI_IF_AP, WIFI_BW_HT20); // Only needed for ESP32 in case you need coexistence with ESP8266 in the same network + #endif //ESP32 + bool espNowOK = quickEspNow.begin(apChannel, WIFI_IF_AP); // Same channel must be used for both AP and ESP-NOW + statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; + } +#endif } apActive = true; } @@ -792,6 +808,7 @@ void WLED::initConnection() DEBUG_PRINTLN(F("ESP-NOW stopping.")); quickEspNow.stop(); statusESPNow = ESP_NOW_STATE_UNINIT; + scanESPNow = millis(); } #endif @@ -843,25 +860,6 @@ void WLED::initConnection() WiFi.hostname(hostname); #endif } - -#ifndef WLED_DISABLE_ESPNOW - if (enableESPNow) { - quickEspNow.onDataSent(espNowSentCB); // see udp.cpp - quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp - bool espNowOK; - if (apActive) { - DEBUG_PRINTLN(F("ESP-NOW initing in AP mode.")); - #ifdef ESP32 - quickEspNow.setWiFiBandwidth(WIFI_IF_AP, WIFI_BW_HT20); // Only needed for ESP32 in case you need coexistence with ESP8266 in the same network - #endif //ESP32 - espNowOK = quickEspNow.begin(apChannel, WIFI_IF_AP); // Same channel must be used for both AP and ESP-NOW - } else { - DEBUG_PRINTLN(F("ESP-NOW initing in STA mode.")); - espNowOK = quickEspNow.begin(); // Use no parameters to start ESP-NOW on same channel as WiFi, in STA mode - } - statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; - } -#endif } void WLED::initInterfaces() @@ -920,6 +918,18 @@ void WLED::initInterfaces() #endif interfacesInited = true; wasConnected = true; + +#ifndef WLED_DISABLE_ESPNOW + // we are connected to WiFi and ESP-NOW will only be used on master for sending out packets + // on the same channel as WiFi it is connected to (slaves will need to find it, see below) + if (enableESPNow) { + quickEspNow.onDataSent(espNowSentCB); // see udp.cpp + quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp + DEBUG_PRINTLN(F("ESP-NOW initing in STA mode.")); + bool espNowOK = quickEspNow.begin(); // Use no parameters to start ESP-NOW on same channel as WiFi, in STA mode + statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; + } +#endif } void WLED::handleConnection() @@ -968,6 +978,7 @@ void WLED::handleConnection() if (!Network.isConnected()) { if (interfacesInited) { + // we were connected but diconnect happened if (scanDone && multiWiFi.size() > 1) { DEBUG_PRINTLN(F("WiFi scan initiated on disconnect.")); findWiFi(true); // reinit scan @@ -985,27 +996,58 @@ void WLED::handleConnection() sendImprovStateResponse(0x03, true); improvActive = 2; } + // try to reconnect if not able to connect for 30s or 18s if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) { if (improvActive == 2) improvActive = 3; DEBUG_PRINTLN(F("Last reconnect too old.")); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); } + // open AP if this is after boot or forced (_NO_CONN) if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { DEBUG_PRINTLN(F("Not connected AP.")); initAP(); // start AP only within first 5min } } - if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && stac == 0) { // disconnect AP after 5min if no clients connected + // disconnect AP after 5min if no clients connected and mode TEMPORARY + if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && stac == 0) { // if AP was enabled more than 10min after boot or if client was connected more than 10min after boot do not disconnect AP mode if (now < 2*WLED_AP_TIMEOUT) { +#ifndef WLED_DISABLE_ESPNOW + // we need to stop ESP-NOW as we are stopping AP + if (statusESPNow == ESP_NOW_STATE_ON) { + DEBUG_PRINTLN(F("ESP-NOW stopping on AP stop.")); + quickEspNow.stop(); + statusESPNow = ESP_NOW_STATE_UNINIT; + scanESPNow = now; + } +#endif dnsServer.stop(); WiFi.softAPdisconnect(true); apActive = false; DEBUG_PRINTLN(F("Temporary AP disabled.")); } } +#ifndef WLED_DISABLE_ESPNOW + // we are still not connected to WiFi and we may have AP active or not. if AP is active we will listen on its channel for ESP-NOW packets + // otherwise we'll try to find our master by hopping channels and wait 10s on each channel + if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && wifiConfigured && !sendNotificationsRT) { + if (statusESPNow == ESP_NOW_STATE_UNINIT) { + quickEspNow.onDataSent(espNowSentCB); // see udp.cpp + quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp + DEBUG_PRINTLN(F("ESP-NOW initing in unconnected STA mode.")); + bool espNowOK = quickEspNow.begin(channelESPNow); // use changeable channel STA mode + statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; + scanESPNow = now; + } + if (statusESPNow == ESP_NOW_STATE_ON && now - scanESPNow > 10000) { + if (++channelESPNow > 13) channelESPNow = 1; + quickEspNow.setChannel(channelESPNow); + scanESPNow = now; + } + } +#endif } else if (!interfacesInited) { //newly connected DEBUG_PRINTLN(); DEBUG_PRINT(F("Connected! IP address: ")); @@ -1027,6 +1069,16 @@ void WLED::handleConnection() apActive = false; DEBUG_PRINTLN(F("Access point disabled (connected).")); } + } else { +#ifndef WLED_DISABLE_ESPNOW + // already established connection, send ESP-NOW beacon every 5s if we are in sync mode (AKA master device) + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON && sendNotificationsRT && now - scanESPNow > 5000) { + uint8_t buffer[12]; + memcpy_P(buffer, PSTR("WLED MASTER."), 12); // use PROGMEM for string + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, buffer, 12); + scanESPNow = now; + } +#endif } } diff --git a/wled00/wled.h b/wled00/wled.h index f761b970d1..ac5f1c644e 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -504,6 +504,8 @@ WLED_GLOBAL byte statusESPNow _INIT(ESP_NOW_STATE_UNINIT); // state of ES WLED_GLOBAL bool useESPNowSync _INIT(false); // use ESP-NOW wireless technology for sync WLED_GLOBAL char linked_remote[13] _INIT(""); // MAC of ESP-NOW remote (Wiz Mote) WLED_GLOBAL char last_signal_src[13] _INIT(""); // last seen ESP-NOW sender +WLED_GLOBAL byte channelESPNow _INIT(1); // last channel used when searching for master +WLED_GLOBAL unsigned long scanESPNow _INIT(0UL); #endif // Time CONFIG From 7a162e6bfaacbc2d214bcb6f417108e0cbe05b7b Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 25 Jul 2024 16:50:53 +0200 Subject: [PATCH 02/16] Update - use slave to find master's beacon - beacon contains channel and time - slave's internal clock is synchronised with master's --- wled00/cfg.cpp | 6 +++ wled00/fcn_declare.h | 7 +++ wled00/remote.cpp | 7 +-- wled00/set.cpp | 5 +- wled00/udp.cpp | 50 +++++++++++++----- wled00/wled.cpp | 121 +++++++++++++++++++++++++++++-------------- wled00/wled.h | 10 +++- wled00/xml.cpp | 6 ++- 8 files changed, 148 insertions(+), 64 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index f9a94e228f..7f4eb28a4f 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -36,8 +36,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject nw = doc["nw"]; #ifndef WLED_DISABLE_ESPNOW CJSON(enableESPNow, nw[F("espnow")]); + char linked_remote[13]; getStringFromJson(linked_remote, nw[F("linked_remote")], 13); linked_remote[12] = '\0'; + #define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0) + for (int i=0; i<6; i++) masterESPNow[i] = (hex2int(linked_remote[i*2])<<4) | hex2int(linked_remote[i*2+1]); + DEBUG_PRINTF_P(PSTR("ESP-NOW linked remote: " MACSTR "\n"), MAC2STR(masterESPNow)); #endif size_t n = 0; @@ -745,6 +749,8 @@ void serializeConfig() { JsonObject nw = root.createNestedObject("nw"); #ifndef WLED_DISABLE_ESPNOW nw[F("espnow")] = enableESPNow; + char linked_remote[13]; + sprintf(linked_remote, "%02x%02x%02x%02x%02x%02x", MAC2STR(masterESPNow)); nw[F("linked_remote")] = linked_remote; #endif diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d95b8ef8e4..cfbe2b5e97 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -248,6 +248,13 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); void refreshNodeList(); void sendSysInfoUDP(); #ifndef WLED_DISABLE_ESPNOW +typedef struct { + char header[5]; // enough to store "WLED" (including termination 0) + uint8_t version:4; // message packet version (changes when packet size changes) + uint8_t channel:4; // master's WiFi channel used + uint32_t time; // may be used for time synchronisation (NOTE: time_t varies in size on ESP32 and ESP8266) + uint8_t reserved[6]; // 6 bytes reserved for future use +} __attribute__((packed, aligned(1))) EspNowBeacon; void espNowSentCB(uint8_t* address, uint8_t status); void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); #endif diff --git a/wled00/remote.cpp b/wled00/remote.cpp index d81f53f949..2301fb0ba9 100644 --- a/wled00/remote.cpp +++ b/wled00/remote.cpp @@ -190,12 +190,7 @@ void handleRemote(uint8_t *incomingData, size_t len) { return; } - DEBUG_PRINT(F("Incoming ESP Now Packet [")); - DEBUG_PRINT(cur_seq); - DEBUG_PRINT(F("] from sender [")); - DEBUG_PRINT(last_signal_src); - DEBUG_PRINT(F("] button: ")); - DEBUG_PRINTLN(incoming->button); + DEBUG_PRINTF_P(PSTR("Incoming ESP Now Packet [%d] button: %d\n"), cur_seq, incoming->button); if (!remoteJson(incoming->button)) switch (incoming->button) { diff --git a/wled00/set.cpp b/wled00/set.cpp index 13295df21e..93362fc993 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -89,8 +89,11 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) bool oldESPNow = enableESPNow; enableESPNow = request->hasArg(F("RE")); if (oldESPNow != enableESPNow) forceReconnect = true; + char linked_remote[13]; strlcpy(linked_remote, request->arg(F("RMAC")).c_str(), 13); - strlwr(linked_remote); //Normalize MAC format to lowercase + #define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0) + for (int i=0; i<6; i++) masterESPNow[i] = (hex2int(linked_remote[i*2])<<4) | hex2int(linked_remote[i*2+1]); + DEBUG_PRINTF_P(PSTR("ESP-NOW linked remote: " MACSTR "\n"), MAC2STR(masterESPNow)); #endif #ifdef WLED_USE_ETHERNET diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 0152e30609..bc3397fd35 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -955,39 +955,61 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8 #ifndef WLED_DISABLE_ESPNOW // ESP-NOW message sent callback function +// if used with unicast messages (PING to master) status will contain success of delivery. this can be used to find master's channel void espNowSentCB(uint8_t* address, uint8_t status) { - DEBUG_PRINTF_P(PSTR("Message sent to " MACSTR ", status: %d\n"), MAC2STR(address), status); + DEBUG_PRINTF_P(PSTR("Message sent to " MACSTR ", status: %d (wifi: %d)\n"), MAC2STR(address), status, WiFi.channel()); +// if (!sendNotificationsRT && status == ESP_NOW_SEND_SUCCESS) { +// if (memcmp(address, masterESPNow, 6) == 0) { +// // we sent message to master successfully, use current channel +// scanESPNow = millis() + 30000; // disable scanning for a few seconds +// } +// } } // ESP-NOW message receive callback function void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast) { - sprintf_P(last_signal_src, PSTR("%02x%02x%02x%02x%02x%02x"), address[0], address[1], address[2], address[3], address[4], address[5]); + //sprintf_P(last_signal_src, PSTR("%02x%02x%02x%02x%02x%02x"), address[0], address[1], address[2], address[3], address[4], address[5]); + memcpy(senderESPNow, address, sizeof(senderESPNow)); #ifdef WLED_DEBUG - DEBUG_PRINT(F("ESP-NOW: ")); DEBUG_PRINT(last_signal_src); DEBUG_PRINT(F(" -> ")); DEBUG_PRINTLN(len); - for (int i=0; i 1) { @@ -999,12 +1025,13 @@ void WLED::handleConnection() sendImprovStateResponse(0x03, true); improvActive = 2; } - // try to reconnect if not able to connect for 30s or 18s - if (now - lastReconnectAttempt > ((stac) ? 300000 : 18000) && wifiConfigured) { + // try to reconnect if not connected after 300s if clients connected to wifi or 60s otherwise + if (now - lastReconnectAttempt > ((stac) ? 300000 : 60000) && wifiConfigured) { if (improvActive == 2) improvActive = 3; DEBUG_PRINTLN(F("Last reconnect too old.")); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); + return; } // open AP if this is after boot or forced (_NO_CONN) if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { @@ -1023,7 +1050,7 @@ void WLED::handleConnection() DEBUG_PRINTLN(F("ESP-NOW stopping on AP stop.")); quickEspNow.stop(); statusESPNow = ESP_NOW_STATE_UNINIT; - scanESPNow = now; + scanESPNow = now + 5000; // postpone searching for a bit } #endif dnsServer.stop(); @@ -1033,21 +1060,30 @@ void WLED::handleConnection() } } #ifndef WLED_DISABLE_ESPNOW - // we are still not connected to WiFi and we may have AP active or not. if AP is active we will listen on its channel for ESP-NOW packets - // otherwise we'll try to find our master by hopping channels and wait 10s on each channel - if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && wifiConfigured && !sendNotificationsRT) { + // we are still not connected to WiFi and we may have AP active or not. if AP is active we will listen (and broadcast pings) on its channel for ESP-NOW packets + // otherwise we'll try to find our master by hopping channels and PINGing it (masterEspNow), a successful delivery (unicast) is indicated in callback function + bool isMasterDefined = masterESPNow[0] | masterESPNow[1] | masterESPNow[2] | masterESPNow[3] | masterESPNow[4] | masterESPNow[5]; + if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && wifiConfigured && !sendNotificationsRT && isMasterDefined && now - lastReconnectAttempt > 7000) { if (statusESPNow == ESP_NOW_STATE_UNINIT) { quickEspNow.onDataSent(espNowSentCB); // see udp.cpp quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp - DEBUG_PRINTLN(F("ESP-NOW initing in unconnected STA mode.")); - bool espNowOK = quickEspNow.begin(channelESPNow); // use changeable channel STA mode + DEBUG_PRINTF_P(PSTR("ESP-NOW initing in unconnected AP/STA mode (channel %d).\n"), (int)channelESPNow); + WiFi.mode(WIFI_AP); + bool espNowOK = quickEspNow.begin(channelESPNow, WIFI_IF_AP); // use changeable channel STA mode statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; - scanESPNow = now; + scanESPNow = now; // prevent immediate change of channel +// char buffer[5]; strcpy_P(buffer, PSTR("PING")); +// quickEspNow.send(masterESPNow, (uint8_t*)buffer, sizeof(buffer)); } - if (statusESPNow == ESP_NOW_STATE_ON && now - scanESPNow > 10000) { - if (++channelESPNow > 13) channelESPNow = 1; - quickEspNow.setChannel(channelESPNow); - scanESPNow = now; + if (statusESPNow == ESP_NOW_STATE_ON) { + if (now > 4000 + scanESPNow) { + if (++channelESPNow > 13) channelESPNow = 1; + if (!quickEspNow.setChannel(channelESPNow)) DEBUG_PRINTLN(F("ESP-NOW Unable to set channel.")); + else DEBUG_PRINTF_P(PSTR("ESP-NOW channel %d set (wifi: %d).\n"), (int)channelESPNow, WiFi.channel()); + scanESPNow = now; +// char buffer[5]; strcpy_P(buffer, PSTR("PING")); +// quickEspNow.send(masterESPNow, (uint8_t*)buffer, sizeof(buffer)); + } } } #endif @@ -1074,12 +1110,17 @@ void WLED::handleConnection() } } else { #ifndef WLED_DISABLE_ESPNOW - // already established connection, send ESP-NOW beacon every 5s if we are in sync mode (AKA master device) - if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON && sendNotificationsRT && now - scanESPNow > 5000) { - uint8_t buffer[12]; - memcpy_P(buffer, PSTR("WLED MASTER."), 12); // use PROGMEM for string - quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, buffer, 12); + // already established connection, send ESP-NOW beacon every 2s if we are in sync mode (AKA master device) + // beacon will contain current/intended channel and local time (for loose synchronisation purposes) + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON && sendNotificationsRT && now > 2000 + scanESPNow) { + EspNowBeacon buffer; + strcpy(buffer.header, "WLED"); + buffer.version = 0; // not intended to change beyond 15 (0 means unspecified/irrelevant) + buffer.channel = WiFi.channel(); + buffer.time = toki.second(); + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), sizeof(buffer)); scanESPNow = now; + DEBUG_PRINTF_P(PSTR("ESP-NOW beacon on channel %d.\n"), WiFi.channel()); } #endif } diff --git a/wled00/wled.h b/wled00/wled.h index ac5f1c644e..78f720c6f2 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -79,6 +79,8 @@ #include #define WIFI_MODE_STA WIFI_STA #define WIFI_MODE_AP WIFI_AP + #define WIFI_MODE_APSTA WIFI_AP_STA + #define ESP_NOW_SEND_SUCCESS 0 #include #endif #else // ESP32 @@ -328,7 +330,11 @@ WLED_GLOBAL char cmDNS[33] _INIT(MDNS_NAME); // mDNS addres WLED_GLOBAL char apSSID[33] _INIT(""); // AP off by default (unless setup) WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID +#ifndef WLED_AP_TIMEOUT WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default +#else +WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_TEMPORARY); // access point opens when no connection after boot by default +#endif #ifdef ARDUINO_ARCH_ESP32 WLED_GLOBAL bool noWifiSleep _INIT(true); // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues #else @@ -502,8 +508,8 @@ WLED_GLOBAL uint16_t serialBaud _INIT(1152); // serial baud rate, multiply by 10 WLED_GLOBAL bool enableESPNow _INIT(false); // global on/off for ESP-NOW WLED_GLOBAL byte statusESPNow _INIT(ESP_NOW_STATE_UNINIT); // state of ESP-NOW stack (0 uninitialised, 1 initialised, 2 error) WLED_GLOBAL bool useESPNowSync _INIT(false); // use ESP-NOW wireless technology for sync -WLED_GLOBAL char linked_remote[13] _INIT(""); // MAC of ESP-NOW remote (Wiz Mote) -WLED_GLOBAL char last_signal_src[13] _INIT(""); // last seen ESP-NOW sender +WLED_GLOBAL byte masterESPNow[6] _INIT_N(({0,0,0,0,0,0})); // MAC of ESP-NOW sync master or linked remote (Wiz Mote) +WLED_GLOBAL byte senderESPNow[6] _INIT_N(({0,0,0,0,0,0})); // last seen ESP-NOW sender WLED_GLOBAL byte channelESPNow _INIT(1); // last channel used when searching for master WLED_GLOBAL unsigned long scanESPNow _INIT(0UL); #endif diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 7da3017152..89298e5303 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -292,6 +292,8 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("WS"),noWifiSleep); #ifndef WLED_DISABLE_ESPNOW + char linked_remote[13]; + sprintf_P(linked_remote, PSTR("%02x%02x%02x%02x%02x%02x"), MAC2STR(masterESPNow)); sappend('c',SET_F("RE"),enableESPNow); sappends('s',SET_F("RMAC"),linked_remote); #else @@ -333,7 +335,9 @@ void getSettingsJS(byte subPage, char* dest) } #ifndef WLED_DISABLE_ESPNOW - if (strlen(last_signal_src) > 0) { //Have seen an ESP-NOW Remote + if (senderESPNow[0] | senderESPNow[1] | senderESPNow[2] | senderESPNow[3] | senderESPNow[4] | senderESPNow[5]) { //Have seen an ESP-NOW Remote + char last_signal_src[13]; + sprintf_P(last_signal_src, PSTR("%02x%02x%02x%02x%02x%02x"), MAC2STR(senderESPNow)); sappends('m',SET_F("(\"rlid\")[0]"),last_signal_src); } else if (!enableESPNow) { sappends('m',SET_F("(\"rlid\")[0]"),(char*)F("(Enable ESP-NOW to listen)")); From c3994874bca861c142e799d61c986edf2ddd04c1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 29 Jul 2024 20:34:14 +0200 Subject: [PATCH 03/16] Fix for channel drift - rename structures - change message header (unify) - allow *any* master (rely on sync groups) - prevent WiFi reconnection while unconnected and master is available --- wled00/fcn_declare.h | 16 ++++++++++++---- wled00/udp.cpp | 31 +++++++++++++------------------ wled00/wled.cpp | 32 +++++++++++++------------------- 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index cfbe2b5e97..122348f8c8 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -249,12 +249,20 @@ void refreshNodeList(); void sendSysInfoUDP(); #ifndef WLED_DISABLE_ESPNOW typedef struct { - char header[5]; // enough to store "WLED" (including termination 0) - uint8_t version:4; // message packet version (changes when packet size changes) - uint8_t channel:4; // master's WiFi channel used + char magic[4]; // enough to store "WLED" + uint8_t packet:4; // packet sequence + uint8_t noOfPackets:4;// total number of packets + uint8_t data[245]; // payload +} __attribute__((packed, aligned(1))) EspNowPartialPacket; + +typedef struct { + char magic[4]; // enough to store "WLED" + uint8_t version:4; // message packet version (changes when packet size changes); not intended to change beyond 15 (0 means unspecified/irrelevant) + uint8_t channel:4; // master's WiFi channel used uint32_t time; // may be used for time synchronisation (NOTE: time_t varies in size on ESP32 and ESP8266) - uint8_t reserved[6]; // 6 bytes reserved for future use + uint8_t reserved[7]; // 7 bytes reserved for future use } __attribute__((packed, aligned(1))) EspNowBeacon; + void espNowSentCB(uint8_t* address, uint8_t status); void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rssi, bool broadcast); #endif diff --git a/wled00/udp.cpp b/wled00/udp.cpp index bc3397fd35..200b91caa6 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -10,12 +10,6 @@ #define UDP_IN_MAXSIZE 1472 #define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times -typedef struct PartialEspNowPacket { - uint8_t magic; - uint8_t packet; - uint8_t noOfPackets; - uint8_t data[247]; -} partial_packet_t; void notify(byte callMode, bool followUp) { @@ -151,7 +145,7 @@ void notify(byte callMode, bool followUp) #ifndef WLED_DISABLE_ESPNOW if (enableESPNow && useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { - partial_packet_t buffer = {'W', 0, 1, {0}}; + EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; // send global data DEBUG_PRINTLN(F("ESP-NOW sending first packet.")); const size_t bufferSize = sizeof(buffer.data)/sizeof(uint8_t); @@ -983,24 +977,25 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs // usermods hook can override processing if (usermods.onEspNowMessage(address, data, len)) return; - // only handle messages from linked master/remote (ignore PING messages) - bool isMasterDefined = masterESPNow[0] | masterESPNow[1] | masterESPNow[2] | masterESPNow[3] | masterESPNow[4] | masterESPNow[5]; - if (!isMasterDefined || memcmp(senderESPNow, masterESPNow, 6) != 0) { + // only handle messages from linked master/remote (ignore PING messages) or any master/remote if 0xFFFFFFFFFFFF + uint8_t anyMaster[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + if (memcmp(senderESPNow, masterESPNow, 6) != 0 && memcmp(masterESPNow, anyMaster, 6) != 0) { DEBUG_PRINTF_P(PSTR("ESP-NOW unpaired remote sender (expected " MACSTR ").\n"), MAC2STR(masterESPNow)); return; } // is received packet a master's heartbeat - if (len == sizeof(EspNowBeacon) && memcmp(data, "WLED", 4) == 0) { + EspNowBeacon *master = reinterpret_cast(data); + if (len == sizeof(EspNowBeacon) && memcmp(master->magic, "WLED", 4) == 0) { DEBUG_PRINTF_P(PSTR("ESP-NOW master heartbeat (wifi: %d).\n"), WiFi.channel()); - EspNowBeacon master; - memcpy(&master, data, sizeof(EspNowBeacon)); - toki.setTime(master.time, TOKI_NO_MS_ACCURACY, TOKI_TS_SEC); + toki.setTime(master->time, TOKI_NO_MS_ACCURACY, TOKI_TS_SEC); updateLocalTime(); // we can assume that slave does not have access to NTP - scanESPNow = millis() + 60000; // disable scanning for a few seconds and do no futrher processing + if (!WLED_CONNECTED) lastReconnectAttempt = millis(); // prevent reconnecting to WiFi if configured (due to channel switching) + scanESPNow = millis() + 60000; // disable scanning for a few seconds and do no futrher processing + channelESPNow = master->channel; // if/when master will allow roaming and could inform slaves the following can be used //if (master.channel != WiFi.channel()) { - // channelESPNow = master.channel; + // channelESPNow = master->channel; // quickEspNow.setChannel(channelESPNow); // scanESPNow = millis(); //} @@ -1013,8 +1008,8 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs return; } - partial_packet_t *buffer = reinterpret_cast(data); - if (len < 3 || !broadcast || buffer->magic != 'W' || !useESPNowSync || WLED_CONNECTED) { + EspNowPartialPacket *buffer = reinterpret_cast(data); + if (len < 6 || !broadcast || !useESPNowSync || memcmp(buffer->magic, "WLED", 4) != 0 || WLED_CONNECTED) { DEBUG_PRINTLN(F("ESP-NOW unexpected packet, not syncing or connected to WiFi.")); return; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 62fcf4a920..5dd969c3aa 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -274,7 +274,7 @@ void WLED::loop() } DEBUG_PRINTF_P(PSTR("TX power: %d/%d\n"), WiFi.getTxPower(), txPower); #endif - DEBUG_PRINTF_P(PSTR("Wifi state: %d\n"), WiFi.status()); + DEBUG_PRINTF_P(PSTR("Wifi state: %d (channel %d, mode %d)\n"), WiFi.status(), WiFi.channel(), WiFi.getMode()); #ifndef WLED_DISABLE_ESPNOW DEBUG_PRINT(F("ESP-NOW state: ")); DEBUG_PRINTLN(statusESPNow); #endif @@ -998,6 +998,7 @@ void WLED::handleConnection() if (!Network.isConnected()) { if (!wifiConfigured && !apActive) { DEBUG_PRINTLN(F("WiFi not configured!")); + WiFi.mode(WIFI_MODE_AP); initAP(); // instantly go to ap mode return; } @@ -1025,8 +1026,8 @@ void WLED::handleConnection() sendImprovStateResponse(0x03, true); improvActive = 2; } - // try to reconnect if not connected after 300s if clients connected to wifi or 60s otherwise - if (now - lastReconnectAttempt > ((stac) ? 300000 : 60000) && wifiConfigured) { + // try to reconnect if not connected after 300s if clients connected to wifi or 30s otherwise + if (now - lastReconnectAttempt > ((stac) ? 300000 : 30000) && wifiConfigured) { if (improvActive == 2) improvActive = 3; DEBUG_PRINTLN(F("Last reconnect too old.")); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list @@ -1037,6 +1038,7 @@ void WLED::handleConnection() if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { DEBUG_PRINTLN(F("Not connected AP.")); + WiFi.mode(WIFI_MODE_APSTA); initAP(); // start AP only within first 5min } } @@ -1060,30 +1062,26 @@ void WLED::handleConnection() } } #ifndef WLED_DISABLE_ESPNOW - // we are still not connected to WiFi and we may have AP active or not. if AP is active we will listen (and broadcast pings) on its channel for ESP-NOW packets - // otherwise we'll try to find our master by hopping channels and PINGing it (masterEspNow), a successful delivery (unicast) is indicated in callback function + // we are still not connected to WiFi and we may have AP active or not. if AP is active we will listen on its channel for ESP-NOW packets + // otherwise we'll try to find our master by hopping channels (master is detected in ESP-NOW receive callback) bool isMasterDefined = masterESPNow[0] | masterESPNow[1] | masterESPNow[2] | masterESPNow[3] | masterESPNow[4] | masterESPNow[5]; - if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && wifiConfigured && !sendNotificationsRT && isMasterDefined && now - lastReconnectAttempt > 7000) { - if (statusESPNow == ESP_NOW_STATE_UNINIT) { + if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && !sendNotificationsRT && isMasterDefined) { + if (statusESPNow == ESP_NOW_STATE_UNINIT && wifiConfigured && now - lastReconnectAttempt > 7000) { quickEspNow.onDataSent(espNowSentCB); // see udp.cpp quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp - DEBUG_PRINTF_P(PSTR("ESP-NOW initing in unconnected AP/STA mode (channel %d).\n"), (int)channelESPNow); + DEBUG_PRINTF_P(PSTR("ESP-NOW initing in unconnected (no)AP mode (channel %d).\n"), (int)channelESPNow); WiFi.mode(WIFI_AP); bool espNowOK = quickEspNow.begin(channelESPNow, WIFI_IF_AP); // use changeable channel STA mode statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; scanESPNow = now; // prevent immediate change of channel -// char buffer[5]; strcpy_P(buffer, PSTR("PING")); -// quickEspNow.send(masterESPNow, (uint8_t*)buffer, sizeof(buffer)); } - if (statusESPNow == ESP_NOW_STATE_ON) { + if (statusESPNow == ESP_NOW_STATE_ON && wifiConfigured && now - lastReconnectAttempt > 7000) { if (now > 4000 + scanESPNow) { if (++channelESPNow > 13) channelESPNow = 1; if (!quickEspNow.setChannel(channelESPNow)) DEBUG_PRINTLN(F("ESP-NOW Unable to set channel.")); else DEBUG_PRINTF_P(PSTR("ESP-NOW channel %d set (wifi: %d).\n"), (int)channelESPNow, WiFi.channel()); scanESPNow = now; -// char buffer[5]; strcpy_P(buffer, PSTR("PING")); -// quickEspNow.send(masterESPNow, (uint8_t*)buffer, sizeof(buffer)); - } + } else if (WiFi.channel() != channelESPNow && WiFi.getMode() == WIFI_AP) quickEspNow.setChannel(channelESPNow); // sometimes channel will chnage, force it back } } #endif @@ -1113,11 +1111,7 @@ void WLED::handleConnection() // already established connection, send ESP-NOW beacon every 2s if we are in sync mode (AKA master device) // beacon will contain current/intended channel and local time (for loose synchronisation purposes) if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON && sendNotificationsRT && now > 2000 + scanESPNow) { - EspNowBeacon buffer; - strcpy(buffer.header, "WLED"); - buffer.version = 0; // not intended to change beyond 15 (0 means unspecified/irrelevant) - buffer.channel = WiFi.channel(); - buffer.time = toki.second(); + EspNowBeacon buffer = {{'W','L','E','D'}, 0, (uint8_t)WiFi.channel(), toki.second(), {0}}; quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), sizeof(buffer)); scanESPNow = now; DEBUG_PRINTF_P(PSTR("ESP-NOW beacon on channel %d.\n"), WiFi.channel()); From 26e2c0e967c2d49875f3484faa992df406ac09f1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 4 Aug 2024 16:47:34 +0200 Subject: [PATCH 04/16] Improvments. --- wled00/fcn_declare.h | 1 + wled00/json.cpp | 4 +- wled00/network.cpp | 108 +++++++++++++++++++++- wled00/udp.cpp | 16 ++-- wled00/wled.cpp | 214 ++++++++++++++++++------------------------- wled00/wled.h | 4 +- 6 files changed, 209 insertions(+), 138 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 122348f8c8..2a4ad6200b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -269,6 +269,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs //network.cpp int getSignalQuality(int rssi); +int8_t findWiFi(bool doScan = false); void WiFiEvent(WiFiEvent_t event); //um_manager.cpp diff --git a/wled00/json.cpp b/wled00/json.cpp index 8957096803..3c587b7877 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -494,9 +494,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) bool apMode = getBoolVal(wifi[F("ap")], apActive); if (!apActive && apMode) WLED::instance().initAP(); // start AP mode immediately else if (apActive && !apMode) { // stop AP mode immediately - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; + WLED::instance().stopAP(); } //bool restart = wifi[F("restart")] | false; //if (restart) forceReconnect = true; diff --git a/wled00/network.cpp b/wled00/network.cpp index 06860cf4de..18060f92c3 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -170,11 +170,116 @@ int getSignalQuality(int rssi) } +// performs asynchronous scan for available networks (which may take couple of seconds to finish) +// returns configured WiFi ID with the strongest signal (or default if no configured networks available) +int8_t findWiFi(bool doScan) { + if (multiWiFi.size() <= 1) { + DEBUG_PRINTLN(F("Defaulf WiFi used.")); + return 0; + } + + if (doScan) WiFi.scanDelete(); // restart scan + + int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <3s with not very crowded air) + + if (status == WIFI_SCAN_FAILED) { + DEBUG_PRINTLN(F("WiFi scan started.")); + WiFi.scanNetworks(true); // start scanning in asynchronous mode + } else if (status >= 0) { // status contains number of found networks + DEBUG_PRINT(F("WiFi scan completed: ")); DEBUG_PRINTLN(status); + int rssi = -9999; + unsigned selected = selectedWiFi; + for (int o = 0; o < status; o++) { + DEBUG_PRINT(F(" WiFi available: ")); DEBUG_PRINT(WiFi.SSID(o)); + DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(WiFi.RSSI(o)); DEBUG_PRINTLN(F("dB")); + for (unsigned n = 0; n < multiWiFi.size(); n++) + if (!strcmp(WiFi.SSID(o).c_str(), multiWiFi[n].clientSSID)) { + // find the WiFi with the strongest signal (but keep priority of entry if signal difference is not big) + if ((n < selected && WiFi.RSSI(o) > rssi-10) || WiFi.RSSI(o) > rssi) { + rssi = WiFi.RSSI(o); + selected = n; + } + break; + } + } + DEBUG_PRINT(F("Selected: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID); + DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(rssi); DEBUG_PRINTLN(F("dB")); + return selected; + } + //DEBUG_PRINT(F("WiFi scan running.")); + return status; // scan is still running or there was an error +} + //handle Ethernet connection event void WiFiEvent(WiFiEvent_t event) { switch (event) { -#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) +#ifdef ESP8266 + case WIFI_EVENT_STAMODE_GOT_IP: + DEBUG_PRINTLN(); + DEBUG_PRINT(F("IP address: ")); DEBUG_PRINTLN(Network.localIP()); + break; + case WIFI_EVENT_STAMODE_CONNECTED: + DEBUG_PRINTLN(F("WiFi: Connected!")); + wasConnected = true; + break; + case WIFI_EVENT_STAMODE_DISCONNECTED: + // called quite often (when not connected to WiFi) + if (wasConnected) { + DEBUG_PRINTLN(F("WiFi: Disconnected")); + interfacesInited = false; + findWiFi(true); // reinit WiFi scan + forceReconnect = true; + } + break; + case WIFI_EVENT_SOFTAPMODE_STACONNECTED: + // AP client connected + DEBUG_PRINTLN(F("WiFi: AP Client Connected")); + apClients++; + DEBUG_PRINTLN(apClients); + break; + case WIFI_EVENT_SOFTAPMODE_STADISCONNECTED: + // AP client disconnected + DEBUG_PRINTLN(F("WiFi: AP Client Disconnected")); + if (--apClients == 0 && WLED_WIFI_CONFIGURED) forceReconnect = true; // no clients reconnect WiFi if awailable + DEBUG_PRINTLN(apClients); + break; +#else + case SYSTEM_EVENT_AP_STADISCONNECTED: + // AP client disconnected + DEBUG_PRINTLN(F("WiFi: AP Client Disconnected")); + if (--apClients == 0 && WLED_WIFI_CONFIGURED) forceReconnect = true; // no clients reconnect WiFi if awailable + DEBUG_PRINTLN(apClients); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + // AP client connected + DEBUG_PRINTLN(F("WiFi: AP Client Connected")); + apClients++; + DEBUG_PRINTLN(apClients); + break; + case SYSTEM_EVENT_STA_GOT_IP: + DEBUG_PRINTLN(); + DEBUG_PRINT(F("IP address: ")); DEBUG_PRINTLN(Network.localIP()); + break; + case SYSTEM_EVENT_STA_CONNECTED: + DEBUG_PRINTLN(F("WiFi: Connected!")); + wasConnected = true; + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + if (wasConnected) { + DEBUG_PRINTLN(F("WiFi: Disconnected")); + interfacesInited = false; + findWiFi(true); // reinit WiFi scan + forceReconnect = true; + } + break; + case SYSTEM_EVENT_AP_START: + DEBUG_PRINTLN(F("WiFi: AP Started")); + break; + case SYSTEM_EVENT_AP_STOP: + DEBUG_PRINTLN(F("WiFi: AP Started")); + break; + #if defined(WLED_USE_ETHERNET) case SYSTEM_EVENT_ETH_START: DEBUG_PRINTLN(F("ETH Started")); break; @@ -205,6 +310,7 @@ void WiFiEvent(WiFiEvent_t event) // alternative access to the device. forceReconnect = true; break; + #endif #endif default: break; diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 200b91caa6..084893aabc 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -989,16 +989,12 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs if (len == sizeof(EspNowBeacon) && memcmp(master->magic, "WLED", 4) == 0) { DEBUG_PRINTF_P(PSTR("ESP-NOW master heartbeat (wifi: %d).\n"), WiFi.channel()); toki.setTime(master->time, TOKI_NO_MS_ACCURACY, TOKI_TS_SEC); - updateLocalTime(); // we can assume that slave does not have access to NTP - if (!WLED_CONNECTED) lastReconnectAttempt = millis(); // prevent reconnecting to WiFi if configured (due to channel switching) - scanESPNow = millis() + 60000; // disable scanning for a few seconds and do no futrher processing - channelESPNow = master->channel; - // if/when master will allow roaming and could inform slaves the following can be used - //if (master.channel != WiFi.channel()) { - // channelESPNow = master->channel; - // quickEspNow.setChannel(channelESPNow); - // scanESPNow = millis(); - //} + updateLocalTime(); // we can assume that slave does not have access to NTP + if (!heartbeatESPNow) calculateSunriseAndSunset(); // if this is first heartbeat update sunrise/sunset + if (!WLED_CONNECTED) lastReconnectAttempt = millis(); // prevent reconnecting to WiFi if configured (due to channel switching) + heartbeatESPNow = millis(); // update heartbeat time + scanESPNow = heartbeatESPNow + 30000; // disable scanning for a few seconds + channelESPNow = master->channel; // pre-configure if heartbeat is heard while scanning for WiFi return; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 5dd969c3aa..8a1607be1e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -479,11 +479,9 @@ void WLED::setup() if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) showWelcomePage = true; WiFi.persistent(false); - #ifdef WLED_USE_ETHERNET WiFi.onEvent(WiFiEvent); - #endif - WiFi.mode(WIFI_STA); // enable scanning + WiFi.mode(WIFI_MODE_STA); // enable scanning findWiFi(true); // start scanning for available WiFi-s #ifdef WLED_ENABLE_ADALIGHT @@ -601,6 +599,23 @@ void WLED::beginStrip() } } +// stop AP (optionally also stop ESP-NOW) +void WLED::stopAP(bool stopESPNow) { + DEBUG_PRINTLN(F("Stopping AP.")); +#ifndef WLED_DISABLE_ESPNOW + // we need to stop ESP-NOW as we are stopping AP + if (stopESPNow && statusESPNow == ESP_NOW_STATE_ON) { + DEBUG_PRINTLN(F("ESP-NOW stopping on AP stop.")); + quickEspNow.stop(); + statusESPNow = ESP_NOW_STATE_UNINIT; + scanESPNow = millis() + 8000; // postpone searching for a bit + } +#endif + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; +} + void WLED::initAP(bool resetAP) { if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) @@ -608,7 +623,7 @@ void WLED::initAP(bool resetAP) #ifndef WLED_DISABLE_ESPNOW if (statusESPNow == ESP_NOW_STATE_ON) { - DEBUG_PRINTLN(F("ESP-NOW stopping.")); + DEBUG_PRINTLN(F("ESP-NOW stopping on AP start.")); quickEspNow.stop(); statusESPNow = ESP_NOW_STATE_UNINIT; } @@ -768,54 +783,16 @@ bool WLED::initEthernet() #endif } -// performs asynchronous scan for available networks (which may take couple of seconds to finish) -// returns configured WiFi ID with the strongest signal (or default if no configured networks available) -int8_t WLED::findWiFi(bool doScan) { - if (multiWiFi.size() <= 1) { - DEBUG_PRINTLN(F("Defaulf WiFi used.")); - return 0; - } - - if (doScan) WiFi.scanDelete(); // restart scan - - int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <3s with not very crowded air) - - if (status == WIFI_SCAN_FAILED) { - DEBUG_PRINTLN(F("WiFi scan started.")); - WiFi.scanNetworks(true); // start scanning in asynchronous mode - } else if (status >= 0) { // status contains number of found networks - DEBUG_PRINT(F("WiFi scan completed: ")); DEBUG_PRINTLN(status); - int rssi = -9999; - unsigned selected = selectedWiFi; - for (int o = 0; o < status; o++) { - DEBUG_PRINT(F(" WiFi available: ")); DEBUG_PRINT(WiFi.SSID(o)); - DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(WiFi.RSSI(o)); DEBUG_PRINTLN(F("dB")); - for (unsigned n = 0; n < multiWiFi.size(); n++) - if (!strcmp(WiFi.SSID(o).c_str(), multiWiFi[n].clientSSID)) { - // find the WiFi with the strongest signal (but keep priority of entry if signal difference is not big) - if ((n < selected && WiFi.RSSI(o) > rssi-10) || WiFi.RSSI(o) > rssi) { - rssi = WiFi.RSSI(o); - selected = n; - } - break; - } - } - DEBUG_PRINT(F("Selected: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID); - DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(rssi); DEBUG_PRINTLN(F("dB")); - return selected; - } - //DEBUG_PRINT(F("WiFi scan running.")); - return status; // scan is still running or there was an error -} - +// initConnection() (re)starts connection to configured WiFi/SSIDs +// once the connection is established initInterfaces() is called void WLED::initConnection() { DEBUG_PRINTLN(F("initConnection() called.")); bool WiFiConfigured = WLED_WIFI_CONFIGURED; - #ifdef WLED_ENABLE_WEBSOCKETS +#ifdef WLED_ENABLE_WEBSOCKETS ws.onEvent(wsEvent); - #endif +#endif WiFi.disconnect(true); // close old connections #ifdef ESP8266 @@ -831,16 +808,16 @@ void WLED::initConnection() lastReconnectAttempt = millis(); if (!apActive) { - DEBUG_PRINTLN(F("Access point disabled (init).")); + DEBUG_PRINTLN(F("Access point disabled (init).")); #ifndef WLED_DISABLE_ESPNOW - if (statusESPNow == ESP_NOW_STATE_ON) { - DEBUG_PRINTLN(F("ESP-NOW stopping.")); - quickEspNow.stop(); - statusESPNow = ESP_NOW_STATE_UNINIT; - } + if (statusESPNow == ESP_NOW_STATE_ON) { + DEBUG_PRINTLN(F("ESP-NOW stopping on STA start.")); + quickEspNow.stop(); + statusESPNow = ESP_NOW_STATE_UNINIT; + } #endif - WiFi.softAPdisconnect(true); - WiFi.mode(WIFI_STA); + WiFi.softAPdisconnect(true); // force disconnect AP + WiFi.mode(WIFI_MODE_STA); } if (WiFiConfigured) { @@ -866,13 +843,14 @@ void WLED::initConnection() } } +// initInterfaces() is called when WiFi connection is established void WLED::initInterfaces() { DEBUG_PRINTLN(F("Init STA interfaces")); #ifndef WLED_DISABLE_ESPNOW if (statusESPNow == ESP_NOW_STATE_ON) { - DEBUG_PRINTLN(F("ESP-NOW stopping.")); + DEBUG_PRINTLN(F("ESP-NOW stopping on connect.")); quickEspNow.stop(); statusESPNow = ESP_NOW_STATE_UNINIT; } @@ -946,13 +924,11 @@ void WLED::initInterfaces() #endif interfacesInited = true; - wasConnected = true; } void WLED::handleConnection() { - static bool scanDone = true; - static byte stacO = 0; + //static bool scanDone = true; unsigned long now = millis(); const bool wifiConfigured = WLED_WIFI_CONFIGURED; @@ -961,134 +937,128 @@ void WLED::handleConnection() if ((wifiConfigured && multiWiFi.size() > 1 && WiFi.scanComplete() < 0) || (now < 2000 && (!wifiConfigured || apBehavior == AP_BEHAVIOR_ALWAYS))) return; - if (lastReconnectAttempt == 0 || forceReconnect) { + if (wifiConfigured && (lastReconnectAttempt == 0 || forceReconnect)) { DEBUG_PRINTLN(F("Initial connect or forced reconnect.")); selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); interfacesInited = false; forceReconnect = false; - wasConnected = false; + //wasConnected = false; // may not be appropriate when disconnecting Ethernet return; } - byte stac = 0; - if (apActive) { - // determine number of connected clients (if any) to AP -#ifdef ESP8266 - stac = wifi_softap_get_station_num(); -#else - wifi_sta_list_t stationList; - esp_wifi_ap_get_sta_list(&stationList); - stac = stationList.num; -#endif - if (stac != stacO) { - // if number of clients changed, do something - stacO = stac; - DEBUG_PRINT(F("Connected AP clients: ")); - DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && wifiConfigured) { // trying to connect, but not connected - if (stac) - WiFi.disconnect(); // disable search so that AP can work - else - initConnection(); // restart search - } - } - } - if (!Network.isConnected()) { if (!wifiConfigured && !apActive) { - DEBUG_PRINTLN(F("WiFi not configured!")); + DEBUG_PRINTLN(F("WiFi not configured opening AP!")); WiFi.mode(WIFI_MODE_AP); - initAP(); // instantly go to ap mode + initAP(); // instantly go to AP mode return; } if (!apActive && apBehavior == AP_BEHAVIOR_ALWAYS) { - DEBUG_PRINTLN(F("Access point ALWAYS enabled.")); - WiFi.mode(WIFI_MODE_APSTA); + DEBUG_PRINTLN(F("AP ALWAYS enabled.")); + WiFi.mode(WIFI_MODE_APSTA); // this will keep AP's channel in sync with STA channel initAP(); } +/* if (interfacesInited) { - // we were connected but diconnect happened + // we were connected but diconnect happened (we can't use events as disconnect is called too many times) if (scanDone && multiWiFi.size() > 1) { + // if we have multiple SSIDs configured rescan WiFi for best match DEBUG_PRINTLN(F("WiFi scan initiated on disconnect.")); findWiFi(true); // reinit scan scanDone = false; return; // try to connect in next iteration } + // 2nd iteration of the same event; choose best SSID and try to reconnect DEBUG_PRINTLN(F("Disconnected!")); selectedWiFi = findWiFi(); initConnection(); interfacesInited = false; scanDone = true; } +*/ //send improv failed 6 seconds after second init attempt (24 sec. after provisioning) if (improvActive > 2 && now - lastReconnectAttempt > 6000) { sendImprovStateResponse(0x03, true); improvActive = 2; } - // try to reconnect if not connected after 300s if clients connected to wifi or 30s otherwise - if (now - lastReconnectAttempt > ((stac) ? 300000 : 30000) && wifiConfigured) { + // WiFi is configured; try to reconnect if not connected after 20s or 300s if clients connected to AP + // this will cycle through all configured SSIDs (findWiFi() sorted SSIDs by signal strength) + if (wifiConfigured && now - lastReconnectAttempt > ((apClients) ? 300000 : 20000)) { +#ifndef WLED_DISABLE_ESPNOW + // wait for 3 skipped heartbeats if ESP-NOW sync is enabled + if (now > 12000 + heartbeatESPNow) +#endif + { if (improvActive == 2) improvActive = 3; DEBUG_PRINTLN(F("Last reconnect too old.")); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); return; + } } - // open AP if this is after boot or forced (_NO_CONN) + // open AP if this is 12s after boot connect attempt or 12s after any disconnect (_NO_CONN) + // !wasConnected means this is after boot and we haven't yet successfully connected to SSID if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { - DEBUG_PRINTLN(F("Not connected AP.")); - WiFi.mode(WIFI_MODE_APSTA); - initAP(); // start AP only within first 5min + DEBUG_PRINTLN(F("Opening not connected AP.")); + WiFi.mode(WIFI_MODE_AP); + initAP(); // start temporary AP only within first 5min + return; } } // disconnect AP after 5min if no clients connected and mode TEMPORARY - if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && stac == 0) { + if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && apClients == 0) { // if AP was enabled more than 10min after boot or if client was connected more than 10min after boot do not disconnect AP mode if (now < 2*WLED_AP_TIMEOUT) { -#ifndef WLED_DISABLE_ESPNOW - // we need to stop ESP-NOW as we are stopping AP - if (statusESPNow == ESP_NOW_STATE_ON) { - DEBUG_PRINTLN(F("ESP-NOW stopping on AP stop.")); - quickEspNow.stop(); - statusESPNow = ESP_NOW_STATE_UNINIT; - scanESPNow = now + 5000; // postpone searching for a bit - } -#endif - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; DEBUG_PRINTLN(F("Temporary AP disabled.")); + stopAP(); + return; } } #ifndef WLED_DISABLE_ESPNOW // we are still not connected to WiFi and we may have AP active or not. if AP is active we will listen on its channel for ESP-NOW packets // otherwise we'll try to find our master by hopping channels (master is detected in ESP-NOW receive callback) bool isMasterDefined = masterESPNow[0] | masterESPNow[1] | masterESPNow[2] | masterESPNow[3] | masterESPNow[4] | masterESPNow[5]; - if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && !sendNotificationsRT && isMasterDefined) { - if (statusESPNow == ESP_NOW_STATE_UNINIT && wifiConfigured && now - lastReconnectAttempt > 7000) { + if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && !sendNotificationsRT && isMasterDefined && now > WLED_AP_TIMEOUT) { + // wait for 15s after starting to scan for WiFi before starting ESP-NOW (give ESP a chance to connect) + if (statusESPNow == ESP_NOW_STATE_UNINIT && wifiConfigured && now - lastReconnectAttempt > 15000) { quickEspNow.onDataSent(espNowSentCB); // see udp.cpp quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp DEBUG_PRINTF_P(PSTR("ESP-NOW initing in unconnected (no)AP mode (channel %d).\n"), (int)channelESPNow); - WiFi.mode(WIFI_AP); - bool espNowOK = quickEspNow.begin(channelESPNow, WIFI_IF_AP); // use changeable channel STA mode + WiFi.disconnect(); // stop looking for WiFi + WiFi.mode(WIFI_MODE_AP); // force AP mode to fix channel + bool espNowOK = quickEspNow.begin(channelESPNow, WIFI_IF_AP); // use fixed channel AP mode statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; - scanESPNow = now; // prevent immediate change of channel + scanESPNow = now; // prevent immediate change of channel + heartbeatESPNow = 0UL; } - if (statusESPNow == ESP_NOW_STATE_ON && wifiConfigured && now - lastReconnectAttempt > 7000) { + if (statusESPNow == ESP_NOW_STATE_ON && wifiConfigured) { if (now > 4000 + scanESPNow) { + // change channel every 4s (after 30s of last heartbeat) if (++channelESPNow > 13) channelESPNow = 1; if (!quickEspNow.setChannel(channelESPNow)) DEBUG_PRINTLN(F("ESP-NOW Unable to set channel.")); - else DEBUG_PRINTF_P(PSTR("ESP-NOW channel %d set (wifi: %d).\n"), (int)channelESPNow, WiFi.channel()); + else { + DEBUG_PRINTF_P(PSTR("ESP-NOW channel %d set (wifi: %d).\n"), (int)channelESPNow, WiFi.channel()); + // update AP channel to match ESP-NOW channel + #ifdef ESP8266 + struct softap_config conf; + wifi_softap_get_config(&conf); + conf.channel = channelESPNow; + wifi_softap_set_config_current(&conf); + #else + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_AP, &conf); + conf.ap.channel = channelESPNow; + esp_wifi_set_config(WIFI_IF_AP, &conf); + #endif + } scanESPNow = now; - } else if (WiFi.channel() != channelESPNow && WiFi.getMode() == WIFI_AP) quickEspNow.setChannel(channelESPNow); // sometimes channel will chnage, force it back + } else if (WiFi.channel() != channelESPNow && WiFi.getMode() == WIFI_MODE_AP) quickEspNow.setChannel(channelESPNow); // sometimes channel will chnage, force it back } } #endif } else if (!interfacesInited) { //newly connected - DEBUG_PRINTLN(); - DEBUG_PRINT(F("Connected! IP address: ")); - DEBUG_PRINTLN(Network.localIP()); if (improvActive) { if (improvError == 3) sendImprovStateResponse(0x00, true); sendImprovStateResponse(0x04); @@ -1101,10 +1071,8 @@ void WLED::handleConnection() // shut down AP if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; - DEBUG_PRINTLN(F("Access point disabled (connected).")); + stopAP(false); // do not stop ESP-NOW + DEBUG_PRINTLN(F("AP disabled (connected).")); } } else { #ifndef WLED_DISABLE_ESPNOW diff --git a/wled00/wled.h b/wled00/wled.h index 78f720c6f2..905313cb8f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -512,6 +512,7 @@ WLED_GLOBAL byte masterESPNow[6] _INIT_N(({0,0,0,0,0,0})); // MAC of ESP-NOW WLED_GLOBAL byte senderESPNow[6] _INIT_N(({0,0,0,0,0,0})); // last seen ESP-NOW sender WLED_GLOBAL byte channelESPNow _INIT(1); // last channel used when searching for master WLED_GLOBAL unsigned long scanESPNow _INIT(0UL); +WLED_GLOBAL unsigned long heartbeatESPNow _INIT(0UL); // last heartbeat/beacon millis() #endif // Time CONFIG @@ -562,6 +563,7 @@ WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use i // internal global variable declarations // wifi WLED_GLOBAL bool apActive _INIT(false); +WLED_GLOBAL byte apClients _INIT(0); WLED_GLOBAL bool forceReconnect _INIT(false); WLED_GLOBAL unsigned long lastReconnectAttempt _INIT(0); WLED_GLOBAL bool interfacesInited _INIT(false); @@ -923,10 +925,10 @@ class WLED { void beginStrip(); void handleConnection(); bool initEthernet(); // result is informational + void stopAP(bool stopESPNow = true); void initAP(bool resetAP = false); void initConnection(); void initInterfaces(); - int8_t findWiFi(bool doScan = false); #if defined(STATUSLED) void handleStatusLED(); #endif From 1ba99e8c1f31475bb5a8a95955f0ccebc1e2bc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 13 Aug 2024 08:29:00 +0200 Subject: [PATCH 05/16] Fix for packet size. --- wled00/udp.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 084893aabc..138fa8d637 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -149,7 +149,7 @@ void notify(byte callMode, bool followUp) // send global data DEBUG_PRINTLN(F("ESP-NOW sending first packet.")); const size_t bufferSize = sizeof(buffer.data)/sizeof(uint8_t); - size_t packetSize = 41; + size_t packetSize = 41; // size of static UDP data (excluding segments) size_t s0 = 0; memcpy(buffer.data, udpOut, packetSize); // stuff as many segments in first packet as possible (normally up to 5) @@ -159,7 +159,7 @@ void notify(byte callMode, bool followUp) s0++; } if (s > s0) buffer.noOfPackets += 1 + ((s - s0) * UDP_SEG_SIZE) / bufferSize; // set number of packets - auto err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize+3); + auto err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize + sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data)); if (!err && s0 < s) { // send rest of the segments buffer.packet++; @@ -172,7 +172,7 @@ void notify(byte callMode, bool followUp) packetSize += UDP_SEG_SIZE; if (packetSize + UDP_SEG_SIZE < bufferSize) continue; DEBUG_PRINTF_P(PSTR("ESP-NOW sending packet: %d (%d)\n"), (int)buffer.packet, packetSize+3); - err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize+3); + err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize + sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data)); buffer.packet++; packetSize = 0; if (err) break; @@ -978,8 +978,8 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs if (usermods.onEspNowMessage(address, data, len)) return; // only handle messages from linked master/remote (ignore PING messages) or any master/remote if 0xFFFFFFFFFFFF - uint8_t anyMaster[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - if (memcmp(senderESPNow, masterESPNow, 6) != 0 && memcmp(masterESPNow, anyMaster, 6) != 0) { + //uint8_t anyMaster[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + if (memcmp(senderESPNow, masterESPNow, 6) != 0 && memcmp(masterESPNow, ESPNOW_BROADCAST_ADDRESS, 6) != 0) { DEBUG_PRINTF_P(PSTR("ESP-NOW unpaired remote sender (expected " MACSTR ").\n"), MAC2STR(masterESPNow)); return; } From 39be781f06c30d8eebfe1447b01f7a71c05ac1d9 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 14 Aug 2024 21:40:41 +0200 Subject: [PATCH 06/16] Reconnect issues --- wled00/network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/network.cpp b/wled00/network.cpp index 18060f92c3..5d58ae01c2 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -227,8 +227,8 @@ void WiFiEvent(WiFiEvent_t event) // called quite often (when not connected to WiFi) if (wasConnected) { DEBUG_PRINTLN(F("WiFi: Disconnected")); + if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan interfacesInited = false; - findWiFi(true); // reinit WiFi scan forceReconnect = true; } break; @@ -268,8 +268,8 @@ void WiFiEvent(WiFiEvent_t event) case SYSTEM_EVENT_STA_DISCONNECTED: if (wasConnected) { DEBUG_PRINTLN(F("WiFi: Disconnected")); + if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan interfacesInited = false; - findWiFi(true); // reinit WiFi scan forceReconnect = true; } break; From ea9bef61a3a47e020f42006629ec026ed17c8e9d Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 14 Aug 2024 22:19:15 +0200 Subject: [PATCH 07/16] Add ESP-NOW audio sync POC --- usermods/audioreactive/audio_reactive.h | 205 +++++++++++++++--------- 1 file changed, 125 insertions(+), 80 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 088ac880b8..2afb9cf931 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -151,7 +151,7 @@ static bool useBandPassFilter = false; // if true, enables a // some prototypes, to ensure consistent interfaces static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float static float fftAddAvg(int from, int to); // average of several FFT result bins -void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results +static void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels @@ -174,6 +174,13 @@ static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table #endif // audio source parameters and constant +#ifdef CONFIG_IDF_TARGET_ESP32C3 +constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +#elif defined(CONFIG_IDF_TARGET_ESP32S2) +constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling +#else constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms //constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms //constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms @@ -182,21 +189,22 @@ constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz //#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling //#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling //#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling +#endif // FFT Constants constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. // the following are observed values, supported by a bit of "educated guessing" -//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels -#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels -#define LOG_256 5.54517744f // log(256) +//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels +#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels +#define LOG_256 5.54517744f // log(256) // These are the input and output vectors. Input vectors receive computed results from FFT. static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins static float vImag[samplesFFT] = {0.0f}; // imaginary parts // Create FFT object -// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 +// lib_deps += https://github.com/kosme/arduinoFFT @ 2.0.1 // these options actually cause slow-downs on all esp32 processors, don't use them. // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 // #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 @@ -207,7 +215,7 @@ static float vImag[samplesFFT] = {0.0f}; // imaginary parts #include /* Create FFT object with weighing factor storage */ -static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); +static ArduinoFFT FFT = ArduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE, true); // Helper functions @@ -293,7 +301,7 @@ void FFTcode(void * parameter) //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes - vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. + vReal[0] = 0.0f; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects @@ -304,8 +312,8 @@ void FFTcode(void * parameter) } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. memset(vReal, 0, sizeof(vReal)); - FFT_MajorPeak = 1; - FFT_Magnitude = 0.001; + FFT_MajorPeak = 1.0f; + FFT_Magnitude = 0.001f; } for (int i = 0; i < samplesFFT; i++) { @@ -446,24 +454,23 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function // Manual linear adjustment of gain using sampleGain adjustment for different input types. fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment - if(fftCalc[i] < 0) fftCalc[i] = 0; + if (fftCalc[i] < 0) fftCalc[i] = 0.0f; } // smooth results - rise fast, fall slower - if(fftCalc[i] > fftAvg[i]) // rise fast - fftAvg[i] = fftCalc[i] *0.75f + 0.25f*fftAvg[i]; // will need approx 2 cycles (50ms) for converging against fftCalc[i] - else { // fall slow - if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero + if (fftCalc[i] > fftAvg[i]) fftAvg[i] = fftCalc[i]*0.75f + 0.25f*fftAvg[i]; // rise fast; will need approx 2 cycles (50ms) for converging against fftCalc[i] + else { // fall slow + if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero - else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero + else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero } // constrain internal vars - just to be sure fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); float currentResult; - if(limiterOn == true) + if (limiterOn == true) currentResult = fftAvg[i]; else currentResult = fftCalc[i]; @@ -476,30 +483,30 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] - break; + currentResult = mapf(currentResult, 0.0f, LOG_256, 0.0f, 255.0f); // map [log(1) ... log(255)] to [0 ... 255] + break; case 2: // Linear scaling currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0f; // giving a bit more room for peaks + currentResult -= 4.0f; // giving a bit more room for peaks (WLEDMM uses -2) if (currentResult < 1.0f) currentResult = 0.0f; currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies - break; + break; case 3: // square root scaling currentResult *= 0.38f; currentResult -= 6.0f; if (currentResult > 1.0f) currentResult = sqrtf(currentResult); - else currentResult = 0.0f; // special handling, because sqrt(0) = undefined + else currentResult = 0.0f; // special handling, because sqrt(0) = undefined currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies - currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] - break; + currentResult = mapf(currentResult, 0.0f, 16.0f, 0.0f, 255.0f); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] + break; case 0: default: // no scaling - leave freq bins as-is - currentResult -= 4; // just a bit more room for peaks - break; + currentResult -= 4; // just a bit more room for peaks (WLEDMM uses -2) + break; } // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. @@ -614,7 +621,7 @@ class AudioReactive : public Usermod { double FFT_MajorPeak; // 08 Bytes }; - #define UDPSOUND_MAX_PACKET 88 // max packet size for audiosync + constexpr static unsigned UDPSOUND_MAX_PACKET = MAX(sizeof(audioSyncPacket), sizeof(audioSyncPacket_v1)); // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) #ifdef UM_AUDIOREACTIVE_ENABLE @@ -638,13 +645,11 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 // used for AGC int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) - double control_integrated = 0.0; // persistent across calls to agcAvg(); "integrator control" = accumulated error - - + float control_integrated = 0.0f; // persistent across calls to agcAvg(); "integrator control" = accumulated error // variables used by getSample() and agcAvg() int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed - double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controller. - double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller + float sampleMax = 0.0f; // Max sample over a few seconds. Needed for AGC controller. + float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller float expAdjF = 0.0f; // Used for exponential filter. float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) @@ -787,8 +792,7 @@ class AudioReactive : public Usermod { float control_error; // "control error" input for PI control - if (last_soundAgc != soundAgc) - control_integrated = 0.0; // new preset - reset integrator + if (last_soundAgc != soundAgc) control_integrated = 0.0f; // new preset - reset integrator // For PI controller, we need to have a constant "frequency" // so let's make sure that the control loop is not running at insane speed @@ -799,12 +803,12 @@ class AudioReactive : public Usermod { if (time_now - last_time > 2) { last_time = time_now; - if((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0)) { + if ((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { // MIC signal is "squelched" - deliver silence tmpAgc = 0; // we need to "spin down" the intgrated error buffer - if (fabs(control_integrated) < 0.01) control_integrated = 0.0; - else control_integrated *= 0.91; + if (fabs(control_integrated) < 0.01f) control_integrated = 0.0f; + else control_integrated *= 0.91f; } else { // compute new setpoint if (tmpAgc <= agcTarget0Up[AGC_preset]) @@ -821,9 +825,9 @@ class AudioReactive : public Usermod { if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) - control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping + control_integrated += control_error * 0.002f * 0.25f; // 2ms = integration time; 0.25 for damping else - control_integrated *= 0.9; // spin down that beasty integrator + control_integrated *= 0.9f; // spin down that beasty integrator // apply PI Control tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain @@ -891,29 +895,29 @@ class AudioReactive : public Usermod { #endif micLev += (micDataReal-micLev) / 12288.0f; - if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal + if (micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align micLev to lowest input signal - micIn -= micLev; // Let's center it to 0 now + micIn -= micLev; // Let's center it to 0 now // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. float micInNoDC = fabsf(micDataReal - micLev); expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF); expAdjF = fabsf(expAdjF); // Now (!) take the absolute value - expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate - if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; // do something meaningfull when "squelch = 0" + expAdjF = (expAdjF <= soundSquelch) ? 0.0f : expAdjF; // simple noise gate + if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0.0f; // do something meaningfull when "squelch = 0" tmpSample = expAdjF; micIn = abs(micIn); // And get the absolute value of each sample - sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment + sampleAdj = tmpSample * sampleGain * inputLevel / 5120.0f /* /40 /128 */ + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment sampleReal = tmpSample; - sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? + sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! // keep "peak" sample, but decay value if current sample is below peak if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { - sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering + sampleMax += 0.5f * (sampleReal - sampleMax); // new peak - with some filtering // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { samplePeak = true; @@ -939,7 +943,7 @@ class AudioReactive : public Usermod { */ // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) void limitSampleDynamics(void) { - const float bigChange = 196; // just a representative number - a large, expected sample value + const float bigChange = 196.0f; // just a representative number - a large, expected sample value static unsigned long last_time = 0; static float last_volumeSmth = 0.0f; @@ -989,7 +993,6 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 void transmitAudioData() { - if (!udpSyncConnected) return; //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); audioSyncPacket transmitData; @@ -1009,19 +1012,28 @@ class AudioReactive : public Usermod { transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; - if (fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error +#ifndef WLED_DISABLE_ESPNOW + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { + EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; + //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); + size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); + memcpy(buffer.data, &transmitData, sizeof(transmitData)); + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize); + } +#endif + + if (udpSyncConnected && fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); fftUdp.endPacket(); } - return; } // transmitAudioData() #endif - static bool isValidUdpSyncVersion(const char *header) { + static inline bool isValidUdpSyncVersion(const char *header) { return strncmp_P(header, UDP_SYNC_HEADER, 6) == 0; } - static bool isValidUdpSyncVersion_v1(const char *header) { + static inline bool isValidUdpSyncVersion_v1(const char *header) { return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0; } @@ -1045,9 +1057,8 @@ class AudioReactive : public Usermod { // If it's true already, then the animation still needs to respond. autoResetPeak(); if (!samplePeak) { - samplePeak = receivedPacket.samplePeak >0 ? true:false; - if (samplePeak) timeOfPeak = millis(); - //userVar1 = samplePeak; + samplePeak = receivedPacket.samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); } //These values are only computed by ESP32 for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket.fftResult[i]; @@ -1073,15 +1084,14 @@ class AudioReactive : public Usermod { // If it's true already, then the animation still needs to respond. autoResetPeak(); if (!samplePeak) { - samplePeak = receivedPacket->samplePeak >0 ? true:false; - if (samplePeak) timeOfPeak = millis(); - //userVar1 = samplePeak; + samplePeak = receivedPacket->samplePeak > 0; + if (samplePeak) timeOfPeak = millis(); } //These values are only available on the ESP32 for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; - my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0); + my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0, 11025.0); // restrict value to range expected by effects + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects } bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. @@ -1239,20 +1249,15 @@ class AudioReactive : public Usermod { if (!audioSource) enabled = false; // audio failed to initialise #endif if (enabled) onUpdateBegin(false); // create FFT task, and initialize network - - + if (enabled) disableSoundProcessing = false; // all good - enable audio processing #ifdef ARDUINO_ARCH_ESP32 if (FFT_Task == nullptr) enabled = false; // FFT task creation failed - if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync - #ifdef WLED_DEBUG - DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #else + if ((!audioSource) || (!audioSource->isInitialized())) { + // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); - #endif disableSoundProcessing = true; } #endif - if (enabled) disableSoundProcessing = false; // all good - enable audio processing if (enabled) connectUDPSoundSync(); if (enabled && addPalettes) createAudioPalettes(); initDone = true; @@ -1446,19 +1451,19 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 void onUpdateBegin(bool init) override { -#ifdef WLED_DEBUG + #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) fftTime = sampleTime = 0; -#endif + #endif // gracefully suspend FFT task (if running) disableSoundProcessing = true; // reset sound data micDataReal = 0.0f; - volumeRaw = 0; volumeSmth = 0; - sampleAgc = 0; sampleAvg = 0; - sampleRaw = 0; rawSampleAgc = 0; - my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 1; - multAgc = 1; + volumeRaw = 0; volumeSmth = 0.0f; + sampleAgc = 0.0f; sampleAvg = 0.0f; + sampleRaw = 0; rawSampleAgc = 0.0f; + my_magnitude = 0.0f; FFT_Magnitude = 0.0f; FFT_MajorPeak = 1.0f; + multAgc = 1.0f; // reset FFT data memset(fftCalc, 0, sizeof(fftCalc)); memset(fftAvg, 0, sizeof(fftAvg)); @@ -1667,8 +1672,8 @@ class AudioReactive : public Usermod { if (receivedFormat == 2) infoArr.add(F(" v2")); } - #if defined(WLED_DEBUG) || defined(SR_DEBUG) - #ifdef ARDUINO_ARCH_ESP32 +#ifdef ARDUINO_ARCH_ESP32 + #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) infoArr = user.createNestedArray(F("Sampling time")); infoArr.add(float(sampleTime)/100.0f); infoArr.add(" ms"); @@ -1684,8 +1689,8 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime)/100.0f); - #endif - #endif + #endif +#endif } } @@ -1951,7 +1956,8 @@ class AudioReactive : public Usermod { //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black //} - + bool onEspNowMessage(uint8_t *sender, uint8_t *data, uint8_t len) override; + /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). * This could be used in the future for the system to determine whether your usermod is installed. @@ -2048,6 +2054,45 @@ void AudioReactive::fillAudioPalettes() { } } +#ifndef WLED_DISABLE_ESPNOW +bool AudioReactive::onEspNowMessage(uint8_t *senderESPNow, uint8_t *data, uint8_t len) { + // only handle messages from linked master/remote (ignore PING messages) or any master/remote if 0xFFFFFFFFFFFF + uint8_t anyMaster[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + if (memcmp(senderESPNow, masterESPNow, 6) != 0 && memcmp(masterESPNow, anyMaster, 6) != 0) { + //DEBUGSR_PRINTF("ESP-NOW unpaired remote sender (expected " MACSTR ").\n", MAC2STR(masterESPNow)); + return false; + } + + EspNowPartialPacket *buffer = reinterpret_cast(data); + if (len < 6 || !(audioSyncEnabled & 0x02) || !useESPNowSync || memcmp(buffer->magic, "WLED", 4) != 0 || WLED_CONNECTED) { + //DEBUGSR_PRINTLN(F("ESP-NOW unexpected packet, not syncing or connected to WiFi.")); + return false; + } + + //DEBUGSR_PRINTLN("ESP-NOW Received Audio Sync Packet"); + bool haveFreshData = false; + uint8_t *fftBuff = buffer->data; + len -= sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data); // adjust size + + // VERIFY THAT THIS IS A COMPATIBLE PACKET + if (len == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { + decodeAudioData(len, fftBuff); + haveFreshData = true; + receivedFormat = 2; + } else if (len == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { + decodeAudioData_v1(len, fftBuff); + haveFreshData = true; + receivedFormat = 1; + } else receivedFormat = 0; // unknown format + + if (haveFreshData) { + last_UDPTime = millis(); // fake UDP received packets + limitSampleDynamics(); + } + return haveFreshData; +} +#endif + // strings to reduce flash memory usage (used more than twice) const char AudioReactive::_name[] PROGMEM = "AudioReactive"; const char AudioReactive::_enabled[] PROGMEM = "enabled"; @@ -2060,5 +2105,5 @@ const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; #endif const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes"; -const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure +const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "AUD02"; // new sync header version, as format no longer compatible with previous structure const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature From 065691227c24e512e6eb263c449a857f8754f169 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 15 Aug 2024 09:41:46 +0200 Subject: [PATCH 08/16] Fix branch bleed error --- usermods/audioreactive/audio_reactive.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 2afb9cf931..15362d794d 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1451,7 +1451,7 @@ class AudioReactive : public Usermod { #ifdef ARDUINO_ARCH_ESP32 void onUpdateBegin(bool init) override { - #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) + #if defined(WLED_DEBUG) || defined(SR_DEBUG) fftTime = sampleTime = 0; #endif // gracefully suspend FFT task (if running) @@ -1673,7 +1673,7 @@ class AudioReactive : public Usermod { } #ifdef ARDUINO_ARCH_ESP32 - #if defined(WLED_DEBUG_USERMODS) && defined(SR_DEBUG) + #if defined(WLED_DEBUG) || defined(SR_DEBUG) infoArr = user.createNestedArray(F("Sampling time")); infoArr.add(float(sampleTime)/100.0f); infoArr.add(" ms"); From bb49afae27afe6e3c00e562c8ff873a9aa21b8ab Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 15 Aug 2024 19:53:01 +0200 Subject: [PATCH 09/16] Revert v2 header - prepend signature to distinguish packet --- usermods/audioreactive/audio_reactive.h | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 15362d794d..0b0958d9ad 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1017,8 +1017,9 @@ class AudioReactive : public Usermod { EspNowPartialPacket buffer = {{'W','L','E','D'}, 0, 1, {0}}; //DEBUGSR_PRINTLN(F("ESP-NOW Sending audio packet.")); size_t packetSize = sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) + sizeof(transmitData); - memcpy(buffer.data, &transmitData, sizeof(transmitData)); - quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize); + memcpy_P(buffer.data, PSTR("AUD"), 3); // prepend Audio sugnature + memcpy(buffer.data+3, &transmitData, sizeof(transmitData)); + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize + 3); } #endif @@ -2069,10 +2070,14 @@ bool AudioReactive::onEspNowMessage(uint8_t *senderESPNow, uint8_t *data, uint8_ return false; } + uint8_t *fftBuff = buffer->data; + // check for Audio signature + if (memcmp_P(fftBuff, PSTR("AUD"), 3) == 0) fftBuff += 3; // skip signature + else return false; + //DEBUGSR_PRINTLN("ESP-NOW Received Audio Sync Packet"); bool haveFreshData = false; - uint8_t *fftBuff = buffer->data; - len -= sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data); // adjust size + len -= sizeof(EspNowPartialPacket) - sizeof(EspNowPartialPacket::data) - 3; // adjust size // VERIFY THAT THIS IS A COMPATIBLE PACKET if (len == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { @@ -2080,9 +2085,9 @@ bool AudioReactive::onEspNowMessage(uint8_t *senderESPNow, uint8_t *data, uint8_ haveFreshData = true; receivedFormat = 2; } else if (len == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftBuff))) { - decodeAudioData_v1(len, fftBuff); - haveFreshData = true; - receivedFormat = 1; + decodeAudioData_v1(len, fftBuff); + haveFreshData = true; + receivedFormat = 1; } else receivedFormat = 0; // unknown format if (haveFreshData) { @@ -2105,5 +2110,5 @@ const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; #endif const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes"; -const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "AUD02"; // new sync header version, as format no longer compatible with previous structure +const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature From 231719dc0ad9da9e52059147bf720359e3ccbf74 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 17 Aug 2024 15:35:28 +0200 Subject: [PATCH 10/16] Connection refactoring --- usermods/audioreactive/audio_reactive.h | 5 +- .../usermod_v2_four_line_display_ALT.h | 2 +- wled00/fcn_declare.h | 1 + wled00/improv.cpp | 2 +- wled00/network.cpp | 16 +- wled00/wled.cpp | 148 ++++++++---------- wled00/wled.h | 1 - 7 files changed, 81 insertions(+), 94 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 0b0958d9ad..a74dbb4b4f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -174,12 +174,9 @@ static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table #endif // audio source parameters and constant -#ifdef CONFIG_IDF_TARGET_ESP32C3 +#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms #define FFT_MIN_CYCLE 30 // Use with 16Khz sampling -#elif defined(CONFIG_IDF_TARGET_ESP32S2) -constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms -#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling #else constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms //constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 008647fa7b..83d99440a7 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -625,7 +625,7 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { while (drawing && millis()-now < 25) delay(1); // wait if someone else is drawing if (drawing || lockRedraw) return; - if (apActive && WLED_WIFI_CONFIGURED && now<15000) { + if (apActive && isWiFiConfigured() && now < 15000) { knownSsid = apSSID; networkOverlay(PSTR("NETWORK INFO"),30000); return; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2a4ad6200b..e408f86a5b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -270,6 +270,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs //network.cpp int getSignalQuality(int rssi); int8_t findWiFi(bool doScan = false); +bool isWiFiConfigured(void); void WiFiEvent(WiFiEvent_t event); //um_manager.cpp diff --git a/wled00/improv.cpp b/wled00/improv.cpp index d18061ba2b..dc4a36c1c9 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -93,7 +93,7 @@ void handleImprovPacket() { case ImprovRPCType::Command_Wifi: parseWiFiCommand(rpcData); break; case ImprovRPCType::Request_State: { unsigned improvState = 0x02; //authorized - if (WLED_WIFI_CONFIGURED) improvState = 0x03; //provisioning + if (isWiFiConfigured()) improvState = 0x03; //provisioning if (Network.isConnected()) improvState = 0x04; //provisioned sendImprovStateResponse(improvState, false); if (improvState == 0x04) sendImprovIPRPCResult(ImprovRPCType::Request_State); diff --git a/wled00/network.cpp b/wled00/network.cpp index 5d58ae01c2..1c6fb20108 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -210,6 +210,12 @@ int8_t findWiFi(bool doScan) { return status; // scan is still running or there was an error } + +bool isWiFiConfigured() { + return multiWiFi.size() > 1 || (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp_P(multiWiFi[0].clientSSID, PSTR(DEFAULT_CLIENT_SSID)) != 0); +} + + //handle Ethernet connection event void WiFiEvent(WiFiEvent_t event) { @@ -241,14 +247,14 @@ void WiFiEvent(WiFiEvent_t event) case WIFI_EVENT_SOFTAPMODE_STADISCONNECTED: // AP client disconnected DEBUG_PRINTLN(F("WiFi: AP Client Disconnected")); - if (--apClients == 0 && WLED_WIFI_CONFIGURED) forceReconnect = true; // no clients reconnect WiFi if awailable + if (--apClients == 0 && isWiFiConfigured()) forceReconnect = true; // no clients reconnect WiFi if awailable DEBUG_PRINTLN(apClients); break; #else case SYSTEM_EVENT_AP_STADISCONNECTED: // AP client disconnected DEBUG_PRINTLN(F("WiFi: AP Client Disconnected")); - if (--apClients == 0 && WLED_WIFI_CONFIGURED) forceReconnect = true; // no clients reconnect WiFi if awailable + if (--apClients == 0 && isWiFiConfigured()) forceReconnect = true; // no clients reconnect WiFi if awailable DEBUG_PRINTLN(apClients); break; case SYSTEM_EVENT_AP_STACONNECTED: @@ -277,7 +283,7 @@ void WiFiEvent(WiFiEvent_t event) DEBUG_PRINTLN(F("WiFi: AP Started")); break; case SYSTEM_EVENT_AP_STOP: - DEBUG_PRINTLN(F("WiFi: AP Started")); + DEBUG_PRINTLN(F("WiFi: AP Stopped")); break; #if defined(WLED_USE_ETHERNET) case SYSTEM_EVENT_ETH_START: @@ -287,7 +293,8 @@ void WiFiEvent(WiFiEvent_t event) { DEBUG_PRINTLN(F("ETH Connected")); if (!apActive) { - WiFi.disconnect(true); + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) WiFi.disconnect(); // if using ESP-NOW just disconnect from current SSID + else WiFi.disconnect(true); // otherwise disable WiFi entirely } if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); @@ -308,6 +315,7 @@ void WiFiEvent(WiFiEvent_t event) // may be necessary to reconnect the WiFi when // ethernet disconnects, as a way to provide // alternative access to the device. + if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan forceReconnect = true; break; #endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 8a1607be1e..4d116f98a6 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -476,13 +476,24 @@ void WLED::setup() usermods.setup(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); - if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) - showWelcomePage = true; + DEBUG_PRINTLN(F("Initializing WiFi")); WiFi.persistent(false); WiFi.onEvent(WiFiEvent); - - WiFi.mode(WIFI_MODE_STA); // enable scanning - findWiFi(true); // start scanning for available WiFi-s +#if defined(ESP32) && ESP_IDF_VERSION_MAJOR==4 + WiFi.useStaticBuffers(true); // use preallocated buffers (for speed) +#endif +#ifdef ESP8266 + WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N); +#endif + if (isWiFiConfigured()) { + showWelcomePage = false; + WiFi.setAutoReconnect(true); // use automatic reconnect functionality + WiFi.mode(WIFI_MODE_STA); // enable scanning + findWiFi(true); // start scanning for available WiFi-s + } else { + showWelcomePage = true; + WiFi.mode(WIFI_MODE_AP); // WiFi is not configured so we'll most likely open an AP + } #ifdef WLED_ENABLE_ADALIGHT //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused @@ -601,7 +612,7 @@ void WLED::beginStrip() // stop AP (optionally also stop ESP-NOW) void WLED::stopAP(bool stopESPNow) { - DEBUG_PRINTLN(F("Stopping AP.")); + DEBUG_PRINTLN(F("WiFi: Stopping AP.")); #ifndef WLED_DISABLE_ESPNOW // we need to stop ESP-NOW as we are stopping AP if (stopESPNow && statusESPNow == ESP_NOW_STATE_ON) { @@ -633,17 +644,17 @@ void WLED::initAP(bool resetAP) WLED_SET_AP_SSID(); strcpy_P(apPass, PSTR(WLED_AP_PASS)); } - DEBUG_PRINT(F("Opening access point ")); + DEBUG_PRINT(F("WiFi: Opening access point ")); DEBUG_PRINTLN(apSSID); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); + WiFi.softAP(apSSID, apPass, apChannel, apHide); // WiFi mode can be either WIFI_MODE_AP or WIFI_MODE_APSTA #ifdef ARDUINO_ARCH_ESP32 WiFi.setTxPower(wifi_power_t(txPower)); #endif if (!apActive) // start captive portal if AP active { - DEBUG_PRINTLN(F("Init AP interfaces")); + DEBUG_PRINTLN(F("WiFi: Init AP interfaces")); server.begin(); if (udpPort > 0 && udpPort != ntpLocalPort) { udpConnected = notifierUdp.begin(udpPort); @@ -788,49 +799,30 @@ bool WLED::initEthernet() void WLED::initConnection() { DEBUG_PRINTLN(F("initConnection() called.")); - bool WiFiConfigured = WLED_WIFI_CONFIGURED; + bool WiFiConfigured = isWiFiConfigured(); -#ifdef WLED_ENABLE_WEBSOCKETS - ws.onEvent(wsEvent); -#endif - - WiFi.disconnect(true); // close old connections -#ifdef ESP8266 - WiFi.setPhyMode(force802_3g ? WIFI_PHY_MODE_11G : WIFI_PHY_MODE_11N); -#endif - - if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { - WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); - } else { - WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0)); - } + WiFi.disconnect(); // close old connections lastReconnectAttempt = millis(); if (!apActive) { - DEBUG_PRINTLN(F("Access point disabled (init).")); -#ifndef WLED_DISABLE_ESPNOW - if (statusESPNow == ESP_NOW_STATE_ON) { - DEBUG_PRINTLN(F("ESP-NOW stopping on STA start.")); - quickEspNow.stop(); - statusESPNow = ESP_NOW_STATE_UNINIT; - } -#endif - WiFi.softAPdisconnect(true); // force disconnect AP + //DEBUG_PRINTLN(F("WiFi: Access point disabled (init).")); + //stopAP(true); WiFi.mode(WIFI_MODE_STA); } if (WiFiConfigured) { - showWelcomePage = false; - - DEBUG_PRINT(F("Connecting to ")); - DEBUG_PRINT(multiWiFi[selectedWiFi].clientSSID); - DEBUG_PRINTLN(F("...")); + DEBUG_PRINTF_P(PSTR("WiFi: Connecting to %s...\r\n"), multiWiFi[selectedWiFi].clientSSID); + + if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { + WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); + } else { + WiFi.config(IPAddress((uint32_t)0), IPAddress((uint32_t)0), IPAddress((uint32_t)0)); + } // convert the "serverDescription" into a valid DNS hostname (alphanumeric) char hostname[25]; prepareHostname(hostname); - WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times #ifdef ARDUINO_ARCH_ESP32 WiFi.setTxPower(wifi_power_t(txPower)); @@ -840,6 +832,9 @@ void WLED::initConnection() wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); WiFi.hostname(hostname); #endif + WiFi.begin(multiWiFi[selectedWiFi].clientSSID, multiWiFi[selectedWiFi].clientPass); // no harm if called multiple times + // once WiFi is configured and begin() called, ESP will keep connecting to the specified SSID in the background + // until connection is established or new configuration is submitted or disconnect() is called } } @@ -848,6 +843,10 @@ void WLED::initInterfaces() { DEBUG_PRINTLN(F("Init STA interfaces")); +#ifdef WLED_ENABLE_WEBSOCKETS + ws.onEvent(wsEvent); +#endif + #ifndef WLED_DISABLE_ESPNOW if (statusESPNow == ESP_NOW_STATE_ON) { DEBUG_PRINTLN(F("ESP-NOW stopping on connect.")); @@ -928,9 +927,8 @@ void WLED::initInterfaces() void WLED::handleConnection() { - //static bool scanDone = true; unsigned long now = millis(); - const bool wifiConfigured = WLED_WIFI_CONFIGURED; + const bool wifiConfigured = isWiFiConfigured(); // ignore connection handling if WiFi is configured and scan still running // or within first 2s if WiFi is not configured or AP is always active @@ -938,70 +936,54 @@ void WLED::handleConnection() return; if (wifiConfigured && (lastReconnectAttempt == 0 || forceReconnect)) { - DEBUG_PRINTLN(F("Initial connect or forced reconnect.")); + // this is first attempt at connecting to SSID or we were forced to reconnect + DEBUG_PRINTLN(F("WiFi: Initial connect or forced reconnect.")); selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); interfacesInited = false; forceReconnect = false; - //wasConnected = false; // may not be appropriate when disconnecting Ethernet return; } if (!Network.isConnected()) { if (!wifiConfigured && !apActive) { - DEBUG_PRINTLN(F("WiFi not configured opening AP!")); - WiFi.mode(WIFI_MODE_AP); + DEBUG_PRINTLN(F("WiFi: Not configured, opening AP!")); initAP(); // instantly go to AP mode return; } if (!apActive && apBehavior == AP_BEHAVIOR_ALWAYS) { - DEBUG_PRINTLN(F("AP ALWAYS enabled.")); + DEBUG_PRINTLN(F("WiFi: AP ALWAYS enabled.")); WiFi.mode(WIFI_MODE_APSTA); // this will keep AP's channel in sync with STA channel initAP(); } -/* - if (interfacesInited) { - // we were connected but diconnect happened (we can't use events as disconnect is called too many times) - if (scanDone && multiWiFi.size() > 1) { - // if we have multiple SSIDs configured rescan WiFi for best match - DEBUG_PRINTLN(F("WiFi scan initiated on disconnect.")); - findWiFi(true); // reinit scan - scanDone = false; - return; // try to connect in next iteration - } - // 2nd iteration of the same event; choose best SSID and try to reconnect - DEBUG_PRINTLN(F("Disconnected!")); - selectedWiFi = findWiFi(); - initConnection(); - interfacesInited = false; - scanDone = true; - } -*/ //send improv failed 6 seconds after second init attempt (24 sec. after provisioning) - if (improvActive > 2 && now - lastReconnectAttempt > 6000) { + if (improvActive > 2 && now > lastReconnectAttempt + 6000) { sendImprovStateResponse(0x03, true); improvActive = 2; } - // WiFi is configured; try to reconnect if not connected after 20s or 300s if clients connected to AP + // WiFi is configured with multiple networks; try to reconnect if not connected after 15s or 300s if clients connected to AP // this will cycle through all configured SSIDs (findWiFi() sorted SSIDs by signal strength) - if (wifiConfigured && now - lastReconnectAttempt > ((apClients) ? 300000 : 20000)) { + // ESP usually connects to WiFi within 10s but we should give it a bit of time before attempting another network + if (wifiConfigured && multiWiFi.size() > 1 && now > lastReconnectAttempt + ((apClients) ? 300000 : 15000)) { #ifndef WLED_DISABLE_ESPNOW // wait for 3 skipped heartbeats if ESP-NOW sync is enabled if (now > 12000 + heartbeatESPNow) #endif { if (improvActive == 2) improvActive = 3; - DEBUG_PRINTLN(F("Last reconnect too old.")); + DEBUG_PRINTF_P(PSTR("WiFi: Last reconnect too old. %lu\r\n"), now); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); + // postpone searching for 2 min after last SSID from list and rely on auto reconnect + if (selectedWiFi + 1U == multiWiFi.size()) lastReconnectAttempt += 120000; return; } } // open AP if this is 12s after boot connect attempt or 12s after any disconnect (_NO_CONN) // !wasConnected means this is after boot and we haven't yet successfully connected to SSID - if (!apActive && now - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { + if (!apActive && now > lastReconnectAttempt + 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { - DEBUG_PRINTLN(F("Opening not connected AP.")); + DEBUG_PRINTLN(F("WiFi: Opening not connected AP.")); WiFi.mode(WIFI_MODE_AP); initAP(); // start temporary AP only within first 5min return; @@ -1011,8 +993,9 @@ void WLED::handleConnection() if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && apClients == 0) { // if AP was enabled more than 10min after boot or if client was connected more than 10min after boot do not disconnect AP mode if (now < 2*WLED_AP_TIMEOUT) { - DEBUG_PRINTLN(F("Temporary AP disabled.")); + DEBUG_PRINTLN(F("WiFi: Temporary AP disabled.")); stopAP(); + WiFi.mode(WIFI_MODE_STA); return; } } @@ -1022,7 +1005,7 @@ void WLED::handleConnection() bool isMasterDefined = masterESPNow[0] | masterESPNow[1] | masterESPNow[2] | masterESPNow[3] | masterESPNow[4] | masterESPNow[5]; if (!apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && enableESPNow && !sendNotificationsRT && isMasterDefined && now > WLED_AP_TIMEOUT) { // wait for 15s after starting to scan for WiFi before starting ESP-NOW (give ESP a chance to connect) - if (statusESPNow == ESP_NOW_STATE_UNINIT && wifiConfigured && now - lastReconnectAttempt > 15000) { + if (statusESPNow == ESP_NOW_STATE_UNINIT && wifiConfigured && now > lastReconnectAttempt + 15000) { quickEspNow.onDataSent(espNowSentCB); // see udp.cpp quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp DEBUG_PRINTF_P(PSTR("ESP-NOW initing in unconnected (no)AP mode (channel %d).\n"), (int)channelESPNow); @@ -1072,20 +1055,19 @@ void WLED::handleConnection() // shut down AP if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { stopAP(false); // do not stop ESP-NOW - DEBUG_PRINTLN(F("AP disabled (connected).")); + DEBUG_PRINTLN(F("WiFi: AP disabled (connected).")); } - } else { + } #ifndef WLED_DISABLE_ESPNOW - // already established connection, send ESP-NOW beacon every 2s if we are in sync mode (AKA master device) - // beacon will contain current/intended channel and local time (for loose synchronisation purposes) - if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON && sendNotificationsRT && now > 2000 + scanESPNow) { - EspNowBeacon buffer = {{'W','L','E','D'}, 0, (uint8_t)WiFi.channel(), toki.second(), {0}}; - quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), sizeof(buffer)); - scanESPNow = now; - DEBUG_PRINTF_P(PSTR("ESP-NOW beacon on channel %d.\n"), WiFi.channel()); - } -#endif + // send ESP-NOW beacon every 2s if we are in sync mode (AKA master device) regardless of STA or AP mode + // beacon will contain current/intended channel and local time (for loose synchronisation purposes) + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON && sendNotificationsRT && now > 2000 + scanESPNow) { + EspNowBeacon buffer = {{'W','L','E','D'}, 0, (uint8_t)WiFi.channel(), toki.second(), {0}}; + quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), sizeof(buffer)); + scanESPNow = now; + DEBUG_PRINTF_P(PSTR("ESP-NOW beacon on channel %d.\n"), WiFi.channel()); } +#endif } // If status LED pin is allocated for other uses, does nothing diff --git a/wled00/wled.h b/wled00/wled.h index fc13d809c6..aebc055e64 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -389,7 +389,6 @@ WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_8_5dBm); WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm); #endif #endif -#define WLED_WIFI_CONFIGURED (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) != 0) #ifdef WLED_USE_ETHERNET #ifdef WLED_ETH_DEFAULT // default ethernet board type if specified From 954fb96c17b38071d4be4c8eff78968eecbf11e8 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 17 Aug 2024 21:29:07 +0200 Subject: [PATCH 11/16] Fix for ethernet compile --- wled00/network.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wled00/network.cpp b/wled00/network.cpp index 1c6fb20108..26bfebb9a3 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -293,8 +293,12 @@ void WiFiEvent(WiFiEvent_t event) { DEBUG_PRINTLN(F("ETH Connected")); if (!apActive) { + #ifndef WLED_DISABLE_ESPNOW if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) WiFi.disconnect(); // if using ESP-NOW just disconnect from current SSID else WiFi.disconnect(true); // otherwise disable WiFi entirely + #else + WiFi.disconnect(true); // disable WiFi entirely + #endif } if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); From 98266eb12c15842d64adfd3c53bad5baa60a87cf Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 18 Aug 2024 10:44:16 +0200 Subject: [PATCH 12/16] Add some debug timings --- wled00/network.cpp | 16 ++++++++-------- wled00/wled.cpp | 25 ++++++++++++------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/wled00/network.cpp b/wled00/network.cpp index 26bfebb9a3..d969e5a346 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -178,15 +178,15 @@ int8_t findWiFi(bool doScan) { return 0; } - if (doScan) WiFi.scanDelete(); // restart scan + if (doScan) WiFi.scanDelete(); // restart scan - int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <3s with not very crowded air) + int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <6s with not very crowded air) if (status == WIFI_SCAN_FAILED) { - DEBUG_PRINTLN(F("WiFi scan started.")); + DEBUG_PRINTF_P(PSTR("WiFi: Scan started. @ %lu ms\r\n"), millis()); WiFi.scanNetworks(true); // start scanning in asynchronous mode - } else if (status >= 0) { // status contains number of found networks - DEBUG_PRINT(F("WiFi scan completed: ")); DEBUG_PRINTLN(status); + } else if (status >= 0) { // status contains number of found networks (including duplicate SSIDs with different BSSID) + DEBUG_PRINTF_P(PSTR("WiFi: Scan completed: %d @ %lu ms\r\n"), status, millis()); int rssi = -9999; unsigned selected = selectedWiFi; for (int o = 0; o < status; o++) { @@ -202,7 +202,7 @@ int8_t findWiFi(bool doScan) { break; } } - DEBUG_PRINT(F("Selected: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID); + DEBUG_PRINT(F("Selected SSID: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID); DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(rssi); DEBUG_PRINTLN(F("dB")); return selected; } @@ -226,7 +226,7 @@ void WiFiEvent(WiFiEvent_t event) DEBUG_PRINT(F("IP address: ")); DEBUG_PRINTLN(Network.localIP()); break; case WIFI_EVENT_STAMODE_CONNECTED: - DEBUG_PRINTLN(F("WiFi: Connected!")); + DEBUG_PRINTF_P(PSTR("WiFi: Connected! @ %lu ms\r\n"), millis()); wasConnected = true; break; case WIFI_EVENT_STAMODE_DISCONNECTED: @@ -268,7 +268,7 @@ void WiFiEvent(WiFiEvent_t event) DEBUG_PRINT(F("IP address: ")); DEBUG_PRINTLN(Network.localIP()); break; case SYSTEM_EVENT_STA_CONNECTED: - DEBUG_PRINTLN(F("WiFi: Connected!")); + DEBUG_PRINTF_P(PSTR("WiFi: Connected! @ %lu ms\r\n"), millis()); wasConnected = true; break; case SYSTEM_EVENT_STA_DISCONNECTED: diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 4d116f98a6..aeacf4faef 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -612,7 +612,7 @@ void WLED::beginStrip() // stop AP (optionally also stop ESP-NOW) void WLED::stopAP(bool stopESPNow) { - DEBUG_PRINTLN(F("WiFi: Stopping AP.")); + DEBUG_PRINTF_P(PSTR("WiFi: Stopping AP. @ %lu ms\r\n"), millis()); #ifndef WLED_DISABLE_ESPNOW // we need to stop ESP-NOW as we are stopping AP if (stopESPNow && statusESPNow == ESP_NOW_STATE_ON) { @@ -644,8 +644,7 @@ void WLED::initAP(bool resetAP) WLED_SET_AP_SSID(); strcpy_P(apPass, PSTR(WLED_AP_PASS)); } - DEBUG_PRINT(F("WiFi: Opening access point ")); - DEBUG_PRINTLN(apSSID); + DEBUG_PRINTF_P(PSTR("WiFi: Opening access point %s @ %lu ms\r\n"), apSSID, millis()); WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); // WiFi mode can be either WIFI_MODE_AP or WIFI_MODE_APSTA #ifdef ARDUINO_ARCH_ESP32 @@ -654,7 +653,7 @@ void WLED::initAP(bool resetAP) if (!apActive) // start captive portal if AP active { - DEBUG_PRINTLN(F("WiFi: Init AP interfaces")); + DEBUG_PRINTF_P(PSTR("WiFi: Init AP interfaces @ %lu ms\r\n"), millis()); server.begin(); if (udpPort > 0 && udpPort != ntpLocalPort) { udpConnected = notifierUdp.begin(udpPort); @@ -806,13 +805,13 @@ void WLED::initConnection() lastReconnectAttempt = millis(); if (!apActive) { - //DEBUG_PRINTLN(F("WiFi: Access point disabled (init).")); + //DEBUG_PRINTF_P(PSTR("WiFi: Access point disabled (init). @ %lu ms\r\n"), millis()); //stopAP(true); WiFi.mode(WIFI_MODE_STA); } if (WiFiConfigured) { - DEBUG_PRINTF_P(PSTR("WiFi: Connecting to %s...\r\n"), multiWiFi[selectedWiFi].clientSSID); + DEBUG_PRINTF_P(PSTR("WiFi: Connecting to %s... @ %lu ms\r\n"), multiWiFi[selectedWiFi].clientSSID, millis()); if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); @@ -937,7 +936,7 @@ void WLED::handleConnection() if (wifiConfigured && (lastReconnectAttempt == 0 || forceReconnect)) { // this is first attempt at connecting to SSID or we were forced to reconnect - DEBUG_PRINTLN(F("WiFi: Initial connect or forced reconnect.")); + DEBUG_PRINTF_P(PSTR("WiFi: Initial connect or forced reconnect. @ %lu ms\r\n"), millis()); selectedWiFi = findWiFi(); // find strongest WiFi initConnection(); interfacesInited = false; @@ -947,12 +946,12 @@ void WLED::handleConnection() if (!Network.isConnected()) { if (!wifiConfigured && !apActive) { - DEBUG_PRINTLN(F("WiFi: Not configured, opening AP!")); + DEBUG_PRINTF_P(PSTR("WiFi: Not configured, opening AP! @ %lu ms\r\n"), millis()); initAP(); // instantly go to AP mode return; } if (!apActive && apBehavior == AP_BEHAVIOR_ALWAYS) { - DEBUG_PRINTLN(F("WiFi: AP ALWAYS enabled.")); + DEBUG_PRINTF_P(PSTR("WiFi: AP ALWAYS enabled. @ %lu ms\r\n"), millis()); WiFi.mode(WIFI_MODE_APSTA); // this will keep AP's channel in sync with STA channel initAP(); } @@ -971,7 +970,7 @@ void WLED::handleConnection() #endif { if (improvActive == 2) improvActive = 3; - DEBUG_PRINTF_P(PSTR("WiFi: Last reconnect too old. %lu\r\n"), now); + DEBUG_PRINTF_P(PSTR("WiFi: Last reconnect too old @ %lu ms\r\n"), now); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list initConnection(); // postpone searching for 2 min after last SSID from list and rely on auto reconnect @@ -983,7 +982,7 @@ void WLED::handleConnection() // !wasConnected means this is after boot and we haven't yet successfully connected to SSID if (!apActive && now > lastReconnectAttempt + 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { - DEBUG_PRINTLN(F("WiFi: Opening not connected AP.")); + DEBUG_PRINTF_P(PSTR("WiFi: Opening not connected AP. @ %lu ms\r\n"), millis()); WiFi.mode(WIFI_MODE_AP); initAP(); // start temporary AP only within first 5min return; @@ -993,7 +992,7 @@ void WLED::handleConnection() if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && apClients == 0) { // if AP was enabled more than 10min after boot or if client was connected more than 10min after boot do not disconnect AP mode if (now < 2*WLED_AP_TIMEOUT) { - DEBUG_PRINTLN(F("WiFi: Temporary AP disabled.")); + DEBUG_PRINTF_P(PSTR("WiFi: Temporary AP disabled. @ %lu ms\r\n"), millis()); stopAP(); WiFi.mode(WIFI_MODE_STA); return; @@ -1055,7 +1054,7 @@ void WLED::handleConnection() // shut down AP if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { stopAP(false); // do not stop ESP-NOW - DEBUG_PRINTLN(F("WiFi: AP disabled (connected).")); + DEBUG_PRINTF_P(PSTR("WiFi: AP disabled (connected). @ %lu ms\r\n"), millis()); } } #ifndef WLED_DISABLE_ESPNOW From df4bd991c4cf645a763a0711390a2bc4ac0b2237 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 22 Aug 2024 17:09:55 +0200 Subject: [PATCH 13/16] Refactor network connectivity - part 1 --- wled00/network.cpp | 82 +++++++++++++++++++++------------------------- wled00/wled.cpp | 48 ++++++++++++++++++--------- 2 files changed, 69 insertions(+), 61 deletions(-) diff --git a/wled00/network.cpp b/wled00/network.cpp index d969e5a346..3b8ae46398 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -215,81 +215,74 @@ bool isWiFiConfigured() { return multiWiFi.size() > 1 || (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp_P(multiWiFi[0].clientSSID, PSTR(DEFAULT_CLIENT_SSID)) != 0); } +#if defined(ESP8266) + #define ARDUINO_EVENT_WIFI_AP_STADISCONNECTED WIFI_EVENT_SOFTAPMODE_STADISCONNECTED + #define ARDUINO_EVENT_WIFI_AP_STACONNECTED WIFI_EVENT_SOFTAPMODE_STACONNECTED + #define ARDUINO_EVENT_WIFI_STA_GOT_IP WIFI_EVENT_STAMODE_GOT_IP + #define ARDUINO_EVENT_WIFI_STA_CONNECTED WIFI_EVENT_STAMODE_CONNECTED + #define ARDUINO_EVENT_WIFI_STA_DISCONNECTED WIFI_EVENT_STAMODE_DISCONNECTED +#elif defined(ESP32) && !defined(ESP_ARDUINO_VERSION_MAJOR) //ESP_IDF_VERSION_MAJOR==3 + // not strictly IDF v3 but Arduino core related + #define ARDUINO_EVENT_WIFI_AP_STADISCONNECTED SYSTEM_EVENT_AP_STADISCONNECTED + #define ARDUINO_EVENT_WIFI_AP_STACONNECTED SYSTEM_EVENT_AP_STACONNECTED + #define ARDUINO_EVENT_WIFI_STA_GOT_IP SYSTEM_EVENT_STA_GOT_IP + #define ARDUINO_EVENT_WIFI_STA_CONNECTED SYSTEM_EVENT_STA_CONNECTED + #define ARDUINO_EVENT_WIFI_STA_DISCONNECTED SYSTEM_EVENT_STA_DISCONNECTED + #define ARDUINO_EVENT_WIFI_AP_START SYSTEM_EVENT_AP_START + #define ARDUINO_EVENT_WIFI_AP_STOP SYSTEM_EVENT_AP_STOP + #define ARDUINO_EVENT_ETH_START SYSTEM_EVENT_ETH_START + #define ARDUINO_EVENT_ETH_CONNECTED SYSTEM_EVENT_ETH_CONNECTED + #define ARDUINO_EVENT_ETH_DISCONNECTED SYSTEM_EVENT_ETH_DISCONNECTED +#endif //handle Ethernet connection event void WiFiEvent(WiFiEvent_t event) { switch (event) { -#ifdef ESP8266 - case WIFI_EVENT_STAMODE_GOT_IP: - DEBUG_PRINTLN(); - DEBUG_PRINT(F("IP address: ")); DEBUG_PRINTLN(Network.localIP()); - break; - case WIFI_EVENT_STAMODE_CONNECTED: - DEBUG_PRINTF_P(PSTR("WiFi: Connected! @ %lu ms\r\n"), millis()); - wasConnected = true; - break; - case WIFI_EVENT_STAMODE_DISCONNECTED: - // called quite often (when not connected to WiFi) - if (wasConnected) { - DEBUG_PRINTLN(F("WiFi: Disconnected")); - if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan - interfacesInited = false; - forceReconnect = true; - } - break; - case WIFI_EVENT_SOFTAPMODE_STACONNECTED: - // AP client connected - DEBUG_PRINTLN(F("WiFi: AP Client Connected")); - apClients++; - DEBUG_PRINTLN(apClients); - break; - case WIFI_EVENT_SOFTAPMODE_STADISCONNECTED: - // AP client disconnected - DEBUG_PRINTLN(F("WiFi: AP Client Disconnected")); - if (--apClients == 0 && isWiFiConfigured()) forceReconnect = true; // no clients reconnect WiFi if awailable - DEBUG_PRINTLN(apClients); - break; -#else - case SYSTEM_EVENT_AP_STADISCONNECTED: + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: // AP client disconnected DEBUG_PRINTLN(F("WiFi: AP Client Disconnected")); if (--apClients == 0 && isWiFiConfigured()) forceReconnect = true; // no clients reconnect WiFi if awailable DEBUG_PRINTLN(apClients); break; - case SYSTEM_EVENT_AP_STACONNECTED: + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: // AP client connected + // it looks like this doesn't work as expected DEBUG_PRINTLN(F("WiFi: AP Client Connected")); apClients++; DEBUG_PRINTLN(apClients); break; - case SYSTEM_EVENT_STA_GOT_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP: DEBUG_PRINTLN(); DEBUG_PRINT(F("IP address: ")); DEBUG_PRINTLN(Network.localIP()); break; - case SYSTEM_EVENT_STA_CONNECTED: + case ARDUINO_EVENT_WIFI_STA_CONNECTED: DEBUG_PRINTF_P(PSTR("WiFi: Connected! @ %lu ms\r\n"), millis()); wasConnected = true; break; - case SYSTEM_EVENT_STA_DISCONNECTED: - if (wasConnected) { + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + if (wasConnected && interfacesInited) { DEBUG_PRINTLN(F("WiFi: Disconnected")); - if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan + if (interfacesInited && WiFi.scanComplete() >= 0) { + findWiFi(true); // reinit WiFi scan + forceReconnect = true; + } interfacesInited = false; - forceReconnect = true; } break; - case SYSTEM_EVENT_AP_START: + #ifdef ESP32 + case ARDUINO_EVENT_WIFI_AP_START: DEBUG_PRINTLN(F("WiFi: AP Started")); break; - case SYSTEM_EVENT_AP_STOP: + case ARDUINO_EVENT_WIFI_AP_STOP: DEBUG_PRINTLN(F("WiFi: AP Stopped")); break; + #endif #if defined(WLED_USE_ETHERNET) - case SYSTEM_EVENT_ETH_START: + case ARDUINO_EVENT_ETH_START: DEBUG_PRINTLN(F("ETH Started")); break; - case SYSTEM_EVENT_ETH_CONNECTED: + case ARDUINO_EVENT_ETH_CONNECTED: { DEBUG_PRINTLN(F("ETH Connected")); if (!apActive) { @@ -312,7 +305,7 @@ void WiFiEvent(WiFiEvent_t event) showWelcomePage = false; break; } - case SYSTEM_EVENT_ETH_DISCONNECTED: + case ARDUINO_EVENT_ETH_DISCONNECTED: DEBUG_PRINTLN(F("ETH Disconnected")); // This doesn't really affect ethernet per se, // as it's only configured once. Rather, it @@ -323,7 +316,6 @@ void WiFiEvent(WiFiEvent_t event) forceReconnect = true; break; #endif -#endif default: break; } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index aeacf4faef..c90bdb32fb 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -653,6 +653,9 @@ void WLED::initAP(bool resetAP) if (!apActive) // start captive portal if AP active { + #ifdef WLED_ENABLE_WEBSOCKETS + ws.onEvent(wsEvent); + #endif DEBUG_PRINTF_P(PSTR("WiFi: Init AP interfaces @ %lu ms\r\n"), millis()); server.begin(); if (udpPort > 0 && udpPort != ntpLocalPort) { @@ -960,22 +963,34 @@ void WLED::handleConnection() sendImprovStateResponse(0x03, true); improvActive = 2; } +/* + #ifdef ESP8266 + int staClients = wifi_softap_get_station_num(); + #else + wifi_sta_list_t stationList; + esp_wifi_ap_get_sta_list(&stationList); + int staClients = stationList.num; + #endif +*/ // WiFi is configured with multiple networks; try to reconnect if not connected after 15s or 300s if clients connected to AP // this will cycle through all configured SSIDs (findWiFi() sorted SSIDs by signal strength) // ESP usually connects to WiFi within 10s but we should give it a bit of time before attempting another network - if (wifiConfigured && multiWiFi.size() > 1 && now > lastReconnectAttempt + ((apClients) ? 300000 : 15000)) { -#ifndef WLED_DISABLE_ESPNOW - // wait for 3 skipped heartbeats if ESP-NOW sync is enabled - if (now > 12000 + heartbeatESPNow) -#endif - { - if (improvActive == 2) improvActive = 3; - DEBUG_PRINTF_P(PSTR("WiFi: Last reconnect too old @ %lu ms\r\n"), now); - if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list - initConnection(); - // postpone searching for 2 min after last SSID from list and rely on auto reconnect - if (selectedWiFi + 1U == multiWiFi.size()) lastReconnectAttempt += 120000; - return; + // when a disconnect happens (see onEvent()) the WiFi scan is reinitiated and forced reconnect scheduled + if (wifiConfigured && multiWiFi.size() > 1 && now > lastReconnectAttempt + ((apActive) ? WLED_AP_TIMEOUT : 15000)) { + if ((!apActive || apClients == 0) + #ifndef WLED_DISABLE_ESPNOW + // wait for 3 skipped heartbeats if ESP-NOW sync is enabled + && now > 12000 + heartbeatESPNow + #endif + ) { + if (improvActive == 2) improvActive = 3; + DEBUG_PRINTF_P(PSTR("WiFi: Last reconnect too old @ %lu ms\r\n"), now); + if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list + stopAP(true); + initConnection(); + // postpone searching for 2 min after last SSID from list and rely on auto reconnect + if (selectedWiFi + 1U == multiWiFi.size()) lastReconnectAttempt += 120000; + return; } } // open AP if this is 12s after boot connect attempt or 12s after any disconnect (_NO_CONN) @@ -983,17 +998,18 @@ void WLED::handleConnection() if (!apActive && now > lastReconnectAttempt + 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { DEBUG_PRINTF_P(PSTR("WiFi: Opening not connected AP. @ %lu ms\r\n"), millis()); + WiFi.disconnect(true); // prevent connecting to WiFi while AP is open (may be reset above) WiFi.mode(WIFI_MODE_AP); initAP(); // start temporary AP only within first 5min return; } } // disconnect AP after 5min if no clients connected and mode TEMPORARY - if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT && apClients == 0) { + if (apActive && apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT) { // if AP was enabled more than 10min after boot or if client was connected more than 10min after boot do not disconnect AP mode - if (now < 2*WLED_AP_TIMEOUT) { + if (apClients == 0 && now < 2*WLED_AP_TIMEOUT) { DEBUG_PRINTF_P(PSTR("WiFi: Temporary AP disabled. @ %lu ms\r\n"), millis()); - stopAP(); + stopAP(true); WiFi.mode(WIFI_MODE_STA); return; } From 0bf1cafef41e018fdccc0b02a7bfe7826e80ab1a Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 27 Aug 2024 00:10:43 +0200 Subject: [PATCH 14/16] Some clarifications, ETH fix --- wled00/network.cpp | 29 +++++++++++++++++++---------- wled00/wled.cpp | 37 +++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/wled00/network.cpp b/wled00/network.cpp index 3b8ae46398..0472742bd8 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -3,7 +3,7 @@ #include "wled_ethernet.h" -#ifdef WLED_USE_ETHERNET +#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) // The following six pins are neither configurable nor // can they be re-assigned through IOMUX / GPIO matrix. // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface @@ -221,7 +221,7 @@ bool isWiFiConfigured() { #define ARDUINO_EVENT_WIFI_STA_GOT_IP WIFI_EVENT_STAMODE_GOT_IP #define ARDUINO_EVENT_WIFI_STA_CONNECTED WIFI_EVENT_STAMODE_CONNECTED #define ARDUINO_EVENT_WIFI_STA_DISCONNECTED WIFI_EVENT_STAMODE_DISCONNECTED -#elif defined(ESP32) && !defined(ESP_ARDUINO_VERSION_MAJOR) //ESP_IDF_VERSION_MAJOR==3 +#elif defined(ARDUINO_ARCH_ESP32) && !defined(ESP_ARDUINO_VERSION_MAJOR) //ESP_IDF_VERSION_MAJOR==3 // not strictly IDF v3 but Arduino core related #define ARDUINO_EVENT_WIFI_AP_STADISCONNECTED SYSTEM_EVENT_AP_STADISCONNECTED #define ARDUINO_EVENT_WIFI_AP_STACONNECTED SYSTEM_EVENT_AP_STACONNECTED @@ -270,15 +270,14 @@ void WiFiEvent(WiFiEvent_t event) interfacesInited = false; } break; - #ifdef ESP32 + #ifdef ARDUINO_ARCH_ESP32 case ARDUINO_EVENT_WIFI_AP_START: DEBUG_PRINTLN(F("WiFi: AP Started")); break; case ARDUINO_EVENT_WIFI_AP_STOP: DEBUG_PRINTLN(F("WiFi: AP Stopped")); break; - #endif - #if defined(WLED_USE_ETHERNET) + #if defined(WLED_USE_ETHERNET) case ARDUINO_EVENT_ETH_START: DEBUG_PRINTLN(F("ETH Started")); break; @@ -286,12 +285,21 @@ void WiFiEvent(WiFiEvent_t event) { DEBUG_PRINTLN(F("ETH Connected")); if (!apActive) { - #ifndef WLED_DISABLE_ESPNOW - if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) WiFi.disconnect(); // if using ESP-NOW just disconnect from current SSID - else WiFi.disconnect(true); // otherwise disable WiFi entirely - #else + #ifndef WLED_DISABLE_ESPNOW + if (useESPNowSync && statusESPNow == ESP_NOW_STATE_ON) { + DEBUG_PRINTLN(F("ESP-NOW restarting on ETH connected.")); + quickEspNow.stop(); + WiFi.disconnect(true); // disconnect from WiFi and disengage STA mode + WiFi.mode(WIFI_MODE_AP); + quickEspNow.onDataSent(espNowSentCB); // see udp.cpp + quickEspNow.onDataRcvd(espNowReceiveCB); // see udp.cpp + quickEspNow.setWiFiBandwidth(WIFI_IF_AP, WIFI_BW_HT20); // Only needed for ESP32 in case you need coexistence with ESP8266 in the same network + bool espNowOK = quickEspNow.begin(channelESPNow, WIFI_IF_AP); // Same channel must be used for both AP and ESP-NOW + statusESPNow = espNowOK ? ESP_NOW_STATE_ON : ESP_NOW_STATE_ERROR; + } else WiFi.disconnect(true); // otherwise disable WiFi entirely + #else WiFi.disconnect(true); // disable WiFi entirely - #endif + #endif } if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); @@ -315,6 +323,7 @@ void WiFiEvent(WiFiEvent_t event) if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan forceReconnect = true; break; + #endif #endif default: break; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c90bdb32fb..af2713a1bd 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -479,7 +479,7 @@ void WLED::setup() DEBUG_PRINTLN(F("Initializing WiFi")); WiFi.persistent(false); WiFi.onEvent(WiFiEvent); -#if defined(ESP32) && ESP_IDF_VERSION_MAJOR==4 +#if defined(ARDUINO_ARCH_ESP32) && ESP_IDF_VERSION_MAJOR==4 WiFi.useStaticBuffers(true); // use preallocated buffers (for speed) #endif #ifdef ESP8266 @@ -623,7 +623,7 @@ void WLED::stopAP(bool stopESPNow) { } #endif dnsServer.stop(); - WiFi.softAPdisconnect(true); + WiFi.softAPdisconnect(true); // disengage AP mode on stop apActive = false; } @@ -645,8 +645,8 @@ void WLED::initAP(bool resetAP) strcpy_P(apPass, PSTR(WLED_AP_PASS)); } DEBUG_PRINTF_P(PSTR("WiFi: Opening access point %s @ %lu ms\r\n"), apSSID, millis()); - WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); // WiFi mode can be either WIFI_MODE_AP or WIFI_MODE_APSTA + WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); // will also engage WIFI_MODE_AP + WiFi.softAP(apSSID, apPass, apChannel, apHide); // WiFi mode can be either WIFI_MODE_AP or WIFI_MODE_APSTA(==WIFI_MODE_STA & WIFI_MODE_AP) #ifdef ARDUINO_ARCH_ESP32 WiFi.setTxPower(wifi_power_t(txPower)); #endif @@ -801,21 +801,15 @@ bool WLED::initEthernet() void WLED::initConnection() { DEBUG_PRINTLN(F("initConnection() called.")); - bool WiFiConfigured = isWiFiConfigured(); - - WiFi.disconnect(); // close old connections + WiFi.disconnect(true); // close old connections (and also disengage STA mode) lastReconnectAttempt = millis(); - if (!apActive) { - //DEBUG_PRINTF_P(PSTR("WiFi: Access point disabled (init). @ %lu ms\r\n"), millis()); - //stopAP(true); - WiFi.mode(WIFI_MODE_STA); - } - - if (WiFiConfigured) { + if (isWiFiConfigured()) { DEBUG_PRINTF_P(PSTR("WiFi: Connecting to %s... @ %lu ms\r\n"), multiWiFi[selectedWiFi].clientSSID, millis()); + WiFi.mode(WIFI_MODE_STA); // engage explicit STA mode + // determine if using DHCP or static IP address, will also engage STA mode if not already if (multiWiFi[selectedWiFi].staticIP != 0U && multiWiFi[selectedWiFi].staticGW != 0U) { WiFi.config(multiWiFi[selectedWiFi].staticIP, multiWiFi[selectedWiFi].staticGW, multiWiFi[selectedWiFi].staticSN, dnsAddress); } else { @@ -941,7 +935,7 @@ void WLED::handleConnection() // this is first attempt at connecting to SSID or we were forced to reconnect DEBUG_PRINTF_P(PSTR("WiFi: Initial connect or forced reconnect. @ %lu ms\r\n"), millis()); selectedWiFi = findWiFi(); // find strongest WiFi - initConnection(); + initConnection(); // start connecting to preferred/configured WiFi interfacesInited = false; forceReconnect = false; return; @@ -950,13 +944,12 @@ void WLED::handleConnection() if (!Network.isConnected()) { if (!wifiConfigured && !apActive) { DEBUG_PRINTF_P(PSTR("WiFi: Not configured, opening AP! @ %lu ms\r\n"), millis()); - initAP(); // instantly go to AP mode + initAP(); // instantly go to AP mode (but will not disengage STA mode if engaged!) return; } if (!apActive && apBehavior == AP_BEHAVIOR_ALWAYS) { DEBUG_PRINTF_P(PSTR("WiFi: AP ALWAYS enabled. @ %lu ms\r\n"), millis()); - WiFi.mode(WIFI_MODE_APSTA); // this will keep AP's channel in sync with STA channel - initAP(); + initAP(); // if STA is engaged (it should be) this will keep AP's channel in sync with STA channel } //send improv failed 6 seconds after second init attempt (24 sec. after provisioning) if (improvActive > 2 && now > lastReconnectAttempt + 6000) { @@ -977,6 +970,9 @@ void WLED::handleConnection() // ESP usually connects to WiFi within 10s but we should give it a bit of time before attempting another network // when a disconnect happens (see onEvent()) the WiFi scan is reinitiated and forced reconnect scheduled if (wifiConfigured && multiWiFi.size() > 1 && now > lastReconnectAttempt + ((apActive) ? WLED_AP_TIMEOUT : 15000)) { + // this code is executed if ESP was unsuccessful in connecting to prefered WiFi. it is repeated every 15s + // to select different WiFi from the list (if only one WiFi is configure there will be 2 min between calls) if connects + // are not successful. if AP is still active when AP timeout us reached AP is suspended if ((!apActive || apClients == 0) #ifndef WLED_DISABLE_ESPNOW // wait for 3 skipped heartbeats if ESP-NOW sync is enabled @@ -986,7 +982,7 @@ void WLED::handleConnection() if (improvActive == 2) improvActive = 3; DEBUG_PRINTF_P(PSTR("WiFi: Last reconnect too old @ %lu ms\r\n"), now); if (++selectedWiFi >= multiWiFi.size()) selectedWiFi = 0; // we couldn't connect, try with another network from the list - stopAP(true); + stopAP(true); // stop ESP-NOW and disengage AP mode initConnection(); // postpone searching for 2 min after last SSID from list and rely on auto reconnect if (selectedWiFi + 1U == multiWiFi.size()) lastReconnectAttempt += 120000; @@ -994,11 +990,12 @@ void WLED::handleConnection() } } // open AP if this is 12s after boot connect attempt or 12s after any disconnect (_NO_CONN) + // i.e. initial connect was unsuccessful // !wasConnected means this is after boot and we haven't yet successfully connected to SSID if (!apActive && now > lastReconnectAttempt + 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) { if (!(apBehavior == AP_BEHAVIOR_TEMPORARY && now > WLED_AP_TIMEOUT)) { DEBUG_PRINTF_P(PSTR("WiFi: Opening not connected AP. @ %lu ms\r\n"), millis()); - WiFi.disconnect(true); // prevent connecting to WiFi while AP is open (may be reset above) + WiFi.disconnect(true); // disengage STA mode/prevent connecting to WiFi while AP is open (may be reset above) WiFi.mode(WIFI_MODE_AP); initAP(); // start temporary AP only within first 5min return; From 87e2b23753552e878e98ff8a15e7ead301f82743 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 11 Sep 2024 17:20:32 +0200 Subject: [PATCH 15/16] void --- wled00/fcn_declare.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 7947eeaa0b..ab6776e65e 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -270,7 +270,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs //network.cpp int getSignalQuality(int rssi); int8_t findWiFi(bool doScan = false); -bool isWiFiConfigured(void); +bool isWiFiConfigured(); void WiFiEvent(WiFiEvent_t event); //um_manager.cpp From 7c7d84809eda779ca4d588ca9f7f1f3130e7f04f Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 23 Sep 2024 20:06:28 +0200 Subject: [PATCH 16/16] Remove erroneous #endif --- wled00/udp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/wled00/udp.cpp b/wled00/udp.cpp index b3c8de4122..c68a3241ba 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -1002,7 +1002,6 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs channelESPNow = master->channel; // pre-configure if heartbeat is heard while scanning for WiFi return; } -#endif // handle WiZ Mote data if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {