From 7d09210b9c75febf2ff367b2c56cace02d5ab4ae Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:52:32 +0200 Subject: [PATCH] feat(admin): add confirmed "Start OTA" action for remote nodes start ota was already sendable via Admin's Custom-command row (and CLI-reachable directly), but had no dedicated menu entry. Adds a row to the Actions tab that confirms first (Start/Cancel, defaulting to Cancel) before sending -- unlike Reboot, OTA parks the remote in BLE DFU mode for the duration of the update, disruptive enough to warrant the extra step. Co-Authored-By: Claude Sonnet 5 --- .../tools_screen/tools_screen.md | 4 +-- examples/companion_radio/ui-new/AdminScreen.h | 36 +++++++++++++++++++ release-notes.md | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/solo_features/tools_screen/tools_screen.md b/docs/solo_features/tools_screen/tools_screen.md index a5d032bb..1b8dcf49 100644 --- a/docs/solo_features/tools_screen/tools_screen.md +++ b/docs/solo_features/tools_screen/tools_screen.md @@ -568,13 +568,13 @@ Send commands to a **repeater/room server you have admin permission on** — the | **System** | Name, Owner info, Admin password | | **Radio** | Frequency, Bandwidth, Spreading factor, Coding rate, TX power | | **Routing** | Repeat, Advert interval, Flood advert interval, Max hops | - | **Actions** | Send advert, Send zero-hop advert, Sync clock, Reboot, **Custom command...** | + | **Actions** | Send advert, Send zero-hop advert, Sync clock, Reboot, Start OTA, **Custom command...** | **Enter** on a row does one of four things, depending on the field: - **Name / Owner info** first **fetch** the node's current value, then open the keyboard **pre-filled** with it to edit — submitting sends the change. If the fetch fails or times out, the keyboard still opens (blank), so the value can be set blind. - **Radio and Routing rows** are typed, not free text: **Repeat** is an ON/OFF toggle; **Advert interval / Flood advert interval / Max hops / TX power** are number steppers (**LEFT/RIGHT** to adjust, within that field's valid range); **Frequency** uses the same digit-by-digit cursor editor as Settings' own Radio screen (**LEFT/RIGHT** moves between digits, **UP/DOWN** changes the selected one); **Bandwidth / Spreading factor / Coding rate** step through their valid discrete LoRa values with **LEFT/RIGHT**. All four Radio-tuple fields (Frequency/Bandwidth/SF/Coding rate) fetch and re-send the same underlying `radio` value together — editing any one of them still only overwrites that one, the other three round-trip unchanged. **Enter** sends the change; **Cancel** discards it and returns to the row list without sending anything. - **Admin password** has no fetch (there's no way to read a password back) — it opens straight to a blank keyboard. - - **Actions** (Reboot, Send advert, …) send immediately, no editing step. + - **Actions** (Reboot, Send advert, …) send immediately, no editing step — except **Start OTA**, which asks **Start / Cancel** first (defaulting to Cancel): it puts the remote into BLE DFU mode for the duration of the update, far more disruptive than a quick reboot. - **Custom command...** (last row of Actions) opens the same free-text entry for anything not covered above — up to 160 characters, see the linked reference for the full grammar. The keyboard's **{}** key doubles as command completion here: it lists commands matching whatever's typed since the last space (narrowing as you type), and picking one completes that word instead of just inserting after it. 4. **Read the reply** — the text reply opens in a scrollable view (**UP/DOWN** to scroll, **Cancel/Enter** to go back to the category tabs). diff --git a/examples/companion_radio/ui-new/AdminScreen.h b/examples/companion_radio/ui-new/AdminScreen.h index b89d5d86..4bdbe204 100644 --- a/examples/companion_radio/ui-new/AdminScreen.h +++ b/examples/companion_radio/ui-new/AdminScreen.h @@ -25,6 +25,7 @@ #include "FullscreenMsgView.h" #include "TabBar.h" +#include "PopupMenu.h" // "Start OTA" confirmation #include "RadioParamsEditor.h" // DigitEditor + stepSF/stepBW/stepCR -- same widgets Settings/Repeater use locally #include // PERM_ACL_ADMIN / PERM_ACL_ROLE_MASK @@ -137,6 +138,15 @@ class AdminScreen : public UIScreen { FullscreenMsgView _reply_view; char _reply_text[200] = ""; + // "Start OTA" confirmation -- pulls the remote out of the mesh into BLE DFU + // mode for the duration of the update, far more disruptive than the other + // one-shot actions on this tab, so unlike Reboot it doesn't fire on a bare + // Enter. Only this one action needs a confirm today, so it's special-cased + // in activateField() by command string rather than adding a generic + // needs_confirm flag to every AdminField literal below. + PopupMenu _confirm; + const AdminField* _pending_confirm_field = nullptr; + KeyboardWidget& kb() { return _task->keyboard(); } bool isAdminOk(const uint8_t* pub_key) const { @@ -226,6 +236,14 @@ class AdminScreen : public UIScreen { if (f.get_cmd == nullptr && f.set_prefix == nullptr) { // Custom command... openValueKb(_cmd_text, true); } else if (f.set_prefix == nullptr) { // Action + if (!strcmp(f.get_cmd, "start ota")) { // see _confirm's comment + _pending_confirm_field = &f; + _confirm.begin("Start OTA update?", 2); + _confirm.addItem("Start"); + _confirm.addItem("Cancel"); + _confirm.setSelected(1); // default highlight = Cancel + return; + } strncpy(_cmd_text, f.get_cmd, sizeof(_cmd_text) - 1); _cmd_text[sizeof(_cmd_text) - 1] = '\0'; sendCommand(); @@ -333,6 +351,8 @@ public: _value_editing = false; _login_waiting = false; _admin_ok = false; + _confirm.active = false; + _pending_confirm_field = nullptr; } // Canonical entry for a specific target -- called by UITask::openAdminFor(), @@ -519,6 +539,7 @@ public: } } }); + if (_confirm.active) { _confirm.render(display); return 50; } return _value_editing ? 50 : 2000; } @@ -552,6 +573,20 @@ public: } if (_phase == COMMAND) { + if (_confirm.active) { + auto res = _confirm.handleInput(c); + if (res == PopupMenu::SELECTED) { + if (_confirm.selectedIndex() == 0 && _pending_confirm_field) { + strncpy(_cmd_text, _pending_confirm_field->get_cmd, sizeof(_cmd_text) - 1); + _cmd_text[sizeof(_cmd_text) - 1] = '\0'; + sendCommand(); + } + _pending_confirm_field = nullptr; + } else if (res == PopupMenu::CANCELLED) { + _pending_confirm_field = nullptr; + } + return true; + } if (_kb_active) { auto r = kb().handleInput(c); if (r == KeyboardWidget::DONE) { @@ -670,6 +705,7 @@ const AdminScreen::AdminField AdminScreen::ACTION_FIELDS[] = { { "Send zero-hop advert", "advert.zerohop", nullptr }, { "Sync clock", "clock sync", nullptr }, { "Reboot", "reboot", nullptr }, // disruptive + no confirm: keep off the default row + { "Start OTA", "start ota", nullptr }, // confirmed first -- see _confirm's comment { "Custom command...", nullptr, nullptr }, }; diff --git a/release-notes.md b/release-notes.md index 6a007570..4cf5da05 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ - **Optional magnetic "flip cover" screen lock**, for anyone who wants to wire a Hall-effect or reed sensor to a free GPIO — no board ships one built in. Set `PIN_HALL_SENSOR` as a build flag on your own env and closing the cover locks and blanks the screen instantly, opening it unlocks and wakes it — no combo, independent of Auto-lock. See [Screen Lock](docs/solo_features/screen_lock/screen_lock.md#magnetic-cover-hall-sensor). - **Experimental: solo build for ProMicro (nRF52840)**, with CardKB support sharing the board's primary I2C bus (no free pins for a second one on this board). Contributed by @tchellow — thanks! - **New [Build Flags](docs/solo_features/build_flags.md) reference** — every optional `-D` flag a solo build understands (GPIO, CardKB/joystick, Hall-sensor cover lock, buzzer/vibration, GPS switch, display/battery tuning) in one place. +- **Tools › Admin gains a "Start OTA" action** for a repeater/room server you're logged into — sends the same `start ota` CLI command the Custom-command row already reached, now with its own menu row and a confirm ("Start" defaulting to "Cancel" first) since it puts the remote into BLE DFU mode for the duration of the update. ### Fixes