From ef299ba7faf33485e70e6c1a89f9819312538597 Mon Sep 17 00:00:00 2001 From: David Montero Date: Mon, 6 Apr 2026 06:11:29 +0200 Subject: [PATCH] fix: align ESP32 WiFi SSID/channel with QEMU access_points[] array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes for ESP32 WiFi not connecting in production: 1. espidf_compiler.py: Change WiFi normalization from 'Velxio-GUEST' on channel 6 to 'Espressif' on channel 5. The lcgamboa QEMU binary downloaded from GitHub Releases only contains the original three APs: PICSimLabWifi (ch1), Espressif (ch5), MasseyWifi (ch10). Channel 6 had no matching AP, so the beacon timer's channel-match condition never fired → firmware scanned forever and never connected. 2. esp32_worker.py: Redirect fd 1 to /dev/null before loading QEMU so raw UART bytes from QEMU's -nographic mux don't corrupt the JSON event pipe. The real pipe fd is saved and sys.stdout is rebound so _emit() continues to work. This also prevents stdout pipe back-pressure from stalling qemu_main_loop() (and thus REALTIME timers). Co-Authored-By: Claude Sonnet 4.6 --- backend/app/services/esp32_worker.py | 19 ++++++++++++++ backend/app/services/espidf_compiler.py | 33 ++++++++++++++----------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/backend/app/services/esp32_worker.py b/backend/app/services/esp32_worker.py index 974d544d..f50a1723 100644 --- a/backend/app/services/esp32_worker.py +++ b/backend/app/services/esp32_worker.py @@ -611,6 +611,25 @@ def main() -> None: # noqa: C901 (complexity OK for inline worker) os.dup2(_nul, 0) os.close(_nul) + # Also redirect fd 1 (stdout) to /dev/null so QEMU's -nographic UART mux + # doesn't write raw UART bytes onto our JSON event pipe. Without this: + # 1. Raw UART bytes prefix each JSON line, corrupting the protocol. + # 2. On a busy host the pipe fills up, causing _on_uart_tx (called + # synchronously from qemu_main_loop) to block inside sys.stdout.flush(), + # which stalls qemu_main_loop() and prevents QEMU_CLOCK_REALTIME timers + # (including Esp32_WLAN_beacon_timer) from firing → WiFi never connects. + # Save the real pipe fd and rebind sys.stdout so _emit() keeps working. + import io as _io + _orig_stdout_fd = os.dup(1) + _nul_w = os.open(os.devnull, os.O_WRONLY) + os.dup2(_nul_w, 1) + os.close(_nul_w) + sys.stdout = _io.TextIOWrapper( + _io.FileIO(_orig_stdout_fd, mode='w', closefd=True), + line_buffering=True, + write_through=True, + ) + qemu_t = threading.Thread(target=_qemu_thread, daemon=True, name=f'qemu-{machine}') qemu_t.start() diff --git a/backend/app/services/espidf_compiler.py b/backend/app/services/espidf_compiler.py index dc997d6e..3afa7c3d 100644 --- a/backend/app/services/espidf_compiler.py +++ b/backend/app/services/espidf_compiler.py @@ -34,8 +34,11 @@ _STATIC_IP = '192.168.4.15' _GATEWAY_IP = '192.168.4.2' _NETMASK = '255.255.255.0' -# SSID the QEMU WiFi AP broadcasts -_QEMU_WIFI_SSID = 'Velxio-GUEST' +# SSID the QEMU WiFi AP broadcasts. +# Must match one of the access_point_info entries in esp32_wifi_ap.c +# (the lcgamboa QEMU fork). "Espressif" is on channel 5 in that array. +_QEMU_WIFI_SSID = 'Espressif' +_QEMU_WIFI_CHANNEL = 5 class ESPIDFCompiler: @@ -109,11 +112,11 @@ class ESPIDFCompiler: """ Normalize WiFi SSID/password/channel in Arduino sketches for QEMU. - QEMU's WiFi AP broadcasts "Velxio-GUEST" on channel 6 with open auth. + QEMU's WiFi AP broadcasts _QEMU_WIFI_SSID on _QEMU_WIFI_CHANNEL with open auth. This method rewrites the user's sketch so that: - - Any SSID string literal → "Velxio-GUEST" + - Any SSID string literal → _QEMU_WIFI_SSID - Password → "" (open auth) - - Channel → 6 + - Channel → _QEMU_WIFI_CHANNEL The user's editor still shows their original code; only the compiled binary is modified. """ @@ -121,9 +124,9 @@ class ESPIDFCompiler: return code # 1) Replace SSID variable definitions: - # const char* ssid = "anything" → "Velxio-GUEST" - # char ssid[] = "anything" → "Velxio-GUEST" - # #define WIFI_SSID "anything" → "Velxio-GUEST" + # const char* ssid = "anything" → _QEMU_WIFI_SSID + # char ssid[] = "anything" → _QEMU_WIFI_SSID + # #define WIFI_SSID "anything" → _QEMU_WIFI_SSID code = re.sub( r'((?:const\s+)?char\s*\*?\s*ssid\s*\[?\]?\s*=\s*)"[^"]*"', rf'\1"{_QEMU_WIFI_SSID}"', @@ -138,19 +141,19 @@ class ESPIDFCompiler: ) # 2) Normalize WiFi.begin() calls: - # WiFi.begin("X") → WiFi.begin("Velxio-GUEST", "", 6) - # WiFi.begin("X", "pass") → WiFi.begin("Velxio-GUEST", "", 6) - # WiFi.begin(ssid, pass, N) → WiFi.begin(ssid, "", 6) - # WiFi.begin(ssid) → WiFi.begin(ssid, "", 6) + # WiFi.begin("X") → WiFi.begin(_QEMU_WIFI_SSID, "", _QEMU_WIFI_CHANNEL) + # WiFi.begin("X", "pass") → WiFi.begin(_QEMU_WIFI_SSID, "", _QEMU_WIFI_CHANNEL) + # WiFi.begin(ssid, pass, N) → WiFi.begin(ssid, "", _QEMU_WIFI_CHANNEL) + # WiFi.begin(ssid) → WiFi.begin(ssid, "", _QEMU_WIFI_CHANNEL) def _rewrite_wifi_begin(m: re.Match) -> str: args = m.group(1) parts = [a.strip() for a in args.split(',')] ssid_arg = parts[0] - # If SSID is a string literal, force to Velxio-GUEST + # If SSID is a string literal, force to _QEMU_WIFI_SSID if ssid_arg.startswith('"'): ssid_arg = f'"{_QEMU_WIFI_SSID}"' - return f'WiFi.begin({ssid_arg}, "", 6)' + return f'WiFi.begin({ssid_arg}, "", {_QEMU_WIFI_CHANNEL})' code = re.sub( r'WiFi\.begin\s*\(([^)]+)\)', @@ -158,7 +161,7 @@ class ESPIDFCompiler: code ) - logger.info('[espidf] WiFi normalized: SSID→%s, channel→6, open auth', _QEMU_WIFI_SSID) + logger.info('[espidf] WiFi normalized: SSID→%s, channel→%d, open auth', _QEMU_WIFI_SSID, _QEMU_WIFI_CHANNEL) return code def _translate_sketch_to_espidf(self, sketch_code: str) -> str: