From ef7943c3c0f862c004e17ff15096b12daf3962f3 Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Sat, 6 Jun 2026 17:53:43 -0600 Subject: [PATCH] feat: wifi companion configuration via cli rescue The idea with this was allowing users to use WiFi companions, without having to compile a build. This change enables CLI Rescue commands to companion wifi builds so they can be provisioned via USB cable. (The CLI is limited in number of characters, so some longer SSIDs would still have to be compiled in, for now.) This would also allow a wifi companion firmware to be available for download from the MeshCore site and while the user is setting up, they could provision their SSID/password on the web-CLI or a local terminal via USB without having to compile a build. Longer-term, other solutions could be devised to make this more flexible, like the mobile app could add in the wifi provisioning step with a Bluetooth bootstrap stage, leveraging this code as the mechanism to store/configure. CLI Rescue commands added: | Command | Syntax | Description | |---|---|---| | Set WiFi SSID | `wifi_ssid ` | Set the network name | | Set WiFi Password | `wifi_pwd ` | Set the network password | | Commit WiFi Settings | `wifi_commit` | Apply and save settings | | Clear WiFi Settings | `wifi_clear` | Reset all WiFi settings | This is dependent on [PR 2706](https://github.com/meshcore-dev/MeshCore/pull/2706). Tested on Heltec v3 --- examples/companion_radio/MyMesh.cpp | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6fbb0f7428..8fc54004ab 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2004,9 +2004,17 @@ void MyMesh::enterCLIRescue() { _cli_rescue = true; cli_command[0] = 0; Serial.println("========= CLI Rescue ========="); + Serial.println("Commands: set pin , rebuild, erase, reboot, ls, cat, rm"); +#ifdef WIFI_SSID + Serial.println("WiFi: wifi_ssid , wifi_pwd , wifi_commit, wifi_clear"); +#endif } void MyMesh::checkCLIRescueCmd() { +#ifdef WIFI_SSID + static char staged_ssid[33] = {0}; + static char staged_pwd[65] = {0}; +#endif int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { char c = Serial.read(); @@ -2168,6 +2176,42 @@ void MyMesh::checkCLIRescueCmd() { } +#ifdef WIFI_SSID + } else if (memcmp(cli_command, "wifi_ssid ", 10) == 0) { + strncpy(staged_ssid, &cli_command[10], sizeof(staged_ssid) - 1); + staged_ssid[sizeof(staged_ssid) - 1] = 0; + Serial.printf(" > SSID staged: \"%s\" (not saved yet, use wifi_commit)\n", staged_ssid); + } else if (memcmp(cli_command, "wifi_pwd ", 9) == 0) { + strncpy(staged_pwd, &cli_command[9], sizeof(staged_pwd) - 1); + staged_pwd[sizeof(staged_pwd) - 1] = 0; + Serial.println(" > Password staged (not saved yet, use wifi_commit)"); + } else if (strcmp(cli_command, "wifi_commit") == 0) { + if (staged_ssid[0] == 0 || staged_pwd[0] == 0) { + Serial.println(" Error: stage both wifi_ssid and wifi_pwd before committing"); + } else { + File f = _store->getPrimaryFS()->open("/wifi_config", "w", true); + if (f) { + f.print(staged_ssid); f.print('\n'); + f.print(staged_pwd); f.print('\n'); + f.close(); + Serial.printf(" > Saved SSID \"%s\" to flash, rebooting...\n", staged_ssid); + board.reboot(); + } else { + Serial.println(" Error: failed to write /wifi_config"); + } + } + } else if (strcmp(cli_command, "wifi_clear") == 0) { + _store->getPrimaryFS()->remove("/wifi_config"); + Serial.println(" > /wifi_config removed, using compiled-in defaults on next boot, rebooting..."); + board.reboot(); + } else if (strcmp(cli_command, "wifi_show") == 0) { + Serial.printf(" Staged SSID: \"%s\"\n", staged_ssid[0] ? staged_ssid : "(none)"); + Serial.printf(" Staged PWD: %s\n", staged_pwd[0] ? "(set)" : "(none)"); + bool has_config = _store->getPrimaryFS()->exists("/wifi_config"); + Serial.printf(" /wifi_config on flash: %s\n", has_config ? "yes" : "no (using compiled-in defaults)"); +#endif + + } else if (strcmp(cli_command, "reboot") == 0) { board.reboot(); // doesn't return } else {