mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 11:16:39 +00:00
docs: UI Core + frontends design (draft)
Shared hardware-independent UI Core (engines, models, declarative settings schema) behind MyMesh's AbstractUITask, with ui-lvgl (rich, Wio Tracker L2 first) and ui-new (lite, OLED/e-ink/nRF52) as frontends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# UI Core + frontends — design
|
||||
|
||||
Status: 📋 draft for review (2026-09-24). Branch: `wio-tracker-l2`.
|
||||
|
||||
## Why
|
||||
|
||||
The Wio Tracker L2 (320×240 colour touch LCD, no joystick) gets a new LVGL 9
|
||||
interface. Rather than a second, parallel copy of everything Solo does, the
|
||||
application logic moves out of `ui-new` into a hardware-independent **UI Core**
|
||||
that every frontend shares:
|
||||
|
||||
| Frontend | Devices | Toolkit | Input |
|
||||
|---|---|---|---|
|
||||
| `ui-lvgl` (rich) | Wio Tracker L2 first; later other ESP32-S3 colour boards | LVGL 9 | pointer (touch) + focus (buttons, CardKB) |
|
||||
| `ui-new` (lite) | OLED / e-ink / nRF52 (Wio L1, T-Echo Lite, Heltec, …) | `DisplayDriver` | focus (joystick, buttons, CardKB) |
|
||||
|
||||
A feature written once in the Core (live share, locator, unread tracking, a new
|
||||
setting) then shows up on both. `ui-new` is not rewritten: it is re-pointed at
|
||||
the Core and stays the lite frontend.
|
||||
|
||||
Non-goals: changing the mesh protocol, `MyMesh`, persistence formats
|
||||
(`NodePrefs` layout, `/scopes1`, trail files), or the companion app protocol.
|
||||
|
||||
## Current state
|
||||
|
||||
`ui-new` is 18.5k lines. `UITask` implements `AbstractUITask` (the interface
|
||||
`MyMesh` calls into) *and* hosts the screens, so logic and drawing are
|
||||
interleaved. Classified:
|
||||
|
||||
**Models — already UI-free, move as-is**
|
||||
- `MessageHistory.h` — channel (48) / DM (32) rings, unread counters + overflow flags.
|
||||
- `Trail.h` / `TrailStore`, waypoints (`WaypointsView` storage half), `ScopeList.h`.
|
||||
|
||||
**Engines — logic living inside `UITask.cpp` / `UITask.h`**
|
||||
- Unread tracking — DM unread table (`_dm_unread_table`, `newMsg`, `addDMMsg`, `reconcileDMUnread`), room unread.
|
||||
- Notifications — `showAlert`, `notify`, `SoundNotifier`, LED (`userLedHandler`), wake-on-message (`checkDisplayOn`, auto-off).
|
||||
- Live share — session timer, movement/heartbeat gate, `sendLocationShare`, scope guard, `onSharedLocation`/live-track expiry.
|
||||
- Locator — geofence state machine, proximity beeper (`evaluateLocator`, `fireLocator`, targets).
|
||||
- Trail — sampling, auto-pause, low-battery auto-save.
|
||||
- Course over ground — `pushCogFix`, `currentCourse`, `currentLocation`.
|
||||
- Clock tools — alarm / countdown / ring (`evaluateAlarm`, `tickClockTools`, `fireClockAlert`).
|
||||
- Ping — `startPing`, `handlePingResult`.
|
||||
- Device controls — GPS on/off, GPIO (`setGpioMode`, bot GPIO), buzzer mode/volume, brightness, radio apply (`applyTxPower`, `applyApc`, `applyRadioParams`, …).
|
||||
- Bot hooks — `botSetGPS`, `botBuzz`, `botSetGPIO`, …
|
||||
|
||||
**Screen-embedded logic (harder to extract)**
|
||||
- `SettingsScreen.h` (1.3k lines) — ~40 items, each with inline get/format/step/apply code.
|
||||
- `NearbyScreen.h` — contact discovery/sorting; `AdminScreen.h` — repeater admin session; `BotScreen.h`; `RepeaterScreen.h`; `MessagesScreen.h` (2.6k lines) — list ordering, favourites, context-menu actions mixed with rendering.
|
||||
|
||||
**Pure view / widgets — stay in `ui-new`**
|
||||
- Rendering of every screen, `KeyboardWidget`, `PopupMenu`, `AccordionList`, `TabBar`, `icons.h`, `GfxUtils.h`, marquee, badges.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
MyMesh ──(AbstractUITask callbacks)──► UiCore ──events──► Frontend (ui-new | ui-lvgl)
|
||||
▲ │ ▲ │
|
||||
└──────────── mesh actions ────────────┘ └──── actions ─────┘
|
||||
engines, models, settings schema
|
||||
Platform services (per board): Sound, DisplayPower, Input sources, Led
|
||||
```
|
||||
|
||||
### 1. `UiCore` owns the mesh-facing interface
|
||||
|
||||
`UiCore` implements `AbstractUITask`. `MyMesh` keeps calling exactly the same
|
||||
methods; they land in the Core, which updates models/engines and emits events.
|
||||
The frontend no longer implements `AbstractUITask` at all. This is the single
|
||||
point that makes both frontends receive identical behaviour.
|
||||
|
||||
### 2. Engines
|
||||
|
||||
One class per engine from the inventory above, each with `begin(prefs, …)`,
|
||||
`loop(now_ms)` and a small public API. No drawing, no `DisplayDriver`, no
|
||||
screen pointers. Engines that need to show something emit an event
|
||||
(`AlertRequested{text, ms}`, `LocShareEnded`, `AlarmFired`, …) instead of
|
||||
calling `showAlert()` directly.
|
||||
|
||||
Constraints (the lite frontend runs on nRF52 with ~215 KB flash / ~65 KB RAM
|
||||
free): no heap allocation, no RTTI/exceptions, no STL containers, fixed-size
|
||||
buffers — the same rules `ui-new` follows today. Extraction must be roughly
|
||||
size-neutral on `WioTrackerL1_companion_solo_dual`.
|
||||
|
||||
### 3. Events: Core → frontend
|
||||
|
||||
A small fixed-size queue of tagged events plus coarse dirty flags
|
||||
(`DIRTY_MESSAGES`, `DIRTY_CONTACTS`, `DIRTY_STATUS`, …). The frontend drains
|
||||
the queue in its `loop()` and redraws what is dirty. No callbacks into the
|
||||
frontend from inside mesh processing — which also keeps the e-ink busy-wait
|
||||
pump rule ("never re-enter UI code from the radio path") trivially true.
|
||||
|
||||
### 4. Actions: frontend → Core
|
||||
|
||||
Plain methods on the Core facade: `sendDM`, `sendChannelMsg`, `markRead`,
|
||||
`setLiveShare`, `setLocatorTarget`, `toggleGps`, `startPing`, … Both frontends
|
||||
call the same ones, so e.g. "marking a channel read" has one implementation.
|
||||
|
||||
### 5. Declarative settings schema
|
||||
|
||||
Replaces the per-item code in `SettingsScreen`:
|
||||
|
||||
```cpp
|
||||
struct SettingDef {
|
||||
const char* id; // stable key, also used for search / CLI
|
||||
const char* label;
|
||||
uint8_t section; // Radio / System / Display / …
|
||||
SettingType type; // Bool, Enum, Int, Text, Action
|
||||
// accessors over NodePrefs; enum labels; min/max/step
|
||||
int (*get)(const NodePrefs&);
|
||||
void (*set)(NodePrefs&, int);
|
||||
const char* (*label_for)(int v); // Enum/Int formatting
|
||||
bool (*visible)(const NodePrefs&); // build- or state-dependent rows
|
||||
bool (*locked)(const NodePrefs&, const char** why); // "Off while repeating"
|
||||
void (*apply)(UiCore&); // side effect after set (radio, display…)
|
||||
};
|
||||
```
|
||||
|
||||
`ui-new` renders it as today's accordion list; `ui-lvgl` as switches, dropdowns
|
||||
and sliders. Build-time gating (`FEAT_*`) stays in the table via `#if`, so a
|
||||
hidden row costs no flash.
|
||||
|
||||
### 6. Platform services
|
||||
|
||||
Per-board implementations behind small interfaces: `Sound` (buzzer PWM today;
|
||||
I2S ES8311 tone generator on L2), `DisplayPower` (auto-off, brightness), `Led`,
|
||||
and input sources. Input reaches frontends in two forms: **focus keys** (the
|
||||
existing `KEY_*` codes — joystick, buttons, CardKB) and **pointer events**
|
||||
(`down/move/up` with coordinates — touch). LVGL consumes both natively; `ui-new`
|
||||
consumes keys only.
|
||||
|
||||
## `ui-lvgl` specifics
|
||||
|
||||
- LVGL 9 (pinned), memory in a PSRAM pool; partial-render buffers in internal RAM, flush over QSPI via LovyanGFX.
|
||||
- Theme tokens (colours, spacing, typography) in one place — the first chance to realise the "Amber Trace" direction in colour.
|
||||
- Fonts with full Latin/Polish/Cyrillic coverage.
|
||||
- Runs in the WASM sim at 320×240 with mouse as touch, so most UI work needs no flashing.
|
||||
- PR #3381's LVGL UI is a reference for platform glue (flush, GT911, PSRAM pool) only; its screens are not adopted.
|
||||
|
||||
## Migration plan
|
||||
|
||||
Each step keeps `WioTrackerL1_companion_solo_dual` behaviour identical and is
|
||||
checked in the sim plus on L1 hardware before the next one.
|
||||
|
||||
1. **Skeleton.** `examples/companion_radio/ui-core/`; add it to every solo env's `build_src_filter` / include path. Move `MessageHistory` + DM unread tracking into it; `ui-new` uses them through the Core.
|
||||
2. **Engines, one per commit.** Clock tools → ping → course-over-ground → live share → locator → trail → notifications. `UITask` shrinks to screen management + drawing.
|
||||
3. **Flip the interface.** `UiCore` implements `AbstractUITask`; `ui-new`'s `UITask` becomes a frontend fed by events.
|
||||
4. **Settings schema.** Convert `SettingsScreen` section by section.
|
||||
5. **`ui-lvgl` skeleton** for L2 + sim target: boot, home, message list/conversation, keyboard. Then screens by priority.
|
||||
6. Contacts/Nearby, Admin, Bot logic extraction as the LVGL screens for them are built.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Upstream merge cost accepted** (2026-09-24). Moving code out of `ui-new`
|
||||
will conflict with upstream edits to the same files; this fork's `ui-new` is
|
||||
already heavily diverged, and upstream is itself working toward a larger UI
|
||||
abstraction — revisit alignment when that lands.
|
||||
- **Scope is UI only** (2026-09-24). The settings schema and event queue serve
|
||||
the on-device frontends; CLI and companion-app settings/push paths are
|
||||
untouched for now.
|
||||
Reference in New Issue
Block a user