From f527920019d59b1834336e18ded95746dc78ae8a Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 8 Jun 2026 11:54:09 +0200 Subject: [PATCH 1/3] [WiFi] Fix stack overflow crash when no credentials set --- .../net/wifi/ESPEasyWiFi_state_machine.cpp | 60 +++++++++++++------ 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp index 799b0b4242..f0550b2ebd 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp @@ -86,7 +86,7 @@ void ESPEasyWiFi_t::loop() setState(_state == WiFiState_e::STA_Connected_Setup ? WiFiState_e::STA_Connected : WiFiState_e::IdleWaiting); } else { - setState(WiFiState_e::WiFiOFF); + setState(WiFiState_e::WiFiOFF, 100); } } } @@ -252,7 +252,11 @@ void ESPEasyWiFi_t::loop() if (!WiFi_AP_Candidates.hasCandidateCredentials() && !Settings.DoNotStartAPfallback_ConnectFail()) { - setState(WiFiState_e::AP_only, WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT); + if (shouldStartAP_fallback()) { + setState(WiFiState_e::AP_Fallback, Settings.APfallback_minimal_on_time_sec() * 1000); + } else { + setState(WiFiState_e::AP_only, WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT); + } } else { setState(WiFiState_e::WiFiOFF, 100); } @@ -285,7 +289,7 @@ void ESPEasyWiFi_t::loop() setState(WiFiState_e::STA_Reconnecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); } else { wifi_STA_data->mark_connect_failed(); - setState(WiFiState_e::WiFiOFF); + setState(WiFiState_e::WiFiOFF, 100); } } @@ -347,13 +351,27 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { } //# endif // ifndef BUILD_NO_DEBUG - if ((_state == WiFiState_e::AP_only) || - (_state == WiFiState_e::AP_Fallback)) { - Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); -// setAP(false); + + const WiFiState_e oldState = _state; + + // Need to set the newState first as some of the functions below will call + // setState, causing a loop, or calling to change state multiple times. + + + if (timeout == 0) + { + _state_timeout.clear(); + + } else { + _state_timeout.setMillisFromNow(timeout); } - if (_state == WiFiState_e::STA_Connected) + _last_state_change.setNow(); + _state = newState; + + + + if (oldState == WiFiState_e::STA_Connected) { auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); @@ -366,22 +384,19 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { } } - if ((_state == WiFiState_e::STA_AP_Scanning) || - (_state == WiFiState_e::STA_Scanning)) + if ((oldState == WiFiState_e::STA_AP_Scanning) || + (oldState == WiFiState_e::STA_Scanning)) { WiFi_AP_Candidates.process_WiFiscan(); } - if (timeout == 0) - { - _state_timeout.clear(); - - } else { - _state_timeout.setMillisFromNow(timeout); + if ((oldState == WiFiState_e::AP_only) || + (oldState == WiFiState_e::AP_Fallback)) { + Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); +// setAP(false); } - _last_state_change.setNow(); - _state = newState; + switch (newState) @@ -393,6 +408,13 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { break; case WiFiState_e::WiFiOFF: // TODO TD-er: Must cancel all and turn off WiFi. + + if (doWifiIsAP(WiFi.getMode())) + Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); +/* + if (doWifiIsSTA(WiFi.getMode())) + Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_STA); +*/ setSTA_AP(false, false); break; case WiFiState_e::AP_only: @@ -450,7 +472,7 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { // WiFi.STA.setDefault(); # endif // ifdef ESP32 - if (_state == WiFiState_e::STA_Connected_Setup) { + if (oldState == WiFiState_e::STA_Connected_Setup) { Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); setMode(ESPEasyWiFi_mode_e::STA_only); } From 757d8a1668af0de450512992d01d86c327934309 Mon Sep 17 00:00:00 2001 From: TD-er Date: Wed, 10 Jun 2026 14:51:39 +0200 Subject: [PATCH 2/3] [WiFi] Simplify state machine for WiFi STA --- .../NWPluginData_static_runtime.cpp | 16 +- .../net/wifi/ESPEasyWiFi_STA_State.dotuml | 54 +++ .../ESPEasyWiFi_mode_state_machine.dotuml | 38 ++ .../net/wifi/ESPEasyWiFi_state_machine.cpp | 438 ++++++------------ .../net/wifi/ESPEasyWiFi_state_machine.h | 8 +- .../wifi/ESPEasyWiFi_state_machine_old.dotuml | 56 +++ src/ESPEasy/net/wifi/ESPEasyWifi.h | 4 +- src/ESPEasy/net/wifi/WiFi_STA_State.cpp | 31 ++ .../wifi/{WiFi_State.h => WiFi_STA_State.h} | 26 +- src/ESPEasy/net/wifi/WiFi_State.cpp | 33 -- src/src/Helpers/PeriodicalActions.cpp | 2 +- 11 files changed, 357 insertions(+), 349 deletions(-) create mode 100644 src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml create mode 100644 src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml create mode 100644 src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_old.dotuml create mode 100644 src/ESPEasy/net/wifi/WiFi_STA_State.cpp rename src/ESPEasy/net/wifi/{WiFi_State.h => WiFi_STA_State.h} (70%) delete mode 100644 src/ESPEasy/net/wifi/WiFi_State.cpp diff --git a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp index 4029709625..ebd8788223 100644 --- a/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp +++ b/src/ESPEasy/net/DataStructs/NWPluginData_static_runtime.cpp @@ -246,12 +246,16 @@ void NWPluginData_static_runtime::processEvents() if (connected_changed || establishConnect_changed) { - if (_connectedStats.isOn()) { - log_connected(); - - // _establishConnectStats.resetCount(); - } else if (_connectedStats.isOff() && !_establishConnectStats.isOn()) { - log_disconnected(); + if (_establishConnectStats.isSet()) { + if (_connectedStats.isOn()) { + log_connected(); + + // _establishConnectStats.resetCount(); + } else if (_establishConnectStats.isSet() && + _connectedStats.isOff() && + !_establishConnectStats.isOn()) { + log_disconnected(); + } } } diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml new file mode 100644 index 0000000000..d152ad1467 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml @@ -0,0 +1,54 @@ +StateDiagram [frame=true framecolor=steelblue label="State Diagram ESPEasy WiFi_STA_State_e"] { + state Disabled + state TimeOut + state Idle + state IdleWaiting + state STA_Scanning + state STA_AP_Scanning + state STA_Connecting + state STA_Reconnecting + state STA_Connected + + choice AlreadyConnected + choice AP_on_no_nw_found + choice KnownAPfound + choice KnownAPonDisconnect + + choice ExitSTA + + initialState->Disabled + + + Disabled -> IdleWaiting "Init STA" + Disabled <- ExitSTA "Exit STA" + IdleWaiting -> AlreadyConnected "Already\nConnected?" + AlreadyConnected -> STA_Connected "Connected" + AlreadyConnected -> Idle "Ready" + Idle -> KnownAPfound "Known\nNetwork\nfound?" + KnownAPfound -> AP_on_no_nw_found "Scan\nNeeded" + KnownAPfound -> STA_Connecting "No Scan\nNeeded" + AP_on_no_nw_found -> STA_AP_Scanning "AP" + AP_on_no_nw_found -> STA_Scanning "No AP" + + STA_Connecting -> STA_Connected + STA_Reconnecting -> STA_Connected + KnownAPonDisconnect -> STA_Reconnecting "No Scan\nNeeded" + KnownAPonDisconnect <- STA_Connected "Disconnect" + IdleWaiting <- KnownAPonDisconnect "Scan\nNeeded" + + + IdleWaiting <- STA_Scanning "Scan\nDone" + IdleWaiting <- STA_AP_Scanning "Scan\nDone" + + STA_AP_Scanning <- STA_AP_Scanning "Next\nChannel" + + TimeOut <- STA_Scanning "Scan\nFail" + TimeOut <- STA_AP_Scanning "Scan\nFail" + TimeOut <- STA_Connecting "Connect\nFail" + + TimeOut -> IdleWaiting + + IdleWaiting <- IdleWaiting "Wait for\nStartup Delay/\nSetup" + + TimeOut <- STA_Reconnecting "Reconnect\nFail" +} \ No newline at end of file diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml b/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml new file mode 100644 index 0000000000..3bb80004e4 --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml @@ -0,0 +1,38 @@ +StateDiagram [frame=true framecolor=steelblue label="State Diagram ESPEasyWiFi_mode_e"] { + state Off + state CredentialsUpdated + state STA_only + state STA_AP + state AP_only + state Setup + + choice AllowSetup + choice HasCredentials + choice HasCredentialsSetupAllowed + choice APalwaysOn + choice APalwaysOnNoCred + + + + initialState->Off + Off -> AllowSetup "Check\nAllowSetup" + AllowSetup -> HasCredentials "Setup not\nAllowed" + HasCredentials -> APalwaysOn "Has\nCredentials" + APalwaysOn -> STA_AP "AP" + APalwaysOn -> STA_only "No AP" + HasCredentials -> APalwaysOnNoCred "No\nCredentials" + APalwaysOnNoCred -> Off "No AP" + APalwaysOnNoCred -> AP_only "AP" + AllowSetup -> HasCredentialsSetupAllowed "Setup\nAllowed" + HasCredentialsSetupAllowed -> Setup "No\nCredentials" + HasCredentialsSetupAllowed -> APalwaysOn "Has\nCredentials" + + CredentialsUpdated <- Setup "Setup\nDone" + CredentialsUpdated -> AllowSetup + + STA_AP -> AP_only "No Known\nNetwork found" + + STA_AP <- STA_AP "Retry/\nReconnect" + STA_only <- STA_only "Retry/\nReconnect" + +} \ No newline at end of file diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp index f0550b2ebd..0724f7b462 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.cpp @@ -1,5 +1,5 @@ #include "../wifi/ESPEasyWiFi_state_machine.h" -#include "ESPEasy/net/wifi/WiFi_State.h" +#include "ESPEasy/net/wifi/WiFi_STA_State.h" #if FEATURE_WIFI @@ -27,6 +27,9 @@ namespace wifi { void ESPEasyWiFi_t::setup() { if (!Settings.getNetworkEnabled(NETWORK_INDEX_WIFI_STA)) { return; } + WiFi_AP_Candidates.clearCache(); + WiFi_AP_Candidates.load_knownCredentials(); + // TODO TD-er: Must maybe also call 'disable()' first? // TODO TD-er: Load settings @@ -43,28 +46,13 @@ void ESPEasyWiFi_t::setup() { void ESPEasyWiFi_t::enable() {} -void ESPEasyWiFi_t::disable() { setState(WiFiState_e::Disabled, 100); } +void ESPEasyWiFi_t::disable() { setState(WiFi_STA_State_e::Disabled, 100); } void ESPEasyWiFi_t::begin() { - if (connected()) setState(WiFiState_e::STA_Connected); - if (WiFi_AP_Candidates.hasCandidates()) { - setState(WiFiState_e::IdleWaiting, 100); + if (connected()) { + setState(WiFi_STA_State_e::STA_Connected, 0); } else { - if (WiFi_AP_Candidates.hasScanned()) { - // Has no candidates, but scan was performed. - if (shouldStartAP_fallback()) { - setState(WiFiState_e::AP_only, WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT); - } - } else { - if (WifiIsAP(WiFi.getMode())) { - // TODO TD-er: Must check if any client is connected. - // If not, then we can disable AP mode and switch to WiFiState_e::STA_Scanning - setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); - } else { - // setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); - setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); - } - } + setState(WiFi_STA_State_e::IdleWaiting, 100); } } @@ -75,245 +63,118 @@ void ESPEasyWiFi_t::loop() if (!wifi_STA_data) { return; } - if (_state != WiFiState_e::IdleWaiting) { + if ((_state != WiFi_STA_State_e::IdleWaiting) && + (_state != WiFi_STA_State_e::TimeOut)) { if (_callbackError || (_state_timeout.isSet() && _state_timeout.timeReached())) { // TODO TD-er: Must check what error was given??? _callbackError = false; - if (getMode() == ESPEasyWiFi_mode_e::Setup /* && _state == WiFiState_e::AP_Fallback */) { - - setState(_state == WiFiState_e::STA_Connected_Setup ? WiFiState_e::STA_Connected : WiFiState_e::IdleWaiting); - } else { - setState(WiFiState_e::WiFiOFF, 100); - } + // TODO TD-er: Must perhaps check what action was pending and act on it? + setState(WiFi_STA_State_e::TimeOut, 100); + return; } } switch (_state) { - case WiFiState_e::Disabled: + case WiFi_STA_State_e::Disabled: // Do nothing here, as the device is disabled. break; - case WiFiState_e::WiFiOFF: - begin(); - - // setState(WiFiState_e::IdleWaiting, 100); + case WiFi_STA_State_e::TimeOut: + // TODO TD-er: Not sure yet what else to do here. + setState(WiFi_STA_State_e::IdleWaiting, 100); break; - case WiFiState_e::AP_only: - case WiFiState_e::AP_Fallback: - - // TODO TD-er: Should also check for 'setup' mode, so we switch to connecting state instead of IdleWaiting - // For sure not just when there are candidates. - if (!ESPEasy::net::wifi::wifiAPmodeActivelyUsed()) { - if (WiFi_AP_Candidates.hasCandidates() || - _state_timeout.timeReached()) { - setState(WiFiState_e::IdleWaiting, 100); - } - } - break; - case WiFiState_e::IdleWaiting: - - if (connected()) { - if (getMode() == ESPEasyWiFi_mode_e::Setup) { - setState(WiFiState_e::STA_Connected_Setup, 60000); - } - else { - setState(WiFiState_e::STA_Connected, 100); - } - break; - } - - if (_state_timeout.timeReached() || (getSTA_connected_state() == STA_connected_state::Idle)) { - // This is where we decide what to do next: - // - Reconnect - // - Scan - // + case WiFi_STA_State_e::IdleWaiting: + // TODO TD-er: Must check whether we are allowed to continue - // Do we have candidate to connect to ? - if (WiFi_AP_Candidates.hasCandidates()) { - setState(WiFiState_e::STA_Connecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); - } else if ((WiFi_AP_Candidates.scanComplete() == 0) - || (WiFi_AP_Candidates.scanComplete() == -3)) { - if (WifiIsAP(WiFi.getMode())) { - // TODO TD-er: Must check if any client is connected. - // If not, then we can disable AP mode and switch to WiFiState_e::STA_Scanning - setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); - } else { - // setState(WiFiState_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); - setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); - } - - // Move up? - } else if (!WiFi_AP_Candidates.hasCandidateCredentials() || - !Settings.DoNotStartAPfallback_ConnectFail()) { - if (!WiFi_AP_Candidates.hasCandidateCredentials() - - // && !WiFiEventData.warnedNoValidWiFiSettings - ) - { - // Check for whether No Valid WiFi settings was already logged - static uint32_t lastTimeLoggedNoWiFiCredentials = 0; - if (timePassedSince(lastTimeLoggedNoWiFiCredentials) > 5000) { - addLog(LOG_LEVEL_ERROR, F("WIFI : No valid wifi settings")); - lastTimeLoggedNoWiFiCredentials = millis(); - } - - // WiFiEventData.warnedNoValidWiFiSettings = true; - } - wifi_STA_data->mark_connect_failed(); - - wifi_STA_data->_establishConnectStats.clear(); - - // WiFiEventData.wifiConnectAttemptNeeded = false; - // end move up?? - } - } + setState(WiFi_STA_State_e::Idle, 100); break; - case WiFiState_e::STA_Scanning: + case WiFi_STA_State_e::Idle: + // All done when setState was called + // Just a placeholder in the loop() + break; + case WiFi_STA_State_e::STA_Scanning: + case WiFi_STA_State_e::STA_AP_Scanning: { // -1 if scan not finished auto scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); + // Check if scanning is finished + // When scanning per channel, call for scanning next channel if (scanCompleteStatus >= 0) { WiFi_AP_Candidates.load_knownCredentials(); WiFi_AP_Candidates.process_WiFiscan(); # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, strformat( - F("WiFi : Scan done, found %d APs"), - WiFi_AP_Candidates.scanComplete())); -# endif // ifndef BUILD_NO_DEBUG - } else if (scanCompleteStatus == -2) { // WIFI_SCAN_FAILED - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); - - // WiFi.scanDelete(); - setState(WiFiState_e::WiFiOFF, 1000); - } - if (_state_timeout.timeReached() || (scanCompleteStatus >= 0)) { - // WiFi.scanDelete(); - - if (_state_timeout.timeReached()) { -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); -# endif + if (_state == WiFi_STA_State_e::STA_Scanning) { + addLog(LOG_LEVEL_INFO, strformat( + F("WiFi : Scan done, found %d APs"), + scanCompleteStatus)); + } else { + addLog(LOG_LEVEL_INFO, strformat( + F("WiFi : Scan channel %d done, found %d APs"), + _scan_channel, + scanCompleteStatus)); } +# endif // ifndef BUILD_NO_DEBUG - if (WiFi_AP_Candidates.hasCandidates()) { - setState(WiFiState_e::WiFiOFF, 100); - } else { - // FIXME TD-er: This might not be a responsibility of this state machine.... - if (shouldStartAP_fallback()) { - setState(WiFiState_e::AP_Fallback, Settings.APfallback_minimal_on_time_sec() * 1000); + if (_state == WiFi_STA_State_e::STA_AP_Scanning) { + ++_scan_channel; - // TODO TD-er: Must keep track of whether the user has forced AP to be autostarted. - } else { - setState(WiFiState_e::WiFiOFF, 1000); + if (_scan_channel > 14) { + _scan_channel = 0; + setState(WiFi_STA_State_e::IdleWaiting, 100); } + else { + setState(WiFi_STA_State_e::STA_AP_Scanning, 500); + } + } else { + setState(WiFi_STA_State_e::IdleWaiting, 100); } - } - break; - } - case WiFiState_e::STA_AP_Scanning: - { - // -1 if scan not finished - auto scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - - if (scanCompleteStatus >= 0) { - WiFi_AP_Candidates.load_knownCredentials(); - WiFi_AP_Candidates.process_WiFiscan(); -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, strformat( - F("WiFi : Scan channel %d done, found %d APs"), - _scan_channel, - WiFi_AP_Candidates.scanComplete())); -# endif // ifndef BUILD_NO_DEBUG } else if (scanCompleteStatus == -2) { // WIFI_SCAN_FAILED addLog(LOG_LEVEL_ERROR, F("WiFi : Scan failed")); // WiFi.scanDelete(); - setState(WiFiState_e::WiFiOFF, 1000); - } - - if (_state_timeout.timeReached() || (scanCompleteStatus >= 0)) { - // WiFi.scanDelete(); - - if (_state_timeout.timeReached()) { -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("WiFi : Scan Running Timeout")); -# endif - } - ++_scan_channel; - - if (_scan_channel > 14) { - _scan_channel = 0; - - if (!WiFi_AP_Candidates.hasCandidateCredentials() && - !Settings.DoNotStartAPfallback_ConnectFail()) { - if (shouldStartAP_fallback()) { - setState(WiFiState_e::AP_Fallback, Settings.APfallback_minimal_on_time_sec() * 1000); - } else { - setState(WiFiState_e::AP_only, WIFI_STATE_MACHINE_AP_ONLY_TIMEOUT); - } - } else { - setState(WiFiState_e::WiFiOFF, 100); - } - } - else { - setState(WiFiState_e::STA_AP_Scanning, 500); - } + setState(WiFi_STA_State_e::TimeOut, 1000); } break; - - // Check if scanning is finished - // When scanning per channel, call for scanning next channel } - case WiFiState_e::STA_Connecting: - case WiFiState_e::STA_Reconnecting: + case WiFi_STA_State_e::STA_Connecting: + case WiFi_STA_State_e::STA_Reconnecting: // Check if (re)connecting has finished { const STA_connected_state sta_connected_state = getSTA_connected_state(); if (sta_connected_state == STA_connected_state::Connected) { - if (getMode() == ESPEasyWiFi_mode_e::Setup) { - setState(WiFiState_e::STA_Connected_Setup, 60000); - } - else { - setState(WiFiState_e::STA_Connected, 100); - } - } else if (_state_timeout.timeReached()) { - if (_state == WiFiState_e::STA_Connecting) { - setState(WiFiState_e::STA_Reconnecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); - } else { - wifi_STA_data->mark_connect_failed(); - setState(WiFiState_e::WiFiOFF, 100); - } + setState(WiFi_STA_State_e::STA_Connected, 0); } break; } - case WiFiState_e::STA_Connected: + case WiFi_STA_State_e::STA_Connected: // Check if still connected if (getSTA_connected_state() != STA_connected_state::Connected) { - // setState(WiFiState_e::WiFiOFF); + + auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + + if (wifi_STA_data) + wifi_STA_data->mark_disconnected(); + + if (WiFi.status() == WL_CONNECTED) { + WiFi.disconnect(true); + } + + if (WiFi_AP_Candidates.hasCandidates()) { - setState(WiFiState_e::STA_Connecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + setState(WiFi_STA_State_e::STA_Reconnecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); } else { - setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); + setState(WiFi_STA_State_e::IdleWaiting, 100); } - - /* - if (Settings.UseRules) - { - eventQueue.addDeDup(F("WiFi#Disconnected")); - } - statusLED(false); - */ - } else { // Else mark last timestamp seen as connected _last_seen_connected.setNow(); @@ -338,23 +199,12 @@ bool ESPEasyWiFi_t::connected() const void ESPEasyWiFi_t::disconnect() { doWiFiDisconnect(); } -void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { +void ESPEasyWiFi_t::setState(WiFi_STA_State_e newState, uint32_t timeout) { if (newState == _state) { return; } -//# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog( - LOG_LEVEL_INFO, - concat(F("WiFi : Set state from: "), toString(_state)) + - concat(F(" to: "), toString(newState)) + - concat(F(" timeout: "), timeout)); - } -//# endif // ifndef BUILD_NO_DEBUG + const WiFi_STA_State_e oldState = _state; - const WiFiState_e oldState = _state; - - // Need to set the newState first as some of the functions below will call + // Need to set the newState first as some of the functions below will call // setState, causing a loop, or calling to change state multiple times. @@ -369,73 +219,100 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { _last_state_change.setNow(); _state = newState; + // # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog( + LOG_LEVEL_INFO, + concat(F("WiFi : Set state from: "), toString(oldState)) + + concat(F(" to: "), toString(newState)) + + concat(F(" timeout: "), timeout)); + } - if (oldState == WiFiState_e::STA_Connected) - { - auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + // # endif // ifndef BUILD_NO_DEBUG - if (wifi_STA_data) { - wifi_STA_data->mark_disconnected(); - if (WiFi.status() == WL_CONNECTED) { - WiFi.disconnect(true); - } - } - } + if (oldState == WiFi_STA_State_e::STA_Connected) + {} - if ((oldState == WiFiState_e::STA_AP_Scanning) || - (oldState == WiFiState_e::STA_Scanning)) + if ((oldState == WiFi_STA_State_e::STA_AP_Scanning) || + (oldState == WiFi_STA_State_e::STA_Scanning)) { + WiFi_AP_Candidates.load_knownCredentials(); WiFi_AP_Candidates.process_WiFiscan(); - } - if ((oldState == WiFiState_e::AP_only) || - (oldState == WiFiState_e::AP_Fallback)) { - Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); -// setAP(false); } - - - switch (newState) { - case WiFiState_e::Disabled: + case WiFi_STA_State_e::Disabled: // Do nothing here, as the device is disabled. + + // TODO TD-er: Maybe call scheduler? + // if (doWifiIsSTA(WiFi.getMode())) + // Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_STA); WifiDisconnect(); setSTA(false); break; - case WiFiState_e::WiFiOFF: - // TODO TD-er: Must cancel all and turn off WiFi. - - if (doWifiIsAP(WiFi.getMode())) - Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); -/* - if (doWifiIsSTA(WiFi.getMode())) - Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_STA); -*/ - setSTA_AP(false, false); - break; - case WiFiState_e::AP_only: - case WiFiState_e::AP_Fallback: - Scheduler.setNetworkInitTimer(0, NETWORK_INDEX_WIFI_AP); + + case WiFi_STA_State_e::TimeOut: + { + auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); + + if (oldState == WiFi_STA_State_e::STA_Connecting) { + if (wifi_STA_data) { + wifi_STA_data->mark_connect_failed(); + } + setState(WiFi_STA_State_e::STA_Reconnecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + } else if (oldState == WiFi_STA_State_e::STA_Reconnecting) { + if (wifi_STA_data) { + wifi_STA_data->mark_connect_failed(); + } + } + + + setState(WiFi_STA_State_e::IdleWaiting, 100); break; - case WiFiState_e::IdleWaiting: - // Do nothing here as we're waiting till the timeout is over + } + case WiFi_STA_State_e::IdleWaiting: + if (connected()) { + setState(WiFi_STA_State_e::STA_Connected); + } + // Nothing else to do here as we're waiting till the timeout is over break; - case WiFiState_e::STA_AP_Scanning: + case WiFi_STA_State_e::Idle: + if (!doWifiIsSTA(WiFi.getMode())) + Scheduler.setNetworkInitTimer(0, NETWORK_INDEX_WIFI_STA); + + // This is where we decide what to do next: + // - Reconnect + // - Scan + // + // Do we have candidate to connect to ? + if (WiFi_AP_Candidates.hasCandidates()) { + setState(WiFi_STA_State_e::STA_Connecting, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); + } else { + // No known candidates, so need to scan first. + if (WifiIsAP(WiFi.getMode())) { + // TODO TD-er: Must check if any client is connected. + // If not, then we can disable AP mode and switch to WiFi_STA_State_e::STA_Scanning + setState(WiFi_STA_State_e::STA_AP_Scanning, WIFI_STATE_MACHINE_STA_AP_SCANNING_TIMEOUT); + } else { + setState(WiFi_STA_State_e::STA_Scanning, WIFI_STATE_MACHINE_STA_SCANNING_TIMEOUT); + } + } + break; + case WiFi_STA_State_e::STA_AP_Scanning: // Start scanning per channel if (_scan_channel == 0) { _scan_channel = 1; } - - // break; - case WiFiState_e::STA_Scanning: + // fall through + case WiFi_STA_State_e::STA_Scanning: // Start scanning startScanning(); break; - case WiFiState_e::STA_Connecting: - case WiFiState_e::STA_Reconnecting: + case WiFi_STA_State_e::STA_Connecting: + case WiFi_STA_State_e::STA_Reconnecting: // Start connecting ++_connect_attempt; @@ -443,28 +320,12 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { if (!connectSTA()) { // TODO TD-er: Must keep track of failed attempts and start AP when either no credentials present or nr. of attempts failed > some // threshold. - if (!WiFi_AP_Candidates.hasCandidates()) { - setState(WiFiState_e::STA_Scanning, WIFI_STATE_MACHINE_STA_CONNECTING_TIMEOUT); - } else { - setState(WiFiState_e::IdleWaiting, 100); - } - } - break; - - case WiFiState_e::STA_Connected_Setup: - { - Scheduler.setNetworkInitTimer(0, NETWORK_INDEX_WIFI_AP); - _last_seen_connected.setNow(); - auto wifi_STA_data = getWiFi_STA_NWPluginData_static_runtime(); - - if (wifi_STA_data) { - wifi_STA_data->mark_connected(); + addLog(LOG_LEVEL_ERROR, F("WiFi : Connect STA failed")); + setState(WiFi_STA_State_e::TimeOut, 100); } - break; - } - case WiFiState_e::STA_Connected: + case WiFi_STA_State_e::STA_Connected: { # ifdef ESP32 @@ -472,10 +333,6 @@ void ESPEasyWiFi_t::setState(WiFiState_e newState, uint32_t timeout) { // WiFi.STA.setDefault(); # endif // ifdef ESP32 - if (oldState == WiFiState_e::STA_Connected_Setup) { - Scheduler.setNetworkExitTimer(0, NETWORK_INDEX_WIFI_AP); - setMode(ESPEasyWiFi_mode_e::STA_only); - } _connect_attempt = 0; _last_seen_connected.setNow(); _state_timeout.clear(); @@ -509,7 +366,7 @@ void ESPEasyWiFi_t::checkConnectProgress() {} void ESPEasyWiFi_t::startScanning() { - _state = _scan_channel == 0 ? WiFiState_e::STA_Scanning : WiFiState_e::STA_AP_Scanning; + _state = _scan_channel == 0 ? WiFi_STA_State_e::STA_Scanning : WiFi_STA_State_e::STA_AP_Scanning; setSTA(true); WifiScan(true, _scan_channel); _last_state_change.setNow(); @@ -690,8 +547,13 @@ bool ESPEasyWiFi_t::shouldStartAP_fallback() const bool ESPEasyWiFi_t::shouldRedirectTo_setup() const { - if (!Settings.ApCaptivePortal()) return false; - if (Settings.StartAPfallback_NoCredentials() && !SecuritySettings.hasWiFiCredentials()) { + if (!Settings.ApCaptivePortal()) { return false; } + + if (Settings.StartAPfallback_NoCredentials() + && !WiFi_AP_Candidates.hasCandidateCredentials() + + // && !SecuritySettings.hasWiFiCredentials() + ) { return true; } diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h index 6e72577e3d..4070269933 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine.h @@ -7,7 +7,7 @@ # include "../../../src/Helpers/LongTermTimer.h" # include "../wifi/WiFi_STA_connected_state.h" -# include "../wifi/WiFi_State.h" +# include "../wifi/WiFi_STA_State.h" # include @@ -63,7 +63,7 @@ class ESPEasyWiFi_t // Process the state machine for managing WiFi connection void loop(); - WiFiState_e getState() const + WiFi_STA_State_e getState() const { return _state; } @@ -80,7 +80,7 @@ class ESPEasyWiFi_t private: - void setState(WiFiState_e newState, + void setState(WiFi_STA_State_e newState, uint32_t timeout = 0); // Handle timeouts + start of AP mode @@ -110,7 +110,7 @@ class ESPEasyWiFi_t // String _last_ssid; // MAC_address _last_bssid; // uint8_t _last_channel = 0; - WiFiState_e _state = WiFiState_e::Disabled; + WiFi_STA_State_e _state = WiFi_STA_State_e::Disabled; ESPEasyWiFi_mode_e _mode = ESPEasyWiFi_mode_e::Off; diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_old.dotuml b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_old.dotuml new file mode 100644 index 0000000000..0c10d35c9f --- /dev/null +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_state_machine_old.dotuml @@ -0,0 +1,56 @@ +StateDiagram [frame=true framecolor=steelblue label="State Diagram ESPEasy WiFi_STA_State_e"] { + state Disabled + state TimeOut + state begin + state WiFiOFF + state AP_only + state AP_Fallback + state IdleWaiting + state STA_Scanning + state STA_AP_Scanning + state STA_Connecting + state STA_Reconnecting + state STA_Connected + state STA_Connected_Setup + choice bConn + choice bCand + choice bScan + choice bAPmode + choice timeoutSetupMode + initialState->WiFiOFF + TimeOut -> timeoutSetupMode "Setup" + timeoutSetupMode -> IdleWaiting "!connected" + timeoutSetupMode -> WiFiOFF "!Setup" + WiFiOFF -> begin + begin -> bConn + bConn -> STA_Connected "connected" + bConn -> bCand "!connected" + bCand -> IdleWaiting "hasCandidates" + bCand -> bScan "!hasCandidates" + bScan -> AP_only "scanned" + bScan -> bAPmode "!scanned" + bAPmode -> STA_AP_Scanning "APmode" + bAPmode -> STA_Scanning "!APmode" + AP_only -> IdleWaiting "!APactiveUsed & (hasCandidates || timeout)" + AP_Fallback -> IdleWaiting "!APactiveUsed & (hasCandidates || timeout)" + IdleWaiting -> STA_Connected_Setup "connected() && mode==setup" + IdleWaiting -> STA_Connected "connected() && mode!=setup" + IdleWaiting -> STA_Connecting "timeout & hasCandidates" + IdleWaiting -> STA_AP_Scanning "timeout & !hasCandidates & scanfail & APmode" + IdleWaiting -> STA_Scanning "timeout & !hasCandidates & scanfail & !APmode" + STA_Scanning -> WiFiOFF "scanfail || scansuccess || !startAP_fallback" + STA_Scanning -> AP_Fallback "scanfail & startAP_fallback" + STA_AP_Scanning -> WiFiOFF "scanfail || scansuccess || !startAP_fallback" + STA_AP_Scanning -> AP_Fallback "scanfail & startAP_fallback" + STA_AP_Scanning -> AP_only "scanfail & !startAP_fallback" + STA_AP_Scanning -> STA_AP_Scanning "scan next channel" + STA_Connecting -> STA_Connected_Setup "connected & SetupMode" + STA_Connecting -> STA_Connected "connected & !SetupMode" + STA_Connecting -> STA_Reconnecting "!connected & timeout" + STA_Reconnecting -> IdleWaiting "!connected & timeout" + STA_Reconnecting -> STA_Connected_Setup "connected & SetupMode" + STA_Reconnecting -> STA_Connected "connected & !SetupMode" + STA_Connected -> STA_Connecting "!connected & hasCandidates" + STA_Connected -> STA_Scanning "!connected & !hasCandidates" + STA_Connected -> STA_Connected "connected + setTXpwr" +} \ No newline at end of file diff --git a/src/ESPEasy/net/wifi/ESPEasyWifi.h b/src/ESPEasy/net/wifi/ESPEasyWifi.h index 3098e6fd88..9f06126e95 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWifi.h +++ b/src/ESPEasy/net/wifi/ESPEasyWifi.h @@ -21,7 +21,9 @@ # define SOFTAP_STATION_COUNT WiFi.AP.stationCount() # endif # ifdef ESP8266 -# define SOFTAP_STATION_COUNT WiFi.softAPgetStationNum() +//# define SOFTAP_STATION_COUNT WiFi.softAPgetStationNum() + +# define SOFTAP_STATION_COUNT wifi_softap_get_station_num() # endif diff --git a/src/ESPEasy/net/wifi/WiFi_STA_State.cpp b/src/ESPEasy/net/wifi/WiFi_STA_State.cpp new file mode 100644 index 0000000000..cf6fbd4cdd --- /dev/null +++ b/src/ESPEasy/net/wifi/WiFi_STA_State.cpp @@ -0,0 +1,31 @@ +#include "../wifi/WiFi_STA_State.h" + +#if FEATURE_WIFI + + +namespace ESPEasy { +namespace net { +namespace wifi { + +const __FlashStringHelper* toString(WiFi_STA_State_e state) +{ + switch (state) + { + case ESPEasy::net::wifi::WiFi_STA_State_e::Disabled: return F("Disabled"); + case ESPEasy::net::wifi::WiFi_STA_State_e::TimeOut: return F("TimeOut"); + case ESPEasy::net::wifi::WiFi_STA_State_e::Idle: return F("Idle"); + case ESPEasy::net::wifi::WiFi_STA_State_e::IdleWaiting: return F("IdleWaiting"); + case ESPEasy::net::wifi::WiFi_STA_State_e::STA_Scanning: return F("STA_Scanning"); + case ESPEasy::net::wifi::WiFi_STA_State_e::STA_AP_Scanning: return F("STA_AP_Scanning"); + case ESPEasy::net::wifi::WiFi_STA_State_e::STA_Connecting: return F("STA_Connecting"); + case ESPEasy::net::wifi::WiFi_STA_State_e::STA_Reconnecting: return F("STA_Reconnecting"); + case ESPEasy::net::wifi::WiFi_STA_State_e::STA_Connected: return F("STA_Connected"); + } + return F(""); +} + +} // namespace wifi +} // namespace net +} // namespace ESPEasy + +#endif // if FEATURE_WIFI diff --git a/src/ESPEasy/net/wifi/WiFi_State.h b/src/ESPEasy/net/wifi/WiFi_STA_State.h similarity index 70% rename from src/ESPEasy/net/wifi/WiFi_State.h rename to src/ESPEasy/net/wifi/WiFi_STA_State.h index 98e8f039bc..86ef57653c 100644 --- a/src/ESPEasy/net/wifi/WiFi_State.h +++ b/src/ESPEasy/net/wifi/WiFi_STA_State.h @@ -52,23 +52,20 @@ namespace wifi { */ -enum class WiFiState_e +enum class WiFi_STA_State_e { // WiFi radio is off and no new attempt should be made (e.g. low power mode or Ethernet active) Disabled, - // WiFi radio off, to continue, everything needs to be (re)initialized - WiFiOFF, + // Error state, some action failed + TimeOut, - // Only running in AP mode - // Typically this is only used when STA is off and AP Auto Start is checked - // TODO TD-er: Must implement this. - AP_only, + // State from where we decide to start scanning or connecting + Idle, - // Fallback mode which is started when connecting to AP was not possible - AP_Fallback, - - // WiFi was in some kind of error state or needs waiting period + // Not allowed yet to continue (re)connecting. + // For example when the interface is a fallback interface and not yet allowed to start + // Or when in setup mode and no need to connect. IdleWaiting, // STA mode + scanning @@ -86,13 +83,10 @@ enum class WiFiState_e STA_Reconnecting, // Connected to an AP - STA_Connected, - - STA_Connected_Setup - + STA_Connected }; -const __FlashStringHelper* toString(WiFiState_e state); +const __FlashStringHelper* toString(WiFi_STA_State_e state); } // namespace wifi diff --git a/src/ESPEasy/net/wifi/WiFi_State.cpp b/src/ESPEasy/net/wifi/WiFi_State.cpp deleted file mode 100644 index 132545f31f..0000000000 --- a/src/ESPEasy/net/wifi/WiFi_State.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "../wifi/WiFi_State.h" - -#if FEATURE_WIFI - - -namespace ESPEasy { -namespace net { -namespace wifi { - -const __FlashStringHelper* toString(WiFiState_e state) -{ - switch (state) - { - case ESPEasy::net::wifi::WiFiState_e::Disabled: return F("Disabled"); - case ESPEasy::net::wifi::WiFiState_e::WiFiOFF: return F("OFF"); - case ESPEasy::net::wifi::WiFiState_e::AP_only: return F("AP_only"); - case ESPEasy::net::wifi::WiFiState_e::AP_Fallback: return F("AP_Fallback"); - case ESPEasy::net::wifi::WiFiState_e::IdleWaiting: return F("IdleWaiting"); - case ESPEasy::net::wifi::WiFiState_e::STA_Scanning: return F("STA_Scanning"); - case ESPEasy::net::wifi::WiFiState_e::STA_AP_Scanning: return F("STA_AP_Scanning"); - case ESPEasy::net::wifi::WiFiState_e::STA_Connecting: return F("STA_Connecting"); - case ESPEasy::net::wifi::WiFiState_e::STA_Reconnecting: return F("STA_Reconnecting"); - case ESPEasy::net::wifi::WiFiState_e::STA_Connected: return F("STA_Connected"); - case ESPEasy::net::wifi::WiFiState_e::STA_Connected_Setup: return F("STA_Connected_Setup"); - } - return F(""); -} - -} // namespace wifi -} // namespace net -} // namespace ESPEasy - -#endif // if FEATURE_WIFI diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 97447c08a3..c2c829222e 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -38,7 +38,7 @@ #include "../../ESPEasy/net/Globals/ESPEasyWiFiEvent.h" #include "../../ESPEasy/net/Globals/NetworkState.h" #include "../../ESPEasy/net/Globals/NWPlugins.h" -#include "../../ESPEasy/net/wifi/WiFi_State.h" +#include "../../ESPEasy/net/wifi/WiFi_STA_State.h" #ifdef USES_C015 From 8549f39667147c0daa585e487d3082abc0fb9e1e Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 11 Jun 2026 16:01:48 +0200 Subject: [PATCH 3/3] [WiFi] Update WiFi state diagrams --- .../net/wifi/ESPEasyWiFi_STA_State.dotuml | 14 ++--- .../ESPEasyWiFi_mode_state_machine.dotuml | 57 ++++++++++++------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml index d152ad1467..034d39b516 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_STA_State.dotuml @@ -1,13 +1,13 @@ StateDiagram [frame=true framecolor=steelblue label="State Diagram ESPEasy WiFi_STA_State_e"] { - state Disabled - state TimeOut + state Disabled [fillcolor=lightblue] + state TimeOut as "TimeOut/\nError" [fillcolor=orangered] state Idle state IdleWaiting - state STA_Scanning - state STA_AP_Scanning - state STA_Connecting - state STA_Reconnecting - state STA_Connected + state STA_Scanning as "STA\nScanning" + state STA_AP_Scanning as "STA+AP\nScanning" + state STA_Connecting as "STA\nConnecting" + state STA_Reconnecting as "STA\nReconnecting" + state STA_Connected as "STA\nConnected" [fillcolor=palegreen] choice AlreadyConnected choice AP_on_no_nw_found diff --git a/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml b/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml index 3bb80004e4..49b84923a0 100644 --- a/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml +++ b/src/ESPEasy/net/wifi/ESPEasyWiFi_mode_state_machine.dotuml @@ -1,38 +1,55 @@ StateDiagram [frame=true framecolor=steelblue label="State Diagram ESPEasyWiFi_mode_e"] { - state Off - state CredentialsUpdated - state STA_only - state STA_AP - state AP_only - state Setup - - choice AllowSetup + choice NeedSetup choice HasCredentials - choice HasCredentialsSetupAllowed + choice HasCredentialsSetupRequired choice APalwaysOn choice APalwaysOnNoCred - + state STA_only + state STA_AP + state AP_only + + state Off [fillcolor=lightblue] + state Setup with behaviors "WiFi scan\nStart webUI\nEnter Credentials\nSave" [fillcolor=yellow] { + state Scan as "STA_only Scan" + state StartAP as "AP_only\nWebUI" + state ClientConnect as "User Connects\nCaptive Portal to /setup" + state EnterCredentials as "Enter WiFi Credentials" + state ClientDisconnect as "User\nDisconnects" + state CredentialsUpdated as "Credentials Updated" + initialState -> Scan + Scan -> StartAP + StartAP -> ClientConnect + ClientDisconnect <- ClientConnect + Scan <- ClientDisconnect + Scan <- StartAP "Time Out" + ClientConnect -> EnterCredentials + EnterCredentials -> CredentialsUpdated + CredentialsUpdated -> finalState + } + + initialState->Off - Off -> AllowSetup "Check\nAllowSetup" - AllowSetup -> HasCredentials "Setup not\nAllowed" + NeedSetup <- Off "Check\nNeed Setup?" + NeedSetup -> HasCredentials "Setup not\nAllowed/\nRequired" HasCredentials -> APalwaysOn "Has\nCredentials" - APalwaysOn -> STA_AP "AP" + APalwaysOn -> STA_AP "AP Always On\nor\nSetup 1st connect" APalwaysOn -> STA_only "No AP" HasCredentials -> APalwaysOnNoCred "No\nCredentials" - APalwaysOnNoCred -> Off "No AP" + Off <- APalwaysOnNoCred "No AP" APalwaysOnNoCred -> AP_only "AP" - AllowSetup -> HasCredentialsSetupAllowed "Setup\nAllowed" - HasCredentialsSetupAllowed -> Setup "No\nCredentials" - HasCredentialsSetupAllowed -> APalwaysOn "Has\nCredentials" + NeedSetup -> HasCredentialsSetupRequired "Setup\nAllowed" + HasCredentialsSetupRequired -> APalwaysOn "Has Credentials,\nNo Setup" - CredentialsUpdated <- Setup "Setup\nDone" - CredentialsUpdated -> AllowSetup + Setup <- HasCredentialsSetupRequired "No Credentials,\nEnter Setup" + Setup -> NeedSetup "Setup\nDone" + STA_only <- STA_only "Retry/\nReconnect" STA_AP -> AP_only "No Known\nNetwork found" STA_AP <- STA_AP "Retry/\nReconnect" - STA_only <- STA_only "Retry/\nReconnect" + + Setup <- STA_AP "Setup\nFailed 1st\nConnect" } \ No newline at end of file