From 55203c2ddd8cb601b4483b502309765969931e54 Mon Sep 17 00:00:00 2001 From: tchellow Date: Sun, 23 Aug 2026 21:44:17 -0300 Subject: [PATCH 01/11] Add CardKB support for ProMicro via shared I2C bus --- .../companion_radio/ui-new/SettingsScreen.h | 35 ++-------------- examples/companion_radio/ui-new/UITask.cpp | 25 ++++++----- examples/companion_radio/ui-new/UITask.h | 19 ++++++--- variants/promicro/platformio.ini | 41 ++++++++++++++++++- 4 files changed, 72 insertions(+), 48 deletions(-) diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 913749e0..3271ffc3 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -61,7 +61,6 @@ class SettingsScreen : public UIScreen { CUSTOM_FREQ, CUSTOM_SF, CUSTOM_BW, CUSTOM_CR, POWER_SAVE, TX_APC, - SCOPE_NAME, // System section SECTION_SYSTEM, DEVICE_NAME, @@ -77,7 +76,7 @@ class SettingsScreen : public UIScreen { KEYBOARD_TYPE, KEYBOARD_MAIN_ALPHABET, KEYBOARD_ALPHABET, -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if defined(CARDKB_I2C) KEYBOARD_CARDKB_COMPACT, #endif // Contacts section @@ -556,11 +555,6 @@ class SettingsScreen : public UIScreen { // Suppressed (and locked) while repeating — a repeater holds full TX power. if (p && p->client_repeat) display.print("--"); else display.print((p && p->tx_apc) ? "ON" : "OFF"); - } else if (item == SCOPE_NAME) { - display.print("Scope"); - int vx = valCol(display); - display.drawTextEllipsized(vx, y, display.width() - vx - _reserve, - (p && p->default_scope_name[0]) ? p->default_scope_name : "(none)"); #if AUTO_OFF_MILLIS > 0 } else if (item == AUTO_OFF) { display.print("AutoOff"); @@ -611,7 +605,7 @@ class SettingsScreen : public UIScreen { display.print("Additional"); display.setCursor(valCol(display), y); display.print(NodePrefs::keyboardAlphabetLabel(p ? p->keyboard_alt_alphabet : 0)); -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if defined(CARDKB_I2C) } else if (item == KEYBOARD_CARDKB_COMPACT) { display.print("Ext. KB"); display.setCursor(valCol(display), y); @@ -684,7 +678,6 @@ class SettingsScreen : public UIScreen { // Keyboard state for editing message slots int _edit_slot = -1; // -1 = not editing, 0..9 = slot being edited bool _edit_name = false; // editing DEVICE_NAME via the keyboard - bool _edit_scope = false; // editing SCOPE_NAME via the keyboard KeyboardWidget* _kb; // Radio preset picker — names are too long for the value column, so Enter on @@ -708,7 +701,6 @@ public: void onShow() override { _dirty = false; _edit_name = false; - _edit_scope = false; resetList(); _editor.freq.active = false; } @@ -716,7 +708,7 @@ public: int render(DisplayDriver& display) override { display.setTextSize(1); - if (_edit_slot >= 0 || _edit_name || _edit_scope || _picker.saving) { + if (_edit_slot >= 0 || _edit_name || _picker.saving) { return _kb->render(display); } @@ -779,19 +771,6 @@ public: return true; } - // Keyboard editing mode for the scope name - if (_edit_scope) { - auto res = _kb->handleInput(c); - if (res == KeyboardWidget::DONE) { - the_mesh.setPrimaryScope(_kb->buf); - _dirty = true; - _edit_scope = false; - } else if (res == KeyboardWidget::CANCELLED) { - _edit_scope = false; - } - return true; - } - // Digit-by-digit Freq editor if (_editor.active()) { if (_editor.handleFreqInput(c) && p) { _task->applyRadioParams(); _dirty = true; } @@ -987,12 +966,6 @@ public: _kb->clearPlaceholders(); // a device name is literal, not a message return true; } - if (_selected == SCOPE_NAME && p && enter) { - _edit_scope = true; - _kb->begin(p->default_scope_name, (int)sizeof(p->default_scope_name) - 1); - _kb->clearPlaceholders(); // a scope name is literal, not a message - return true; - } if (_selected == REBOOT && enter) { _task->savePrefsIfDirty(_dirty); // don't lose pending edits across the restart _task->showAlert("Rebooting...", 800); @@ -1020,7 +993,7 @@ public: _dirty = true; return true; } -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if defined(CARDKB_I2C) if (_selected == KEYBOARD_CARDKB_COMPACT && p && (left || right || enter)) { p->keyboard_cardkb_compact ^= 1; _dirty = true; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 01807d86..16b92980 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1397,11 +1397,14 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no uint32_t aoff = autoOffMillis(); _auto_off = millis() + (aoff > 0 ? aoff : AUTO_OFF_MILLIS); -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) - // Wire1 is already brought up by sensors.begin() (EnvironmentSensorManager), - // which runs before this -- just probe for a CardKB sitting on it. - Wire1.beginTransmission(0x5F); - _has_cardkb = (Wire1.endTransmission() == 0); +#if defined(CARDKB_I2C) + // On the ENV_PIN_SDA/SCL path, Wire1 is already brought up by + // sensors.begin() (EnvironmentSensorManager), which runs before this. On + // the CARDKB_USE_PRIMARY_WIRE path, Wire is already brought up by the + // board's own begin() (display/RTC), also before this -- either way, just + // probe for a CardKB sitting on the bus. + CARDKB_I2C.beginTransmission(0x5F); + _has_cardkb = (CARDKB_I2C.endTransmission() == 0); #endif #if defined(PIN_USER_BTN) @@ -2104,7 +2107,7 @@ bool UITask::dequeueKey(char& c) { return true; } -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if defined(CARDKB_I2C) // CardKB's "fn" column (key_map in M5Stack's unit_CardKB.cpp): Fn+ sends 0x80 + that key's row index, entirely disjoint from every other // code this UI recognises. Indexed by (raw - 0x80); non-letter slots (digits, @@ -2145,7 +2148,7 @@ static const char CARDKB_FN_BASE[48] = { // once), so _cardkb_last_raw debounces it into one press per physical // keypress, same as a MomentaryButton's CLICK event. void UITask::pollCardKB() { -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if defined(CARDKB_I2C) if (!_has_cardkb) return; // No artificial throttle: unlike a MomentaryButton (BUTTON_USE_INTERRUPTS // latches every edge in an ISR ring buffer, so it survives a blocking e-ink @@ -2156,9 +2159,9 @@ void UITask::pollCardKB() { // Polling every loop() iteration (same as a digital button's check(), which // has no throttle either) just shrinks that miss window down to exactly the // render() duration instead of render()+30ms. - Wire1.requestFrom(0x5F, 1); - if (!Wire1.available()) return; - uint8_t raw = Wire1.read(); + CARDKB_I2C.requestFrom(0x5F, 1); + if (!CARDKB_I2C.available()) return; + uint8_t raw = CARDKB_I2C.read(); if (raw == _cardkb_last_raw) return; // still held (or still released) -- no new edge _cardkb_last_raw = raw; if (raw == 0) return; // key just released, nothing to enqueue @@ -2480,7 +2483,7 @@ void UITask::loop() { } // Hint popup at bottom (like alert style) _display->setTextSize(1); -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) +#if defined(CARDKB_I2C) const char* hint = _lock_seq_count == 0 ? (_has_cardkb ? "Back+3xEnter/Fn+Esc" : "Hold Back + 3xEnter") : _lock_seq_count == 1 ? "Enter x2 more..." : "Enter x1 more..."; #else diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 37c6ff44..f96e010d 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -193,12 +193,21 @@ class UITask : public AbstractUITask { void enqueueKey(char c); bool dequeueKey(char& c); - // Optional M5Stack CardKB (I2C keyboard, addr 0x5F) on the Grove/Wire1 bus - // -- reuses ENV_PIN_SDA/ENV_PIN_SCL (already brought up for - // EnvironmentSensorManager) as the "this board has a second I2C bus" gate, - // rather than a new board-specific pin define. No-op entirely on boards - // without that bus, or when nothing ACKs 0x5F at boot. + // Optional M5Stack CardKB (I2C keyboard, addr 0x5F). Two ways to reach it: + // - ENV_PIN_SDA/ENV_PIN_SCL defined -> CardKB rides the dedicated second + // I2C bus (Wire1) that EnvironmentSensorManager already brings up. + // - CARDKB_USE_PRIMARY_WIRE defined instead -> CardKB shares the board's + // main Wire bus (whatever the display/RTC already use), for boards + // where no second bus is free. Mutually exclusive with the above. + // Either way it's a no-op on boards with neither define, or when nothing + // ACKs 0x5F at boot. #if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) + #define CARDKB_I2C Wire1 +#elif defined(CARDKB_USE_PRIMARY_WIRE) + #define CARDKB_I2C Wire +#endif + +#if defined(CARDKB_I2C) bool _has_cardkb = false; // CardKB is level-triggered, not edge-triggered -- it keeps returning the // same byte for as long as the physical key is held, not just once. Track diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 90cb475f..01b0d537 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -176,4 +176,43 @@ lib_deps = [env:ProMicro_kiss_modem] extends = Promicro build_src_filter = ${Promicro.build_src_filter} - +<../examples/kiss_modem/> \ No newline at end of file + +<../examples/kiss_modem/> +; ============================================================ +; Solo build (UI completa on-device) com CardKB no barramento +; I2C ja usado pelo OLED (SDA=D8, SCL=D7 -- confirmado contra o +; esquematico oficial da fakeTec V5 rev.B). Requer o patch +; CARDKB_USE_PRIMARY_WIRE em UITask.h/.cpp e SettingsScreen.h. +; ============================================================ +[env:ProMicro_companion_solo_dual] +extends = Promicro +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +; -Ofast (default do core) infla o flash num build solo feature-completo; +; -Os cabe com folga. +build_unflags = -Ofast +build_flags = ${Promicro.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D DUAL_SERIAL=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SSD1306Display + -D FIRMWARE_SOLO_BUILD=1 + -D UI_SENSORS_PAGE=1 + -D ENABLE_SCREENSHOT + -Os + ; CardKB compartilhando o barramento do OLED/RTC (D8/D7) -- + ; NAO definir ENV_PIN_SDA/SCL aqui, colidiria com o LoRa (D13/D14). + -D CARDKB_USE_PRIMARY_WIRE=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Promicro.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${Promicro.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 From 13fb4a189f1263b663123b0978cf6cb86bb1742f Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:13:41 +0200 Subject: [PATCH 02/11] feat(ui): page the fullscreen message view in reading order LEFT went to the newer message and RIGHT to the older one, which reads backwards against the page metaphor the "<" / ">" markers set up. Swap it: LEFT turns back to the older message, RIGHT forward to the newer one, and the markers follow (they were keyed to the opposite flags). PREV/NEXT are named in message order, not screen order -- MessagesScreen's _hist_sel counts newest-first, so PREV is the older message -- so only the key mapping and the two marker conditions change; the caller side is untouched. Applies to both the DM and channel fullscreen views, which share handleInput(). AdminScreen's reply view treats every non-NONE result as "close", so it is unaffected. Docs and release notes updated to match. Co-Authored-By: Claude Opus 5 --- .../solo_features/message_screen/message_screen.md | 2 +- .../companion_radio/ui-new/FullscreenMsgView.h | 14 ++++++++++---- release-notes.md | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/solo_features/message_screen/message_screen.md b/docs/solo_features/message_screen/message_screen.md index dcb5a896..4e595e5c 100644 --- a/docs/solo_features/message_screen/message_screen.md +++ b/docs/solo_features/message_screen/message_screen.md @@ -74,7 +74,7 @@ Messages appear as chat bubbles sized to their content — **right**-anchored fo | :-----------------------: | :-----------------------: | | ![](./fullscreen_oled.png) | ![](./fullscreen_eink.png) | -Navigate between messages with **LEFT** (newer) and **RIGHT** (older). Long messages scroll with **UP/DOWN**. +Navigate between messages like pages in a book — **LEFT** goes back to the older message, **RIGHT** forward to the newer one. The `<` / `>` markers along the bottom edge show which directions still have a message. Long messages scroll with **UP/DOWN**. If the message is a reply addressed to someone (`@[nick]`), a **To: nick** bar is shown below the sender name and the body is displayed without the address prefix. diff --git a/examples/companion_radio/ui-new/FullscreenMsgView.h b/examples/companion_radio/ui-new/FullscreenMsgView.h index 0ee60238..9bd68656 100644 --- a/examples/companion_radio/ui-new/FullscreenMsgView.h +++ b/examples/companion_radio/ui-new/FullscreenMsgView.h @@ -157,12 +157,14 @@ struct FullscreenMsgView { display.print(s_wrap_lines[scroll + i]); } drawScrollIndicator(display, startY, visible * lineH, lcount, visible, scroll); + // Page markers read like a book: the older message is back to the left, + // the newer one forward to the right (see handleInput). const int nav_y = display.height() - lineH; - if (has_next) { + if (has_prev) { display.setCursor(0, nav_y); display.print("<"); } - if (has_prev) { + if (has_next) { display.setCursor(display.width() - cw, nav_y); display.print(">"); } @@ -172,8 +174,12 @@ struct FullscreenMsgView { Result handleInput(char c) { if (c == KEY_UP) { if (scroll > 0) scroll--; return NONE; } if (c == KEY_DOWN) { if (scroll < _max_scroll) scroll++; return NONE; } - if (keyIsPrev(c)) return NEXT; // page between messages (encoder too) - if (keyIsNext(c)) return PREV; + // Page between messages (encoder too), in reading order: left goes back to + // the older message, right forward to the newer one. PREV/NEXT are named in + // message order, and MessagesScreen's _hist_sel counts newest-first, so PREV + // (older) is what a left press means. + if (keyIsPrev(c)) return PREV; + if (keyIsNext(c)) return NEXT; if (c == KEY_CONTEXT_MENU) return REPLY; if (c == KEY_ENTER || c == KEY_CANCEL) return CLOSE; return NONE; diff --git a/release-notes.md b/release-notes.md index 769464c8..ec08da22 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ ### Fixes +- **Paging through the fullscreen message view now reads like a book** — **RIGHT** moves forward to the newer message, **LEFT** back to the older one, and the `<` / `>` markers along the bottom edge follow suit. It was the other way round before. - **Hardware CAD and RSSI interference-threshold detection were silently hardcoded off on companion_radio**, regardless of any setting. CAD now auto-enables whenever RX power-save (duty-cycle) is active — the noise floor isn't kept fresh during duty-cycle sleep, so a fresh hardware channel scan before TX is needed instead of the RSSI-threshold check. - **A long message could split a multi-byte UTF-8 character in half when truncated to fit the send frame** (e.g. a Polish/accented character landing right at the cut-off), delivering a mangled trailing byte to the app. Truncation now stops at the last complete character. - **Deleting the default "Public" channel didn't stick — it came back on every reboot.** A deleted channel is correctly left out of the saved channel file (to avoid rewriting it every save), but the Public channel was unconditionally re-added at every boot *before* the saved list was loaded, so its absence from the file never got a chance to matter. Now only seeded on a genuinely fresh device (no channel file yet) — once you've saved any channel state at all, your own list is authoritative. From 58e6bfcec5aca20205d7391a5265374865305097 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:48:33 +0200 Subject: [PATCH 03/11] feat(ui): optional magnetic flip-cover screen lock via user-wired Hall sensor No board in this repo has one built in, and no default pin is assumed anywhere -- whoever wires a Hall-effect or reed sensor to a free GPIO sets PIN_HALL_SENSOR (and HALL_ACTIVE_HIGH, for a sensor that pulls the pin high rather than low on presence) as a build_flag on their own env. Entirely opt-in and a no-op elsewhere, same pattern as PIN_GPIO1..4/ADC_MULTIPLIER/ CARDKB_ENABLE. Level-triggered polling (like pollCardKB()) rather than an edge interrupt -- a magnet held near the sensor reads the same way every tick, so the new pollHallSensor() only acts on the two transitions. Closing locks and blanks the display with no wake grace (the cover is physically over the screen, so there's nothing to show); opening unlocks and wakes it, with no key combo either way. Both are independent of the Auto-lock setting, which is a timeout, not a physical event. Co-Authored-By: Claude Sonnet 5 --- docs/solo_features/screen_lock/screen_lock.md | 11 +++++ examples/companion_radio/ui-new/UITask.cpp | 43 +++++++++++++++++++ examples/companion_radio/ui-new/UITask.h | 14 ++++++ release-notes.md | 1 + 4 files changed, 69 insertions(+) diff --git a/docs/solo_features/screen_lock/screen_lock.md b/docs/solo_features/screen_lock/screen_lock.md index 774760bb..299e85c2 100644 --- a/docs/solo_features/screen_lock/screen_lock.md +++ b/docs/solo_features/screen_lock/screen_lock.md @@ -51,3 +51,14 @@ The display turns off again automatically after 5 seconds of inactivity (or 2 se ### Auto-lock Enable **Auto-lock** in **Settings › Display** to lock the device automatically whenever the display turns off due to auto-off timeout. + +--- + +### Magnetic cover (Hall sensor) + +Optional, user-supplied hardware — no board in this repo has one built in. Wire a Hall-effect or reed sensor to any free GPIO, then set `PIN_HALL_SENSOR` (and `HALL_ACTIVE_HIGH=1`, if your module pulls the pin high rather than low when the magnet is present) as `build_flags` in your own env. No-op entirely unless `PIN_HALL_SENSOR` is defined. + +Fully autonomous, independent of Auto-lock and of any key combo: + +- **Magnet near (cover closed)** — locks and blanks the display immediately, no wake grace. +- **Magnet away (cover opened)** — unlocks and wakes the display right away. diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 01807d86..a6e6f9de 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1404,6 +1404,17 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no _has_cardkb = (Wire1.endTransmission() == 0); #endif +#if defined(PIN_HALL_SENSOR) + // Internal pull matches the default polarity: pulled up so an active-low + // sensor reads HIGH at rest, pulled down so an active-high one reads LOW at + // rest. Most reed/Hall breakouts are open-drain, active-low -- HALL_ACTIVE_HIGH + // is only for modules wired the other way. + pinMode(PIN_HALL_SENSOR, HALL_ACTIVE_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); + _hall_magnet_present = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH) + : (digitalRead(PIN_HALL_SENSOR) == LOW); + if (_hall_magnet_present) _locked = true; // booting with the cover already closed +#endif + #if defined(PIN_USER_BTN) user_btn.begin(); #endif @@ -2237,6 +2248,37 @@ void UITask::pollCardKB() { #endif } +// Level-triggered, like pollCardKB() -- a magnet held near the sensor reads the +// same way every tick, so this only acts on the two edges (closed/opened), not +// on every poll. Fully autonomous: closing locks and blanks the display +// immediately (no wake grace -- the cover is physically over the screen, so +// there's nothing to show), opening unlocks and wakes it, with no combo or +// keypress either way. Independent of Auto-lock (Settings > Display), which is +// a timeout-driven setting -- this is a direct physical event. +void UITask::pollHallSensor() { +#if defined(PIN_HALL_SENSOR) + bool present = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH) + : (digitalRead(PIN_HALL_SENSOR) == LOW); + if (present == _hall_magnet_present) return; + _hall_magnet_present = present; + + if (present) { // cover closed + _locked = true; + _lock_wake_until = 0; + if (_display) _display->turnOff(); +#ifdef PIN_LED + digitalWrite(PIN_LED, LOW); // same as the auto-off path -- one less thing lit under a closed cover +#endif + } else { // cover opened + _locked = false; + if (_display && !_display->isOn()) _display->turnOn(); + uint32_t aoff = autoOffMillis(); + if (aoff > 0) _auto_off = millis() + aoff; + } + _next_refresh = 0; +#endif +} + void UITask::loop() { // Background delivery: resend pending on-device DMs whose ACK timed out, and // finalise the ✗ marker — runs regardless of which screen is active. @@ -2335,6 +2377,7 @@ void UITask::loop() { } #endif pollCardKB(); + pollHallSensor(); #ifdef ENV_USE_TCA8418 { extern char tca8418_keypad_read(); // provided by the active variant diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 37c6ff44..a2ce2411 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -208,6 +208,20 @@ class UITask : public AbstractUITask { #endif void pollCardKB(); + // Optional magnetic "flip cover" lock: a Hall-effect or reed sensor wired to + // any free GPIO, closing (pulling active) when a magnet is near. Entirely + // user-supplied -- no board in this repo ships one, so PIN_HALL_SENSOR must + // be added as a build_flag by whoever wires the sensor up; no-op everywhere + // else. HALL_ACTIVE_HIGH overrides the default polarity for modules that + // pull the pin HIGH (instead of LOW) when the magnet is present. +#if defined(PIN_HALL_SENSOR) +#ifndef HALL_ACTIVE_HIGH + #define HALL_ACTIVE_HIGH 0 +#endif + bool _hall_magnet_present = false; +#endif + void pollHallSensor(); + void setCurrScreen(UIScreen* c); // Centred alert overlay (the showAlert() box). Wraps long text to up to diff --git a/release-notes.md b/release-notes.md index ec08da22..bc6ce158 100644 --- a/release-notes.md +++ b/release-notes.md @@ -3,6 +3,7 @@ ### What's new - **On-device community scope, and repeater-side scope filtering.** Settings › Radio gets a **Scope** field — type a region/community name (e.g. `pl`) and every device typing the same name derives the same shared tag, no key exchange needed; it's what your own DM/channel sends carry, previously only settable from a connected app. Tools › Repeater gains **Scope only** (only relay flood traffic matching your own scope, or one of the new **Extra scopes** below — a no-op until a scope is actually set, so it can't silently blackhole all forwarding) and **Extra scopes** (comma-separated additional regions to relay for, without changing what scope this device's own messages send under). +- **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). ### Fixes From a389a733d7d78113948de8456965258cece4a2bd Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:49:37 +0200 Subject: [PATCH 04/11] refactor(solo): move solo build configs into their own solo/ folder; one build per board; document all build flags Solo's `_solo_dual` env for each board lived inside variants//, mixed in with the shared upstream board configs (repeater, room server, plain companion) that every MeshCore fork carries. Split them out into their own top-level solo// folder -- each still `extends` the board base defined in its original variants//platformio.ini, just no longer interleaved with it file-wise. - platformio.ini: extra_configs picks up solo/*/platformio.ini alongside variants/*/platformio.ini. - build-solo-firmwares.yml: env-discovery grep now scans solo/ instead of variants/ (the release matrix is unchanged -- same 8 envs, same names). - pr-build-check.yml: solo/** added to the trigger paths. - GAT562 30S Mesh Kit: dropped the separate solo_ble env -- solo_dual is a strict superset (BLE still works, plus USB), and every other board only ever had one solo build to begin with. - GAT562 Mesh Watch13: renamed solo_ble -> solo_dual (added DUAL_SERIAL=1) to match. No comment anywhere recorded whether this board's USB data lines are actually broken out to a connector -- DUAL_SERIAL compiles and works over BLE regardless, so worst case the USB half goes unused. - Removed a dead AUTO_SHUTDOWN_MILLIVOLTS from the three solo envs that set it (Heltec V3/V4, T-Echo Lite+KeyShield): that macro is only ever read in the old ui-tiny UITask, never ui-new, which every solo build (these included) uses -- it did nothing on any of them. Left alone everywhere else it's set (non-solo companion envs elsewhere use ui-tiny/ui-orig, or the flag is legitimately read); out of scope here. - Added docs/solo_features/build_flags.md: every optional -D flag a solo build understands (GPIO, CardKB/joystick, Hall-sensor cover lock, buzzer/ vibration, GPS switch, display/battery tuning), verified against the code rather than the existing per-board comments, with what's already baked into every solo build kept separate from what's opt-in. - README: doc index + Building from source section link to the new page; path references to the moved solo files updated to solo//. All 8 solo_dual envs rebuilt clean after the move; native test suite (40 cases) unaffected. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/build-solo-firmwares.yml | 2 +- .github/workflows/pr-build-check.yml | 2 + README.md | 9 +- docs/solo_features/build_flags.md | 98 +++++++++++++++++++ docs/solo_features/external_keyboard.md | 7 +- platformio.ini | 1 + solo/gat562_30s_mesh_kit/platformio.ini | 52 ++++++++++ solo/gat562_mesh_watch13/platformio.ini | 50 ++++++++++ solo/heltec_v3/platformio.ini | 89 +++++++++++++++++ solo/heltec_v4/platformio.ini | 96 ++++++++++++++++++ solo/lilygo_techo_lite/platformio.ini | 43 ++++++++ solo/m5stack_cardputer_adv/platformio.ini | 34 +++++++ solo/wio-tracker-l1-eink/platformio.ini | 19 ++++ solo/wio-tracker-l1/platformio.ini | 19 ++++ variants/gat562_30s_mesh_kit/platformio.ini | 85 ---------------- variants/gat562_mesh_watch13/platformio.ini | 39 -------- variants/heltec_v3/platformio.ini | 85 ---------------- variants/heltec_v4/platformio.ini | 92 ----------------- variants/lilygo_techo_lite/platformio.ini | 39 -------- variants/m5stack_cardputer_adv/platformio.ini | 29 ------ variants/wio-tracker-l1-eink/platformio.ini | 14 --- variants/wio-tracker-l1/platformio.ini | 14 --- 22 files changed, 516 insertions(+), 402 deletions(-) create mode 100644 docs/solo_features/build_flags.md create mode 100644 solo/gat562_30s_mesh_kit/platformio.ini create mode 100644 solo/gat562_mesh_watch13/platformio.ini create mode 100644 solo/heltec_v3/platformio.ini create mode 100644 solo/heltec_v4/platformio.ini create mode 100644 solo/lilygo_techo_lite/platformio.ini create mode 100644 solo/m5stack_cardputer_adv/platformio.ini create mode 100644 solo/wio-tracker-l1-eink/platformio.ini create mode 100644 solo/wio-tracker-l1/platformio.ini diff --git a/.github/workflows/build-solo-firmwares.yml b/.github/workflows/build-solo-firmwares.yml index 3938bc01..5194facc 100644 --- a/.github/workflows/build-solo-firmwares.yml +++ b/.github/workflows/build-solo-firmwares.yml @@ -26,7 +26,7 @@ jobs: - name: List solo_dual envs id: list-envs run: | - envs=$(grep -rh '^\[env:' platformio.ini variants/ \ + envs=$(grep -rh '^\[env:' solo/ \ | sed 's/\[env://;s/\]//' \ | grep '_solo_dual$' \ | jq -R -s -c 'split("\n") | map(select(length > 0))') diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index 2ef40448..5d1b6e98 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -7,6 +7,7 @@ on: - 'src/**' - 'examples/**' - 'variants/**' + - 'solo/**' - 'platformio.ini' - '.github/workflows/pr-build-check.yml' push: @@ -15,6 +16,7 @@ on: - 'src/**' - 'examples/**' - 'variants/**' + - 'solo/**' - 'platformio.ini' - '.github/workflows/pr-build-check.yml' diff --git a/README.md b/README.md index 54e1bc7c..76d506f1 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Each joystick contact simply shorts its pin to GND — the firmware enables the Either device is enough on its own — unwired pins read as not-pressed, and a missing CardKB just isn't detected at boot. For **CardKB-only**, wire just SDA/SCL and set Settings › Keyboard › **Ext. KB = Compact**, which needs no joystick at all. -These are only defaults, set in the `[env:Heltec_v3_companion_solo_dual]` / `[env:heltec_v4_companion_solo_dual]` blocks in [`variants/heltec_v3/platformio.ini`](./variants/heltec_v3/platformio.ini) and [`variants/heltec_v4/platformio.ini`](./variants/heltec_v4/platformio.ini) — comments there list which GPIOs are already claimed. Full details in [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md). +These are only defaults, set in the `[env:Heltec_v3_companion_solo_dual]` / `[env:heltec_v4_companion_solo_dual]` blocks in [`solo/heltec_v3/platformio.ini`](./solo/heltec_v3/platformio.ini) and [`solo/heltec_v4/platformio.ini`](./solo/heltec_v4/platformio.ini) — comments there list which GPIOs are already claimed. Full details in [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md). --- @@ -153,6 +153,7 @@ These are only defaults, set in the `[env:Heltec_v3_companion_solo_dual]` / `[en | [Screen Lock](./docs/solo_features/screen_lock/screen_lock.md) | Lock/unlock sequence, lock screen, auto-lock | | [Tools Screen](./docs/solo_features/tools_screen/tools_screen.md) | GPS trail & waypoints, compass, navigation, nearby nodes, ringtone editor, remote bot, auto-advert, live location sharing, locator, diagnostics, repeater, remote admin | | [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md) | CardKB shortcuts, Full vs Compact mode, wired joystick, Heltec V3/V4 wiring | +| [Build Flags](./docs/solo_features/build_flags.md) | Every optional `-D` build flag a solo build understands — GPIO, Hall sensor, buzzer/vibration, GPS switch, display/battery tuning | | [Solo UI framework](./docs/design/solo_ui_framework.md) | **Developer guide** — the reusable building blocks (screens, lists, popups, mini-icons, geo/persistence helpers) and how to add a new feature | | [Feature roadmap](./docs/development/roadmap.md) | **Developer notes** — planned / done / rejected features and the code-audit backlog | @@ -217,6 +218,11 @@ FIRMWARE_VERSION=v1.0.0 bash build.sh build-firmware # release artifacts i The last command runs the same path CI does: a `.uf2` + DFU `.zip` on nRF52, or an app-only `.bin` plus a `-merged.bin` on ESP32. Releases carry the `.uf2`, the `.zip` and the `-merged.bin` only. +Every environment above builds as-is with no extra hardware required. If you've +wired up something extra — CardKB, a joystick, GPIO, a magnetic cover sensor, +a buzzer — see [Build Flags](./docs/solo_features/build_flags.md) for the full +list of optional `-D` flags to add to your own `solo//platformio.ini`. + ### Releasing Pushing a `v*` tag runs [Build Solo Firmwares](./.github/workflows/build-solo-firmwares.yml), which discovers every `*_solo_dual` environment automatically, builds them all and opens a **draft** release with the artifacts attached. Write the notes from `release-notes.md` and publish it. See [RELEASE.md](./RELEASE.md) for the upstream companion/repeater/room-server tags. @@ -228,6 +234,7 @@ Pushing a `v*` tag runs [Build Solo Firmwares](./.github/workflows/build-solo-fi | `examples/companion_radio/ui-new/` | the solo UI — screens, widgets, `UITask` | | `src/helpers/ui/` | display drivers, fonts, buttons, buzzer | | `variants//` | per-board `platformio.ini`, `target.h`, `target.cpp` | +| `solo//` | that board's solo (`*_solo_dual`) environment — kept separate from `variants/` so this fork's Solo-specific additions don't mix into configs upstream/other forks also carry | | `docs/solo_features/` | user documentation for this fork | | `docs/design/`, `docs/development/` | developer notes and the feature roadmap | | `tools/` | host-side helpers (screenshot, GPX export, font conversion) | diff --git a/docs/solo_features/build_flags.md b/docs/solo_features/build_flags.md new file mode 100644 index 00000000..655a1cdb --- /dev/null +++ b/docs/solo_features/build_flags.md @@ -0,0 +1,98 @@ +## Build Flags + +[Go back](../../README.md) + +Reference for the `-D` build flags a Solo build understands beyond the +per-board defaults already set in `solo//platformio.ini`. Add any of +these to your own board's `build_flags` (in `solo//platformio.ini`) to +enable optional hardware you've wired up yourself, or to tune a default. +Everything below is a no-op when left unset — adding a flag for hardware that +isn't there costs nothing and can't brick a build; the exceptions (pin +conflicts, wrong polarity) are called out per flag. + +None of this needs touching to get a board running — see the +[environment table](../../README.md#building-from-source) for the flag-free +default build for each supported board. + +--- + +### Already part of every solo build + +Set once per board in `solo//platformio.ini`, not usually touched +per-user. Listed here so the rest of this page can assume them. + +| Flag | Meaning | +| --- | --- | +| `FIRMWARE_SOLO_BUILD=1` | Marks the build as Solo (full on-device UI) rather than a companion-only firmware. | +| `DUAL_SERIAL=1` | The companion app can attach over BLE *or* USB serial, whichever it finds; BLE wins if both are live. Every solo build sets this — Solo standardises on one build per board rather than splitting BLE-only / USB-only variants. | +| `MAX_CONTACTS=` | Size of the contact table. Default is 32 if unset; solo builds set 350. | +| `MAX_GROUP_CHANNELS=` | Size of the channel list. Required for channel support to compile in at all — not optional the way the rest of this page is. | +| `OFFLINE_QUEUE_SIZE=` | How many messages queue for later delivery while the phone app is disconnected. Default 16; solo builds set 256. | +| `UI_SENSORS_PAGE=1` | Enables the on-device sensors dashboard page. | +| `BLE_PIN_CODE=` | See below — not a plain fixed value in practice. | +| `DISPLAY_CLASS=` | Selects the display driver (e.g. `SSD1306Display`, `GxEPDDisplay`, `ST7789Display` — see `src/helpers/ui/` for the full set). Fixed by whatever panel the board actually has; only relevant if you're wiring on a *different* display than stock, in which case the matching driver's `.cpp` also needs adding to `build_src_filter`. | + +**`BLE_PIN_CODE`** has a special case baked in: if it's left at the literal +value `123456` *and* the board has a display, pairing uses a random 6-digit +PIN generated fresh each session and shown on-device, rather than a fixed one. +Any other numeric value is used as a static PIN instead. Leaving the flag out +entirely disables the PIN prompt (BLE pairing is unauthenticated). + +--- + +### Input hardware add-ons + +Each of these assumes you're wiring something up yourself — check pins are +actually free on your board first (see the board's `variant.h` and its +existing `solo//platformio.ini` for what's already claimed). + +| Flag | Adds | +| --- | --- | +| `ENV_PIN_SDA` / `ENV_PIN_SCL` | CardKB (M5Stack I2C keyboard, addr `0x5F`) on a second I2C bus. Probed at boot — harmless with nothing plugged in. See [External Keyboard & Joystick](./external_keyboard.md). | +| `UI_HAS_JOYSTICK=1` + `UI_HAS_JOYSTICK_UPDOWN=1` (optional) + `JOYSTICK_UP` / `JOYSTICK_DOWN` / `JOYSTICK_LEFT` / `JOYSTICK_RIGHT` + `PIN_USER_BTN` + `PIN_BACK_BTN` | A wired joystick (four direction contacts + a press contact for Enter). Replaces single-button navigation entirely once enabled. See [External Keyboard & Joystick](./external_keyboard.md). | +| `PIN_GPIO1` .. `PIN_GPIO4` | Up to four general-purpose pins, each independently switchable between Off / Input / Output (GPIO1/GPIO2 also get an Analog step, if wired to an ADC-capable pin) from Tools › GPIO, and via the `!gpio1`..`!gpio4` bot commands. Not restricted to any particular board — works anywhere the pins are actually free. See [Tools Screen › GPIO](./tools_screen/tools_screen.md#gpio). | +| `PIN_HALL_SENSOR` + `HALL_ACTIVE_HIGH=1` (optional) | A Hall-effect or reed sensor for a magnetic flip cover: closing locks and blanks the screen instantly, opening unlocks and wakes it, no combo either way. `HALL_ACTIVE_HIGH` is only for a module wired to pull the pin high (rather than low) when the magnet is near. See [Screen Lock › Magnetic cover](./screen_lock/screen_lock.md#magnetic-cover-hall-sensor). | +| `PIN_GPS_SWITCH` | A physical on/off switch for GPS power, read alongside the software GPS toggle — the Tools screen shows `gps off(hw)` / `gps off(sw)` when the two disagree, instead of silently trusting one over the other. | + +--- + +### Output / feedback hardware + +| Flag | Adds | +| --- | --- | +| `PIN_BUZZER` (+ `PIN_BUZZER_EN`, optional) | A passive piezo buzzer, driven by direct PWM (nRF52) or the `NonBlockingRtttl` library (everything else) for RTTTL ringtones and UI beeps. `PIN_BUZZER_EN` is an optional enable line some boards wire separately from the signal pin. | +| `PIN_VIBRATION` | A vibration motor for haptic notification, via `GenericVibration`. | + +--- + +### GPS + +| Flag | Meaning | +| --- | --- | +| `ENV_INCLUDE_GPS=1` | Compiles in GPS support (NMEA parsing, location provider) at all. | +| `GPS_BAUD_RATE=` | Baud rate for the GPS module's serial link (`Serial1`). Match your module's default. | + +`PIN_GPS_SWITCH` (above) is independent of both — it's a hardware kill switch +layered on top of GPS support, not a requirement for it. + +--- + +### Display & battery tuning + +| Flag | Meaning | +| --- | --- | +| `OLED_MISC_FIXED_FONT=1` | Pulls in a full Latin/Greek/Cyrillic 6×9 fixed font (~14 KB flash) so typed text in those alphabets renders as itself instead of block placeholders. Worth it on any board with a keyboard; skip it on space-constrained builds without one. | +| `DISPLAY_ROTATION=<0-3>` | Rotates the panel in 90° steps, for a board mounted sideways or upside down. | +| `ENABLE_SCREENSHOT` | Lets Solo Tools read the framebuffer over USB to capture a screenshot. | +| `KEEP_DISPLAY_ON_USB` | Refreshes the auto-off deadline continuously while externally (USB) powered, so the auto-off timer only starts counting once power is actually removed. Off by default because OLED panels burn in quickly with a permanently-lit screen — only worth enabling for an LCD/e-ink target, or a display you don't mind replacing. | +| `AUTO_OFF_MILLIS=` | How long the display stays on before auto-off. Default 15000 (15s); `0` disables auto-off entirely. | +| `UI_RECENT_LIST_SIZE=` | How many entries the recent-activity lists show before scrolling. Default 4. | + +--- + +### Misc + +| Flag | Meaning | +| --- | --- | +| `ADVERT_NAME='"name"'` | Sets the default node name baked into a fresh device, instead of the hex of the first 4 bytes of its public key. Note the doubled quoting — it's a C string literal passed through a build flag. | +| `MESHCORE_VERSION='"x.y"'` | Overrides the upstream MeshCore base-version string shown in diagnostics, for boards whose port hasn't been rebased onto the latest yet. Cosmetic only — doesn't change protocol behaviour. | diff --git a/docs/solo_features/external_keyboard.md b/docs/solo_features/external_keyboard.md index 05d36e5c..56f3978a 100644 --- a/docs/solo_features/external_keyboard.md +++ b/docs/solo_features/external_keyboard.md @@ -115,8 +115,8 @@ hardware**; still worth checking against your own V3 module before soldering. Everything above lives in the `[env:Heltec_v3_companion_solo_dual]` / `[env:heltec_v4_companion_solo_dual]` blocks in -[`variants/heltec_v3/platformio.ini`](../../variants/heltec_v3/platformio.ini) -and [`variants/heltec_v4/platformio.ini`](../../variants/heltec_v4/platformio.ini), +[`solo/heltec_v3/platformio.ini`](../../solo/heltec_v3/platformio.ini) +and [`solo/heltec_v4/platformio.ini`](../../solo/heltec_v4/platformio.ini), with comments explaining which pins are safe to reuse. To build a CardKB-only device, comment out the joystick block and set Ext. KB to Compact. @@ -134,4 +134,5 @@ independent of the CardKB code — a board can have either, or neither. Neither keypad follows CardKB's exact Fn-shortcut table (Fn+Enter, Fn+letter accent popups, Tab, Fn+Esc lock) — see each board's own -`platformio.ini` / keyboard driver under `variants/` for its current keymap. +keyboard driver under `variants/` and its solo `platformio.ini` under `solo/` +for its current keymap. diff --git a/platformio.ini b/platformio.ini index e78124a4..57d37a7d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,6 +11,7 @@ [platformio] extra_configs = variants/*/platformio.ini + solo/*/platformio.ini platformio.local.ini [arduino_base] diff --git a/solo/gat562_30s_mesh_kit/platformio.ini b/solo/gat562_30s_mesh_kit/platformio.ini new file mode 100644 index 00000000..2d83aaac --- /dev/null +++ b/solo/gat562_30s_mesh_kit/platformio.ini @@ -0,0 +1,52 @@ +; Solo build for GAT562 30S Mesh Kit -- extends the board base defined in +; variants/gat562_30s_mesh_kit/platformio.ini. Solo configs live in their own +; top-level solo/ folder, separate from the shared upstream board variants in +; variants/, so this repo's Solo-specific additions don't get mixed in with +; configs other MeshCore forks/upstream also carry. +; +; Dual (BLE+USB) only -- the previous BLE-only solo_ble env was dropped since +; dual is a strict superset (BLE still works, plus USB) and Solo standardises +; on one build per board. +[env:GAT562_30S_Mesh_Kit_solo_dual] +extends = GAT562_30S_Mesh_Kit +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast +; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich +; solo build. -Os keeps every feature, fits comfortably, and is actually safer +; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. +build_unflags = -Ofast +build_flags = + ${GAT562_30S_Mesh_Kit.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.17"' + -D UI_SENSORS_PAGE=1 + -D UI_HAS_JOYSTICK_UPDOWN=1 + -D ADVERT_NAME='"@@MAC"' + -D ENV_INCLUDE_GPS=1 + ; misc-fixed 6x9 OLED font (full Latin/Greek/Cyrillic), same as the Wio L1 + ; builds. Solo only: it costs ~14 KB of flash and the repeater/companion + ; builds have no keyboard to type those alphabets on. + -D OLED_MISC_FIXED_FONT=1 + -D DUAL_SERIAL=1 + -D ENABLE_SCREENSHOT + -Os + ; QSPIFLASH disabled: QSPI IO0/IO2 pins (P0.30/P0.28) conflict with joystick LEFT/UP + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${GAT562_30S_Mesh_Kit.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/solo/gat562_mesh_watch13/platformio.ini b/solo/gat562_mesh_watch13/platformio.ini new file mode 100644 index 00000000..e8413143 --- /dev/null +++ b/solo/gat562_mesh_watch13/platformio.ini @@ -0,0 +1,50 @@ +; Solo build for GAT562 Mesh Watch13 -- extends the board base defined in +; variants/gat562_mesh_watch13/platformio.ini. Solo configs live in their own +; top-level solo/ folder, separate from the shared upstream board variants in +; variants/, so this repo's Solo-specific additions don't get mixed in with +; configs other MeshCore forks/upstream also carry. +; +; Renamed from the previous BLE-only solo_ble to solo_dual (DUAL_SERIAL added +; below) to match every other board's single solo build. This board has no +; comment anywhere recording whether its USB data lines are actually broken +; out to a connector (only charging pins might be) -- DUAL_SERIAL compiles +; and works over BLE regardless, so worst case the USB half just goes unused. +[env:GAT562_Mesh_Watch13_solo_dual] +extends = GAT562_Mesh_Watch13 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${GAT562_Mesh_Watch13.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -D DUAL_SERIAL=1 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.17"' + -D UI_SENSORS_PAGE=1 + -D ADVERT_NAME='"@@MAC"' + ; GPS — re-enable (base undefines it); Serial1 pins 15/16 from variant + -D ENV_INCLUDE_GPS=1 + -D GPS_BAUD_RATE=9600 + ; Buzzer available on IO3 (pin 21) — uncomment to enable +; -D PIN_BUZZER=21 +; -D PIN_VIBRATION=36 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +; If PIN_BUZZER is enabled above, also add: +; + +lib_deps = + ${GAT562_Mesh_Watch13.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 +; If PIN_BUZZER is enabled, also add: +; end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/solo/heltec_v3/platformio.ini b/solo/heltec_v3/platformio.ini new file mode 100644 index 00000000..bd6703eb --- /dev/null +++ b/solo/heltec_v3/platformio.ini @@ -0,0 +1,89 @@ +; Solo build for Heltec LoRa32 V3 -- extends the board base defined in +; variants/heltec_v3/platformio.ini. Solo configs live in their own top-level +; solo/ folder, separate from the shared upstream board variants in variants/, +; so this repo's Solo-specific additions don't get mixed in with configs +; other MeshCore forks/upstream also carry. +; +; Solo build: the full standalone on-device UI (message history, contacts, map, +; tools) rather than a screen that only mirrors the phone app. Dual transport -- +; the companion app can attach over BLE or USB serial, whichever it finds, and +; BLE wins while both are live. +; +; This firmware needs a real input device: the solo UI cannot be driven from the +; PRG button alone. Both supported ones are wired up in target.cpp and enabled +; below, so either is enough on its own and having both is fine. The pins are +; defaults picked from what this board leaves free -- change them to match how +; the device is actually built. Pins with nothing attached read as not-pressed, +; so the unused half costs nothing. +; +; * CardKB (M5Stack I2C keyboard, addr 0x5F) on a second I2C bus. Any two free +; GPIOs -- NOT 17/18, the OLED already owns those on Wire. It's probed at +; boot, so a build with these set still runs fine with nothing plugged in. +; Pair it with Settings > Keyboard > Ext. KB = Compact, which is designed to +; need no joystick at all. The same bus is scanned for environment sensors. +; +; * Wired joystick: four direction pins plus a fifth "press" contact. The +; press contact drives Enter (your thumb's already on the stick) via +; PIN_USER_BTN -- overridden below (a later -D wins, no -U needed: this whole +; project builds with -w) from the PRG-button default +; the base env sets, since PRG's GPIO0 can't be moved to the stick. PRG +; becomes Back instead (PIN_BACK_BTN), so it no longer needs its own +; separately-wired button. Each contact just shorts its pin to GND; +; target.cpp enables the internal pull-ups. Drop +; UI_HAS_JOYSTICK_UPDOWN for a left/right-only stick. JOYSTICK_ROTATION (0-3) +; rotates the mapping if the stick is mounted sideways -- it's also a runtime +; setting, so leave it out unless you want a different default. +; Comment the whole block out for a CardKB-only build. UI_HAS_JOYSTICK +; replaces the single-button handling rather than adding to it (UITask.cpp +; dispatches on '#if UI_HAS_JOYSTICK ... #elif defined(PIN_USER_BTN)'), which +; costs nothing here -- that fallback only feeds KEY_NEXT/KEY_PREV, which the +; solo screens don't act on. +; +; V3 and V4 are pin-compatible per Heltec's own docs, so the defaults below are +; carried over from the heltec_v4_companion_solo_dual env, where this exact +; CardKB + joystick combination is confirmed working on real hardware -- still +; worth checking against your own V3 module before soldering, but not a blind +; guess like the earlier SDA 41/SCL 42 default was. PIN_GPS_RX=47/TX=48/EN=26 +; above are dead defines on V3 (ENV_INCLUDE_GPS is never set to 1 in this file), +; so they don't conflict with reusing 47/48 here despite the name. +[env:Heltec_v3_companion_solo_dual] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D DUAL_SERIAL=1 ; companion app over BLE *or* USB serial + -D OFFLINE_QUEUE_SIZE=256 + -D FIRMWARE_SOLO_BUILD=1 + -D UI_SENSORS_PAGE=1 + -D OLED_MISC_FIXED_FONT=1 ; full Latin/Greek/Cyrillic 6x9 font, ~14 KB flash + -D ENABLE_SCREENSHOT ; same as the other solo builds — Solo Tools reads the framebuffer over USB + ; CardKB on the second I2C bus. Harmless with nothing plugged in. + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + ; Wired joystick — comment the block out for a CardKB-only build. The + ; press contact (Enter) overrides PIN_USER_BTN from its PRG default; + ; PRG becomes Back (PIN_BACK_BTN) instead. + -D UI_HAS_JOYSTICK=1 + -D UI_HAS_JOYSTICK_UPDOWN=1 + -D JOYSTICK_UP=23 + -D JOYSTICK_DOWN=6 + -D JOYSTICK_LEFT=47 + -D JOYSTICK_RIGHT=48 + -D PIN_BACK_BTN=0 ; PRG button + -D PIN_USER_BTN=33 ; joystick press contact -- Enter, overrides the PRG default above +; -D PIN_BUZZER= +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/solo/heltec_v4/platformio.ini b/solo/heltec_v4/platformio.ini new file mode 100644 index 00000000..525f470b --- /dev/null +++ b/solo/heltec_v4/platformio.ini @@ -0,0 +1,96 @@ +; Solo build for Heltec LoRa32 V4 -- extends the board base defined in +; variants/heltec_v4/platformio.ini. Solo configs live in their own top-level +; solo/ folder, separate from the shared upstream board variants in variants/, +; so this repo's Solo-specific additions don't get mixed in with configs +; other MeshCore forks/upstream also carry. +; +; Solo build: the full standalone on-device UI (message history, contacts, map, +; tools) rather than a screen that only mirrors the phone app. Dual transport -- +; the companion app can attach over BLE or USB serial, whichever it finds, and +; BLE wins while both are live. OLED board only -- the TFT variant would need +; its own DISPLAY_CLASS. +; +; This firmware needs a real input device: the solo UI cannot be driven from the +; PRG button alone. Both supported ones are wired up in target.cpp and enabled +; below, so either is enough on its own and having both is fine. The pins are +; defaults picked from what this board leaves free -- change them to match how +; the device is actually built. Pins with nothing attached read as not-pressed, +; so the unused half costs nothing. +; +; * CardKB (M5Stack I2C keyboard, addr 0x5F) on a second I2C bus. Any two free +; GPIOs -- NOT 17/18, the OLED already owns those on Wire. It's probed at +; boot, so a build with these set still runs fine with nothing plugged in. +; Pair it with Settings > Keyboard > Ext. KB = Compact, which is designed to +; need no joystick at all. The same bus is scanned for environment sensors. +; Defaults below: SDA 3, SCL 4 -- the pins heltec_v4_sensor and +; heltec_v4_expansionkit_repeater already use for their second I2C bus, i.e. +; the expansion kit's. (Those envs disagree on the order: two say SDA 3 / +; SCL 4, one says the reverse. 3/4 is the pair either way; swap them if the +; keyboard doesn't answer.) +; +; * Wired joystick: four direction pins plus a fifth "press" contact. The +; press contact drives Enter (your thumb's already on the stick) via +; PIN_USER_BTN -- overridden below (a later -D wins, no -U needed: this whole +; project builds with -w) from the PRG-button default +; the base env sets, since PRG's GPIO0 can't be moved to the stick. PRG +; becomes Back instead (PIN_BACK_BTN), so it no longer needs its own +; separately-wired button. Each contact just shorts its pin to GND; +; target.cpp enables the internal pull-ups. Drop +; UI_HAS_JOYSTICK_UPDOWN for a left/right-only stick. JOYSTICK_ROTATION (0-3) +; rotates the mapping if the stick is mounted sideways -- it's also a runtime +; setting, so leave it out unless you want a different default. +; Comment the whole block out for a CardKB-only build. UI_HAS_JOYSTICK +; replaces the single-button handling rather than adding to it (UITask.cpp +; dispatches on '#if UI_HAS_JOYSTICK ... #elif defined(PIN_USER_BTN)'), which +; costs nothing here -- that fallback only feeds KEY_NEXT/KEY_PREV, which the +; solo screens don't act on. +; +; Free on this board, per its pinout: 3, 4, 6, 23, 33, 43, 44, 45, 46, 47, 48. +; Two of those can't take an I2C pull-up: 45 selects VDD_SPI at boot, so held +; high the chip comes up expecting 1.8V flash and won't boot at all, and 46 is +; the boot-mode strap. 3 is a strapping pin too, but it only picks the JTAG +; source -- and it's what this board's own expansion kit uses for I2C anyway. +; 43/44 are UART0: unused by this build, since ARDUINO_USB_CDC_ON_BOOT (set in +; boards/heltec_v4.json) makes Serial the native USB port, but a USB-UART bridge +; may still be wired to them, so they're left alone. +[env:heltec_v4_companion_solo_dual] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D DUAL_SERIAL=1 ; companion app over BLE *or* USB serial + -D OFFLINE_QUEUE_SIZE=256 + -D FIRMWARE_SOLO_BUILD=1 + -D UI_SENSORS_PAGE=1 + -D OLED_MISC_FIXED_FONT=1 ; full Latin/Greek/Cyrillic 6x9 font, ~14 KB flash + -D ENABLE_SCREENSHOT ; same as the other solo builds — Solo Tools reads the framebuffer over USB + ; CardKB on the second I2C bus. Harmless with nothing plugged in. + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + ; Wired joystick — comment the block out for a CardKB-only build. The + ; press contact (Enter) overrides PIN_USER_BTN from its PRG default; + ; PRG becomes Back (PIN_BACK_BTN) instead. + -D UI_HAS_JOYSTICK=1 + -D UI_HAS_JOYSTICK_UPDOWN=1 + -D JOYSTICK_UP=23 + -D JOYSTICK_DOWN=6 + -D JOYSTICK_LEFT=47 + -D JOYSTICK_RIGHT=48 + -D PIN_BACK_BTN=0 ; PRG button + -D PIN_USER_BTN=33 ; joystick press contact -- Enter, overrides the PRG default above +; -D PIN_BUZZER= +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/solo/lilygo_techo_lite/platformio.ini b/solo/lilygo_techo_lite/platformio.ini new file mode 100644 index 00000000..fccfdc27 --- /dev/null +++ b/solo/lilygo_techo_lite/platformio.ini @@ -0,0 +1,43 @@ +; Solo build for LilyGO T-Echo Lite + KeyShield -- extends the board base +; defined in variants/lilygo_techo_lite/platformio.ini. Solo configs live in +; their own top-level solo/ folder, separate from the shared upstream board +; variants in variants/, so this repo's Solo-specific additions don't get +; mixed in with configs other MeshCore forks/upstream also carry. +; +[env:LilyGo_T-Echo-Lite_keyshield_companion_solo_dual] +extends = LilyGo_T-Echo-Lite +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast +; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich +; solo build. -Os keeps every feature, fits comfortably, and is actually safer +; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. +build_unflags = -Ofast +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D LILYGO_TECHO_LITE_KEYSHIELD + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + ; -D QSPIFLASH=1 + -D BLE_PIN_CODE=123456 + ; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 + -D ENV_USE_TCA8418=1 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.17"' + -D ENABLE_SCREENSHOT + -Os +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T-Echo-Lite.lib_deps} + densaugeo/base64 @ ~1.4.0 + ;https://github.com/lewisxhe/SensorLib.git diff --git a/solo/m5stack_cardputer_adv/platformio.ini b/solo/m5stack_cardputer_adv/platformio.ini new file mode 100644 index 00000000..42201b93 --- /dev/null +++ b/solo/m5stack_cardputer_adv/platformio.ini @@ -0,0 +1,34 @@ +; Solo build for M5Stack Cardputer ADV -- extends the board base defined in +; variants/m5stack_cardputer_adv/platformio.ini. Solo configs live in their own +; top-level solo/ folder, separate from the shared upstream board variants in +; variants/, so this repo's Solo-specific additions don't get mixed in with +; configs other MeshCore forks/upstream also carry. +; +[env:M5Stack_Cardputer_ADV_companion_solo_dual] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D DUAL_SERIAL=1 + -D OFFLINE_QUEUE_SIZE=256 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.17"' + -D UI_SENSORS_PAGE=1 + ; misc-fixed 6x9 font (full Latin/Greek/Cyrillic), same as the Wio L1/Heltec + ; solo builds. Solo only: it costs ~14 KB of flash, and this board's own + ; QWERTY keyboard is exactly the case that benefits from typing those + ; alphabets and having them render as themselves instead of blocks. + -D OLED_MISC_FIXED_FONT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/solo/wio-tracker-l1-eink/platformio.ini b/solo/wio-tracker-l1-eink/platformio.ini new file mode 100644 index 00000000..3dd6d146 --- /dev/null +++ b/solo/wio-tracker-l1-eink/platformio.ini @@ -0,0 +1,19 @@ +; Solo build for Seeed Wio Tracker L1 (E-ink) -- extends WioTrackerL1Eink, +; defined in variants/wio-tracker-l1-eink/platformio.ini. Solo configs live in +; their own top-level solo/ folder, separate from the shared upstream board +; variants in variants/, so this repo's Solo-specific additions don't get +; mixed in with configs other MeshCore forks/upstream also carry. +; +; Dual BLE+USB with added Solo +[env:WioTrackerL1Eink_companion_solo_dual] +extends = WioTrackerL1Eink +; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast +; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich +; solo build. -Os keeps every feature, fits comfortably, and is actually safer +; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. +build_unflags = -Ofast +build_flags = ${WioTrackerL1Eink.build_flags} + -D DUAL_SERIAL=1 + -D ENABLE_SCREENSHOT + -Os +extra_scripts = post:create-uf2.py diff --git a/solo/wio-tracker-l1/platformio.ini b/solo/wio-tracker-l1/platformio.ini new file mode 100644 index 00000000..e727680f --- /dev/null +++ b/solo/wio-tracker-l1/platformio.ini @@ -0,0 +1,19 @@ +; Solo build for Seeed Wio Tracker L1 (OLED) -- extends WioTrackerL1CompanionDual, +; defined in variants/wio-tracker-l1/platformio.ini. Solo configs live in their +; own top-level solo/ folder, separate from the shared upstream board variants +; in variants/, so this repo's Solo-specific additions don't get mixed in with +; configs other MeshCore forks/upstream also carry. +; +[env:WioTrackerL1_companion_solo_dual] +extends = WioTrackerL1CompanionDual +; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast +; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich +; solo build. -Os keeps every feature, fits comfortably, and is actually safer +; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. +build_unflags = -Ofast +build_flags = ${WioTrackerL1CompanionDual.build_flags} + -D UI_HAS_JOYSTICK_UPDOWN=1 + -D ENABLE_SCREENSHOT + -D DUAL_SERIAL=1 + -Os +extra_scripts = post:create-uf2.py diff --git a/variants/gat562_30s_mesh_kit/platformio.ini b/variants/gat562_30s_mesh_kit/platformio.ini index 7fe96f5c..ef6e9f6d 100644 --- a/variants/gat562_30s_mesh_kit/platformio.ini +++ b/variants/gat562_30s_mesh_kit/platformio.ini @@ -113,91 +113,6 @@ lib_deps = densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 -[env:GAT562_30S_Mesh_Kit_solo_ble] -extends = GAT562_30S_Mesh_Kit -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast -; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich -; solo build. -Os keeps every feature, fits comfortably, and is actually safer -; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. -build_unflags = -Ofast -build_flags = - ${GAT562_30S_Mesh_Kit.build_flags} - -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 - -D OFFLINE_QUEUE_SIZE=256 - -D FIRMWARE_SOLO_BUILD=1 - -D MESHCORE_VERSION='"1.17"' - -D UI_SENSORS_PAGE=1 - -D UI_HAS_JOYSTICK_UPDOWN=1 - -D ADVERT_NAME='"@@MAC"' - -D ENV_INCLUDE_GPS=1 - ; misc-fixed 6x9 OLED font (full Latin/Greek/Cyrillic), same as the Wio L1 - ; builds. Solo only: it costs ~14 KB of flash and the repeater/companion - ; builds have no keyboard to type those alphabets on. - -D OLED_MISC_FIXED_FONT=1 - -Os - ; QSPIFLASH disabled: QSPI IO0/IO2 pins (P0.30/P0.28) conflict with joystick LEFT/UP - ; -D MESH_PACKET_LOGGING=1 - ; -D MESH_DEBUG=1 -build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter} - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${GAT562_30S_Mesh_Kit.lib_deps} - adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 - -[env:GAT562_30S_Mesh_Kit_solo_dual] -extends = GAT562_30S_Mesh_Kit -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast -; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich -; solo build. -Os keeps every feature, fits comfortably, and is actually safer -; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. -build_unflags = -Ofast -build_flags = - ${GAT562_30S_Mesh_Kit.build_flags} - -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 - -D OFFLINE_QUEUE_SIZE=256 - -D FIRMWARE_SOLO_BUILD=1 - -D MESHCORE_VERSION='"1.17"' - -D UI_SENSORS_PAGE=1 - -D UI_HAS_JOYSTICK_UPDOWN=1 - -D ADVERT_NAME='"@@MAC"' - -D ENV_INCLUDE_GPS=1 - ; misc-fixed 6x9 OLED font (full Latin/Greek/Cyrillic), same as the Wio L1 - ; builds. Solo only: it costs ~14 KB of flash and the repeater/companion - ; builds have no keyboard to type those alphabets on. - -D OLED_MISC_FIXED_FONT=1 - -D DUAL_SERIAL=1 - -D ENABLE_SCREENSHOT - -Os - ; QSPIFLASH disabled: QSPI IO0/IO2 pins (P0.30/P0.28) conflict with joystick LEFT/UP - ; -D MESH_PACKET_LOGGING=1 - ; -D MESH_DEBUG=1 -build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter} - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${GAT562_30S_Mesh_Kit.lib_deps} - adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 [env:GAT562_30S_Mesh_Kit_kiss_modem] extends = GAT562_30S_Mesh_Kit diff --git a/variants/gat562_mesh_watch13/platformio.ini b/variants/gat562_mesh_watch13/platformio.ini index 13fd3e16..f3510b74 100644 --- a/variants/gat562_mesh_watch13/platformio.ini +++ b/variants/gat562_mesh_watch13/platformio.ini @@ -87,45 +87,6 @@ lib_deps = ${GAT562_Mesh_Watch13.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:GAT562_Mesh_Watch13_solo_ble] -extends = GAT562_Mesh_Watch13 -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -build_flags = - ${GAT562_Mesh_Watch13.build_flags} - -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 - -D OFFLINE_QUEUE_SIZE=256 - -D FIRMWARE_SOLO_BUILD=1 - -D MESHCORE_VERSION='"1.17"' - -D UI_SENSORS_PAGE=1 - -D ADVERT_NAME='"@@MAC"' - ; GPS — re-enable (base undefines it); Serial1 pins 15/16 from variant - -D ENV_INCLUDE_GPS=1 - -D GPS_BAUD_RATE=9600 - ; Buzzer available on IO3 (pin 21) — uncomment to enable -; -D PIN_BUZZER=21 -; -D PIN_VIBRATION=36 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter} - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -; If PIN_BUZZER is enabled above, also add: -; + -lib_deps = - ${GAT562_Mesh_Watch13.lib_deps} - adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 -; If PIN_BUZZER is enabled, also add: -; end2endzone/NonBlockingRTTTL@^1.3.0 - [env:GAT562_Mesh_Watch13_kiss_modem] extends = GAT562_Mesh_Watch13 build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter} diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index b1515020..65e636eb 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -180,91 +180,6 @@ lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 -; Solo build: the full standalone on-device UI (message history, contacts, map, -; tools) rather than a screen that only mirrors the phone app. Dual transport -- -; the companion app can attach over BLE or USB serial, whichever it finds, and -; BLE wins while both are live. -; -; This firmware needs a real input device: the solo UI cannot be driven from the -; PRG button alone. Both supported ones are wired up in target.cpp and enabled -; below, so either is enough on its own and having both is fine. The pins are -; defaults picked from what this board leaves free -- change them to match how -; the device is actually built. Pins with nothing attached read as not-pressed, -; so the unused half costs nothing. -; -; * CardKB (M5Stack I2C keyboard, addr 0x5F) on a second I2C bus. Any two free -; GPIOs -- NOT 17/18, the OLED already owns those on Wire. It's probed at -; boot, so a build with these set still runs fine with nothing plugged in. -; Pair it with Settings > Keyboard > Ext. KB = Compact, which is designed to -; need no joystick at all. The same bus is scanned for environment sensors. -; -; * Wired joystick: four direction pins plus a fifth "press" contact. The -; press contact drives Enter (your thumb's already on the stick) via -; PIN_USER_BTN -- overridden below (a later -D wins, no -U needed: this whole -; project builds with -w) from the PRG-button default -; the base env sets, since PRG's GPIO0 can't be moved to the stick. PRG -; becomes Back instead (PIN_BACK_BTN), so it no longer needs its own -; separately-wired button. Each contact just shorts its pin to GND; -; target.cpp enables the internal pull-ups. Drop -; UI_HAS_JOYSTICK_UPDOWN for a left/right-only stick. JOYSTICK_ROTATION (0-3) -; rotates the mapping if the stick is mounted sideways -- it's also a runtime -; setting, so leave it out unless you want a different default. -; Comment the whole block out for a CardKB-only build. UI_HAS_JOYSTICK -; replaces the single-button handling rather than adding to it (UITask.cpp -; dispatches on '#if UI_HAS_JOYSTICK ... #elif defined(PIN_USER_BTN)'), which -; costs nothing here -- that fallback only feeds KEY_NEXT/KEY_PREV, which the -; solo screens don't act on. -; -; V3 and V4 are pin-compatible per Heltec's own docs, so the defaults below are -; carried over from the heltec_v4_companion_solo_dual env, where this exact -; CardKB + joystick combination is confirmed working on real hardware -- still -; worth checking against your own V3 module before soldering, but not a blind -; guess like the earlier SDA 41/SCL 42 default was. PIN_GPS_RX=47/TX=48/EN=26 -; above are dead defines on V3 (ENV_INCLUDE_GPS is never set to 1 in this file), -; so they don't conflict with reusing 47/48 here despite the name. -[env:Heltec_v3_companion_solo_dual] -extends = Heltec_lora32_v3 -build_flags = - ${Heltec_lora32_v3.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display - -D BLE_PIN_CODE=123456 ; dynamic, random PIN - -D DUAL_SERIAL=1 ; companion app over BLE *or* USB serial - -D AUTO_SHUTDOWN_MILLIVOLTS=3400 - -D OFFLINE_QUEUE_SIZE=256 - -D FIRMWARE_SOLO_BUILD=1 - -D UI_SENSORS_PAGE=1 - -D OLED_MISC_FIXED_FONT=1 ; full Latin/Greek/Cyrillic 6x9 font, ~14 KB flash - -D ENABLE_SCREENSHOT ; same as the other solo builds — Solo Tools reads the framebuffer over USB - ; CardKB on the second I2C bus. Harmless with nothing plugged in. - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 - ; Wired joystick — comment the block out for a CardKB-only build. The - ; press contact (Enter) overrides PIN_USER_BTN from its PRG default; - ; PRG becomes Back (PIN_BACK_BTN) instead. - -D UI_HAS_JOYSTICK=1 - -D UI_HAS_JOYSTICK_UPDOWN=1 - -D JOYSTICK_UP=23 - -D JOYSTICK_DOWN=6 - -D JOYSTICK_LEFT=47 - -D JOYSTICK_RIGHT=48 - -D PIN_BACK_BTN=0 ; PRG button - -D PIN_USER_BTN=33 ; joystick press contact -- Enter, overrides the PRG default above -; -D PIN_BUZZER= -build_src_filter = ${Heltec_lora32_v3.build_src_filter} - + - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${Heltec_lora32_v3.lib_deps} - densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 - [env:Heltec_v3_companion_radio_wifi] extends = Heltec_lora32_v3 build_flags = diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 65fb8fc7..d718c006 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -223,98 +223,6 @@ lib_deps = ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 -; Solo build: the full standalone on-device UI (message history, contacts, map, -; tools) rather than a screen that only mirrors the phone app. Dual transport -- -; the companion app can attach over BLE or USB serial, whichever it finds, and -; BLE wins while both are live. OLED board only -- the TFT variant would need -; its own DISPLAY_CLASS. -; -; This firmware needs a real input device: the solo UI cannot be driven from the -; PRG button alone. Both supported ones are wired up in target.cpp and enabled -; below, so either is enough on its own and having both is fine. The pins are -; defaults picked from what this board leaves free -- change them to match how -; the device is actually built. Pins with nothing attached read as not-pressed, -; so the unused half costs nothing. -; -; * CardKB (M5Stack I2C keyboard, addr 0x5F) on a second I2C bus. Any two free -; GPIOs -- NOT 17/18, the OLED already owns those on Wire. It's probed at -; boot, so a build with these set still runs fine with nothing plugged in. -; Pair it with Settings > Keyboard > Ext. KB = Compact, which is designed to -; need no joystick at all. The same bus is scanned for environment sensors. -; Defaults below: SDA 3, SCL 4 -- the pins heltec_v4_sensor and -; heltec_v4_expansionkit_repeater already use for their second I2C bus, i.e. -; the expansion kit's. (Those envs disagree on the order: two say SDA 3 / -; SCL 4, one says the reverse. 3/4 is the pair either way; swap them if the -; keyboard doesn't answer.) -; -; * Wired joystick: four direction pins plus a fifth "press" contact. The -; press contact drives Enter (your thumb's already on the stick) via -; PIN_USER_BTN -- overridden below (a later -D wins, no -U needed: this whole -; project builds with -w) from the PRG-button default -; the base env sets, since PRG's GPIO0 can't be moved to the stick. PRG -; becomes Back instead (PIN_BACK_BTN), so it no longer needs its own -; separately-wired button. Each contact just shorts its pin to GND; -; target.cpp enables the internal pull-ups. Drop -; UI_HAS_JOYSTICK_UPDOWN for a left/right-only stick. JOYSTICK_ROTATION (0-3) -; rotates the mapping if the stick is mounted sideways -- it's also a runtime -; setting, so leave it out unless you want a different default. -; Comment the whole block out for a CardKB-only build. UI_HAS_JOYSTICK -; replaces the single-button handling rather than adding to it (UITask.cpp -; dispatches on '#if UI_HAS_JOYSTICK ... #elif defined(PIN_USER_BTN)'), which -; costs nothing here -- that fallback only feeds KEY_NEXT/KEY_PREV, which the -; solo screens don't act on. -; -; Free on this board, per its pinout: 3, 4, 6, 23, 33, 43, 44, 45, 46, 47, 48. -; Two of those can't take an I2C pull-up: 45 selects VDD_SPI at boot, so held -; high the chip comes up expecting 1.8V flash and won't boot at all, and 46 is -; the boot-mode strap. 3 is a strapping pin too, but it only picks the JTAG -; source -- and it's what this board's own expansion kit uses for I2C anyway. -; 43/44 are UART0: unused by this build, since ARDUINO_USB_CDC_ON_BOOT (set in -; boards/heltec_v4.json) makes Serial the native USB port, but a USB-UART bridge -; may still be wired to them, so they're left alone. -[env:heltec_v4_companion_solo_dual] -extends = heltec_v4_oled -build_flags = - ${heltec_v4_oled.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display - -D BLE_PIN_CODE=123456 ; dynamic, random PIN - -D DUAL_SERIAL=1 ; companion app over BLE *or* USB serial - -D AUTO_SHUTDOWN_MILLIVOLTS=3400 - -D OFFLINE_QUEUE_SIZE=256 - -D FIRMWARE_SOLO_BUILD=1 - -D UI_SENSORS_PAGE=1 - -D OLED_MISC_FIXED_FONT=1 ; full Latin/Greek/Cyrillic 6x9 font, ~14 KB flash - -D ENABLE_SCREENSHOT ; same as the other solo builds — Solo Tools reads the framebuffer over USB - ; CardKB on the second I2C bus. Harmless with nothing plugged in. - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 - ; Wired joystick — comment the block out for a CardKB-only build. The - ; press contact (Enter) overrides PIN_USER_BTN from its PRG default; - ; PRG becomes Back (PIN_BACK_BTN) instead. - -D UI_HAS_JOYSTICK=1 - -D UI_HAS_JOYSTICK_UPDOWN=1 - -D JOYSTICK_UP=23 - -D JOYSTICK_DOWN=6 - -D JOYSTICK_LEFT=47 - -D JOYSTICK_RIGHT=48 - -D PIN_BACK_BTN=0 ; PRG button - -D PIN_USER_BTN=33 ; joystick press contact -- Enter, overrides the PRG default above -; -D PIN_BUZZER= -build_src_filter = ${heltec_v4_oled.build_src_filter} - + - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_oled.lib_deps} - densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 - [env:heltec_v4_companion_radio_wifi] extends = heltec_v4_oled build_flags = diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index cb45ff41..a0b833c8 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -45,45 +45,6 @@ lib_deps = debug_tool = jlink upload_protocol = nrfutil -[env:LilyGo_T-Echo-Lite_keyshield_companion_solo_dual] -extends = LilyGo_T-Echo-Lite -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast -; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich -; solo build. -Os keeps every feature, fits comfortably, and is actually safer -; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. -build_unflags = -Ofast -build_flags = - ${LilyGo_T-Echo-Lite.build_flags} - -I src/helpers/ui - -I examples/companion_radio/ui-new - -D LILYGO_TECHO_LITE_KEYSHIELD - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - ; -D QSPIFLASH=1 - -D BLE_PIN_CODE=123456 - ; -D BLE_DEBUG_LOGGING=1 - -D OFFLINE_QUEUE_SIZE=256 - -D UI_RECENT_LIST_SIZE=9 - -D UI_SENSORS_PAGE=1 - ; -D MESH_PACKET_LOGGING=1 - ; -D MESH_DEBUG=1 - -D AUTO_SHUTDOWN_MILLIVOLTS=3300 - -D ENV_USE_TCA8418=1 - -D FIRMWARE_SOLO_BUILD=1 - -D MESHCORE_VERSION='"1.17"' - -D ENABLE_SCREENSHOT - -Os -build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${LilyGo_T-Echo-Lite.lib_deps} - densaugeo/base64 @ ~1.4.0 - ;https://github.com/lewisxhe/SensorLib.git - [env:LilyGo_T-Echo-Lite_repeater] extends = LilyGo_T-Echo-Lite build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} diff --git a/variants/m5stack_cardputer_adv/platformio.ini b/variants/m5stack_cardputer_adv/platformio.ini index 465dedbb..83955333 100644 --- a/variants/m5stack_cardputer_adv/platformio.ini +++ b/variants/m5stack_cardputer_adv/platformio.ini @@ -97,35 +97,6 @@ lib_deps = ${M5Stack_Cardputer_ADV.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:M5Stack_Cardputer_ADV_companion_solo_dual] -extends = M5Stack_Cardputer_ADV -build_flags = - ${M5Stack_Cardputer_ADV.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 - -D DUAL_SERIAL=1 - -D OFFLINE_QUEUE_SIZE=256 - -D FIRMWARE_SOLO_BUILD=1 - -D MESHCORE_VERSION='"1.17"' - -D UI_SENSORS_PAGE=1 - ; misc-fixed 6x9 font (full Latin/Greek/Cyrillic), same as the Wio L1/Heltec - ; solo builds. Solo only: it costs ~14 KB of flash, and this board's own - ; QWERTY keyboard is exactly the case that benefits from typing those - ; alphabets and having them render as themselves instead of blocks. - -D OLED_MISC_FIXED_FONT=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${M5Stack_Cardputer_ADV.lib_deps} - densaugeo/base64 @ ~1.4.0 - [env:M5Stack_Cardputer_ADV_repeater] extends = M5Stack_Cardputer_ADV build_flags = diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index 9a10923e..c4fb4834 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -70,20 +70,6 @@ build_flags = ${WioTrackerL1Eink.build_flags} -D DUAL_SERIAL=1 extra_scripts = post:create-uf2.py -; Dual BLE+USB with added Solo -[env:WioTrackerL1Eink_companion_solo_dual] -extends = WioTrackerL1Eink -; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast -; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich -; solo build. -Os keeps every feature, fits comfortably, and is actually safer -; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. -build_unflags = -Ofast -build_flags = ${WioTrackerL1Eink.build_flags} - -D DUAL_SERIAL=1 - -D ENABLE_SCREENSHOT - -Os -extra_scripts = post:create-uf2.py - ; Dual BLE+USB with screenshot support (development/debug) [env:WioTrackerL1Eink_companion_dual_dev] extends = WioTrackerL1Eink diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 7a336a77..c77ec713 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -150,20 +150,6 @@ build_flags = ${WioTrackerL1CompanionDual.build_flags} -D UI_HAS_JOYSTICK_UPDOWN=1 extra_scripts = post:create-uf2.py -[env:WioTrackerL1_companion_solo_dual] -extends = WioTrackerL1CompanionDual -; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast -; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich -; solo build. -Os keeps every feature, fits comfortably, and is actually safer -; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. -build_unflags = -Ofast -build_flags = ${WioTrackerL1CompanionDual.build_flags} - -D UI_HAS_JOYSTICK_UPDOWN=1 - -D ENABLE_SCREENSHOT - -D DUAL_SERIAL=1 - -Os -extra_scripts = post:create-uf2.py - [env:WioTrackerL1_companion_dual_dev] extends = WioTrackerL1CompanionDual build_flags = ${WioTrackerL1CompanionDual.build_flags} From b71b46fd842ac9cc9076283049b5f76b6a621045 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:22:50 +0200 Subject: [PATCH 05/11] fix(ui): debounce the Hall sensor poll against reed-switch contact bounce pollHallSensor() acted on the raw pin reading immediately, unlike every other physical-input path in this file (MomentaryButton, pollCardKB()'s own last-raw edge check). A cheap mechanical reed switch -- one of the two sensor types the docs explicitly recommend wiring here, alongside a solid-state Hall IC -- can chatter for a few ms while the magnet crosses the trigger distance, so a poll every loop() tick during that window could flip _locked and fire _display->turnOff()/turnOn() repeatedly in that short span: wasted work on any panel, and a real cost on e-ink where each is a slow full-panel operation. A raw reading now has to hold steady for HALL_DEBOUNCE_MS (25ms, same threshold as MomentaryButton's ISR_DEBOUNCE_MS) before it replaces _hall_magnet_present and triggers the lock/unlock actions. Co-Authored-By: Claude Sonnet 5 --- examples/companion_radio/ui-new/UITask.cpp | 19 ++++++++++++++++--- examples/companion_radio/ui-new/UITask.h | 7 +++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index a6e6f9de..fb4163e0 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2255,11 +2255,24 @@ void UITask::pollCardKB() { // there's nothing to show), opening unlocks and wakes it, with no combo or // keypress either way. Independent of Auto-lock (Settings > Display), which is // a timeout-driven setting -- this is a direct physical event. +// +// Debounced against a mechanical reed switch chattering for a few ms as the +// magnet crosses the trigger distance -- a raw flip only becomes the new +// _hall_magnet_present once it's been steady for HALL_DEBOUNCE_MS, so a bounce +// can't fire the lock/unlock actions (each including a full display +// off/on -- slow and disruptive on e-ink) more than once per real transition. void UITask::pollHallSensor() { #if defined(PIN_HALL_SENSOR) - bool present = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH) - : (digitalRead(PIN_HALL_SENSOR) == LOW); - if (present == _hall_magnet_present) return; + bool raw = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH) + : (digitalRead(PIN_HALL_SENSOR) == LOW); + if (raw != _hall_candidate) { + _hall_candidate = raw; + _hall_candidate_since = millis(); + } + if (_hall_candidate == _hall_magnet_present) return; // no debounced change yet + if (millis() - _hall_candidate_since < HALL_DEBOUNCE_MS) return; // not steady long enough + + bool present = _hall_candidate; _hall_magnet_present = present; if (present) { // cover closed diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index a2ce2411..995dd046 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -219,6 +219,13 @@ class UITask : public AbstractUITask { #define HALL_ACTIVE_HIGH 0 #endif bool _hall_magnet_present = false; + // Contact-bounce guard for a mechanical reed switch (a Hall-effect IC reads + // clean, but the docs recommend either): a raw reading only replaces + // _hall_magnet_present once it's held steady for HALL_DEBOUNCE_MS, same + // threshold and reasoning as MomentaryButton's ISR_DEBOUNCE_MS. + static const uint32_t HALL_DEBOUNCE_MS = 25; + bool _hall_candidate = false; + uint32_t _hall_candidate_since = 0; #endif void pollHallSensor(); From 8fdb03bd9a38de46ac522000d696fdfbf0d828ce Mon Sep 17 00:00:00 2001 From: tchellow Date: Fri, 28 Aug 2026 10:55:52 -0300 Subject: [PATCH 06/11] Rebase onto main, move CARDKB_I2C out of class body, resolve via platformio.ini --- .../companion_radio/ui-new/SettingsScreen.h | 29 ++++++++++++++++- examples/companion_radio/ui-new/UITask.cpp | 10 +++--- examples/companion_radio/ui-new/UITask.h | 31 ++++++++++--------- variants/promicro/platformio.ini | 21 +++++++------ 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/examples/companion_radio/ui-new/SettingsScreen.h b/examples/companion_radio/ui-new/SettingsScreen.h index 3271ffc3..02c18683 100644 --- a/examples/companion_radio/ui-new/SettingsScreen.h +++ b/examples/companion_radio/ui-new/SettingsScreen.h @@ -61,6 +61,7 @@ class SettingsScreen : public UIScreen { CUSTOM_FREQ, CUSTOM_SF, CUSTOM_BW, CUSTOM_CR, POWER_SAVE, TX_APC, + SCOPE_NAME, // System section SECTION_SYSTEM, DEVICE_NAME, @@ -555,6 +556,11 @@ class SettingsScreen : public UIScreen { // Suppressed (and locked) while repeating — a repeater holds full TX power. if (p && p->client_repeat) display.print("--"); else display.print((p && p->tx_apc) ? "ON" : "OFF"); + } else if (item == SCOPE_NAME) { + display.print("Scope"); + int vx = valCol(display); + display.drawTextEllipsized(vx, y, display.width() - vx - _reserve, + (p && p->default_scope_name[0]) ? p->default_scope_name : "(none)"); #if AUTO_OFF_MILLIS > 0 } else if (item == AUTO_OFF) { display.print("AutoOff"); @@ -678,6 +684,7 @@ class SettingsScreen : public UIScreen { // Keyboard state for editing message slots int _edit_slot = -1; // -1 = not editing, 0..9 = slot being edited bool _edit_name = false; // editing DEVICE_NAME via the keyboard + bool _edit_scope = false; // editing SCOPE_NAME via the keyboard KeyboardWidget* _kb; // Radio preset picker — names are too long for the value column, so Enter on @@ -701,6 +708,7 @@ public: void onShow() override { _dirty = false; _edit_name = false; + _edit_scope = false; resetList(); _editor.freq.active = false; } @@ -708,7 +716,7 @@ public: int render(DisplayDriver& display) override { display.setTextSize(1); - if (_edit_slot >= 0 || _edit_name || _picker.saving) { + if (_edit_slot >= 0 || _edit_name || _edit_scope || _picker.saving) { return _kb->render(display); } @@ -771,6 +779,19 @@ public: return true; } + // Keyboard editing mode for the scope name + if (_edit_scope) { + auto res = _kb->handleInput(c); + if (res == KeyboardWidget::DONE) { + the_mesh.setPrimaryScope(_kb->buf); + _dirty = true; + _edit_scope = false; + } else if (res == KeyboardWidget::CANCELLED) { + _edit_scope = false; + } + return true; + } + // Digit-by-digit Freq editor if (_editor.active()) { if (_editor.handleFreqInput(c) && p) { _task->applyRadioParams(); _dirty = true; } @@ -966,6 +987,12 @@ public: _kb->clearPlaceholders(); // a device name is literal, not a message return true; } + if (_selected == SCOPE_NAME && p && enter) { + _edit_scope = true; + _kb->begin(p->default_scope_name, (int)sizeof(p->default_scope_name) - 1); + _kb->clearPlaceholders(); // a scope name is literal, not a message + return true; + } if (_selected == REBOOT && enter) { _task->savePrefsIfDirty(_dirty); // don't lose pending edits across the restart _task->showAlert("Rebooting...", 800); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 16b92980..27998072 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1398,11 +1398,11 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no _auto_off = millis() + (aoff > 0 ? aoff : AUTO_OFF_MILLIS); #if defined(CARDKB_I2C) - // On the ENV_PIN_SDA/SCL path, Wire1 is already brought up by + // On the ENV_PIN_SDA/SCL path, CARDKB_I2C is Wire1, already brought up by // sensors.begin() (EnvironmentSensorManager), which runs before this. On - // the CARDKB_USE_PRIMARY_WIRE path, Wire is already brought up by the - // board's own begin() (display/RTC), also before this -- either way, just - // probe for a CardKB sitting on the bus. + // boards that set CARDKB_I2C=Wire directly in platformio.ini, that bus is + // brought up by the board's own begin() instead -- also before this. + // Either way, just probe for a CardKB sitting on it. CARDKB_I2C.beginTransmission(0x5F); _has_cardkb = (CARDKB_I2C.endTransmission() == 0); #endif @@ -2120,7 +2120,7 @@ static const char CARDKB_FN_BASE[48] = { }; #endif -// Poll an optional CardKB (I2C keyboard, addr 0x5F) on Wire1/Grove, feeding +// Poll an optional CardKB (I2C keyboard, addr 0x5F) on CARDKB_I2C, feeding // the same key queue as every physical button. Most of its output needs no // translation at all: CardKB's own arrow/Enter/Esc byte codes are already // identical to this UI's KEY_LEFT/UP/DOWN/RIGHT/ENTER/CANCEL (0xB4-0xB7, 13, diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index f96e010d..c3bc058c 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -23,6 +23,21 @@ #include "../AbstractUITask.h" #include "../NodePrefs.h" #include "../Trail.h" + +// Optional M5Stack CardKB (I2C keyboard, addr 0x5F). CARDKB_I2C names which +// TwoWire it lives on -- set at file scope (not inside the class body, and +// not resolved via an #elif ladder) so both this header and SettingsScreen.h +// see a fully-resolved macro no matter which one gets included first. +// - Boards with a free second I2C bus define ENV_PIN_SDA/ENV_PIN_SCL +// (already brought up for EnvironmentSensorManager) and get Wire1 here, +// same as before. +// - Boards without a free second bus set -D CARDKB_I2C=Wire directly in +// platformio.ini, sharing whatever bus the display/RTC already use. +// Either way it's a no-op on boards that define neither, or when nothing +// ACKs 0x5F at boot. +#if !defined(CARDKB_I2C) && defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) + #define CARDKB_I2C Wire1 +#endif #include "../Waypoint.h" #include "../LiveTrack.h" #include "KeyboardWidget.h" @@ -193,20 +208,8 @@ class UITask : public AbstractUITask { void enqueueKey(char c); bool dequeueKey(char& c); - // Optional M5Stack CardKB (I2C keyboard, addr 0x5F). Two ways to reach it: - // - ENV_PIN_SDA/ENV_PIN_SCL defined -> CardKB rides the dedicated second - // I2C bus (Wire1) that EnvironmentSensorManager already brings up. - // - CARDKB_USE_PRIMARY_WIRE defined instead -> CardKB shares the board's - // main Wire bus (whatever the display/RTC already use), for boards - // where no second bus is free. Mutually exclusive with the above. - // Either way it's a no-op on boards with neither define, or when nothing - // ACKs 0x5F at boot. -#if defined(ENV_PIN_SDA) && defined(ENV_PIN_SCL) - #define CARDKB_I2C Wire1 -#elif defined(CARDKB_USE_PRIMARY_WIRE) - #define CARDKB_I2C Wire -#endif - + // Optional M5Stack CardKB (I2C keyboard, addr 0x5F). See the CARDKB_I2C + // definition near the top of this file for which bus it's on and why. #if defined(CARDKB_I2C) bool _has_cardkb = false; // CardKB is level-triggered, not edge-triggered -- it keeps returning the diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 01b0d537..83b591b8 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -178,17 +178,21 @@ extends = Promicro build_src_filter = ${Promicro.build_src_filter} +<../examples/kiss_modem/> ; ============================================================ -; Solo build (UI completa on-device) com CardKB no barramento -; I2C ja usado pelo OLED (SDA=D8, SCL=D7 -- confirmado contra o -; esquematico oficial da fakeTec V5 rev.B). Requer o patch -; CARDKB_USE_PRIMARY_WIRE em UITask.h/.cpp e SettingsScreen.h. +; Solo build (full on-device UI) with CardKB support. +; CardKB shares the board's primary I2C bus (already used by the +; OLED display / RTC) instead of a dedicated second bus: the default +; Wire1 pins on this variant (D13/D14) physically collide with the +; LoRa SPI bus (P_LORA_NSS=13, P_LORA_MOSI=14), and no other free +; GPIO pair is broken out on this board for a second dedicated bus. +; Do NOT define ENV_PIN_SDA/ENV_PIN_SCL here -- that would bring up +; a real Wire1 on the colliding pins instead. ; ============================================================ [env:ProMicro_companion_solo_dual] extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -; -Ofast (default do core) infla o flash num build solo feature-completo; -; -Os cabe com folga. +; -Ofast (the core's default) doesn't fit a feature-complete solo build; +; -Os fits with headroom. build_unflags = -Ofast build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new @@ -202,9 +206,8 @@ build_flags = ${Promicro.build_flags} -D UI_SENSORS_PAGE=1 -D ENABLE_SCREENSHOT -Os - ; CardKB compartilhando o barramento do OLED/RTC (D8/D7) -- - ; NAO definir ENV_PIN_SDA/SCL aqui, colidiria com o LoRa (D13/D14). - -D CARDKB_USE_PRIMARY_WIRE=1 + ; CardKB on the shared primary bus (D8=SDA / D7=SCL on this board). + -D CARDKB_I2C=Wire ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Promicro.build_src_filter} From f952bf237f830d39e14181726bfb74e51d92e002 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:22:03 +0200 Subject: [PATCH 07/11] chore(solo): move PR #31's new ProMicro solo env into solo/promicro/ Merged after the fact, so it followed the old convention (solo env inside variants/promicro/platformio.ini alongside the shared board configs). Moved to match every other board post-restructuring -- extends still points at Promicro, defined in variants/promicro/platformio.ini. Co-Authored-By: Claude Sonnet 5 --- solo/promicro/platformio.ini | 45 ++++++++++++++++++++++++++++++++ variants/promicro/platformio.ini | 42 ----------------------------- 2 files changed, 45 insertions(+), 42 deletions(-) create mode 100644 solo/promicro/platformio.ini diff --git a/solo/promicro/platformio.ini b/solo/promicro/platformio.ini new file mode 100644 index 00000000..05e5bdbb --- /dev/null +++ b/solo/promicro/platformio.ini @@ -0,0 +1,45 @@ +; Solo build for ProMicro (nRF52840) -- extends the board base defined in +; variants/promicro/platformio.ini. Solo configs live in their own top-level +; solo/ folder, separate from the shared upstream board variants in variants/, +; so this repo's Solo-specific additions don't get mixed in with configs +; other MeshCore forks/upstream also carry. +; +; Full on-device UI, with CardKB support: CardKB shares the board's primary +; I2C bus (already used by the OLED display / RTC) instead of a dedicated +; second bus -- the default Wire1 pins on this variant (D13/D14) physically +; collide with the LoRa SPI bus (P_LORA_NSS=13, P_LORA_MOSI=14), and no other +; free GPIO pair is broken out on this board for a second dedicated bus. Do +; NOT define ENV_PIN_SDA/ENV_PIN_SCL here -- that would bring up a real Wire1 +; on the colliding pins instead. +[env:ProMicro_companion_solo_dual] +extends = Promicro +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +; -Ofast (the core's default) doesn't fit a feature-complete solo build; +; -Os fits with headroom. +build_unflags = -Ofast +build_flags = ${Promicro.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D DUAL_SERIAL=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SSD1306Display + -D FIRMWARE_SOLO_BUILD=1 + -D UI_SENSORS_PAGE=1 + -D ENABLE_SCREENSHOT + -Os + ; CardKB on the shared primary bus (D8=SDA / D7=SCL on this board). + -D CARDKB_I2C=Wire +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Promicro.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${Promicro.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 83b591b8..195098f8 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -177,45 +177,3 @@ lib_deps = extends = Promicro build_src_filter = ${Promicro.build_src_filter} +<../examples/kiss_modem/> -; ============================================================ -; Solo build (full on-device UI) with CardKB support. -; CardKB shares the board's primary I2C bus (already used by the -; OLED display / RTC) instead of a dedicated second bus: the default -; Wire1 pins on this variant (D13/D14) physically collide with the -; LoRa SPI bus (P_LORA_NSS=13, P_LORA_MOSI=14), and no other free -; GPIO pair is broken out on this board for a second dedicated bus. -; Do NOT define ENV_PIN_SDA/ENV_PIN_SCL here -- that would bring up -; a real Wire1 on the colliding pins instead. -; ============================================================ -[env:ProMicro_companion_solo_dual] -extends = Promicro -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -; -Ofast (the core's default) doesn't fit a feature-complete solo build; -; -Os fits with headroom. -build_unflags = -Ofast -build_flags = ${Promicro.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 - -D DUAL_SERIAL=1 - -D OFFLINE_QUEUE_SIZE=256 - -D DISPLAY_CLASS=SSD1306Display - -D FIRMWARE_SOLO_BUILD=1 - -D UI_SENSORS_PAGE=1 - -D ENABLE_SCREENSHOT - -Os - ; CardKB on the shared primary bus (D8=SDA / D7=SCL on this board). - -D CARDKB_I2C=Wire -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${Promicro.build_src_filter} - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Promicro.lib_deps} - adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 From 433548dfeeba14e6fc275a51bc53838b79673231 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:24:44 +0200 Subject: [PATCH 08/11] docs: list ProMicro solo build, document CARDKB_I2C, update release notes - README's env table and external_keyboard.md's device-support table both get a ProMicro row -- missed when the PR #31 merge landed since neither was in scope for that commit. - external_keyboard.md and build_flags.md now cover CARDKB_I2C alongside ENV_PIN_SDA/ENV_PIN_SCL: the PR generalised CardKB bus selection to a single macro after build_flags.md was first written, so the doc only had the older (still valid, but no longer the only) form. - release-notes.md: added the ProMicro solo build and the new Build Flags reference page to v1.26's What's New. Co-Authored-By: Claude Sonnet 5 --- README.md | 1 + docs/solo_features/build_flags.md | 3 ++- docs/solo_features/external_keyboard.md | 6 ++++++ release-notes.md | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 76d506f1..ff6ce113 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,7 @@ git config merge.ours.driver true | `heltec_v4_companion_solo_dual` | Heltec LoRa32 V4 *(experimental)* | | `M5Stack_Cardputer_ADV_companion_solo_dual` | M5Stack Cardputer ADV *(experimental)* | | `LilyGo_T-Echo-Lite_keyshield_companion_solo_dual` | LilyGO T-Echo Lite + KeyShield *(experimental)* | +| `ProMicro_companion_solo_dual` | ProMicro (nRF52840) + CardKB *(experimental)* | ```sh pio run -e # build only diff --git a/docs/solo_features/build_flags.md b/docs/solo_features/build_flags.md index 655a1cdb..4c552b77 100644 --- a/docs/solo_features/build_flags.md +++ b/docs/solo_features/build_flags.md @@ -48,7 +48,8 @@ existing `solo//platformio.ini` for what's already claimed). | Flag | Adds | | --- | --- | -| `ENV_PIN_SDA` / `ENV_PIN_SCL` | CardKB (M5Stack I2C keyboard, addr `0x5F`) on a second I2C bus. Probed at boot — harmless with nothing plugged in. See [External Keyboard & Joystick](./external_keyboard.md). | +| `ENV_PIN_SDA` / `ENV_PIN_SCL` | CardKB (M5Stack I2C keyboard, addr `0x5F`) on a second I2C bus (resolves to `Wire1`). Probed at boot — harmless with nothing plugged in. See [External Keyboard & Joystick](./external_keyboard.md). | +| `CARDKB_I2C=` | Same CardKB support, naming the bus directly — for a board with no free pins for a second bus, set `CARDKB_I2C=Wire` to share the primary bus (already used by the display/RTC) instead of defining `ENV_PIN_SDA`/`ENV_PIN_SCL`. Takes precedence if both are somehow set. | | `UI_HAS_JOYSTICK=1` + `UI_HAS_JOYSTICK_UPDOWN=1` (optional) + `JOYSTICK_UP` / `JOYSTICK_DOWN` / `JOYSTICK_LEFT` / `JOYSTICK_RIGHT` + `PIN_USER_BTN` + `PIN_BACK_BTN` | A wired joystick (four direction contacts + a press contact for Enter). Replaces single-button navigation entirely once enabled. See [External Keyboard & Joystick](./external_keyboard.md). | | `PIN_GPIO1` .. `PIN_GPIO4` | Up to four general-purpose pins, each independently switchable between Off / Input / Output (GPIO1/GPIO2 also get an Analog step, if wired to an ADC-capable pin) from Tools › GPIO, and via the `!gpio1`..`!gpio4` bot commands. Not restricted to any particular board — works anywhere the pins are actually free. See [Tools Screen › GPIO](./tools_screen/tools_screen.md#gpio). | | `PIN_HALL_SENSOR` + `HALL_ACTIVE_HIGH=1` (optional) | A Hall-effect or reed sensor for a magnetic flip cover: closing locks and blanks the screen instantly, opening unlocks and wakes it, no combo either way. `HALL_ACTIVE_HIGH` is only for a module wired to pull the pin high (rather than low) when the magnet is near. See [Screen Lock › Magnetic cover](./screen_lock/screen_lock.md#magnetic-cover-hall-sensor). | diff --git a/docs/solo_features/external_keyboard.md b/docs/solo_features/external_keyboard.md index 56f3978a..12ffb90e 100644 --- a/docs/solo_features/external_keyboard.md +++ b/docs/solo_features/external_keyboard.md @@ -23,6 +23,7 @@ see [Built-in keyboards](#built-in-keyboards-cardputer-adv-t-echo-lite--keyshiel | Heltec V4 *(experimental)* | ✅ solder to free GPIOs | ✅ solder to free GPIOs | | M5Stack Cardputer ADV *(experimental)* | — | built-in keyboard instead, see below | | LilyGO T-Echo Lite + KeyShield *(experimental)* | — | built-in keypad instead, see below | +| ProMicro (nRF52840) *(experimental)* | ✅ shares the primary I2C bus (D8/D7) — no free pins for a second bus | — | --- @@ -35,6 +36,11 @@ for it once at boot — nothing to enable in Settings. The same bus is scanned for environment sensors, so a CardKB and a sensor can share it. +On a board with no free pins for a second bus (ProMicro), CardKB instead +shares the primary bus already used by the display/RTC — set `CARDKB_I2C=Wire` +as a build flag rather than `ENV_PIN_SDA`/`ENV_PIN_SCL`. See +[Build Flags](./build_flags.md) for both forms. + ### Typing Printable characters insert straight at the cursor, bypassing the on-screen grid diff --git a/release-notes.md b/release-notes.md index bc6ce158..6a007570 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,8 @@ - **On-device community scope, and repeater-side scope filtering.** Settings › Radio gets a **Scope** field — type a region/community name (e.g. `pl`) and every device typing the same name derives the same shared tag, no key exchange needed; it's what your own DM/channel sends carry, previously only settable from a connected app. Tools › Repeater gains **Scope only** (only relay flood traffic matching your own scope, or one of the new **Extra scopes** below — a no-op until a scope is actually set, so it can't silently blackhole all forwarding) and **Extra scopes** (comma-separated additional regions to relay for, without changing what scope this device's own messages send under). - **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. ### Fixes From 77f4ac48d68112ee71b8adf95ba2617ca354ab71 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:44:12 +0200 Subject: [PATCH 09/11] docs: credit tchellow in README Contributors PR #31 (ProMicro CardKB support) landed a few commits back but the Contributors list was never updated. Co-Authored-By: Claude Sonnet 5 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ff6ce113..8cbde4e8 100644 --- a/README.md +++ b/README.md @@ -252,5 +252,6 @@ Big thanks to the people who contributed to this fork: - [vanous](https://github.com/vanous) - [marczykm](https://github.com/marczykm) +- [tchellow](https://github.com/tchellow) Built on upstream [MeshCore](https://github.com/meshcore-dev/MeshCore) and its [community](https://github.com/meshcore-dev/MeshCore/graphs/contributors). 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 10/11] 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 From 9e90758900fdfbc181f4ec6a54c058e39634bb14 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 29 Aug 2026 19:02:22 +0200 Subject: [PATCH 11/11] docs: fix pre-merge audit gaps (missing boards, Ext. KB row) Full documentation audit before merging 1.26 into main turned up: - README's Supported Devices table was missing GAT562 Mesh Watch13 (a CI-built, shipping firmware with zero end-user docs -- pre-existing gap, not introduced by this branch) and ProMicro (added to the build table by an earlier commit but never to the device list up top). Building-from-source's env table was also missing Watch13. - settings_screen.md's Keyboard table never documented the Ext. KB (Full/Compact) setting, despite external_keyboard.md already describing it in detail. - nrf52_power_management.md's board-support table only listed 5 of the 13 boards that actually have a PowerMgtConfig implementation wired up (grep-verified against every variants/*/*.cpp defining `power_config`) -- added the other 8, and fixed one board's listed name (RAK 3401, not "RAK WisMesh Tag") to match its actual getManufacturerName(). Co-Authored-By: Claude Sonnet 5 --- README.md | 5 ++++- docs/nrf52_power_management.md | 11 +++++++++-- docs/solo_features/settings_screen/settings_screen.md | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8cbde4e8..9a04af34 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,18 @@ Solo firmware thread: https://discord.com/channels/1495203904898728149/150529433 | Seeed Wio Tracker L1 (OLED) | nRF52840 | SSD1306 / SH1106 128 × 64 | `solo--WioTrackerL1.uf2` | | Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo--WioTrackerL1Eink.uf2` | | GAT562 30S Mesh Kit | nRF52840 | SSD1306 128 × 64 | `solo--GAT562-30S-Mesh-Kit.uf2` | +| GAT562 Mesh Watch13 *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo--GAT562-Mesh-Watch13.uf2` | | Heltec LoRa32 V3 *(experimental)* | ESP32-S3 | SSD1306 128 × 64 | `solo--Heltec-v3-merged.bin` | | Heltec LoRa32 V4 *(experimental)* | ESP32-S3 | SSD1306 128 × 64 | `solo--heltec-v4-merged.bin` | | M5Stack Cardputer ADV *(experimental)* | ESP32-S3 | ST7789 TFT 240 × 135 | `solo--M5Stack-Cardputer-ADV-merged.bin` | | LilyGO T-Echo Lite + KeyShield *(experimental)* | nRF52840 | GxEPD2 250 × 122 | `solo--LilyGo-T-Echo-Lite-keyshield.uf2` | +| ProMicro *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo--ProMicro.uf2` | All firmware files are published on the [releases page](https://github.com/MarekZegare4/MeshCore-Solo/releases). Each binary supports both BLE and USB serial — there are no separate BLE/USB builds. The MCU column decides how you flash: nRF52840 boards take a drag-and-drop `.uf2`, ESP32-S3 boards take a `.bin` written with a flasher — see [Flashing](#flashing). -The Wio Tracker L1s, GAT562 and Cardputer ADV work out of the box (the Cardputer has a built-in QWERTY keyboard). Heltec V3/V4 need [a keyboard or joystick wired up](#hardware-setup--heltec-v3--v4) first. The T-Echo Lite needs the KeyShield add-on (its own T9 keypad) to be usable standalone — see [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md). +The Wio Tracker L1s, both GAT562 boards and Cardputer ADV work out of the box (the Cardputer has a built-in QWERTY keyboard). Heltec V3/V4 need [a keyboard or joystick wired up](#hardware-setup--heltec-v3--v4) first. The T-Echo Lite needs the KeyShield add-on (its own T9 keypad) to be usable standalone. ProMicro has no onboard buttons at all — a CardKB is required, sharing the board's primary I2C bus — see [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md).