diff --git a/docs/cli_command_availability.md b/docs/cli_command_availability.md index b87f70c3..667510b4 100644 --- a/docs/cli_command_availability.md +++ b/docs/cli_command_availability.md @@ -71,7 +71,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without | Logging | [`get/set usb.logging`](cli_commands.md#control-live-usb-logging) | Logging artifacts; session-only live USB output gate | No | Yes | No | | Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | | Radio | [`get tx`; `set tx `](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | -| Radio | [`tempradio ...`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | +| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | | Radio | [`get/set/del radioat`; `get/set/del tempradioat`](cli_commands.md#schedule-radio-parameter-changes) | Full parser | Yes | Yes | Yes | | Radio | [`get freq`; `set freq `](cli_commands.md#view-or-change-this-nodes-frequency) | `set` is local serial only | Yes | Yes | Yes | | Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature | @@ -229,7 +229,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without | Logging | [`get/set usb.logging`](cli_commands.md#control-live-usb-logging) | Logging artifacts; session-only live USB output gate | No | Yes | No | No | Yes | | Radio | [`get radio`; `set radio ...`](cli_commands.md#view-or-change-this-nodes-radio-parameters) | All text CLI roles | Yes | Yes | Yes | Yes | Yes | | Radio | [`get tx`; `set tx `](cli_commands.md#view-or-change-this-nodes-transmit-power) | Board TX-power limits apply | Yes | Yes | Yes | Yes | Yes | -| Radio | [`tempradio ...`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes | +| Radio | [`tempradio ...`; `normalradio`](cli_commands.md#change-the-radio-parameters-for-a-set-duration) | Full parser | Yes | Yes | Yes | Yes | Yes | | Radio | [`get/set/del radioat`; `get/set/del tempradioat`](cli_commands.md#schedule-radio-parameter-changes) | Full parser | Yes | Yes | Yes | Yes | Yes | | Radio | [`get freq`; `set freq `](cli_commands.md#view-or-change-this-nodes-frequency) | `set` is local serial only | Yes | Yes | Yes | Yes | Yes | | Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature | Feature | Feature | diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 1f3a6042..7455aafb 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -576,6 +576,7 @@ It also does not change the node-storage capture controlled by `log start` and #### Change the radio parameters for a set duration **Usage:** - `tempradio ,,,,` +- `normalradio` **Parameters:** - `freq`: Frequency in MHz (150-2500) @@ -584,7 +585,11 @@ It also does not change the node-storage capture controlled by `log start` and - `cr`: Coding rate (5-8) - `timeout_mins`: Duration in minutes (must be > 0) -**Note:** This is not saved to preferences and will clear on reboot +**Notes:** +- `tempradio` is not saved to preferences and clears on reboot. +- `normalradio` cancels pending and active temporary-radio windows, then + restores the saved radio tuple after its CLI reply has drained on the + current channel. Permanent `radioat` entries are not removed. --- diff --git a/docs/ota_easy.md b/docs/ota_easy.md index 7318fe30..a3053038 100644 --- a/docs/ota_easy.md +++ b/docs/ota_easy.md @@ -36,8 +36,9 @@ The fourth value is the transmit coding rate. This guide uses CR5, but the parti do not need to match. `tempradio` is not saved and the node returns to its normal radio settings when the window ends or the node -reboots. This frequency is intended for North American configurations. Confirm that it is permitted in your -location and change it when necessary. +reboots. Current full-parser firmware also accepts `normalradio`, which cancels the temporary window and +restores the saved tuple after replying on the current channel. This frequency is intended for North American +configurations. Confirm that it is permitted in your location and change it when necessary. ## Before you start diff --git a/docs/rak3401_mota_chain.md b/docs/rak3401_mota_chain.md index 55a5252b..1ebf627c 100644 --- a/docs/rak3401_mota_chain.md +++ b/docs/rak3401_mota_chain.md @@ -287,9 +287,15 @@ different problems. After step 9 is proven, restore each saved destination and relay value exactly, including `rxdelay`, RXPS, CPU power saving, `af`, `ota config hops`, and relay -timing. Ordinary repeater firmware does not implement `normalradio`. While the -node is still reachable on the temporary channel, shorten its lease to one -minute with the same tuple: +timing. Current full-parser repeater firmware accepts `normalradio` and restores +the saved tuple after replying on the temporary channel: + +```text +normalradio +``` + +The historical endpoint in this pinned bundle predates that command. If it +replies `Unknown command`, shorten its lease to one minute with the same tuple: ```text tempradio 909.950,500,5,5,1 diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 3c589282..584c15b3 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -4239,6 +4239,21 @@ void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, refreshScheduledRadioState(); } +bool MyMesh::scheduleNormalRadio() { + // Cancel every pending/active temporary entry, but leave permanent radioat + // changes intact. The saved apply waits for the CLI reply to leave the + // outbound queue before changing modulation parameters. + for (int i = 0; i < MAX_SCHEDULED_RADIO_SETTINGS; i++) { + if (!scheduled_radio_settings[i].temporary) continue; + scheduled_radio_settings[i].active = false; + scheduled_radio_settings[i].started = false; + } + temp_radio_handoff_pending = false; + refreshScheduledRadioState(); + queueSavedRadioApply(); + return true; +} + bool MyMesh::formatFileSystem() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return InternalFS.format(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8c4686f8..59446b56 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -869,6 +869,7 @@ public: // CommonCLICallbacks void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; + bool scheduleNormalRadio() override; #if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL) uint32_t getUserGpioRequestSource() const override { return _gpio_reply_tracker.requestSource(); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index e6395e31..16e8d4da 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -1443,6 +1443,16 @@ void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, revert_radio_at = futureMillis(2000 + timeout_mins * 60 * 1000); // schedule when to revert radio params } +bool MyMesh::scheduleNormalRadio() { + // A one-tick deadline cancels a not-yet-applied window or restores an active + // one as soon as the command reply has drained from the outbound queue. + set_radio_at = 0; + revert_radio_at = futureMillis(1); + radio_apply_retry_at = 0; + radio_apply_failures = 0; + return true; +} + bool MyMesh::formatFileSystem() { #if defined(NRF52_PLATFORM) return InternalFS.format(); diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index e643de3e..a700d797 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -371,6 +371,7 @@ public: // CommonCLICallbacks void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; + bool scheduleNormalRadio() override; #if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL) uint32_t getUserGpioRequestSource() const override { return _gpio_reply_tracker.requestSource(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 99307562..726fd86a 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -1062,6 +1062,16 @@ void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params } +bool SensorMesh::scheduleNormalRadio() { + // Keep the reply on the current tuple, then let the existing safe restore + // path wait for an idle outbound queue before touching the radio. + set_radio_at = 0; + revert_radio_at = futureMillis(1); + radio_apply_retry_at = 0; + radio_apply_failures = 0; + return true; +} + void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 78c6de18..3632887c 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -93,6 +93,7 @@ public: void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override { } void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; + bool scheduleNormalRadio() override; #if defined(ESP32_PLATFORM) || defined(USER_GPIO_CONTROL) uint32_t getUserGpioRequestSource() const override { return _gpio_reply_tracker.requestSource(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 16d12e45..f74dbf8f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -2369,6 +2369,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } else { strcpy(reply, "ERR: bad pubkey"); } + } else if (strcmp(command, "normalradio") == 0) { + if (_callbacks->scheduleNormalRadio()) { + strcpy(reply, "OK - normal radio restore scheduled"); + } else { + strcpy(reply, "Error: unsupported"); + } } else if (memcmp(command, "tempradio ", 10) == 0) { strcpy(tmp, &command[10]); const char *parts[5]; diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index e50162d6..bf248fca 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -442,6 +442,9 @@ public: virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; + // Cancel pending/active temporary-radio windows and restore the saved tuple + // after the command reply has drained on the current channel. + virtual bool scheduleNormalRadio() { return false; } #if defined(ENABLE_OTA) virtual bool isTempRadioActive() const { return false; } #endif