From a55660b795339178a98359d1837d0e10f2c812ea Mon Sep 17 00:00:00 2001 From: mikecarper Date: Fri, 28 Aug 2026 16:55:35 -0700 Subject: [PATCH] fix: reserve controls and expose ESP-NOW channel --- docs/terminal_chat_cli.md | 26 ++++++++------- examples/simple_secure_chat/main.cpp | 46 +++++++++++++++++++++++++- src/helpers/UserGpioPinPolicy.h | 12 +++++++ test/test_user_gpio/test_user_gpio.cpp | 14 ++++++++ 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/docs/terminal_chat_cli.md b/docs/terminal_chat_cli.md index 8d09640a..c0c0b1dd 100644 --- a/docs/terminal_chat_cli.md +++ b/docs/terminal_chat_cli.md @@ -201,18 +201,20 @@ selection is rejected. Such a WiFi/BLE/primary-ESP-NOW build therefore uses get espnow.channel set espnow.channel <1-13> ``` -On Full Companion builds whose primary mesh radio is ESP-NOW, this shows or -saves the single channel shared by ESP-NOW, the setup AP, and the -infrastructure-WiFi station. The default is channel 1. Use only a channel -permitted in your region and supported by the router. A saved change takes -effect only after reboot. Every primary ESP-NOW node and the router's 2.4 GHz -radio must use the same fixed channel; power saving does not permit separate -channels. On these ESP32 Full builds, `wifi.powersave max` is unavailable and -`min` is the coexistence setting. This channel setting is also distinct from -`bridge.channel`, which controls the optional ESP-NOW bridge transport. An -updated LoRa-primary ESP-NOW bridge can join this raw primary transport by -selecting the same channel and running `set bridge.format raw`; its default -`wrapped` format remains the bridge-to-bridge protocol. +On builds whose primary mesh radio is ESP-NOW, this shows or saves the primary +radio's channel. The default is channel 1. Use only a channel permitted in +your region, and restart or power-cycle the node after changing it. Every +primary ESP-NOW peer must use the same channel. + +On Full Companion, the setup AP and infrastructure-WiFi station also share +that channel, so the router's 2.4 GHz radio must remain fixed to it. Power +saving does not permit separate channels. `wifi.powersave max` is unavailable +on these ESP32 Full builds and `min` is the coexistence setting. This channel +setting is also distinct from `bridge.channel`, which controls the optional +ESP-NOW bridge transport. An updated LoRa-primary ESP-NOW bridge can join this +raw primary transport by selecting the same channel and running +`set bridge.format raw`; its default `wrapped` format remains the +bridge-to-bridge protocol. ``` get radio.rxgain diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 85d18df3..3e8b33f9 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -20,6 +20,9 @@ #if defined(ESP32_PLATFORM) #include #endif +#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW + #include +#endif #include #include @@ -503,7 +506,44 @@ public: } } else if (memcmp(command, "import ", 7) == 0) { importCard(&command[7]); - } else if (strcmp(command, "get usb.logging") == 0) { + } +#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW + else if (strcmp(command, "get espnow.channel") == 0) { + const uint8_t saved = mesh::wifi::loadConfiguredEspNowChannel(); + const uint8_t active = mesh::wifi::activeEspNowChannel(); + if (saved == active) { + Serial.printf("> %u (saved and active)\n", (unsigned)saved); + } else { + Serial.printf("> saved %u, active %u; reboot required\n", + (unsigned)saved, (unsigned)active); + } + } else if (strncmp(command, "set espnow.channel", 18) == 0 + && (command[18] == 0 || command[18] == ' ' + || command[18] == '\t')) { + const char* value = command + 18; + while (*value == ' ' || *value == '\t') value++; + uint8_t channel = 0; + if (!mesh::wifi::parseEspNowChannel(value, channel)) { + Serial.println("Error: ESP-NOW channel must be 1-13"); + } else { + // Snapshot the boot channel before writing NVS. The running radio + // remains on that channel until restart, keeping this node joined to + // its current peers for the rest of this boot. + const uint8_t active = mesh::wifi::activeEspNowChannel(); + if (!mesh::wifi::saveConfiguredEspNowChannel(channel)) { + Serial.println("Error: failed to save ESP-NOW channel"); + } else if (channel == active) { + Serial.printf("OK - ESP-NOW channel %u saved and active\n", + (unsigned)channel); + } else { + Serial.printf( + "OK - ESP-NOW channel %u saved; active %u; reboot required\n", + (unsigned)channel, (unsigned)active); + } + } + } +#endif + else if (strcmp(command, "get usb.logging") == 0) { #if MESH_USB_LOGGING_AVAILABLE Serial.printf(" usb.logging %s\n", mesh::isUsbLoggingEnabled() ? "on" : "off"); @@ -558,6 +598,10 @@ public: Serial.println(" set {name|lat|lon|freq|tx|af} {value}"); Serial.println(" get usb.logging"); Serial.println(" set usb.logging {on|off}"); +#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW + Serial.println(" get espnow.channel"); + Serial.println(" set espnow.channel <1-13>"); +#endif Serial.println(" card"); Serial.println(" import {biz card}"); Serial.println(" clock"); diff --git a/src/helpers/UserGpioPinPolicy.h b/src/helpers/UserGpioPinPolicy.h index 505708e3..d08b2a7e 100644 --- a/src/helpers/UserGpioPinPolicy.h +++ b/src/helpers/UserGpioPinPolicy.h @@ -60,6 +60,12 @@ inline bool isFirmwareReserved(uint8_t pin) { #ifdef P_LORA_PA_POWER P_LORA_PA_POWER, #endif +#ifdef P_LORA_LF_PA_POWER + P_LORA_LF_PA_POWER, +#endif +#ifdef P_LORA_HF_PA_POWER + P_LORA_HF_PA_POWER, +#endif #ifdef P_LORA_TX_LED P_LORA_TX_LED, #endif @@ -406,6 +412,12 @@ inline bool isFirmwareReserved(uint8_t pin) { #ifdef PIN_TFT_EN PIN_TFT_EN, #endif +#ifdef ST7789_CS + ST7789_CS, +#endif +#ifdef ST7789_RS + ST7789_RS, +#endif #ifdef PIN_TOUCH_RST PIN_TOUCH_RST, #endif diff --git a/test/test_user_gpio/test_user_gpio.cpp b/test/test_user_gpio/test_user_gpio.cpp index e20d28ee..f8135709 100644 --- a/test/test_user_gpio/test_user_gpio.cpp +++ b/test/test_user_gpio/test_user_gpio.cpp @@ -1,7 +1,13 @@ #include +#define P_LORA_LF_PA_POWER 4 +#define P_LORA_HF_PA_POWER 3 +#define ST7789_CS 16 +#define ST7789_RS 15 + #include #include +#include #include #include @@ -38,6 +44,14 @@ protected: } }; +TEST(UserGpioPinPolicyTest, ReservesRadioPaAndSt7789ControlAliases) { + EXPECT_TRUE(UserGpioPinPolicy::isFirmwareReserved(P_LORA_LF_PA_POWER)); + EXPECT_TRUE(UserGpioPinPolicy::isFirmwareReserved(P_LORA_HF_PA_POWER)); + EXPECT_TRUE(UserGpioPinPolicy::isFirmwareReserved(ST7789_CS)); + EXPECT_TRUE(UserGpioPinPolicy::isFirmwareReserved(ST7789_RS)); + EXPECT_FALSE(UserGpioPinPolicy::isFirmwareReserved(22)); +} + TEST_F(UserGpioTest, ListsOnlyBoardApprovedPins) { UserGpio gpio(board);