diff --git a/build_as_lib.py b/build_as_lib.py index d8e95378eb..fbc7c15a7a 100644 --- a/build_as_lib.py +++ b/build_as_lib.py @@ -20,10 +20,12 @@ src_filter.append("+") elif item == "ESP32": src_filter.append("+") + src_filter.append("+") elif item == "NRF52_PLATFORM": src_filter.append("+") elif item == "RP2040_PLATFORM": src_filter.append("+") + src_filter.append("+") # DISPLAY HANDLING elif isinstance(item, tuple) and item[0] == "DISPLAY_CLASS": diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 2c33406632..7f468fa30f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -865,6 +865,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) { _iter_started = false; _cli_rescue = false; + cli_command[0] = 0; offline_queue_len = 0; app_target_ver = 0; clearPendingReqs(); @@ -2050,6 +2051,21 @@ void MyMesh::checkCLIRescueCmd() { _prefs.ble_pin = atoi(&config[4]); savePrefs(); Serial.printf(" > pin is now %06d\n", _prefs.ble_pin); +#ifdef WIFI_SSID + } else if (memcmp(config, "wifi.ssid ", 10) == 0) { + StrHelper::strncpy(_prefs.wifi_ssid, &config[10], sizeof(_prefs.wifi_ssid)); + savePrefs(); + Serial.printf(" > wifi.ssid is now %s (set wifi.pwd too, then reboot)\n", _prefs.wifi_ssid); + } else if (memcmp(config, "wifi.pwd ", 9) == 0) { + StrHelper::strncpy(_prefs.wifi_pwd, &config[9], sizeof(_prefs.wifi_pwd)); + savePrefs(); + Serial.println(" > wifi.pwd updated (reboot to apply)"); + } else if (strcmp(config, "wifi.clear") == 0) { + _prefs.wifi_ssid[0] = 0; + _prefs.wifi_pwd[0] = 0; + savePrefs(); + Serial.println(" > wifi config cleared, using build-time credentials (reboot to apply)"); +#endif } else { Serial.printf(" Error: unknown config: %s\n", config); } @@ -2233,6 +2249,11 @@ void MyMesh::loop() { checkCLIRescueCmd(); } else { checkSerialInterface(); +#if defined(WIFI_SSID) && defined(RP2040_PLATFORM) && !defined(ENABLE_USB_INTERFACE) + // RP2040 WiFi builds are headless and have no way into the rescue CLI (that needs a + // display + long-press), so serve config commands on the otherwise unused USB serial + checkCLIRescueCmd(); +#endif } // is there are pending dirty contacts write needed? diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 21766de82d..179253d024 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -40,6 +40,10 @@ class NodePrefs : public ConfigSerializer { // persisted to file uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) char default_scope_name[31]; uint8_t default_scope_key[16]; +#ifdef WIFI_SSID + char wifi_ssid[33] = {0}; // if empty, the compile-time WIFI_SSID is used + char wifi_pwd[64] = {0}; +#endif private: class RadioPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now) @@ -121,6 +125,20 @@ class NodePrefs : public ConfigSerializer { // persisted to file }; CompanionPrefs companion; +#ifdef WIFI_SSID + class WiFiPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("ssid", _parent->wifi_ssid, sizeof(_parent->wifi_ssid)); + def("pwd", _parent->wifi_pwd, sizeof(_parent->wifi_pwd)); + } + public: + WiFiPrefs(NodePrefs* parent) : _parent(parent) { } + }; + WiFiPrefs wifi; +#endif + protected: void structure() override { def("name", node_name, sizeof(node_name)); @@ -132,9 +150,16 @@ class NodePrefs : public ConfigSerializer { // persisted to file def("gps", gps); def("repeat", repeat); def("comp", companion); +#ifdef WIFI_SSID + def("wifi", wifi); +#endif } public: - NodePrefs() : radio(this), gps(this), companion(this) { + NodePrefs() : radio(this), gps(this), companion(this) +#ifdef WIFI_SSID + , wifi(this) +#endif + { node_name[0] = 0; default_scope_name[0] = 0; memset(default_scope_key, 0, sizeof(default_scope_key)); diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 89f0e6cb9f..ff0794ab90 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -36,9 +36,14 @@ MultiSerialInterface interface_manager; #ifndef TCP_PORT #define TCP_PORT 5000 #endif - #ifdef ESP32 - // include esp32 wifi interface - #include + #ifndef WIFI_RETRY_INTERVAL + #define WIFI_RETRY_INTERVAL 30000 // millis between reconnect attempts + #endif + #ifndef WIFI_RETRY_TIMEOUT + #define WIFI_RETRY_TIMEOUT 5000 // RP2040: cap on how long one join may block loop() + #endif + #if defined(ESP32) || defined(RP2040_PLATFORM) + #include SerialWifiInterface wifi_interface; #else #error "SerialWifiInterface is not defined for this platform" @@ -108,9 +113,12 @@ void halt() { } /* WIFI RECONNECT TRACKERS */ -#if defined(ESP32) && defined(WIFI_SSID) +#ifdef WIFI_SSID bool wifi_needs_reconnect = false; unsigned long last_wifi_reconnect_attempt = 0; + const char* wifi_ssid = WIFI_SSID; // replaced by stored prefs, if set + const char* wifi_pwd = WIFI_PWD; + bool wifi_was_connected = false; #endif void setup() { @@ -192,6 +200,7 @@ void setup() { // add wifi interface #ifdef WIFI_SSID +#if defined(ESP32) board.setInhibitSleep(true); // prevent sleep when WiFi is active WiFi.setAutoReconnect(true); @@ -204,8 +213,28 @@ void setup() { wifi_needs_reconnect = false; } }); +#endif - WiFi.begin(WIFI_SSID, WIFI_PWD); + // stored credentials win over the build-time ones ('set wifi.ssid ' over USB serial). + // they are taken as a pair, so 'set wifi.ssid' alone gives an open-network join, not a + // silent fallback to the build-time password of a different network. + if (the_mesh.getNodePrefs()->wifi_ssid[0]) { + wifi_ssid = the_mesh.getNodePrefs()->wifi_ssid; + wifi_pwd = the_mesh.getNodePrefs()->wifi_pwd; + } + if (wifi_pwd[0] == 0) wifi_pwd = NULL; // NULL (not "") selects an open network + WIFI_DEBUG_PRINTLN("connecting to %s", wifi_ssid); + +#if defined(RP2040_PLATFORM) + // ponytail: the join itself blocks inside the core (CYW43::begin busy-waits for the + // association), so every attempt stalls the mesh loop. beginNoBlock() only skips the + // extra DHCP wait. Give the first connect a full window, then bound the retries below. + // Upgrade path if the stall ever matters: run WiFi on core1. + WiFi.beginNoBlock(wifi_ssid, wifi_pwd); + last_wifi_reconnect_attempt = millis(); // let DHCP finish before the poll can retry +#else + WiFi.begin(wifi_ssid, wifi_pwd); +#endif wifi_interface.begin(TCP_PORT); interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface); #endif @@ -262,12 +291,31 @@ void loop() { #endif } -#if defined(ESP32) && defined(WIFI_SSID) - // Safely attempt to reconnect every 10 seconds if flagged - if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) { - WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect..."); - WiFi.disconnect(); - WiFi.reconnect(); +#ifdef WIFI_SSID + // RP2040 has no WiFi event callbacks, so poll the link state instead + #if defined(RP2040_PLATFORM) + wifi_needs_reconnect = (WiFi.status() != WL_CONNECTED); + if (wifi_was_connected == wifi_needs_reconnect) { // link state changed + wifi_was_connected = !wifi_needs_reconnect; + if (wifi_was_connected) { + WIFI_DEBUG_PRINTLN("connected, listening on %s:%d", WiFi.localIP().toString().c_str(), TCP_PORT); + } else { + WIFI_DEBUG_PRINTLN("link lost"); + } + } + #endif + + // Safely attempt to reconnect if flagged. On RP2040 each attempt blocks the mesh loop + // for up to WIFI_RETRY_TIMEOUT, so retry less often and cap how long a join may stall. + if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > WIFI_RETRY_INTERVAL)) { + WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect to %s (status %d)...", wifi_ssid, WiFi.status()); + #if defined(RP2040_PLATFORM) + WiFi.setTimeout(WIFI_RETRY_TIMEOUT); + WiFi.beginNoBlock(wifi_ssid, wifi_pwd); // no reconnect() on this platform + #else + WiFi.disconnect(); + WiFi.reconnect(); + #endif last_wifi_reconnect_attempt = millis(); } #endif diff --git a/platformio.ini b/platformio.ini index e78124a40b..4f86d5aedb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -64,6 +64,7 @@ build_flags = ${arduino_base.build_flags} -D ESP32_PLATFORM ; -D ESP32_CPU_FREQ=80 ; change it to your need build_src_filter = ${arduino_base.build_src_filter} + + [esp32_ota] lib_deps = diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/wifi/SerialWifiInterface.cpp similarity index 100% rename from src/helpers/esp32/SerialWifiInterface.cpp rename to src/helpers/wifi/SerialWifiInterface.cpp diff --git a/src/helpers/esp32/SerialWifiInterface.h b/src/helpers/wifi/SerialWifiInterface.h similarity index 100% rename from src/helpers/esp32/SerialWifiInterface.h rename to src/helpers/wifi/SerialWifiInterface.h diff --git a/variants/rpi_picow/platformio.ini b/variants/rpi_picow/platformio.ini index 0fe8c43696..32944a9a49 100644 --- a/variants/rpi_picow/platformio.ini +++ b/variants/rpi_picow/platformio.ini @@ -81,20 +81,22 @@ lib_ignore = BLE ; lib_deps = ${rpi_picow.lib_deps} ; densaugeo/base64 @ ~1.4.0 -; [env:PicoW_companion_radio_wifi] -; extends = rpi_picow -; build_flags = ${rpi_picow.build_flags} -; -D MAX_CONTACTS=100 -; -D MAX_GROUP_CHANNELS=8 -; -D WIFI_DEBUG_LOGGING=1 -; -D WIFI_SSID='"myssid"' -; -D WIFI_PWD='"mypwd"' -; ; -D MESH_PACKET_LOGGING=1 -; ; -D MESH_DEBUG=1 -; build_src_filter = ${rpi_picow.build_src_filter} -; +<../examples/companion_radio/*.cpp> -; lib_deps = ${rpi_picow.lib_deps} -; densaugeo/base64 @ ~1.4.0 +[env:PicoW_companion_radio_wifi] +extends = rpi_picow +build_flags = ${rpi_picow.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rpi_picow.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = ${rpi_picow.lib_deps} + densaugeo/base64 @ ~1.4.0 +lib_ignore = BLE [env:PicoW_terminal_chat] extends = rpi_picow