From 739a67c9f1504e029272a2fc68d241f1e9107846 Mon Sep 17 00:00:00 2001 From: Michael Graff Date: Fri, 4 Sep 2026 20:18:51 -0500 Subject: [PATCH] Allow WiFi credentials to be set at runtime Store ssid/pwd in NodePrefs and set them with 'set wifi.ssid' / 'set wifi.pwd' over USB serial; build-time WIFI_SSID/WIFI_PWD stay as the fallback. Headless WiFi builds get the config CLI on Serial, which is otherwise unused there. --- examples/companion_radio/MyMesh.cpp | 34 ++++++++++++++++++++++++++++ examples/companion_radio/NodePrefs.h | 27 +++++++++++++++++++++- examples/companion_radio/main.cpp | 13 +++++++++-- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index ee8114ca..8550890e 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -932,6 +932,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(); @@ -2156,6 +2157,35 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char* return true; } +#ifdef WIFI_SSID + // local console only: these are credentials, and remote admin has no business with them + if (sender_timestamp == 0) { + if (memcmp(command, "set wifi.ssid ", 14) == 0) { + StrHelper::strncpy(_prefs.wifi_ssid, &command[14], sizeof(_prefs.wifi_ssid)); + savePrefs(); + sprintf(reply, "> wifi.ssid is now %s (set wifi.pwd too, then reboot)", _prefs.wifi_ssid); + return true; + } + if (memcmp(command, "set wifi.pwd ", 13) == 0) { + StrHelper::strncpy(_prefs.wifi_pwd, &command[13], sizeof(_prefs.wifi_pwd)); + savePrefs(); + strcpy(reply, "> wifi.pwd updated (reboot to apply)"); + return true; + } + if (strcmp(command, "set wifi.clear") == 0) { + _prefs.wifi_ssid[0] = 0; + _prefs.wifi_pwd[0] = 0; + savePrefs(); + strcpy(reply, "> wifi config cleared, using build-time credentials (reboot to apply)"); + return true; + } + if (strcmp(command, "get wifi.ssid") == 0) { // no 'get wifi.pwd', by design + sprintf(reply, "> %s", _prefs.wifi_ssid[0] ? _prefs.wifi_ssid : "(build-time)"); + return true; + } + } +#endif + if (strcmp(command, "board") == 0) { strcpy(reply, board.getManufacturerName()); return true; @@ -2386,6 +2416,10 @@ void MyMesh::loop() { checkCLIRescueCmd(); } else { checkSerialInterface(); +#if defined(WIFI_SSID) && !defined(ENABLE_USB_INTERFACE) + // headless WiFi build: USB serial isn't a companion transport, so use it for config + 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 f6f9b887..c725c317 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -44,6 +44,10 @@ public: char default_scope_name[31]; uint8_t default_scope_key[16]; int8_t tz_offset = 0; +#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 CommonRadioPrefs { @@ -160,6 +164,20 @@ private: DynamicConfigSerializer custom; +#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)); @@ -172,9 +190,16 @@ protected: def("repeat", repeat); def("comp", companion); def("custom", custom); +#ifdef WIFI_SSID + def("wifi", wifi); +#endif } public: - NodePrefs() : radio(this), gps(this), companion(this), custom(&radio) { + NodePrefs() : radio(this), gps(this), companion(this), custom(&radio) +#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 7c8c12b9..64aa3d26 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -110,6 +110,8 @@ void halt() { #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; #endif void setup() { @@ -206,7 +208,14 @@ void setup() { }); #endif - WiFi.begin(WIFI_SSID, WIFI_PWD); + // stored credentials win over the build-time ones ('set wifi.ssid ' over USB serial) + if (the_mesh.getNodePrefs()->wifi_ssid[0]) { + wifi_ssid = the_mesh.getNodePrefs()->wifi_ssid; + wifi_pwd = the_mesh.getNodePrefs()->wifi_pwd; + } + WIFI_DEBUG_PRINTLN("connecting to %s", wifi_ssid); + + WiFi.begin(wifi_ssid, wifi_pwd); wifi_interface.begin(TCP_PORT); interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface); #endif @@ -273,7 +282,7 @@ void loop() { if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) { WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect..."); #if defined(RP2040_PLATFORM) - WiFi.begin(WIFI_SSID, WIFI_PWD); // no reconnect() on this platform + WiFi.begin(wifi_ssid, wifi_pwd); // no reconnect() on this platform #else WiFi.disconnect(); WiFi.reconnect();