Skip to content

Commit 1751783

Browse files
authored
Merge pull request #3357 from skandragon/feat/picow-wifi-ble
Add WiFi to RPI PicoW (and perhaps others)
2 parents 017fd09 + cee4375 commit 1751783

34 files changed

Lines changed: 613 additions & 60 deletions

File tree

build_as_lib.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
src_filter.append("+<helpers/stm32/*>")
2121
elif item == "ESP32":
2222
src_filter.append("+<helpers/esp32/*>")
23+
src_filter.append("+<helpers/wifi/*>")
2324
elif item == "NRF52_PLATFORM":
2425
src_filter.append("+<helpers/nrf52/*>")
2526
elif item == "RP2040_PLATFORM":
2627
src_filter.append("+<helpers/rp2040/*>")
28+
src_filter.append("+<helpers/wifi/*>")
2729

2830
# DISPLAY HANDLING
2931
elif isinstance(item, tuple) and item[0] == "DISPLAY_CLASS":

examples/companion_radio/MyMesh.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include "MyMesh.h"
22

33
#include <Arduino.h> // needed for PlatformIO
4+
#ifdef ENABLE_WIFI_INTERFACE
5+
#include <WiFi.h>
6+
#endif
47
#include <Mesh.h>
58

69
#define CMD_APP_START 1
@@ -932,6 +935,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
932935
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
933936
_iter_started = false;
934937
_cli_rescue = false;
938+
cli_command[0] = 0;
935939
offline_queue_len = 0;
936940
app_target_ver = 0;
937941
clearPendingReqs();
@@ -2160,6 +2164,54 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char*
21602164
return true;
21612165
}
21622166

