mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 03:06:39 +00:00
chore: stage I1 -- repo cleanup, minimal README
Working plans (docs/development, docs/design redesign notes) removed; the two developer guides moved to docs/developer/. README cut to devices, flashing, links and building (Wio Tracker L2 added); its feature list and doc tables moved to docs/solo_features/README.md. RELEASE.md describes the Solo release flow. Upstream's GitHub Pages workflow (docs.meshcore.io) dropped, CLAUDE.md untracked, Buy Me a Coffee back in FUNDING.yml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
github: meshcore-dev
|
github: meshcore-dev
|
||||||
|
buy_me_a_coffee: MarekZegarek
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
name: Build and deploy Docs site to GitHub Pages
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
github-pages:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.13'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
pip install mkdocs-material
|
|
||||||
mkdocs build
|
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
uses: peaceiris/actions-gh-pages@v4.1.0
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
cname: docs.meshcore.io
|
|
||||||
publish_dir: ./site
|
|
||||||
publish_branch: 'gh-pages'
|
|
||||||
@@ -28,3 +28,6 @@ variants/sim/web/maps/
|
|||||||
# graphify
|
# graphify
|
||||||
graphify-out/
|
graphify-out/
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# local assistant notes
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
## graphify
|
|
||||||
|
|
||||||
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
|
|
||||||
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
|
|
||||||
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
|
|
||||||
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
|
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
# MeshCore Solo Companion Firmware
|
# MeshCore Solo Companion Firmware
|
||||||
|
|
||||||
A fork of the official [MeshCore](https://github.com/meshcore-dev/MeshCore) companion radio firmware with a full standalone on-device UI and extra features.
|
A fork of the official [MeshCore](https://github.com/meshcore-dev/MeshCore) companion radio firmware with a full standalone on-device UI — messages, contacts, GPS navigation and tools without a phone.
|
||||||
|
|
||||||
**Try it out live in your browser: [solo.marekzegarek.com](https://solo.marekzegarek.com)**
|
**Try it live in your browser: [solo.marekzegarek.com](https://solo.marekzegarek.com)**
|
||||||
|
|
||||||
Join the discussion on the official MeshCore Discord: https://discord.gg/sdhYArU2jr
|
[](https://buymeacoffee.com/MarekZegarek)
|
||||||
|
|
||||||
Solo firmware thread: https://discord.com/channels/1495203904898728149/1505294337884553447
|
Discussion: [MeshCore Discord](https://discord.gg/sdhYArU2jr) — [Solo firmware thread](https://discord.com/channels/1495203904898728149/1505294337884553447)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Supported Devices
|
## Supported devices
|
||||||
|
|
||||||
| Device | MCU | Display | Firmware file |
|
| Device | MCU | Display | Firmware file |
|
||||||
| ------ | --- | ------- | ------------- |
|
| ------ | --- | ------- | ------------- |
|
||||||
| Seeed Wio Tracker L1 (OLED) | nRF52840 | SSD1306 / SH1106 128 × 64 | `solo-<version>-WioTrackerL1.uf2` |
|
| Seeed Wio Tracker L1 (OLED) | nRF52840 | SSD1306 / SH1106 128 × 64 | `solo-<version>-WioTrackerL1.uf2` |
|
||||||
| Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo-<version>-WioTrackerL1Eink.uf2` |
|
| Seeed Wio Tracker L1 (E-ink) | nRF52840 | GxEPD2 250 × 122 | `solo-<version>-WioTrackerL1Eink.uf2` |
|
||||||
|
| Seeed Wio Tracker L2 | ESP32-S3 | 320 × 240 touch LCD | `solo-<version>-Wio-Tracker-L2-merged.bin` |
|
||||||
| GAT562 30S Mesh Kit | nRF52840 | SSD1306 128 × 64 | `solo-<version>-GAT562-30S-Mesh-Kit.uf2` |
|
| GAT562 30S Mesh Kit | nRF52840 | SSD1306 128 × 64 | `solo-<version>-GAT562-30S-Mesh-Kit.uf2` |
|
||||||
| GAT562 Mesh Watch13 *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo-<version>-GAT562-Mesh-Watch13.uf2` |
|
| GAT562 Mesh Watch13 *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo-<version>-GAT562-Mesh-Watch13.uf2` |
|
||||||
| Heltec LoRa32 V3 *(experimental)* | ESP32-S3 | SSD1306 128 × 64 | `solo-<version>-Heltec-v3-merged.bin` |
|
| Heltec LoRa32 V3 *(experimental)* | ESP32-S3 | SSD1306 128 × 64 | `solo-<version>-Heltec-v3-merged.bin` |
|
||||||
@@ -24,240 +25,49 @@ Solo firmware thread: https://discord.com/channels/1495203904898728149/150529433
|
|||||||
| LilyGO T-Echo Lite + KeyShield *(experimental)* | nRF52840 | GxEPD2 250 × 122 | `solo-<version>-LilyGo-T-Echo-Lite-keyshield.uf2` |
|
| LilyGO T-Echo Lite + KeyShield *(experimental)* | nRF52840 | GxEPD2 250 × 122 | `solo-<version>-LilyGo-T-Echo-Lite-keyshield.uf2` |
|
||||||
| ProMicro *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo-<version>-ProMicro.uf2` |
|
| ProMicro *(experimental)* | nRF52840 | SSD1306 128 × 64 | `solo-<version>-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.
|
Firmware files are on the [releases page](https://github.com/MarekZegare4/MeshCore-Solo/releases). Every binary serves the companion app over both BLE and USB serial.
|
||||||
|
|
||||||
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).
|
Heltec V3/V4 need [a keyboard or joystick wired up](./docs/solo_features/external_keyboard.md#wiring-heltec-v3--v4); ProMicro needs a CardKB; the T-Echo Lite needs the KeyShield. The rest work out of the box.
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
<!-- **Enclosures (Wio Tracker L1)**
|
|
||||||
- [E-ink case](https://www.printables.com/model/1420534-seeed-wio-tracker-l1-e-ink-enclosure)
|
|
||||||
- [OLED case](https://www.printables.com/model/1380791-meshpack-seeed-l1-oled) -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Feature highlights
|
|
||||||
|
|
||||||
- Extended language support — one unified 6×9 font (Latin, Greek, Cyrillic) plus on-screen keyboard alphabets for Cyrillic, Greek, Polish, Czech, Slovak, German, French, Spanish, Portuguese and Nordic. Pick two in Settings › Keyboard (**Main**/**Additional**) and switch between them while typing
|
|
||||||
|
|
||||||
- Enabled sensor screens with support for onboard sensors (temperature, humidity, pressure, luminosity, CO₂) and GPS data
|
|
||||||
|
|
||||||
- **GPS navigation** — a full navigation suite that needs no extra hardware (details in the [Tools Screen](./docs/solo_features/tools_screen/tools_screen.md) docs):
|
|
||||||
|
|
||||||
- **Waypoints** — mark a spot (car, camp, water…) with a short label, see it on the trail map, and get live bearing + distance back to it; the list always offers a one-tap backtrack to where your trail started
|
|
||||||
- **GPS compass** — heading derived from course-over-ground (no magnetometer needed), shown as a clear scrolling heading tape with a large degrees + cardinal readout
|
|
||||||
- **Navigate to anything** — a saved waypoint, a node straight from Nearby Nodes, or a location someone shares with you in a message
|
|
||||||
- **Share & save locations** — send a waypoint to a contact or channel; on the other end, navigate to or save any shared location with one menu
|
|
||||||
- **Live location sharing** — broadcast your position over the mesh as you move (movement-gated, to a channel or contact) and see others who share theirs as pins on the map and live distance/bearing in Nearby
|
|
||||||
- **Locator** — arm a geofence around a waypoint or a person, get alerted on arrive/leave or near/far, with an optional homing beeper that speeds up as you close in. Set from the Locator screen, Nearby Nodes, or Waypoints; target shown as a flag on the map
|
|
||||||
- **GPS trail** — background route recording with an auto-fit map (waypoints + live position), summary stats, auto-pause on stops, and [GPX export](#solo-tools)
|
|
||||||
- **Metric or imperial** — one global Units setting drives every distance and speed across the UI
|
|
||||||
|
|
||||||
- [Messages Screen](./docs/solo_features/message_screen/message_screen.md) — view and send messages, open message details, reply with quick messages or custom text, navigate to / save locations shared in a message, per-channel notification and melody overrides, add/edit/delete channels on-device
|
|
||||||
|
|
||||||
- [Favourites Dial](./docs/solo_features/favourites_dial/favourites_dial.md) — pin up to six contacts for quick access from the home screen
|
|
||||||
|
|
||||||
- [Settings Screen](./docs/solo_features/settings_screen/settings_screen.md) — configure display, sound, home page order, radio and system settings
|
|
||||||
|
|
||||||
- [Clock Screen](./docs/solo_features/clock_screen/clock_screen.md) — view time and date plus up to three configurable data fields, with built-in clock tools (one-shot alarm, countdown timer, stopwatch)
|
|
||||||
|
|
||||||
- [Screen Lock](./docs/solo_features/screen_lock/screen_lock.md) — lock the device to prevent accidental keypresses, with a lock screen showing time and sensor data
|
|
||||||
|
|
||||||
- [Tools Screen](./docs/solo_features/tools_screen/tools_screen.md) — GPS trail & waypoints, compass, nearby nodes (with ping & navigate), ringtone editor, remote bot, auto-advert, live location sharing, locator, diagnostics, repeater, remote admin
|
|
||||||
|
|
||||||
- [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md) — optional, auto-detected: **CardKB** for typing without the on-screen grid (Fn+Enter submits, Fn+letter picks an accent, Tab is Hold-Enter, Fn+Esc locks), plus a **wired joystick** for boards without one. Compact mode makes CardKB-only operation practical
|
|
||||||
|
|
||||||
- **Battery saving (radio)** — two optional, independent toggles under Settings › Radio:
|
|
||||||
- **Pwr save** — hardware duty-cycle receive (SX126x `SetRxDutyCycle`): the radio cycles RX↔sleep on its own and wakes on a preamble, cutting average RX current with only a little added receive latency
|
|
||||||
- **Auto pwr** — Adaptive Power Control: trims actual TX power on strong links (from ACK SNR) and ramps back up to the configured ceiling on weak/lost links; the home screen shows the live power
|
|
||||||
|
|
||||||
### E-ink Display (Wio Tracker L1)
|
|
||||||
|
|
||||||
The e-ink variant targets the Wio Tracker L1 fitted with a 2.13″ GxEPD2 panel (250 × 122 px). Every screen is adapted for it:
|
|
||||||
|
|
||||||
- **Adaptive layout** — every screen reflows correctly in both landscape (250 × 122) and portrait (122 × 250) orientations
|
|
||||||
- **Display rotation** — configurable in Settings › Display; applied immediately and persisted across reboots
|
|
||||||
- **Joystick rotation** — independent of display rotation; useful for custom enclosures
|
|
||||||
- **Full refresh interval** — configurable in Settings › Display; reduces ghosting on long sessions
|
|
||||||
- **Clock seconds suppressed by default** — seconds are hidden to reduce per-second panel refreshes and extend display lifetime; re-enable in Settings › Display
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Flashing
|
## Flashing
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> When migrating from official or other custom firmware, backup your data and **perform a factory reset** to prevent conflicts with existing settings:
|
> Coming from official or other custom firmware: back up your data in the companion app, then **Erase flash** in the [MeshCore Flasher](https://meshcore.io/flasher) before flashing. Updating from an earlier Solo release doesn't need this unless the release notes say so.
|
||||||
>
|
|
||||||
> 1. Open device settings in the companion app and download a data backup
|
|
||||||
> 2. Go to [MeshCore Flasher](https://meshcore.io/flasher), select your device, and perform **Erase flash** before flashing
|
|
||||||
>
|
|
||||||
> Updating from an earlier Solo release does not need this, unless the release notes say otherwise.
|
|
||||||
|
|
||||||
### nRF52840 boards — Wio Tracker L1, GAT562, T-Echo Lite + KeyShield
|
**nRF52840 boards** — press reset twice quickly; the device shows up as a USB drive. Copy the `.uf2` onto it.
|
||||||
|
|
||||||
1. Download the `.uf2` file for your device from the [releases page](https://github.com/MarekZegare4/MeshCore-Solo/releases)
|
**ESP32-S3 boards** — flash the `-merged.bin` at offset `0x0` with the [MeshCore Flasher](https://meshcore.io/flasher) or `esptool.py --chip esp32s3 write_flash 0x0 <file>-merged.bin`. The Wio Tracker L2 then updates itself over WiFi (Settings › System › Firmware update).
|
||||||
2. Press reset twice quickly to enter bootloader mode — the device should appear as a mass storage drive on your computer
|
|
||||||
3. Copy the `.uf2` file to the drive to flash the firmware
|
|
||||||
|
|
||||||
### ESP32-S3 boards — Heltec V3, V4, Cardputer ADV
|
|
||||||
|
|
||||||
ESP32-S3 has no UF2 bootloader and no mass-storage mode. Releases ship a single **`-merged.bin`** per board — bootloader, partition table and app in one image — which goes to offset `0x0`:
|
|
||||||
|
|
||||||
- **[MeshCore Flasher](https://meshcore.io/flasher)** or any Web Serial ESP tool — select the `-merged.bin` and flash at `0x0`
|
|
||||||
- **esptool** — `esptool.py --chip esp32s3 write_flash 0x0 solo-<version>-<device>-merged.bin`
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Building from source also produces an app-only `firmware.bin`. It belongs at offset `0x10000` and needs a bootloader already on the chip — flashing it at `0x0`, or onto a freshly erased chip, leaves the device dead-silent. `pio run -e <env> -t upload` writes bootloader, partition table and app at their correct offsets in one go; a hand-flashed single `.bin` doesn't. Releases only ship the merged image, so this only matters when building yourself.
|
|
||||||
|
|
||||||
### Connecting the companion app
|
|
||||||
|
|
||||||
Applies to every board: each binary serves the companion app over **both** BLE and USB serial, but not at once.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> BLE has priority over USB serial. While a BLE connection is active the USB protocol is suspended. To connect the app over USB, disconnect from BLE first or disable BLE directly on the device.
|
> BLE has priority over USB serial: while a BLE connection is active the USB protocol is suspended.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hardware setup — Heltec V3 / V4
|
|
||||||
|
|
||||||
A single button can't drive the solo UI, and neither Heltec board has one built in. Both stock builds enable an **M5Stack CardKB** and a **wired joystick** on these pins — V3 and V4 are pin-compatible, so the assignment is identical:
|
|
||||||
|
|
||||||
| Function | GPIO | Notes |
|
|
||||||
| -------- | :--: | ----- |
|
|
||||||
| CardKB SDA | **3** | second I2C bus (`Wire1`) — *not* the OLED's 17/18 |
|
|
||||||
| CardKB SCL | **4** | |
|
|
||||||
| Joystick UP | **23** | |
|
|
||||||
| Joystick DOWN | **6** | |
|
|
||||||
| Joystick LEFT | **47** | |
|
|
||||||
| Joystick RIGHT | **48** | |
|
|
||||||
| Joystick press — Enter | **33** | the stick's own fifth contact; required whenever the joystick is enabled |
|
|
||||||
| Back | **0** | the onboard PRG button — nothing to wire |
|
|
||||||
|
|
||||||
Each joystick contact simply shorts its pin to GND — the firmware enables the internal pull-ups, so no external resistors are needed. CardKB needs power and ground alongside SDA/SCL; check your unit's own voltage rating before picking a rail.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This assignment is confirmed working on real **V4** hardware. V3 inherits it because Heltec documents the two boards as pin-compatible, but it hasn't been verified on a physical V3 — worth a continuity check against your own module before soldering.
|
|
||||||
|
|
||||||
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 [`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).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### This fork
|
[docs/solo_features](./docs/solo_features/README.md) — features, screens, external keyboards, build flags and developer guides.
|
||||||
|
|
||||||
| Document | Description |
|
**Solo Tools** — [a web app](https://marekzegare4.github.io/Solo-tools/) (Chromium, Web Serial) that takes screenshots and exports the GPS trail as GPX over USB; the same as local scripts in [tools/](./tools/README.md).
|
||||||
| -------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
||||||
| [Messages Screen](./docs/solo_features/message_screen/message_screen.md) | Sending messages, context menus, reply, navigate to / save shared locations, Notif/Melody overrides |
|
|
||||||
| [Favourites Dial](./docs/solo_features/favourites_dial/favourites_dial.md) | Pinned contacts grid, unread badges, pin/unpin |
|
|
||||||
| [Clock Screen](./docs/solo_features/clock_screen/clock_screen.md) | Clock page, date, configurable data fields, alarm / timer / stopwatch |
|
|
||||||
| [Settings Screen](./docs/solo_features/settings_screen/settings_screen.md) | All settings sections with values and interactions |
|
|
||||||
| [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 |
|
|
||||||
|
|
||||||
### Upstream MeshCore
|
|
||||||
|
|
||||||
| Document | Description |
|
|
||||||
| -------------------------------------------------- | ------------------------------------------------ |
|
|
||||||
| [FAQ](./docs/faq.md) | Frequently asked questions |
|
|
||||||
| [CLI Commands](./docs/cli_commands.md) | Commands for repeaters, room servers and sensors |
|
|
||||||
| [Terminal Chat CLI](./docs/terminal_chat_cli.md) | Commands for the terminal chat client |
|
|
||||||
| [Companion Protocol](./docs/companion_protocol.md) | Serial/BLE frame protocol between device and app |
|
|
||||||
| [Packet Format](./docs/packet_format.md) | LoRa packet structure |
|
|
||||||
| [QR Codes](./docs/qr_codes.md) | Channel and contact QR code formats |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Solo Tools
|
## Building
|
||||||
|
|
||||||
All solo builds include screenshot and GPX trail export support out of the box — no special build flags required.
|
|
||||||
|
|
||||||
### Web app — nothing to install
|
|
||||||
|
|
||||||
Open [Solo Tools](https://marekzegare4.github.io/Solo-tools/) in a browser with Web Serial support (Chromium-based) and click **Connect device**:
|
|
||||||
|
|
||||||
- **Screenshot** — capture the current display contents as a PNG. Triggered entirely from the browser; nothing to press on the device.
|
|
||||||
- **GPX export** — stream the recorded GPS trail and download a timestamped `.gpx` file. Start it on the device with **Tools › Trail › Hold Enter › Export** once connected.
|
|
||||||
|
|
||||||
### Offline equivalents
|
|
||||||
|
|
||||||
The same two features are available as local scripts — `tools/screenshot.py` and `tools/trail_export.py` — plus a font converter. See [tools/README.md](./tools/README.md).
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Both routes use USB serial, suspended while a BLE connection is active — disconnect BLE first. If the app is connected over **USB**, disconnect that too, or the raw export stream disrupts its frame protocol. (Safe over BLE, since USB receive is ignored then.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
This fork tracks the upstream [MeshCore](https://github.com/meshcore-dev/MeshCore) repository. To prevent upstream changes from overwriting this README during merges, `README.md` is protected via `.gitattributes`. After cloning, run once:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git config merge.ours.driver true
|
pio run -e <env> -t upload # build and flash over USB
|
||||||
|
FIRMWARE_VERSION=v1.0.0 bash build.sh build-firmware <env> # release artifacts into out/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building from source
|
Environments are the `*_solo_dual` (OLED / e-ink) and `*_solo_lvgl` (touch) entries in `solo/<board>/platformio.ini`. Optional hardware (CardKB, joystick, GPIO, buzzer…) is enabled with [build flags](./docs/solo_features/build_flags.md). Releasing: [RELEASE.md](./RELEASE.md).
|
||||||
|
|
||||||
| Environment | Device |
|
This README is protected from upstream merges via `.gitattributes`; after cloning run once `git config merge.ours.driver true`.
|
||||||
| ----------- | ------ |
|
|
||||||
| `WioTrackerL1_companion_solo_dual` | Wio Tracker L1 (OLED) |
|
|
||||||
| `WioTrackerL1Eink_companion_solo_dual` | Wio Tracker L1 (E-ink) |
|
|
||||||
| `GAT562_30S_Mesh_Kit_solo_dual` | GAT562 30S Mesh Kit |
|
|
||||||
| `GAT562_Mesh_Watch13_solo_dual` | GAT562 Mesh Watch13 *(experimental)* |
|
|
||||||
| `Heltec_v3_companion_solo_dual` | Heltec LoRa32 V3 *(experimental)* |
|
|
||||||
| `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 <env> # build only
|
|
||||||
pio run -e <env> -t upload # build and flash over USB
|
|
||||||
FIRMWARE_VERSION=v1.0.0 bash build.sh build-firmware <env> # release artifacts into out/
|
|
||||||
```
|
|
||||||
|
|
||||||
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/<board>/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.
|
|
||||||
|
|
||||||
### Repository layout
|
|
||||||
|
|
||||||
| Path | Contents |
|
|
||||||
| ---- | -------- |
|
|
||||||
| `examples/companion_radio/ui-new/` | the solo UI — screens, widgets, `UITask` |
|
|
||||||
| `src/helpers/ui/` | display drivers, fonts, buttons, buzzer |
|
|
||||||
| `variants/<board>/` | per-board `platformio.ini`, `target.h`, `target.cpp` |
|
|
||||||
| `solo/<board>/` | 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) |
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
Contributions are welcome. Fork the repository, make your changes, and open a pull request. Please follow the existing code style and keep changes focused.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
Big thanks to the people who contributed to this fork:
|
Big thanks to [vanous](https://github.com/vanous), [marczykm](https://github.com/marczykm), [tchellow](https://github.com/tchellow) and [3urobeat](https://github.com/3urobeat).
|
||||||
|
|
||||||
- [vanous](https://github.com/vanous)
|
|
||||||
- [marczykm](https://github.com/marczykm)
|
|
||||||
- [tchellow](https://github.com/tchellow)
|
|
||||||
- [3urobeat](https://github.com/3urobeat)
|
|
||||||
|
|
||||||
Built on upstream [MeshCore](https://github.com/meshcore-dev/MeshCore) and its [community](https://github.com/meshcore-dev/MeshCore/graphs/contributors).
|
Built on upstream [MeshCore](https://github.com/meshcore-dev/MeshCore) and its [community](https://github.com/meshcore-dev/MeshCore/graphs/contributors).
|
||||||
|
|||||||
+15
-13
@@ -1,15 +1,17 @@
|
|||||||
# Releasing Firmware
|
# Releasing Solo firmware
|
||||||
|
|
||||||
GitHub Actions is set up to automatically build and release firmware.
|
1. Add the notes for the new version at the top of `release-notes.md`.
|
||||||
|
2. Push a `v*` tag (e.g. `v1.29`). [Build Solo Firmwares](./.github/workflows/build-solo-firmwares.yml)
|
||||||
|
builds every `*_solo_dual` and `*_solo_lvgl` environment in `solo/` with
|
||||||
|
`FIRMWARE_VERSION` set to the tag and opens a **draft** release with:
|
||||||
|
- `.uf2` + `-ota.zip` (DFU package) for nRF52 boards;
|
||||||
|
- `-merged.bin` (flash at `0x0`) for ESP32 boards;
|
||||||
|
- `-ota.bin` (app image) for the `*_solo_lvgl` boards — what their
|
||||||
|
Settings › System › Firmware update downloads.
|
||||||
|
3. Paste the notes into the draft and publish it. Only a published,
|
||||||
|
non-prerelease release is what devices and the website see as the latest:
|
||||||
|
the on-device update reads `releases/latest`, and the website's simulator
|
||||||
|
is refreshed from its `solo-sim-wasm.zip` ([Build Solo Sim](./.github/workflows/build-solo-sim.yml)).
|
||||||
|
|
||||||
It will automatically build firmware when one of the following tag formats are pushed.
|
The upstream `companion-v*`, `repeater-v*` and `room-server-v*` tags still
|
||||||
|
build the stock firmwares through their own workflows.
|
||||||
- `companion-v1.0.0`
|
|
||||||
- `repeater-v1.0.0`
|
|
||||||
- `room-server-v1.0.0`
|
|
||||||
|
|
||||||
> NOTE: replace `v1.0.0` with the version you want to release as.
|
|
||||||
|
|
||||||
- You can push one, or more tags on the same commit, and they will all build separately.
|
|
||||||
- Once the firmware has been built, a new (draft) GitHub Release will be created.
|
|
||||||
- You will need to update the release notes, and publish it.
|
|
||||||
|
|||||||
@@ -1,218 +0,0 @@
|
|||||||
# Nearby Nodes — analiza i propozycja uporządkowania
|
|
||||||
|
|
||||||
> Branch: `refactor/nearby-nodes` (zmergowany do `main`)
|
|
||||||
> Plik źródłowy: [NearbyScreen.h](../../examples/companion_radio/ui-new/NearbyScreen.h)
|
|
||||||
> Status: **zaimplementowane** — dokument zachowany jako zapis analizy/decyzji.
|
|
||||||
>
|
|
||||||
> **Odchylenia od propozycji (stan faktyczny):**
|
|
||||||
> - Akcji **Filter…** w menu nie ma — duplikowała cykl `LEFT/RIGHT` po typie, więc
|
|
||||||
> filtr został wyłącznie na liście (sekcja 3.1 zakładała Filter… też w menu).
|
|
||||||
> - **Sort** nie jest togglem przez Enter, lecz zmienia się **in-place przez
|
|
||||||
> `LEFT/RIGHT` na podświetlonym wierszu** w popupie (wzorzec ustawień Trail),
|
|
||||||
> a wiersz pojawia się tylko dla źródła Zapisane (skan nie ma dystansu).
|
|
||||||
> - Filtr i sort **utrzymują się** między wejściami na ekran (nie są resetowane).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Jak narzędzie jest zbudowane dzisiaj
|
|
||||||
|
|
||||||
Ekran łączy w sobie **trzy osobne tryby** o własnych listach, widokach
|
|
||||||
szczegółów i popupach:
|
|
||||||
|
|
||||||
```
|
|
||||||
NearbyScreen
|
|
||||||
├── LIST (kontakty zapisane w mesh) ← tryb domyślny
|
|
||||||
│ ├── filtr cyklowany LEFT/RIGHT (7 stanów)
|
|
||||||
│ ├── DETAIL (Enter) → Lat/Lon/Dist/Type/Seen
|
|
||||||
│ │ └── _opts popup (Hold Enter): Navigate / Ping / Save waypoint
|
|
||||||
│ │ └── _ping_menu popup
|
|
||||||
│ ├── NAV view (pełnoekranowa nawigacja)
|
|
||||||
│ └── _ctx_menu popup (Hold Enter): Discover / Navigate / Save waypoint
|
|
||||||
│
|
|
||||||
└── DISCOVER (skan na żywo NODE_DISCOVER_REQ) ← osobny pod-ekran
|
|
||||||
├── lista wyników (karty 2-liniowe)
|
|
||||||
│ Hold Enter = ponowny skan (brak menu!)
|
|
||||||
└── DETAIL (Enter) → pubkey / RSSI / SNR / status
|
|
||||||
└── _ping_menu popup (Hold Enter = od razu ping, bez Options)
|
|
||||||
```
|
|
||||||
|
|
||||||
Stan ekranu trzyma **15+ pól** (`_detail`, `_nav`, `_discover_mode`,
|
|
||||||
`_ddetail`, `_pinging`, `_filter`, dwa komplety `_sel/_scroll`, trzy
|
|
||||||
bufory wyników ping…) i **trzy** instancje `PopupMenu` (`_ctx_menu`,
|
|
||||||
`_opts`, `_ping_menu`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Co konkretnie jest nieuporządkowane
|
|
||||||
|
|
||||||
### 2.1 Jeden cykl filtra miesza trzy różne osie
|
|
||||||
|
|
||||||
`LEFT/RIGHT` przewija 7 stanów:
|
|
||||||
|
|
||||||
```
|
|
||||||
Fav · ALL · Comp · Rpt · Room · Snsr · TIME
|
|
||||||
```
|
|
||||||
|
|
||||||
Ale to nie jest jedna oś — to **trzy** wciśnięte w jeden liniowy cykl:
|
|
||||||
|
|
||||||
| Stan | Czym naprawdę jest |
|
|
||||||
| --------------------------- | ---------------------------------------------------- |
|
|
||||||
| `Fav` | filtr **ulubionych** (flaga `ci.flags & 1`) |
|
|
||||||
| `ALL/Comp/Rpt/Room/Snsr` | filtr **po typie** węzła |
|
|
||||||
| `TIME` | **sortowanie** (po `lastmod` zamiast po dystansie) |
|
|
||||||
|
|
||||||
Konsekwencje:
|
|
||||||
- użytkownik „scrolluje po kategoriach” na ślepo — nie widzi wszystkich
|
|
||||||
naraz, musi cyklować, by trafić w to, czego szuka;
|
|
||||||
- **kombinacje są niemożliwe**: nie da się zobaczyć „ulubionych
|
|
||||||
repeaterów posortowanych po dystansie" ani „repeaterów posortowanych
|
|
||||||
po czasie” — model wymusza dokładnie jeden stan z siedmiu;
|
|
||||||
- `TIME` jako „filtr" jest mylące — zmienia kolejność, nie zawartość;
|
|
||||||
- etykieta w nagłówku (`NEARBY[TIME]`) nie mówi, że to sort.
|
|
||||||
|
|
||||||
### 2.2 Te same akcje, różne menu w zależności od miejsca
|
|
||||||
|
|
||||||
| Akcja | Lista (`_ctx_menu`) | Detail kontaktu (`_opts`) | Detail discover |
|
|
||||||
| -------------- | :-----------------: | :-----------------------: | :-------------: |
|
|
||||||
| Navigate | ✓ | ✓ | — |
|
|
||||||
| Save waypoint | ✓ | ✓ | — |
|
|
||||||
| Ping | — | ✓ | ✓ |
|
|
||||||
| Discover | ✓ | — | — |
|
|
||||||
|
|
||||||
Ten sam węzeł oferuje inny zestaw akcji w zależności od tego, gdzie na
|
|
||||||
niego patrzysz. Ping jest osiągalny tylko ze szczegółów, Discover tylko
|
|
||||||
z listy.
|
|
||||||
|
|
||||||
### 2.3 „Hold Enter” znaczy co innego na każdym ekranie
|
|
||||||
|
|
||||||
| Ekran | Hold Enter (`KEY_CONTEXT_MENU`) |
|
|
||||||
| ----------------- | ------------------------------------ |
|
|
||||||
| Lista nearby | otwiera menu kontekstowe |
|
|
||||||
| Detail kontaktu | otwiera menu Options |
|
|
||||||
| Lista discover | **ponowny skan** (żadnego menu) |
|
|
||||||
| Detail discover | **od razu Ping** (pomija Options) |
|
|
||||||
|
|
||||||
Brak spójnego modelu „przytrzymaj = menu akcji”.
|
|
||||||
|
|
||||||
### 2.4 `_ping_menu` to bespoke widget
|
|
||||||
|
|
||||||
W przeciwieństwie do reszty popupów, ping-menu:
|
|
||||||
- ma wiersze tylko-do-odczytu (RTT / SNR), więc połyka `UP/DOWN`;
|
|
||||||
- przebudowuje się w trakcie (`rebuildPingMenu`) gdy przychodzą wyniki;
|
|
||||||
- zostaje otwarte po `SELECTED` (reszta popupów się zamyka);
|
|
||||||
- ma własny `handlePingMenuInput` z trybem `allow_enter_to_open`.
|
|
||||||
|
|
||||||
To działa, ale jest to czwarty, niestandardowy wzorzec interakcji w
|
|
||||||
jednym narzędziu.
|
|
||||||
|
|
||||||
### 2.5 Duplikacja list/detail/ping
|
|
||||||
|
|
||||||
`renderDiscover`/`handleInputDiscover`/`renderDiscoverDetail` to niemal
|
|
||||||
równoległa kopia logiki listy i szczegółów nearby (osobne `_dsel`,
|
|
||||||
`_dscroll`, `_d_visible`, własne rysowanie kart). Discover i Nearby
|
|
||||||
robią to samo — pokazują listę węzłów z możliwością wejścia w szczegóły
|
|
||||||
i pingowania — ale dwoma osobnymi ścieżkami kodu.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Propozycja uporządkowania
|
|
||||||
|
|
||||||
Trzy zasady przewodnie: **(a) jedno menu akcji wszędzie**, **(b) filtr
|
|
||||||
i sortowanie jako osobne, jawne osie**, **(c) jeden wzorzec listy**.
|
|
||||||
|
|
||||||
### 3.1 Rozdziel filtr od sortowania
|
|
||||||
|
|
||||||
Zamiast jednego cyklu 7-stanowego — dwie niezależne osie wybierane z
|
|
||||||
menu (nie przez ślepe cyklowanie):
|
|
||||||
|
|
||||||
**Filtr** (oś „co pokazujemy") i **sort** (oś „w jakiej kolejności") są
|
|
||||||
od siebie niezależne i **łączą się dowolnie**:
|
|
||||||
|
|
||||||
```
|
|
||||||
FILTR (jedna oś — typ węzła, z Ulubionymi) SORT (przełącznik)
|
|
||||||
• Wszystkie • Dystans (domyślnie)
|
|
||||||
• Ulubione • Ostatnio słyszane
|
|
||||||
• Companion
|
|
||||||
• Repeater
|
|
||||||
• Room
|
|
||||||
• Sensor
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sterowanie (zatwierdzone):**
|
|
||||||
- `LEFT/RIGHT` = szybki cykl **tylko po filtrze-typie** (jedna spójna oś,
|
|
||||||
znany gest; bez „TIME" zanieczyszczającego cykl);
|
|
||||||
- **Sort** = przełącznik w menu akcji (`Dystans ↔ Ostatnio słyszane`),
|
|
||||||
trzymany niezależnie od filtra;
|
|
||||||
- **Filter…** dostępny też w menu akcji (odkrywalność — cała lista
|
|
||||||
widoczna naraz, nie tylko cyklowanie).
|
|
||||||
|
|
||||||
Dzięki temu „ulubione repeatery po dystansie" staje się możliwe, a w
|
|
||||||
nagłówku widać oba wymiary, np. `NEARBY · Rpt · ↓dist`.
|
|
||||||
|
|
||||||
### 3.2 Jedno spójne menu akcji (Hold Enter wszędzie)
|
|
||||||
|
|
||||||
Jeden `PopupMenu` (zamiast `_ctx_menu` + `_opts`), pozycje zależne od
|
|
||||||
kontekstu, ale **kolejność i nazwy stałe**:
|
|
||||||
|
|
||||||
```
|
|
||||||
Hold Enter → Options
|
|
||||||
• Navigate (gdy węzeł ma GPS)
|
|
||||||
• Ping (gdy znamy pubkey)
|
|
||||||
• Save waypoint (gdy węzeł ma GPS)
|
|
||||||
──────────────
|
|
||||||
• Filter… (podmenu z 3.1)
|
|
||||||
• Sort… (toggle z 3.1)
|
|
||||||
• Discover scan (uruchamia skan na żywo = przełącza źródło)
|
|
||||||
```
|
|
||||||
|
|
||||||
To samo menu na liście i w szczegółach, w obu źródłach (Zapisane /
|
|
||||||
Skan). Pozycje niedostępne są pomijane (jak już teraz robi `has_node`),
|
|
||||||
ale nigdy nie zmieniają kolejności ani nazw. „Hold Enter = menu akcji" —
|
|
||||||
bez wyjątków. Ping zawsze przez Options (znika „od razu Ping" z detalu
|
|
||||||
Discover); rescan to pozycja menu, nie ukryty Hold Enter.
|
|
||||||
|
|
||||||
### 3.3 Jedna lista, dwa źródła (Zapisane / Skan na żywo)
|
|
||||||
|
|
||||||
**Zatwierdzone:** zamiast osobnego pod-ekranu Discover — **jeden
|
|
||||||
komponent** listy/szczegółów/menu/ping napędzany przełącznikiem
|
|
||||||
**źródła**:
|
|
||||||
|
|
||||||
- ta sama nawigacja, to samo menu akcji, ten sam wzorzec szczegółów i
|
|
||||||
pingu (znika duplikacja `renderDiscover*`, niespójne Hold Enter i
|
|
||||||
połowa pól stanu);
|
|
||||||
- różni się tylko **prawa kolumna** wiersza i pola w detalu:
|
|
||||||
- źródło **Zapisane** → dystans / azymut / lastmod (jak dziś),
|
|
||||||
- źródło **Skan na żywo** → RSSI / SNR (dane z `DiscoverResult`).
|
|
||||||
|
|
||||||
To nie jest dosłowne zlanie dwóch list w jedną tablicę (dane mają różny
|
|
||||||
kształt — kontakt ma GPS, wynik skanu ma sygnał), tylko **jedna ścieżka
|
|
||||||
interakcji nad dwoma źródłami**. Wybór źródła = „Discover scan" w menu
|
|
||||||
(uruchamia `NODE_DISCOVER_REQ` i przełącza widok na wyniki) oraz powrót
|
|
||||||
do Zapisanych przez Cancel.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Proponowany zakres (etapami)
|
|
||||||
|
|
||||||
| Etap | Zmiana | Ryzyko |
|
|
||||||
| ---- | ------------------------------------------------------------------------ | :----: |
|
|
||||||
| 1 | Rozdziel sort od filtra: `TIME` znika z cyklu; `LEFT/RIGHT` cyklują tylko typ+Fav; sort jako stan + toggle | niskie |
|
|
||||||
| 2 | Scal `_ctx_menu` i `_opts` w jedno menu akcji o stałej kolejności; dodaj Filter…/Sort… | niskie |
|
|
||||||
| 3 | Ujednolić Hold Enter w Discover (menu zamiast bezpośredniego rescan/ping; Ping zawsze przez Options) | średnie |
|
|
||||||
| 4 | Scal listę Discover z listą Nearby w jeden komponent z przełącznikiem źródła | wyższe |
|
|
||||||
|
|
||||||
Kolejność implementacji: 1 → 2 → 3 → 4. Etapy 1–2 dają największą
|
|
||||||
poprawę „uporządkowania" przy najmniejszym ryzyku; 3–4 domykają
|
|
||||||
spójność (jedna lista, jedno menu wszędzie).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Decyzje (zatwierdzone 2026-06-14)
|
|
||||||
|
|
||||||
1. **Filtr i sort — pełne rozdzielenie osi.** `LEFT/RIGHT` = szybki cykl
|
|
||||||
filtra-typu; Sort jako toggle w menu; Filter… też w menu dla
|
|
||||||
odkrywalności. (Nie chowamy wszystkiego do menu — zachowujemy szybki
|
|
||||||
gest.)
|
|
||||||
2. **Discover — jedna lista, dwa źródła.** Wspólny komponent
|
|
||||||
interakcji; różni się tylko prawa kolumna/pola detalu (dystans vs
|
|
||||||
sygnał).
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# Trail (Tools › Trail) — analiza i propozycja uporządkowania
|
|
||||||
|
|
||||||
> Branch: `refactor/trail-screen` (zmergowany do `main`)
|
|
||||||
> Plik źródłowy: [TrailScreen.h](../../examples/companion_radio/ui-new/TrailScreen.h)
|
|
||||||
> Status: **zaimplementowane** — dokument zachowany jako zapis analizy/decyzji.
|
|
||||||
> Aktualny opis funkcji od strony użytkownika: [tools_screen.md › GPS Trail](../solo_features/tools_screen/tools_screen.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Jak ekran jest zbudowany dzisiaj
|
|
||||||
|
|
||||||
```
|
|
||||||
TrailScreen
|
|
||||||
├── 3 widoki (LEFT/RIGHT): Summary · Map · List
|
|
||||||
├── popup akcji (Hold Enter) — JEDNA płaska lista, do 12 pozycji
|
|
||||||
│ ├── ustawienia (LEFT/RIGHT cykluje w miejscu): Min dist · Readout · Grid
|
|
||||||
│ ├── toggle: Start/Stop tracking
|
|
||||||
│ ├── waypointy: Mark here · Waypoints · Clear waypoints
|
|
||||||
│ └── trail: Save · Load · Export(live) · Export(saved) · Reset
|
|
||||||
├── pod-ekrany waypointów (nakładane na widoki):
|
|
||||||
│ ├── WP_LIST (lista + dystanse; Trail-start + „+ Add by coords")
|
|
||||||
│ ├── WP_NAV (navview)
|
|
||||||
│ ├── WP_ADD (formularz lat/lon/label)
|
|
||||||
│ └── _wp_ctx popup: Rename · Delete · Send
|
|
||||||
└── KeyboardWidget (label / lat / lon) — nakładka pełnoekranowa
|
|
||||||
```
|
|
||||||
|
|
||||||
Renderowanie mapy: `renderMap()` (≈140 linii) + `renderGrid()` (≈130 linii)
|
|
||||||
+ 7 funkcji rysujących markery.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Co jest nieuporządkowane
|
|
||||||
|
|
||||||
### 2.1 Popup akcji to jedna płaska lista 12 pozycji z mieszanymi rolami
|
|
||||||
|
|
||||||
`openActionMenu()` (`TrailScreen.h:363`) buduje jedno menu, które miesza
|
|
||||||
**cztery różne klasy** pozycji:
|
|
||||||
|
|
||||||
| Klasa | Pozycje | Interakcja |
|
|
||||||
| ---------------- | ----------------------------------------- | --------------------- |
|
|
||||||
| Ustawienia | Min dist, Readout, Grid | LEFT/RIGHT (w miejscu) |
|
|
||||||
| Stan nagrywania | Start/Stop tracking | Enter |
|
|
||||||
| Waypointy | Mark here, Waypoints, Clear waypoints | Enter |
|
|
||||||
| Plik trasy | Save, Load, Export(live), Export(saved), Reset | Enter |
|
|
||||||
|
|
||||||
Problemy:
|
|
||||||
- **Długi scroll** — na OLED widać ~4 wiersze naraz, więc do „Reset trail"
|
|
||||||
trzeba przewinąć przez całą listę;
|
|
||||||
- **Dwa wzorce interakcji w jednym menu** — część pozycji reaguje na
|
|
||||||
LEFT/RIGHT (ustawienia), część na Enter (akcje). Enter na wierszu
|
|
||||||
ustawień nic sensownego nie robi — tylko zamyka i otwiera menu od nowa
|
|
||||||
(`reopenAt`, `TrailScreen.h:581`);
|
|
||||||
- **Brak kontekstu widoku** — `Grid` (dotyczy tylko mapy) i `Readout`
|
|
||||||
(dotyczy tylko Summary) są widoczne zawsze, też tam, gdzie nie mają
|
|
||||||
efektu;
|
|
||||||
- **`Grid` ma dwie ścieżki** — i LEFT/RIGHT (`:221`) i Enter (`:234`)
|
|
||||||
robią to samo; lekko myli.
|
|
||||||
|
|
||||||
### 2.2 `renderGrid` dostaje 11 skalarnych parametrów — brak wspólnej projekcji
|
|
||||||
|
|
||||||
`renderMap` liczy projekcję (lokalne lambdy `projectLL`/`project`,
|
|
||||||
`:816`), a `renderGrid` (`:876`) dostaje **11 osobnych liczb**
|
|
||||||
(`area_*`, `min/max_lat`, `min_lon`, `lon_scale_geo`, `scale`, `off_*`) i
|
|
||||||
**powtarza tę samą matematykę projekcji** ręcznie w pętli (`:988`,
|
|
||||||
`:992`). To samo równanie żyje w trzech miejscach. Każda zmiana modelu
|
|
||||||
mapy wymaga edycji w kilku miejscach naraz.
|
|
||||||
|
|
||||||
### 2.3 Wybór kroku siatki — wielostopniowa heurystyka z nieaktualnym komentarzem
|
|
||||||
|
|
||||||
`renderGrid` wybiera krok siatki w **czterech** następujących po sobie
|
|
||||||
korektach (`:912`–`:952`):
|
|
||||||
1. największy krok ≤ `target_m`,
|
|
||||||
2. zwiększaj aż odstęp pikseli ≥ `MIN_GRID_PX` (22 px),
|
|
||||||
3. zmniejszaj aż zmieszczą się ≥2 interwały,
|
|
||||||
4. zwiększaj aż liczba linii ≤ `MAX_GRID_LINES` (40).
|
|
||||||
|
|
||||||
Uwagi:
|
|
||||||
- Komentarz przy kroku 4 (`:937`) mówi o „static buffers (40×40 = ~1600
|
|
||||||
intersections)" — **takich buforów już nie ma**; pętla rysuje na bieżąco
|
|
||||||
z `continue`-guardami (`:986`–`1002`). Cap 40 ogranicza dziś tylko
|
|
||||||
liczbę iteracji pętli (wydajność), nie chroni żadnego bufora. Komentarz
|
|
||||||
wprowadza w błąd.
|
|
||||||
- Kroki 2 i 3 mogą sobie przeczyć na bardzo małych ekranach
|
|
||||||
(`MIN_GRID_PX = 22` vs `shorter_px/2`, gdy `shorter_px < 44`).
|
|
||||||
Nie powoduje błędu, ale „ostateczny" krok bywa wtedy przypadkowy.
|
|
||||||
|
|
||||||
### 2.4 Drobne
|
|
||||||
|
|
||||||
- `_act_map[16]` z komentarzem „12 used today; pad" — ręczne pilnowanie
|
|
||||||
rozmiaru; `pushAction` już to zabezpiecza, więc magiczna 16 jest zbędna.
|
|
||||||
- Bounding-box mapy i markery mają sporo powtarzalnego clamp-to-edge
|
|
||||||
(`:852`, `:1011`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Propozycja uporządkowania
|
|
||||||
|
|
||||||
### 3.1 Popup: dwa poziomy zamiast jednej płaskiej listy
|
|
||||||
|
|
||||||
Górne menu krótkie (akcje), ustawienia i operacje na pliku w podmenu:
|
|
||||||
|
|
||||||
```
|
|
||||||
Hold Enter → Trail
|
|
||||||
• Start / Stop tracking
|
|
||||||
• Mark here
|
|
||||||
• Waypoints… → istniejący WP_LIST
|
|
||||||
• Trail file… → Save / Load / Export (live) / Export (saved) / Reset
|
|
||||||
• Settings… → Min dist · Readout · Grid (LEFT/RIGHT w miejscu)
|
|
||||||
```
|
|
||||||
|
|
||||||
Korzyści:
|
|
||||||
- górne menu to **~5 pozycji, bez scrolla** na OLED;
|
|
||||||
- **jeden wzorzec na poziom**: górny i „Trail file" = Enter-akcje;
|
|
||||||
„Settings" = wartości cyklowane LEFT/RIGHT — bez mieszania w jednym
|
|
||||||
widoku;
|
|
||||||
- destrukcyjny `Reset` przeniesiony do „Trail file…", dalej od przypadkowego
|
|
||||||
Entera;
|
|
||||||
- (opcjonalnie) `Grid` pokazywać tylko gdy aktywny jest widok Map, a
|
|
||||||
`Readout` tylko przy Summary — menu zależne od kontekstu widoku.
|
|
||||||
|
|
||||||
> Wariant minimalny (mniej kodu): zostać przy jednej liście, ale
|
|
||||||
> **pogrupować** (ustawienia → akcje → plik), `Reset` na sam dół, usunąć
|
|
||||||
> podwójną ścieżkę `Grid`. Mniej porządku niż podmenu, ale tańsze.
|
|
||||||
|
|
||||||
### 3.2 Mapa: wspólny obiekt projekcji
|
|
||||||
|
|
||||||
Wydzielić mały `MapProjection` liczony raz w `renderMap` i przekazywany
|
|
||||||
do `renderGrid` oraz markerów:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
struct MapProjection {
|
|
||||||
int32_t min_lat, max_lat, min_lon;
|
|
||||||
float lon_scale_geo, scale;
|
|
||||||
int off_x, off_y, area_x, area_y, area_w, area_h;
|
|
||||||
void project(int32_t lat, int32_t lon, int& px, int& py) const;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
- `renderGrid(display, proj)` zamiast 11 parametrów;
|
|
||||||
- jedno równanie projekcji (dziś powielone 3×);
|
|
||||||
- markery/waypointy też przez `proj.project(...)`.
|
|
||||||
|
|
||||||
Czysto refaktoryzacyjne — bez zmiany wyglądu mapy.
|
|
||||||
|
|
||||||
### 3.3 Siatka: uproszczenie i naprawa komentarza
|
|
||||||
|
|
||||||
- poprawić/skasować komentarz o „static buffers" (już nieaktualny);
|
|
||||||
- scalić wybór kroku w jedną pętlę „znajdź najmniejszy krok, który daje
|
|
||||||
odstęp ≥ MIN_GRID_PX i ≤ MAX_GRID_LINES linii" zamiast czterech
|
|
||||||
następujących korekt;
|
|
||||||
- bbox etykiety/strzałki północy liczyć z jednej funkcji pomocniczej.
|
|
||||||
|
|
||||||
Wynik wizualnie identyczny, logika krótsza i łatwiejsza do utrzymania.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Proponowany zakres (etapami)
|
|
||||||
|
|
||||||
| Etap | Zmiana | Ryzyko |
|
|
||||||
| ---- | ----------------------------------------------------------------- | :----: |
|
|
||||||
| 1 | Popup → dwa poziomy (Trail file…, Settings…); Reset głębiej; usuń podwójny Grid | niskie |
|
|
||||||
| 2 | Wydziel `MapProjection`; `renderGrid` i markery przez projekcję | średnie (czysty refactor) |
|
|
||||||
| 3 | Uprość wybór kroku siatki; popraw nieaktualne komentarze; sprzątnij `_act_map` | niskie |
|
|
||||||
|
|
||||||
Etap 1 = największa poprawa „uporządkowania popupu" (główna prośba).
|
|
||||||
Etapy 2–3 = czyszczenie logiki mapy/siatki bez zmiany wyglądu.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Decyzje (zatwierdzone 2026-06-14)
|
|
||||||
|
|
||||||
1. **Popup — pełne podmenu.** Górne menu krótkie; `Trail file…` i
|
|
||||||
`Settings…` jako podmenu.
|
|
||||||
2. **Menu zależne od widoku — tak.** `Grid` widoczny w Settings tylko na
|
|
||||||
widoku Map, `Readout` tylko na Summary.
|
|
||||||
3. **Mapa — `MapProjection` + uproszczenie siatki** (etap 2 i 3 razem).
|
|
||||||
4. **Siatka — kwadratowe oczka, dociągnięte do krótszego boku.** Krok w
|
|
||||||
przestrzeni pikseli (skala izotropiczna). Krótszy bok dzielony na
|
|
||||||
całkowitą liczbę równych kwadratów (siatka dotyka tej pary krawędzi —
|
|
||||||
na poziomym OLED: góra/dół), na dłuższym boku mieści się całkowita
|
|
||||||
liczba tych samych kwadratów, wyśrodkowana. Dzięki temu oczka są
|
|
||||||
**kwadratowe**, siatka wpisana w ramkę i symetryczna (brak
|
|
||||||
jednostronnego przesunięcia). Etykieta skali = **nominalna** okrągła
|
|
||||||
wartość. (Kwadraty + linie na wszystkich 4 krawędziach są
|
|
||||||
geometrycznie niemożliwe dla dowolnego prostokąta — wybrano kwadraty +
|
|
||||||
2 krawędzie + symetria.)
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
# Wio Tracker L2 (ui-lvgl) — hardware checklist
|
|
||||||
|
|
||||||
Things verified only in the browser simulator so far. Tick on the device;
|
|
||||||
note anything odd next to the item. Branch `wio-tracker-l2`.
|
|
||||||
|
|
||||||
## Feedback round 1 (2026-09-25) — fixed, check on the device
|
|
||||||
Confirmed on the device: live share sends and receives positions.
|
|
||||||
- [ ] Map: ☰ tools moved to the left column (back, pin, ☰); nothing overlaps the crosshair
|
|
||||||
- [ ] Target ring sits exactly on the marker (all markers were drawn ~3 px low)
|
|
||||||
- [ ] A person sharing on a channel, picked as target, is followed as they move (was a fixed point where they were); bar shows the person icon
|
|
||||||
- [ ] Settings: subtitles no longer cover the titles (all list rows)
|
|
||||||
- [ ] Map credit is a small "©" (tap: full line in a toast); full text also in Settings > About; long toasts wrap
|
|
||||||
- [ ] Home: Messages, Nearby, Map, Settings — "Map" is the navigation map; the nodes map opens from Nearby's header (map icon), back returns to Nearby
|
|
||||||
- [ ] Home tiles are wider; more apps will go on further swipeable pages (dots appear once there are two)
|
|
||||||
- [ ] Overzoom limited to 2 levels (x4); past that "No map detail here at this zoom" — download more
|
|
||||||
- [ ] Panning: no tile decoding while the finger moves (smooth drag, tiles fill in once you stop); tiles around the view are decoded ahead while idle; newly revealed area shows the coarser tile until the sharp one is ready
|
|
||||||
- [ ] Note how panning feels now: ______ (next step if still slow: decode on the second core / JPEG tiles)
|
|
||||||
|
|
||||||
## Feedback round 2 (2026-09-25) — fixed, check on the device
|
|
||||||
Confirmed: settings rows OK, map pans much faster, ring centred.
|
|
||||||
- [ ] Target ring is wider (30 px) so the green / flag marker shows inside it; a target with no marker on it (map point, message position, stale live share) gets an orange centre dot
|
|
||||||
- [ ] "↓ Resume?" pill sits top right beside +, clear of the zoom pill; tapping it opens the download popup
|
|
||||||
- [ ] Pin button → Waypoints list (distance, tap → Go / Name / Share / Delete) with "Here (GPS)" and "Coordinates"
|
|
||||||
- [ ] Coordinates: digits keyboard, "50.06142, 19.93721 Name" or with a space instead of the comma; bad input keeps the field with a hint; the map centres on the new waypoint
|
|
||||||
- [ ] Holding the map no longer adds a waypoint straight away: "This spot" popup with Add waypoint / Go here
|
|
||||||
|
|
||||||
## Feedback round 3 (2026-09-25) — fixed, check on the device
|
|
||||||
Confirmed: green dot inside the ring, waypoints much better, download works, settings OK.
|
|
||||||
- [ ] Ring exactly on the marker at every zoom (it was computed in float: pixels off at z16+). The trail line had the same error and is fixed too
|
|
||||||
- [ ] Home: swipe left / right anywhere (clock or tiles) turns the page; a swipe starting on a tile doesn't open it; tapping a dot also switches
|
|
||||||
- [ ] Settings > Display & power: Brightness is a slider (5-100 %), changes live while dragging, kept after a reboot and after the screen sleeps / wakes
|
|
||||||
- [ ] After the update: prefs load normally (new field, schema sentinel 0xC0DE002F) — brightness starts from the old level, nothing else reset
|
|
||||||
|
|
||||||
## New: Clock, settings pages, radio (2026-09-25)
|
|
||||||
- [ ] Home has two pages now: swipe left → Clock (dots under the tiles follow; Home remembers the page)
|
|
||||||
- [ ] Clock > Alarm: hour / minute rollers (setting a time arms it), On switch, Repeat (Once / Daily / Weekdays / Weekends); rings at the local time
|
|
||||||
- [ ] Clock > Timer: H / M / S rollers, Start → big countdown, Stop; when it ends a full-screen "Timer done" card with Dismiss appears on any screen (also wakes the display; the user button dismisses too). Rings with a melody since the Sound round
|
|
||||||
- [ ] Clock > Stopwatch: Start / Stop / Reset with tenths
|
|
||||||
- [ ] Settings > Display & power: Screen off after; Wake on message
|
|
||||||
- [ ] Settings > Display & power: Battery shutdown (not while on USB), GPS power saving, Time zone (clock + alarm follow)
|
|
||||||
- [ ] Settings > Messages & contacts: Resend direct messages, Contact expiry + "Remove inactive contacts now" (first tap shows the count, second removes; favourites kept), Favourites first
|
|
||||||
- [ ] Settings > Radio: Preset list (built-ins + your saved ones), Frequency (tap, type), SF, Bandwidth, Coding rate, TX power, Auto power; changes apply at once — check messages still flow after switching back to your usual preset
|
|
||||||
- [ ] L1 (ui-new): Settings > Radio TX power / Auto power / presets still apply (now through the shared RadioControl)
|
|
||||||
|
|
||||||
## New: Channels (2026-09-25)
|
|
||||||
- [ ] Messages: favourite channels first with a star, muted ones with a speaker icon
|
|
||||||
- [ ] Hold a channel row (or ⚙ in an open channel) → options: Alerts (Default / Muted / Always, one tap), Scope (only if regions exist), Fav, Read (only with unread), Edit, Delete (second tap confirms)
|
|
||||||
- [ ] "+ Add channel" → Public (re-adds the open channel; "already exists" if it's there), Hashtag (topic → "#topic"), Private (name + passphrase, or Hex key switch → exactly 32 hex chars)
|
|
||||||
- [ ] A hashtag / private channel made on L2 talks to the same channel made on L1 or in the phone app
|
|
||||||
- [ ] Edit: rename keeps the key (shown at the top); typing a new passphrase changes it
|
|
||||||
- [ ] Deleting a channel that was the live share / bot target turns those off; a channel re-added in that slot starts with default notifications / no favourite (also when deleted from the phone app)
|
|
||||||
- [ ] L1 (ui-new): Messages > Channels add / edit / delete / mute / pin still work (now through the shared ChannelControl)
|
|
||||||
- [ ] L1: deleting a contact from the app still unpins it from the dial and clears its mute / melody (cleanup moved into the Core)
|
|
||||||
|
|
||||||
## New: Repeater admin (2026-09-25)
|
|
||||||
- [ ] Nearby > a repeater or room server you have in contacts > "Admin" (the Fav button is just a star there)
|
|
||||||
- [ ] First time: password field + keyboard; wrong password → "Login failed", back on the node; right one → tabs System / Radio / Routing / Actions
|
|
||||||
- [ ] Next time: logs in by itself with the saved password (no keyboard); a node you just used opens straight away
|
|
||||||
- [ ] Out of range: "No answer to the login" after a while, and the saved password is forgotten (asks again next time)
|
|
||||||
- [ ] Name / Owner info: "Reading..." then a text field with the current value; ✓ sends it, reply popup "OK"
|
|
||||||
- [ ] Admin password: set a new one → the saved password follows (next login works without typing)
|
|
||||||
- [ ] Routing > Repeat switch; Advert interval / Flood advert / Max hops with − / +; Radio > SF / Bandwidth / Coding rate choices, Frequency typed, TX power − / +
|
|
||||||
- [ ] Actions: Send advert / zero-hop / Sync clock give a reply; Reboot and Start OTA ask first (red button)
|
|
||||||
- [ ] Custom command: e.g. `ver`, `neighbors` (long replies scroll)
|
|
||||||
- [ ] "Reading... (tap to stop)" stops waiting when tapped
|
|
||||||
- [ ] Room server login from the phone app / L1 still works while this exists (only admin logins go to the new session)
|
|
||||||
- [ ] L1 (ui-new): Tools > Admin works as before (login, saved password, typed values, confirm reboot); it now also skips the login for the node you just used
|
|
||||||
|
|
||||||
## Roadmap stage 2: Arduino-ESP32 3.3.12 / IDF 5.5 (2026-09-26)
|
|
||||||
Env `Wio_Tracker_L2_companion_solo_lvgl` (Arduino 3.x since 2026-09-26; the 2.0.17 build is `_arduino2`). Bluetooth runs on NimBLE now, the speaker on the new I2S driver. Internal heap at runtime: 147.9 KB free on 3.x vs 147.8 KB on 2.0.17 (large UI buffers moved to PSRAM; static RAM 109 → 71 KB).
|
|
||||||
- [ ] Boots, screen and touch as before; WAKE and USER buttons
|
|
||||||
- [ ] Bluetooth: the app pairs with the PIN (asks for it: the link is encrypted), connects, syncs contacts and messages, sends; reconnects after the app is closed / reopened; the Bluetooth switch still works
|
|
||||||
- [ ] Sound: startup chime, message sounds, melody editor, volume; no new knocks
|
|
||||||
- [ ] LoRa: send / receive on a channel and a DM, adverts
|
|
||||||
- [ ] GPS fix, map, trail drawn on the map (its buffers moved to PSRAM)
|
|
||||||
- [ ] WiFi: scan, map download over HTTPS; the WiFi switch
|
|
||||||
- [ ] SD card: maps load, trail GPX export
|
|
||||||
- [ ] Settings / contacts / channels survive the update from the 2.0.17 build (same flash layout)
|
|
||||||
|
|
||||||
## Roadmap stage 1: quick fixes (2026-09-26)
|
|
||||||
- [ ] Keyboard: the keyboard key (bottom left) closes it; in a chat the typed text stays, in a dialog nothing is saved
|
|
||||||
- [ ] Symbols page ("1#"): "_" is in the third row now
|
|
||||||
- [ ] Bot > Reply: every placeholder in the row above the field ({name} {hops} {loc} {time} {batt}, sensors); swipe it sideways
|
|
||||||
- [ ] WAKE button (side): screen off / on; silences a ringing alarm
|
|
||||||
- [ ] USER button: back (answers at once, no double-click wait); held 1 s, sound off / on with a toast and the mute icon; wakes the screen when it's off
|
|
||||||
- [ ] Settings > CONNECTIVITY > WiFi: a switch as on Bluetooth (toast, row says "Off" / the network); tapping the row opens the network settings (editable while off; Scan asks to turn it on); off: map download says "WiFi is off"; kept across a reboot
|
|
||||||
- [ ] Shorter descriptions on settings rows, Bot, Repeater, Send advert, Bluetooth ("PIN 123456" / "App connected")
|
|
||||||
|
|
||||||
## New: Quick messages, placeholders, advert, Bluetooth (2026-09-26)
|
|
||||||
- [ ] Chat "+" (left of the text field): Insert {loc} / {time} / {batt} (+ sensors) into the message; they are filled in when sent (a reply's "@[name] " stays as typed)
|
|
||||||
- [ ] Chat "+" > a quick message sends it at once (the popup shows what will go out, filled in); "Edit quick messages" opens the list
|
|
||||||
- [ ] Settings > Messages & contacts > Quick messages: 10 slots, tap to edit (field, placeholder chips, keyboard); empty clears; "OK" is there on a fresh device; the same slots as L1's Settings > Messages
|
|
||||||
- [ ] Nearby > advert button (tower icon) > Nearby only (zero-hop) / Everyone (flood through repeaters); another node sees you
|
|
||||||
- [ ] Settings > CONNECTIVITY > Bluetooth: off / on; the row shows the pairing PIN while waiting, "the app is connected" when paired; USB keeps working with Bluetooth off; after a reboot Bluetooth is on again (as on L1)
|
|
||||||
- [ ] Messages: "Read all" in the header while anything is unread
|
|
||||||
- [ ] Nearby > a node with a position: flag button saves it as a waypoint; pin button puts a contact on the favourites dial; with five or more buttons they show icons only; delete asks with "trash?" and a toast
|
|
||||||
- [ ] L1: quick messages, placeholders and the sensor placeholder list work as before (now shared through ui-core/MessageText.h)
|
|
||||||
|
|
||||||
## New: Sound (2026-09-26)
|
|
||||||
The speaker plays through the ES8311 codec (I2S MCLK 10 / BCK 11 / WS 12 / DOUT 16, amp on expander P12). Pins come from Seeed's Meshtastic port; nothing was heard on the device yet.
|
|
||||||
- [ ] A short startup chime after boot (sound On); a goodbye sound on Power off
|
|
||||||
- [ ] Settings > Sound: On / Off / Auto (Auto: silent while the app is connected); Off shows a muted-speaker icon in the status bar
|
|
||||||
- [ ] Volume slider (5 steps): a beep on release at the new level; the quietest is still audible, the loudest doesn't distort or rattle: ______
|
|
||||||
- [x] No knock between sounds, however fast or slow the taps (fixed: notes fade in / out on a raised cosine, a cut note fades over 15 ms, and the DMA queue is kept full of silence between sounds -- a sound starting into a queue that had run dry was played from a half-written buffer)
|
|
||||||
- [x] Known, kept: one soft knock on the first sound after >3 s of silence -- the class-D amp's own power-up pop (a longer codec settle didn't change it, only delayed the sound). Kept the amp off between sounds for battery / GNSS; alternatives if it bothers: amp on while the screen is on, or always on
|
|
||||||
- [ ] Direct message, channel message and advert each play their sound (Built-in / Melody 1 / Melody 2 / None under PLAYS FOR); "Advert sound for: Direct only" skips adverts that came through repeaters
|
|
||||||
- [ ] Hold a chat in Messages: Alerts (Default / Muted / Always) and Sound (Default / Melody 1 / Melody 2) apply to that chat only; Always plays even with sound Off
|
|
||||||
- [ ] Clock alarm / timer rings with a repeating melody until Dismiss (or the user button); sound Off doesn't silence it
|
|
||||||
- [ ] Arrival alert plays a rising / falling triple; Proximity beeper ticks faster closer to the target
|
|
||||||
- [ ] Settings > Sound > Melodies: + adds a note (a copy of the selected one), tap a note to pick it; pitch / octave / length change it with a short preview; trash deletes; BPM; Play / Stop in the header, the note sounding lights up in full colour (the strip scrolls along); saved on Back or when switching Melody 1 / 2 ("Melody 1 saved"); the same melodies play on L1 if the prefs are shared
|
|
||||||
- [ ] Timing stays even while the map redraws (notes are counted in samples by the audio task, not by the UI loop)
|
|
||||||
- [ ] GPS keeps its fix while sounds play (the amp is off between sounds to keep its switching noise away from the antenna)
|
|
||||||
|
|
||||||
## New: Repeater mode (2026-09-25)
|
|
||||||
- [ ] Settings > CONNECTIVITY > Repeater (row shows Off / On and what it relays on): switch on → "Repeater on", the loop icon appears in the status bar; off → it goes away
|
|
||||||
- [ ] Relay on Custom: the radio moves to the profile's frequency while on and back to the chat frequency when off (Settings > Radio row / another node on each frequency); Current: stays on the chat frequency
|
|
||||||
- [ ] Custom profile: Preset dropdown, Frequency (typed, out-of-range refused), SF / BW / CR apply at once; while relaying on it the change is live
|
|
||||||
- [ ] While on: Settings > Radio > Auto power is greyed ("Off while repeating") and TX runs at the set power
|
|
||||||
- [ ] Another node two hops away receives messages through this device; Skip adverts stops relaying adverts only; Max hops / Min SNR / Yield / Skip duplicates / Scope only take effect (compare with L1 Tools › Repeater showing the same values)
|
|
||||||
- [ ] Extra scopes popup: a switch per scope, the row counts "N of M relayed"; with no scopes it points to Settings > Radio
|
|
||||||
- [ ] Fresh device (erased flash): Min SNR starts at Off, not 0 dB (fixed default, also on L1)
|
|
||||||
|
|
||||||
## New: My presets, scopes, battery display, clock seconds (2026-09-25)
|
|
||||||
- [ ] Settings > Radio > MY PRESETS > Save current settings: name popup, Enter → "Preset saved", the row shows freq / SF / BW / CR with a tick while in use, and it is in the Preset dropdown
|
|
||||||
- [ ] Tap a saved preset → Use (radio switches) / Delete (confirm, red) → "Preset deleted"; a 5th name replaces the oldest (hint says so when all 4 are used); presets survive a reboot
|
|
||||||
- [ ] L1: Settings › Radio › Preset lists the presets saved on L2 and vice versa (same slots)
|
|
||||||
- [ ] Settings > Radio > SCOPE > Scopes: "* (no scope)" is the default; + Add → name → listed; tap → Default / Rename / Delete; the Radio screen row shows the default
|
|
||||||
- [ ] Deleting the default scope makes * the default; a channel set to the deleted scope goes back to none (channel options > Scope); the app shows the same default scope after a sync
|
|
||||||
- [ ] Settings > Display & power > Battery display: Icon / Percent / Voltage change the status bar at once; percent follows the L1 curve (empties at the Battery shutdown voltage)
|
|
||||||
- [ ] Settings > Display & power > TIME > Clock seconds off: Home and lock screen clocks show HH:MM; on: HH:MM:SS
|
|
||||||
|
|
||||||
## New: Diagnostics, auto-advert, compass (2026-09-25)
|
|
||||||
- [ ] Home > Diagnostics (page 3): tabs Live / System / Font; Live counters (uptime, rx/tx, heap, noise floor, RSSI/SNR, queue, errors) tick every second
|
|
||||||
- [ ] Live > Reset → confirm popup → "Counters reset", rx/tx and forwarded go back to 0
|
|
||||||
- [ ] System shows firmware, build date, board, node name, frequency / SF / BW / CR, TX power; Font shows Polish, Greek and Cyrillic samples without boxes
|
|
||||||
- [ ] Nearby > advert button > AUTOMATIC > Auto-advert 30 s: another node sees your advert (with position) every ~30 s; Off stops it; survives a reboot
|
|
||||||
- [ ] L1: Tools › Auto-Advert shows the value set on L2 (same pref), Tools › Diagnostics unchanged
|
|
||||||
- [ ] Home page 2 > Compass: without a fix "Waiting for a GPS fix" (GPS off: hint to turn it on); standing still "Move to set the heading"; walking: dial turns so your course is under the amber pointer, degrees + cardinal on the right
|
|
||||||
|
|
||||||
## New: Node name, reboot, lock screen, favourites dial (2026-09-25)
|
|
||||||
- [ ] Settings > NODE > Name: popup with the current name, Enter saves; Home and Settings show the new name; another node sees it after your next advert
|
|
||||||
- [ ] Settings > SYSTEM > Reboot / Power off: confirm popup (red button); reboot comes back normally, messages and settings kept
|
|
||||||
- [ ] Settings > Display & power > Lock screen on; > TIME > 12-hour clock changes status bar ("2:05 PM"), Home and lock screen (AM/PM before the date)
|
|
||||||
- [ ] With Lock screen on: screen off (timeout or the button on Home) → wake → clock card with "N new messages"; tapping anywhere does nothing; the knob follows the finger; sliding to the right end unlocks; letting go early springs it back
|
|
||||||
- [ ] Locked: a new message still wakes the screen and shows the toast; the button turns the screen off again; alarm ring shows over the lock
|
|
||||||
- [ ] In the pocket for a while with Lock screen on: nothing changed / sent
|
|
||||||
- [ ] Home page 2 > Favourites: 6 slots; tap an empty one → pick a channel / contact / room; filled slot shows # / person / house icon and unread badge; tap opens it (a room logs in first); hold → Change / Remove
|
|
||||||
- [ ] Channel options and conversation options: pin icon → "Pin to favourites" with six slots (current one highlighted, tap it again to unpin)
|
|
||||||
- [ ] L1 (ui-new): favourites dial and Pin to dial work as before
|
|
||||||
|
|
||||||
## New: Rooms, conversation options, message actions (2026-09-25)
|
|
||||||
- [ ] Messages: sections CHANNELS / DIRECT / ROOMS; "All" / "★ Fav" pill on CHANNELS and ROOMS shows only favourites (and survives a reboot)
|
|
||||||
- [ ] ROOMS lists room servers ("Tap to log in" / last post); "ROOMS - N new" when posts arrived
|
|
||||||
- [ ] Tap a room never logged into: password popup (empty is fine for public rooms) → "Logging in..." → "Logged in" → thread opens
|
|
||||||
- [ ] Wrong password: "Login failed - wrong password?" and the popup comes back; no answer: "No answer from the room"
|
|
||||||
- [ ] Reboot, tap the same room: logs in with the saved password without asking, then opens
|
|
||||||
- [ ] Room thread: other people's posts show their name above the text; your post gets "delivered"
|
|
||||||
- [ ] Room ⚙ / hold the row: "Logged in" status, Fav, Login (new password), Logout (forgets the password; next tap asks again)
|
|
||||||
- [ ] DIRECT: hold a conversation (or ⚙ in the thread): Alerts Default / Muted / Always, Fav (star in the list), Read; muted shows the mute icon
|
|
||||||
- [ ] New message: CONTACTS "All" / "★ Fav" pill
|
|
||||||
- [ ] Hold someone's message bubble: path ("Path (2 hops): A > B" or "Heard directly"), Reply puts "@[name] " in the field with the keyboard up, Set target (message with a position) sets the navigation target
|
|
||||||
- [ ] Hold your own channel post: "Relayed by: ..." once a repeater echoed it
|
|
||||||
- [ ] L1 (ui-new): Room Servers login / logout / saved password work as before (now in the shared RoomSessions)
|
|
||||||
|
|
||||||
## New: Bot (2026-09-25)
|
|
||||||
- [ ] Home page 2: Clock, Bot
|
|
||||||
- [ ] Bot: tabs Channel / Room / Direct / Other; header shows "N sent" once it has replied
|
|
||||||
- [ ] Switches (Enable, Commands, Actions) change and survive a reboot; Direct > DM allow All / Fav
|
|
||||||
- [ ] Channel / Room: list popup (current one highlighted), picking closes it and shows the name
|
|
||||||
- [ ] Trigger: text popup ("*" = any message, shown as "(any msg)"); Reply: {name} {hops} {loc} {time} buttons insert at the cursor
|
|
||||||
- [ ] Other > Quiet from / to: − / + hour, Done; both the same = "Off"
|
|
||||||
- [ ] With Channel enabled + trigger "!hi" + reply "Hi {name}, {hops}": another node writing "!hi" on that channel gets the reply; `!ping` answered when Commands is on
|
|
||||||
- [ ] L1 (ui-new): Tools > Bot looks and works as before (now from the shared BotConfig table)
|
|
||||||
|
|
||||||
## Basics after the_mesh moved to PSRAM (`MESH_IN_PSRAM`)
|
|
||||||
- [ ] Boots normally, contacts and channels are all there
|
|
||||||
- [ ] Messages arrive and send (channel + DM), delivery ticks work
|
|
||||||
- [ ] Companion app over BLE connects and syncs
|
|
||||||
- [ ] A few hours of uptime without a reboot
|
|
||||||
|
|
||||||
## WiFi map download
|
|
||||||
- [ ] Settings > WiFi: Scan lists networks, pick one, password, Save
|
|
||||||
- [ ] Map > download button: popup shows tile count / size, Download starts
|
|
||||||
- [ ] Pill on the map: "Connecting..." then "↓ n / total"
|
|
||||||
- [ ] Popup while downloading: IP, dBm, heap (internal free / largest block) — note the numbers: ______
|
|
||||||
- [ ] Mesh messages still arrive during a download; BLE stays connected
|
|
||||||
- [ ] Stop → popup shows "Unfinished: z…-…, N tiles" with Resume / trash
|
|
||||||
- [ ] Power off mid-download → power on → map shows "↓ Resume?" (top right; tap it) → Resume continues (count jumps past the tiles already on the card)
|
|
||||||
- [ ] Trash discards the unfinished job
|
|
||||||
- [ ] Download finishes: toast "Map: Done, N new tiles", new tiles appear on the map
|
|
||||||
|
|
||||||
## Overzoom (map past the downloaded detail)
|
|
||||||
- [ ] Zoom in beyond the highest downloaded level: map stays visible (magnified), zoom pill reads e.g. "z17 (map z15)"
|
|
||||||
- [ ] Panning over magnified tiles is smooth enough (note if it stutters)
|
|
||||||
- [ ] From a magnified view, download a higher zoom for that area — the sharp tiles replace the magnified ones
|
|
||||||
|
|
||||||
## GPS
|
|
||||||
- [ ] Settings > Navigation shows a "GPS" switch; toggling it turns the module on/off (and survives a reboot)
|
|
||||||
- [ ] Status bar: GPS icon grey while searching, green with a fix, gone when off
|
|
||||||
- [ ] Map crosshair with GPS off: turns GPS on ("GPS on, waiting for a fix"), centres once a fix arrives
|
|
||||||
- [ ] Bot `!gps on` / `!gps off` works on L2
|
|
||||||
|
|
||||||
## Two maps
|
|
||||||
- [ ] Nodes map (Nearby > map icon): contacts with a position as markers, tap → node detail, back → map
|
|
||||||
- [ ] Map: hold the map → "This spot" → Add waypoint → "WPn" at the finger
|
|
||||||
- [ ] Map: pin button → Waypoints → Here (GPS) → waypoint at the GPS position
|
|
||||||
- [ ] Bar (bottom) → "Navigate to" list: waypoints with distance, Trail start (if a trail exists), people sharing live
|
|
||||||
- [ ] Tap a waypoint → target: ring, dashed line from you, bar with distance / bearing / your course / ETA; view frames you + target
|
|
||||||
- [ ] ✕ on the bar clears the target
|
|
||||||
- [ ] Waypoint menu (pencil): Go / Name (Polish letters, 11-byte limit) / Share / Delete (second tap confirms)
|
|
||||||
- [ ] Share → Messages → pick a conversation → compose holds `[WAY]lat,lon name` → send; a Solo L1 receiving it can save it
|
|
||||||
- [ ] Map > ☰ > Options > "Show others' positions" on → someone's `[LOC]` share appears on the Map and in the list; tap → navigate to them
|
|
||||||
- [ ] Node detail (a node with a position) has the compass button → Map with that node as target
|
|
||||||
- [ ] Map > ☰ > Options > "Arrival alert" on + target set → toast "Arrived: …" when you get within the radius
|
|
||||||
|
|
||||||
## Positions in messages
|
|
||||||
- [ ] A received message with `[WAY]lat,lon name`, `[LOC]lat,lon` or plain "lat, lon" shows Go / Save under the bubble
|
|
||||||
- [ ] Save → waypoint named from the [WAY] label (else the sender), Polish letters not cut in half
|
|
||||||
- [ ] Go → Map with that spot as the target
|
|
||||||
|
|
||||||
## Trail & live share (Map > ☰ tools button, left column)
|
|
||||||
- [ ] Record → pill "REC 0 m" at the top; walk: blue trail line follows, distance grows
|
|
||||||
- [ ] Auto-pause (Map > ☰ > Options): standing still → "PAUSED", walking resumes
|
|
||||||
- [ ] Stop / Save / Load / Reset (second tap confirms) behave; a trail saved on L2 loads (same /trail file as L1)
|
|
||||||
- [ ] Export GPX → toast "Saved trails/trail-YYYYMMDD-HHMM.gpx"; the file opens in a GPX viewer (track + waypoints)
|
|
||||||
- [ ] Live share: pick "Send to" (channel or favourite), Share live → pill "LIVE 1h00"; another node sees the [LOC] updates while you move; stops by itself after the chosen time
|
|
||||||
- [ ] Send once: with sharing on → "Position sent"; with it off → Messages with `[LOC]lat,lon` waiting in the compose field
|
|
||||||
- [ ] Track back (with a recorded trail): bar "Back: n pt" with distance / bearing, ring on the breadcrumb; walking advances it; at the start toast "Back at the trail start"; ✕ stops it
|
|
||||||
- [ ] ☰ > Download this area opens the download popup
|
|
||||||
- [ ] Waypoint averaging 5 s/10 s/30 s: pin → "Averaging GPS… n s" pill (tap cancels) → waypoint saved at the mean
|
|
||||||
|
|
||||||
## Map > ☰ > Options (schema-driven)
|
|
||||||
- [ ] Every row changes and survives a reboot; Settings > Display & power > "Imperial units" relabels point spacing and distances
|
|
||||||
- [ ] Changing "Stop sharing after" during a session restarts its clock
|
|
||||||
- [ ] Radius / Alert on (arrive, leave, both) change when the arrival toast fires
|
|
||||||
|
|
||||||
## Same data on L1 (ui-new) after the Core changes
|
|
||||||
- [ ] L1: waypoints saved before the update are still listed (Tools › Trail › Waypoints)
|
|
||||||
- [ ] L1: add / rename / delete waypoint, navigate, ETA line still shows
|
|
||||||
- [ ] L1: Home GPS toggle still works
|
|
||||||
- [ ] L1: Tools › Trail Save / Load / Reset still work (now via TrailEngine)
|
|
||||||
- [ ] L1: Mark with averaging (Trail › Settings › Mark avg) still counts down and marks
|
|
||||||
- [ ] L1: Track back still advances along the trail and ends at the start
|
|
||||||
|
|
||||||
## Roadmap stage 4: UI layout (2026-09-26)
|
|
||||||
- [ ] Home page 3: Repeater, Admin, Diagnostics tiles open their screens; back (arrow or USER button) returns to Home
|
|
||||||
- [ ] Home > Admin: repeaters and room servers listed, favourites (star) first; tap → login / admin tabs; back leaves to the list; with none heard yet a short note instead
|
|
||||||
- [ ] Settings order like L1: DISPLAY (Display & power), SOUND, RADIO (Radio, Bluetooth, WiFi), SYSTEM (Name, GPS, Reboot, Power off), KEYBOARD, CONTACTS & MESSAGES, ABOUT; no Repeater / Diagnostics / Send advert / trail rows left there
|
|
||||||
- [ ] Settings > Display & power: UNITS section with "Imperial units"
|
|
||||||
- [ ] Map > ☰ "Map tools": TRAIL, LIVE SHARE, ARRIVAL ALERT (On/Off with mode and radius), MAP; "Options: trail, sharing, alert" opens Map options; back returns to the map
|
|
||||||
- [ ] Nearby header: back, title, advert (tower), map, sort, scan (refresh icon) -- nothing overlaps; advert popup sends Nearby only / Everyone and sets Auto-advert
|
|
||||||
- [ ] Status bar, right to left next to the battery: Bluetooth (grey on, white with the app), GPS (grey searching, green fix), bell (alarm set), mute, then amber: auto-advert (tower), trail (route), live share (pin), repeater (loop), arrival alert (flag, with a target); each appears / disappears within a second of switching it
|
|
||||||
- [ ] All icons at once still leave the clock readable on the left
|
|
||||||
|
|
||||||
## Roadmap stage 5: screen PIN (2026-09-26)
|
|
||||||
- [ ] Settings > Display & power > SECURITY > Screen PIN "Off"; tap → keypad; 1234 ✓, 1234 ✓ → toast "PIN set", row "On", list stays at the bottom
|
|
||||||
- [ ] A different second entry → "Didn't match - new PIN again"; fewer than 4 digits + ✓ → "At least 4 digits"
|
|
||||||
- [ ] Screen off (WAKE or timeout) and on → PIN card (clock, unread count, keypad) instead of the slider, even with "Lock screen" off
|
|
||||||
- [ ] Right PIN unlocks as soon as the last digit is in; wrong one → "Wrong PIN - n tries left"; 5 misses → "Too many tries - wait 30 s" counting down, keys ignored meanwhile
|
|
||||||
- [ ] USER button does nothing on the PIN card; a ringing alarm is still dismissed by the buttons
|
|
||||||
- [ ] Reboot → PIN card straight after boot
|
|
||||||
- [ ] Change PIN (row tap with a PIN set) works; "Remove" in the popup's header → "PIN removed", slider lock / no lock as before
|
|
||||||
- [ ] PIN survives a reboot and a firmware update (NVS "mc_lock"), not stored in NodePrefs
|
|
||||||
|
|
||||||
## Roadmap stage 5: live map tiles (2026-09-26)
|
|
||||||
- [ ] Map > ☰ > MAP > "Live tiles" on by default; with WiFi on and a network saved, zoom into an area without tiles → pill "WiFi Connecting..." then "live n", tiles appear one by one, the "(map zN)" magnification goes away
|
|
||||||
- [ ] The fetched tiles are on the card (/maps/z/x/y.png): reopen the map offline → still there
|
|
||||||
- [ ] Leave the map: WiFi drops ~30 s later; back within 30 s → no reconnect
|
|
||||||
- [ ] Live tiles off → nothing fetched, toast "Live tiles off"; on without a saved network → "Pick a WiFi network first"
|
|
||||||
- [ ] WiFi off in Settings → no live fetching; an area download still works as before (and takes over from live tiles)
|
|
||||||
- [ ] No SD card map folder at all + live tiles → the folder is created and the map fills in
|
|
||||||
|
|
||||||
## Roadmap stage 5: firmware update (2026-09-26, Arduino 3.x build only)
|
|
||||||
- [ ] Settings > SYSTEM > Firmware update shows the installed version; 2.0.17 build: "Not available in this build"
|
|
||||||
- [ ] Check for update: connects WiFi, asks GitHub; before a release with an L2 image: "vX has no image for this device" (proves TLS + the API work)
|
|
||||||
- [ ] With a release carrying solo-vX-Wio-Tracker-L2-ota.bin: "vX is available" + Install (size in MB); install shows progress, screen stays on, back is refused ("Updating - wait")
|
|
||||||
- [ ] After "restarting..." the device boots the new version (Settings shows it); contacts, messages, settings, PIN and WiFi kept
|
|
||||||
- [ ] Up to date → "Up to date - the latest release is vX"; WiFi off / no network / map download running → toasts instead
|
|
||||||
- [ ] Wrong network in the middle (unplug router) → "Download stalled", old firmware still boots
|
|
||||||
|
|
||||||
## Roadmap stage 6: motion and look (2026-09-26)
|
|
||||||
- [ ] Boot: splash (amber MESHCORE, SOLO, Solo version, "MeshCore x - built date", three dots rising in turn) for ~2.5 s, then fades into Home (or the PIN card); a tap skips it
|
|
||||||
- [ ] Changing screens: the new one fades in from the background with its content drifting into place (up going in, down coming back); rebuilding the same screen (settings toggles, a new message) doesn't animate
|
|
||||||
- [ ] Selected things look the same everywhere (dim accent + light text): Nearby chips, Clock / Bot / Diagnostics tabs, Repeater segments; primary actions are full accent with dark text (map Download / Resume, Firmware update, WiFi / channel Save, Clock Start, alarm Dismiss, New message)
|
|
||||||
- [ ] Settings > Display & power > LOOK > Accent colour: tap a swatch → the page, status icons, chips, names recolour at once; other screens in the new colour; kept after a reboot (splash too)
|
|
||||||
- [ ] Home page swipe / dot tap: the tile row slides in from the swipe side
|
|
||||||
- [ ] Popups (map tools, Nearby advert, scan, radio frequency, map download): backdrop fades, panel rises; toasts rise in and fade out
|
|
||||||
- [ ] Buttons shrink slightly while pressed
|
|
||||||
- [ ] Status bar icons evenly spaced, muted shows a speaker with a cross (also next to muted conversations)
|
|
||||||
- [ ] Own channel post: "1s ✓ 2" (green, number of repeaters heard relaying it) under the bubble, nothing before an echo; DM: ✓ delivered, ✗ (red) not delivered, "..." sending
|
|
||||||
- [ ] Hold a message: quote, time + hops, path diagram sender → repeaters → this device; own post: "Relayed by n repeaters" + names; own DM: Delivered / Not delivered / Sending in words
|
|
||||||
- [ ] Map download popup: no WiFi button, fits the screen (scrolls if the unfinished-job row is shown); no network saved → toast pointing to Settings > WiFi
|
|
||||||
- [ ] Unlock (slider or right PIN): the lock fades away while the screen underneath drifts up into place, no instant jump
|
|
||||||
- [ ] Lock slider: knob snaps to the end near it but only unlocks on letting go; let go earlier → it springs back smoothly
|
|
||||||
|
|
||||||
## Roadmap stage 2: limits for the PSRAM (2026-09-26)
|
|
||||||
- [ ] Boot with `-D UI_HEAP_REPORT`: internal heap free still ~145 KB+ (the bigger rings go to PSRAM)
|
|
||||||
- [ ] Long walk / drive with the trail on: well past 512 points, the map stays smooth zoomed out, saving and GPX export work
|
|
||||||
- [ ] Waypoints: more than 16 can be added; the full message says (64)
|
|
||||||
- [ ] Busy channels: a conversation shows up to 50 bubbles, older ones are kept longer than before
|
|
||||||
- [ ] Nearby with many nodes: up to 64 rows; contact list "All" shows beyond 64 contacts
|
|
||||||
- [ ] Home tile and screen title read "Nodes" (was "Nearby")
|
|
||||||
- [ ] Nodes > advert: "Zero hop" / "Flood" (were "Nearby only" / "Everyone"), toasts "Zero-hop advert sent" / "Flood advert sent"
|
|
||||||
|
|
||||||
## Roadmap stage 3: message history on the SD card (2026-09-26)
|
|
||||||
- [ ] Receive and send a few channel posts and DMs, reboot: they're all back (Messages list and inside), relay marks / delivery ticks as before the reboot
|
|
||||||
- [ ] A busy channel: "Older messages (n)" at the top pages back 50 at a time, "Newer messages" at the bottom returns; older than the ~256 the memory holds are still there
|
|
||||||
- [ ] Settings > System > Storage: SD used / free bar, Maps / Messages / Trails / Other fill in while "Counting files..." runs (a big map takes a while), internal flash bar
|
|
||||||
- [ ] Kept per chat 500 → 100: conversations with more keep their newest 100; back to 500 keeps working (new messages add up again)
|
|
||||||
- [ ] Delete message history: first tap asks, second deletes; Messages is empty, the card's Messages size drops to ~0
|
|
||||||
- [ ] Without an SD card: messages work as before (memory only), Storage says "No SD card"
|
|
||||||
- [ ] A channel deleted and added again (same name / key) gets its history back
|
|
||||||
- [ ] Map tools > Save with a card: toast "Saved trails/trail-....trl"; Save again later: a second file
|
|
||||||
- [ ] Map tools > Load: list newest first ("Saved on the device" too if there is one); a trail: distance / time / points, Load frames it on the map, GPX writes trails/<name>.gpx, Delete asks then deletes
|
|
||||||
- [ ] Diagnostics > Live: GPS row (no data / no fix, n sats / fix, n sats), Last start, Last crash (after a crash)
|
|
||||||
@@ -1,365 +0,0 @@
|
|||||||
# Wio Tracker L2 (ui-lvgl) roadmap
|
|
||||||
|
|
||||||
The L1 → L2 feature port and its audit are done (2026-09-26). This is the plan
|
|
||||||
for what comes next, in order. Tick items off as they land.
|
|
||||||
|
|
||||||
Decisions already made:
|
|
||||||
|
|
||||||
- **Buttons:** WAKE (top, expander P00) turns the screen off / on. USER/BOOT
|
|
||||||
(side) takes you to Home's clock page (a cross-fade); holding it mutes /
|
|
||||||
unmutes the device, also with the screen off; it doesn't wake the screen
|
|
||||||
(a press in a pocket).
|
|
||||||
- **Placeholders:** the "+" popup next to a text field is the placeholder UI;
|
|
||||||
it must offer every placeholder and appear at every field that sends text.
|
|
||||||
- **Message history:** kept on the SD card, about 100 per conversation to
|
|
||||||
start with.
|
|
||||||
- **OTA:** firmware comes from this fork's GitHub Releases.
|
|
||||||
- **PIN:** screen lock only, for now.
|
|
||||||
|
|
||||||
## 1. Quick fixes
|
|
||||||
|
|
||||||
- [x] Keyboard: a key that closes it (so far a message could only be sent).
|
|
||||||
- [x] "+" placeholders at every field that sends text (compose, quick
|
|
||||||
messages, bot replies).
|
|
||||||
- [x] Short, terse descriptions on buttons and rows.
|
|
||||||
- [x] WiFi off switch.
|
|
||||||
- [x] Buttons as decided above.
|
|
||||||
|
|
||||||
## 2. Arduino-ESP32 3.x, PSRAM
|
|
||||||
|
|
||||||
- [x] A separate L2 env on pioarduino 55.03.312-1 (Arduino-ESP32 3.3.12,
|
|
||||||
IDF 5.5): `Wio_Tracker_L2_companion_solo_lvgl_v3`; the 2.0.17 env stays
|
|
||||||
until the new one passes the hardware checklist.
|
|
||||||
- [x] Port: speaker (new I2S driver), BLE (NimBLE), ESP-NOW (IDF 5 callbacks);
|
|
||||||
HTTPS map download and LovyanGFX build unchanged (check on the device).
|
|
||||||
- [x] Large buffers in PSRAM (`psramBuf`): trail drawing, tile cache, map
|
|
||||||
marks, list rows, keyboard maps, message metadata. Internal heap free at
|
|
||||||
runtime: 2.0.17 147.8 KB; 3.x 128.2 KB before, 147.9 KB after (static RAM
|
|
||||||
109 → 71 KB). Measured with `-D UI_HEAP_REPORT`. History and a larger
|
|
||||||
trail come to PSRAM with stage 3.
|
|
||||||
- [x] The 3.x env is the default (2026-09-26, after stages 5 and 6 were
|
|
||||||
tested on it): `Wio_Tracker_L2_companion_solo_lvgl` is Arduino 3.3.12
|
|
||||||
and is the one published; 2.0.17 stays as
|
|
||||||
`Wio_Tracker_L2_companion_solo_lvgl_arduino2` (not published, can't
|
|
||||||
self-update).
|
|
||||||
- [x] Limits sized for the PSRAM, overridable per board so the nRF52
|
|
||||||
defaults stay (the L1 build is byte-for-byte unchanged): trail 512 →
|
|
||||||
4096 points (`TRAIL_CAPACITY`; the map drops points under 2 px apart
|
|
||||||
when drawing, so a long trail doesn't slow redraws; 8 → 32 trail
|
|
||||||
segments), waypoints 16 → 64, message history 48 → 256 channel and
|
|
||||||
32 → 128 DM entries (`HIST_CH_MAX` / `HIST_DM_MAX`; stage 3 moves it
|
|
||||||
to SD), Nearby 32 → 64; in ui-lvgl: contact list 64 → 256 rows,
|
|
||||||
rooms 16 → 32, conversation 30 → 50 bubbles, pickers 64 → 128, WiFi
|
|
||||||
scan 12 → 20. The sim builds with the same limits.
|
|
||||||
Measured after: internal heap 154.1 KB free (was 147.9), PSRAM
|
|
||||||
5.67 MB free.
|
|
||||||
|
|
||||||
## 3. Data on the SD card
|
|
||||||
|
|
||||||
- [x] Message history on SD (`HistoryStore.h`, `-D HIST_ARCHIVE`): one
|
|
||||||
file per conversation in `/sdcard/meshcore/history` (channels keyed by a
|
|
||||||
hash of name + secret, contacts / rooms by key prefix), a ring of the
|
|
||||||
history entries themselves, every new message and later change (relay
|
|
||||||
echo, delivery) written at once. After a reboot the newest entries go
|
|
||||||
back into the RAM ring (Messages list, previews); a conversation shows
|
|
||||||
50 at a time with "Older messages (n)" / "Newer messages" and reads the
|
|
||||||
card, so it reaches back as far as the card keeps. Kept per chat: 100 /
|
|
||||||
250 / 500 (default) / 1000 / 2000, ~20 KB per 100 messages.
|
|
||||||
- [x] Settings > Storage: SD card used / free with what takes it (maps,
|
|
||||||
messages, GPX trails, other; files counted in the background), the
|
|
||||||
internal flash, "Kept per chat", "Delete message history" (tap twice).
|
|
||||||
- [x] Saved trails on the SD card: Map tools > Save writes a new
|
|
||||||
`/sdcard/trails/trail-YYYYMMDD-HHMM.trl` each time (the internal slot
|
|
||||||
without a card); Load lists them newest first (plus the internal slot,
|
|
||||||
where the low-battery auto-save goes) -- each with distance, time and
|
|
||||||
points, Load (the map frames it), GPX, Delete.
|
|
||||||
- [x] Diagnostics > Live: GPS (off / no data / fix or no fix with satellites),
|
|
||||||
why the device last started (crash, watchdog, low voltage...), and the
|
|
||||||
last crash from the core dump partition (task and address).
|
|
||||||
|
|
||||||
## 4. UI layout
|
|
||||||
|
|
||||||
L1 splits its tools into many small screens because of the joystick and the
|
|
||||||
128x64 display; the L2 groups them where they are used instead.
|
|
||||||
|
|
||||||
- [x] Tools: trail, live share and arrival alert stay in the map (its tools
|
|
||||||
popup, with their options behind "Options"); the advert (send now,
|
|
||||||
auto-advert) is in Nearby; Repeater, Admin (a list of repeaters and
|
|
||||||
room servers) and Diagnostics are Home tiles next to Favourites,
|
|
||||||
Compass, Clock and Bot. The melody editor stays in Sound.
|
|
||||||
- [x] Settings in L1's order: display, sound, radio (with Bluetooth, WiFi),
|
|
||||||
system, keyboard, contacts & messages.
|
|
||||||
- [x] Every status icon L1 has (Bluetooth, GPS, alarm, mute, auto-advert,
|
|
||||||
trail, live share, repeater) plus arrival alert; background modes in
|
|
||||||
the accent colour instead of L1's blinking.
|
|
||||||
|
|
||||||
## 5. Security and internet
|
|
||||||
|
|
||||||
- [x] Screen-lock PIN: 4-8 digits in NVS (`lvport::loadPin`), asked on
|
|
||||||
every wake and after a reboot, 5 misses pause entry for 30 s;
|
|
||||||
Settings > Display & power > SECURITY.
|
|
||||||
- [x] Map tiles fetched live while online: the map queues tiles it is
|
|
||||||
missing, `TileDownloader` fetches them one at a time over WiFi (connected
|
|
||||||
on the first miss, dropped 30 s after leaving the map) and saves them to
|
|
||||||
the card; Map > ☰ > Live tiles (NVS, on by default).
|
|
||||||
- [x] One-button OTA from GitHub Releases: Settings > System > Firmware
|
|
||||||
update (`OtaScreen.h`). The latest release's `-Wio-Tracker-L2-ota.bin`
|
|
||||||
(app image; `build-solo-firmwares.yml` now publishes `*_solo_lvgl`) is
|
|
||||||
streamed over TLS verified with the framework's CA bundle into the idle
|
|
||||||
slot of `default_16MB.csv` (two 6.25 MB app slots, no layout change),
|
|
||||||
chip id checked, then restart. Arduino 3.x builds only.
|
|
||||||
- [x] The 3.x env is the `*_solo_lvgl` one (stage 2).
|
|
||||||
- [ ] With the first release that has an L2 asset: test an update end to
|
|
||||||
end.
|
|
||||||
|
|
||||||
## 6. Theme
|
|
||||||
|
|
||||||
- [x] A consistent style, written down in `Theme.h`: accent fill = the
|
|
||||||
primary action (Download, Install, Save, Go), dim accent fill =
|
|
||||||
selected / on (tabs, chips, segments -- also the default theme's
|
|
||||||
CHECKED), accent text = names, counts, modes; one card radius for
|
|
||||||
buttons and rows, pills for chips, `RADIUS_SM` inside; slightly lighter
|
|
||||||
surfaces. Accent colour selectable (Settings > Display & power > LOOK:
|
|
||||||
amber, orange, coral, violet, cyan, lime; NVS `mc_ui`).
|
|
||||||
- [x] Light motion (`Anim.h`): a new screen emerges from the middle (a
|
|
||||||
background-coloured cover fades while the content drifts 6 px), Home
|
|
||||||
pages slide after a swipe, popups and toasts rise and fade in, buttons
|
|
||||||
shrink a little while pressed.
|
|
||||||
- [x] Frame time measured on the device (`-D UI_PERF_TEST`: walks screens by
|
|
||||||
itself, prints render + flush per refresh): ~48 → ~33 ms per transition
|
|
||||||
frame with uncompressed fonts and two 120-line buffers. Internal-RAM /
|
|
||||||
DMA buffers, `-O2`, hot code in IRAM, two draw threads, system malloc:
|
|
||||||
no gain. The rest is LVGL's software rendering at 320x240.
|
|
||||||
- [x] Status bar icons in equal cells; muted is a speaker with a cross.
|
|
||||||
- [x] Under an own message: the age and a small mark as in L1 (✓ n repeaters
|
|
||||||
for channel posts; ✓ / ✗ / ... for DMs) instead of the words.
|
|
||||||
- [x] The path window of a message: quote, time and hops, a path diagram
|
|
||||||
(sender → repeaters → this device), repeaters that relayed an own post,
|
|
||||||
an own DM's delivery in words.
|
|
||||||
- [x] Tile download popup fits the screen; its WiFi button went (WiFi is set
|
|
||||||
up in one place, Settings).
|
|
||||||
- [x] Splash screen (`Splash.h`): MeshCore wordmark, SOLO, the Solo and the
|
|
||||||
upstream version, build date, loading dots.
|
|
||||||
|
|
||||||
## 7. Research: LVGL for the other displays
|
|
||||||
|
|
||||||
- [x] Find out whether LVGL on every display variant gives consistency and a
|
|
||||||
nicer look more easily. Adopt only if the result is better and every
|
|
||||||
feature is kept (L1: 128×64 OLED, nRF52, RAM already 68% used).
|
|
||||||
|
|
||||||
Findings (2026-09-26). Solo builds, static RAM / flash used:
|
|
||||||
|
|
||||||
| Board | MCU | Display | RAM | Flash |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| Wio Tracker L1 | nRF52840 | 128×64 OLED | 68% (74 KB free) | 70% (214 KB free) |
|
|
||||||
| Wio Tracker L1 e-ink | nRF52840 | e-ink | 70% | 71% |
|
|
||||||
| T-Echo Lite | nRF52840 | e-ink | 69% | 64% |
|
|
||||||
| GAT562 Mesh Watch13 | nRF52840 | 128×64 OLED | 68% | 92% (55 KB free) |
|
|
||||||
| ProMicro, GAT562 30S | nRF52840 | 128×64 OLED | (as L1) | |
|
|
||||||
| Heltec V3 / V4 | ESP32-S3 | 128×64 OLED | 56% | 44% of 3.2 MB |
|
|
||||||
| Cardputer ADV | ESP32-S3, no PSRAM | 240×135 colour TFT, keyboard | 56% | 43% of 3.2 MB |
|
|
||||||
|
|
||||||
What LVGL costs on the L2: the library ~310 KB of code (full config, PNG
|
|
||||||
decoder and all widgets; a minimal one is ~120-150 KB), the ui-lvgl screens
|
|
||||||
~225 KB, fonts 360 KB (uncompressed, European + Cyrillic, 12-40 px). ui-new
|
|
||||||
on the L1 is ~137 KB.
|
|
||||||
|
|
||||||
- **128×64 OLED (L1, Heltec, ProMicro, GAT562): to be tried.** Colour and
|
|
||||||
anti-aliasing don't matter there, but the v2 UI goals do: motion (Home
|
|
||||||
carousel slide, loading-dot wave, animated splash, a bottom drawer, radar
|
|
||||||
sweep), soft corners, graphic indicators instead of text, a bigger font
|
|
||||||
to try out, dithering for large elements. LVGL has the animation engine,
|
|
||||||
shapes, TTF fonts at any size and self-laying-out lists for that; rendered
|
|
||||||
in greyscale and converted to 1 bit in the flush, a Bayer threshold there
|
|
||||||
would turn every fade / translucent fill into dithering while 1-bpp text
|
|
||||||
and icons stay crisp. Against it: a rewrite of ui-new's screens (~15.6k
|
|
||||||
lines; `ui-core/` carries over), I2C caps a full frame at ~23 ms either
|
|
||||||
way, and nRF52 memory -- estimated ~20-30 KB RAM (heap + an 8 KB L8
|
|
||||||
buffer) and ~120-150 KB flash for a trimmed LVGL, against 74 KB RAM /
|
|
||||||
214 KB flash free on the L1 (ui-new's drawing code would go) and only
|
|
||||||
55 KB flash on the Watch13. **Next step, after this roadmap:** a spike on
|
|
||||||
the L1 -- Home carousel with the slide and the bottom drawer, a message
|
|
||||||
list with soft bubbles, the dot wave, dithering in the flush -- measured
|
|
||||||
(RAM, flash, fps) and shown in the sim next to ui-new, then decide.
|
|
||||||
- **E-ink (L1 e-ink, T-Echo Lite): no.** Slow full refreshes rule out
|
|
||||||
motion; LVGL's small dirty areas fit partial refresh poorly and would need
|
|
||||||
batching. Same RAM limits as above.
|
|
||||||
- **Cardputer ADV: the one candidate.** A 240×135 colour screen now shows
|
|
||||||
ui-new's 128×64 picture scaled up; ESP32-S3 with room to spare (flash
|
|
||||||
43%). LVGL would use the real resolution and colour, reuse `Theme.h`,
|
|
||||||
`Anim.h`, the fonts and much of the ui-lvgl screen code. Open points: no
|
|
||||||
PSRAM (the L2's PSRAM buffers -- tiles, lists, keyboard maps -- need
|
|
||||||
smaller internal ones or dropping, no raster map), no touch (keyboard
|
|
||||||
focus navigation, LVGL groups, instead of taps), a 135 px-high layout.
|
|
||||||
Worth a separate pilot if the Cardputer matters; otherwise skip.
|
|
||||||
|
|
||||||
So: e-ink stays on ui-new; the OLED boards get an LVGL spike (above) once
|
|
||||||
this roadmap is done; the Cardputer follows whatever the spike shows.
|
|
||||||
|
|
||||||
### L1 spike (2026-09-28): ui-oled -- LVGL doesn't fit the nRF52's RAM
|
|
||||||
|
|
||||||
A throwaway frontend (ui-oled, a solo -Os env and a `SIM_UI=oled` sim page;
|
|
||||||
the code wasn't kept -- a next try starts fresh on the device it's for): the
|
|
||||||
companion with ui-oled in place of ui-new. Home as a strip of pages sliding
|
|
||||||
left / right
|
|
||||||
(clock, messages, radio, GPS, stats) with a pill page indicator, time in the
|
|
||||||
header, a drawer pulled up from the bottom (Bluetooth, GPS, sound, advert),
|
|
||||||
the latest channel as soft bubbles opening from the middle, the dot wave on
|
|
||||||
the splash. LVGL 9.2 renders 1 bit (I1) into a 1 KB buffer, the flush writes
|
|
||||||
the SH1106 page buffer; text is ui-new's misc-fixed 6x9 font wrapped as an
|
|
||||||
LVGL font. It ran in the simulator; on the L1 it never got past building
|
|
||||||
its screens.
|
|
||||||
|
|
||||||
Flash is not the problem: LVGL 77 KB (trimmed config) + the spike's screens
|
|
||||||
34 KB against ui-new's 133 KB -- the spike build was 25 KB smaller than
|
|
||||||
today's solo. RAM is:
|
|
||||||
|
|
||||||
- LVGL needs its own memory outside the heap: a pool for the widgets (the
|
|
||||||
spike peaked at 12 KB in the sim), the draw buffer, and a bigger stack --
|
|
||||||
the nRF52 loop task has 4 KB, so every LVGL call went through a task of its
|
|
||||||
own with a 5-6 KB stack.
|
|
||||||
- UiCore wants 27 KB of heap in one piece (history rings, engines). With
|
|
||||||
ui-new the heap has ~47 KB free when it's made; the first spike build left
|
|
||||||
28 KB (UiCore failed: hang at "UI core"), the trimmed one (12 KB pool, 5 KB
|
|
||||||
stack, offline queue 256 -> 200) got past it and stopped building the
|
|
||||||
screens with 16 KB left.
|
|
||||||
- Making room means giving features up: contacts (184 B each), the offline
|
|
||||||
queue (177 B a frame), message history. Not worth it for the look.
|
|
||||||
|
|
||||||
Also learned: LVGL 9.2 at 1 bit drops the pixels of rounded corners (border
|
|
||||||
and fill alike; anti-aliasing off doesn't help) -- the spike drew its soft
|
|
||||||
corners itself. Dithering was dropped at the user's call: half-dithered text
|
|
||||||
is only noise.
|
|
||||||
|
|
||||||
**Decision (user, 2026-09-28): no LVGL on the nRF52 boards.** ui-new stays
|
|
||||||
there; the v2 look (slide, drawer, soft bubbles, dot wave) can be done in
|
|
||||||
ui-new's own drawing code instead. The ESP32-S3 OLED boards (Heltec V3/V4,
|
|
||||||
56% RAM) and the Cardputer are the only LVGL candidates left.
|
|
||||||
|
|
||||||
## Backlog (found along the way)
|
|
||||||
|
|
||||||
- [x] USB power detection (fixed 2026-09-26): the AW35615 at 0x22 has a
|
|
||||||
FUSB302-style register map (device ID 0x91); VBUSOK (STATUS0 bit 7)
|
|
||||||
needs the measure block on, and POWER (0x0B) resets to 0x01. Board
|
|
||||||
init sets PWR[1..2]; STATUS0 then reads 0x80 on USB. Used by the
|
|
||||||
status bar (charge bolt), Diagnostics > Live "Power", the GPS screen
|
|
||||||
hint and the low-battery warning (skipped on the cable).
|
|
||||||
- [x] GPS screen (2026-09-26): Home > GPS and Settings > System > GPS
|
|
||||||
details -- sky plot, C/N0 bar per satellite, fix / TTFF / DOPs,
|
|
||||||
per-constellation counts (helpers/sensors/GpsSky.h, -D GPS_SKYVIEW;
|
|
||||||
the sim feeds it made-up NMEA).
|
|
||||||
- [x] GPS indoors (2026-09-26): diagnosed with the new GPS screen and raw
|
|
||||||
NMEA logs. On the USB cable (charging) the L76K tracks satellites at
|
|
||||||
25-35 dB-Hz but never decodes navigation data: no UTC in 10 min, no
|
|
||||||
fix in over an hour. On battery, same spot by the window: UTC within
|
|
||||||
seconds, first fix after 377 s. Screen, WiFi, Bluetooth and LoRa TX
|
|
||||||
were ruled out. Cause: noise from USB power / the charger -- a board
|
|
||||||
property, not fixable in firmware. Test GPS on battery.
|
|
||||||
- [ ] GPS indoors, still open (user, 2026-09-26): even on battery the L2
|
|
||||||
does worse than the L1 with the same L76K -- first fix after 377 s by
|
|
||||||
a window, 4 of 22 satellites used, the fix drops and comes back.
|
|
||||||
Something is still off (antenna / placement / another noise source);
|
|
||||||
look again later with the GPS screen, e.g. side by side with the L1.
|
|
||||||
|
|
||||||
Ideas to come back to (2026-09-26):
|
|
||||||
|
|
||||||
- [ ] Speaker click: would keeping the amplifier on at minimum volume
|
|
||||||
remove it?
|
|
||||||
Tried 2026-09-28: amp switched only with the ES8311 DAC volume at the
|
|
||||||
bottom (codec soft ramp, reg 0x37) -- still knocks, so it's the amp's
|
|
||||||
power step itself. Left for later; options: amp on while the screen is
|
|
||||||
on, always on (check GPS), or a longer linger.
|
|
||||||
- [ ] Import routes from the SD card (optional extra).
|
|
||||||
- [x] Battery life without losing features (2026-09-27): the UI loop sleeps
|
|
||||||
until something is due (none while packets are queued; 1-2 ms with a
|
|
||||||
melody / the app connected; up to 10 ms awake, 20 ms dark); screen off:
|
|
||||||
CPU at 80 MHz, touch controller asleep unless it wakes the screen,
|
|
||||||
backlight driver in standby. GPS off really cuts the L76K's rail (and
|
|
||||||
its UART). Battery divider powered only for a reading (measured: settles
|
|
||||||
at once). Also fixed: WiFi left on after a scan cut short, live tiles'
|
|
||||||
WiFi kept with the screen off on the map, a battery ADC read every
|
|
||||||
second. Not measured yet: the current before / after.
|
|
||||||
- [x] Live tiles: bounded cache on the card (2026-09-26) -- they go to
|
|
||||||
/sdcard/maps-live, apart from downloaded maps; an index keeps their
|
|
||||||
order and the oldest are deleted past the limit (Settings > Storage >
|
|
||||||
Live map tiles: 16 / 64 / 256 MB / 1 GB, default 64 MB; Delete).
|
|
||||||
Live tiles saved by earlier builds sit in /sdcard/maps and can't be
|
|
||||||
told apart from downloaded ones.
|
|
||||||
- [x] Hiking trails: Waymarked Trails overlay in /sdcard/maps-trails, drawn
|
|
||||||
over the base map (Map tools > Hiking trails); fetched with an area
|
|
||||||
download (re-running one adds just the trails) and live; empty tiles
|
|
||||||
as 0-byte files.
|
|
||||||
- [x] Map areas: a frame with corner handles picks the area to download
|
|
||||||
(the map pans / zooms under it; size, zoom range, tiles, trails in a
|
|
||||||
bar); downloaded areas are listed in /sdcard/maps/areas.txt (Map tools
|
|
||||||
> Map areas: show, rename, fill gaps, refresh, trails on / off, delete
|
|
||||||
-- tiles another area covers stay). Maps fetched before the list aren't
|
|
||||||
in it (re-download them).
|
|
||||||
- [x] A grid under the map (and the minimap), its step a round distance in
|
|
||||||
the metric / imperial unit, with a scale bar: position and scale where
|
|
||||||
no tile is loaded.
|
|
||||||
- [x] Vector maps spike: own VT2 format from OSM (tools/maps/osm_vector.py,
|
|
||||||
Overpass JSON -> /sdcard/vmap, data zooms 10/12/14, bbox per feature),
|
|
||||||
scanline polygons + thick lines drawn into the 256 px tile; behind Map
|
|
||||||
tools > Vector map (test), raster where no vector data. Measured on the
|
|
||||||
L2: 8-60 ms drawing, SD read ~30 ms per new data file (cached after).
|
|
||||||
- [x] Vector maps: VT3 (delta / varint points, ~half the size); hiking
|
|
||||||
routes per stretch, the routes sharing it as side-by-side stripes on a
|
|
||||||
white band, less simplified; demanding / alpine paths dotted; labels of
|
|
||||||
named points (places, peaks with height, huts, passes, springs...) as
|
|
||||||
a layer over the tiles, placed by priority. No street names (raster).
|
|
||||||
- [x] Contours on the vector map: osm_vector.py --dem (tools/maps/dem.py,
|
|
||||||
standard library: Terrain Tiles from AWS Open Data, SRTM ~25 m,
|
|
||||||
smoothed, marching squares); 100 m lines from z12, 20 m from z15.
|
|
||||||
- [x] Vector regions: a region is one pack file (/vmap/*.vpk, index +
|
|
||||||
tiles + points, read in place; osm_vector.py --pack); input from
|
|
||||||
Overpass or Geofabrik PBF extracts (--pbf, pyosmium; several across a
|
|
||||||
border) for a --bbox; --lang for names. Map tools > Vector regions
|
|
||||||
lists the packs (show, delete). No download on the device: packs are
|
|
||||||
made by the user.
|
|
||||||
- [ ] A Tools page on the solo site (meshcore-solo-site, beside the sim):
|
|
||||||
vector pack maker in the browser (pick a box on a map, Overpass +
|
|
||||||
DEM, a JS port of osm_vector.py in a worker, .vpk to save), the GPX
|
|
||||||
downloader (tools/gpx-downloader) and screenshots (tools/screenshot.py
|
|
||||||
over Web Serial).
|
|
||||||
- [ ] Vector map styling: anti-aliasing, dark theme.
|
|
||||||
|
|
||||||
User list, second batch (2026-09-26):
|
|
||||||
|
|
||||||
- [x] Status bar icons look like different sizes? Own icon font
|
|
||||||
(fonts/status_icons.py refits FontAwesome to one size); scrollbars
|
|
||||||
everywhere thin and at the edge, clear of the content.
|
|
||||||
- [x] Under own messages: a small counter as in the original (L1), not a
|
|
||||||
checkmark with "relayed".
|
|
||||||
- [x] Polish the path popup of a sent / received message (roles on the
|
|
||||||
right, our post's repeaters under us, long paths scroll).
|
|
||||||
- [x] Map download popup runs off the screen; take the WiFi settings out
|
|
||||||
of it (one place: Settings > WiFi). Compact: a progress bar, one
|
|
||||||
line of counts, errors on one line, only the network's name.
|
|
||||||
- [x] Mentions: "@[nick]" shows as "@nick" in the accent colour (a span);
|
|
||||||
our own name underlined and its bubble outlined. Previews and the
|
|
||||||
message popup's quote show "@nick" too.
|
|
||||||
- [x] Splash: "MeshCore Solo" with the upstream version and ours, all in
|
|
||||||
the MeshCore title font (the wordmark's letters plus l, v, d, digits,
|
|
||||||
'.', '-' drawn to match; Noto for a line with anything else).
|
|
||||||
- [x] Emoji: every single-codepoint emoji plus all flags, colour Twemoji
|
|
||||||
at 16 px baked into flash (~1.2 MB; fonts/emoji.py -> ui_emoji_data.c),
|
|
||||||
the text fonts' fallback. Flags are swapped for one private codepoint
|
|
||||||
before text reaches a label. Other sequences (skin tones, ZWJ) draw as
|
|
||||||
their parts. Credit in Settings > About. Later: an emoji picker on the
|
|
||||||
keyboard.
|
|
||||||
- [x] Tap-to-wake toggle (Settings > Display, NVS, on by
|
|
||||||
default; off: only the top button wakes it).
|
|
||||||
- [x] Favourites as their own screen in the menu rather than a tile: a
|
|
||||||
Home card left of the main page (six slots, unread badges).
|
|
||||||
- [x] WiFi indicator icon in the status bar: while WiFi is switched on,
|
|
||||||
bright when connected, dim otherwise.
|
|
||||||
- [x] Home rebuilt like a phone's (2026-09-27): favourites | clock with
|
|
||||||
three user-picked telemetry fields (L1's dashboard_fields; tap the
|
|
||||||
clock for Clock) | minimap framing you, live shares and the target
|
|
||||||
(tap: Navigation map) | apps, 3x2 per page. The side button returns
|
|
||||||
to the clock page.
|
|
||||||
- [x] Arrange Home's apps (2026-09-28): hold an app (or Settings > Home
|
|
||||||
apps) -- drag a tile to move it, hold it at a screen edge for the next
|
|
||||||
page, tap to hide / show; Settings can't be hidden. Saved in NVS on
|
|
||||||
Done or on leaving Home.
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# Plan: from here to the merge into dev (2026-09-28)
|
|
||||||
|
|
||||||
Work in order; each item is implemented, checked in the sim, flashed, and
|
|
||||||
committed only after it's accepted. Tick items off as they land. Working
|
|
||||||
file: goes in the repo clean-up (stage I).
|
|
||||||
|
|
||||||
## A. Bugs
|
|
||||||
|
|
||||||
- [x] **A1. Restart on a screenshot + "340 B stack free".** Likely one cause.
|
|
||||||
Diagnostics shows the UI loop task's lowest-ever stack headroom
|
|
||||||
(`uxTaskGetStackHighWaterMark`); the Arduino loop stack is 8 KB, so
|
|
||||||
340 B left is nearly an overflow. `takeScreenshot()` keeps a 960 B line
|
|
||||||
buffer on that stack, plus the FAT write.
|
|
||||||
1. Confirm with Diagnostics > Last crash after a screenshot restart.
|
|
||||||
2. Move the line buffer off the stack.
|
|
||||||
3. Raise the loop stack (internal RAM).
|
|
||||||
4. Measure the headroom on heavy screens (map, history, OTA).
|
|
||||||
5. Label the Diagnostics row with the task it measures.
|
|
||||||
Done 2026-09-28: loop stack 16 KB (SET_LOOP_TASK_STACK_SIZE), line
|
|
||||||
buffer static, row "UI stack free (lowest)". Measured 8532 B free
|
|
||||||
after map, history and screenshots, so the UI peaks at ~7.8 KB --
|
|
||||||
the old 8 KB left 340 B. Could go down to 12 KB if RAM gets tight.
|
|
||||||
- [x] **A2. USB popup before the splash screen is gone.** `usbPoll()` waits
|
|
||||||
for the splash to finish; a cable plugged in at boot shows the popup
|
|
||||||
right after it.
|
|
||||||
|
|
||||||
## B. Map
|
|
||||||
|
|
||||||
- [x] **B1. Map menu order.** List the current entries, propose groups and
|
|
||||||
an order (view, overlays, areas & downloads, trails, test), agree on
|
|
||||||
it before coding. Done: TRAIL / LIVE SHARE / ARRIVAL ALERT (each with
|
|
||||||
its own options page, one section of PG_NAV), OFFLINE MAPS (Map areas,
|
|
||||||
Live tiles; "Download an area" dropped, a running / unfinished
|
|
||||||
download shows at the top of Map areas), LAYERS.
|
|
||||||
- [x] **B2. Downloaded areas.** Selecting an area previews it at once (no
|
|
||||||
Show button); leaving the menu hides it and restores the previous map
|
|
||||||
view. Fixes the area staying on the map after Show.
|
|
||||||
|
|
||||||
## C. Diagnostics: the noise tests (to think over)
|
|
||||||
|
|
||||||
Diagnostics stays as it is. Only the Noise tab is in question: the tests that
|
|
||||||
found the interference source (noise floor with the board's parts off one by
|
|
||||||
one, the 850-930 MHz and mesh-channel sweeps, the spike hunt, the 15 s states
|
|
||||||
for a second radio). They are L2-specific and block the loop for ~90 s.
|
|
||||||
|
|
||||||
- [x] Decide: remove them, or keep one universal tool (for example a noise
|
|
||||||
floor sweep round the mesh frequency that works on any board) and drop
|
|
||||||
the L2 part toggling and spike hunt. Done: one universal Measure (the
|
|
||||||
floor on the mesh frequency + a +-1.1 MHz sweep, ~12 s, a legend under
|
|
||||||
it); the L2-only tests are gone. Left for stage F: the L2 board's
|
|
||||||
setGrovePower / setSdPower, now unused.
|
|
||||||
|
|
||||||
## D. Quiet hours in the core
|
|
||||||
|
|
||||||
- [x] A start and end time when the device is silent; in ui-core, so L1, L2
|
|
||||||
and other boards get it. New NodePrefs fields go at the end of the
|
|
||||||
stored layout, so an L1 keeps its settings after the update.
|
|
||||||
To decide: the Clock alarm rings anyway (proposed yes); whether a
|
|
||||||
message wakes the screen during quiet hours; with the clock not set,
|
|
||||||
quiet hours are off (proposed).
|
|
||||||
Done 2026-09-28: NodePrefs quiet_hours / quiet_from / quiet_to
|
|
||||||
(sentinel 0x30, sizeof 2832), off by default, 22-7; the alarm rings, a
|
|
||||||
message doesn't wake the screen, a manual mute / unmute stands until
|
|
||||||
the window ends. hourInWindow() / localHour() shared with the bot's
|
|
||||||
quiet hours. L2 tested; L1 built, not flashed yet.
|
|
||||||
|
|
||||||
## E. OTA end-to-end test
|
|
||||||
|
|
||||||
- [x] After the feature changes: a real release with an L2 asset (the v3 env
|
|
||||||
must be the `*_solo_lvgl` one before tagging), install over WiFi.
|
|
||||||
Publishing the release needs confirmation first.
|
|
||||||
Done 2026-09-29: temporary release `dev-ota-test` (61645260, app image
|
|
||||||
from build.sh, marked latest) installed over WiFi on the L2; it came up
|
|
||||||
as dev-ota-test-61645260. The release is to be deleted by hand.
|
|
||||||
|
|
||||||
## F. The big review
|
|
||||||
|
|
||||||
- [x] Dead code; similar elements written several times, merged into one;
|
|
||||||
places to speed things up or save RAM, flash and battery.
|
|
||||||
First a list of findings to accept, then fixes in batches, each checked
|
|
||||||
in the sim and on the device.
|
|
||||||
Done 2026-09-28 (-265 lines): dead code (anim::fadeHide,
|
|
||||||
presetCount, the L2 board's setSdPower / setGrovePower / expanderOK);
|
|
||||||
one buttonBar() / barButton() for 7 copies of the button row;
|
|
||||||
confirmBody() for 5 red-button popups; tapConfirmed() for 11
|
|
||||||
"tap again" buttons, all 3 s and the label restored; noteLabel() for
|
|
||||||
~30 wrapped notes; localTm() in NodePrefs.h and one MONTHS table
|
|
||||||
(lvgl, MsgExpand, the L1 clock); the sim keeps UI settings in an
|
|
||||||
in-memory nvs::, one set of load / save helpers; asleep, the loop
|
|
||||||
wakes every 50 ms, not 20. Left: map tables to PSRAM only if the
|
|
||||||
internal heap gets tight; emoji 1.25 MB of flash (fits).
|
|
||||||
|
|
||||||
## G. Core parity with L1 SOLO
|
|
||||||
|
|
||||||
- [x] A feature table: L1 SOLO / ui-core / L2 (ui-lvgl). What's missing goes
|
|
||||||
into ui-core, not into each UI separately.
|
|
||||||
Done 2026-09-28: L1 sends through UiCore::sendDirectText /
|
|
||||||
sendChannelText; ui-core/Telemetry.h (dashboard fields, sensor
|
|
||||||
readings) for both; CLI rescue on L2 (side button held in the first
|
|
||||||
8 s); L1's repeater radio through rptctl; L1 Settings built from
|
|
||||||
SettingsSchema (short labels, SCHEMA_* placeholders per section; 20
|
|
||||||
hand-written rows gone). Kept per device: Noise measure (L2), screen
|
|
||||||
PIN (L2, a PR pending). Later, as settings are touched: radio switches,
|
|
||||||
keyboard alphabets and chat filters into the schema.
|
|
||||||
|
|
||||||
## H. Before the merge into dev
|
|
||||||
|
|
||||||
- [x] The pull request about the lock screen. Done 2026-09-29: PR #37 merged
|
|
||||||
and adapted (ui-core/ScreenLock.h shared with L2, NodePrefs sentinel
|
|
||||||
0x31, number pad as a keyboard flag, PIN typed twice and saved at once,
|
|
||||||
no USB drive behind the PIN on L2).
|
|
||||||
- [x] The new issues. #38 (signing ERR 4): the buffer allocated before the
|
|
||||||
reply, halving down; the nRF52 Solo builds' offline queue 256 -> 128,
|
|
||||||
so the L1 heap has ~27 KB free instead of ~5. Also fixed: L1 ABC
|
|
||||||
keyboard letters blank since bf1e6fca.
|
|
||||||
- [x] A last review: all 10 CI solo envs, L1 / Heltec companion + repeater
|
|
||||||
and the native sim build (RAK 4631 over flash, ignored).
|
|
||||||
- [x] Merge into dev (fast-forward).
|
|
||||||
|
|
||||||
## I. Repo, documentation, website, firmware tiers (its own detailed plan)
|
|
||||||
|
|
||||||
- [ ] Repo: remove working files (plans etc.), keep only the project's code;
|
|
||||||
README down to the minimum; add Buy Me a Coffee.
|
|
||||||
- [ ] Documentation written from scratch: short, describing the firmware's
|
|
||||||
features, easy to browse; sections marking where devices differ; no
|
|
||||||
screenshots of every screen and device. It stays in the repo, its
|
|
||||||
official entry is the simulator website.
|
|
||||||
- [ ] Website rebuilt and polished; the simulator lets you pick which
|
|
||||||
firmware to simulate.
|
|
||||||
- [ ] Firmware in three tiers, one shared core, extras (map tiles etc.) by
|
|
||||||
what the hardware can do, making full use of each device:
|
|
||||||
- minimal -- nRF52, a limited UI, small e-ink and OLED;
|
|
||||||
- standard -- mostly ESP32, large e-ink and LCD, higher resolutions,
|
|
||||||
a somewhat richer UI;
|
|
||||||
- color -- colour touch screens.
|
|
||||||
|
|
||||||
Proposed: write a short tier spec (which boards, which features, what's in
|
|
||||||
the core) as a document before stage F, so the review consolidates towards
|
|
||||||
it; implement the tiers, docs and website after the merge.
|
|
||||||
|
|
||||||
## Open decisions
|
|
||||||
|
|
||||||
- I: tier spec before stage F, or after the merge.
|
|
||||||
@@ -1,937 +0,0 @@
|
|||||||
# Feature roadmap
|
|
||||||
|
|
||||||
Joystick-only UX constraints: 4 directions + Enter + Back. No text entry except inside KeyboardWidget. Everything else navigable with cursor + press.
|
|
||||||
|
|
||||||
Status legend: 📋 planned · 🚧 in progress · ✅ done · ❌ rejected/deferred
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Priority queue
|
|
||||||
|
|
||||||
### ✅ Mark-all-read at type level
|
|
||||||
|
|
||||||
Hold Enter on the MESSAGE mode-select screen (DM / Channels / Rooms) opens a 1-item context menu "Mark all read". Acts on the currently highlighted mode and shows a brief confirmation alert.
|
|
||||||
|
|
||||||
Implementation:
|
|
||||||
- New `UITask::clearAllDMUnread()` — `memset` over `_dm_unread_table`
|
|
||||||
- `MessagesScreen::clearAllChannelUnread()` already existed
|
|
||||||
- `UITask::clearRoomUnread()` already existed
|
|
||||||
- Title is a `static const char*` table (PopupMenu stores the title pointer verbatim — locals would dangle)
|
|
||||||
- Zero schema impact, all counters live in RAM
|
|
||||||
|
|
||||||
### ✅ Favourites dial
|
|
||||||
|
|
||||||
Phase 1 ✅ (storage + read-only render + grid nav)
|
|
||||||
Phase 2 ✅ (pin from Contact options menu in QuickMsg + slot picker submenu)
|
|
||||||
- Enter on a filled tile opens that contact's DM directly
|
|
||||||
- Cancel from a Favourites-opened DM returns to the home screen
|
|
||||||
Phase 3 ✅ (in-place pin picker on empty tile: upstream-favourited contacts first,
|
|
||||||
then recent DM contacts deduped; selecting a contact that's already pinned
|
|
||||||
elsewhere moves it to the new slot)
|
|
||||||
|
|
||||||
**Follow-up done**: OLED unread-badge overlap fixed — badge and name share the
|
|
||||||
same baseline, drawTextEllipsized's max width subtracts badge width + a 3 px
|
|
||||||
gap so names shorten to "Nam…" before the digit.
|
|
||||||
|
|
||||||
A 2×3 grid (six slots) of pinned contacts on its own home page, between Clock and Messages. Joystick picks a tile, Enter opens the existing DM conversation or sends a pre-set quick reply.
|
|
||||||
|
|
||||||
Data model:
|
|
||||||
- New field in NodePrefs: `uint8_t favourite_contacts[6][6]` — first 6 bytes of each contact's `pub_key` (enough to disambiguate locally)
|
|
||||||
- Lookup at render time: walk contacts, match prefix, render name + unread badge
|
|
||||||
- Empty slot renders as "+" placeholder; Enter on empty opens a contact picker (existing UI)
|
|
||||||
|
|
||||||
Pinning UX:
|
|
||||||
- In QuickMsg DM list, long-press on a contact → context menu → "Pin to dial" → asks which of the 6 slots
|
|
||||||
- Unpin via the same menu (only shown when contact is already pinned)
|
|
||||||
|
|
||||||
Schema bump: add `favourite_contacts` to NodePrefs, bump `SCHEMA_SENTINEL` low byte.
|
|
||||||
|
|
||||||
Render layout (250×122 landscape e-ink):
|
|
||||||
```
|
|
||||||
╔══════════════════════════════╗
|
|
||||||
║ Favourites ║
|
|
||||||
╠══════════════════════════════╣
|
|
||||||
║ ┌──────┐ ┌──────┐ ┌──────┐ ║
|
|
||||||
║ │Alice │ │Bob 3 │ │ + │ ║
|
|
||||||
║ └──────┘ └──────┘ └──────┘ ║
|
|
||||||
║ ┌──────┐ ┌──────┐ ┌──────┐ ║
|
|
||||||
║ │Carol │ │ + │ │ + │ ║
|
|
||||||
║ └──────┘ └──────┘ └──────┘ ║
|
|
||||||
╚══════════════════════════════╝
|
|
||||||
```
|
|
||||||
|
|
||||||
Joystick navigation is natural with 6 tiles (UP/DOWN between rows, LEFT/RIGHT within row).
|
|
||||||
|
|
||||||
### ✅ GPS trail (renamed from breadcrumb)
|
|
||||||
|
|
||||||
Phase 1 ✅ (storage + sampling + Summary view + G indicator in status bar)
|
|
||||||
Phase 2 ✅ (auto-fit Map view with cos(lat) aspect compensation; LEFT/RIGHT cycles views)
|
|
||||||
- Summary scrolls on short panels (OLED) so hint stops overlapping
|
|
||||||
- Status-bar G blinks at the same cadence as A (forces 1 s home refresh)
|
|
||||||
- Default sampling 30 s + 5 m min-delta (was 60 s + 25 m — too sparse on foot)
|
|
||||||
- "Avg speed" replaces "Speed"; Time uses RTC so it ticks every render
|
|
||||||
- Stop → start creates a new segment; the map doesn't bridge dead time,
|
|
||||||
and total distance skips segment boundaries
|
|
||||||
Phase 3 ✅ (per-point list view with HH:MM local time + delta-from-previous;
|
|
||||||
segment-start rows show "start" instead of a delta)
|
|
||||||
Phase 4 ✅ (Hold-Enter popup grows Save / Load / Reset / Export GPX /
|
|
||||||
Export saved entries. Single flash slot at /trail (binary header with
|
|
||||||
magic+version+count+accumulated_ms then raw TrailPoint records). GPX 1.1
|
|
||||||
dump goes over USB Serial; "Export GPX" streams the live RAM ring,
|
|
||||||
"Export saved" streams the flash file straight to USB without touching
|
|
||||||
the live ring. Segments respect SEG_START boundaries. Alert reflects
|
|
||||||
BLE-app-collision state)
|
|
||||||
Phase 5 ✅ (Settings + actions consolidated into a single Hold-Enter popup —
|
|
||||||
Min dist + Units cycled with LEFT/RIGHT (popup stays open), plus
|
|
||||||
Start/Stop tracking and Reset action items. Short Enter never toggles —
|
|
||||||
both start and stop go through the popup, so a stray tap can never change
|
|
||||||
tracking state. View counter (N/3) lives in the title bar; the bottom hint
|
|
||||||
row is gone, content fills the freed space. Sampling cadence fixed at 1 s,
|
|
||||||
GPS upd setting also removed; both rely on the sensor manager's defaults)
|
|
||||||
Polish ✅ (map view: filled/open dot markers around segment breaks;
|
|
||||||
"Waiting for GPS fix" status when started without a lock;
|
|
||||||
capacity bumped to 512 points; elapsed/avg-speed run on millis() instead
|
|
||||||
of RTC so they tick even before GPS time is synced)
|
|
||||||
|
|
||||||
Tools › Breadcrumb. Periodically samples `(lat, lon, ts)` into a RAM ring buffer; user explicitly saves snapshots to flash.
|
|
||||||
|
|
||||||
**Logging is a runtime state**, not a settings value. User starts/stops from the
|
|
||||||
Tools › Breadcrumb screen. Once active, sampling continues in the background
|
|
||||||
regardless of which screen is shown, and a `G` indicator appears in the status
|
|
||||||
bar (analogous to `A` for auto-advert). A reboot resets the active state to
|
|
||||||
off; the RAM trail is also lost on reboot unless saved to a flash slot first.
|
|
||||||
|
|
||||||
Settings only control sampling cadence and the min-distance gate — they don't
|
|
||||||
enable/disable the feature.
|
|
||||||
|
|
||||||
**Storage model — RAM ring with explicit save**
|
|
||||||
|
|
||||||
Rationale: auto-off only blanks the display, the firmware keeps running, so the RAM trail survives every idle scenario. Typical use is a single trip start→stop while wearing the device; persisting across reboots is rarely wanted. RAM-only avoids ~1400 flash writes/day and the LittleFS wear that comes with continuous logging.
|
|
||||||
|
|
||||||
- Live ring: `BreadcrumbEntry[BC_RAM_CAP]` in `UITask` (or a dedicated component). Each entry `int32_t lat_1e6, int32_t lon_1e6, uint32_t ts` = 12 B. Cap = 256 → 3 KB RAM. nRF52840 (256 KB RAM) has plenty of headroom.
|
|
||||||
- Wrap-on-write: oldest entry replaced when buffer full.
|
|
||||||
- Reboot wipes the live trail (intentional; matches the "this trip" model).
|
|
||||||
|
|
||||||
**Snapshot slots on flash** (user-initiated only):
|
|
||||||
- `/breadcrumb.0`, `/breadcrumb.1`, `/breadcrumb.2` — three named slots
|
|
||||||
- Each file: small header (count, start_ts, end_ts, total_distance_m) + entry array
|
|
||||||
- Written only on explicit "Save trail" action — zero background writes, zero wear concern
|
|
||||||
- Optional: auto-save to slot 0 on detected low-battery shutdown (single write before going dark)
|
|
||||||
|
|
||||||
UI screens (LEFT/RIGHT cycles):
|
|
||||||
1. **Summary** — total distance (km), elapsed time (h:mm), point count, current speed (from last 2 samples), GPS fix indicator
|
|
||||||
2. **Trail map** — ASCII bounding-box plot. Auto-fit the polygon, current position marked `X`, start marked `*`. UP/DOWN zoom, LEFT/RIGHT pan when zoomed.
|
|
||||||
3. **Last N entries list** — scroll through recent points with timestamp + delta from previous.
|
|
||||||
|
|
||||||
Joystick actions:
|
|
||||||
- Enter → toggle live logging on/off (status bar shows `*` when active, like auto-advert `A`)
|
|
||||||
- Back → exit
|
|
||||||
- Hold Enter → context menu:
|
|
||||||
- "Reset trail" — clear RAM ring
|
|
||||||
- "Save trail → slot N" — snapshot RAM into chosen flash slot
|
|
||||||
- "Load trail ← slot N" — restore from chosen slot into RAM ring
|
|
||||||
- "Export over USB" — dump live RAM trail as KML/GPX over serial
|
|
||||||
|
|
||||||
Statistics computed on the fly walking the ring:
|
|
||||||
- Total distance: sum of Haversine(p[i], p[i-1])
|
|
||||||
- Elapsed time: ts[last] - ts[first]
|
|
||||||
- Current speed: dist(last, prev) / (ts[last] - ts[prev])
|
|
||||||
|
|
||||||
Settings:
|
|
||||||
- Settings › GPS › Breadcrumb interval: 30 s / 1 min / 5 min / 15 min (default 1 min) — only the cadence; logging on/off is a Tools toggle
|
|
||||||
- Settings › GPS › Breadcrumb min delta: 5 m / 25 m / 100 m (skip near-stationary samples to keep the ring densely populated with real movement)
|
|
||||||
- Export format: GPX (standard for GPS tracks; OSMAnd / Garmin compatible)
|
|
||||||
|
|
||||||
Schema impact: new prefs fields `uint8_t breadcrumb_interval_idx`, `uint8_t breadcrumb_min_delta_idx`. Sentinel bump. The slot files are separate from prefs.
|
|
||||||
|
|
||||||
Edge cases:
|
|
||||||
- No GPS fix: skip sampling, status indicator dims
|
|
||||||
- Low-batt shutdown: optional auto-save to slot 0 (one write) before powerdown
|
|
||||||
- Memory: 3 KB RAM is negligible on this MCU; if RAM ever tightens, drop to 128 entries
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backlog (not yet prioritised)
|
|
||||||
|
|
||||||
### ✅ Waypoints + navigation cluster — mark a spot, navigate back
|
|
||||||
|
|
||||||
**✅ Shipped** (branch `feat/waypoints-nav`). The whole navigation suite landed. Notable deltas from the original spec that follows:
|
|
||||||
|
|
||||||
- **Waypoints** — Mark here / list / Rename / Delete / **Clear waypoints** / **Send**; stored in `/waypoints` (16 max), independent of trail recording and kept across Reset trail.
|
|
||||||
- **Map** — waypoint marker shows the **first two** label chars (not one), placed edge-aware so it stays on-map. With a trail the view frames the recorded route and clamps far waypoints to the nearest edge; with no trail it auto-fits to waypoints + live position. Degenerate single-point case handled.
|
|
||||||
- **Shared NavView** — one `navview::draw(...)` reused by waypoints, Trail-start backtrack, Nearby-node nav and message-location nav. Shows distance + `To:` + `Hdg:` (two absolute bearings), honouring the global Units setting.
|
|
||||||
- **COG ring in UITask** — heading source decoupled from trail logging, time-sampled with gross-error rejection + min-displacement gate; restarts after a >15 s GPS gap so a reacquired fix can't imply a teleport heading.
|
|
||||||
- **Standalone Compass** (Tools › Compass) — heading-up **scrolling tape** with a fixed travel-direction pointer + large degrees/cardinal readout. (A north-up circular dial was tried first and dropped — only a few-px needle fits the OLED's vertical space.)
|
|
||||||
- **Global Units** (Settings › System: Metric/Imperial) — drives every distance/speed in Tools, the min-distance gate, and the map scale-bar. New `units_imperial` + `trail_show_pace` prefs (schema 0xC0DE0006); the old combined `trail_units_idx` retired.
|
|
||||||
- **Location over mesh** — Waypoints list → **Send** shares `[WAY]lat,lon label`; a received location ({loc} text or a [WAY] share) offers **Navigate / Save waypoint** from both the message list row and the fullscreen view. Backed by a shared `geo::parseLatLon`.
|
|
||||||
|
|
||||||
Shared helpers extracted: `geo::` (haversineKm/bearingDeg/bearingCardinal/fmtDist/parseLatLon) in `GeoUtils.h`; 1-px `gfx::drawLine/drawCircle` in `GfxUtils.h`; one `UITask::currentLocation()` GPS accessor.
|
|
||||||
|
|
||||||
The original design spec is kept below as a record.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Turns Solo from a comms device into a basic GPS navigator. Mark the current
|
|
||||||
position with a short label, then later get bearing + distance back to it —
|
|
||||||
ideal for off-grid use (car, camp, trailhead, water source).
|
|
||||||
|
|
||||||
**Storage** — dedicated flash file `/waypoints` (separate from prefs, like
|
|
||||||
`/trail`). Fixed table, no schema-sentinel impact:
|
|
||||||
|
|
||||||
```
|
|
||||||
struct Waypoint {
|
|
||||||
int32_t lat_1e6, lon_1e6; // saved fix
|
|
||||||
uint32_t ts; // when marked (RTC)
|
|
||||||
char label[12]; // short name, NUL-terminated ("CAR", "CAMP", "H2O"…)
|
|
||||||
};
|
|
||||||
static const int WAYPOINT_MAX = 16; // 16 × 24 B = 384 B file
|
|
||||||
```
|
|
||||||
|
|
||||||
**Marking** — from the GPS or Trail screen: Hold Enter → "Mark here".
|
|
||||||
- Requires a GPS fix; otherwise alert "No GPS fix".
|
|
||||||
- Opens the existing `KeyboardWidget` to type the label (≤11 chars). Empty
|
|
||||||
input auto-labels `WP<n>`.
|
|
||||||
- Saves the current fix + label, appends to the file.
|
|
||||||
|
|
||||||
**Visible on the trail Map** — waypoints render on the existing Map view as a
|
|
||||||
distinct marker (e.g. a hollow diamond or a small flag) so they show in
|
|
||||||
context with the recorded track:
|
|
||||||
- Fold waypoint coords into the map's bounding box so off-track waypoints
|
|
||||||
stay in frame. Today `renderMap()` derives the box from
|
|
||||||
`TrailStore::boundingBox()`; extend it to also span the waypoint table
|
|
||||||
(and handle the "waypoints but empty trail" case — map still renders).
|
|
||||||
- Generalise the `project()` lambda to take raw (lat, lon) instead of a
|
|
||||||
`TrailPoint&` so the same projection draws both track points and waypoints.
|
|
||||||
- Marker shows the label's first character beside it when there's room
|
|
||||||
(122 px is tight with many waypoints); the full label lives in the list /
|
|
||||||
nav view.
|
|
||||||
|
|
||||||
**Trail workflow integration** — waypoints live *inside* the Trail screen, not
|
|
||||||
a separate Tools entry, because marking points of interest happens while you
|
|
||||||
are recording:
|
|
||||||
- **Mark**: Trail → Hold Enter → "Mark here" (new action-menu row). Opens the
|
|
||||||
keyboard for the label, saves the current fix. Works whether or not tracking
|
|
||||||
is active — a waypoint is independent of trail recording state.
|
|
||||||
- **Manage / navigate**: Trail → Hold Enter → "Waypoints" → a PopupMenu list
|
|
||||||
of saved waypoints (label + distance). Selecting one:
|
|
||||||
- Enter → fullscreen nav (see below).
|
|
||||||
- Hold Enter → Rename / Delete (later: **Share over mesh**).
|
|
||||||
- Waypoints persist across trail Reset / reboot (separate `/waypoints` file),
|
|
||||||
unlike the RAM trail ring.
|
|
||||||
|
|
||||||
**Navigation view — two bearings, no compass needed**
|
|
||||||
|
|
||||||
The L1 has no magnetometer, so we can't show "you are facing X". Instead the
|
|
||||||
nav view shows two *absolute* bearings and lets the user do the comparison —
|
|
||||||
robust against GPS jitter, no relative-turn maths:
|
|
||||||
|
|
||||||
```
|
|
||||||
CAMP ← waypoint label
|
|
||||||
1.4 km ← distance to target
|
|
||||||
To: 145° SE ← absolute bearing target-from-me (haversine/bearingDeg)
|
|
||||||
Hdg: 090° E ← my course over ground, derived from recent GPS movement
|
|
||||||
```
|
|
||||||
|
|
||||||
Reading it: target is at 145°, I'm travelling at 90° → I need to bear right.
|
|
||||||
When standing still (course undefined) the Hdg line shows `--` instead of a
|
|
||||||
stale value.
|
|
||||||
|
|
||||||
**Course over ground (COG)** — a small `TrailStore` helper:
|
|
||||||
`bool currentCourse(int& deg)` walks back from the newest fix until the
|
|
||||||
cumulative distance from it exceeds a threshold (~10–15 m so GPS noise
|
|
||||||
doesn't dominate) and returns the bearing from that older fix to the newest.
|
|
||||||
Returns false (→ `--`) when there isn't enough recent movement. Refreshed ~1 s.
|
|
||||||
`bearingDeg` / `bearingCardinal` are currently private statics in NearbyScreen;
|
|
||||||
lift them into a shared header (or TrailStore) so both screens use them.
|
|
||||||
|
|
||||||
This COG value can later back a standalone "heading" trail view if wanted, but
|
|
||||||
the two-bearing nav view already covers the practical need.
|
|
||||||
|
|
||||||
**Shared nav view — also navigate to a node** ⭐
|
|
||||||
|
|
||||||
The nav view's target is just `(lat, lon, label)` — it doesn't care whether
|
|
||||||
that came from a waypoint, the trail start (Backtrack), or a node's
|
|
||||||
last-known advert position. Make it a small reusable component
|
|
||||||
(`NavView` / `drawNavTo(display, target_lat, target_lon, label)`) and wire it
|
|
||||||
in from **Nearby Nodes** too: node detail → "Navigate" → same To/Hdg/distance
|
|
||||||
screen, retargeting the selected node. This folds the backlog "Compass to
|
|
||||||
contact" idea into one screen and means Nearby stops being a static snapshot —
|
|
||||||
you can actually walk toward a person.
|
|
||||||
|
|
||||||
Node target is the contact's `gps_lat/gps_lon` from its last advert (already
|
|
||||||
read in NearbyScreen). It's a *last-known* fix, not live, so the label could
|
|
||||||
show the advert age (e.g. `Alice (5m)`); pair it with Auto-Advert on the other
|
|
||||||
device for a moving target.
|
|
||||||
|
|
||||||
**COG source must be decoupled from trail recording.** Deriving the heading
|
|
||||||
from `TrailStore` only works while a trail is actively being recorded — but
|
|
||||||
node/waypoint navigation shouldn't require the user to start trail logging.
|
|
||||||
Fix: a tiny independent COG ring in UITask (≈4 fixes), maintained on every GPS
|
|
||||||
poll regardless of trail state. The trail ring stays a separate concern, so
|
|
||||||
the Hdg line is available everywhere (waypoints, nodes, backtrack).
|
|
||||||
|
|
||||||
The COG ring is **time-sampled with guards**, not raw displacement-gated:
|
|
||||||
push every GPS fix at the normal poll cadence (~1 s) into a short rolling
|
|
||||||
window (a few seconds of fixes), and compute the heading as the bearing
|
|
||||||
across the window (oldest→newest), optionally low-pass smoothed. Time-based
|
|
||||||
sampling gives a steadier, averaged course than bearing between just two
|
|
||||||
points, and tracks slow movement without lagging.
|
|
||||||
|
|
||||||
Two guards keep it honest:
|
|
||||||
- **Gross-error rejection** — drop a fix before it enters the window if it
|
|
||||||
implies an impossible jump (implied speed over a sane cap, e.g. > ~50 m/s
|
|
||||||
between consecutive fixes), or if the provider exposes a usable
|
|
||||||
validity/HDOP signal. One bad fix shouldn't swing the heading.
|
|
||||||
- **Minimum displacement over the window** — only emit a heading once the
|
|
||||||
total movement across the window exceeds a small threshold (a few m). Below
|
|
||||||
that the user is effectively stationary: hold the last good heading, and
|
|
||||||
show `--` until the window has cleared the threshold at least once. This
|
|
||||||
is what stops a standing user from getting a spinning bearing.
|
|
||||||
|
|
||||||
Threshold(s) can be fixed constants to start; expose in Settings later if it
|
|
||||||
proves worth tuning.
|
|
||||||
|
|
||||||
Open question: whether the nav view should also be reachable as a 4th Map
|
|
||||||
overlay state (cycle a "highlighted" waypoint with LEFT/RIGHT on the Map) or
|
|
||||||
stay list-driven only. Start list-driven; add map cycling later if wanted.
|
|
||||||
|
|
||||||
### ✅ Backtrack — navigate home along the recorded trail
|
|
||||||
|
|
||||||
Shipped: the Waypoints list always begins with a synthetic **Trail start** row
|
|
||||||
whenever a trail exists, opening the shared nav view to the first recorded
|
|
||||||
point. No new storage. (Navigates to the start point, not progressive
|
|
||||||
nearest-point following — adequate for "get me back".)
|
|
||||||
|
|
||||||
### ✅ Compass to contact — folded into the Waypoints nav view
|
|
||||||
|
|
||||||
Superseded by the shared nav view described under **Waypoints** above:
|
|
||||||
navigate to a node = open that nav view with the contact's last-advert
|
|
||||||
`gps_lat/gps_lon` as the target. No separate compass screen needed; the
|
|
||||||
"two absolute bearings (To / Hdg)" approach replaces the relative-heading
|
|
||||||
arrow this entry originally assumed. Kept here only as a cross-reference.
|
|
||||||
|
|
||||||
### 📋 Heading-up (track-up) map orientation
|
|
||||||
|
|
||||||
Today the trail Map is north-up. Optionally orient it to the current course
|
|
||||||
(COG, same source as the compass tape) so the travel direction is up — a Trail
|
|
||||||
action-menu toggle **Orientation: North-up / Heading-up**.
|
|
||||||
|
|
||||||
Lightweight approach: rotate the already-fitted map around its centre by
|
|
||||||
`-cog` (rotate each projected point before drawing). Point-glyph markers rotate
|
|
||||||
cleanly; label text stays upright; the north arrow then points to actual north
|
|
||||||
instead of straight up.
|
|
||||||
|
|
||||||
Caveats that make it more than a one-line toggle:
|
|
||||||
- **COG-only heading** (no magnetometer) — undefined while stationary, so hold
|
|
||||||
the last good course or fall back to north-up; it can't be heading-up when
|
|
||||||
you're standing still.
|
|
||||||
- **Jitter** — rotating the whole map by raw COG makes it shake; needs heading
|
|
||||||
smoothing / hysteresis (only re-rotate past ~10–15° of change).
|
|
||||||
- **Fit** — rotated content overflows the rectangle. Refitting to the rotated
|
|
||||||
bbox makes the scale "breathe" as you turn; alternative is to accept minor
|
|
||||||
edge clipping.
|
|
||||||
- **Grid** — the axis-aligned scale grid becomes diagonal; drop it in
|
|
||||||
heading-up mode or rewrite it.
|
|
||||||
- **e-ink** — a rotating map ghosts badly and refreshes slowly; this is really
|
|
||||||
an OLED feature, flag it as degraded on e-ink.
|
|
||||||
|
|
||||||
A fuller position-centred navigator (you fixed at screen centre, fixed/preset
|
|
||||||
zoom, pan) is a larger separate feature; start with the rotate-the-fit version
|
|
||||||
if pursued.
|
|
||||||
|
|
||||||
### 🚧 Battery / power optimization (duty-cycle RX + APC)
|
|
||||||
|
|
||||||
**On branch `feat/power-saving`** — two independent toggles under Settings ›
|
|
||||||
Radio, both **default OFF**. Under field testing; not yet merged.
|
|
||||||
|
|
||||||
**⛔ Disabled (2026-09-22) — hardware duty-cycle RX ("Pwr save")**
|
|
||||||
- Uses the SX126x's own **RX duty-cycle** (`SetRxDutyCycle`, datasheet 13.1.7) via
|
|
||||||
RadioLib `startReceiveDutyCycleAuto(preamble, 8)`: the chip's sequencer cycles
|
|
||||||
RX↔sleep, latches a preamble and stays in RX to receive the packet (RX_DONE on
|
|
||||||
DIO1). No MCU state machine — `recvRaw()` reads the packet exactly as in
|
|
||||||
continuous RX. `armRecv()` arms duty-cycle when power-save is on, else a normal
|
|
||||||
`startReceive()`; `loop()` re-arms only on a toggle. Falls back to continuous RX
|
|
||||||
if the modem doesn't support duty-cycle (non-SX126x). `state` stays `STATE_RX`
|
|
||||||
so the dispatcher's not-in-RX watchdog never trips. Duty-cycle engages when the
|
|
||||||
*assumed sender* preamble ≥ 2·8+1 symbols; using our own outgoing preamble
|
|
||||||
(`preambleLengthForSF(sf)`: 32 at SF≤8, 16 at SF9-12) as that assumption meant
|
|
||||||
duty-cycle only ever actually engaged at SF≤8.
|
|
||||||
- **Field report + investigation:** a user on the stock "EU/UK (Narrow)" preset
|
|
||||||
(SF8) saw reception drop from ~1-5 msg/min to ~1/3h with Pwr save on, unaffected
|
|
||||||
by a better antenna. Root cause, confirmed against the SX1262 datasheet and
|
|
||||||
RadioLib's own maintainers ([jgromes/RadioLib#1597](https://github.com/jgromes/RadioLib/issues/1597),
|
|
||||||
closed as inherent chip behaviour, not a library bug): the SX126x's duty-cycle
|
|
||||||
preamble-detection state machine restarts every sleep/wake cycle and needs the
|
|
||||||
*actual transmitted* preamble to closely match what we've configured our
|
|
||||||
receiver to expect — tolerance in that issue's own testing was only 1-2 symbols
|
|
||||||
either way, well short of covering e.g. a repeater still on pre-v1.16 firmware
|
|
||||||
(16 symbols vs. our 32). A mismatch isn't a gradual sensitivity hit, it
|
|
||||||
deterministically drops every packet from that sender no matter the signal
|
|
||||||
strength — and a lone node mostly hears repeater rebroadcasts, exactly the
|
|
||||||
nodes least likely to be freshly updated. There is no software workaround:
|
|
||||||
RadioLib's own parameters only trade which senders you're blind to (shortening
|
|
||||||
`minSymbols` to tolerate a shorter assumed preamble directly shortens the
|
|
||||||
wake-window's correlator dwell time, trading the preamble-mismatch failure mode
|
|
||||||
for a marginal-signal one instead). A network-wide capability negotiation (e.g.
|
|
||||||
via the still-unused `ADV_FEAT1_MASK`/`ADV_FEAT2_MASK` fields already reserved
|
|
||||||
in `AdvertDataHelpers.h`) could plausibly gate this safely, but is a real
|
|
||||||
feature, not a quick fix — rejected for now as out of scope. Checked whether
|
|
||||||
IoTThinks' MeshCore fork (github.com/IoTThinks/MeshCore) had solved this: it
|
|
||||||
hasn't, and doesn't hit the problem at all, because its "power saving" never
|
|
||||||
touches the radio — `ESP32Board::sleep()`/NRF52 `board.sleep(0)` only light-sleep
|
|
||||||
the **MCU**, waking on the radio's own DIO1 GPIO interrupt while the radio itself
|
|
||||||
stays in plain continuous RX the whole time. That's the same MCU-idle mechanism
|
|
||||||
already noted below (native NRF52 companion power-saving from the v1.16
|
|
||||||
upstream merge) — safe, but doesn't touch the dominant power draw (the radio in
|
|
||||||
continuous RX), unlike a real duty-cycle.
|
|
||||||
- **Resolution:** `examples/companion_radio/Features.h` now defines
|
|
||||||
`FEAT_RX_POWERSAVE 0`, gating out the Settings row (`SettingsScreen.h`), the
|
|
||||||
Diagnostics RXPS watchdog row (`DiagnosticsScreen.h`), and every call site that
|
|
||||||
would apply `_prefs.rx_powersave` to the radio or to CAD auto-enable
|
|
||||||
(`MyMesh.cpp`, `UITask::applyPowerSave()`) — including forcing
|
|
||||||
`setPowerSaving(false)` unconditionally so a *stale* `rx_powersave=1` byte left
|
|
||||||
over in an existing prefs file from before this change can't do anything either.
|
|
||||||
The real duty-cycle implementation itself
|
|
||||||
(`RadioLibWrapper`/`CustomSX1262Wrapper::startPowerSaveRecv()`) is left in place,
|
|
||||||
unneutered, matching our own preamble convention — it's simply unreachable now.
|
|
||||||
Flipping `FEAT_RX_POWERSAVE` back on requires solving the network-compatibility
|
|
||||||
problem above first, not just re-adding the toggle.
|
|
||||||
- Companion: `rx_powersave` pref (schema `0xC0DE0009`) still exists in
|
|
||||||
`NodePrefs`/`DataStore` purely for file-format stability; nothing reads it
|
|
||||||
anywhere behavior-relevant while `FEAT_RX_POWERSAVE` is 0.
|
|
||||||
|
|
||||||
> **History:** an earlier attempt used a *software* CAD state machine (scan →
|
|
||||||
> warm-sleep window → on-detect full RX, with `standbyXOSC`/burst windows). It
|
|
||||||
> fought the hardware — querying a warm-sleeping chip from `checkSend()` gave a
|
|
||||||
> phantom-busy channel that stalled TX for ~4 s, and ACKs dropped in the scan
|
|
||||||
> gaps. Replaced wholesale by the hardware duty-cycle above, which turned out to
|
|
||||||
> have its own, deeper problem (see above).
|
|
||||||
|
|
||||||
**✅ Done — Adaptive Power Control ("Auto pwr")**
|
|
||||||
- `tx_power_dbm` becomes a *ceiling*; APC drives the radio's actual power within
|
|
||||||
`[APC_MIN_DBM −9, ceiling]` to hold the link margin near a target. Lives in
|
|
||||||
`MyMesh` (`applyApc` + `apcSampleSnr`/`apcOnFailure` controller), `tx_apc` pref.
|
|
||||||
- **Two feedback sources**, both the *reverse* link (no protocol change):
|
|
||||||
- **Direct messages** — ACK SNR (`onAckRecv`); missed ACK (`onSendTimeout`) =
|
|
||||||
lost confirmation.
|
|
||||||
- **Channel/flood messages** — no ACK exists, so we hash each originated flood
|
|
||||||
(`apcTrackFloodSend` in `sendFloodScoped`, hash excludes the path) and listen
|
|
||||||
in `filterRecvFloodPacket` for a repeater rebroadcasting it; the heard echo's
|
|
||||||
SNR is a sample, and **no echo within ~6 s** counts as a lost confirmation.
|
|
||||||
This is what lets a channel send recover after APC trimmed power below what the
|
|
||||||
repeaters can hear (previously it could strand channel TX at the floor).
|
|
||||||
- Margin is measured **above the per-SF demod floor** (`−7.5 − 2.5·(SF−7)` dB) so
|
|
||||||
one target works across SF7–12. Each SNR sample is smoothed with an EWMA (α=0.4);
|
|
||||||
power steps **proportionally** to the error (capped ±2 dB) with a ±2 dB deadband
|
|
||||||
to avoid hunting; the EWMA is nudged by each step so it doesn't re-trigger on a
|
|
||||||
stale sample.
|
|
||||||
- Lost confirmation → step up `+4 dB`; **2 consecutive** losses → jump to the
|
|
||||||
ceiling (ramp gradually first since a loss is an ambiguous power signal). Any
|
|
||||||
confirmation clears the streak. Live power shown on the radio page + name bar.
|
|
||||||
|
|
||||||
**⏸ To return to (not done)**
|
|
||||||
- **Current measurement** — never taken (no PPK2/meter to hand). Reliability is
|
|
||||||
confirmed in use; the actual mA win is still unquantified. Do this first.
|
|
||||||
- **APC sample targeting** — a direct ACK's SNR is the last hop of the *return*
|
|
||||||
path (sound on symmetric/direct links); a flood echo is the *first* hop from us
|
|
||||||
to a repeater, which is exactly what our TX power controls. Both feed one shared
|
|
||||||
controller; per-source weighting or direct-only (0-hop) gating could be explored.
|
|
||||||
|
|
||||||
**Original analysis (kept for context):**
|
|
||||||
|
|
||||||
Goal: multiply battery life **without losing functionality**. The dominant
|
|
||||||
draw on this node is the radio in **continuous RX** (always-on `startReceive()`
|
|
||||||
with boosted gain in [`RadioLibWrappers.cpp`](src/helpers/radiolib/RadioLibWrappers.cpp));
|
|
||||||
the MCU already sleeps between iterations (`sd_app_evt_wait()`/WFE in
|
|
||||||
[`NRF52Board.cpp`](src/helpers/NRF52Board.cpp)), so the framework is not the
|
|
||||||
bottleneck — the radio is. The win is framework-agnostic and can land in this
|
|
||||||
Arduino tree while keeping the Solo UI and upstream sync.
|
|
||||||
|
|
||||||
Inspired by **ZephCore** (Zephyr MeshCore port, https://github.com/liquidraver/ZephCore),
|
|
||||||
whose battery edge comes from radio/peripheral power technique, not the kernel:
|
|
||||||
|
|
||||||
- **CAD-based RX windowing** — the biggest lever. Instead of continuous RX, use
|
|
||||||
the SX1262 RX-duty-cycle / Channel Activity Detection to briefly sample for a
|
|
||||||
preamble, then sleep, repeat. ZephCore quotes ~10–15 mA → ~3–5 mA RX. RadioLib
|
|
||||||
supports CAD / `startReceiveDutyCycle`. **Tradeoff:** slightly higher receive
|
|
||||||
latency / a small sensitivity hit — acceptable for a companion, must be a
|
|
||||||
toggle/setting so users who want lowest latency can keep continuous RX.
|
|
||||||
- **Adaptive Power Control (APC)** — drop `tx_power_dbm` dynamically when link
|
|
||||||
quality (SNR/RSSI of acks) allows; raise it back when needed.
|
|
||||||
- **Config-level wins** (mostly already exposed): BLE off when idle, OLED
|
|
||||||
auto-off (e-ink idle ≈ 0), longer advert intervals.
|
|
||||||
|
|
||||||
**Explicitly out of scope: GPS power gating.** ZephCore powers the GNSS only
|
|
||||||
during a fix; this device is used as a *live* navigator + trail recorder, so GPS
|
|
||||||
stays continuously powered. Don't gate it.
|
|
||||||
|
|
||||||
Cross-check: as of the **v1.16 upstream merge**, MeshCore now ships native
|
|
||||||
NRF52 companion power-saving ([PR #1238](https://github.com/meshcore-dev/MeshCore/pull/1238),
|
|
||||||
[docs/nrf52_power_management.md](docs/nrf52_power_management.md)). The companion
|
|
||||||
loop now sleeps via `board.sleep(0)` whenever `hasPendingWork()` is false — but
|
|
||||||
that only covers **MCU idle** (it does not touch the radio, which is the real
|
|
||||||
draw), and there is no on/off toggle because not-sleeping would only waste power.
|
|
||||||
The same merge also brought the **preamble 16→32 bump for SF<9**, which is what
|
|
||||||
makes the hardware RX duty-cycle viable at SF8 (it needs ≥ 2·8+1 = 17 preamble
|
|
||||||
symbols to latch). Prefer adopting/extending upstream work over a parallel
|
|
||||||
implementation. Note ZephCore's licence before copying any code verbatim
|
|
||||||
(architecture inspiration is fine).
|
|
||||||
|
|
||||||
Sequencing: hardware duty-cycle RX and APC are both done and in field test on
|
|
||||||
`feat/power-saving`; see the status block at the top of this entry for what's
|
|
||||||
left (PPK2 current measurement, multi-hop APC gating).
|
|
||||||
|
|
||||||
### ✅ Companion repeater + forwarding filters + diagnostics
|
|
||||||
|
|
||||||
On-device repeater for the Solo companion, scoped to the SX1262 boards (Wio
|
|
||||||
Tracker L1 OLED/e-ink, GAT562 30S). All on `feature/companion-repeater-presets`.
|
|
||||||
|
|
||||||
- **Repeater toggle** (`client_repeat`, Tools › Repeater) — the companion relays
|
|
||||||
flood/direct traffic, still working as a normal companion. By default it
|
|
||||||
switches to a dedicated band on enable (see profile note below) rather than
|
|
||||||
relaying on whatever network it's chatting on. `MyMesh::allowPacketForward`
|
|
||||||
gates it; loop
|
|
||||||
detection (`isRepeatLooped`, ported from `simple_repeater`) and an advert
|
|
||||||
flood-depth cap are always applied. Packet pool bumped 16→32 to match the
|
|
||||||
repeater workload (a too-small pool starved channel/DM reception once relaying
|
|
||||||
queued retransmits).
|
|
||||||
- **Consolidated on Tools › Repeater** — the toggle and the five filters share
|
|
||||||
one full-width screen, rather than the original Settings › Radio sub-items
|
|
||||||
(whose indented labels collided with the value column on a 128px OLED). Live
|
|
||||||
forwarding stats (Forwarded / Pool free / Queue) stay on Tools › Diagnostics.
|
|
||||||
- **Optional dedicated radio profile** (`Network: Current/Custom`,
|
|
||||||
`repeater_use_profile` + `repeater_freq/bw/sf/cr`). Custom switches the radio to
|
|
||||||
a preset/manual profile when the repeater is enabled and restores the
|
|
||||||
companion's params when disabled (user-chosen revert-on-disable); a profile
|
|
||||||
equal to the companion = "same network", a different one = drop onto a separate
|
|
||||||
repeater network. `MyMesh::applyRepeaterRadio()` is the single decision point,
|
|
||||||
called at boot and on every toggle/edit; `repeaterProfileValid()` gates it.
|
|
||||||
Schema sentinel `0xC0DE0010`. **Custom is the default**: a never-configured
|
|
||||||
device (or one upgrading from a pre-`0x10` file, which has no saved profile)
|
|
||||||
turns the profile on and seeds `repeater_sf/bw/cr` from `LORA_SF/BW/CR` plus
|
|
||||||
a band-matched `repeater_freq` — relaying on the same network the operator is
|
|
||||||
chatting on isn't the MeshCore community norm, so "Current" stays opt-in.
|
|
||||||
`defaultRepeaterFreqForBand()` (`NodePrefs.h`) buckets the companion's own
|
|
||||||
`freq` into whichever of the three license-exempt bands MeshCore's app-driven
|
|
||||||
repeat toggle historically restricted to (433.000 / 869.495 / 918.000 MHz —
|
|
||||||
see `repeat_freq_ranges` in `MyMesh.cpp`), so the seeded default can't land
|
|
||||||
outside what's legal for wherever the companion's own network already is.
|
|
||||||
`LORA_FREQ/BW/SF/CR` fallback `#define`s moved from `MyMesh.h` to
|
|
||||||
`NodePrefs.h` so `DataStore.cpp`'s migration code can see them too.
|
|
||||||
- **Forwarding filters** (all opt-in, default off, flood-only — a direct route's
|
|
||||||
named next hop is never dropped). Shown only while the repeater is on:
|
|
||||||
- **Skip advert** — don't re-flood adverts (highest-volume flood).
|
|
||||||
- **Max hops** — drop a flood past N hops.
|
|
||||||
- **Yield** — scale the retransmit delay for *forwarded* floods only
|
|
||||||
(`getRetransmitDelay`); own sends pass their own delay to `sendFlood`, so the
|
|
||||||
companion's own traffic is never slowed. Widens the overhear window.
|
|
||||||
- **Min SNR** — drop a flood copy below a dB threshold (`-128` sentinel = off,
|
|
||||||
so an upgraded prefs file can't read as "filter at 0 dB").
|
|
||||||
- **Suppress dup** — overhear cancel: a received flood whose hash matches a
|
|
||||||
queued retransmit cancels our copy (`Dispatcher::suppressQueuedDuplicate`,
|
|
||||||
`wantsOverhearSuppress` hook). MeshCore had no overhear-cancel before — the
|
|
||||||
unused `removeOutboundByIdx`/`getOutboundByIdx` finally have a caller. Packet
|
|
||||||
hash ignores the path for non-TRACE, so our copy and the peer's relayed copy
|
|
||||||
hash equal. Pairs with Yield (longer delay → wider window to hear a peer).
|
|
||||||
- **Diagnostics screen** (Tools › Diagnostics) — single read-only page: per-type
|
|
||||||
RX/TX counters (generic `Dispatcher::n_recv_by_type`/`n_sent_by_type`), uptime,
|
|
||||||
heap + stack (new `DeviceDiag` helper, nRF52 linker-symbol/`sbrk` heap +
|
|
||||||
FreeRTOS stack high-water), noise floor, RSSI/SNR, pool free, outbound queue,
|
|
||||||
**Forwarded** (`Mesh::n_forwarded` — actual retransmits; backed out on overhear
|
|
||||||
cancel so it reflects what hits the air), and **Errors** (Dispatcher
|
|
||||||
`ERR_EVENT_*` flags decoded to F/C/R). Hold Enter opens a one-item "Reset
|
|
||||||
counters" menu (Back dismisses) — `resetStats` made virtual; `Mesh` override
|
|
||||||
also clears `n_forwarded`.
|
|
||||||
- **Radio settings locked while relaying** — a repeater must hear all traffic and
|
|
||||||
relay at consistent power, so duty-cycle RX ("Pwr save") and APC ("Auto pwr")
|
|
||||||
are forced off whenever `client_repeat` is on (effective `pref && !client_repeat`
|
|
||||||
via `applyPowerSave()` / `apcActive()`), applied at boot, on the on-device
|
|
||||||
toggle, and on the app's `CMD_SET_RADIO_PARAMS`; Settings shows `--` and blocks
|
|
||||||
the toggle, preserving the user's pref for when the repeater goes off. A
|
|
||||||
blinking `»` status-bar indicator (`ICON_REPEATER`) shows relaying at a glance.
|
|
||||||
- Prefs persisted behind schema sentinels `0xC0DE000E` (four knobs),
|
|
||||||
`0xC0DE000F` (suppress-dup), `0xC0DE0010` (radio profile), with stray-byte
|
|
||||||
clamps for upgraders.
|
|
||||||
- **Open question:** the app-side dedicated-band gate in `CMD_SET_RADIO_PARAMS`
|
|
||||||
was commented out (not deleted) to match the on-device toggle's any-frequency
|
|
||||||
behaviour — undecided whether that gate was UX-only or regulatory.
|
|
||||||
- **Not done:** live two-device mesh verification (A→repeater→C); counters make
|
|
||||||
it observable but don't replace the field test.
|
|
||||||
|
|
||||||
### SOS broadcast
|
|
||||||
|
|
||||||
Configurable in Settings › System › SOS:
|
|
||||||
- Target: channel index or DM contact
|
|
||||||
- Message template (uses placeholders)
|
|
||||||
|
|
||||||
Trigger: Hold Back + Hold Enter for 3 s on any screen → confirmation popup ("Send SOS?") → Enter to send. Sends with `{loc}` and `{batt}` filled. 30 s cooldown.
|
|
||||||
|
|
||||||
### ✅ Range test — shipped as Nearby Nodes ping
|
|
||||||
|
|
||||||
The practical need is covered by **ping** rather than a dedicated screen: in
|
|
||||||
Nearby Nodes, a node's detail view → **Hold Enter → Ping** sends a direct mesh
|
|
||||||
ping and shows RTT + SNR (own and remote), repeatable on demand. Available from
|
|
||||||
both the stored-node detail and the active-discovery detail.
|
|
||||||
|
|
||||||
The original idea below (a Tools › Range Test screen with continuous 5 s
|
|
||||||
pinging and a 30-sample sparkline) was **not** built — kept as a possible
|
|
||||||
future enhancement on top of the existing ping.
|
|
||||||
|
|
||||||
Tools › Range Test:
|
|
||||||
- Pick a node from contacts/nearby
|
|
||||||
- Enter starts pinging every 5 s, logs RTT + RSSI + SNR (ring ~30)
|
|
||||||
- Display shows current values + 30-sample sparkline (block characters)
|
|
||||||
- Enter stops; Hold Enter for context menu (reset, change target)
|
|
||||||
|
|
||||||
### Quiet hours
|
|
||||||
|
|
||||||
Settings › Sound › Quiet Hours:
|
|
||||||
- Enable on/off
|
|
||||||
- Start HH (LEFT/RIGHT to change, 24 h)
|
|
||||||
- End HH
|
|
||||||
|
|
||||||
When within window: buzzer set to "off" (overrides setting), display brightness → 0. Restores prefs values when window ends. Time source: rtc_clock.
|
|
||||||
|
|
||||||
### Channel scanner home page
|
|
||||||
|
|
||||||
Toggleable in Settings › Home Pages. Lists channels with: name, unread count, last message age. Enter opens the channel. Sort by recency by default; LEFT/RIGHT toggles to alphabetical.
|
|
||||||
|
|
||||||
### Contact distance sort
|
|
||||||
|
|
||||||
QuickMsg DM list: a 4-th sort mode (currently sorted by message count). LEFT/RIGHT on the list header cycles: name | message-count | recency | distance. Distance uses GPS pos from contact's last advert.
|
|
||||||
|
|
||||||
### Signal stats screen
|
|
||||||
|
|
||||||
Tools › Stats:
|
|
||||||
- Battery voltage 60-min sparkline
|
|
||||||
- RSSI of last 30 received packets
|
|
||||||
- Noise floor current
|
|
||||||
- Free heap (if available)
|
|
||||||
|
|
||||||
Read-only. UP/DOWN switches between metrics. Bottom shows current value as text.
|
|
||||||
|
|
||||||
### ✅ Auto-reply query commands with live data
|
|
||||||
|
|
||||||
Realised as a **command bot** rather than a trigger/reply table: with **Commands** ON, a DM is scanned for `!word` tokens and answered with live node data via `expandMsg` — `!batt`/`!loc`/`!time`/`!temp`/`!status`/`!ping`/`!help`, plus `!hops` (per-message hop count via `getPathHashCount()`, `direct` if heard directly). Multiple commands in one message are merged into a single ` | `-joined reply (one transmission/throttle/counter tick) via the shared `botScanCommands`. Works in DMs (per-contact throttle, ignores quiet hours — a pull) and on the bot's **monitored channel** (broadcast: per-channel cooldown, respects quiet hours). Toggled independently of the trigger bot. See [`MyMeshBot.h`](examples/companion_radio/MyMeshBot.h) `tryBotCommand` / `tryBotChannelCommand` / `botCommandReply`.
|
|
||||||
|
|
||||||
### ✅ Lock-screen unread count
|
|
||||||
|
|
||||||
Shipped: the lock screen shows a total unread badge (`<n> unread`, summing DM +
|
|
||||||
channel + room counters) below the time. Reuses the existing unread counters;
|
|
||||||
no schema change.
|
|
||||||
|
|
||||||
### Power profile presets
|
|
||||||
|
|
||||||
Settings › Profile: Indoor / Outdoor / Expedition. Each pre-fills:
|
|
||||||
- Auto-off seconds
|
|
||||||
- GPS interval
|
|
||||||
- Auto-advert interval
|
|
||||||
- Brightness
|
|
||||||
|
|
||||||
Single Enter applies. Stored as `uint8_t profile_idx` with hardcoded value tables.
|
|
||||||
|
|
||||||
### Battery curve calibration
|
|
||||||
|
|
||||||
Settings › System › Batt Calibration: edit 5 voltage breakpoints used to convert mV → %. UP/DOWN selects breakpoint, LEFT/RIGHT changes voltage in 50 mV steps. Helps users with non-standard LiPos report accurate %.
|
|
||||||
|
|
||||||
### ✅ Mark-read at type level — done (see "Mark-all-read at type level" above)
|
|
||||||
|
|
||||||
### Display test pattern
|
|
||||||
|
|
||||||
Tools › Display Test: full-screen grid + bars + Lemon glyph dump. Useful for verifying driver/font changes after flashing.
|
|
||||||
|
|
||||||
### Group "who's online" ping
|
|
||||||
|
|
||||||
From channel view, Hold Enter → "Who's online?". Sends 0-hop discovery to channel members, collects responses for 10 s, shows a list with RSSI. Similar to existing Nearby active discovery but scoped to a channel.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## New ideas (unprioritised)
|
|
||||||
|
|
||||||
Captured for later triage. None designed in detail yet (DM delivery status has since shipped — see below).
|
|
||||||
|
|
||||||
### ✅ DM delivery status
|
|
||||||
|
|
||||||
**✅ Shipped** (in `main`). A per-message marker sits at the end of each outgoing
|
|
||||||
DM row in the history and in the fullscreen message view. Deltas from the spec
|
|
||||||
that follows:
|
|
||||||
- **Pending** isn't a single `·/…` — it draws **one square dot per send attempt**,
|
|
||||||
so an auto-resent message shows its retry count at a glance.
|
|
||||||
- **Delivered / failed** use drawn **scalable mini-icons** (`✓` / `✗`) that scale
|
|
||||||
with the font, not glyph-font characters — legible on every layout (see
|
|
||||||
`icons.h`, authored as compile-time ASCII-art).
|
|
||||||
- Adds **DM auto-resend + incoming dedup**, plus a channel **"relayed into mesh"**
|
|
||||||
marker (`✓` when a repeater echo confirms the channel message went out).
|
|
||||||
|
|
||||||
Original spec:
|
|
||||||
|
|
||||||
Show whether an outgoing direct message reached the recipient, using the ACK
|
|
||||||
that MeshCore already produces (no protocol change). `sendMessage()` returns
|
|
||||||
`expected_ack` + `est_timeout`; the ACK arrives via `onAckRecv` / `isAckPending`
|
|
||||||
— the same mechanism APC and ping already consume.
|
|
||||||
|
|
||||||
Per-message status glyph at the end of each outgoing DM row in the history:
|
|
||||||
- `·` / `…` — sent, awaiting ACK (pending)
|
|
||||||
- `✓` — delivered to the recipient (ACK matched)
|
|
||||||
- `✗` / `!` — timed out, no confirmation
|
|
||||||
|
|
||||||
Data model (RAM only, no schema bump — DM history already lives in RAM):
|
|
||||||
- `DmHistEntry` gains `uint8_t ack_status` (0=incoming/none, 1=pending,
|
|
||||||
2=delivered, 3=failed) and `uint32_t ack_tag` (the `expected_ack` CRC).
|
|
||||||
|
|
||||||
Wiring:
|
|
||||||
- On send: store `ack_tag = expected_ack`, `ack_status = pending`, record the
|
|
||||||
send time + `est_timeout`.
|
|
||||||
- On ACK: route `onAckRecv(ack_crc)` to the UI; find the entry whose `ack_tag`
|
|
||||||
matches and set it delivered (single shared callback, like `onPingResult`).
|
|
||||||
- Timeout: in the UI loop, a pending entry older than `est_timeout` → failed.
|
|
||||||
|
|
||||||
Edge cases:
|
|
||||||
- Sends with no path / `expected_ack == 0` (and channel messages, which have no
|
|
||||||
ACK) can't be confirmed → show plain "sent" (`→`) and no delivery state.
|
|
||||||
- Glyph rendering: prefer a Lemon-font check; on the plain ASCII font fall back
|
|
||||||
to drawn 1-px marks or letters so it reads on the OLED.
|
|
||||||
|
|
||||||
### 📋 Periodic location beacon ("live share")
|
|
||||||
|
|
||||||
Auto-send `{loc}` every N minutes to a chosen channel or DM contact — group
|
|
||||||
trip tracking. Builds on the existing auto-advert cadence pattern and `{loc}`
|
|
||||||
expansion. A status-bar indicator (like `A`/`G`) while active; off by default.
|
|
||||||
|
|
||||||
### 📋 Arrival / proximity alert
|
|
||||||
|
|
||||||
Buzzer + alert when within X m of the active nav target (waypoint / node /
|
|
||||||
backtrack). Closes the loop on the navigator — you no longer have to stare at
|
|
||||||
the distance readout. Radius configurable (e.g. 20/50/100 m).
|
|
||||||
|
|
||||||
### 📋 "Where am I" location screen
|
|
||||||
|
|
||||||
Tools entry showing current lat/lon (optionally MGRS/UTM grid ref), fix quality
|
|
||||||
(sats / HDOP if the provider exposes it), altitude, and a one-press share to a
|
|
||||||
channel/DM. Complements the nav suite with an at-a-glance position readout.
|
|
||||||
|
|
||||||
### 📋 On-screen QR — share own contact / channel
|
|
||||||
|
|
||||||
Render the device's own contact (or a channel) as a QR on the display so a
|
|
||||||
phone can import it without the companion app. The QR payload format already
|
|
||||||
exists (`docs/qr_codes.md`); this is the on-device render side.
|
|
||||||
|
|
||||||
### 📋 Sunrise / sunset + golden hour
|
|
||||||
|
|
||||||
Computed from GPS position + RTC date — pure math, no extra hardware. A Clock
|
|
||||||
dashboard field or a small Tools readout. Useful for planning outdoor activity.
|
|
||||||
|
|
||||||
### 📋 "Find my device" — remote buzzer
|
|
||||||
|
|
||||||
A received command (from a paired contact, or a dedicated channel keyword) makes
|
|
||||||
the node play a locator tone for a few seconds. Helps find a dropped/misplaced
|
|
||||||
device. Gate behind a setting to avoid abuse.
|
|
||||||
|
|
||||||
### 📋 Trail auto-pause
|
|
||||||
|
|
||||||
Stop counting elapsed/avg-speed (and optionally skip sampling) when stationary,
|
|
||||||
so "moving time" and average speed reflect actual travel. Reuses the COG ring's
|
|
||||||
min-displacement gate to detect standing still.
|
|
||||||
|
|
||||||
### 📋 Night mode
|
|
||||||
|
|
||||||
Quick toggle for an inverted / minimum-brightness scheme for night use, separate
|
|
||||||
from the brightness levels. On e-ink flag as degraded (inversion ghosts).
|
|
||||||
|
|
||||||
### 📋 Stopwatch / timer
|
|
||||||
|
|
||||||
Simple Tools utility — count-up stopwatch and a count-down timer with a buzzer
|
|
||||||
at zero. Joystick: Enter start/stop, Hold Enter reset.
|
|
||||||
|
|
||||||
### 📋 Trail elevation / ascent
|
|
||||||
|
|
||||||
If the GPS provider exposes altitude, add total ascent + current elevation to
|
|
||||||
the trail Summary and `<ele>` tags to the GPX export. Skip cleanly when no
|
|
||||||
altitude is available.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deferred
|
|
||||||
|
|
||||||
### ❌ Morse keyboard
|
|
||||||
|
|
||||||
Cute but niche. Skip unless explicitly requested.
|
|
||||||
|
|
||||||
### ❌ Buzzer EVENTS_STOPPED IRQ chaining
|
|
||||||
|
|
||||||
Marginal real-world gain (2 ms between notes during melody playback only), high risk on the PWM peripheral.
|
|
||||||
|
|
||||||
### ❌ Vibration feedback
|
|
||||||
|
|
||||||
Wio Tracker L1 doesn't have a haptic motor. N/A.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation order suggestion
|
|
||||||
|
|
||||||
1. **Mark-all-read** — smallest patch, biggest day-to-day comfort gain
|
|
||||||
2. **Favourites dial** — new home page + new prefs field; touches familiar areas (QuickMsg context menu, NodePrefs, schema sentinel bump)
|
|
||||||
3. **GPS breadcrumb** — largest of the three; introduces a new flash file and a Tools sub-screen with multiple views
|
|
||||||
|
|
||||||
After #3, re-prioritise the backlog with the user.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Code audit — known bugs / hardening backlog
|
|
||||||
|
|
||||||
Pass through wio-unified after commit `321d769e`. Grouped by severity. Listed but **not yet fixed**.
|
|
||||||
|
|
||||||
## Critical
|
|
||||||
|
|
||||||
### ✅ `onChannelMessageRecv` / `onChannelDataRecv` — guard for `findChannelIdx == -1`
|
|
||||||
|
|
||||||
[`MyMesh.cpp:561-602`](examples/companion_radio/MyMesh.cpp#L561-L602), [`MyMesh.cpp:619-625`](examples/companion_radio/MyMesh.cpp#L619-L625)
|
|
||||||
|
|
||||||
Both group-channel receive paths now check the result of `findChannelIdx()` before continuing:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int idx = findChannelIdx(channel);
|
|
||||||
if (idx < 0) {
|
|
||||||
MESH_DEBUG_PRINTLN("...: unknown channel secret — dropping message");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t channel_idx = (uint8_t)idx;
|
|
||||||
```
|
|
||||||
|
|
||||||
Unknown-secret packets no longer pollute the offline queue, UI history or trigger the bot with a bogus `idx=255`.
|
|
||||||
|
|
||||||
### ✅ `addChannelMsg` guards against bogus index
|
|
||||||
|
|
||||||
[`MessagesScreen.h:414-433`](examples/companion_radio/ui-new/MessagesScreen.h#L414-L433)
|
|
||||||
|
|
||||||
Defensive `if (ch_idx >= MAX_GROUP_CHANNELS) return;` at function entry — prevents ring-buffer pollution in case any future caller forgets the upstream guard. With C1 fixed this should never trigger, but the cost is zero.
|
|
||||||
|
|
||||||
## High
|
|
||||||
|
|
||||||
### ✅ `findChannelIdx` scans all-zero secret in uninitialised slots
|
|
||||||
|
|
||||||
[`BaseChatMesh.cpp:908`](src/helpers/BaseChatMesh.cpp#L908)
|
|
||||||
|
|
||||||
Fixed (local override): `findChannelIdx()` now returns `-1` immediately when the queried secret is all-zero, so a corrupted/empty channel can't match an unused all-zero slot. Complements the load-side skip already in `loadChannels()`.
|
|
||||||
|
|
||||||
### ✅ `saveChannels` writes all 40 slots to `/channels2`
|
|
||||||
|
|
||||||
[`DataStore.cpp:687`](examples/companion_radio/DataStore.cpp#L687)
|
|
||||||
|
|
||||||
Fixed: the save loop now skips unused slots (all-zero secret) instead of writing every slot up to `MAX_GROUP_CHANNELS`, so the file holds only the channels actually configured (was always ~2.7 KB). `loadChannels()` already compacted empty entries on read, so the loaded result is unchanged — only on-flash size and write wear drop.
|
|
||||||
|
|
||||||
### 📋 `msgRead(0)` wipes the whole DM unread table
|
|
||||||
|
|
||||||
[`UITask.cpp:1403-1410`](examples/companion_radio/ui-new/UITask.cpp#L1403-L1410)
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
if (msgcount == 0) {
|
|
||||||
memset(_dm_unread_table, 0, sizeof(_dm_unread_table));
|
|
||||||
((MessagesScreen*)messages_screen)->clearAllChannelUnread();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When the companion app reads the last message from the offline queue, all on-device badges disappear. Previously discussed and a fix was reverted as "intended sync behaviour" — keep as known limitation; document or restrict to "Favourites Dial badges only".
|
|
||||||
|
|
||||||
### ✅ Message buffers sized below the protocol maximum (clipped long messages)
|
|
||||||
|
|
||||||
[`MessagesScreen.h`](examples/companion_radio/ui-new/MessagesScreen.h), [`KeyboardWidget.h`](examples/companion_radio/ui-new/KeyboardWidget.h)
|
|
||||||
|
|
||||||
`ChHistEntry::text` was 140 B and `DmHistEntry::text` only 80 B, while the
|
|
||||||
keyboard capped input at 139 B — all below MeshCore's `MAX_TEXT_LEN` (160 B).
|
|
||||||
Channel messages embed the sender as `"Name: body"` in the payload, so the
|
|
||||||
prefix ate into the 140 and clipped the tail; DMs over ~80 B were cut outright;
|
|
||||||
and Polish text (2 bytes per accented char) roughly halved the visible limit.
|
|
||||||
Fixed: history + fullscreen/preview copies sized to `MAX_TEXT_LEN + 1`, keyboard
|
|
||||||
cap raised to 160 with per-field maxima kept on the smaller stores (custom_msgs,
|
|
||||||
bot reply). Full-length messages now compose, send, store and display intact.
|
|
||||||
|
|
||||||
### ✅ `loadPrefsInt` scopes `trail_units_idx` reset to the 0xC0DE0003 jump
|
|
||||||
|
|
||||||
[`DataStore.cpp:326-343`](examples/companion_radio/DataStore.cpp#L326-L343)
|
|
||||||
|
|
||||||
The reset is now gated on `sentinel == 0xC0DE0003` so newer mismatches (e.g. 0xC0DE0004 → 0xC0DE0005, which both saved the field correctly) no longer clobber the user's choice.
|
|
||||||
|
|
||||||
## Medium
|
|
||||||
|
|
||||||
### ❌ `CMD_SET_DEFAULT_FLOOD_SCOPE` off-by-one — not a bug
|
|
||||||
|
|
||||||
[`MyMesh.cpp:2143`](examples/companion_radio/MyMesh.cpp#L2143)
|
|
||||||
|
|
||||||
Re-checked: `default_scope_name` is declared `char[31]` (not 32), so `n < 31` correctly admits the maximum 30-character string + NUL. The audit entry was a misread.
|
|
||||||
|
|
||||||
### ✅ `strlen` on `cmd_frame` without null-termination — replaced with `strnlen`
|
|
||||||
|
|
||||||
[`MyMesh.cpp:2140-2147`](examples/companion_radio/MyMesh.cpp#L2140-L2147)
|
|
||||||
|
|
||||||
The 31-byte name slot in `CMD_SET_DEFAULT_FLOOD_SCOPE` doesn't have to be NUL-terminated by the sender. Switched to `strnlen(…, 31)` so the search can't run past the field into the 16-byte key (or beyond the frame).
|
|
||||||
|
|
||||||
### 📋 `PopupMenu._cap` updated only in `render()`
|
|
||||||
|
|
||||||
[`PopupMenu.h:37-44, 77-90`](examples/companion_radio/ui-new/PopupMenu.h#L37-L90)
|
|
||||||
|
|
||||||
Left as-is: the framework always renders before forwarding input, so the fragile invariant doesn't fire in practice. Worth a refactor only if the call order ever changes.
|
|
||||||
|
|
||||||
### ✅ Scan detail guarded against narrow displays
|
|
||||||
|
|
||||||
[`NearbyScreen.h:448-451`](examples/companion_radio/ui-new/NearbyScreen.h#L448-L451)
|
|
||||||
|
|
||||||
Pub-key line is skipped entirely when `max_chars < 4` instead of feeding a negative length to `strncpy`. (Lived in `renderDiscoverDetail` before the one-list refactor; now in `renderScanDetail`.)
|
|
||||||
|
|
||||||
### 📋 `expandMsg` GPS validity test treats (0, 0) as invalid
|
|
||||||
|
|
||||||
[`MyMeshBot.h:95, 132, 182`](examples/companion_radio/MyMeshBot.h#L95)
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
sensors.node_lat != 0.0 || sensors.node_lon != 0.0 // proxy for "valid GPS"
|
|
||||||
```
|
|
||||||
|
|
||||||
Point (0, 0) is a legitimate location (Gulf of Guinea). Corner case but a logic error. Left for a future pass with a proper GPS-validity bool.
|
|
||||||
|
|
||||||
## Low
|
|
||||||
|
|
||||||
### ✅ SNR division by 4 — now uses `%.1f`
|
|
||||||
|
|
||||||
[`NearbyScreen.h:467-470`](examples/companion_radio/ui-new/NearbyScreen.h#L467-L470), [`330-332`](examples/companion_radio/ui-new/NearbyScreen.h#L330-L332)
|
|
||||||
|
|
||||||
Scan detail view (`SNR: %.1f dB`, `Rem: %.1f dB`) and the ping popup keep the 0.25 dB resolution. (After the one-list refactor the scan list cards show **RSSI** in the right column, not SNR.)
|
|
||||||
|
|
||||||
### ✅ Trail `_count` cast to `uint16_t`
|
|
||||||
|
|
||||||
[`Trail.h:27`](examples/companion_radio/Trail.h#L27)
|
|
||||||
|
|
||||||
A `static_assert(CAPACITY <= 0xFFFF, …)` next to the CAPACITY definition now fails the build if it is ever grown past what the uint16_t save-header count can hold, instead of silently truncating. Safe today (CAPACITY=512).
|
|
||||||
|
|
||||||
### ✅ Bot `strstr` on truncated 199-char buffer — not reachable
|
|
||||||
|
|
||||||
[`MyMeshBot.h:11`](examples/companion_radio/MyMeshBot.h#L11)
|
|
||||||
|
|
||||||
Re-checked: `BOT_SCRATCH` is 200 and `MAX_TEXT_LEN` is 160, so an incoming message never reaches the 199-char truncation point — the scratch buffer (used by the centralised `botTriggerMatches()`) always holds the whole message. No fix needed.
|
|
||||||
|
|
||||||
### ✅ `strncpy("?", buf, sizeof(buf))` replaced with `strcpy`
|
|
||||||
|
|
||||||
[`MessagesScreen.h:785, 858`](examples/companion_radio/ui-new/MessagesScreen.h#L785)
|
|
||||||
|
|
||||||
Two fallback "?" sender names now use a plain `strcpy` so we don't memset 21 unused bytes for a one-character string.
|
|
||||||
|
|
||||||
### ❌ Title truncation in MSG_PICK reply mode — not an overflow
|
|
||||||
|
|
||||||
[`MessagesScreen.h:1279-1287`](examples/companion_radio/ui-new/MessagesScreen.h#L1279)
|
|
||||||
|
|
||||||
Re-checked: `rlen` is clamped to 20 before building `"RE:" + nick`, so the title is ≤23 chars and fits `title[24]` with no overflow. A nick longer than 20 chars is shown truncated, but that's an intentional fit-to-header limit (the OLED header only fits ~21 chars anyway), not a bug.
|
|
||||||
|
|
||||||
## Priority for merge
|
|
||||||
|
|
||||||
Fix status after this pass:
|
|
||||||
|
|
||||||
- ✅ C1 + C2 — `findChannelIdx == -1` guarded at both channel-recv paths; `addChannelMsg` defends against bogus index
|
|
||||||
- ✅ H4 — `trail_units_idx` reset scoped to the 0xC0DE0003 jump
|
|
||||||
- ✅ M2 — `strnlen` instead of `strlen` on default scope name
|
|
||||||
- ✅ M4 — `renderDiscoverDetail` skips pub-key line on very narrow displays
|
|
||||||
- ✅ L1 — SNR shown with 0.25 dB precision everywhere
|
|
||||||
- ✅ L4 — fallback `"?"` sender no longer memsets through `strncpy`
|
|
||||||
- ✅ Trail map grid silent loss — superseded by the Trail refactor: `renderGrid` now picks a round labelled step (`1m…100km` / `10ft…100mi`) nearest ~1/3 of the shorter side and enforces a `MIN_GRID_PX` floor, so the grid can never silently vanish on an elongated trail. [`TrailScreen.h:635`](examples/companion_radio/ui-new/TrailScreen.h#L635)
|
|
||||||
- ❌ M1 — re-checked, not a bug (`default_scope_name[31]`)
|
|
||||||
- 📋 H1 + H2 — still open; need coordinated fix in upstream `BaseChatMesh` (`findChannelIdx` should iterate `num_channels`, not `MAX_GROUP_CHANNELS`; `saveChannels` should stop at the first uninitialised slot) or a local override
|
|
||||||
- 📋 H3 — left as known limitation pending UX call
|
|
||||||
- 📋 M3, M5, L2, L3, L6 — minor or stylistic; left in backlog
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# MeshCore Solo — documentation
|
||||||
|
|
||||||
|
## Documents
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
| -------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||||
|
| [Messages Screen](./message_screen/message_screen.md) | Sending messages, context menus, reply, navigate to / save shared locations, Notif/Melody overrides |
|
||||||
|
| [Favourites Dial](./favourites_dial/favourites_dial.md) | Pinned contacts grid, unread badges, pin/unpin |
|
||||||
|
| [Clock Screen](./clock_screen/clock_screen.md) | Clock page, date, configurable data fields, alarm / timer / stopwatch |
|
||||||
|
| [Settings Screen](./settings_screen/settings_screen.md) | All settings sections with values and interactions |
|
||||||
|
| [Screen Lock](./screen_lock/screen_lock.md) | Lock/unlock sequence, lock screen, auto-lock |
|
||||||
|
| [Tools Screen](./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](./external_keyboard.md) | CardKB shortcuts, Full vs Compact mode, wired joystick, Heltec V3/V4 wiring |
|
||||||
|
| [Build Flags](./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](../developer/ui-framework.md) | **Developer guide** — the reusable building blocks (screens, lists, popups, mini-icons, geo/persistence helpers) and how to add a new feature |
|
||||||
|
| [UI Core](../developer/ui-core.md) | **Developer guide** — the frontend-independent layer (models, settings schema, events) shared by the OLED/e-ink UI and the LVGL touch UI |
|
||||||
|
|
||||||
|
## Upstream MeshCore
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
| -------------------------------------------------- | ------------------------------------------------ |
|
||||||
|
| [FAQ](../faq.md) | Frequently asked questions |
|
||||||
|
| [CLI Commands](../cli_commands.md) | Commands for repeaters, room servers and sensors |
|
||||||
|
| [Terminal Chat CLI](../terminal_chat_cli.md) | Commands for the terminal chat client |
|
||||||
|
| [Companion Protocol](../companion_protocol.md) | Serial/BLE frame protocol between device and app |
|
||||||
|
| [Packet Format](../packet_format.md) | LoRa packet structure |
|
||||||
|
| [QR Codes](../qr_codes.md) | Channel and contact QR code formats |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Extended language support — one unified 6×9 font (Latin, Greek, Cyrillic) plus on-screen keyboard alphabets for Cyrillic, Greek, Polish, Czech, Slovak, German, French, Spanish, Portuguese and Nordic. Pick two in Settings › Keyboard (**Main**/**Additional**) and switch between them while typing
|
||||||
|
|
||||||
|
- Enabled sensor screens with support for onboard sensors (temperature, humidity, pressure, luminosity, CO₂) and GPS data
|
||||||
|
|
||||||
|
- **GPS navigation** — a full navigation suite that needs no extra hardware (details in the [Tools Screen](./tools_screen/tools_screen.md) docs):
|
||||||
|
|
||||||
|
- **Waypoints** — mark a spot (car, camp, water…) with a short label, see it on the trail map, and get live bearing + distance back to it; the list always offers a one-tap backtrack to where your trail started
|
||||||
|
- **GPS compass** — heading derived from course-over-ground (no magnetometer needed), shown as a clear scrolling heading tape with a large degrees + cardinal readout
|
||||||
|
- **Navigate to anything** — a saved waypoint, a node straight from Nearby Nodes, or a location someone shares with you in a message
|
||||||
|
- **Share & save locations** — send a waypoint to a contact or channel; on the other end, navigate to or save any shared location with one menu
|
||||||
|
- **Live location sharing** — broadcast your position over the mesh as you move (movement-gated, to a channel or contact) and see others who share theirs as pins on the map and live distance/bearing in Nearby
|
||||||
|
- **Locator** — arm a geofence around a waypoint or a person, get alerted on arrive/leave or near/far, with an optional homing beeper that speeds up as you close in. Set from the Locator screen, Nearby Nodes, or Waypoints; target shown as a flag on the map
|
||||||
|
- **GPS trail** — background route recording with an auto-fit map (waypoints + live position), summary stats, auto-pause on stops, and [GPX export](../../README.md#documentation)
|
||||||
|
- **Metric or imperial** — one global Units setting drives every distance and speed across the UI
|
||||||
|
|
||||||
|
- [Messages Screen](./message_screen/message_screen.md) — view and send messages, open message details, reply with quick messages or custom text, navigate to / save locations shared in a message, per-channel notification and melody overrides, add/edit/delete channels on-device
|
||||||
|
|
||||||
|
- [Favourites Dial](./favourites_dial/favourites_dial.md) — pin up to six contacts for quick access from the home screen
|
||||||
|
|
||||||
|
- [Settings Screen](./settings_screen/settings_screen.md) — configure display, sound, home page order, radio and system settings
|
||||||
|
|
||||||
|
- [Clock Screen](./clock_screen/clock_screen.md) — view time and date plus up to three configurable data fields, with built-in clock tools (one-shot alarm, countdown timer, stopwatch)
|
||||||
|
|
||||||
|
- [Screen Lock](./screen_lock/screen_lock.md) — lock the device to prevent accidental keypresses, with a lock screen showing time and sensor data
|
||||||
|
|
||||||
|
- [Tools Screen](./tools_screen/tools_screen.md) — GPS trail & waypoints, compass, nearby nodes (with ping & navigate), ringtone editor, remote bot, auto-advert, live location sharing, locator, diagnostics, repeater, remote admin
|
||||||
|
|
||||||
|
- [External Keyboard & Joystick](./external_keyboard.md) — optional, auto-detected: **CardKB** for typing without the on-screen grid (Fn+Enter submits, Fn+letter picks an accent, Tab is Hold-Enter, Fn+Esc locks), plus a **wired joystick** for boards without one. Compact mode makes CardKB-only operation practical
|
||||||
|
|
||||||
|
- **Auto pwr** (Settings › Radio) — Adaptive Power Control: trims actual TX power on strong links (from ACK SNR) and ramps back up to the configured ceiling on weak/lost links; the home screen shows the live power
|
||||||
|
|
||||||
|
## E-ink Display (Wio Tracker L1)
|
||||||
|
|
||||||
|
The e-ink variant targets the Wio Tracker L1 fitted with a 2.13″ GxEPD2 panel (250 × 122 px). Every screen is adapted for it:
|
||||||
|
|
||||||
|
- **Adaptive layout** — every screen reflows correctly in both landscape (250 × 122) and portrait (122 × 250) orientations
|
||||||
|
- **Display rotation** — configurable in Settings › Display; applied immediately and persisted across reboots
|
||||||
|
- **Joystick rotation** — independent of display rotation; useful for custom enclosures
|
||||||
|
- **Full refresh interval** — configurable in Settings › Display; reduces ghosting on long sessions
|
||||||
|
- **Clock seconds suppressed by default** — seconds are hidden to reduce per-second panel refreshes and extend display lifetime; re-enable in Settings › Display
|
||||||
@@ -25,7 +25,7 @@ Press **Enter** on a contact or channel to open its history, then press **Enter*
|
|||||||
- **Custom message** — opens the on-screen keyboard
|
- **Custom message** — opens the on-screen keyboard
|
||||||
- **Q1–Q10** — quick reply templates editable in Settings › Messages
|
- **Q1–Q10** — quick reply templates editable in Settings › Messages
|
||||||
|
|
||||||
While typing, **UP** from the top letter row enters cursor mode — LEFT/RIGHT move the insertion point, UP/DOWN jump to start/end (then to the grid on a second press), Enter/Cancel exit from anywhere — so you can edit mid-text, not just at the end. **Hold Enter** on a letter with accented variants (a, e, c, n, o, s, z…) opens a one-row accent popup instead — LEFT/RIGHT picks, Enter inserts, Cancel dismisses. Full key set (Shift, T9, Cyrillic/Greek) in the [UI framework guide](../../design/solo_ui_framework.md).
|
While typing, **UP** from the top letter row enters cursor mode — LEFT/RIGHT move the insertion point, UP/DOWN jump to start/end (then to the grid on a second press), Enter/Cancel exit from anywhere — so you can edit mid-text, not just at the end. **Hold Enter** on a letter with accented variants (a, e, c, n, o, s, z…) opens a one-row accent popup instead — LEFT/RIGHT picks, Enter inserts, Cancel dismisses. Full key set (Shift, T9, Cyrillic/Greek) in the [UI framework guide](../../developer/ui-framework.md).
|
||||||
|
|
||||||
The keyboard supports placeholders that insert live data at send time:
|
The keyboard supports placeholders that insert live data at send time:
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ Lists all available home screen pages. For each entry:
|
|||||||
| CR | 5–8 | LEFT/RIGHT. Coding rate (4/5–4/8). |
|
| CR | 5–8 | LEFT/RIGHT. Coding rate (4/5–4/8). |
|
||||||
| Auto pwr | ON / OFF | **Adaptive Power Control.** Lowers TX power on strong links, ramps back to the **TX Pwr** ceiling on weak/lost ones. Link quality from DM ACK SNR, or — for channels (no ACK) — a repeater's rebroadcast. Live power shown on the radio page/name bar. Default OFF. **Suppressed (`--`) while the repeater is on** — restored once it's switched off. |
|
| Auto pwr | ON / OFF | **Adaptive Power Control.** Lowers TX power on strong links, ramps back to the **TX Pwr** ceiling on weak/lost ones. Link quality from DM ACK SNR, or — for channels (no ACK) — a repeater's rebroadcast. Live power shown on the radio page/name bar. Default OFF. **Suppressed (`--`) while the repeater is on** — restored once it's switched off. |
|
||||||
|
|
||||||
There is no "Pwr save" row: hardware RX duty-cycle receive was tried and disabled (`FEAT_RX_POWERSAVE 0` in `examples/companion_radio/Features.h`) — the SX126x's duty-cycle preamble detection needs the sender's actual preamble to exactly match what we configure, which a mixed-firmware mesh can't guarantee (silently drops every packet from a mismatched sender, no matter the signal strength). See `docs/development/roadmap.md` for the full writeup.
|
There is no "Pwr save" row: hardware RX duty-cycle receive was tried and disabled (`FEAT_RX_POWERSAVE 0` in `examples/companion_radio/Features.h`) — the SX126x's duty-cycle preamble detection needs the sender's actual preamble to exactly match what we configure, which a mixed-firmware mesh can't guarantee (silently drops every packet from a mismatched sender, no matter the signal strength).
|
||||||
| Scope | list | Shows the list's **default** scope. **Enter** opens the **SCOPE** list: `*` (wildcard = unscoped, always first, can't be renamed or deleted) plus up to 8 named scopes of your own (e.g. `pl`). Typing a name derives a shared key the same way on every device, so any device that types the same name lands on the same scope automatically, no key exchange needed. **Enter** on a row opens **Set as default** / **Rename** / **Delete** (delete confirms first); **+ Add scope** at the bottom opens the keyboard. The **default** scope (marked `[default]`) governs **DMs** and the **repeater's own relay slot**; each **channel** carries its own pick — set from the channel's context menu (see [Message Screen](../message_screen/message_screen.md)) — and a channel left on `*` sends unscoped. Scopes tag flood traffic so repeaters can tell your community's messages apart from others sharing the same frequency; paired with **Tools › Repeater › Scope only** it's also what this device relays for in repeater mode. The default also syncs both ways with the phone app's default-scope setting. Not encryption — message content is unaffected either way. Upgrading from a build with the old single Scope field carries it over as the default entry and seeds every existing channel with it, so nothing changes on the air. |
|
| Scope | list | Shows the list's **default** scope. **Enter** opens the **SCOPE** list: `*` (wildcard = unscoped, always first, can't be renamed or deleted) plus up to 8 named scopes of your own (e.g. `pl`). Typing a name derives a shared key the same way on every device, so any device that types the same name lands on the same scope automatically, no key exchange needed. **Enter** on a row opens **Set as default** / **Rename** / **Delete** (delete confirms first); **+ Add scope** at the bottom opens the keyboard. The **default** scope (marked `[default]`) governs **DMs** and the **repeater's own relay slot**; each **channel** carries its own pick — set from the channel's context menu (see [Message Screen](../message_screen/message_screen.md)) — and a channel left on `*` sends unscoped. Scopes tag flood traffic so repeaters can tell your community's messages apart from others sharing the same frequency; paired with **Tools › Repeater › Scope only** it's also what this device relays for in repeater mode. The default also syncs both ways with the phone app's default-scope setting. Not encryption — message content is unaffected either way. Upgrading from a build with the old single Scope field carries it over as the default entry and seeds every existing channel with it, so nothing changes on the air. |
|
||||||
|
|
||||||
| OLED | E-Ink |
|
| OLED | E-Ink |
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ A circular tab carousel of live device and mesh stats, refreshed once a second (
|
|||||||
|
|
||||||
The packet counters, **Forwarded** and **Errors**, are cumulative since boot. On the **Live** tab, **Hold Enter** opens a *Reset counters?* confirm (defaults to Cancel); the live readings (noise, RSSI/SNR, pool, queue, uptime) are not affected. **Cancel/Back** returns to the Tools list.
|
The packet counters, **Forwarded** and **Errors**, are cumulative since boot. On the **Live** tab, **Hold Enter** opens a *Reset counters?* confirm (defaults to Cancel); the live readings (noise, RSSI/SNR, pool, queue, uptime) are not affected. **Cancel/Back** returns to the Tools list.
|
||||||
|
|
||||||
There is no "RXPS wd s/h" row: it belongs to hardware RX duty-cycle receive, which is currently disabled (see the Settings screen doc and `docs/development/roadmap.md`) — nothing to watchdog.
|
There is no "RXPS wd s/h" row: it belongs to hardware RX duty-cycle receive, which is currently disabled (see the Settings screen doc) — nothing to watchdog.
|
||||||
|
|
||||||
The counters make the repeater behaviour observable: **Forwarded** confirms the node is actually relaying (not just configured to), and **Pool free** / **Queue** show whether forwarding is exhausting the packet pool. See **Tools › Repeater** for the relaying options.
|
The counters make the repeater behaviour observable: **Forwarded** confirms the node is actually relaying (not just configured to), and **Pool free** / **Queue** show whether forwarding is exhausting the packet pool. See **Tools › Repeater** for the relaying options.
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
// (RadioLibWrapper::armRecv()/CustomSX1262Wrapper::startPowerSaveRecv()) is
|
// (RadioLibWrapper::armRecv()/CustomSX1262Wrapper::startPowerSaveRecv()) is
|
||||||
// left in place for if a network-wide compatibility mechanism ever lands --
|
// left in place for if a network-wide compatibility mechanism ever lands --
|
||||||
// flipping this back to 1 requires solving that first, not just re-adding the
|
// flipping this back to 1 requires solving that first, not just re-adding the
|
||||||
// Settings toggle. See docs/development/roadmap.md for the full writeup.
|
// Settings toggle.
|
||||||
// Lives here (not MyMesh.h) so every `#if FEAT_RX_POWERSAVE` user sees the
|
// Lives here (not MyMesh.h) so every `#if FEAT_RX_POWERSAVE` user sees the
|
||||||
// same definition: an undefined macro in `#if` silently reads as 0, which
|
// same definition: an undefined macro in `#if` silently reads as 0, which
|
||||||
// would split the build the day this is flipped to 1.
|
// would split the build the day this is flipped to 1.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// and always read back clamped to what the ring still holds for that contact --
|
// and always read back clamped to what the ring still holds for that contact --
|
||||||
// the same self-healing shape as MessageHistory::chUnread() for channels.
|
// the same self-healing shape as MessageHistory::chUnread() for channels.
|
||||||
//
|
//
|
||||||
// Header-only UI Core model (see docs/development/ui-core.md); no drawing, no
|
// Header-only UI Core model (see docs/developer/ui-core.md); no drawing, no
|
||||||
// screen state.
|
// screen state.
|
||||||
|
|
||||||
class DmUnreadTable {
|
class DmUnreadTable {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// Declarative settings (docs/development/ui-core.md, step 4): one table of
|
// Declarative settings (docs/developer/ui-core.md, step 4): one table of
|
||||||
// NodePrefs options with their labels, value lists and side effects, so every
|
// NodePrefs options with their labels, value lists and side effects, so every
|
||||||
// frontend renders the same settings without its own per-item code. ui-lvgl
|
// frontend renders the same settings without its own per-item code. ui-lvgl
|
||||||
// draws it as switches and dropdowns, one page per Page below; ui-new still has
|
// draws it as switches and dropdowns, one page per Page below; ui-new still has
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// UI Core: hardware-independent UI state and logic shared by every frontend
|
// UI Core: hardware-independent UI state and logic shared by every frontend
|
||||||
// (ui-new today, ui-lvgl later). See docs/development/ui-core.md.
|
// (ui-new today, ui-lvgl later). See docs/developer/ui-core.md.
|
||||||
//
|
//
|
||||||
// The Core is MyMesh's Listener: it files incoming/outgoing messages into the
|
// The Core is MyMesh's Listener: it files incoming/outgoing messages into the
|
||||||
// history, keeps the unread counters, runs the engines, and tells the frontend
|
// history, keeps the unread counters, runs the engines, and tells the frontend
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// does, and reports to the frontend through UiEventQueue. This interface is the
|
// does, and reports to the frontend through UiEventQueue. This interface is the
|
||||||
// remainder -- calls that must stay synchronous with mesh processing, or whose
|
// remainder -- calls that must stay synchronous with mesh processing, or whose
|
||||||
// logic still lives in the frontend until its extraction step
|
// logic still lives in the frontend until its extraction step
|
||||||
// (docs/development/ui-core.md). None of these may draw or block.
|
// (docs/developer/ui-core.md). None of these may draw or block.
|
||||||
|
|
||||||
class UiCoreHost {
|
class UiCoreHost {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Core → frontend events. Engines never call into the frontend (no drawing, no
|
// Core → frontend events. Engines never call into the frontend (no drawing, no
|
||||||
// sound, no display power); they push a tagged event here and the frontend
|
// sound, no display power); they push a tagged event here and the frontend
|
||||||
// drains the queue from its own loop() and reacts in its own way (ui-new:
|
// drains the queue from its own loop() and reacts in its own way (ui-new:
|
||||||
// alert overlay + buzzer; ui-lvgl: a dialog). See docs/development/ui-core.md.
|
// alert overlay + buzzer; ui-lvgl: a dialog). See docs/developer/ui-core.md.
|
||||||
//
|
//
|
||||||
// Fixed-size ring, no heap. When full the oldest event is dropped -- events are
|
// Fixed-size ring, no heap. When full the oldest event is dropped -- events are
|
||||||
// hints for the view; the authoritative state stays queryable on the engines.
|
// hints for the view; the authoritative state stays queryable on the engines.
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ static const char* waypointsFull() {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ui-lvgl skeleton (docs/development/ui-core.md, step 5): status bar, home,
|
// ui-lvgl skeleton (docs/developer/ui-core.md, step 5): status bar, home,
|
||||||
// conversation list, contact picker, conversation view with compose. Every
|
// conversation list, contact picker, conversation view with compose. Every
|
||||||
// piece of state it shows comes from the UI Core; this file only draws it.
|
// piece of state it shows comes from the UI Core; this file only draws it.
|
||||||
|
|
||||||
@@ -990,8 +990,7 @@ void UITask::loop() {
|
|||||||
prefsFlush();
|
prefsFlush();
|
||||||
usbPoll();
|
usbPoll();
|
||||||
#if defined(UI_HEAP_REPORT) && defined(ESP32)
|
#if defined(UI_HEAP_REPORT) && defined(ESP32)
|
||||||
// -D UI_HEAP_REPORT: internal / PSRAM heap once, 20 s after boot (the
|
// -D UI_HEAP_REPORT: internal / PSRAM heap once, 20 s after boot.
|
||||||
// framework comparison in docs/development/l2-roadmap.md).
|
|
||||||
static bool heap_done = false;
|
static bool heap_done = false;
|
||||||
if (!heap_done && millis() > 20000) {
|
if (!heap_done && millis() > 20000) {
|
||||||
heap_done = true;
|
heap_done = true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// ui-lvgl: the rich (colour + touch) frontend of the UI Core. Wio Tracker L2
|
// ui-lvgl: the rich (colour + touch) frontend of the UI Core. Wio Tracker L2
|
||||||
// first. All application state lives in the Core (../ui-core); this class owns
|
// first. All application state lives in the Core (../ui-core); this class owns
|
||||||
// only LVGL screens, display power and input. See docs/development/ui-core.md.
|
// only LVGL screens, display power and input. See docs/developer/ui-core.md.
|
||||||
|
|
||||||
#include <MeshCore.h>
|
#include <MeshCore.h>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ lib_deps =
|
|||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
; ui-lvgl: the real L2 interface -- LVGL 9 frontend over the shared UI Core
|
; ui-lvgl: the real L2 interface -- LVGL 9 frontend over the shared UI Core
|
||||||
; (docs/development/ui-core.md, step 5), on Arduino-ESP32 3.3.12 / ESP-IDF 5.5
|
; (docs/developer/ui-core.md, step 5), on Arduino-ESP32 3.3.12 / ESP-IDF 5.5
|
||||||
; (pioarduino). *_solo_lvgl envs are published by build-solo-firmwares.yml
|
; (pioarduino). *_solo_lvgl envs are published by build-solo-firmwares.yml
|
||||||
; (merged image + the -ota.bin that Settings > Firmware update installs).
|
; (merged image + the -ota.bin that Settings > Firmware update installs).
|
||||||
[env:Wio_Tracker_L2_companion_solo_lvgl]
|
[env:Wio_Tracker_L2_companion_solo_lvgl]
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ public:
|
|||||||
// guarantee we can't currently make. A mismatch doesn't cost a little
|
// guarantee we can't currently make. A mismatch doesn't cost a little
|
||||||
// sensitivity, it silently drops every packet from that sender regardless of
|
// sensitivity, it silently drops every packet from that sender regardless of
|
||||||
// signal strength (2026-09-22 field report: near-total reception loss on a
|
// signal strength (2026-09-22 field report: near-total reception loss on a
|
||||||
// stock SF8 preset, unaffected by antenna gain). See companion Features.h and
|
// stock SF8 preset, unaffected by antenna gain). See companion Features.h
|
||||||
// docs/development/roadmap.md before ever flipping FEAT_RX_POWERSAVE back on.
|
// before ever flipping FEAT_RX_POWERSAVE back on.
|
||||||
int16_t startPowerSaveRecv() override {
|
int16_t startPowerSaveRecv() override {
|
||||||
return ((SX126x *)_radio)->startReceiveDutyCycleAuto(preambleLengthForSF(_preamble_sf), 8);
|
return ((SX126x *)_radio)->startReceiveDutyCycleAuto(preambleLengthForSF(_preamble_sf), 8);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user