Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build_as_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
src_filter.append("+<helpers/stm32/*>")
elif item == "ESP32":
src_filter.append("+<helpers/esp32/*>")
src_filter.append("+<helpers/wifi/*>")
elif item == "NRF52_PLATFORM":
src_filter.append("+<helpers/nrf52/*>")
elif item == "RP2040_PLATFORM":
src_filter.append("+<helpers/rp2040/*>")
src_filter.append("+<helpers/wifi/*>")

# DISPLAY HANDLING
elif isinstance(item, tuple) and item[0] == "DISPLAY_CLASS":
Expand Down
57 changes: 57 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "MyMesh.h"

#include <Arduino.h> // needed for PlatformIO
#ifdef ENABLE_WIFI_INTERFACE
#include <WiFi.h>
#endif
#include <Mesh.h>

#define CMD_APP_START 1
Expand Down Expand Up @@ -932,6 +935,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();
Expand Down Expand Up @@ -2156,6 +2160,54 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char*
return true;
}

#ifdef ENABLE_WIFI_INTERFACE
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 (reboot to apply)");
return true;
}
if (strcmp(command, "get wifi.ssid") == 0) { // no 'get wifi.pwd', by design
sprintf(reply, "> %s", _prefs.getWifiSSID()[0] ? _prefs.getWifiSSID() : "(not set)");
return true;
}
if (memcmp(command, "set wifi.enabled ", 17) == 0) {
_prefs.wifi_enabled = atoi(&command[17]) ? 1 : 0;
savePrefs();
sprintf(reply, "> wifi.enabled is now %d (reboot to apply)", _prefs.wifi_enabled);
return true;
}
if (strcmp(command, "get wifi.enabled") == 0) {
sprintf(reply, "> %d", _prefs.wifi_enabled);
return true;
}
if (strcmp(command, "get wifi.status") == 0) {
strcpy(reply, WiFi.status() == WL_CONNECTED ? "> connected" : "> disconnected");
return true;
}
if (strcmp(command, "get wifi.ip") == 0) {
if (WiFi.status() == WL_CONNECTED) {
sprintf(reply, "> %s", WiFi.localIP().toString().c_str());
} else {
strcpy(reply, "> (not connected)");
}
return true;
}
#endif

if (strcmp(command, "board") == 0) {
strcpy(reply, board.getManufacturerName());
return true;
Expand Down Expand Up @@ -2386,6 +2438,11 @@ void MyMesh::loop() {
checkCLIRescueCmd();
} else {
checkSerialInterface();
#if defined(ENABLE_WIFI_INTERFACE) && 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?
Expand Down
34 changes: 33 additions & 1 deletion examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ class NodePrefs : public ConfigSerializer { // persisted to file
char default_scope_name[31];
uint8_t default_scope_key[16];
int8_t tz_offset = 0;
#ifdef ENABLE_WIFI_INTERFACE
#ifndef WIFI_SSID
#define WIFI_SSID ""
#endif
char wifi_ssid[33] = {0}; // if empty, the compile-time WIFI_SSID is used
char wifi_pwd[64] = {0};
uint8_t wifi_enabled = 1; // enabled by default to allow wifi only builds to work. wifi won't be started if ssid is empty
// use ssid from prefs, or fallback to ssid from build flags
const char* getWifiSSID() const { return wifi_ssid[0] ? wifi_ssid : WIFI_SSID; }
#endif

private:
class RadioPrefs : public CommonRadioPrefs {
Expand Down Expand Up @@ -160,6 +170,21 @@ class NodePrefs : public ConfigSerializer { // persisted to file

DynamicConfigSerializer custom;

#ifdef ENABLE_WIFI_INTERFACE
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));
def("enabled", _parent->wifi_enabled);
}
public:
WiFiPrefs(NodePrefs* parent) : _parent(parent) { }
};
WiFiPrefs wifi;
#endif

protected:
void structure() override {
def("name", node_name, sizeof(node_name));
Expand All @@ -172,9 +197,16 @@ class NodePrefs : public ConfigSerializer { // persisted to file
def("repeat", repeat);
def("comp", companion);
def("custom", custom);
#ifdef ENABLE_WIFI_INTERFACE
def("wifi", wifi);
#endif
}
public:
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio) {
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio)
#ifdef ENABLE_WIFI_INTERFACE
, wifi(this)
#endif
{
node_name[0] = 0;
default_scope_name[0] = 0;
memset(default_scope_key, 0, sizeof(default_scope_key));
Expand Down
123 changes: 95 additions & 28 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,38 @@ MultiSerialInterface interface_manager;
// include nrf52 bluetooth interface
#include <helpers/nrf52/SerialBLEInterface.h>
SerialBLEInterface bluetooth_interface;
#elif defined(RP2040_PLATFORM)
// include rp2040 (Pico W / CYW43) bluetooth interface
#include <helpers/rp2040/SerialBLEInterface.h>
SerialBLEInterface bluetooth_interface;
#else
#error "SerialBLEInterface is not defined for this platform"
#endif
#endif

// include wifi interface
#ifdef WIFI_SSID
#ifdef ENABLE_WIFI_INTERFACE
#ifndef WIFI_SSID
#define WIFI_SSID ""
#endif
#ifndef WIFI_PWD
#define WIFI_PWD ""
#endif
#ifndef TCP_PORT
#define TCP_PORT 5000
#endif
#ifdef ESP32
// include esp32 wifi interface
#include <helpers/esp32/SerialWifiInterface.h>
#ifndef WIFI_RETRY_INTERVAL
#if defined(RP2040_PLATFORM)
#define WIFI_RETRY_INTERVAL 30000 // each attempt blocks loop(), so retry less often
#else
#define WIFI_RETRY_INTERVAL 10000 // millis between reconnect attempts
#endif
#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 <helpers/wifi/SerialWifiInterface.h>
SerialWifiInterface wifi_interface;
#else
#error "SerialWifiInterface is not defined for this platform"
Expand Down Expand Up @@ -108,9 +127,13 @@ void halt() {
}

/* WIFI RECONNECT TRACKERS */
#if defined(ESP32) && defined(WIFI_SSID)
#ifdef ENABLE_WIFI_INTERFACE
bool wifi_needs_reconnect = false;
unsigned long last_wifi_reconnect_attempt = 0;
char wifi_ssid[33] = WIFI_SSID; // replaced by stored prefs at boot, if set
char wifi_pwd[64] = WIFI_PWD;
bool wifi_was_connected = false;
bool wifi_enabled = false; // set at boot from prefs; false also when the effective SSID is blank
#endif

void setup() {
Expand Down Expand Up @@ -191,23 +214,46 @@ void setup() {
#endif

// add wifi interface
#ifdef WIFI_SSID
board.setInhibitSleep(true); // prevent sleep when WiFi is active
WiFi.setAutoReconnect(true);

WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
wifi_needs_reconnect = true;
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
wifi_needs_reconnect = false;
}
});
#ifdef ENABLE_WIFI_INTERFACE
// use wifi ssid and password from prefs if ssid is not empty, otherwise use the build flag defaults
if (the_mesh.getNodePrefs()->wifi_ssid[0]) {
strcpy(wifi_ssid, the_mesh.getNodePrefs()->wifi_ssid);
strcpy(wifi_pwd, the_mesh.getNodePrefs()->wifi_pwd);
}
// only start wifi if enabled and ssid is not empty
wifi_enabled = the_mesh.getNodePrefs()->wifi_enabled;
if (wifi_enabled && wifi_ssid[0]) {
#if defined(ESP32)
board.setInhibitSleep(true); // prevent sleep when WiFi is active
WiFi.setAutoReconnect(true);

WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
wifi_needs_reconnect = true;
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
wifi_needs_reconnect = false;
}
});
#endif

