From 8a88ef7a85300d7eff5149667441dde6283fe02f Mon Sep 17 00:00:00 2001 From: Tobias Hennerbichler Date: Mon, 20 Jul 2026 22:42:08 +0200 Subject: [PATCH 1/4] Fixes and improvements for ESP32 in AP mode: - Now correctly reports the device's IP in AP mode for UDP discovery requests. Previously reported as 0.0.0.0. - Split logic between AP mode and Infra modes since WiFi.status() returns a meaningless value in AP mode. AP mode now immediately establishes TCP/UDP servers. Server establishment moved to own function. - Also adds null-pointer guards to tcp/udp loop while servers are not established yet. Not relevant for AP mode, but included here due to simplicity. Tested on ESP32 in WIFI_MODE_AP_ONLY. This PR does NOT address the infrastructure modes or the WIFI_MODE_ATTEMPT_INFRASTRUCTURE_FAIL_TO_AP mode in particular, which has some issues in the failover trigger. Local changes are ready and can be included in future PRs if desired. --- src/WifiControl.cpp | 139 ++++++++++++++++++++++++++------------------ src/WifiControl.hpp | 4 +- 2 files changed, 86 insertions(+), 57 deletions(-) diff --git a/src/WifiControl.cpp b/src/WifiControl.cpp index e3c1dacb..a35b06c1 100644 --- a/src/WifiControl.cpp +++ b/src/WifiControl.cpp @@ -61,8 +61,11 @@ void WifiControl::startAccessPointMode() WiFi.setHostname(WIFI_HOSTNAME); #endif - WiFi.softAP(WIFI_HOSTNAME, WIFI_AP_MODE_WPAKEY); + WiFi.mode(WIFI_AP); WiFi.softAPConfig(local_ip, gateway, subnet); + WiFi.softAP(WIFI_HOSTNAME, WIFI_AP_MODE_WPAKEY); + + establishServers(); } String wifiStatus(int status) @@ -111,49 +114,64 @@ String WifiControl::getStatus() result += WiFi.getHostname(); #endif - result += "," + WiFi.localIP().toString() + ":" + WIFI_PORT; + result += "," + getIP() + ":" + WIFI_PORT; result += "," + String(WIFI_INFRASTRUCTURE_MODE_SSID) + "," + String(WIFI_HOSTNAME); return result; } +void WifiControl::establishServers() +{ + delete _tcpServer; + _tcpServer = new WiFiServer(WIFI_PORT); + _tcpServer->begin(); + _tcpServer->setNoDelay(true); + + delete _udp; + _udp = new WiFiUDP(); + _udp->begin(4031); +} + +String WifiControl::getIP() +{ + return WIFI_MODE == WIFI_MODE_DISABLED ? "NONE" + : WIFI_MODE == WIFI_MODE_AP_ONLY ? WiFi.softAPIP().toString() + : WiFi.localIP().toString(); +} + void WifiControl::loop() { if (WIFI_MODE == WIFI_MODE_DISABLED) { return; } - if (_status != WiFi.status()) + + if (WIFI_MODE != WIFI_MODE_AP_ONLY) { - _status = WiFi.status(); - LOG(DEBUG_WIFI, "[WIFI]: Connected status changed to %s", wifiStatus(_status).c_str()); - if (_status == WL_CONNECTED) + if (_status != WiFi.status()) { - delete _tcpServer; - _tcpServer = new WiFiServer(WIFI_PORT); - _tcpServer->begin(); - _tcpServer->setNoDelay(true); - - delete _udp; - _udp = new WiFiUDP(); - _udp->begin(4031); - - LOG(DEBUG_WIFI, - "[WIFI]: Connecting to SSID %s at %s:%d", - WIFI_INFRASTRUCTURE_MODE_SSID, - WiFi.localIP().toString().c_str(), - WIFI_PORT); + _status = WiFi.status(); + LOG(DEBUG_WIFI, "[WIFI]: Connected status changed to %s", wifiStatus(_status).c_str()); + if (_status == WL_CONNECTED) + { + establishServers(); + LOG(DEBUG_WIFI, + "[WIFI]: Connecting to SSID %s at %s:%d", + WIFI_INFRASTRUCTURE_MODE_SSID, + WiFi.localIP().toString().c_str(), + WIFI_PORT); + } + + if (_status != WL_CONNECTED) + { + infraToAPFailover(); + return; + } } } _mount->loop(); - if (_status != WL_CONNECTED) - { - infraToAPFailover(); - return; - } - tcpLoop(); udpLoop(); } @@ -172,49 +190,58 @@ void WifiControl::infraToAPFailover() void WifiControl::tcpLoop() { - if (client && client.connected()) + if (!_tcpServer) + return; + + if (!client.connected()) { - while (client.available()) + client = _tcpServer->accept(); + if (!client.connected()) + return; + } + + int peek; + int avail; + while ((avail = client.available()) != 0) + { + LOG(DEBUG_WIFI, "[WIFITCP]: Available bytes %d. Peeking.", avail); + + // Peek first byte and check for ACK (0x06) handshake + peek = client.peek(); + LOG(DEBUG_WIFI, "[WIFITCP]: First byte is %x", peek); + if (peek == 0x06) { - LOG(DEBUG_WIFI, "[WIFITCP]: Available bytes %d. Peeking.", client.available()); + client.read(); + LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- Handshake request"); + client.write("P"); + LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> P (polar mode)"); + } + else + { + String cmd = client.readStringUntil('#'); + LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- %s#", cmd.c_str()); + const char *retVal = _cmdProcessor->processCommand(cmd); - // Peek first byte and check for ACK (0x06) handshake - LOG(DEBUG_WIFI, "[WIFITCP]: First byte is %x", client.peek()); - if (client.peek() == 0x06) + if (retVal[0] != '\0') { - client.read(); - LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- Handshake request"); - client.write("P"); - LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> P (polar mode)"); + client.write(retVal); + LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> %s", retVal); } else { - String cmd = client.readStringUntil('#'); - LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- %s#", cmd.c_str()); - const char *retVal = _cmdProcessor->processCommand(cmd); - - if (retVal[0] != '\0') - { - client.write(retVal); - LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> %s", retVal); - } - else - { - LOG(DEBUG_WIFI, "[WIFITCP]: No Reply"); - } + LOG(DEBUG_WIFI, "[WIFITCP]: No Reply"); } - - _mount->loop(); } - } - else - { - client = _tcpServer->available(); + + _mount->loop(); } } void WifiControl::udpLoop() { + if (!_udp) + return; + int packetSize = _udp->parsePacket(); if (packetSize) { @@ -222,7 +249,7 @@ void WifiControl::udpLoop() String reply = "skyfi:"; reply += WIFI_HOSTNAME; reply += "@"; - reply += WiFi.localIP().toString(); + reply += getIP(); LOG(DEBUG_WIFI, "[WIFIUDP]: Received %d bytes from %s, port %d", packetSize, diff --git a/src/WifiControl.hpp b/src/WifiControl.hpp index 943aec2e..aeaa437a 100644 --- a/src/WifiControl.hpp +++ b/src/WifiControl.hpp @@ -29,7 +29,9 @@ class WifiControl void infraToAPFailover(); void tcpLoop(); void udpLoop(); - wl_status_t _status; + void establishServers(); + String getIP(); + wl_status_t _status = WL_DISCONNECTED; Mount *_mount; LcdMenu *_lcdMenu; MeadeCommandProcessor *_cmdProcessor; From fd4785a712ec6d4bfc84467067dfb3a812e036c5 Mon Sep 17 00:00:00 2001 From: Tobias Hennerbichler Date: Tue, 21 Jul 2026 20:13:07 +0200 Subject: [PATCH 2/4] Addressed issues 1-4: - 1: Was included due to a bad reversion of local changes to the Infra mode. Suggested fix was applied. - 2: getStatus() now neither uses WiFi.status() nor WIFI_INFRASTRUCTURE_MODE_SSID in AP mode (both skipped) - 3: Not addressed in this PR. Will be included in future PR addressing infra modes. - 4: Now uses a DEFINE as the UDP port placeholder --- src/WifiControl.cpp | 29 ++++++++++++++++++----------- src/WifiControl.hpp | 2 ++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/WifiControl.cpp b/src/WifiControl.cpp index a35b06c1..4f7d3b60 100644 --- a/src/WifiControl.cpp +++ b/src/WifiControl.cpp @@ -109,13 +109,21 @@ String WifiControl::getStatus() result += "Infra-Fail-To-AP,"; } - result += wifiStatus(WiFi.status()) + ","; + if(WIFI_MODE != WIFI_MODE_AP_ONLY) + { + result += wifiStatus(WiFi.status()) + ","; + } #if defined(ESP32) result += WiFi.getHostname(); + result += ","; #endif - result += "," + getIP() + ":" + WIFI_PORT; - result += "," + String(WIFI_INFRASTRUCTURE_MODE_SSID) + "," + String(WIFI_HOSTNAME); + result += getIP() + ":" + WIFI_PORT + ","; + if (WIFI_MODE != WIFI_MODE_AP_ONLY) + { + result += String(WIFI_INFRASTRUCTURE_MODE_SSID) + ","; + } + result += String(WIFI_HOSTNAME); return result; } @@ -129,7 +137,7 @@ void WifiControl::establishServers() delete _udp; _udp = new WiFiUDP(); - _udp->begin(4031); + _udp->begin(WIFI_UDP_DISCOVERY_PORT); } String WifiControl::getIP() @@ -161,12 +169,11 @@ void WifiControl::loop() WiFi.localIP().toString().c_str(), WIFI_PORT); } - - if (_status != WL_CONNECTED) - { - infraToAPFailover(); - return; - } + } + if (_status != WL_CONNECTED) + { + infraToAPFailover(); + return; } } @@ -263,7 +270,7 @@ void WifiControl::udpLoop() incomingPacket[lookingFor.length()] = 0; if (lookingFor.equalsIgnoreCase(incomingPacket)) { - _udp->beginPacket(_udp->remoteIP(), 4031); + _udp->beginPacket(_udp->remoteIP(), WIFI_UDP_DISCOVERY_PORT); /*unsigned char bytes[255]; reply.getBytes(bytes, 255); _udp->write(bytes, reply.length());*/ diff --git a/src/WifiControl.hpp b/src/WifiControl.hpp index aeaa437a..a1d88482 100644 --- a/src/WifiControl.hpp +++ b/src/WifiControl.hpp @@ -10,6 +10,8 @@ #include #endif +#define WIFI_UDP_DISCOVERY_PORT 4031 + // Forward declarations class Mount; class LcdMenu; From 595f69c704b28e99c3cfecdb5e9e4849124a7e2f Mon Sep 17 00:00:00 2001 From: Tobias Hennerbichler Date: Tue, 21 Jul 2026 21:40:40 +0200 Subject: [PATCH 3/4] - 1: Added the suggested fix concering the readStringUntil function: - The tcpLoop now tracks the data sent across loop calls and only starts processing if the '#' character is received. - _mount->loop() is now called after every byte - Prevents memory overconsumption by defining a maximum size that should likely be large enough for commands. - Skips all '\n' and '\r' characters from the command string to prevent them from being processed. Should not influence the functionality since the Meade LX200 protocol does not seem to use them in any command. - 2: Added a log message if the softAP call fails to improve debugging. --- src/WifiControl.cpp | 79 +++++++++++++++++++++++++++++++++------------ src/WifiControl.hpp | 4 +++ 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/WifiControl.cpp b/src/WifiControl.cpp index 4f7d3b60..2d177682 100644 --- a/src/WifiControl.cpp +++ b/src/WifiControl.cpp @@ -10,6 +10,7 @@ WifiControl::WifiControl(Mount *mount, LcdMenu *lcdMenu) { _mount = mount; _lcdMenu = lcdMenu; + clearCmd(); } void WifiControl::setup() @@ -63,7 +64,11 @@ void WifiControl::startAccessPointMode() WiFi.mode(WIFI_AP); WiFi.softAPConfig(local_ip, gateway, subnet); - WiFi.softAP(WIFI_HOSTNAME, WIFI_AP_MODE_WPAKEY); + if (!WiFi.softAP(WIFI_HOSTNAME, WIFI_AP_MODE_WPAKEY)) + { + LOG(DEBUG_WIFI, "[WIFI]: AP mode could not be started!"); + return; + } establishServers(); } @@ -109,7 +114,7 @@ String WifiControl::getStatus() result += "Infra-Fail-To-AP,"; } - if(WIFI_MODE != WIFI_MODE_AP_ONLY) + if (WIFI_MODE != WIFI_MODE_AP_ONLY) { result += wifiStatus(WiFi.status()) + ","; } @@ -147,6 +152,12 @@ String WifiControl::getIP() : WiFi.localIP().toString(); } +void WifiControl::clearCmd() +{ + _currCmd = ""; + _currCmd.reserve(_defaultCapacity); +} + void WifiControl::loop() { if (WIFI_MODE == WIFI_MODE_DISABLED) @@ -202,45 +213,71 @@ void WifiControl::tcpLoop() if (!client.connected()) { + if (_currCmd.length() != 0) + { + clearCmd(); + } client = _tcpServer->accept(); if (!client.connected()) return; } - int peek; int avail; while ((avail = client.available()) != 0) { LOG(DEBUG_WIFI, "[WIFITCP]: Available bytes %d. Peeking.", avail); // Peek first byte and check for ACK (0x06) handshake - peek = client.peek(); - LOG(DEBUG_WIFI, "[WIFITCP]: First byte is %x", peek); - if (peek == 0x06) + if (_currCmd.length() == 0) { - client.read(); - LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- Handshake request"); - client.write("P"); - LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> P (polar mode)"); + int peek = client.peek(); + LOG(DEBUG_WIFI, "[WIFITCP]: First byte is %x", peek); + if (peek == 0x06) + { + client.read(); + LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- Handshake request"); + client.write("P"); + LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> P (polar mode)"); + _mount->loop(); + } } - else + + int recv; + while ((recv = client.read()) != -1) { - String cmd = client.readStringUntil('#'); - LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- %s#", cmd.c_str()); - const char *retVal = _cmdProcessor->processCommand(cmd); + // Prevent newlines from polluting the command string + if (recv == '\n' || recv == '\r') + { + _mount->loop(); + continue; + } - if (retVal[0] != '\0') + _currCmd += (char) recv; + if (recv == '#') { - client.write(retVal); - LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> %s", retVal); + LOG(DEBUG_WIFI, "[WIFITCP]: Query <-- %s", _currCmd.c_str()); + const char *retVal = _cmdProcessor->processCommand(_currCmd); + + if (retVal[0] != '\0') + { + client.write(retVal); + LOG(DEBUG_WIFI, "[WIFITCP]: Reply --> %s", retVal); + } + else + { + LOG(DEBUG_WIFI, "[WIFITCP]: No Reply"); + } + clearCmd(); } - else + + // Prevent memory overconsumption + if (_currCmd.length() == _maxCapacity) { - LOG(DEBUG_WIFI, "[WIFITCP]: No Reply"); + LOG(DEBUG_WIFI, "[WIFITCP]: Command string reached maximum capacity --> clear"); + clearCmd(); } + _mount->loop(); } - - _mount->loop(); } } diff --git a/src/WifiControl.hpp b/src/WifiControl.hpp index a1d88482..ad919a21 100644 --- a/src/WifiControl.hpp +++ b/src/WifiControl.hpp @@ -32,6 +32,7 @@ class WifiControl void tcpLoop(); void udpLoop(); void establishServers(); + void clearCmd(); String getIP(); wl_status_t _status = WL_DISCONNECTED; Mount *_mount; @@ -41,9 +42,12 @@ class WifiControl WiFiServer *_tcpServer = nullptr; WiFiUDP *_udp = nullptr; WiFiClient client; + String _currCmd; unsigned long _infraStart = 0; unsigned long _infraWait = 30000; // 30 second timeout for + static constexpr size_t _defaultCapacity = 32; + static constexpr size_t _maxCapacity = 128; }; extern WifiControl wifiControl; From 4126462475243211a6de98ed28702c2715701417 Mon Sep 17 00:00:00 2001 From: Lutz Date: Wed, 7 Oct 2026 22:38:47 -0700 Subject: [PATCH 4/4] style: apply clang-format v18 to WifiControl Co-Authored-By: Claude --- src/WifiControl.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WifiControl.hpp b/src/WifiControl.hpp index ad919a21..3039617e 100644 --- a/src/WifiControl.hpp +++ b/src/WifiControl.hpp @@ -10,7 +10,7 @@ #include #endif -#define WIFI_UDP_DISCOVERY_PORT 4031 + #define WIFI_UDP_DISCOVERY_PORT 4031 // Forward declarations class Mount; @@ -44,10 +44,10 @@ class WifiControl WiFiClient client; String _currCmd; - unsigned long _infraStart = 0; - unsigned long _infraWait = 30000; // 30 second timeout for + unsigned long _infraStart = 0; + unsigned long _infraWait = 30000; // 30 second timeout for static constexpr size_t _defaultCapacity = 32; - static constexpr size_t _maxCapacity = 128; + static constexpr size_t _maxCapacity = 128; }; extern WifiControl wifiControl;