2167+
#ifdef ENABLE_WIFI_INTERFACE
2168+
if (memcmp(command, "set wifi.ssid ", 14) == 0) {
2169+
StrHelper::strncpy(_prefs.wifi_ssid, &command[14], sizeof(_prefs.wifi_ssid));
2170+
savePrefs();
2171+
sprintf(reply, "> wifi.ssid is now %s (set wifi.pwd too, then reboot)", _prefs.wifi_ssid);
2172+
return true;
2173+
}
2174+
if (memcmp(command, "set wifi.pwd ", 13) == 0) {
2175+
StrHelper::strncpy(_prefs.wifi_pwd, &command[13], sizeof(_prefs.wifi_pwd));
2176+
savePrefs();
2177+
strcpy(reply, "> wifi.pwd updated (reboot to apply)");
2178+
return true;
2179+
}
2180+
if (strcmp(command, "set wifi.clear") == 0) {
2181+
_prefs.wifi_ssid[0] = 0;
2182+
_prefs.wifi_pwd[0] = 0;
2183+
savePrefs();
2184+
strcpy(reply, "> wifi config cleared (reboot to apply)");
2185+
return true;
2186+
}
2187+
if (strcmp(command, "get wifi.ssid") == 0) { // no 'get wifi.pwd', by design
2188+
sprintf(reply, "> %s", _prefs.getWifiSSID()[0] ? _prefs.getWifiSSID() : "(not set)");
2189+
return true;
2190+
}
2191+
if (memcmp(command, "set wifi.enabled ", 17) == 0) {
2192+
_prefs.wifi_enabled = atoi(&command[17]) ? 1 : 0;
2193+
savePrefs();
2194+
sprintf(reply, "> wifi.enabled is now %d (reboot to apply)", _prefs.wifi_enabled);
2195+
return true;
2196+
}
2197+
if (strcmp(command, "get wifi.enabled") == 0) {
2198+
sprintf(reply, "> %d", _prefs.wifi_enabled);
2199+
return true;
2200+
}
2201+
if (strcmp(command, "get wifi.status") == 0) {
2202+
strcpy(reply, WiFi.status() == WL_CONNECTED ? "> connected" : "> disconnected");
2203+
return true;
2204+
}
2205+
if (strcmp(command, "get wifi.ip") == 0) {
2206+
if (WiFi.status() == WL_CONNECTED) {
2207+
sprintf(reply, "> %s", WiFi.localIP().toString().c_str());
2208+
} else {
2209+
strcpy(reply, "> (not connected)");
2210+
}
2211+
return true;
2212+
}
2213+
#endif
2214+
21632215
if (strcmp(command, "board") == 0) {
21642216
strcpy(reply, board.getManufacturerName());
21652217
return true;
@@ -2390,6 +2442,11 @@ void MyMesh::loop() {
23902442
checkCLIRescueCmd();
23912443
} else {
23922444
checkSerialInterface();
2445+
#if defined(ENABLE_WIFI_INTERFACE) && defined(RP2040_PLATFORM) && !defined(ENABLE_USB_INTERFACE)
2446+
// RP2040 WiFi builds are headless and have no way into the rescue CLI (that needs a
2447+
// display + long-press), so serve config commands on the otherwise unused USB serial
2448+
checkCLIRescueCmd();
2449+
#endif
23932450
}
23942451

23952452
// is there are pending dirty contacts write needed?

examples/companion_radio/NodePrefs.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ class NodePrefs : public ConfigSerializer { // persisted to file
4848
char default_scope_name[31];
4949
uint8_t default_scope_key[16];
5050
int8_t tz_offset = 0;
51+
#ifdef ENABLE_WIFI_INTERFACE
52+
#ifndef WIFI_SSID
53+
#define WIFI_SSID ""
54+
#endif
55+
char wifi_ssid[33] = {0}; // if empty, the compile-time WIFI_SSID is used
56+
char wifi_pwd[64] = {0};
57+
uint8_t wifi_enabled = 1; // enabled by default to allow wifi only builds to work. wifi won't be started if ssid is empty
58+
// use ssid from prefs, or fallback to ssid from build flags
59+
const char* getWifiSSID() const { return wifi_ssid[0] ? wifi_ssid : WIFI_SSID; }
60+
#endif
5161

5262
private:
5363
class RadioPrefs : public CommonRadioPrefs {
@@ -164,6 +174,21 @@ class NodePrefs : public ConfigSerializer { // persisted to file
164174

165175
DynamicConfigSerializer custom;
166176

177+
#ifdef ENABLE_WIFI_INTERFACE
178+
class WiFiPrefs : public ConfigSerializer {
179+
NodePrefs* _parent;
180+
protected:
181+
void structure() override {
182+
def("ssid", _parent->wifi_ssid, sizeof(_parent->wifi_ssid));
183+
def("pwd", _parent->wifi_pwd, sizeof(_parent->wifi_pwd));
184+
def("enabled", _parent->wifi_enabled);
185+
}
186+
public:
187+
WiFiPrefs(NodePrefs* parent) : _parent(parent) { }
188+
};
189+
WiFiPrefs wifi;
190+
#endif
191+
167192
protected:
168193
void structure() override {
169194
def("name", node_name, sizeof(node_name));
@@ -176,9 +201,16 @@ class NodePrefs : public ConfigSerializer { // persisted to file
176201
def("repeat", repeat);
177202
def("comp", companion);
178203
def("custom", custom);
204+
#ifdef ENABLE_WIFI_INTERFACE
205+
def("wifi", wifi);
206+
#endif
179207
}
180208
public:
181-
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio) {
209+
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio)
210+
#ifdef ENABLE_WIFI_INTERFACE
211+
, wifi(this)
212+
#endif
213+
{
182214
node_name[0] = 0;
183215
default_scope_name[0] = 0;
184216
memset(default_scope_key, 0, sizeof(default_scope_key));

examples/companion_radio/main.cpp

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,38 @@ MultiSerialInterface interface_manager;
2626
// include nrf52 bluetooth interface
2727
#include <helpers/nrf52/SerialBLEInterface.h>
2828
SerialBLEInterface bluetooth_interface;
29+
#elif defined(RP2040_PLATFORM)
30+
// include rp2040 (Pico W / CYW43) bluetooth interface
31+
#include <helpers/rp2040/SerialBLEInterface.h>
32+
SerialBLEInterface bluetooth_interface;
2933
#else
3034
#error "SerialBLEInterface is not defined for this platform"
3135
#endif
3236
#endif
3337

3438
// include wifi interface
35-
#ifdef WIFI_SSID
39+
#ifdef ENABLE_WIFI_INTERFACE
40+
#ifndef WIFI_SSID
41+
#define WIFI_SSID ""
42+
#endif
43+
#ifndef WIFI_PWD
44+
#define WIFI_PWD ""
45+
#endif
3646
#ifndef TCP_PORT
3747
#define TCP_PORT 5000
3848
#endif
39-
#ifdef ESP32
40-
// include esp32 wifi interface
41-
#include <helpers/esp32/SerialWifiInterface.h>
49+
#ifndef WIFI_RETRY_INTERVAL
50+
#if defined(RP2040_PLATFORM)
51+
#define WIFI_RETRY_INTERVAL 30000 // each attempt blocks loop(), so retry less often
52+
#else
53+
#define WIFI_RETRY_INTERVAL 10000 // millis between reconnect attempts
54+
#endif
55+
#endif
56+
#ifndef WIFI_RETRY_TIMEOUT
57+
#define WIFI_RETRY_TIMEOUT 5000 // RP2040: cap on how long one join may block loop()
58+
#endif
59+
#if defined(ESP32) || defined(RP2040_PLATFORM)
60+
#include <helpers/wifi/SerialWifiInterface.h>
4261
SerialWifiInterface wifi_interface;
4362
#else
4463
#error "SerialWifiInterface is not defined for this platform"
@@ -108,9 +127,13 @@ void halt() {
108127
}
109128

110129
/* WIFI RECONNECT TRACKERS */
111-
#if defined(ESP32) && defined(WIFI_SSID)
130+
#ifdef ENABLE_WIFI_INTERFACE
112131
bool wifi_needs_reconnect = false;
113132
unsigned long last_wifi_reconnect_attempt = 0;
133+
char wifi_ssid[33] = WIFI_SSID; // replaced by stored prefs at boot, if set
134+
char wifi_pwd[64] = WIFI_PWD;
135+
bool wifi_was_connected = false;
136+
bool wifi_enabled = false; // set at boot from prefs; false also when the effective SSID is blank
114137
#endif
115138

116139
void setup() {
@@ -191,23 +214,46 @@ void setup() {
191214
#endif
192215

193216
// add wifi interface
194-
#ifdef WIFI_SSID
195-
board.setInhibitSleep(true); // prevent sleep when WiFi is active
196-
WiFi.setAutoReconnect(true);
197-
198-
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
199-
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
200-
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
201-
wifi_needs_reconnect = true;
202-
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
203-
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
204-
wifi_needs_reconnect = false;
205-
}
206-
});
217+
#ifdef ENABLE_WIFI_INTERFACE
218+
// use wifi ssid and password from prefs if ssid is not empty, otherwise use the build flag defaults
219+
if (the_mesh.getNodePrefs()->wifi_ssid[0]) {
220+
strcpy(wifi_ssid, the_mesh.getNodePrefs()->wifi_ssid);
221+
strcpy(wifi_pwd, the_mesh.getNodePrefs()->wifi_pwd);
222+
}
223+
// only start wifi if enabled and ssid is not empty
224+
wifi_enabled = the_mesh.getNodePrefs()->wifi_enabled;
225+
if (wifi_enabled && wifi_ssid[0]) {
226+
#if defined(ESP32)
227+
board.setInhibitSleep(true); // prevent sleep when WiFi is active
228+
WiFi.setAutoReconnect(true);
229+
230+
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
231+
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
232+
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
233+
wifi_needs_reconnect = true;
234+
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
235+
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
236+
wifi_needs_reconnect = false;
237+
}
238+
});
239+
#endif
207240

208-
WiFi.begin(WIFI_SSID, WIFI_PWD);
209-
wifi_interface.begin(TCP_PORT);
210-
interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface);
241+
WIFI_DEBUG_PRINTLN("connecting to %s", wifi_ssid);
242+
243+
#if defined(RP2040_PLATFORM)
244+
// the join itself blocks inside the core (CYW43::begin busy-waits for the
245+
// association), so every attempt stalls the mesh loop. beginNoBlock() only skips the
246+
// extra DHCP wait. Give the first connect a full window, then bound the retries below.
247+
WiFi.beginNoBlock(wifi_ssid, wifi_pwd);
248+
last_wifi_reconnect_attempt = millis(); // let DHCP finish before the poll can retry
249+
#else
250+
WiFi.begin(wifi_ssid, wifi_pwd);
251+
#endif
252+
wifi_interface.begin(TCP_PORT);
253+
interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface);
254+
} else {
255+
WIFI_DEBUG_PRINTLN("wifi disabled");
256+
}
211257
#endif
212258