WiFi.begin(WIFI_SSID, WIFI_PWD);
wifi_interface.begin(TCP_PORT);
interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface);
WIFI_DEBUG_PRINTLN("connecting to %s", wifi_ssid);

#if defined(RP2040_PLATFORM)
// 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.
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);
} else {
WIFI_DEBUG_PRINTLN("wifi disabled");
}
#endif

// add usb interface
Expand Down Expand Up @@ -262,13 +308,34 @@ 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();
last_wifi_reconnect_attempt = millis();
#ifdef ENABLE_WIFI_INTERFACE
if (wifi_enabled) {
// 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
}
4 changes: 2 additions & 2 deletions examples/companion_radio/ui-new/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "../MyMesh.h"
#include "target.h"
#include <time.h>
#ifdef WIFI_SSID
#ifdef ENABLE_WIFI_INTERFACE
#include <WiFi.h>
#endif

Expand Down Expand Up @@ -260,7 +260,7 @@ class HomeScreen : public UIScreen {
sprintf(tmp, "%02d/%02d/%d", dt.day(), dt.month(), dt.year());
display.drawTextCentered(display.width() / 2, 80, tmp);
#endif
#ifdef WIFI_SSID
#ifdef ENABLE_WIFI_INTERFACE
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
display.setTextSize(1);
Expand Down
4 changes: 2 additions & 2 deletions examples/companion_radio/ui-tiny/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "target.h"
#include "u8g2_icons.h"

#ifdef WIFI_SSID
#ifdef ENABLE_WIFI_INTERFACE
#include <WiFi.h>
#endif

Expand Down Expand Up @@ -173,7 +173,7 @@ class HomeScreen : public UIScreen {
display.setCursor(0, 19);
display.print(tmp);

#ifdef WIFI_SSID
#ifdef ENABLE_WIFI_INTERFACE
IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
display.setTextSize(1);
Expand Down
2 changes: 2 additions & 0 deletions src/helpers/ConfigSerializer.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "ConfigSerializer.h"
#include <stdlib.h> // atoi/atol/atof (Arduino.h pulls this in on-device, native builds do not)

bool ConfigSerializer::saveSerial(Stream& s) {
Context context(&s, OP::WRITE);
Expand Down Expand Up @@ -62,6 +63,7 @@ int ConfigSerializer::Context::readNext() {
case EXPECT_COMMA_OR_KEY:
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
case EXPECT_KEY:
if (rd_len == 0 && c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } // empty object, eg. 'custom:{}'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requesting review of this from @ripplebiz

if (rd_len > 0 && c == ':') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_VAL_OR_OBJ; return TOK_KEY; }
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE;
if (rd_len < CONFIG_MAX_KEYLEN-1 && is_key_char(c)) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
Expand Down
Loading
Loading