From 648e7c5acfd323cc844db98b9430bacbeabce1bb Mon Sep 17 00:00:00 2001 From: Tobias Hennerbichler Date: Mon, 20 Jul 2026 22:42:08 +0200 Subject: [PATCH 1/3] 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 d8cd59a53fe9d08080559eef704f8d001b87ef78 Mon Sep 17 00:00:00 2001 From: Tobias Hennerbichler Date: Tue, 21 Jul 2026 20:13:07 +0200 Subject: [PATCH 2/3] 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 e45653066ef625540c7ee7c0e9ccaab5d4a6eb2c Mon Sep 17 00:00:00 2001 From: Tobias Hennerbichler Date: Tue, 21 Jul 2026 21:40:40 +0200 Subject: [PATCH 3/3] - 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;