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.
All firmware files are published on the [releases page](https://github.com/MarekZegare4/MeshCore-Solo/releases). Each binary supports both BLE and USB serial — there are no separate BLE/USB builds.
The MCU column decides how you flash: nRF52840 boards take a drag-and-drop `.uf2`, ESP32-S3 boards take a `.bin` written with a flasher — see [Flashing](#flashing).
The Wio Tracker L1s, GAT562 and Cardputer ADV work out of the box (the Cardputer has a built-in QWERTY keyboard). Heltec V3/V4 need [a keyboard or joystick wired up](#hardware-setup--heltec-v3--v4) first. The T-Echo Lite needs the KeyShield add-on (its own T9 keypad) to be usable standalone — see [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md).
- 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
- **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)
- [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
- [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
- [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
- **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
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`
> 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.
Applies to every board: each binary serves the companion app over **both** BLE and USB serial, but not at once.
> [!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.
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:
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 [`variants/heltec_v3/platformio.ini`](./variants/heltec_v3/platformio.ini) and [`variants/heltec_v4/platformio.ini`](./variants/heltec_v4/platformio.ini) — comments there list which GPIOs are already claimed. Full details in [External Keyboard & Joystick](./docs/solo_features/external_keyboard.md).
| [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 |
- **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.
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).
> 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.)
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:
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.
### 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` |
Contributions are welcome. Fork the repository, make your changes, and open a pull request. Please follow the existing code style and keep changes focused.
Big thanks to the people who contributed to this fork:
- [vanous](https://github.com/vanous)
- [marczykm](https://github.com/marczykm)
Built on upstream [MeshCore](https://github.com/meshcore-dev/MeshCore) and its [community](https://github.com/meshcore-dev/MeshCore/graphs/contributors).