213259
// add usb interface
@@ -262,13 +308,34 @@ void loop() {
262308
#endif
263309
}
264310

265-
#if defined(ESP32) && defined(WIFI_SSID)
266-
// Safely attempt to reconnect every 10 seconds if flagged
267-
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
268-
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect...");
269-
WiFi.disconnect();
270-
WiFi.reconnect();
271-
last_wifi_reconnect_attempt = millis();
311+
#ifdef ENABLE_WIFI_INTERFACE
312+
if (wifi_enabled) {
313+
// RP2040 has no WiFi event callbacks, so poll the link state instead
314+
#if defined(RP2040_PLATFORM)
315+
wifi_needs_reconnect = (WiFi.status() != WL_CONNECTED);
316+
if (wifi_was_connected == wifi_needs_reconnect) { // link state changed
317+
wifi_was_connected = !wifi_needs_reconnect;
318+
if (wifi_was_connected) {
319+
WIFI_DEBUG_PRINTLN("connected, listening on %s:%d", WiFi.localIP().toString().c_str(), TCP_PORT);
320+
} else {
321+
WIFI_DEBUG_PRINTLN("link lost");
322+
}
323+
}
324+
#endif
325+
326+
// Safely attempt to reconnect if flagged. On RP2040 each attempt blocks the mesh loop
327+
// for up to WIFI_RETRY_TIMEOUT, so retry less often and cap how long a join may stall.
328+
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > WIFI_RETRY_INTERVAL)) {
329+
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect to %s (status %d)...", wifi_ssid, WiFi.status());
330+
#if defined(RP2040_PLATFORM)
331+
WiFi.setTimeout(WIFI_RETRY_TIMEOUT);
332+
WiFi.beginNoBlock(wifi_ssid, wifi_pwd); // no reconnect() on this platform
333+
#else
334+
WiFi.disconnect();
335+
WiFi.reconnect();
336+
#endif
337+
last_wifi_reconnect_attempt = millis();
338+
}
272339
}
273340
#endif
274341
}

examples/companion_radio/ui-new/UITask.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include "../MyMesh.h"
44
#include "target.h"
55
#include <time.h>
6-
#ifdef WIFI_SSID
6+
#ifdef ENABLE_WIFI_INTERFACE
77
#include <WiFi.h>
88
#endif
99

@@ -260,7 +260,7 @@ class HomeScreen : public UIScreen {
260260
sprintf(tmp, "%02d/%02d/%d", dt.day(), dt.month(), dt.year());
261261
display.drawTextCentered(display.width() / 2, 80, tmp);
262262
#endif
263-
#ifdef WIFI_SSID
263+
#ifdef ENABLE_WIFI_INTERFACE
264264
IPAddress ip = WiFi.localIP();
265265
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
266266
display.setTextSize(1);

examples/companion_radio/ui-tiny/UITask.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include "target.h"
55
#include "u8g2_icons.h"
66

7-
#ifdef WIFI_SSID
7+
#ifdef ENABLE_WIFI_INTERFACE
88
#include <WiFi.h>
99
#endif
1010

@@ -173,7 +173,7 @@ class HomeScreen : public UIScreen {
173173
display.setCursor(0, 19);
174174
display.print(tmp);
175175

176-
#ifdef WIFI_SSID
176+
#ifdef ENABLE_WIFI_INTERFACE
177177
IPAddress ip = WiFi.localIP();
178178
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
179179
display.setTextSize(1);

src/helpers/ConfigSerializer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "ConfigSerializer.h"
2+
#include <stdlib.h> // atoi/atol/atof (Arduino.h pulls this in on-device, native builds do not)
23

34
bool ConfigSerializer::saveSerial(Stream& s) {
45
Context context(&s, OP::WRITE);
@@ -62,6 +63,7 @@ int ConfigSerializer::Context::readNext() {
6263
case EXPECT_COMMA_OR_KEY:
6364
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
6465
case EXPECT_KEY:
66+
if (rd_len == 0 && c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } // empty object, eg. 'custom:{}'
6567
if (rd_len > 0 && c == ':') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_VAL_OR_OBJ; return TOK_KEY; }
6668
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE;
6769
if (rd_len < CONFIG_MAX_KEYLEN-1 && is_key_char(c)) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }

0 commit comments

Comments
 (0)