Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fff2811ed | ||
|
|
33306d9fa9 | ||
|
|
dd7ba1f9f9 | ||
|
|
077bc12f24 | ||
|
|
0a51e7e504 | ||
|
|
e4aa702251 | ||
|
|
6d1d38196e | ||
|
|
19107bf7e0 | ||
|
|
e26b406e02 | ||
|
|
f2c1b73636 | ||
|
|
5627c90bb3 | ||
|
|
41eb4a559f | ||
|
|
8bb2b47cd7 | ||
|
|
eeefa47fa2 | ||
|
|
1f01a267dd | ||
|
|
2c7ea98d42 | ||
|
|
fb0346e249 | ||
|
|
3d487aefcf | ||
|
|
be6e1f20ff | ||
|
|
f5d8cfa390 | ||
|
|
fdaf48402c | ||
|
|
3b1a07dd93 | ||
|
|
31cb4c38a8 | ||
|
|
8dc02a2b23 | ||
|
|
47ba9e010c | ||
|
|
16f18a6a11 | ||
|
|
75883a62a2 | ||
|
|
f22c9decc5 | ||
|
|
99a76a4569 | ||
|
|
999dde93a3 | ||
|
|
f1e2bc375b | ||
|
|
f29728bdf8 | ||
|
|
fd8e835cf3 | ||
|
|
0b1349aa00 | ||
|
|
3b773986f0 | ||
|
|
c3e0cdb8d8 | ||
|
|
9b30dad9e8 | ||
|
|
2b554940af | ||
|
|
3779724000 | ||
|
|
71dff36904 | ||
|
|
35d0daf203 | ||
|
|
4df3dce481 | ||
|
|
e7c7df65fa | ||
|
|
4e6a9e0108 | ||
|
|
2d6a88bd1f | ||
|
|
b1ad449147 | ||
|
|
b1b689c48f | ||
|
|
b525f75335 | ||
|
|
3d6e6f0963 | ||
|
|
5e4f9a221e | ||
|
|
57f206220d | ||
|
|
756bb9f821 | ||
|
|
99423e5bf8 | ||
|
|
cab0c81208 | ||
|
|
b67f559d15 | ||
|
|
85e8681fef | ||
|
|
3fbc3e3582 | ||
|
|
22a08c6df6 | ||
|
|
041dd60b8d | ||
|
|
36e49d4d9e | ||
|
|
ae02fb6238 | ||
|
|
fd0b952620 | ||
|
|
f029dcc744 | ||
|
|
be0a49255c | ||
|
|
2353c56adc | ||
|
|
0adcd26b78 | ||
|
|
53a60dfbb2 | ||
|
|
e5cd766f73 | ||
|
|
5c97d02fbe | ||
|
|
01833d0365 | ||
|
|
a95f976262 | ||
|
|
2540e230c6 | ||
|
|
6073b6fa4e | ||
|
|
57802eba3d | ||
|
|
b16206bf7d | ||
|
|
c4c28d493d | ||
|
|
8e7d6ae902 | ||
|
|
a87ebe1e81 | ||
|
|
69ffe71bfe | ||
|
|
e617bf306c | ||
|
|
687eb3865c | ||
|
|
9881bb0038 | ||
|
|
4b0c2e9de1 | ||
|
|
43f040ec0a | ||
|
|
c22c76b704 | ||
|
|
79df5281e4 | ||
|
|
308be40b96 | ||
|
|
8a2825e7fd | ||
|
|
5f40653e58 | ||
|
|
d787d77365 | ||
|
|
831959ea5c | ||
|
|
4df391a283 | ||
|
|
3f375f2fe4 | ||
|
|
9da367d0bc | ||
|
|
01a5238662 | ||
|
|
1514e7638c | ||
|
|
8c7fb6af5c | ||
|
|
c9033a3823 | ||
|
|
946e73bb0b | ||
|
|
326d34aaec | ||
|
|
d2fe2ed728 | ||
|
|
9547ce6ede | ||
|
|
49a8f31b8c | ||
|
|
ded72bb91a | ||
|
|
cb08116eae | ||
|
|
f0a7265dec | ||
|
|
814c8f5941 | ||
|
|
76cd025bf5 | ||
|
|
d20241627c | ||
|
|
0b886e4fee | ||
|
|
a49726d4ce | ||
|
|
6aa1147eb3 | ||
|
|
d9f45405d5 | ||
|
|
f04e01769d | ||
|
|
9785d8b7d1 | ||
|
|
bf96253308 | ||
|
|
65a485d28c | ||
|
|
b2950a7f88 | ||
|
|
e95dd755e1 | ||
|
|
2532ddc7ba | ||
|
|
6309503060 | ||
|
|
b7bebd508b | ||
|
|
b470097534 | ||
|
|
1aab86b530 | ||
|
|
bc129b5ffb | ||
|
|
8133a47769 | ||
|
|
4bbfcb5550 | ||
|
|
9bcad3c2c2 | ||
|
|
bf5c6828a3 | ||
|
|
facbe49b01 | ||
|
|
3c845115bf | ||
|
|
f59d9f4b23 | ||
|
|
48e77933e2 | ||
|
|
49a6f80419 | ||
|
|
667436fdd2 | ||
|
|
d59cf5d9ae | ||
|
|
e682fbcb5c | ||
|
|
db7a43fcc4 | ||
|
|
cca9a444e8 | ||
|
|
381658ab2a | ||
|
|
4846d4fa51 | ||
|
|
c322b1fa3a | ||
|
|
d964c029d9 | ||
|
|
d2d3060051 | ||
|
|
e2eb0d3118 | ||
|
|
dd19cbe52d | ||
|
|
aa598d626b | ||
|
|
862ec32f80 | ||
|
|
1eeeb267d2 | ||
|
|
2b30ca2a5a | ||
|
|
dc4ba96825 | ||
|
|
f18929b312 | ||
|
|
df7f9bc2ab | ||
|
|
2a34ad476e | ||
|
|
0ea242c81f | ||
|
|
b3c638842e | ||
|
|
a8f8a79d3f | ||
|
|
30ed2d77f6 | ||
|
|
c87cd3cc98 | ||
|
|
996eac21fd | ||
|
|
3a3a37d193 | ||
|
|
05b24f01ad | ||
|
|
dcf3958cc7 | ||
|
|
6de8346e7f | ||
|
|
f61b8fbbcf | ||
|
|
be32315b98 | ||
|
|
fe4ed029f5 | ||
|
|
53251c33e0 | ||
|
|
f9c3db99c2 | ||
|
|
230b0936bc | ||
|
|
5da14d8bda | ||
|
|
32e1668406 | ||
|
|
d4ca18335e | ||
|
|
259aa36e40 | ||
|
|
78a6c01f95 | ||
|
|
51b513b181 | ||
|
|
6dc09b3185 | ||
|
|
b3004b85eb | ||
|
|
a7d435b03b | ||
|
|
8718e49827 | ||
|
|
641700fad0 | ||
|
|
38c0e7a016 | ||
|
|
3e71867e71 | ||
|
|
0be7c20600 | ||
|
|
ae690e3fbb | ||
|
|
ad98f661cc | ||
|
|
546cc9079a | ||
|
|
9d5df3001d | ||
|
|
82b6d3b56d | ||
|
|
a38ea1e3bb | ||
|
|
be1996ce77 | ||
|
|
01900296f3 | ||
|
|
4e1ac2ca6b | ||
|
|
5d9a184314 | ||
|
|
8e17b1b071 | ||
|
|
b58cecab5c | ||
|
|
816045bb6b | ||
|
|
11939a744e | ||
|
|
f47d71a05b | ||
|
|
8eb63a7edd | ||
|
|
5623237951 | ||
|
|
b90b8bac14 | ||
|
|
3585b3a1e0 | ||
|
|
a4d3715529 | ||
|
|
ffc501d09d | ||
|
|
61666ac033 | ||
|
|
5f69a61c9c | ||
|
|
ddc4a34d4d | ||
|
|
df4a675cd3 | ||
|
|
8d068cfcc1 | ||
|
|
c4f31e5a69 | ||
|
|
0af47e5936 | ||
|
|
492d0886ec | ||
|
|
96dffaef58 | ||
|
|
3a1495784e | ||
|
|
26c9ccdc07 | ||
|
|
0d8e635848 | ||
|
|
0731b5ff67 | ||
|
|
b5e428734c | ||
|
|
18904c9a5d | ||
|
|
39a4db8f03 | ||
|
|
03e50b0609 | ||
|
|
d75db73fc8 | ||
|
|
56cf91ef5b | ||
|
|
b4c941f954 | ||
|
|
1e00ac3876 | ||
|
|
2b45225e0b | ||
|
|
dad7d295dc | ||
|
|
2d49fb69ec | ||
|
|
94ef98a12f | ||
|
|
2db46ec190 | ||
|
|
11775ce5ac | ||
|
|
7d3b4a4336 | ||
|
|
f78e3ed493 | ||
|
|
b986f49e1a | ||
|
|
f6192aa6e5 | ||
|
|
cff58bbaa2 | ||
|
|
b31e37cbbe | ||
|
|
76f0754d0d | ||
|
|
87cb1c3880 | ||
|
|
f39e072bd7 | ||
|
|
bed04417ed | ||
|
|
b1adef7364 | ||
|
|
36e53d9a1c | ||
|
|
7498c502cc | ||
|
|
bbb7d31975 | ||
|
|
83cf0202f7 | ||
|
|
142ea87064 | ||
|
|
1ca2f4e3e6 | ||
|
|
59b1b2ab09 | ||
|
|
c62107c28a | ||
|
|
eb0f3cf0b0 | ||
|
|
36a328a4f7 | ||
|
|
13fca6369f | ||
|
|
abdb878006 | ||
|
|
2b4f2bc29e | ||
|
|
79b4f10c25 | ||
|
|
42746ef387 | ||
|
|
300bf89e1a | ||
|
|
45e46cd3ab | ||
|
|
4bb7c08bf0 | ||
|
|
97b24c4268 | ||
|
|
981fcba226 | ||
|
|
50f73f41b0 | ||
|
|
c5c0a04484 | ||
|
|
10ea0d8399 | ||
|
|
475245c90e | ||
|
|
1f43a8ba37 | ||
|
|
d491065719 | ||
|
|
ab52385f98 | ||
|
|
d11a5bb6b2 | ||
|
|
80af33e7f9 | ||
|
|
c8849916df | ||
|
|
278b31cc92 | ||
|
|
24f338438e | ||
|
|
3e1b45504f | ||
|
|
12c34a2b35 | ||
|
|
0a3f9fe82f | ||
|
|
02cff3389d | ||
|
|
4e3c212a89 | ||
|
|
a804d709cf | ||
|
|
7111789fa7 | ||
|
|
fbc015cf5c | ||
|
|
3ae9582431 | ||
|
|
36e5ed4c36 | ||
|
|
f817fca41c | ||
|
|
5fcf6ddac3 | ||
|
|
5e6aaa8852 | ||
|
|
cd2dc8b3f8 | ||
|
|
f4fb7171e5 | ||
|
|
8322b5361d | ||
|
|
266f5c8adc | ||
|
|
069aeb8908 | ||
|
|
61a914240f | ||
|
|
29204dd33f | ||
|
|
bb86a32b04 | ||
|
|
f985458363 | ||
|
|
30083efdd7 | ||
|
|
4a0d7f5247 | ||
|
|
d22390aeb8 | ||
|
|
3130a1198e | ||
|
|
1f755ec3b8 |
No files matched your search
@@ -28,6 +28,8 @@ scripts/build/wifi_secrets.h
|
||||
# Local working notes + agent config (contain ops/infra details — never publish)
|
||||
CLAUDE.md
|
||||
.claude/
|
||||
/dev-docs/
|
||||
/crash-dumps/
|
||||
|
||||
# Custom timestamped build outputs
|
||||
/builds/
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Supported devices
|
||||
|
||||
Current hardware support, install paths and maturity. "Stable" means the board
|
||||
ships on the stable release channel; "Beta" means it is new and lives on the
|
||||
test channel until the next stable promote. Install links:
|
||||
[flasher.wadamesh.com](https://flasher.wadamesh.com) (browser, Chrome/Edge over
|
||||
USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases).
|
||||
|
||||
| Device | MCU / radio | Display and input | Install | Channel | Status |
|
||||
|---|---|---|---|---|---|
|
||||
| LilyGo T-Deck / T-Deck Plus | ESP32-S3, SX1262 | 2.8" 320x240 touch, QWERTY, trackball | Web flasher (standalone) or Launcher app image | Stable | Fully supported, reference device |
|
||||
| Heltec V4 + TFT | ESP32-S3, SX1262 | 2.4" 240x320 touch (CHSC6x) | Web flasher | Stable | Fully supported; Expansion Kit sensors, V4.3 high-gain RX toggle |
|
||||
| Tanmatsu | ESP32-P4 + ESP32-C6, SX1262 | 4" 800x480, 69-key keyboard (no touch) | Tanmatsu app store on the device (runs under the badge.team launcher, not web-flashable) | Store tracks the test channel | Fully supported; LoRa + Wi-Fi + Bluetooth simultaneously, standalone and companion in one |
|
||||
| Elecrow ThinkNode M9 | ESP32-S3, LR1110 | 2.4" 240x320 (no touch), I2C QWERTY + d-pad | Web flasher | Beta (new in beta_38) | Hardware-complete community port by ded (#138): GPS, microSD, buzzer, lock screen, d-pad navigation |
|
||||
| RAK WisMesh Tap V2 (RAK3312) | ESP32-S3, SX1262 | Touch display (LovyanGFX, 30+ fps) | Web flasher | Beta (new in beta_38) | Early community port by Ethac.chen (#136); core mesh, chat and map working |
|
||||
|
||||
## Feature notes per board
|
||||
|
||||
- **T-Deck**: the everything device: touch, physical keyboard, trackball cursor
|
||||
or d-pad navigation, microSD (deep 5000-message chat history, map tile packs,
|
||||
data storage), GPS on the Plus, notification sounds through the I2S speaker.
|
||||
- **Heltec V4 + TFT**: touch UI with the on-screen keyboard; the optional
|
||||
Expansion Kit adds environment sensors (home-screen chart) and a piezo
|
||||
buzzer. V4.3 boards get the switchable high-gain receive LNA toggle.
|
||||
- **Tanmatsu**: keyboard-driven UI (no touchscreen) with the coloured function
|
||||
keys mapped to tabs, ALT accent picker, UI scaling (Normal/Large/Huge),
|
||||
microSD for all persistent data. Ships through the Tanmatsu launcher store,
|
||||
updates arrive as store updates.
|
||||
- **ThinkNode M9**: keyboard plus d-pad navigation (no touch), same feature set
|
||||
as the other boards where the hardware allows. New in beta_38; expect rough
|
||||
edges and report them.
|
||||
- **RAK WisMesh Tap V2**: newest port, touch-driven. The browser-flash path is
|
||||
fresh; if the flasher cannot open the serial port, put the board in download
|
||||
mode manually and retry, and please report it.
|
||||
|
||||
## Requested boards
|
||||
|
||||
Open hardware requests, roughly in demand order. Ports are welcome, see
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md); the M9 and Tap V2 both started as community
|
||||
PRs.
|
||||
|
||||
- LilyGo T-Deck Pro Max: [#62](https://github.com/ALLFATHER-BV/wadamesh/issues/62)
|
||||
- LilyGo T-pager: [#60](https://github.com/ALLFATHER-BV/wadamesh/issues/60)
|
||||
- SenseCAP Indicator D1L: [#14](https://github.com/ALLFATHER-BV/wadamesh/issues/14)
|
||||
|
||||
## Channels
|
||||
|
||||
- **Stable**: the tested default. The flasher installs it unless you pick Beta.
|
||||
- **Beta**: new features and fixes earlier; on-device opt-in via Settings,
|
||||
About, "Get test builds (beta)". New boards debut here and move to Stable
|
||||
with the next promote.
|
||||
@@ -51,14 +51,32 @@ ed25519 (bundled in lib/ed25519)
|
||||
AsyncElegantOTA (vendored in arch/esp32/AsyncElegantOTA)
|
||||
Copyright (c) Ayush Sharma — MIT License
|
||||
|
||||
TFT_eSPI (Heltec V4 display backend)
|
||||
TFT_eSPI (T-LoRa Pager display backend)
|
||||
Copyright (c) Bodmer — MIT / FreeBSD License — https://github.com/Bodmer/TFT_eSPI
|
||||
|
||||
Adafruit GFX / ST7735_ST7789 (T-Deck display backend)
|
||||
Adafruit GFX / ST7735_ST7789 (T-Deck / Heltec V4 display backend)
|
||||
Copyright (c) Adafruit Industries — BSD License
|
||||
|
||||
chsc6x (Heltec V4 CHSC6x cap-touch driver)
|
||||
Quency-D — https://github.com/Quency-D/chsc6x
|
||||
|
||||
SensorLib (T-LoRa Pager XL9555/BQ27220/AW9364 drivers)
|
||||
Copyright (c) Lewis He — MIT License — https://github.com/lewisxhe/SensorLib
|
||||
|
||||
ES8311 register sequence and clock-divider coefficients (T-LoRa Pager audio
|
||||
codec driver, variants/lilygo_tlora_pager/Es8311Codec.cpp; T-Display P4 audio
|
||||
driver, variants/tdisplay_p4/P4Audio.cpp)
|
||||
Copyright (c) 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
Apache License 2.0 — https://github.com/espressif/esp-adf-libs and
|
||||
https://github.com/espressif/esp-bsp (components/es8311)
|
||||
wadamesh's drivers are original code (not copies of esp_codec_dev's
|
||||
plugin/HAL architecture) but their ES8311 register addresses, startup/
|
||||
power sequences, and MCLK clock-divider coefficients are derived from
|
||||
Espressif's public es8311.c/es8311_reg.h; P4Audio.cpp additionally
|
||||
re-bases the esp-bsp init flow onto Arduino Wire.
|
||||
|
||||
LovyanGFX (RAK WisMesh Tap V2 display backend)
|
||||
Copyright (c) lovyan03 — FreeBSD License — https://github.com/lovyan03/LovyanGFX
|
||||
|
||||
This list is not exhaustive and is expanded as dependencies are vendored or
|
||||
added.
|
||||
@@ -17,8 +17,14 @@ MeshCore fork via PlatformIO `lib_deps`.
|
||||
|
||||
## Boards
|
||||
|
||||
- LilyGo T-Deck / T-Deck Plus — env `LilyGo_TDeck_companion_radio_touch`
|
||||
- Heltec V4 + TFT + CHSC6x touch — env `heltec_v4_tft_companion_radio_usb_tcp_touch`
|
||||
See **[DEVICES.md](DEVICES.md)** for the full support matrix, install paths and
|
||||
per-board status.
|
||||
|
||||
- LilyGo T-Deck / T-Deck Plus — env `LilyGo_TDeck_companion_radio_touch` (stable)
|
||||
- Heltec V4 + TFT + CHSC6x touch — env `heltec_v4_tft_companion_radio_usb_tcp_touch` (stable)
|
||||
- Tanmatsu (ESP32-P4) — built from `tanmatsu/` (ESP-IDF), ships via the Tanmatsu app store
|
||||
- Elecrow ThinkNode M9 — env `ThinkNode_M9_companion_radio_touch` (beta)
|
||||
- RAK WisMesh Tap V2 (RAK3312) — env `rak_tap_v2_companion_radio_touch` (beta)
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# WadaMesh release channels — Stable + Test
|
||||
|
||||
Goal: ship **test** builds to the community fast, let them shake out, then **promote**
|
||||
a proven test build to **stable**. What ships to stable is exactly what was tested
|
||||
(promote the binary, don't rebuild).
|
||||
|
||||
## Two concepts, kept separate
|
||||
|
||||
- **Branches** = where code lives.
|
||||
- **Channels** = what a device / the flasher pulls (Stable vs Beta).
|
||||
|
||||
## Branches
|
||||
|
||||
- **`main`** — active line. All work lands here; every *test* release is cut from `main`. (Unchanged from today.)
|
||||
- **`stable`** — promoted line. Moves only when a test build is blessed. Starts at the **beta_20** commit.
|
||||
- Promote = `git branch -f stable <blessed beta commit>` + push, then run the stable-promotion publish.
|
||||
|
||||
## Versioning / tags
|
||||
|
||||
- **Test builds:** `beta_N` (immutable, incrementing), marked **GitHub pre-release**. Also mirrored to a rolling **`beta-latest`** pre-release (overwritten each time) for the Launcher beta sync.
|
||||
- **Stable:** the blessed `beta_N`, marked **GitHub Latest**. Keep the `beta_N` id for now; friendly `vX.Y` names are an optional later polish (would need a rebuild-with-tag per promotion).
|
||||
- First stable = **beta_20**.
|
||||
|
||||
## Distribution matrix
|
||||
|
||||
| Target | Stable | Test / Beta |
|
||||
|---|---|---|
|
||||
| GitHub release | blessed `beta_N`, marked **Latest** | every `beta_N`, **pre-release** + rolling `beta-latest` |
|
||||
| VPS archive | `releases/TOUCH/<beta_N>/` (only blessed lands here) | `releases/BETA/<beta_N>/` |
|
||||
| VPS channel feed | `stable/` (version.json + bins) | `beta/` (version.json + bins) |
|
||||
| Flasher (flasher.wadamesh.com) | default | "Beta / testing" toggle |
|
||||
| On-device update check | stable feed (legacy firmware reads `releases/TOUCH`) | beta feed (Phase 2 setting) |
|
||||
| LauncherHub (bmorcelli, T-Deck) | existing entry (follows GitHub Latest) | new "WadaMesh (Beta)" entry (Phase 3) |
|
||||
| Tanmatsu app-store | promote on stable only | (skipped for tests) |
|
||||
|
||||
## The linchpin: keeping field devices on stable
|
||||
|
||||
Legacy firmware's update check scans `releases/TOUCH/` for the highest `beta_N`. So:
|
||||
|
||||
- **Test builds archive under `releases/BETA/`** — invisible to legacy firmware.
|
||||
- **Promotion copies the blessed build into `releases/TOUCH/`** — legacy devices then see it and show "update available," landing everyone on stable.
|
||||
|
||||
Net effect: existing devices quietly settle on **stable (beta_20)** and stop chasing
|
||||
every beta; beta testers opt in via the flasher Beta toggle (or, later, the on-device
|
||||
Beta channel).
|
||||
|
||||
## Two workflows
|
||||
|
||||
### Cut a TEST release (fast, frequent) — from `main`
|
||||
1. `beta_N` = next number.
|
||||
2. Build both touch envs with the tag embedded.
|
||||
3. GitHub: create `beta_N` release **`--prerelease`** with the 7 assets; also refresh the rolling `beta-latest` pre-release.
|
||||
4. VPS: rsync to `releases/BETA/<beta_N>/` + refresh the `beta/` feed.
|
||||
5. Flasher Beta toggle now serves it. Skip LauncherHub / Tanmatsu / site. (~minutes)
|
||||
|
||||
### Promote to STABLE (deliberate, when proven)
|
||||
1. `git branch -f stable <beta_N commit>` + push.
|
||||
2. GitHub: mark that `beta_N` release **Latest** (clear pre-release).
|
||||
3. VPS: copy `releases/BETA/<beta_N>/` → `releases/TOUCH/<beta_N>/`; refresh `stable/` + `latest/` (flasher stable) feeds.
|
||||
4. Full fan-out: LauncherHub follows automatically; push the Tanmatsu store; bump the site "what's new."
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 1 — channel split, no firmware change (do now)
|
||||
- [ ] Create + push `stable` branch at beta_20.
|
||||
- [ ] VPS: stand up `stable/`, `beta/`, `releases/BETA/`; point `latest/` (flasher stable) at beta_20; freeze `releases/TOUCH/` at beta_20.
|
||||
- [ ] GitHub: ensure beta_20 is **Latest**; future betas → `--prerelease`.
|
||||
- [ ] Flasher: Stable (default) / Beta toggle + the beta manifest.
|
||||
- [ ] `release.sh` / `gen-flasher-meta.py`: add `--channel beta|stable` producing the right feed/assets + the rolling `beta-latest`.
|
||||
- [ ] Rewrite the `wadamesh-release` skill into the two-workflow form.
|
||||
|
||||
### Phase 2 — on-device channel selector (ships in a beta, then promoted)
|
||||
- [ ] Settings → **Update channel: Stable / Beta**, default Stable (persisted).
|
||||
- [ ] `verchkFetchLatest` reads the `stable/` or `beta/` feed per the setting.
|
||||
- [ ] Beta devices get the new-beta banner automatically; stable devices never see betas.
|
||||
|
||||
### Phase 3 — LauncherHub beta listing (PR to bmorcelli, when wanted)
|
||||
- [ ] Rolling `beta-latest` already produced in Phase 1.
|
||||
- [ ] PR `bmorcelli/M5Stack-json-fw`: add a `wadamesh_beta.py` (mirror `bruce_beta.py`, track `beta-latest`) + a `t-deck.json` (Beta) entry.
|
||||
- [ ] Result: T-Deck Launcher shows **WadaMesh** + **WadaMesh (Beta)**, both auto-synced.
|
||||
- Until then: T-Deck Launcher beta testers side-load the beta `.bin` manually (it is a T-Deck-only gap; V4 uses the flasher).
|
||||
|
||||
## Where current work lands
|
||||
|
||||
- The uncommitted post-beta_20 work (auto-advert GUI, message-flash, docs, RAM pass)
|
||||
plus the hardened **MQTT-privacy** branch → commit to `main` → cut as **beta_21**,
|
||||
the first TEST release in this model. Community tests it; promote when proven.
|
||||
- This is exactly the model that handles PR #71: land the reviewed/hardened pieces on
|
||||
`main`, ship as a beta, promote later.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Locked:** `main` + `stable`; keep `beta_N` ids with a Stable/Beta channel label; Phase 1 now, Phase 2 in beta_21, Phase 3 as a fast-follow.
|
||||
- **Open (reversible):** whether stable later gets friendly `vX.Y` names (needs a rebuild-with-tag per promotion).
|
||||
@@ -0,0 +1,235 @@
|
||||
# Tanmatsu port — working tracker
|
||||
|
||||
Goal: run the **full** wadamesh UI + functionality on the **Tanmatsu** (Nicolai
|
||||
Electronics), kept as **one codebase** with the existing S3 boards so a UI change
|
||||
ships everywhere at once. This file is the running plan/status — update it as we go.
|
||||
|
||||
Status: **device IN HAND (2026-06-20); ENTIRE APP COMPILES + LINKS on P4** (`f04dba6`) · branch
|
||||
`tanmatsu-port` on `beta_9` · the **full shared src/ — MyMesh + the 28k-line LVGL UITask + all
|
||||
helpers + variant glue — compiles, and the project builds `application.bin`** · LVGL 8.4 + the
|
||||
core + ~10 Arduino libs all build · **next = the real `app_main` bring-up (the spike main still
|
||||
ships, so the bin isn't the real app yet), storage-on-FAT, the 800×480 layout, the bsp_input→LVGL
|
||||
indev, then on-device install via BadgeLink/AppFS.**
|
||||
|
||||
---
|
||||
|
||||
## Decision (architecture) — UPDATED 2026-06-20: wadamesh is an APP on the launcher
|
||||
|
||||
On the Tanmatsu **everything is an app** (badge.team OS + GUI launcher + **AppFS** app
|
||||
store). So wadamesh ships as a **launcher app, NOT a firmware that replaces the OS.**
|
||||
AppFS apps are **standard ESP-IDF app binaries** installed into an AppFS flash partition
|
||||
(via `appfs_add_file.py` / BadgeLink / the badge.team app store) and started from the
|
||||
launcher; a running app owns the screen + input via `badge-bsp` and returns to the
|
||||
launcher on exit (docs.tanmatsu.cloud → AppFS).
|
||||
|
||||
⇒ The Tanmatsu target is an **ESP-IDF app**, NOT the pioarduino/Arduino *standalone*
|
||||
flash (that would overwrite the launcher). The PlatformIO route in the old plan is
|
||||
**dropped**; the "IDF subproject" that was the fallback is now the **primary path** —
|
||||
because the whole app model is IDF-native.
|
||||
|
||||
We still keep **one repo, one `src/`**: the shared app (UITask, MyMesh, i18n, fonts…)
|
||||
compiles inside the IDF app via **arduino-esp32 as an ESP-IDF component**, alongside
|
||||
`badge-bsp` (display/keyboard/power), `tanmatsu-lora` (LoRa — bridge already written in
|
||||
`variants/tanmatsu/`) and `esp_wifi_remote` (C6 Wi-Fi/BLE). Our **LVGL 8.3** flushes to
|
||||
the badge-bsp `esp_lcd` panel (not PAX; not the LVGL-9.3 `badge-bsp-lvgl`). **Distribution
|
||||
= the badge.team app store** — the Tanmatsu analog of LauncherHub. `boards/tanmatsu.json`
|
||||
+ the pioarduino env in the old plan are no longer the build; the LoRa bridge + variant
|
||||
wiring carry over.
|
||||
|
||||
## Compile spike — RESULTS (2026-06-20) ✅ arduino-as-component PROVEN on P4
|
||||
|
||||
IDF-app build lives in **`tanmatsu/`** (badge.team Makefile + project-local IDF **v5.5.1**
|
||||
in `tanmatsu/esp-idf`, gitignored). Build via **`./tanmatsu/build.sh <target>`** (wraps
|
||||
`idf.py -DDEVICE=tanmatsu -DSDKCONFIG_DEFAULTS="sdkconfigs/general;sdkconfigs/tanmatsu;sdkconfigs/wadamesh" -DIDF_TARGET=esp32p4`).
|
||||
Toolchain: PlatformIO already had the riscv32-esp P4 toolchain; IDF cloned via the Makefile
|
||||
+ `idf_tools.py install cmake ninja` (the targeted `install.sh esp32p4` skips those).
|
||||
|
||||
Proven end-to-end:
|
||||
- ✅ Stock badge.team template builds for P4 (baseline) — toolchain + component manager OK.
|
||||
- ✅ **arduino-esp32 3.3.10** compiles + links **alongside badge-bsp** (241 arduino objs).
|
||||
esp32p4 first-class in arduino since 3.1.3. ⇒ the "one shared `src/` via arduino-on-P4"
|
||||
architecture is **validated** — this was the make-or-break risk.
|
||||
|
||||
**Fixes (all in `tanmatsu/sdkconfigs/wadamesh`, our overlay — badge.team files kept pristine):**
|
||||
1. `CONFIG_FREERTOS_HZ=1000` — arduino-esp32 hard-requires it (badge.team defaults 100).
|
||||
2. `CONFIG_BT_ENABLED=n` (+ `BT_NIMBLE_ENABLED=n`, `ESP_HOSTED_ENABLE_BT_NIMBLE=n`) — arduino's
|
||||
bundled BLE won't compile vs IDF 5.5.1 NimBLE (`ble_gap_read_local_irk` renamed). P4 has no
|
||||
native BLE + we use the separate NimBLE-Arduino lib, so BT-off compiles arduino
|
||||
BLE/SimpleBLE/BluetoothSerial out via their `#if` guards. **Revisit hosted BLE (phone
|
||||
companion over the C6) later.** NB: arduino's `CONFIG_ARDUINO_SELECTIVE_COMPILATION` does
|
||||
NOT filter in this setup (verified) — use feature disables, not the selective knob.
|
||||
|
||||
**Gotchas found (carry into the real src/ wiring):**
|
||||
- **badge-bsp headers have NO `extern "C"` guards** → wrap every `#include "bsp/*.h"` in
|
||||
`extern "C" { }` from C++ or the bsp symbols mangle and fail to link.
|
||||
- **Two esp-hosted stacks collide:** arduino + `espressif/esp_wifi_remote` pull
|
||||
`espressif/esp_hosted`, whose whole-archive C6 SDIO driver duplicates badge.team's
|
||||
`nicolaielectronics/esp-hosted-tanmatsu` → "multiple definition". **Fix:
|
||||
`tanmatsu/components/esp_hosted/` (empty forwarder, `REQUIRES esp-hosted-tanmatsu`) shadows
|
||||
the registry component by name** (local components win) → one esp-hosted, the hardware fork,
|
||||
and arduino's `WiFi.h` rides on it. Runtime TODO: the `ESP_HOSTED_GPIO_SLAVE_RESET_SLAVE`
|
||||
config warning (C6 reset pin) — set it when WiFi is brought up live.
|
||||
|
||||
## Spike C1 — MeshCore core compiles on P4 (2026-06-20) ✅
|
||||
|
||||
The core is vendored by **`tanmatsu/fetch-deps.sh`** from `.pio/libdeps/.../MeshCore` into
|
||||
`components/meshcore/core/` (gitignored) under a committed `components/meshcore/CMakeLists.txt`.
|
||||
The Arduino libs it needs (Crypto, RTClib, Adafruit BusIO, Adafruit Unified Sensor, Melopero
|
||||
RV3028, CayenneLPP, ArduinoJson) all go in ONE committed **`ardlibs`** component (so cross-lib
|
||||
`#include`s resolve). Build: `./build.sh build`. What it took:
|
||||
- vendored lib dir names must be **space-free** (spaces break IDF's component-req matching → `BUG:`).
|
||||
- the core's bundled **C ed25519** (`lib/ed25519/`) must be added explicitly (PlatformIO
|
||||
auto-compiles it as a nested lib; IDF doesn't).
|
||||
- `ardlibs` needs `nvs_flash` (Crypto's RNG seeds from NVS); `meshcore` needs `nvs_flash`+`efuse`.
|
||||
- **strip IDF's `-Werror=all`** from `COMPILE_OPTIONS` — vendored code trips reorder/format/
|
||||
class-memaccess, and a bare `-Wno-error` does NOT cancel `-Werror=all` (done in main/CMakeLists).
|
||||
- exclude over-globbed files vs `build_as_lib.py`: non-ESP32 platform helpers (nrf52/stm32/rp2040),
|
||||
`helpers/radiolib/*` (→ LoRa bridge), the S3 display drivers, `ESPNOWRadio`, `SerialBLEInterface`
|
||||
(BT off), and the `MC_VENDORED_TOUCH_APP` files (our own src/ ships them).
|
||||
|
||||
**Remaining for a running app — ordered worklist (next session):**
|
||||
1. **Tanmatsu define set** (`main/CMakeLists.txt` build flags) — a *curated subset* of the S3 touch
|
||||
`build_flags`: KEEP `ESP32_PLATFORM`, `MC_VENDORED_TOUCH_APP`, `UI_LVGL=1`, `HAS_TOUCH_UI=1`,
|
||||
`MULTI_TRANSPORT_COMPANION`, `LORA_FREQ/BW/SF`, `LV_CONF_PATH=lv_conf.h`+`LV_CONF_INCLUDE_SIMPLE=1`
|
||||
+`LV_INDEV_DEF_SCROLL_THROW=7`, `MAX_CONTACTS`, `ENABLE_PRIVATE_KEY_*`, `ENV_INCLUDE_GPS`,
|
||||
`-I src -I src/ui-touch -I include`. DROP every S3-board define (`HELTEC_LORA_V4*`, `USE_SX1262`,
|
||||
`RADIO_CLASS`/`WRAPPER_CLASS`, all `P_LORA_*`/`PIN_*`/TFT/CHSC6x, `ST7789_*`, `RADIOLIB_*`). Give
|
||||
the Tanmatsu its own board/display identity (badge-bsp, NOT `DISPLAY_CLASS=ST7789`).
|
||||
2. **`wadamesh_app` component** — glob `src/` MINUS `main.cpp` (own app_main) + the S3 input helpers
|
||||
(`HeltecV4CapTouch`, `TDeckKeyboard/Touch/Trackball` → Tanmatsu input is `bsp_input`); add
|
||||
`ui-touch/` once LVGL is in.
|
||||
3. **LVGL component** — vendor lvgl 8.3 + the repo `lv_conf.h`.
|
||||
4. **Transports** — re-add AsyncTCP+ESPAsyncWebServer with a WDT-config fix (`CONFIG_ESP_TASK_WDT_*`)
|
||||
or the ESP32Async fork; OR ship companion over USB-serial first (off the critical path).
|
||||
5. **`variants/tanmatsu/` glue** — `TanmatsuBoard` (board API over bsp/power), the LVGL→badge-bsp
|
||||
display driver (`flush_cb`→`bsp_display_blit`, 800×480 RGB565), a `bsp_input`→`lv_indev`
|
||||
keyboard/pointer, `radio_init`→the `TanmatsuLoraRadio` bridge, storage→the FAT `locfd` partition.
|
||||
6. **`app_main`** — replicate setup()/loop(): board.begin → radio_init → storage → the_mesh.begin →
|
||||
ui_task.begin(disp) → loop{ui_task.loop; the_mesh.loop}.
|
||||
7. **Install** — `make build` → `application.bin` → BadgeLink/AppFS → the badge.team app store.
|
||||
|
||||
Biggest chunks: UITask (28k lines) vs the Tanmatsu defines + LVGL, and the 800×480 display flush
|
||||
(768 KB/frame → PSRAM). All methodical integration on the proven foundation.
|
||||
|
||||
Dep status: vendored + compiling = MeshCore core, ed25519, Crypto, RTClib, Adafruit BusIO, Adafruit
|
||||
Unified Sensor, Melopero RV3028, CayenneLPP, ArduinoJson, MicroNMEA. Still to add = LVGL, AsyncTCP +
|
||||
ESPAsyncWebServer (WDT fix), and (only if telemetry wanted) the Adafruit sensor libs.
|
||||
|
||||
## Why this is feasible (verified 2026-06-18)
|
||||
|
||||
- **Arduino-ESP32 supports the ESP32-P4** — core **3.3.10** (2026-06-05) has P4
|
||||
boards incl. MIPI-display variants (`ESP32P4_4DS_MIPI`).
|
||||
- **PlatformIO can target it** via the **pioarduino** platform (rel `55.03.39`),
|
||||
whose board list already includes **`m5stack-tab5-p4`** — a P4 + MIPI display +
|
||||
C6-coprocessor-radio handheld, i.e. a structural near-twin of the Tanmatsu. Crib
|
||||
display/WiFi bring-up from that board.
|
||||
- **Radio maps cleanly:** `mesh::Radio` (7 pure virtuals) ↔ `tanmatsu-lora`'s
|
||||
`lora.h` (raw 256-byte tx/rx, settable freq/bw/sf/cr/sync/preamble/power,
|
||||
RSSI/SNR). Same SX1262/1268 family the S3 boards use. Bridge written (below).
|
||||
- **No driver writing:** display/keyboard/power/wifi/lora all exist as IDF
|
||||
components (`badge-bsp`, `tanmatsu-wifi`, `tanmatsu-lora`).
|
||||
|
||||
## Hardware / platform facts
|
||||
|
||||
| | | (confirmed from docs.tanmatsu.cloud + hackster/CNX) |
|
||||
|---|---|---|
|
||||
| Main SoC | ESP32-**P4** dual-core RISC-V | **@ 400 MHz, 32 MB PSRAM, 16 MB flash**, microSD (SDIO) |
|
||||
| Display | **3.97" MIPI-DSI, 800×480, RGB565** | `esp_lcd` panel from `badge-bsp`; we flush **LVGL 8.3** to it (not PAX) |
|
||||
| Input | **69-key QWERTY** + 6 fn keys | events via `bsp_input_get_queue()` |
|
||||
| Coprocessors | **WCH CH32V203** (keyboard + power) · **ESP32-C6** (WiFi/BLE/802.15.4 via `tanmatsu-wifi`) · **SX1262** LoRa 433/868-915 (via `tanmatsu-lora`, "remote") | the P4 has no native radio |
|
||||
| Native SDK | ESP-IDF ≥ 5.5, CMake, C, PAX graphics | template: `Nicolai-Electronics/tanmatsu-template` |
|
||||
|
||||
## Research findings (2026-06-18, no hardware)
|
||||
|
||||
**Radio bridge — fully validated against `tanmatsu-lora/lora.c`; the code was already correct.**
|
||||
- `lora_packet_stats_t = {rssi_pkt_raw u8, snr_pkt_raw i8, signal_rssi_pkt_raw u8}`; `rssi_dbm = -rssi_pkt_raw/2`, `snr_db = snr_pkt_raw/4`. ✓ matches the bridge.
|
||||
- `lora_send_packet()` is **blocking** (waits ≤2 s on the transaction semaphore) → `isSendComplete()` latch is correct. ✓
|
||||
- `config.bandwidth` = kHz, valid {7,10,15,20,31,41,62,125,250,500} (62.5→62); `config.coding_rate` = 5..8 → 4/5..4/8. `setParams()` is correct. ✓
|
||||
- Only remaining radio tuning (small, MeshCore-side): `sync_word` / `preamble_length` / `power` to match `RadioLibWrappers.cpp` so it interoperates with the existing mesh.
|
||||
|
||||
**Display/input — DECISION: keep wadamesh's LVGL 8.3 and write our own flush; do NOT adopt `badge-bsp-lvgl`.**
|
||||
- `badge-bsp` exposes the screen as an `esp_lcd` panel (`bsp_display_get_panel` / `_get_panel_io` / `_get_parameters` → h_res/v_res/color_format) and the keyboard as a FreeRTOS event queue (`bsp_input_get_queue`). Reference wiring: `badgeteam/konsool-template-lvgl`.
|
||||
- BUT the badge LVGL component (`badgeteam/esp32-component-badge-bsp-lvgl`) targets **LVGL 9.3**, and wadamesh's UITask is **LVGL 8.3**. Adopting it forces an 8→9 migration of ~30k lines — not worth it. So: bring up our own LVGL 8.3 (esp_lvgl_port supports 8.x, or a manual `flush_cb`) flushing to the badge-bsp `esp_lcd` panel, + an `lv_indev` that reads the `bsp_input` queue.
|
||||
- **UI layout target: 800×480 landscape** — make UITask responsive for it (it already branches on `hor_res`).
|
||||
|
||||
**Repos:** `badgeteam/esp32-component-badge-bsp` (BSP) · `badgeteam/konsool-template-lvgl` (LVGL-on-bsp reference) · `Nicolai-Electronics/esp32-component-tanmatsu-lora` (lora.c).
|
||||
|
||||
## Plan / status
|
||||
|
||||
- [x] Confirm framework gap is bridgeable (Arduino-P4 + pioarduino + Tab5 precedent)
|
||||
- [x] Map `mesh::Radio` ↔ `lora.h`; write `TanmatsuLoraRadio` (full drop-in for `radio_driver`: setParams/setTxPower/setRxBoostedGainMode/idle/getPacketsRecvErrors/RSSI/SNR)
|
||||
- [x] `idf_component.yml` for the BSP/wifi/lora components
|
||||
- [x] `boards/tanmatsu.json` (based on pioarduino `m5stack-tab5-p4`)
|
||||
- [x] `variants/tanmatsu/{target.h,target.cpp,TanmatsuBoard.h}` — wires `radio_driver`=bridge, `board`, `radio_init()`, `radio_new_identity()` (StdRNG)
|
||||
- [ ] **Compile spike** — get `src/` to build for P4 under pioarduino (no HW needed; see below)
|
||||
- [ ] `[env:tanmatsu_companion_radio_touch]` in `platformio.ini` — port the shared touch flags, drop S3 LoRa pins; **resolve the RadioLib build issue (next item)**
|
||||
- [x] RadioLib build question — RESOLVED: keep RadioLib in `lib_deps` + the same `RADIOLIB_*` flags as the S3 envs. `RadioLibWrappers.cpp` still compiles, but since the Tanmatsu `target.cpp` never instantiates `WRAPPER_CLASS`/`RADIO_CLASS`, the unused code is linker-stripped. Net: the env mirrors the T-Deck's RadioLib deps/flags; only the `radio_driver` *type* differs (set in `variants/tanmatsu/target.h`). No `build_src_filter` gymnastics needed.
|
||||
- [ ] Wire the IDF components into the pio build (component manager) ⚠️ main risk
|
||||
- [ ] **Headless bring-up:** call `radio_init()` → `TanmatsuLoraRadio` joins the mesh (serial log)
|
||||
- [ ] WiFi/BLE via `esp_wifi_remote`/`tanmatsu-wifi` (likely close to free in Arduino)
|
||||
- [ ] LVGL display driver: flush → `bsp_display_blit`; LVGL indev ← `bsp/input` (keyboard)
|
||||
- [ ] Make UITask responsive for the larger screen (it already branches on `hor_res`)
|
||||
- [ ] Tune the `TanmatsuLoraRadio` TODO(device) items
|
||||
- [ ] Release path (separate bin; not covered by the `wadamesh-release` skill yet)
|
||||
|
||||
## What's done this session (no hardware)
|
||||
|
||||
A structurally-complete variant skeleton — the radio + board wiring is real (mirrors
|
||||
the T-Deck variant); display/input/wifi glue + the env are the device/spike work left.
|
||||
|
||||
- `variants/tanmatsu/TanmatsuLoraRadio.{h,cpp}` — the LoRa bridge, real code against
|
||||
the actual `lora.h` v0.3.0 API + `mesh::Radio` contract, reusing MeshCore's own
|
||||
airtime/packetScore math. A full drop-in for `radio_driver` (every method the app
|
||||
calls in `MyMesh.cpp`). Compiles-ready *pending* the `TODO(device)` items.
|
||||
- `variants/tanmatsu/target.{h,cpp}` — wires `radio_driver` (the bridge), `board`,
|
||||
`radio_init()` (→ `lora_init_remote`), `radio_new_identity()` (seeded via StdRNG).
|
||||
- `variants/tanmatsu/TanmatsuBoard.h` — `: public ESP32Board`; battery/power via
|
||||
badge-bsp left as TODO(device).
|
||||
- `variants/tanmatsu/idf_component.yml` — the managed-component deps.
|
||||
- `boards/tanmatsu.json` — P4/16MB/PSRAM board def (based on `m5stack-tab5-p4`;
|
||||
confirm flash size + f_cpu on the real unit).
|
||||
- This tracker + the `tanmatsu-port` memory.
|
||||
|
||||
### `TanmatsuLoraRadio` — TODO(device) (confirm/tune on hardware)
|
||||
- `lora_packet_stats_t` field names + raw→real RSSI/SNR conversion (assumed `-raw/2`, `raw/4`).
|
||||
- `bandwidth` units (assumed kHz) and `coding_rate` encoding (assumed 4/x, x=5..8).
|
||||
- `sync_word` / `preamble_length` — match `RadioLibWrappers.cpp` so it interoperates with the existing mesh.
|
||||
- Whether `lora_send_packet()` is blocking (assumed yes → `isSendComplete()` latch is fine).
|
||||
- Where the variant creates the radio object + calls `begin()`/`setParams()` (mirror the S3 radio glue in the variant target).
|
||||
|
||||
## Compile spike (run first, needs only the toolchain)
|
||||
|
||||
```bash
|
||||
# add the env (below) to platformio.ini + a stub boards/tanmatsu.json, then:
|
||||
PIO=/Users/kaj/Library/Python/3.9/bin/pio
|
||||
$PIO run -e tanmatsu_companion_radio_touch # expect: platform/toolchain resolves, src/ starts compiling
|
||||
```
|
||||
Outcome tells us: does pioarduino-P4 + Arduino build the shared `src/`? Compiles →
|
||||
proceed with display/radio glue. Fails on the IDF-component wiring → that's the known
|
||||
risk; fall back to the IDF-subproject layout.
|
||||
|
||||
### env block to add to `platformio.ini` (draft)
|
||||
```ini
|
||||
[env:tanmatsu_companion_radio_touch]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.39/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = tanmatsu ; boards/tanmatsu.json — model on pioarduino m5stack-tab5-p4
|
||||
build_flags =
|
||||
${env.build_flags} ; reuse the shared touch flags
|
||||
-D HAS_TANMATSU=1
|
||||
-I variants/tanmatsu
|
||||
; board_build.partitions = variants/tanmatsu/partitions_tanmatsu.csv ; TODO
|
||||
; the BSP/wifi/lora IDF components are pulled via variants/tanmatsu/idf_component.yml
|
||||
```
|
||||
|
||||
## Risks / open questions
|
||||
1. **IDF components inside PlatformIO** (`badge-bsp`/`tanmatsu-lora`) — the fiddliest part; pioarduino supports the component manager but expect friction. **Still the #1 risk.**
|
||||
2. **Display = our own LVGL 8.3 flush onto the badge-bsp `esp_lcd` panel** (decided; don't use the LVGL-9.3 `badge-bsp-lvgl`). Work = the flush_cb + the `bsp_input`→`lv_indev` glue + an 800×480 layout pass on UITask. Bounded now that the BSP API + konsool reference are known.
|
||||
3. **WiFi/BLE via the C6** (`esp_wifi_remote` + `esp_hosted` over SDIO). Arduino `WiFi.h` on the P4 *is* backed by `esp_wifi_remote` in arduino-esp32 3.x, so wadamesh's WiFi (tile fetch, setup wizard, network tab) can likely run **mostly unchanged** — but it needs the C6 on the right `esp_hosted` slave firmware + the components wired in, and it's a **known-finicky area** in practice (cf. esphome#10956). The template uses IDF wifi helpers (`wifi_remote.h`), not Arduino `WiFi.h`, so confirm Arduino WiFi coexists with `tanmatsu-wifi`'s C6 bring-up during the spike. A real task + risk, not free — but not a rewrite.
|
||||
4. **Heap/PSRAM** — fine: **32 MB PSRAM**, and an 800×480 RGB565 buffer is only ~768 KB. Lots of headroom vs the S3 boards.
|
||||
|
||||
## References
|
||||
- Template: https://github.com/Nicolai-Electronics/tanmatsu-template (ESP-IDF, P4, PAX, badge-bsp)
|
||||
- LoRa component: https://github.com/Nicolai-Electronics/esp32-component-tanmatsu-lora (`include/lora.h`, v0.3.0)
|
||||
- Precedent board: pioarduino `m5stack-tab5-p4` (P4 + MIPI + C6)
|
||||
- `mesh::Radio` base: monorepo `src/Dispatcher.h`; example backend `src/helpers/esp32/ESPNOWRadio.h`
|
||||
@@ -0,0 +1,996 @@
|
||||
# T-LoRa Pager port — working tracker
|
||||
|
||||
Goal: run the **full** wadamesh UI + functionality on the **LilyGo T-LoRa Pager**
|
||||
(ESP32-S3, **LR1121** radio variant first), kept as **one codebase** with the
|
||||
existing boards so a UI change ships everywhere at once. This file is the running
|
||||
plan/status — update it as we go.
|
||||
|
||||
Status: **M7's UI portion DONE — boot logo, full LVGL UI, QWERTY
|
||||
keyboard nav and rotary encoder all VERIFIED WORKING on real hardware
|
||||
(2026-07-07)**, after root-causing the intermittent black screen to the ST7796
|
||||
panel's hardware-reset line floating: it's wired to **XL9555 channel 6**, which
|
||||
both the LilyGoLib doc's channel table and the canonical arduino-esp32 pins
|
||||
header omit (full war story: `TLORA_PAGER_M7_HW_DEBUG_LOG.md`, session 2 —
|
||||
including why session 1's radio/display "SPI race" theory was disproven from
|
||||
TFT_eSPI source). Hardware-comms gates split out to **Milestone 7b**: radio
|
||||
vs. a live mesh node is now **DONE** (adverts/DMs confirmed reliable both
|
||||
directions as of 2026-07-11, after the TX-power fix — risk 10; still watch
|
||||
upstream LR1121 ACK issue #1376 for anything beyond basic reliability), **SD
|
||||
storage is DONE** (mount/browse/read/write/delete/insert-remove all
|
||||
hardware-verified — risk 1h), and USB companion device-profile frame
|
||||
remains untested — see ⑦b for the current breakdown. **Milestone 8
|
||||
(on-device UI pass) is now active**: fixing weird UI/keyboard behaviors found
|
||||
by manual testing on the device, human supplying photos/repro steps.
|
||||
Notification sound (ES8311 codec + NS4150B amp) landed in **Milestone 8b** —
|
||||
hardware-verified 2026-07-08 (commit `e682fbc`). **M8 is still active**
|
||||
(on-device UI pass); besides the nav-coverage/222-px audit that's its actual
|
||||
stated scope (still not started, see risk 8 — this is the single biggest
|
||||
remaining item), several concrete bugs surfaced and got fixed this
|
||||
milestone: the radio-settings blur-autosave lag (risk 12), the encoder not
|
||||
scrolling open dropdowns (fixed 2026-07-08, commit `4bbfcb5`), the keyboard
|
||||
backlight never being wired up (risk 9, fixed 2026-07-10), and microSD file
|
||||
manager + WAV-picker support (risk 1h, fixed 2026-07-10, scoped — DataStore
|
||||
SD routing/chat-history/tiles/wallpaper/backup remain deferred). An
|
||||
unread-message LED notification was investigated and explicitly decided
|
||||
against (risk 13) — this board has no onboard LED at all. Risk 11
|
||||
(keyboard/encoder-driven scroll not repainting) was **ACCEPTED as
|
||||
workaround-final 2026-07-13** — the Alt+turn free-scroll gesture is the
|
||||
permanent, intended fix for this hardware, root cause not being pursued
|
||||
further absent new evidence (see risk 11). **Milestone 10 (SX1262-variant
|
||||
env) is DONE and hardware-verified** (2026-07-13, `tlora_pager_sx1262_
|
||||
companion_radio_touch` — see worklist ⑩): a real SX1262 unit completed
|
||||
setup and confirmed bidirectional live-mesh messaging (public, a custom `#`
|
||||
channel, and DM). A cluster of keyboard/encoder input-ergonomics fixes
|
||||
landed 2026-07-08 through 2026-07-14 — Enter/Backspace chat-bubble parity,
|
||||
the Shift/Caps rework, the accent-variant and @-mention popups both made
|
||||
encoder-navigable, WASD map panning + Q/E slider nudging, the Fn+Shift
|
||||
chord's conditional Caps-Lock-vs-Home-jump behavior, and app-drawer tiles'
|
||||
focus highlight matching their touch-press accent tint — all hardware-
|
||||
verified; see the dated paragraphs below and the UI-changes inventory table
|
||||
for specifics. **M8's own stated scope (the 222-px vertical-layout audit +
|
||||
full focus-nav-coverage pass) still has not been started** — everything
|
||||
landed in M8 so far has been reactive bug-fixing from user-reported issues,
|
||||
not the systematic audit itself (risk 8) — this remains the single biggest
|
||||
open item in the port.
|
||||
|
||||
**M8 session 2026-07-07 findings** (radio + nav, all committed:
|
||||
`b3c6388`/`df7f9bc`):
|
||||
- **TX power was silently capped at a conservative 10dBm with no way to raise
|
||||
it** — unlike the T-Deck (boots straight at 22dBm). Found while
|
||||
investigating a real-mesh DM test: adverts/DMs sent FROM the pager were
|
||||
unreliable while inbound reception was fine (the far node's own TX was
|
||||
unaffected) — classic asymmetric-range signature. Fixed to
|
||||
`LORA_TX_POWER=22`, confirmed as this chip's real sub-GHz HP-PA ceiling via
|
||||
RadioLib's `LR1120::checkOutputPower()` (LR1121 inherits it: HP PA
|
||||
auto-selects above 14dBm, valid range -9..22 below 1GHz) and trail-mate's
|
||||
own `TRAIL_MATE_LORA_TX_POWER_MAX_DBM=22` for this exact board. Existing
|
||||
saved prefs on an already-flashed device do NOT pick up a new compiled
|
||||
default — only a fresh/factory-reset device does; raise it manually in
|
||||
Settings → Radio & Mesh to test on a unit that predates this fix.
|
||||
- **Risk 1g (RX-boosted-gain never applied for LR1121) fixed** — see risk
|
||||
entry below.
|
||||
- **Encoder direction was inverted** — confirmed on hardware, not a
|
||||
guess: turning the way a user expects to move forward/down moved
|
||||
focus/scroll backward/up. Fixed in `PagerEncoder.cpp`'s
|
||||
`pagerEncoderReadDelta()` (negated once at the source, so every consumer
|
||||
inherits the fix) rather than in UI code.
|
||||
- **Found a real, still-partially-open rendering bug**: keyboard/encoder-
|
||||
driven `lv_obj_scroll_to_view(f, LV_ANIM_OFF)` (in `navFocusCb`) does NOT
|
||||
reliably repaint the newly-scrolled region on this board — confirmed via
|
||||
hardware logging that focus and DEFOCUSED save side-effects (blur-to-save
|
||||
toasts) correctly reach fields far down a settings page, but the glass
|
||||
keeps showing the OLD scroll position throughout. Small new overlays
|
||||
(the toast itself) still paint fine, since those are fresh objects, not a
|
||||
repaint of already-rendered scrolled content. Touch-drag scrolling on the
|
||||
other boards never surfaced this because LVGL's drag/animation handler
|
||||
re-invalidates every animation tick, papering over the same gap that a
|
||||
single one-shot `LV_ANIM_OFF` scroll doesn't cover. **Root cause NOT fixed
|
||||
yet** — an attempted fix (force `lv_obj_invalidate()` on the scrolled
|
||||
ancestor after `scroll_to_view`) made things WORSE (very slow, ~5 presses
|
||||
per visible update) without actually fixing the viewport scroll, and was
|
||||
reverted. **Workaround shipped instead**: holding the pager's orange Alt
|
||||
key + turning the encoder now free-scrolls the page via
|
||||
`navScrollFocused()`'s ANIMATED `lv_obj_scroll_by` (which DOES repaint
|
||||
correctly, consistent with the animation-tick theory above), then snaps
|
||||
focus to whatever's nearest the top of the now-visible viewport
|
||||
(`navRefocusFirstVisible()`). This also fixed a pre-existing bug where
|
||||
Alt+turn always jumped main tabs even from inside a settings sheet/chat —
|
||||
now scoped to the main-tab level only (`navOnMainPage()`). Plain
|
||||
NEXT/PREV-driven `scroll_to_view` on a tall settings page is still broken;
|
||||
picking that apart further (why does one-shot `LV_ANIM_OFF` fail to
|
||||
invalidate on THIS board specifically?) is unscheduled follow-up — likely
|
||||
worth checking whether `lv_timer_handler()`'s own per-pass duration
|
||||
(`[STALL] ui:lvgl`, separately observed at 400ms–1.6s on this board during
|
||||
this investigation) is a contributing/related factor, not just the
|
||||
scroll-invalidate call itself.
|
||||
|
||||
**M8 session 2026-07-08, chat-bubble Enter/Backspace ergonomics (commits
|
||||
`facbe49`/`bf5c682`):** a focused non-composer widget (e.g. a Settings row)
|
||||
didn't activate on Enter in one specific case — fixed. Also added, matching
|
||||
Tanmatsu's identical no-touch gestures: Enter on a focused chat message
|
||||
bubble opens the same Ack/Mention/Copy/Info/Block action menu a touch
|
||||
long-press opens elsewhere (`navEnterBubble()`, shared code — bubbles are
|
||||
the focusable leaves inside a chat's `msgs` container, identified by
|
||||
parent); Backspace jumps straight to the newest message when the "scroll to
|
||||
bottom" button is showing (this board has no touch to tap that floating
|
||||
button). The rotary encoder's own short click did NOT get the bubble-menu
|
||||
behavior in this pass — that gap wasn't closed until 2026-07-14 (below).
|
||||
|
||||
**M8 session 2026-07-13, Shift/Caps rework + accent-popup keyboard nav
|
||||
(commits `79df528`/`43f040e`), hardware-verified:** (1) The Shift/Caps key
|
||||
(`kCapsPos`, renamed `kShiftPos`) changed from trail-mate's original
|
||||
bare-press-toggle-Caps-Lock to conventional semantics — held alone it's a
|
||||
momentary Shift (uppercases while held, releases back to normal); held Alt
|
||||
(Fn) then a Shift press chords into a Caps Lock toggle. New `s_shift_held`
|
||||
static, same pattern as `s_backspace_held`/`s_space_held`. One documented
|
||||
edge case: Alt+Shift+O or Alt+Shift+L all three held phantom-ghosts a stray
|
||||
'q'/'a' (Alt and Shift share row2; col8 is 'o'/'l' on rows 0/1 — a
|
||||
diode-less-matrix 3-key rectangle, no software fix possible, harmless since
|
||||
it's not the intended hold-Alt-tap-Shift gesture). (2) The accent-variant
|
||||
popup (issue #22 — shows accented options when typing a vowel or
|
||||
`n c s y t z l r`) was completely unreachable on this no-touch board — its
|
||||
cells are `NAV_SKIP_FLAG`, excluded from the nav group by design (touch
|
||||
boards pick them by tap). Added a pager-only parallel nav path: Fn(Alt)+
|
||||
Space arms it, the rotary encoder walks the highlighted variant, the
|
||||
encoder's own click confirms (synthesizes `LV_EVENT_CLICKED` on the
|
||||
highlighted cell via `accentNavConfirm()`, reusing `accentBoxCellCb` rather
|
||||
than duplicating its logic), Backspace cancels without deleting the base
|
||||
letter. See worklist ⑤ for the full writeup, including the 2026-07-14
|
||||
follow-up that changed what the Alt+Shift chord actually *does*.
|
||||
|
||||
**Same session, Milestone 10 (SX1262-variant env) — DONE, hardware-verified
|
||||
(commit `4b0c2e9`):** a real SX1262 unit completed setup and confirmed
|
||||
bidirectional live-mesh messaging (public, a custom `#` channel, and DM).
|
||||
Full writeup, including the `target.h` missing-include bug found and fixed,
|
||||
is in worklist ⑩ and `TLORA_PAGER_PORT_MILESTONES.md`'s Milestone 10.
|
||||
|
||||
**Same session, WASD map panning + Q/E slider nudge (commit `9881bb0`),
|
||||
hardware-verified:** the Map tab's pan/drag needed a no-touch equivalent —
|
||||
W/A/S/D now pan north/west/south/east while the Map tab is active, reusing
|
||||
`mapNudge()` (previously Tanmatsu's Ctrl+Arrow-only helper, widened to
|
||||
`defined(HAS_TANMATSU) || defined(TLORA_PAGER)`). This freed up D from the
|
||||
existing slider-nudge binding (added 2026-07-08 for Control Center
|
||||
brightness / Settings sliders / the map zoom bar, since none of those can
|
||||
be dragged without touch or a trackball), which moved from D/F to Q/E so
|
||||
the two gestures never collide (e.g. the map zoom slider focused while
|
||||
WASD-panning).
|
||||
|
||||
**M8 session 2026-07-14, four more hardware-verified fixes:**
|
||||
1. **Encoder short click now opens the message action menu too** (commit
|
||||
`c4c28d4`) — the 2026-07-08 fix above only wired this into keyboard
|
||||
Enter; the encoder's short click still sent a plain `LV_KEY_ENTER` a
|
||||
bubble doesn't react to. `updatePagerEncoder()`'s short-click branch now
|
||||
calls `navEnterBubble()` first, mirroring `handleHwKey()` exactly.
|
||||
2. **Fn+Shift's effect now depends on UI state** (commit `b16206b`) — the
|
||||
chord used to toggle Caps Lock unconditionally, even with no text field
|
||||
focused to see the change reflected in, which was reported as surprising
|
||||
and purposeless outside of typing. `PagerKeyboard.cpp` no longer decides
|
||||
the effect (it has no UI visibility) — it only reports the chord via
|
||||
`pagerKeyboardConsumeAltShiftChord()`; `UITask.cpp`'s new
|
||||
`updatePagerAltShiftChord()` toggles Caps Lock ONLY while a text field is
|
||||
actually being edited (same "ta" derivation `handleHwKey()`'s TLORA_PAGER
|
||||
branch already uses to tell a bound-but-unfocused field apart from one
|
||||
actually being edited), and jumps straight to Home (`navGoToMainTab`)
|
||||
everywhere else.
|
||||
3. **@-mention contact picker made encoder-navigable** (commit `57802eb`) —
|
||||
the same unreachable-without-touch problem the accent box had, fixed the
|
||||
same way (`mentionNavRestyle()`/`mentionNavConfirm()` mirror
|
||||
`accentNavRestyle()`/`accentNavConfirm()` almost exactly), with one
|
||||
deliberate difference: it grabs the encoder the INSTANT the list appears
|
||||
rather than requiring an explicit Fn+Space arm, since — unlike the accent
|
||||
box, which pops up after nearly every letter typed and must not steal
|
||||
focus from ongoing typing — the mention list only shows once the user
|
||||
has deliberately typed "@partial" looking for someone to pick, so there's
|
||||
no ongoing typing to protect.
|
||||
4. **App-drawer tiles' keyboard/encoder focus highlight normalized to their
|
||||
own accent tint** (commit `6073b6f`) — tiles previously fell through to
|
||||
`navFocusCb`'s generic white reverse-video fill when focused via
|
||||
keyboard/encoder nav (a solid white block over a small rounded icon chip
|
||||
read as a glitch), instead of the accent-tinted `LV_STATE_PRESSED` look
|
||||
they already show on a touch press. New `NAV_ACCENTFOCUS_FLAG`
|
||||
(`LV_OBJ_FLAG_USER_3`) lets `navFocusCb` special-case flagged objects
|
||||
(mirrors the existing switch/slider special-case) to use that same
|
||||
accent tint for both states, on every board — not just the pager.
|
||||
|
||||
All three envs build green — Milestone ⑥ (UITask wiring)
|
||||
landed; the pager is a first-class, non-touch UI target. Board JSON verified
|
||||
(`boards/lilygo-t-lora-pager.json`). Milestones ①–⑥ (variant skeleton, LR1121
|
||||
radio glue, ST7796 display + AW9364 backlight, platformio.ini env, TCA8418
|
||||
keyboard + rotary encoder drivers, UITask wiring) landed.
|
||||
`[env:tlora_pager_lr1121_companion_radio_touch]` compiles clean: **RAM 23.8%
|
||||
(78088/327680 B), Flash 66.4% (2696465/4063232 B)** — both still comfortably
|
||||
lower than the two shipping boards (Heltec V4/T-Deck: RAM 25.3%, Flash 73.5%,
|
||||
unchanged — confirmed byte-identical to pre-M6, zero regression). M1–M5's
|
||||
build-blocking fixes (variant/pins split, radio-wrapper include paths, the
|
||||
minimal display-typedef addition) are recorded in Decisions ②/⑧ and worklist
|
||||
③/⑤ below; M6's own findings follow.
|
||||
|
||||
**M6 findings** — the UI-inventory table + worklist ⑥ below have full detail;
|
||||
the highlights:
|
||||
- **Two capability flags I initially set were premature and had to be walked
|
||||
back**: `CAP_SD`/`CAP_FILESYSTEM` looked right from the hardware table (the
|
||||
pager does have a microSD slot), but the actual mount code
|
||||
(`fmSdTryMount()`, `#include <SD.h>`) is still hardcoded to
|
||||
`HAS_TDECK_GT911` specifically — `device_caps.h`'s own `CAP_SD` flag was
|
||||
never actually wired to it. Turning it on for the pager just produced
|
||||
`SD`/`CARD_NONE`/`fmSdTryMount` "not declared" errors, not real SD support.
|
||||
Set back to 0 — a real mount needs its own pager-specific wiring (CS 21, a
|
||||
non-T-Deck shared-SPI helper), unscheduled follow-up, not this milestone.
|
||||
- **`navMaybeRebuild()` — the function that actually populates the LVGL focus
|
||||
group every screen — was unreachable for the pager** in my first pass: it's
|
||||
only called under `#if defined(HAS_TANMATSU) ... #elif CAP_TRACKBALL ...`,
|
||||
neither of which the pager matches. Without a fix here the KEYPAD indev
|
||||
would have an eternally-empty focus group — nothing focusable, navigation
|
||||
completely dead, despite the indev registration itself being correct. Added
|
||||
a `#elif defined(TLORA_PAGER)` arm.
|
||||
- **A self-inflicted `#elif` scoping mistake**: `handleHwKey()` (and several
|
||||
helpers it calls) live inside a large, multiply "paused and reopened"
|
||||
`#if defined(HAS_TDECK_KEYBOARD)` region. Widening the wrong reopen point
|
||||
first orphaned ~50 lines of genuinely T-Deck-specific code (keyboard
|
||||
backlight-mode timer, notify-flash, spacebar-lock countdown) into what was
|
||||
meant to be the pager's own simpler branch. Fixed by tracing each reopen's
|
||||
actual `#endif` (nesting depth, not just grep hits) before touching it, and
|
||||
keeping the T-Deck-only pieces under their own unwidened, more specific gate.
|
||||
- **`isDismissKey()`'s T-Deck logic doesn't transfer**: it treats the letters
|
||||
p/q/a as "dismiss popup" because the T-Deck's sparse keyboard has no
|
||||
dedicated Esc key. The pager's full QWERTY types those letters constantly —
|
||||
reusing that mapping would eat normal typing. Returns `false` for the pager
|
||||
instead; the rotary encoder's long-press already covers ESC.
|
||||
**Execution playbook: `TLORA_PAGER_PORT_MILESTONES.md`** — the worklist below,
|
||||
broken into agent-executable milestones with gates and per-file instructions.
|
||||
|
||||
---
|
||||
|
||||
## Why this port is cheaper than it looks
|
||||
|
||||
The scary part — "the device has no touchscreen" — is **already solved in this
|
||||
codebase**. UITask carries a complete non-touch navigation layer built for the
|
||||
Tanmatsu (keypad-only) and the T-Deck trackball D-pad mode:
|
||||
|
||||
- **Focus-group nav**: `s_nav_group` (`lv_group_t`), amber focus ring + scroll-into-view
|
||||
(`navFocusCb`), per-screen group rebuild (`navMaybeRebuild`), 2-D directional focus
|
||||
(`navMoveDir`), tab-bar handling (`navOnTabBar`/`navSwitchTab`) — `UITask.cpp` ~2138–3128.
|
||||
- **A KEYPAD indev + key FIFO**: `navFifoPush/Pop` feeds `LV_KEY_UP/DOWN/LEFT/RIGHT/
|
||||
NEXT/PREV/ENTER/ESC` into `tanmatsuKeypadRead` (`UITask.cpp` ~3000). The Tanmatsu
|
||||
registers ONLY this indev (no pointer) — `UITask.cpp` ~35706–35717. **That branch is
|
||||
the pager's template.**
|
||||
- **Physical-keyboard routing**: `handleHwKey()` (`UITask.cpp` ~27990) routes keys
|
||||
into the focused textarea (edit mode) or into nav (navigate mode, `s_nav_ta_editing`
|
||||
flag), with the on-screen LVGL keyboard suppressed — exactly how the T-Deck works
|
||||
today. The pager's QWERTY plugs into this unchanged.
|
||||
- **Rotary encoder**: no `LV_INDEV_TYPE_ENCODER` needed — encoder ticks map to
|
||||
`navFifoPush(LV_KEY_NEXT/PREV)` (focus walk), press → `ENTER`, long-press → `ESC`.
|
||||
Reuses everything above.
|
||||
|
||||
So the genuinely NEW work is: the **480×222 ST7796 display driver + layout pass**,
|
||||
the **TCA8418 keyboard driver** (raw matrix → chars, unlike the T-Deck's C3 which
|
||||
resolves ASCII for us), the **rotary driver**, the **LR1121 radio glue**, and a
|
||||
**board class** whose battery/power goes through I²C chips (fuel gauge + IO
|
||||
expander) instead of an ADC pin.
|
||||
|
||||
## Hardware / platform facts
|
||||
|
||||
Confirmed from the LilyGo product page, CNX-Software (2025-08-12), Meshtastic docs,
|
||||
and cross-checked against three working/authoritative sources: upstream
|
||||
`meshcore-dev/MeshCore`'s pager target (see caveat under Decision ②), the official
|
||||
[LilyGoLib hardware page](https://github.com/Xinyuan-LilyGO/LilyGoLib/blob/master/docs/hardware/lilygo-t-lora-pager.md),
|
||||
and `~/dev/trail-mate` (local project with a running LR1121 pager build — pin map
|
||||
below is from its `variants/lilygo_tlora_pager/pins_arduino.h`, which is
|
||||
byte-identical on every pin/channel to the canonical
|
||||
[`espressif/arduino-esp32` pins_arduino.h](https://github.com/espressif/arduino-esp32/blob/master/variants/lilygo_tlora_pager/pins_arduino.h)
|
||||
for this board). **Caveat**: the LilyGoLib page's own "Pins Map" table has
|
||||
internal inconsistencies for the XL9555 channel assignments (e.g. it lists
|
||||
keyboard-enable at ch10 and SD-detect/enable at ch12/ch14, which contradicts
|
||||
its own "PowerManage Channel" table on the same page *and* the canonical
|
||||
arduino-esp32 header). We treat the arduino-esp32 header + trail-mate (which
|
||||
agree with each other and with the "PowerManage Channel" table) as ground
|
||||
truth — that's what our own `TLoraPagerBoard.cpp` already uses.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| SoC | ESP32-**S3** @ 240 MHz, 16 MB flash (QIO), **8 MB QSPI PSRAM** (`memory_type: qio_qspi`) |
|
||||
| Display | 2.33" IPS **ST7796U**, 480×222 (221 PPI, 262K colors, 450 cd/m²), **480×222 landscape**, SPI, **no touch**. CS 38, DC 37, backlight 42 (AW9364 16-level stepped driver). **Hardware reset is XL9555 ch6, NOT unwired** — `TFT_RST=-1` only because it isn't an ESP GPIO; `TLoraPagerBoard::begin()` owns the reset pulse (LOW→50ms→HIGH, vendor sequence). Leaving ch6 floating = intermittent black screen, clean boot log (M7 root cause) |
|
||||
| Shared SPI bus | **SCK 35, MOSI 34, MISO 33** — display + LoRa + SD + ST25R3916 NFC all on it (like the T-Deck's 40/41/38 — solved pattern, CS discipline + SPI transactions) |
|
||||
| Radio | **LR1121** (sub-GHz 830–945 MHz + 2.4 GHz; we use sub-GHz only). CS 36, RST 47, BUSY 48, IRQ/DIO1 14. **Also sold with SX1262** (the LilyGoLib page documents the SX1262 retail SKU as primary) — same board/pins, different defines (cheap 2nd env later) |
|
||||
| Keyboard | Physical QWERTY via **TCA8418** I²C matrix controller (addr `0x34`), INT 6, backlight 46. Raw matrix events — keymap/shift/sym handled on our side |
|
||||
| Encoder | Rotary A 40, B 41, **press 7** |
|
||||
| Buttons | **BOOT = GPIO0** (usable as user button + sleep wake — matches both existing boards' `PIN_USER_BTN=0`). Physical power key is PMU QON, not a GPIO — can only wake the device (1s hold), never programmable |
|
||||
| Power | **BQ25896** charger PMU (addr `0x6B`) + **BQ27220 fuel gauge** (addr `0x55`, battery % / mV over I²C — NOT an ADC divider). Battery: 3.7 V / 1500 mAh (5.55 Wh). DeepSleep ≈530 µA, LightSleep ≈2.26 mA, Power-off ≈26 µA |
|
||||
| IO expander | **XL9555** (addr `0x20`): DRV_EN ch0, AMP_EN ch1, KB_RST ch2, LORA_EN ch3, GPS_EN ch4, NFC_EN ch5, **DISP_RST ch6 — the ST7796 panel's hardware reset** (missing from BOTH the LilyGoLib doc's channel table and arduino-esp32 master's pins header; confirmed from LilyGoLib's `LilyGo_LoRa_Pager.cpp` begin(), which pulses it LOW→50ms→HIGH before display init — leaving it floating was M7's intermittent-black-screen root cause, see `TLORA_PAGER_M7_HW_DEBUG_LOG.md` session 2), GPS_RST ch7, KB_EN ch8, GPIO_EN ch9, SD_DET ch10, SD_PULLEN ch11, SD_EN ch12 |
|
||||
| GPS | u-blox **MIA-M10Q**: TX 12, RX 4, PPS 13 |
|
||||
| I²C bus | SDA 3, SCL 2 — shared by TCA8418 (`0x34`), XL9555 (`0x20`), BQ25896 (`0x6B`), BQ27220 (`0x55`), PCF85063 RTC (`0x51`), BHI260AP IMU (`0x28`), DRV2605 haptics (`0x5A`), ES8311 codec (`0x18`) |
|
||||
| SD | microSD on the shared SPI bus, CS 21, card-detect via expander (ch10), max 32 GB, **FAT32 only** |
|
||||
| Audio | ES8311 codec (I²C `0x18`; I2S MCLK 10, SCK/BCK 11, WS 18, SDOUT 45, SDIN 17 — confirmed against arduino-esp32's canonical `variants/lilygo_tlora_pager/pins_arduino.h`, matching LilyGoLib's own `codec.setPins(I2S_MCLK, I2S_SCK, I2S_WS, I2S_SDOUT, I2S_SDIN)` call in `LilyGo_LoRa_Pager.cpp`) driving an **NS4150B** 3 W Class-D amp (enabled via expander AMP_EN ch1, toggled at runtime around playback — not the boot-time permanently-on drive; see Decision below) |
|
||||
| Misc | PCF85063A RTC (INT 1), BHI260AP IMU (INT 8), ST25R3916 NFC (unused — CS 39, INT 5, powered via expander NFC_EN ch5), USB VID/PID `0x303A:0x82D4` |
|
||||
|
||||
## Board JSON — VERIFIED ✅ (landed as `boards/lilygo-t-lora-pager.json`)
|
||||
|
||||
The JSON (Meshtastic-lineage; byte-identical to trail-mate's copy) is **correct for
|
||||
the LR1121 unit but radio-agnostic**: it describes only the S3 module (flash/PSRAM/
|
||||
USB id/CDC-on-boot), which every radio variant of the pager shares. The radio is
|
||||
selected by our build flags (`RADIO_CLASS`/`WRAPPER_CLASS` + pins), same as the
|
||||
existing boards. Notes:
|
||||
|
||||
- `partitions: app3M_fat9M_16MB.csv` is just the default — we override with our own
|
||||
OTA+tiles+spiffs csv via `board_build.partitions` (see worklist ①).
|
||||
- `variant: lilygo_tlora_pager` + `variants_dir: variants` → needs
|
||||
`variants/lilygo_tlora_pager/pins_arduino.h` (write our own; don't copy
|
||||
trail-mate's — it brands `USB_PRODUCT "TRAIL MATE"`).
|
||||
- `-DARDUINO_USB_MODE=1` (HW-CDC) is in the JSON's extra_flags. The T-Deck ships
|
||||
MODE=1 fine; the Heltec V4 regressed on it (large companion frames dropped —
|
||||
see the note in `platformio.ini`). **Verify the device-profile frame over USB
|
||||
companion early** (worklist ⑦); if it drops bytes, switch to TinyUSB CDC like
|
||||
the V4.
|
||||
|
||||
## Decisions (architecture)
|
||||
|
||||
1. **Normal PlatformIO env, T-Deck model** — NOT the Tanmatsu IDF-subproject route.
|
||||
The pager is a plain ESP32-S3 Arduino target; it slots into `platformio.ini`
|
||||
next to the existing two envs and into `release.sh`'s env list later.
|
||||
2. **LR1121 wrappers vendored in the variant dir — zero core-fork churn for
|
||||
bring-up.** The core fork (`core-v1.16.5`) only has `CustomLR1110*`.
|
||||
**Re-verified 2026-07-06: upstream `meshcore-dev/MeshCore`'s `main` branch does
|
||||
NOT currently have `CustomLR1121*` or a `variants/lilygo_tlora_pager/` dir**
|
||||
(checked `src/helpers/radiolib/` — only `CustomLR1110{,Wrapper}.h` exists there
|
||||
too; issue [meshcore-dev/MeshCore#861](https://github.com/meshcore-dev/MeshCore/issues/861)
|
||||
"Support for LR1121" is still open). Earlier research that assumed an
|
||||
upstream crib source was wrong or looked at a branch/fork that no longer
|
||||
exists — **re-check upstream at Milestone ② time**, but plan for having to
|
||||
author `CustomLR1121{,Wrapper}.h` ourselves by adapting the core fork's own
|
||||
`CustomLR1110{,Wrapper}.h` pair (same RadioLib `LR11x0` family — swap the
|
||||
base type from `LR1110` to `LR1121`), cross-checked against trail-mate's
|
||||
`initLoRa()` (which drives RadioLib's stock `LR1121` class directly, no
|
||||
custom wrapper) for the RF-switch table / `setTCXO` sequence. Since
|
||||
`CustomLR1121{,Wrapper}.h` only subclass RadioLib's `LR1121` and the core's
|
||||
`RadioLibWrapper` (both on the include path), they can live in
|
||||
`variants/lilygo_tlora_pager/` for now and move into the fork at the next
|
||||
`core-*` tag. Precedent: the Tanmatsu keeps its whole radio bridge in its
|
||||
variant dir.
|
||||
**Found at Milestone ④'s first real compile**: `CustomLR1121Wrapper.h`'s
|
||||
bare quoted `#include "RadioLibWrappers.h"`/`"LR11x0Reset.h"` (copied
|
||||
verbatim from `CustomLR1110Wrapper.h`'s shape) don't resolve — those work
|
||||
in the core fork only because the file sits in the *same*
|
||||
`src/helpers/radiolib/` directory as its targets (quote-include searches
|
||||
the including file's own directory first); ours lives in the variant dir
|
||||
instead. Fixed to angle-bracket `<helpers/radiolib/...>` includes, matching
|
||||
`target.h`'s already-correct pattern for the same headers.
|
||||
3. **LR1121 init is explicit** (no `std_init`): RF-switch table on **DIO5/DIO6**
|
||||
(`STBY {L,L} / RX {L,H} / TX {H,L} / TX_HP {H,L}`) + **`setTCXO(3.0f)`** —
|
||||
confirmed in both trail-mate (`boards/tlora_pager/src/tlora_pager_board.cpp`,
|
||||
`initLoRa()`) and upstream. Sync word / preamble / CR come from the same
|
||||
NodePrefs plumbing as the other boards so it interoperates with the mesh.
|
||||
4. **Display = new `ST7796LCDDisplay` app-side** (in `src/helpers/ui/`), implementing
|
||||
the core's `DisplayDriver` interface (`begin/width/height/startFrame/endFrame/
|
||||
setDisplayRotation/writePixelsRGB565`) — that's all the LVGL flush path uses.
|
||||
TFT_eSPI has `ST7796_DRIVER` (`USER_SETUP_LOADED` + `-D` pin set).
|
||||
**Correction, landed with Milestone ③**: "the Heltec V4 already builds on
|
||||
TFT_eSPI, so mirror that wiring" was wrong — `ST7789LCDDisplay` (Heltec
|
||||
V4's *and* T-Deck's display class, same file) is actually Adafruit_GFX/
|
||||
Adafruit_ST7789-based; Heltec V4's TFT_eSPI lib_dep/`-D` flags are
|
||||
vestigial (nothing else in the repo `#include`s `TFT_eSPI.h`). This makes
|
||||
`ST7796LCDDisplay` the first real TFT_eSPI consumer in this codebase —
|
||||
every method call was verified directly against the pinned
|
||||
`bodmer/TFT_eSPI @ ^2.5.43` source, not cribbed from the sibling class.
|
||||
**Also found and mitigated**: this panel's 222px glass is narrower than
|
||||
the ST7796 controller's 320px GRAM, requiring a 49px column/row offset
|
||||
that TFT_eSPI only applies automatically when `-D CGRAM_OFFSET=1` is set —
|
||||
missing from this decision's original flag list; a `#error` guard in
|
||||
`ST7796LCDDisplay.cpp` now catches the omission at M4 compile time (see
|
||||
worklist ③ for the full writeup). Backlight is the AW9364 (stepped pulse
|
||||
dimming), not a plain GPIO PWM — wrapped in `ST7796LCDDisplay` itself using
|
||||
`lewisxhe/SensorLib`'s `AW9364LedDriver` directly (a maintained library,
|
||||
already an M1-established dependency) rather than a hand-rolled driver.
|
||||
5. **Input = the Tanmatsu registration branch** (KEYPAD indev + `s_nav_group` only,
|
||||
no pointer indev), gated by a new device cap. Rotary → nav FIFO; TCA8418 →
|
||||
`handleHwKey()`. New pollable drivers in `src/helpers/input/` following the
|
||||
existing style (begin/poll/read API, no LVGL inside the driver).
|
||||
6. **Battery/power via libraries, not hand-rolled** (CONTRIBUTING rule): lewisxhe
|
||||
**SensorLib** covers BQ27220 (`GaugeBQ27220`), XL9555 (`ExtensionIOXL9555`),
|
||||
PCF85063, DRV2605; **XPowersLib** covers the BQ25896. `TLoraPagerBoard :
|
||||
public ESP32Board` overrides `getBattMilliVolts()` (gauge query),
|
||||
`getManufacturerName()`, power-rail bring-up in `begin()` (expander), and sleep.
|
||||
7. **One codebase**: all pager-specific UI behavior rides existing/new `CAP_*`
|
||||
flags in `src/ui-touch/device_caps.h` — no forked screens.
|
||||
8. **`variants/lilygo_tlora_pager/` app glue and the board's Arduino "variant"
|
||||
pin map had to split into two directories** — found only by actually
|
||||
compiling in Milestone ④, not anticipated by any earlier decision.
|
||||
PlatformIO's arduino-esp32 build script (`platformio-build.py`) always
|
||||
compiles every source file under `board_build.variants_dir/<board.variant>/`
|
||||
as a separate `FrameworkArduinoVariant` library, in a build context with
|
||||
none of our app's `lib_deps` include paths. T-Deck/Heltec V4 never hit this
|
||||
because their board JSONs point `"variant"` at a variant the *framework*
|
||||
already bundles (`esp32s3`, `heltec_v4`) — completely separate from our own
|
||||
`variants/lilygo_tdeck/`/`variants/heltec_v4/` app-glue directories, zero
|
||||
collision. The pager has no framework-bundled variant, so its board JSON
|
||||
was forced to set `"variants_dir": "variants"`, pointing PlatformIO's
|
||||
variant resolution at the exact same directory we'd used for
|
||||
`TLoraPagerBoard.*`/`target.*`/`CustomLR1121*` — so the framework tried to
|
||||
compile those too, and failed on missing `Wire.h`/`RadioLib.h`. Fixed by
|
||||
moving `pins_arduino.h` alone into a new `variants/lilygo_tlora_pager_pins/`
|
||||
folder and pointing the board JSON's `"variant"` there instead; our own
|
||||
`TLoraPagerBoard.*`/`target.*`/`CustomLR1121*` stay in
|
||||
`variants/lilygo_tlora_pager/`, reached only via our own
|
||||
`build_src_filter`, exactly once. Confirmed safe against
|
||||
`get_partition_table_csv()`'s similar `variants_dir`-based fallback logic
|
||||
(moot here since `board_build.partitions` is set to a full explicit path,
|
||||
which that function returns verbatim once its variant-relative lookup
|
||||
attempts fail).
|
||||
|
||||
9. **Notification sound: hand-rolled ES8311 driver, not a vendored codec HAL.**
|
||||
`espressif/esp_codec_dev`'s ES8311 device (Apache-2.0, ~4.5k lines across
|
||||
its generic multi-codec plugin architecture) is more than this
|
||||
single-chip, playback-only board needs, and defaults to the *new*
|
||||
`driver/i2s_std.h` API where this codebase's existing T-Deck sound code
|
||||
uses the legacy `driver/i2s.h` (which already supports MCLK via
|
||||
`i2s_pin_config_t.mck_io_num`, so no new API family was needed). Wrote
|
||||
`variants/lilygo_tlora_pager/Es8311Codec.{h,cpp}` instead — ~200 lines,
|
||||
DAC-only, register sequence and MCLK clock-divider coefficients derived
|
||||
from Espressif's public `es8311.c`/`es8311_reg.h` (see `NOTICE`), fixed to
|
||||
a 256x-sample-rate MCLK ratio covering the standard 8k-48k rates
|
||||
`wavParse()` already accepts. Matches this port's own precedent (T-Deck's
|
||||
sound code is similarly hand-rolled, ~200 lines) and the milestone rule
|
||||
against introducing new abstraction layers.
|
||||
10. **AMP_EN toggled around playback, not left permanently on.** The
|
||||
boot-time `TLoraPagerBoard::begin()` drive of every expander rail
|
||||
(including AMP_EN) exists for bus-integrity reasons specific to the boot
|
||||
window (unpowered chips clamping the shared I²C bus) — unrelated to
|
||||
audio and left untouched.
|
||||
`TLoraPagerBoard::setAmpEnabled()` is the runtime toggle the sound code
|
||||
brackets each chime/WAV with (amp off at idle), matching both the
|
||||
vendor's own `codec.setPaPinCallback()` pattern and this port's existing
|
||||
on-demand-resource philosophy (T-Deck installs/uninstalls its I2S driver
|
||||
per chime for the same reason). Cost is one extra I²C write per
|
||||
chime start/end on a bus already used for every other rail toggle.
|
||||
|
||||
## Worklist (ordered, each step ends build-green for ALL envs)
|
||||
|
||||
- [x] ⓪ Research + board JSON verification; land `boards/lilygo-t-lora-pager.json` + this tracker.
|
||||
- [x] ① **Variant skeleton**: `variants/lilygo_tlora_pager/{pins_arduino.h, TLoraPagerBoard.h/.cpp}` + `partitions_tlora_pager_touch.csv` (T-Deck's OTA/tiles/spiffs/coredump layout, byte-identical offsets). Board class (`TLoraPagerBoard : public ESP32Board`): `begin()` re-inits Wire on SDA3/SCL2, probes the XL9555 expander (addr 0x20) and enables LORA_EN/GPS_EN/KB_EN/KB_RST/SD_EN rails, probes the BQ27220 gauge, handles the deep-sleep RX-packet wake reason (mirrors `TDeckBoard.cpp`). `getBattMilliVolts()` reads the gauge (`refresh()` + `getVoltage()`), falling back to 3700 mV if the probe/refresh fails. `getManufacturerName()` → "LilyGo T-LoRa Pager". `enterDeepSleep()` mirrors `TDeckBoard.h`. BQ25896 charger deliberately left as a TODO (gauge alone covers the UI). `target.{h,cpp}` deferred to ② — nothing references the new files yet, so both shipping envs build unchanged (verified green).
|
||||
- [x] ② **Radio**: `variants/lilygo_tlora_pager/{CustomLR1121.h, CustomLR1121Wrapper.h, target.h, target.cpp}`.
|
||||
`CustomLR1121{,Wrapper}.h` authored by adapting the core fork's own
|
||||
`CustomLR1110{,Wrapper}.h` (LR1110/LR1121 share RadioLib's `LR11x0` base —
|
||||
confirmed by reading RadioLib 7.6.0 source directly: same protected
|
||||
`freqMHz`/`spreadingFactor` members, same `getIrqStatus()`/`getRssiInst()`
|
||||
inherited from `LRxxxx`/`LR11x0`), since upstream has nothing to crib (see
|
||||
Decision ②). `radio_init()` in `target.cpp`: `spi.begin(...)` →
|
||||
`radio.begin(LORA_FREQ, LORA_BW, LORA_SF, cr, ..._SYNC_WORD_PRIVATE,
|
||||
LORA_TX_POWER, 8, 3.0f)` → `setRfSwitchTable(DIO5/DIO6)` → `setCRC(1)`.
|
||||
**Three deliberate deviations from trail-mate, found by reading RadioLib's
|
||||
actual source rather than copying its call sequence — see Decision ② for
|
||||
the full reasoning:**
|
||||
1. No explicit `radio.reset()` before `begin()` — RadioLib's
|
||||
`LR11x0::modSetup()`→`findChip()` already resets the chip internally
|
||||
(with retries); trail-mate's explicit reset is redundant, not wrong.
|
||||
2. No second `radio.setTCXO(3.0f)` call after `begin()` — trail-mate only
|
||||
needs that because its `initLoRa()` calls the *zero-arg* `begin()`
|
||||
(default `tcxoVoltage=1.6V`) and fixes it up after. Our `begin()` passes
|
||||
`3.0f` as the 8th arg directly, which `LR11x0::modSetup()` already
|
||||
applies internally — a second call would be a no-op.
|
||||
3. Added `radio.setCRC(1)` after `begin()` (LR11x0's `begin()` defaults to a
|
||||
2-byte CRC) to match the 1-byte CRC every other MeshCore radio wrapper
|
||||
uses for wire-protocol interop (`CustomSX1262::std_init()` makes the
|
||||
identical override) — trail-mate never needed this since its app isn't
|
||||
interoperating with MeshCore's own framing.
|
||||
Also **not** calling `rtc_clock.begin(Wire)` in `radio_init()` — see the new
|
||||
Risk item below (RTC address collision). `RfSwitchMode_t`/`OpMode_t`/DIO5-6
|
||||
constants verified to exist with the expected shape directly in the pinned
|
||||
`jgromes/RadioLib @ ^7.6.0` source (not just trusted from trail-mate).
|
||||
Env defines for M4 to use: `RADIO_CLASS=CustomLR1121`,
|
||||
`WRAPPER_CLASS=CustomLR1121Wrapper`, `P_LORA_NSS=36 / _RESET=47 / _BUSY=48 /
|
||||
_DIO_1=14`, SPI 35/34/33, **`PIN_GPS_RX=12` / `PIN_GPS_TX=4`** (see the GPS
|
||||
risk item below — these are swapped relative to trail-mate's raw
|
||||
`GPS_RX`/`GPS_TX` macro values, on purpose), `GPS_BAUD_RATE=38400`. Keep the
|
||||
standard `RADIOLIB_EXCLUDE_*` set (LR11X0 stays IN; can also exclude SX126X
|
||||
here).
|
||||
- [x] ③ **Display**: `src/helpers/ui/ST7796LCDDisplay.{h,cpp}` on TFT_eSPI
|
||||
(`ST7796_DRIVER`, `TFT_WIDTH=222`, `TFT_HEIGHT=480`, MADCTL rotation for
|
||||
landscape). This is the **first real TFT_eSPI consumer in this codebase** —
|
||||
`ST7789LCDDisplay` (the sibling this was "modeled on") turned out to be
|
||||
Adafruit_GFX/Adafruit_ST7789-based for both Heltec V4 and T-Deck; the
|
||||
Heltec V4 env's TFT_eSPI lib_dep/`-D` flags are vestigial (nothing
|
||||
`#include`s `TFT_eSPI.h` anywhere else in the repo). Only the
|
||||
`DisplayDriver`-satisfying shape was mirrored, not any API calls — every
|
||||
TFT_eSPI method used (`init`, `setRotation`, `setAttribute`/`CP437_SWITCH`,
|
||||
`textWidth`, `pushColors`, `writecommand`/`TFT_DISPOFF`, `fillScreen`,
|
||||
`fillRect`/`drawRect`/`drawPixel`, `setAddrWindow`/`startWrite`/`endWrite`)
|
||||
was verified directly against the pinned `bodmer/TFT_eSPI @ ^2.5.43` source
|
||||
in `.pio/libdeps/*/TFT_eSPI/TFT_eSPI.h`, not assumed.
|
||||
**Found and fixed a real correctness bug in the same pass**: this panel's
|
||||
222px glass is narrower than the ST7796 controller's 320px GRAM (trail-mate
|
||||
applies explicit 49px column/row offsets — confirmed the source of the
|
||||
magic number: `320 - 222 = 98`, halved/centered = 49px each side). TFT_eSPI's
|
||||
own `ST7796_Rotation.h` already has this exact fix (`colstart=49`/
|
||||
`rowstart=49` depending on rotation, applied automatically inside every
|
||||
`setAddrWindow()` call) — but only when `CGRAM_OFFSET` is `#define`d.
|
||||
Unlike `ST7789_Defines.h` (which self-defines it), `ST7796_Defines.h` does
|
||||
not, and it was **missing from this milestone's own drafted M4 flag list**.
|
||||
Without it the build compiles clean but every frame renders shifted/cropped
|
||||
by 49px with no error — added a `#error` guard in the new `.cpp` that fires
|
||||
the moment M4 compiles this file without the flag, with the fix spelled out
|
||||
in the message. **M4 must add `-D CGRAM_OFFSET=1`.**
|
||||
Backlight: AW9364 stepped pulse-dimmer, wrapped directly in
|
||||
`ST7796LCDDisplay` (no separate `Aw9364Backlight.{h,cpp}` — `ST7789LCDDisplay`
|
||||
turned out to have no brightness hook to mirror at all; brightness on the
|
||||
other two boards is a `UITask.cpp`-owned free function doing raw LEDC PWM
|
||||
on `PIN_TFT_LEDA_CTL`, which the AW9364's pulse protocol can't use).
|
||||
Consumes `lewisxhe/SensorLib`'s `AW9364LedDriver` directly (header-only,
|
||||
already vendored via the M1-established SensorLib dependency) rather than
|
||||
hand-rolling the pulse timing the milestone doc originally suggested.
|
||||
Exposes `setBrightness(uint8_t pct)`/`getBrightness()` (0-100, matching the
|
||||
Settings UI's existing convention) so Milestone 6 can wire it in with one
|
||||
line. **M6 needs a new branch ahead of `UITask.cpp`'s existing
|
||||
`PIN_TFT_LEDA_CTL` PWM branch** — once M4 defines that macro for the pager,
|
||||
the existing LEDC-PWM code would also compile and fight the AW9364's pulse
|
||||
protocol (a duty cycle is not a valid input to this chip). `NOTICE` updated:
|
||||
added a `SensorLib` entry (missing since M1) and corrected the `TFT_eSPI`/
|
||||
`Adafruit GFX` lines' backend descriptions (both were wrong about which
|
||||
boards use which library). `DISPLAY_CLASS=ST7796LCDDisplay`. Gate: both
|
||||
shipping envs build unchanged (neither's `build_src_filter` references
|
||||
`helpers/ui/*.cpp` yet, so the new TU isn't even parsed by either compiler
|
||||
today) — full compiler verification of the new code waits for M4.
|
||||
- [x] ④ **Env**: `[env:tlora_pager_lr1121_companion_radio_touch]` in
|
||||
`platformio.ini`, cloned from the T-Deck env with board/radio/display/GPS/
|
||||
input deltas (see Decisions ①-④, ⑥-⑧ for exact values and reasoning). Added
|
||||
lib_deps: `bodmer/TFT_eSPI @ ^2.5.43`, `adafruit/Adafruit TCA8418 @ ^1.0.2`,
|
||||
`lewisxhe/SensorLib @ 0.3.3` (exact version trail-mate proves works, not
|
||||
guessed) — **dropped** `adafruit/Adafruit ST7735 and ST7789 Library` from
|
||||
the cloned list (T-Deck/Heltec's Adafruit display backend; unused here,
|
||||
TFT_eSPI replaces it). No `XPowersLib` (M1 left the BQ25896 charger out of
|
||||
scope). `default_envs` left unchanged (still just the two shipping boards).
|
||||
Also fixed two structural bugs found only by actually compiling (see
|
||||
Decision ⑧) and added one forced, minimal `UITask.cpp` `#elif
|
||||
defined(TLORA_PAGER)` arm (display-class typedef only — see the Status
|
||||
line). **Compile gate: all three envs build green** — verified.
|
||||
- [x] ⑤ **Input drivers**: `src/helpers/input/{PagerKeyboard,PagerEncoder}.{h,cpp}`.
|
||||
Gated `HAS_PAGER_KEYBOARD`/`HAS_PAGER_ENCODER`, already reached by every
|
||||
env's `+<helpers/input/*.cpp>` filter — no `platformio.ini` change needed
|
||||
for compilation, only the pin flags (`KB_INT=6`, `KB_BACKLIGHT=46`,
|
||||
`ROTARY_A=40`, `ROTARY_B=41`, `ROTARY_C=7`, matching the repo's
|
||||
explicit-`-D`-alongside-`.cpp`-fallback convention).
|
||||
**`PagerKeyboard`**: T-Deck's own keyboard driver turned out to be the
|
||||
wrong shape to mirror directly — its C3 co-processor resolves ASCII itself
|
||||
over I2C, so `TDeckKeyboard.cpp` never sees a raw matrix event. The
|
||||
TCA8418 (Adafruit_TCA8418 lib, already an M4 dependency) reports raw
|
||||
row/col events instead, so the keymap + shift/sym/alt state machine lives
|
||||
in `PagerKeyboard.cpp` itself. Reused trail-mate's `LilyGoKeyboard` keymap
|
||||
tables verbatim (same physical PCB) — `keymap[4][10]`/`symbol_map[4][10]`,
|
||||
Alt as a hold-to-symbol-layer modifier (no separate physical Symbol key on
|
||||
this hardware), Backspace special-cased to `'\b'` — UX choices trail-mate
|
||||
already field-validated, reused rather than re-derived. **The Shift/Caps
|
||||
key's behavior changed 2026-07-13** from trail-mate's original
|
||||
press-to-toggle case lock to conventional keyboard semantics: held alone
|
||||
it's a momentary Shift (uppercases whatever letters are typed while held,
|
||||
releases back to normal); held Alt + a Shift press instead chords. `kCapsPos`
|
||||
renamed `kShiftPos` to match. **Chord effect changed again 2026-07-14**:
|
||||
`PagerKeyboard.cpp` no longer decides what Alt+Shift *does* (it has no UI
|
||||
visibility) — it only reports the chord via
|
||||
`pagerKeyboardConsumeAltShiftChord()`; `UITask.cpp`'s
|
||||
`updatePagerAltShiftChord()` toggles persistent Caps Lock ONLY while a text
|
||||
field is actually being edited (same "ta" derivation as `handleHwKey()`'s
|
||||
TLORA_PAGER branch), and jumps straight Home (`navGoToMainTab`) everywhere
|
||||
else — toggling Caps Lock with no field to see it change in was reported as
|
||||
surprising/purposeless outside of typing.
|
||||
See `PagerKeyboard.cpp`'s matrix-legend comment for the one documented
|
||||
edge case (Alt+Shift+O or Alt+Shift+L all three held phantom-ghosts a
|
||||
stray 'q'/'a' — a diode-less-matrix limitation, not a bug, and not the
|
||||
intended gesture). Ring-buffer/SPSC/threading-contract shape
|
||||
mirrors `TDeckKeyboard.cpp`'s (single poll context, UI-thread-safe
|
||||
`readKey()`) even though the underlying hardware access differs completely.
|
||||
Implemented as straightforward polling (`available()`/`getEvent()` drained
|
||||
every `pagerKeyboardPoll()` call), not INT-pin-gated, despite the milestone
|
||||
doc's "prefer INT-driven drain" suggestion — trail-mate's own INT handling
|
||||
does extra `INT_STAT`/`GPIO_INT_STAT` register bookkeeping whose exact
|
||||
clear-vs-latch semantics weren't independently verified, and an ISR-gated
|
||||
poll that's wrong would present as "keyboard stops after first keypress" —
|
||||
a regression only caught on hardware (M7). Polling is explicitly sanctioned
|
||||
as a fallback by the milestone doc and carries no such risk; INT-driven
|
||||
draining is a valid future optimization once verified on real hardware.
|
||||
**`PagerEncoder`**: the milestone's "ISR edge-counting exactly like
|
||||
`TDeckTrackball.cpp:27-47`" doesn't transfer literally — the T-Deck
|
||||
trackball is 4 independent direction-pulse GPIOs (no direction logic
|
||||
needed, each pin already means one direction), not a true A/B quadrature
|
||||
pair, so it can't answer "which way did it turn." Implemented a standard
|
||||
Gray-code quadrature transition table instead (both edges of both A and B
|
||||
feed one ISR, table lookup yields +1/-1/0 per transition) — same
|
||||
ISR-does-cheap-arithmetic-only shape and `noInterrupts()`-snapshot read
|
||||
pattern as `TDeckTrackball.cpp`, just the right decode logic for a genuine
|
||||
quadrature signal. Divides by `PAGER_ENCODER_STEPS_PER_DETENT` (default 4,
|
||||
the common EC11-style ratio) to convert raw transitions to detents,
|
||||
carrying the remainder forward across reads — **this divisor is an
|
||||
unverified assumption, confirm on hardware in M7**.
|
||||
**Gate:** all three envs build — verified.
|
||||
- [x] ⑥ **UITask wiring**: `device_caps.h` got its `TLORA_PAGER` cap block (no
|
||||
touch, no rotate, no large-screen, GPS 1, OTA 1, lock-screen 1; `CAP_SD`/
|
||||
`CAP_FILESYSTEM` corrected to 0 — see below) plus widened `CAP_KEYBOARD`/
|
||||
`CAP_KEYPAD_NAV` derivations to recognize `HAS_PAGER_KEYBOARD`/
|
||||
`TLORA_PAGER`. `UITask.cpp` changes, all pager-gated: KEYPAD indev
|
||||
registered by widening the Tanmatsu branch's `#if` to
|
||||
`defined(HAS_TANMATSU) || defined(TLORA_PAGER)` (~35706) — pager reuses
|
||||
`tanmatsuKeypadRead`'s plumbing but not `bsp_input_get_queue`, which stays
|
||||
Tanmatsu-only; forced `s_ui_rotation = LV_DISP_ROT_270` and a new
|
||||
`hor_res=480/ver_res=222` branch (~35626); a new `updatePagerEncoder()`
|
||||
(delta → `navPushTap(NEXT/PREV)`, click → ENTER, long-press ≥1000ms →
|
||||
ESC); main-loop drain wired for both `pagerKeyboardPoll()`/
|
||||
`pagerKeyboardReadKey()` → `handleHwKey()` and the encoder function above;
|
||||
`applyBrightness()`/`touchScreenBacklight()` got pager-first branches
|
||||
calling `display.setBrightness()` instead of falling into the
|
||||
`PIN_TFT_LEDA_CTL` PWM branch (closes risk 1f — PWM would never have
|
||||
worked on the AW9364's discrete-pulse interface). Draw buffer already
|
||||
sized off `hor_res` so needed no separate edit. 222-px vertical audit
|
||||
deferred to on-device measurement (M8) rather than guessed constants — see
|
||||
risk 8 below. Final sizes: pager RAM 23.8% (78088/327680 B), Flash 66.4%
|
||||
(2696465/4063232 B); Heltec V4/T-Deck unchanged at 25.3%/73.5% (byte-
|
||||
identical to pre-M6, confirming the gate on every edit). Three corrections
|
||||
vs. the milestone doc's assumptions, all found by compiling rather than
|
||||
assumed — see risks 1h/1i/1j:
|
||||
- `CAP_SD`/`CAP_FILESYSTEM` looked right from the hardware (real microSD
|
||||
slot) but the actual mount code is hardcoded to `HAS_TDECK_GT911`, not
|
||||
truly `CAP_SD`-generic — set back to 0, real support is unscheduled
|
||||
follow-up.
|
||||
- `navMaybeRebuild()` (populates the LVGL focus group every screen) wasn't
|
||||
reachable on the pager's own cap combination — added a
|
||||
`#elif defined(TLORA_PAGER)` arm; without it the KEYPAD indev would have
|
||||
had a permanently empty focus group.
|
||||
- Several T-Deck-keyboard-gated helpers (`handleHwKey`, `isDismissKey`,
|
||||
`tabForKey`, `navMenubarKeysSync`) live inside multiply "paused and
|
||||
reopened" `#if defined(HAS_TDECK_KEYBOARD)` regions; widened each
|
||||
reopen's gate individually (tracing real nesting depth, not just grep
|
||||
hits) rather than touching the whole file, and kept genuinely
|
||||
T-Deck-only sub-logic (the p/q/a dismiss-key mapping, which would eat
|
||||
normal QWERTY typing on the pager) under its own narrower, unwidened gate.
|
||||
- [x] ⑦ **Headless bring-up gate, UI portion** (needs hardware) — DONE
|
||||
(2026-07-06/07 sessions, see `TLORA_PAGER_M7_HW_DEBUG_LOG.md`): flash +
|
||||
serial recipes established; boot clean; SPIFFS mounts; display + boot logo +
|
||||
full UI + keyboard nav + encoder verified on the glass (after 6 real bugs —
|
||||
stale-NVS BLE bonds, screen-wake path, TFT_eSPI S3 SPI-port index, panel
|
||||
INVON, two-SPI-hosts pin theft, and the floating XL9555-ch6 panel reset).
|
||||
- [~] ⑦b **Radio/USB/SD bring-up gates** (needs hardware + second mesh node) —
|
||||
**radio joins the live mesh: DONE**, adverts/DMs confirmed reliable both
|
||||
directions against a second node after the TX-power fix (risk 10,
|
||||
retested 2026-07-11) — still watch upstream LR1121 ACK issue
|
||||
meshcore-dev/MeshCore#1376 for anything beyond basic reliability. USB
|
||||
companion link passing the large device-profile frame (see HW-CDC note
|
||||
above): still untested. **SD storage: DONE** — real mount, browse,
|
||||
read/write/delete, and insert/remove detection all confirmed on hardware
|
||||
(risk 1h). (DataStore/NVS-level SD use — routing contacts/channels to the
|
||||
card — is a separate, still-deferred feature, not part of this bring-up
|
||||
gate.) Remove the temporary `[DISP]` register readback in
|
||||
`ST7796LCDDisplay::begin()` once the USB piece passes.
|
||||
- [~] ⑧ **On-device UI pass** — ACTIVE: nav-coverage audit screen by screen (every interactive control reachable via focus group — the Tanmatsu work paved this), chat layout at 222 px, map pan via encoder/keys, fonts legibility at 480-wide, plus fixing any weird UI/keyboard behaviors surfaced by manual testing (human provides photos/repro steps).
|
||||
- [ ] ⑨ **Release pipeline**: add the env:binname pair to `release.sh` `ENVS`, flasher manifest (`deploy/flasher/manifest-tlora-pager.json`), OTA env name via `FIRMWARE_OTA_ENV`. Separate PR.
|
||||
- [x] ⑩ (Optional, cheap) `tlora_pager_sx1262_...` env for SX1262-variant owners — **DONE, hardware-verified 2026-07-13** (`tlora_pager_sx1262_companion_radio_touch`, all 4 envs green; see `TLORA_PAGER_PORT_MILESTONES.md`'s Milestone 10 for the full writeup, including the `target.h` missing-include bug found + fixed). Radio gate (M7 step 3) confirmed on real hardware: user sent + received messages on public, a custom `#` channel, and DM.
|
||||
|
||||
## UI-changes inventory (what actually changes in `src/ui-touch/`)
|
||||
|
||||
| Area | Change | Size | Status |
|
||||
|---|---|---|---|
|
||||
| Indev registration (~35706) | Widened the Tanmatsu keypad-only branch's gate to `\|\| defined(TLORA_PAGER)`; no pointer indev registered | small | done |
|
||||
| Input drain (main loop ~37350) | Pager branch: `pagerKeyboardPoll()`+`pagerKeyboardReadKey()`→`handleHwKey()`; `updatePagerEncoder()`→`navPushTap(NEXT/PREV/ENTER/ESC)` | small | done |
|
||||
| `handleHwKey()` (~27990) and its multiply-reopened `HAS_TDECK_KEYBOARD` regions | Widened each reopen's gate individually to admit `HAS_PAGER_KEYBOARD`; kept the T-Deck-only p/q/a dismiss mapping under its own narrower gate | small | done |
|
||||
| Resolution block (~35604–35650) | New branch: forced `LV_DISP_ROT_270`, `hor_res=480/ver_res=222` | small | done |
|
||||
| Draw buffer (~1399) | Sized off `hor_res`, no separate edit needed | trivial | done (no-op) |
|
||||
| Brightness (`applyBrightness`~28906, `touchScreenBacklight`~36511) | New pager-first branches calling `display.setBrightness()` instead of the PWM `PIN_TFT_LEDA_CTL` path | small | done |
|
||||
| `navMaybeRebuild()` reachability | Added `#elif defined(TLORA_PAGER)` arm — was unreachable, would've left focus group permanently empty | small | done (bug fix) |
|
||||
| Vertical budget | Audit `STATUSBAR_H`, `TABBAR_H`, `CHAT_KB_H`, modal/chat height helpers for 222 px | **the real work** | deferred to M8 on-device (risk 8) |
|
||||
| Focus-nav coverage | Screen-by-screen pass that every control is in `s_nav_group` | medium, on-device | deferred to M8 |
|
||||
| device_caps.h | New `CAP_*` block: no touch, hw keyboard, encoder, 480×222, GPS, OTA, lock-screen; SD/filesystem left 0 by design — real SD support added via widened individual call sites instead (see risk 1h) | trivial | done |
|
||||
| Keyboard backlight (`pagerKeyboardSetBacklight()`) | `updatePagerKbBacklight()` tick + Settings → Keyboard cycle row (off/on/auto) | small | done (risk 9) |
|
||||
| microSD (file manager + WAV picker) | `fmSdTryMount()`/`fmShowRoots()`/`fmIsAudio()` etc. widened to `\|\| defined(TLORA_PAGER)`; DataStore SD routing, chat-history cap, tile/wallpaper/backup SD use still deferred | medium | done, scoped (risk 1h) |
|
||||
| Chat bubble Enter/Backspace (`handleHwKey()` ~28808) | Enter on a focused bubble opens the Ack/Mention/Copy/Info/Block menu (`navEnterBubble()`); Backspace jumps to the newest message when the scroll-to-bottom button is showing | small | done |
|
||||
| Shift/Caps (`PagerKeyboard.cpp`) | Held Shift = momentary uppercase; held Alt+Shift = chord (see below for what it does) | small | done |
|
||||
| Accent-variant popup keyboard nav | Fn+Space arms it; encoder walks variants; encoder click/Enter confirms; Backspace cancels (`s_accentnav_active`/`accentNavRestyle()`/`accentNavConfirm()`) | medium | done |
|
||||
| @-mention picker keyboard nav | Auto-arms the instant the list appears (no Fn+Space needed); same encoder walk/confirm/cancel as the accent popup (`s_mentionnav_active`/`mentionNavRestyle()`/`mentionNavConfirm()`) | medium | done |
|
||||
| Encoder short click on a focused bubble | Mirrors keyboard Enter — opens the same action menu instead of a plain ENTER a bubble doesn't react to | small | done |
|
||||
| Fn+Shift chord effect | Toggles Caps Lock while editing a text field; jumps to Home (`navGoToMainTab`) everywhere else — `PagerKeyboard.cpp` only reports the chord, `UITask.cpp` decides the effect | small | done |
|
||||
| Map pan (WASD) + slider nudge (Q/E) | WASD pans the Map tab (`mapNudge()`, shared with Tanmatsu's Ctrl+Arrow); Q/E adjust a focused slider (moved off D/F to avoid colliding with WASD) | small | done |
|
||||
| App-drawer tile focus highlight | `NAV_ACCENTFOCUS_FLAG` makes `navFocusCb` use the tile's own accent tint instead of the generic white reverse-fill, on every board | small | done |
|
||||
| SX1262-variant env | `tlora_pager_sx1262_companion_radio_touch` — same board/pins, `CustomSX1262`/`CustomSX1262Wrapper` instead of the LR1121 classes | medium | done (worklist ⑩) |
|
||||
|
||||
Everything else (map, chat, contacts, channels, settings, companion protocol,
|
||||
MQTT, OTA) is resolution-agnostic or already keyed off caps.
|
||||
|
||||
## Risks / open questions
|
||||
|
||||
1. **LR1121 ACK/TX reliability** — upstream issue meshcore-dev/MeshCore#1376 reports
|
||||
ACK problems on the pager's LR1121 (confirmed still open). Track it; our wrapper
|
||||
crib should include any upstream fix. Gate ⑦ tests this explicitly.
|
||||
1b. **No upstream `CustomLR1121` crib source exists yet** (re-verified
|
||||
2026-07-06 — see Decision ②'s caveat). Milestone ② needs to author the
|
||||
wrapper by adapting the core fork's `CustomLR1110{,Wrapper}.h`, not by
|
||||
copying an upstream file. Re-check upstream first in case it lands before
|
||||
we get there — would save the work.
|
||||
1c. **GPS `PIN_GPS_RX`/`PIN_GPS_TX` are named from the GPS module's
|
||||
perspective in wadamesh's own core (`EnvironmentSensorManager.cpp` calls
|
||||
`Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX)`, and `HardwareSerial::setPins()`
|
||||
takes `(rxPin, txPin)` — so `PIN_GPS_TX` supplies the ESP's own **RX** pin,
|
||||
`PIN_GPS_RX` supplies the ESP's own **TX** pin). trail-mate's/the canonical
|
||||
arduino-esp32 `GPS_RX=4`/`GPS_TX=12` macros are named the OPPOSITE way — its
|
||||
own `Serial1.begin(baud, cfg, GPS_RX, GPS_TX)` call uses
|
||||
`HardwareSerial::begin()`'s `(rxPin, txPin)` order directly, so there
|
||||
`GPS_RX` IS the ESP's own RX pin. **Net result: wadamesh's `PIN_GPS_RX` must
|
||||
be set to `12` and `PIN_GPS_TX` to `4`** for M4 — the raw trail-mate values
|
||||
swapped, not copied verbatim. Verified by reading both projects' actual
|
||||
`Serial1.setPins()`/`begin()` call sites and the ESP32 core's
|
||||
`HardwareSerial::setPins()`/`begin()` signatures directly, not by trusting
|
||||
either project's macro names at face value. Baud confirmed at 38400 (same
|
||||
MIA-M10Q as T-Deck Plus).
|
||||
1d. **RTC auto-discovery would misread this board's real RTC.**
|
||||
`AutoDiscoverRTCClock` (core, shared by all boards) only recognizes DS3231
|
||||
(`0x68`), RV3028 (`0x52`), and PCF8563 (`0x51`) — its probe is a bare I2C ACK
|
||||
check. This board's PCF85063A sits at that same `0x51` address but has a
|
||||
different register layout (RTClib's `RTC_PCF8563` driver would misread its
|
||||
registers), so calling `rtc_clock.begin(Wire)` would silently produce
|
||||
garbage timestamps instead of a clean fallback. `target.cpp`'s `radio_init()`
|
||||
deliberately skips that call — same time behavior as T-Deck/Heltec V4 (ESP32
|
||||
software clock) rather than a false "RTC found" that's actually wrong. Real
|
||||
PCF85063A support (SensorLib's `SensorPCF85063`) is unscheduled follow-up
|
||||
work, not part of any milestone ①–⑩ yet.
|
||||
1e. **`-D CGRAM_OFFSET=1` must land in Milestone ④'s pager env flags.**
|
||||
This panel's 222px glass is narrower than the ST7796 controller's 320px
|
||||
GRAM; TFT_eSPI's `ST7796_Rotation.h` only applies the required 49px
|
||||
column/row offset when this flag is set (unlike `ST7789_Defines.h`, which
|
||||
self-defines it). Without it, the pager build compiles clean but every
|
||||
frame renders shifted/cropped by 49px with no error. `ST7796LCDDisplay.cpp`
|
||||
has a `#error` guard that will catch the omission the moment M4 compiles
|
||||
this file — but the actual fix belongs in `platformio.ini`, and it's easy
|
||||
to miss since the build "succeeds."
|
||||
1f. **M6 must add a pager branch to `UITask.cpp`'s brightness code ahead of
|
||||
its existing `PIN_TFT_LEDA_CTL` PWM branch.** Milestone ③ reused that
|
||||
macro name for the pager's AW9364 enable pin (naming consistency across
|
||||
boards), but `UITask.cpp:~28868`'s `#if defined(PIN_TFT_LEDA_CTL) &&
|
||||
(PIN_TFT_LEDA_CTL >= 0)` branch drives that pin with 20kHz LEDC PWM — which
|
||||
the AW9364 does not accept (it needs discrete edge pulses per step, not a
|
||||
duty cycle). Once M4 defines `PIN_TFT_LEDA_CTL=42` for the pager, that
|
||||
existing branch will compile and run for it too unless M6 adds a
|
||||
`#if defined(TLORA_PAGER)` branch first that calls
|
||||
`display.setBrightness(pct)` instead (the hook `ST7796LCDDisplay` already
|
||||
exposes for exactly this).
|
||||
1g. **RX-boosted-gain silently doesn't apply for the pager — RESOLVED
|
||||
2026-07-07 (commit `b3c6388`).** Was deferred, not fixed, in Milestone ④
|
||||
(matches M3's shared-file-edit precedent): with `-D USE_LR1121=1` defined
|
||||
(not `USE_SX1262`/`USE_SX1268`), three call sites defaulted/applied this
|
||||
radio setting only for the SX126x macros even though `CustomLR1121Wrapper`
|
||||
already implements `setRxBoostedGainMode`/`getRxBoostedGainMode`:
|
||||
`src/MyMesh.cpp` ~2540 (default pref value on first boot), ~2556 and ~3355
|
||||
(actually calling `radio_driver.setRxBoostedGainMode(...)`), and
|
||||
`src/DataStore.cpp` ~264 (default pref value). Fixed by adding
|
||||
`|| defined(USE_LR1121)` to all three conditions — safe/additive, zero
|
||||
behavior change for T-Deck/Heltec V4, verified by all-three-envs build.
|
||||
(A fourth, core-lib-only gate still exists in `CommonCLI.cpp`'s
|
||||
`radio.rxgain` companion CLI command, checking `USE_SX1262`/`USE_SX1268`/
|
||||
`USE_LR1110` — left alone in this pass since it's core-lib, not app code;
|
||||
now that the three UI/prefs call sites actually apply the setting, adding
|
||||
`USE_LR1121` there too would be safe follow-up, just unscheduled.)
|
||||
2. **TCA8418 keymap** — raw matrix + our own shift/sym/alt state machine; the T-Deck
|
||||
never needed this (its C3 resolves ASCII). Bounded: trail-mate's layout tables are
|
||||
a working reference. Landed in M5 as `src/helpers/input/PagerKeyboard.cpp`,
|
||||
polling-based (not INT-pin-gated — see worklist ⑤ for why).
|
||||
2a. **Encoder detent scaling — RESOLVED on hardware (2026-07-07).** The
|
||||
assumed 4-transitions-per-detent (`PAGER_ENCODER_STEPS_PER_DETENT`)
|
||||
matches this part: user drove the menus by encoder and reported it feels
|
||||
right ("encoder is looking good"), no fractional/multiple stepping.
|
||||
**Direction was also wrong, found and fixed later the same day**: unlike
|
||||
scaling, the CW/CCW sign was never actually validated against physical
|
||||
rotation — `kQuadTable`'s convention didn't match this part's wiring, so
|
||||
turning the way a user expects to move forward/down moved focus and
|
||||
page-scroll backward/up instead. Fixed by negating once in
|
||||
`PagerEncoder.cpp`'s `pagerEncoderReadDelta()` (the driver, not UI code)
|
||||
so every consumer inherits the corrected sense from one place.
|
||||
1h. **`CAP_SD`/`CAP_FILESYSTEM` are 0 for the pager despite real microSD
|
||||
hardware — a latent gap in `device_caps.h`'s own abstraction, not a pager
|
||||
bug.** **File manager browsing + WAV notification-sound picker RESOLVED
|
||||
2026-07-10** (commits `8c7fb6a`/`1514e76`), scoped deliberately: `CAP_SD`
|
||||
turned out to gate ~37 call sites (file manager, WAV picker, DataStore
|
||||
contacts/channels routing, tile-cache SD fallback, wallpaper scanning,
|
||||
settings backup, screenshots), so flipping the macro itself would have
|
||||
silently turned on ~30 unvetted, T-Deck-only-tested features at once.
|
||||
Instead `CAP_SD`/`CAP_FILESYSTEM` stay 0 by design, and only the ~7
|
||||
specific call sites needed for browsing + WAV picking were widened with
|
||||
`|| defined(TLORA_PAGER)` — see those commits for the full categorized
|
||||
site list. Added `pagerSdCardPresent()` (XL9555 `PAGER_EXPAND_SD_DET`,
|
||||
confirmed-inverted polarity) and a pager `sdSharedSPI()` (reuses
|
||||
`TFT_eSPI::getSPIinstance()`, the same bus the radio already shares).
|
||||
Format support is explicitly out of scope (no `f_mkfs`/`sd_diskio.h` pulled
|
||||
in) — the pager's SD row is tap-to-open only. Hardware-verified: mount,
|
||||
browse, read/write/delete, insert/remove detection, and picking a WAV from
|
||||
SD as a notification sound all confirmed on real hardware. **Still not
|
||||
done** (unscheduled follow-up, deliberately deferred): DataStore
|
||||
contacts/channels routing to SD (`setSecondaryFS`, what Tanmatsu/T-Deck
|
||||
do to dodge SPIFFS GC stalls), the `MAX_UI_MESSAGES_SD` chat-history cap
|
||||
bump, map-tile SD fallback/offline tile packs, lock-screen wallpaper SD
|
||||
scanning, and settings/telemetry backup export-import to SD.
|
||||
1i. **`navMaybeRebuild()` was unreachable for the pager in the first M6
|
||||
pass.** It's only called under `#if defined(HAS_TANMATSU) ... #elif
|
||||
CAP_TRACKBALL ...`, neither of which the pager's cap combination matches
|
||||
(`CAP_TRACKBALL=0`, not `HAS_TANMATSU`). Left alone, the KEYPAD indev
|
||||
would register successfully but its focus group would stay permanently
|
||||
empty — total, silent navigation dead-end, easy to miss since the build
|
||||
still succeeds. Fixed with a `#elif defined(TLORA_PAGER)` arm alongside
|
||||
the existing branches.
|
||||
1j. **`isDismissKey()`'s T-Deck p/q/a mapping doesn't transfer to the
|
||||
pager's full QWERTY** — that mapping exists only because the T-Deck's
|
||||
sparse keyboard lacks a dedicated Esc key; reusing it verbatim for the
|
||||
pager would make it impossible to type those three letters normally.
|
||||
Returns `false` for the pager instead — its Esc equivalent is the rotary
|
||||
encoder's long-press, handled separately in `updatePagerEncoder()`.
|
||||
3. **222-px chat screen** — tightest layout wadamesh has shipped (current min is
|
||||
240). Mitigations: no on-screen keyboard (physical QWERTY), slimmer status/tab
|
||||
bars, landscape chat already exists (320×240 path).
|
||||
4. **HW-CDC companion frames** (`ARDUINO_USB_MODE=1`) — known-regressed on the V4,
|
||||
fine on the T-Deck. Test the big device-profile frame first thing on hardware.
|
||||
5. **Encoder-only ergonomics** on long lists (contacts @ 2000 max) — NEXT/PREV focus
|
||||
walk may need page-jump keys from the QWERTY (cheap: map to `navMoveDir`).
|
||||
6. **Shared SPI contention** (display flush vs radio IRQ vs SD) — same topology the
|
||||
T-Deck ships, so expected fine via SPI transactions + CS discipline; keep an eye
|
||||
on SD-write + RX overlap during history flush. **Partially de-risked in M7:**
|
||||
TFT_eSPI force-defines `SUPPORT_TRANSACTIONS` on ESP32 (its raw-register fast
|
||||
path still takes the SPI HAL mutex), RadioLib does no SPI from ISRs, and mesh +
|
||||
UI loops share one task — so there is NO radio/display race (disproven theory,
|
||||
don't re-chase; see the debug log). CS discipline is now enforced at boot:
|
||||
`TLoraPagerBoard::begin()` parks LORA_NSS/LORA_RST/SD_CS/NFC_CS OUTPUT-HIGH
|
||||
before any bus traffic (LilyGoLib's `initShareSPIPins()` equivalent).
|
||||
7. **NVS-preserving flash chain** applies here too — 4-component flash, never the
|
||||
merged image (wipes saved Wi-Fi creds).
|
||||
8. **222-px vertical layout not yet audited against real constants** —
|
||||
`STATUSBAR_H`/`TABBAR_H`/`CHAT_KB_H` and the modal/chat height helpers were
|
||||
deliberately left untouched in M6 rather than guessing slimmer values
|
||||
desk-side; the 480×222 branch compiles and boots (verified by the wordmark
|
||||
centering math already being generic), but whether every screen actually
|
||||
fits without clipping/overlap at 222 px tall can only be judged on real
|
||||
hardware. Do this first in M8, before the broader nav-coverage pass.
|
||||
9. **Keyboard backlight not wired to any timer/UI — RESOLVED 2026-07-10
|
||||
(commit `c9033a3`).** `pagerKeyboardSetBacklight()` (M5) worked standalone
|
||||
but nothing called it. Fixed with a new `updatePagerKbBacklight()` per-tick
|
||||
apply step (simple on/off at full LEDC duty — no brightness curve, unlike
|
||||
the T-Deck's dimmable slider) plus a "Keyboard backlight" cycle row
|
||||
(off/on/auto) in Settings → Keyboard. Also fixed auto mode never actually
|
||||
responding to input in the same commit: neither `handleHwKey()` nor
|
||||
`updatePagerEncoder()` called `noteKbActivity()` (the keyboard-backlight
|
||||
idle timer), only the separate `noteUserInput()` (screen idle timer) — both
|
||||
now stamp it. Hardware-verified.
|
||||
10. **TX power default — RESOLVED 2026-07-07 (commit `b3c6388`).** Was
|
||||
conservatively set to `LORA_TX_POWER=10` with no way to raise it (unlike
|
||||
the T-Deck, which boots at its chip's real ceiling), following the same
|
||||
"conservative default, tune on hardware later" note as risk 1g. Found via
|
||||
a real DM test against a second mesh node: adverts/DMs sent FROM the
|
||||
pager were unreliable while inbound reception was fine — the far node's
|
||||
own TX power was unaffected, an asymmetric-range signature pointing
|
||||
straight at the pager's own TX being too weak (10dBm is ~16x less power
|
||||
than 22dBm). Fixed to `LORA_TX_POWER=22`, matching the T-Deck's pattern.
|
||||
22dBm confirmed as a real, safe ceiling for this chip below 1GHz — not a
|
||||
guess carried over from the sibling boards — via RadioLib's pinned
|
||||
`LR1120::checkOutputPower()` source (LR1121 inherits it: the HP PA
|
||||
auto-selects above 14dBm, valid range -9..22) and cross-checked against
|
||||
trail-mate's own working `TRAIL_MATE_LORA_TX_POWER_MAX_DBM=22` for this
|
||||
exact board. **Caveat**: a device already flashed before this fix keeps
|
||||
its saved `tx_power_dbm=10` preference — the new compiled default only
|
||||
applies to a fresh/factory-reset device; raise it manually in
|
||||
Settings → Radio & Mesh on units that predate this commit.
|
||||
**Retested against a second mesh node 2026-07-11: confirmed reliable**
|
||||
(adverts/DMs both directions) — the fix holds, not just a theoretical
|
||||
RadioLib-ceiling argument.
|
||||
11. **Keyboard/encoder-driven scroll-into-view doesn't repaint on this
|
||||
board — ACCEPTED as workaround-final 2026-07-13, root cause not
|
||||
pursued further.** User's call: the Alt+turn workaround (shipped
|
||||
2026-07-07, commit `df7f9bc`) is the practical fix for this hardware —
|
||||
a genuine root-cause attempt already made things measurably worse and
|
||||
was reverted (see below), so further root-causing is not planned unless
|
||||
new evidence surfaces. `navFocusCb`'s `lv_obj_scroll_to_view(f, LV_ANIM_OFF)` (the
|
||||
call every board's keyboard/trackball nav relies on to bring a newly-
|
||||
focused off-screen field into view) updates LVGL's internal scroll
|
||||
model correctly on the pager — confirmed via hardware serial logging
|
||||
that focus reaches fields far down a tall settings page (their
|
||||
blur-to-save toasts fire in order) and `lv_obj_get_scroll_y()` genuinely
|
||||
changes — but the glass never repaints to show the new scroll position.
|
||||
A small new overlay (the toast itself) still paints fine, since that's a
|
||||
fresh object, not a repaint of already-rendered scrolled content. This
|
||||
never surfaced on the T-Deck/Heltec V4 because touch-drag scrolling
|
||||
there never exercises this code path at all (gesture scrolling
|
||||
re-invalidates every animation tick, which happens to paper over the
|
||||
same underlying gap a single one-shot `LV_ANIM_OFF` call doesn't cover)
|
||||
— so it's a latent bug in shared nav code, not something pager-specific
|
||||
in origin, just the first board with no touch fallback to mask it.
|
||||
**Attempted fix that made it WORSE, reverted**: forcing an extra
|
||||
`lv_obj_invalidate()` on the scrolled ancestor right after
|
||||
`scroll_to_view` — this made individual focus highlights eventually
|
||||
repaint, but very slowly (~5 encoder presses per visible update), and
|
||||
still never actually scrolled the viewport. Do not re-attempt this exact
|
||||
fix without new evidence. **Workaround shipped instead** (not a fix):
|
||||
holding the pager's orange Alt key + turning the encoder now
|
||||
free-scrolls the whole page via `navScrollFocused()`'s **animated**
|
||||
`lv_obj_scroll_by` (`LV_ANIM_ON`), which empirically DOES repaint
|
||||
correctly — consistent with the animation-tick theory above — then
|
||||
snaps focus to whatever's nearest the top of the now-visible viewport
|
||||
(`navRefocusFirstVisible()`, new). Plain NEXT/PREV navigation into an
|
||||
off-screen field on a tall settings page (Radio & Mesh, Profile, etc.)
|
||||
is still broken without using the Alt+turn workaround, and that's
|
||||
accepted — the Alt+turn gesture is the intended way to reach an
|
||||
off-screen field on a tall settings page going forward, not a stopgap.
|
||||
Root-causing the one-shot-invalidate failure (or checking whether the
|
||||
same gap exists on the touch-less Tanmatsu) is NOT planned work unless
|
||||
something forces it back open.
|
||||
12. **Radio & Mesh settings: blur auto-save re-ran a full flash write + live
|
||||
radio SPI reconfigure on every field defocus — RESOLVED, pager-only fix
|
||||
(2026-07-08).** User-reported "navigating feels laggy" on this screen
|
||||
specifically, confirmed on hardware via a `uiCp("ui:nav")` diagnostic
|
||||
checkpoint (added, measured zero contribution, then removed — the nav
|
||||
focus-group rebuild was NOT the cause, ruling out risk 11's "maybe it's a
|
||||
slow render pipeline" theory for THIS symptom). Root cause:
|
||||
`saveRadioParamsCb` (`UITask.cpp`) is wired to `LV_EVENT_DEFOCUSED` on
|
||||
every textarea in `buildRadioSettings()` (freq/bw/sf/cr/tx/airtime/region)
|
||||
and unconditionally called `the_mesh.savePrefs()` +
|
||||
`the_mesh.applyRadioFromPrefs()` on blur, even when nothing changed. On
|
||||
touch boards a field only blurs on a deliberate tap-out after an edit —
|
||||
rare. On the pager there's no touch fallback, so plain keyboard/encoder
|
||||
"next field" navigation blurs a field on every step: arrowing through the
|
||||
6-field row re-triggered a real flash write + LR1121 reinit up to six
|
||||
times with zero edits made. Measured on hardware via `[STALL] ui:lvgl`
|
||||
(the callback runs synchronously inside LVGL's event dispatch, so its
|
||||
blocking I/O gets attributed to the `ui:lvgl` checkpoint even though it
|
||||
isn't a rendering cost): 6 escalating stalls per navigation pass, up to
|
||||
8.7s each. **Fix**: skip `setRadioParams()`/region persistence on a
|
||||
silent (DEFOCUSED) blur when the parsed field values already match
|
||||
`NodePrefs`, gated `#if defined(TLORA_PAGER)` so touch boards keep their
|
||||
original always-save-on-blur behavior unchanged. Post-fix hardware
|
||||
capture: one `[STALL] ui:lvgl 702ms` (one-time settings-modal build cost)
|
||||
instead of the 6-stall escalation, user-confirmed "much better now".
|
||||
13. **Unread-message LED notification (screen-off glanceable indicator) —
|
||||
INVESTIGATED, DEFERRED, not implemented (2026-07-10).** User asked whether
|
||||
this board could get a Tanmatsu-style "glance at the device anytime and
|
||||
see there's an unread message" indicator while the screen is off. Confirmed
|
||||
from multiple sources — this repo's `pins_arduino.h` ("no onboard status
|
||||
LED"), the full XL9555 channel map and I²C device list in the hardware
|
||||
facts above, the trail-mate reference port (its `board_facts.h` doesn't
|
||||
even carry an `led_present` field), and freshly-fetched vendor sources
|
||||
(LilyGoLib's `docs/hardware/lilygo-t-lora-pager.md` hardware doc and its
|
||||
actual `LilyGo_LoRa_Pager.cpp`/`.h` firmware — the same file that already
|
||||
caught the DISP_RST/ch6 bug, so trusted as ground truth here too) — **this
|
||||
board has no onboard LED of any kind.** Tanmatsu has one: a discrete
|
||||
RGB "envelope" LED on its CH32 coprocessor (`UITask.cpp:2435`,
|
||||
`msgLedRefresh()`/`msgLedFlash()`) that breathes green while unread and
|
||||
flashes white on arrival, independent of screen power. The pager's only
|
||||
real substitute would be pulsing `KB_BACKLIGHT` (GPIO46, wired up this
|
||||
session via `updatePagerKbBacklight()`), but that's the backlight for the
|
||||
ENTIRE physical keyboard, not a single low-current indicator LED — a
|
||||
continuous Tanmatsu-style "breathe while unread" pattern on it would be a
|
||||
real, likely-not-small standby power cost for however long a message sits
|
||||
unread (hours), unlike Tanmatsu's tiny dedicated LED. The bounded
|
||||
alternative (a single brief flash on arrival, mirroring the T-Deck's
|
||||
existing opt-in "Flash on new message", `UITask.cpp:10446`) has negligible
|
||||
power cost but was explicitly rejected by the user as not solving the
|
||||
actual want: a flash you have to be looking at the instant it happens
|
||||
isn't a "come back later and glance at it" indicator, just a gimmick.
|
||||
**Decision: do not implement either variant.** Revisit only if this board
|
||||
ever gets a hardware revision with a real low-power indicator LED, or if a
|
||||
software-only always-partially-on approach becomes acceptable (not
|
||||
currently the case).
|
||||
|
||||
## References
|
||||
|
||||
- Upstream MeshCore (MIT): `meshcore-dev/MeshCore` — does **NOT** currently have
|
||||
`variants/lilygo_tlora_pager/` or `CustomLR1121*` (re-verified 2026-07-06, see
|
||||
Decision ②); only `CustomLR1110{,Wrapper}.h` exists in `src/helpers/radiolib/`,
|
||||
same as our own core fork. Flasher precedent (unrelated to firmware source):
|
||||
`flasher.meshcore.io/lilygo-t-lora-pager/`
|
||||
- Local working port (pin map + LR1121 init + keymap + AW9364/BQ27220/XL9555 usage):
|
||||
`~/dev/trail-mate/boards/tlora_pager/` (esp. `src/tlora_pager_board.cpp initLoRa()`,
|
||||
`include/boards/tlora_pager/tlora_pager_board.h`) and
|
||||
`~/dev/trail-mate/variants/lilygo_tlora_pager/pins_arduino.h`
|
||||
- Canonical pin map (ground truth, matches trail-mate exactly): [`espressif/arduino-esp32`
|
||||
`variants/lilygo_tlora_pager/pins_arduino.h`](https://github.com/espressif/arduino-esp32/blob/master/variants/lilygo_tlora_pager/pins_arduino.h)
|
||||
- Official hardware doc (chip list, I²C addresses, power-rail table, electrical
|
||||
specs): [LilyGoLib `docs/hardware/lilygo-t-lora-pager.md`](https://github.com/Xinyuan-LilyGO/LilyGoLib/blob/master/docs/hardware/lilygo-t-lora-pager.md) —
|
||||
see the caveat under "Hardware / platform facts" above about its Pins Map
|
||||
table's internal inconsistencies.
|
||||
- Hardware docs: LilyGo product page (T-LoRa Pager), CNX-Software 2025-08-12 writeup,
|
||||
Meshtastic device page (`meshtastic.org/docs/hardware/devices/lilygo/tpager/`)
|
||||
- In-repo templates: `variants/lilygo_tdeck/` (S3 + SPI radio + shared bus),
|
||||
Tanmatsu keypad-nav path in `src/ui-touch/UITask.cpp`, `TANMATSU_PORT.md` (tracker
|
||||
precedent)
|
||||
- TFT_eSPI's ST7796 driver support (ground truth for the CGRAM_OFFSET/
|
||||
colstart/rowstart mechanism, Decision ④): `.pio/libdeps/*/TFT_eSPI/TFT_Drivers/{ST7796_Rotation.h,ST7796_Defines.h}`,
|
||||
cross-checked against `ST7789_Defines.h` and `setAddrWindow()` in
|
||||
`TFT_eSPI.cpp` — pulled via the pinned `bodmer/TFT_eSPI @ ^2.5.43` lib_dep.
|
||||
- AW9364 backlight driver actually consumed (not hand-rolled): `lewisxhe/SensorLib`'s
|
||||
`AW9364LedDriver.hpp` (MIT, header-only) — reference copy at
|
||||
`~/dev/trail-mate/.pio/libdeps/*/SensorLib/src/AW9364LedDriver.hpp`.
|
||||
@@ -0,0 +1,239 @@
|
||||
# T-LoRa Pager: Keyboard & Rotary Encoder Guide
|
||||
|
||||
The LilyGo T-LoRa Pager has **no touchscreen and no trackball** — every
|
||||
screen, every setting, and every chat is driven entirely by the physical
|
||||
QWERTY keyboard and the rotary encoder knob. If you're coming from the
|
||||
T-Deck or Heltec V4 (touch-first), this is the one board where learning a
|
||||
handful of gestures up front pays off immediately. This guide covers both
|
||||
radio variants (LR1121 and SX1262) — the UI and input handling are identical
|
||||
on both.
|
||||
|
||||
## The two inputs
|
||||
|
||||
- **Keyboard** — a physical QWERTY matrix with a Space bar, Backspace, Enter,
|
||||
a Shift key, and an orange **Fn/Alt** key (bottom-left). No dedicated Esc,
|
||||
arrow keys, or number row — those are covered by combos below.
|
||||
- **Rotary encoder** — turn to move the on-screen focus highlight; click
|
||||
(press the knob) to select. It's your only pointing device, so almost
|
||||
every screen is navigable with turn + click alone.
|
||||
|
||||
Everything below only fires while you're **not** actively typing into a text
|
||||
field — if a field is focused and you're typing into it, letters type
|
||||
normally and these combos step out of the way.
|
||||
|
||||
## Rotary encoder
|
||||
|
||||
| Gesture | Action | Keyboard equivalent |
|
||||
|---|---|---|
|
||||
| Turn | Move focus to the next/previous item on screen | **Fn (Alt) tapped alone** moves forward one step (NEXT only — no keyboard way to go backward) |
|
||||
| Short click | Select / confirm the focused item | **Enter** |
|
||||
| Hold ~1 s, then release | **Back**: closes a popup → closes an open chat → goes Home → Esc (whichever applies first) | **Backspace held ~1 s** |
|
||||
| **Fn (Alt) + turn**, on a main tab | Jump directly between the 5 main tabs (Chats / Contacts / Home / Map / Settings) | none — encoder only |
|
||||
| **Fn (Alt) + turn**, inside a settings page or chat | Scroll the page up/down | none — encoder only |
|
||||
| Turn, with a dropdown open | Scroll through the dropdown's options | none — encoder only |
|
||||
| Turn, with the accent picker open | Cycle through the accent variants (see below) | none — encoder only (Fn+Space to enter is shared) |
|
||||
| Turn, with the @-mention list open | Cycle through matching contacts (see below) | none — encoder only |
|
||||
| Turn, at the oldest/newest **loaded** message in an open chat | Loads more history in that direction and keeps going — see [Chat screen](#chat-screen) | none — encoder only |
|
||||
|
||||
The knob doubles as your only way to reach the bottom tab bar — since
|
||||
there's no touch to tap an icon, **Fn+turn** while on any main tab is the
|
||||
fastest way to switch sections.
|
||||
|
||||
## Chat screen
|
||||
|
||||
An open chat has no touch-only jump-to-oldest/jump-to-latest buttons on this
|
||||
board — everything is reachable by turning and clicking, and the encoder
|
||||
behaves a bit differently here than anywhere else in the app.
|
||||
|
||||
### Turning through messages
|
||||
|
||||
Message history is loaded lazily: only the messages currently near the
|
||||
viewport actually exist as on-screen items, and older/newer ones are pulled in
|
||||
as you approach them. Turning the encoder normally moves the focus highlight
|
||||
one message at a time, and **at the edge of what's currently loaded, it keeps
|
||||
going** — the turn instead scrolls the list to pull in the next batch of
|
||||
history, then continues moving the highlight onto it. You'll only ever land
|
||||
outside the message list (see below) once you've genuinely reached the very
|
||||
oldest or very newest message in the whole conversation — never mid-history.
|
||||
|
||||
### Leaving the message list
|
||||
|
||||
Turning **past the oldest loaded message** (top edge), once there's truly
|
||||
nothing more to load, wraps around to whatever the last focusable item on the
|
||||
screen is — the channel-settings gear in an open **channel**, or the Send
|
||||
button in a **DM** (a DM has no gear to reach). This is plain focus wrap, the
|
||||
same as reaching the end of any other list in the app; there's no chat-specific
|
||||
handling for this edge.
|
||||
|
||||
Turning **past the newest loaded message** (bottom edge) moves into the reply
|
||||
row below the messages: quick-reply (△) → emoji → text box → Send. Turning
|
||||
further past Send *is* chat-specific: in a channel it goes to the
|
||||
channel-settings gear; in a DM (nothing to reach there) it returns you to the
|
||||
newest message instead.
|
||||
|
||||
### The reply row
|
||||
|
||||
Below the messages, turning steps through: **quick-reply (△) → emoji →
|
||||
text box → Send**, in that order, in both directions — nothing is skipped.
|
||||
Turning past the △ chip (toward the messages) always jumps straight to the
|
||||
newest message, regardless of channel or DM.
|
||||
|
||||
### Backspace and Fn (Alt) — leaving vs. catching up
|
||||
|
||||
These two keys are your shortcuts in and out of "reading mode" on this
|
||||
screen, and they do different things depending on whether you've got unread
|
||||
messages:
|
||||
|
||||
- **Fn (Alt) tapped alone** — jumps straight to the **newest** message and
|
||||
moves focus into the text box, ready to type a reply. This is the
|
||||
deliberate "I'm done reading, let me respond" gesture — it works regardless
|
||||
of where your focus currently is in the chat.
|
||||
- **Backspace (tap)** — jumps to whichever message needs your attention:
|
||||
- If there are unread messages, it jumps to the first one — right below the
|
||||
**"NEW ----"** divider — and selects it, so you can then turn forward
|
||||
through your unread messages in order, oldest-of-the-unread first.
|
||||
- If nothing is unread, it jumps to the newest message instead (same
|
||||
destination as Fn+Alt, but leaves focus on the message itself rather than
|
||||
the text box).
|
||||
|
||||
Backspace's override above only applies while you're inside an open chat and
|
||||
*not* actively editing the text box — the usual rule from the top of this
|
||||
guide; with a field focused, Backspace deletes a character as normal.
|
||||
Fn (Alt) tap's override fires the same way whether or not the text box
|
||||
already has focus, since landing there is the whole point of the gesture.
|
||||
|
||||
## Keyboard layout
|
||||
|
||||
Base layer (no modifier):
|
||||
|
||||
```
|
||||
q w e r t y u i o p
|
||||
a s d f g h j k l [Enter]
|
||||
z x c v b n m
|
||||
[Space]
|
||||
```
|
||||
|
||||
Hold **Fn (Alt)** for numbers/symbols instead:
|
||||
|
||||
```
|
||||
1 2 3 4 5 6 7 8 9 0
|
||||
* / + - = : ' " @
|
||||
_ $ ; ? ! , .
|
||||
[Space]
|
||||
```
|
||||
|
||||
### Shift and Caps Lock
|
||||
|
||||
- **Hold Shift + a letter**: that letter (or letters, for as long as you
|
||||
hold Shift) types uppercase — released, typing goes back to lowercase.
|
||||
Real momentary Shift, just like a normal keyboard.
|
||||
- **Hold Fn (Alt), then press Shift**: while you're editing a text field,
|
||||
toggles **Caps Lock** on/off — stays uppercase until you repeat the chord.
|
||||
Anywhere else (not editing a field), the chord does nothing.
|
||||
- Shift alone, tapped with nothing else, does nothing (as expected).
|
||||
- **Hold Fn (Alt), then press Backspace**: jumps straight to the **Home**
|
||||
screen — works everywhere, including while you're actively editing a field
|
||||
(unlike Alt+Shift above, this one isn't context-dependent). Doesn't delete
|
||||
a character or trigger the plain-Backspace gestures below.
|
||||
|
||||
### Special keys
|
||||
|
||||
| Key | While editing a text field | Otherwise |
|
||||
|---|---|---|
|
||||
| **Enter** | Send / submit / newline | Select the focused item — or, if a chat message bubble is focused, opens its action menu (Ack / Mention / Copy / Info / Block) |
|
||||
| **Backspace** (tap) | Delete a character | In an open chat: jump to your first unread message, or the newest message if nothing's unread — see [Chat screen](#chat-screen) |
|
||||
| **Backspace** (hold ~1 s) | — | Same as the encoder's long-press: **Back** |
|
||||
| **Backspace** (hold ~1 s) *while the screen is locked* | — | Unlock the screen |
|
||||
| **Space** (tap) | Types a space | — |
|
||||
| **Space** (double-tap, within 250 ms) | Switches between English and your configured secondary keyboard layout | — |
|
||||
| **Space** (hold ~1 s) | — | Locks the screen (shows a "Locking…" progress bar; tapping any key cancels) |
|
||||
| **Fn (Alt)** tapped alone (press+release, nothing else) | — | Moves focus to the next field/item — a keyboard-only substitute for a turn of the encoder. In an open chat: jumps to the newest message and focuses the text box instead — see [Chat screen](#chat-screen) |
|
||||
|
||||
The **BOOT** button (top of the device) instantly wakes the screen from
|
||||
idle-dim. It does *not* unlock a screen you've manually locked with the
|
||||
Space-hold gesture above — for that, hold Backspace.
|
||||
|
||||
## Accented characters
|
||||
|
||||
Typing a letter that has accent variants (all vowels, plus `n c s y t z l r`,
|
||||
both cases) pops up a small row of alternatives above the field. To pick one
|
||||
without touch:
|
||||
|
||||
1. **Fn (Alt) + Space** — jumps focus into the popup.
|
||||
2. **Turn the encoder** — cycles through the variants.
|
||||
3. **Enter** — confirms your choice and replaces the letter you just typed.
|
||||
4. **Backspace** — dismisses the popup and keeps the plain letter.
|
||||
|
||||
## @-mentions
|
||||
|
||||
Typing `@` followed by a few letters of a contact's name pops up a matching
|
||||
list in a channel/group chat. Unlike the accent picker above, this one grabs
|
||||
the encoder **immediately** — no Fn+Space needed, since it only appears once
|
||||
you've deliberately started typing a mention:
|
||||
|
||||
1. **Turn the encoder** — cycles through the matching contacts.
|
||||
2. **Enter / encoder click** — confirms the highlighted one, replacing
|
||||
`@partial` with `@FullName ` in your message.
|
||||
3. **Backspace** — dismisses the list without touching what you typed.
|
||||
|
||||
## Sliders
|
||||
|
||||
Any focused slider (Control Center brightness, a Settings slider, the Map
|
||||
zoom bar) can't be dragged without touch — instead:
|
||||
|
||||
- **Q** — decrease
|
||||
- **E** — increase
|
||||
|
||||
Each press moves it proportionally (about 20 presses end-to-end) and
|
||||
persists the new value immediately, the same as releasing a drag.
|
||||
|
||||
## Map screen panning
|
||||
|
||||
While the **Map** tab is active:
|
||||
|
||||
- **W** — pan north
|
||||
- **A** — pan west
|
||||
- **S** — pan south
|
||||
- **D** — pan east
|
||||
|
||||
Each press shifts the view a quarter-screen and reloads tiles/markers as
|
||||
needed.
|
||||
|
||||
## Keyboard backlight
|
||||
|
||||
**Settings → Keyboard → Keyboard backlight** cycles through three modes
|
||||
(tap to advance):
|
||||
|
||||
- **Off** — always dark
|
||||
- **On** — always lit
|
||||
- **Auto** (default) — lights up on any keypress, turns off ~3 seconds after
|
||||
the last one
|
||||
|
||||
Unlike the T-Deck, this is a plain on/off strip under the keys, not a
|
||||
dimmable brightness curve.
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Input | Action |
|
||||
|---|---|
|
||||
| Turn encoder (or Fn tap = NEXT only) | Move focus |
|
||||
| Turn, at the loaded edge of a chat | Load more history, keep moving — only exits the list at the true oldest/newest message |
|
||||
| Click encoder (or Enter) | Select / confirm |
|
||||
| Hold encoder ~1s (or hold Backspace ~1s) | Back |
|
||||
| Fn + turn (main tab) | Switch tabs |
|
||||
| Fn + turn (page/chat) | Scroll |
|
||||
| Fn tap alone | Next field (in a chat: jump to newest message + focus the text box) |
|
||||
| Hold Shift + letter | Momentary uppercase |
|
||||
| Fn + Shift (editing a field) | Toggle Caps Lock |
|
||||
| Fn + Shift (not editing a field) | Nothing |
|
||||
| Fn + Backspace (anywhere) | Jump to Home |
|
||||
| Fn + Space | Enter accent picker |
|
||||
| @ + letters (composer) | Auto-focuses the mention list — no Fn+Space needed |
|
||||
| Enter | Select / send / message action menu |
|
||||
| Backspace (tap) | Delete / in a chat: jump to first unread message, or newest if none |
|
||||
| Backspace (hold 1s) | Back, or unlock if locked |
|
||||
| Space (tap) | Space |
|
||||
| Space (double-tap) | Switch keyboard language |
|
||||
| Space (hold 1s) | Lock screen |
|
||||
| Q / E (slider focused) | Decrease / increase |
|
||||
| W / A / S / D (Map tab) | Pan map north/west/south/east |
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_16MB.csv",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "opi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "attaky_mesh_series"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Attaky Core (ESP32-S3-WROOM-1U-N16R8, 16 MB FLASH, 8 MB OCTAL PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200
|
||||
},
|
||||
"url": "https://attaky.com/",
|
||||
"vendor": "attaky"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_16MB.csv",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "opi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "heltec_v4"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "heltec_wifi_lora_32 v4-R8 (16 MB FLASH, 8 MB OCTAL PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://heltec.org/",
|
||||
"vendor": "heltec"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "app3M_fat9M_16MB.csv",
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x82D4"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "lilygo_tlora_pager_pins",
|
||||
"variants_dir": "variants"
|
||||
},
|
||||
"connectivity": ["wifi"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "LilyGo T-LoRa-Pager (16M Flash 8M QSPI PSRAM )",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.lilygo.cc",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_16MB.csv",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "dio",
|
||||
"psram_type": "qspi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "RAK WisMesh Tap V2 (16 MB FLASH, 8 MB Octal PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 8388608,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": false,
|
||||
"wait_for_upload_port": false,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "https://store.rakwireless.com/products/wismesh-tap-v2",
|
||||
"vendor": "RAKwireless"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"build": {
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1"
|
||||
],
|
||||
"f_cpu": "400000000L",
|
||||
"f_flash": "80000000L",
|
||||
"f_psram": "200000000L",
|
||||
"flash_mode": "qio",
|
||||
"mcu": "esp32p4",
|
||||
"variant": "esp32p4"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"debug": { "openocd_target": "esp32p4.cfg" },
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Tanmatsu (ESP32-P4 + C6)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 512000,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 1500000
|
||||
},
|
||||
"url": "https://nicolaielectronics.nl/",
|
||||
"vendor": "Nicolai Electronics"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_16MB.csv",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "opi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora", "gps"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Elecrow ThinkNode M9 (16 MB FLASH, 8 MB PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.elecrow.com/",
|
||||
"vendor": "Elecrow"
|
||||
}
|
||||
@@ -3,12 +3,25 @@
|
||||
Distribution stack for wadamesh: a **VPS nginx origin behind Cloudflare**.
|
||||
|
||||
```
|
||||
tiles.wadamesh.com →CF (HTTP, edge-cached) → nginx → tile.openstreetmap.org
|
||||
tiles.wadamesh.com →CF (HTTP, edge-cached) → nginx → OpenStreetMap / OpenTopoMap
|
||||
firmware.wadamesh.com →CF (cache bins) → nginx → /srv/wadamesh/firmware
|
||||
flasher.wadamesh.com →CF (HTTPS) → web flasher (TODO — see below)
|
||||
wadamesh.com →CF (HTTPS) → landing page (later)
|
||||
```
|
||||
|
||||
**Map tile styles.** The default `/{z}/{x}/{y}.jpg` route serves **OpenStreetMap**
|
||||
(the firmware default). An opt-in **OpenTopoMap** topographic style is served from
|
||||
`/opentopo/{z}/{x}/{y}.jpg` (explicit OSM alias at `/osm/...`); the device requests
|
||||
it only when the user enables *Map → Options → Topographic map*. Legal: OpenTopoMap
|
||||
map tiles are **© OpenTopoMap (CC-BY-SA)** over **© OpenStreetMap contributors
|
||||
(ODbL) + SRTM** — the touch UI shows that attribution when topo is active, and the
|
||||
14-day disk cache keeps each tile hitting OpenTopoMap at most once per fortnight
|
||||
(their tile-usage policy asks for a contactable UA + caching, both of which the
|
||||
transcode service provides). Deploying the topo routes = update
|
||||
`tiles.wadamesh.com.conf` + `tile-transcode.py`, then
|
||||
`systemctl restart wadamesh-tile-transcode && nginx -t && systemctl reload nginx`
|
||||
and purge the Cloudflare cache for `tiles.wadamesh.com/opentopo/*`.
|
||||
|
||||
The firmware fetches **tiles + the update-check over plain HTTP** (on-device HTTPS
|
||||
isn't viable — mbedTLS needs ~30 KB heap, only ~5 KB is free post-Wi-Fi), so the
|
||||
tile + firmware hosts must stay reachable over HTTP. Cloudflare provides the edge
|
||||
|
||||
@@ -42,39 +42,66 @@ if os.path.exists(nf):
|
||||
notes = "\n".join(l.strip() for l in open(nf, encoding="utf-8")
|
||||
if l.strip() and not l.strip().startswith("#"))
|
||||
|
||||
# Both touch boards are ESP32-S3, single touch-UI ("gui") role. ota_0 sits at
|
||||
# 0x10000 on both (partitions_t*_touch.csv), so flash-update@0x10000 is correct.
|
||||
# Every wadamesh tile here is ESP32-S3, touch-UI ("gui") role. ota_0 sits at
|
||||
# 0x10000 on every touch env (partitions_t*_touch.csv), so flash-update@0x10000 is correct.
|
||||
# `name` MUST match the official MeshCore device name character-for-character, or Mesh America lists it
|
||||
# as a NEW device instead of folding our firmware into the existing tile (TJ Downes, 2026-06). Canonical
|
||||
# names verified from apps.meshamerica.com/proxy/flasher/config.json. The V4 entry is the Expansion Kit
|
||||
# (Touch) variant only, per Kaj. `slug` still maps to our bin filenames on firmware.wadamesh.com.
|
||||
#
|
||||
# A board may carry `variants` instead of a single `slug`: one "gui" firmware entry is emitted per
|
||||
# variant, all under the one tile. The T-Lora Pager ships with either radio, and Mesh America folds both
|
||||
# under a single "LilyGo T-Lora Pager" tile distinguished by a [RADIO] title prefix — matching how their
|
||||
# own stock pager entries are labelled ([SX1262]/[LR1121]), so ours sit naturally alongside them.
|
||||
BOARDS = [
|
||||
{"name": "wadamesh — Heltec V4 TFT", "slug": "heltec-v4-tft"},
|
||||
{"name": "wadamesh — LilyGo T-Deck", "slug": "tdeck"},
|
||||
{"name": "Heltec v4 + Expansion Kit (Touch)", "slug": "heltec-v4-tft"},
|
||||
{"name": "LilyGo T-Deck", "slug": "tdeck"},
|
||||
{"name": "LilyGo T-Lora Pager", "variants": [
|
||||
{"title": "[LR1121] Touch UI", "slug": "tlora-pager-lr1121"},
|
||||
{"title": "[SX1262] Touch UI", "slug": "tlora-pager-sx1262"},
|
||||
]},
|
||||
]
|
||||
|
||||
def _firmware(title, slug):
|
||||
merged, appbin = "wadamesh-%s-merged.bin" % slug, "wadamesh-%s.bin" % slug
|
||||
return {
|
||||
"role": "gui",
|
||||
"title": title,
|
||||
"version": {TAG: {
|
||||
"notes": notes,
|
||||
"files": [
|
||||
{"type": "flash-wipe", "name": merged,
|
||||
"url": "%s/%s" % (BASE, merged),
|
||||
"title": "Full install (bootloader + firmware)"},
|
||||
{"type": "flash-update", "name": appbin,
|
||||
"url": "%s/%s" % (BASE, appbin),
|
||||
"title": "Update (app only)"},
|
||||
],
|
||||
}},
|
||||
}
|
||||
|
||||
def device(b):
|
||||
merged, appbin = "wadamesh-%s-merged.bin" % b["slug"], "wadamesh-%s.bin" % b["slug"]
|
||||
# `variants` (per-radio builds) fold under one tile; a plain board is the single default variant.
|
||||
variants = b.get("variants") or [{"title": "Touch UI", "slug": b["slug"]}]
|
||||
return {
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": b["name"],
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='%s'>" % ICON,
|
||||
"firmware": [{
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {TAG: {
|
||||
"notes": notes,
|
||||
"files": [
|
||||
{"type": "flash-wipe", "name": merged,
|
||||
"url": "%s/%s" % (BASE, merged),
|
||||
"title": "Full install (bootloader + firmware)"},
|
||||
{"type": "flash-update", "name": appbin,
|
||||
"url": "%s/%s" % (BASE, appbin),
|
||||
"title": "Update (app only)"},
|
||||
],
|
||||
}},
|
||||
}],
|
||||
"firmware": [_firmware(v["title"], v["slug"]) for v in variants],
|
||||
}
|
||||
|
||||
# Optional "what's this?" blurb Mesh America shows on a hover tooltip next to our provider badge
|
||||
# (spec section 3, added by TJ 2026-06-30). Plain text only — rendered escaped, so no markup/links.
|
||||
DESCRIPTION = (
|
||||
"WADAMESH is a full touch-screen interface for MeshCore: on-device chat, contacts, a live map "
|
||||
"with offline tiles, and complete radio settings. Same MeshCore protocol and phone-app companion "
|
||||
"as stock MeshCore, just a richer on-device front-end."
|
||||
)
|
||||
|
||||
catalog = {
|
||||
"description": DESCRIPTION,
|
||||
"maker": {"wadamesh": {"name": "wadamesh"}},
|
||||
"device": [device(b) for b in BOARDS],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"description": "WADAMESH is a full touch-screen interface for MeshCore: on-device chat, contacts, a live map with offline tiles, and complete radio settings. Same MeshCore protocol and phone-app companion as stock MeshCore, just a richer on-device front-end.",
|
||||
"maker": {
|
||||
"wadamesh": {
|
||||
"name": "wadamesh"
|
||||
@@ -8,7 +9,7 @@
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "wadamesh — Heltec V4 TFT",
|
||||
"name": "Heltec v4 + Expansion Kit (Touch)",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
@@ -16,19 +17,19 @@
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {
|
||||
"beta_10": {
|
||||
"notes": "",
|
||||
"beta_50": {
|
||||
"notes": "Fixed the on-device updater downloading the wrong firmware on several boards. The V4-R8, ThinkNode M9, RAK Tap V2, T-LoRa Pager and Attaky now each pull their own build instead of the Heltec V4 TFT one.\nT-Display P4: the \"Get test builds (beta)\" toggle and the on-device update now appear. They were hidden before, so the P4 could only be updated over USB. Newly enabled, please test and report.\nLock screen fixed: tap Backspace to peek the screen without unlocking, hold Backspace to unlock. The clock and text are repositioned to fit the wide screen and stay readable.\nOn-screen keyboard now works with the detachable keyboard module: the keys stay down while the module is attached, and the module's # key brings them back up for symbols. The POWER button toggles the screen and wakes the board.\nBoard fixes: correct LoRa clock (no false TCXO), GPS baud, and sensor probing that was colliding with the touch and keyboard chips.",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-heltec-v4-tft-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-heltec-v4-tft-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-heltec-v4-tft-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-heltec-v4-tft.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-heltec-v4-tft.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-heltec-v4-tft.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
@@ -40,7 +41,7 @@
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "wadamesh — LilyGo T-Deck",
|
||||
"name": "LilyGo T-Deck",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
@@ -48,19 +49,74 @@
|
||||
"role": "gui",
|
||||
"title": "Touch UI",
|
||||
"version": {
|
||||
"beta_10": {
|
||||
"notes": "",
|
||||
"beta_50": {
|
||||
"notes": "Fixed the on-device updater downloading the wrong firmware on several boards. The V4-R8, ThinkNode M9, RAK Tap V2, T-LoRa Pager and Attaky now each pull their own build instead of the Heltec V4 TFT one.\nT-Display P4: the \"Get test builds (beta)\" toggle and the on-device update now appear. They were hidden before, so the P4 could only be updated over USB. Newly enabled, please test and report.\nLock screen fixed: tap Backspace to peek the screen without unlocking, hold Backspace to unlock. The clock and text are repositioned to fit the wide screen and stay readable.\nOn-screen keyboard now works with the detachable keyboard module: the keys stay down while the module is attached, and the module's # key brings them back up for symbols. The POWER button toggles the screen and wakes the board.\nBoard fixes: correct LoRa clock (no false TCXO), GPS baud, and sensor probing that was colliding with the touch and keyboard chips.",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-tdeck-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-tdeck-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-tdeck-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-tdeck.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_10/wadamesh-tdeck.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-tdeck.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"maker": "wadamesh",
|
||||
"class": "wadamesh",
|
||||
"name": "LilyGo T-Lora Pager",
|
||||
"type": "esp32",
|
||||
"tooltip": "<img class='device' src='https://wadamesh.com/wadamesh-icon-512.png'>",
|
||||
"firmware": [
|
||||
{
|
||||
"role": "gui",
|
||||
"title": "[LR1121] Touch UI",
|
||||
"version": {
|
||||
"beta_50": {
|
||||
"notes": "Fixed the on-device updater downloading the wrong firmware on several boards. The V4-R8, ThinkNode M9, RAK Tap V2, T-LoRa Pager and Attaky now each pull their own build instead of the Heltec V4 TFT one.\nT-Display P4: the \"Get test builds (beta)\" toggle and the on-device update now appear. They were hidden before, so the P4 could only be updated over USB. Newly enabled, please test and report.\nLock screen fixed: tap Backspace to peek the screen without unlocking, hold Backspace to unlock. The clock and text are repositioned to fit the wide screen and stay readable.\nOn-screen keyboard now works with the detachable keyboard module: the keys stay down while the module is attached, and the module's # key brings them back up for symbols. The POWER button toggles the screen and wakes the board.\nBoard fixes: correct LoRa clock (no false TCXO), GPS baud, and sensor probing that was colliding with the touch and keyboard chips.",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-tlora-pager-lr1121-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-tlora-pager-lr1121-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-tlora-pager-lr1121.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-tlora-pager-lr1121.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "gui",
|
||||
"title": "[SX1262] Touch UI",
|
||||
"version": {
|
||||
"beta_50": {
|
||||
"notes": "Fixed the on-device updater downloading the wrong firmware on several boards. The V4-R8, ThinkNode M9, RAK Tap V2, T-LoRa Pager and Attaky now each pull their own build instead of the Heltec V4 TFT one.\nT-Display P4: the \"Get test builds (beta)\" toggle and the on-device update now appear. They were hidden before, so the P4 could only be updated over USB. Newly enabled, please test and report.\nLock screen fixed: tap Backspace to peek the screen without unlocking, hold Backspace to unlock. The clock and text are repositioned to fit the wide screen and stay readable.\nOn-screen keyboard now works with the detachable keyboard module: the keys stay down while the module is attached, and the module's # key brings them back up for symbols. The POWER button toggles the screen and wakes the board.\nBoard fixes: correct LoRa clock (no false TCXO), GPS baud, and sensor probing that was colliding with the touch and keyboard chips.",
|
||||
"files": [
|
||||
{
|
||||
"type": "flash-wipe",
|
||||
"name": "wadamesh-tlora-pager-sx1262-merged.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-tlora-pager-sx1262-merged.bin",
|
||||
"title": "Full install (bootloader + firmware)"
|
||||
},
|
||||
{
|
||||
"type": "flash-update",
|
||||
"name": "wadamesh-tlora-pager-sx1262.bin",
|
||||
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_50/wadamesh-tlora-pager-sx1262.bin",
|
||||
"title": "Update (app only)"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -25,12 +25,22 @@ server {
|
||||
root /srv/wadamesh/firmware;
|
||||
|
||||
# The update-check feed — served at the no-slash path the firmware requests.
|
||||
# STABLE channel (the default on-device update check).
|
||||
location = /releases/TOUCH {
|
||||
default_type application/json;
|
||||
alias /srv/wadamesh/firmware/releases/TOUCH/index.json;
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
}
|
||||
|
||||
# Same, for the opt-in TEST/BETA channel — what a device requests when the
|
||||
# "Get test builds (beta)" switch is on. Without this exact-match the no-slash
|
||||
# path 301-redirects to a dir with no index and the beta check comes back empty.
|
||||
location = /releases/BETA {
|
||||
default_type application/json;
|
||||
alias /srv/wadamesh/firmware/releases/BETA/index.json;
|
||||
add_header Cache-Control "public, max-age=60";
|
||||
}
|
||||
|
||||
# Bins + everything else under /releases/. CORS so the browser flasher
|
||||
# (flasher.wadamesh.com) can fetch the images cross-origin.
|
||||
location /releases/ {
|
||||
@@ -41,7 +51,7 @@ server {
|
||||
}
|
||||
|
||||
# Rolling "latest" merged images — the web flasher's manifests point here so
|
||||
# they always install the newest build (release.sh refreshes this dir).
|
||||
# they always install the newest build (release.sh refreshes this dir). STABLE.
|
||||
location /latest/ {
|
||||
autoindex off;
|
||||
types { application/octet-stream bin; }
|
||||
@@ -49,5 +59,15 @@ server {
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
|
||||
# Rolling "latest" for the TEST/BETA channel — the beta web-flasher feed
|
||||
# (version.json + manifests + rolling bins). Without this it falls to the
|
||||
# catch-all 404 and the flasher's beta toggle can't load metadata.
|
||||
location /latest-beta/ {
|
||||
autoindex off;
|
||||
types { application/octet-stream bin; }
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
}
|
||||
|
||||
location / { return 404; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
# tiles.wadamesh.com — OSM tile proxy for the wadamesh map tab.
|
||||
# tiles.wadamesh.com — map-tile proxy for the wadamesh map tab. Default style is
|
||||
# OpenStreetMap; an opt-in OpenTopoMap topographic style is served from /opentopo/.
|
||||
#
|
||||
# The firmware fetches plain HTTP (on-device HTTPS isn't workable: mbedTLS wants
|
||||
# ~30 KB heap for a handshake and only ~5 KB is free after Wi-Fi associates), so
|
||||
@@ -62,6 +63,49 @@ server {
|
||||
proxy_connect_timeout 5s;
|
||||
}
|
||||
|
||||
# OpenTopoMap (topographic) — OPT-IN alternate style. The firmware requests
|
||||
# this path only when the user enables "Topographic map"; the default
|
||||
# /{z}/{x}/{y}.jpg above stays OpenStreetMap. Legal: map © OpenTopoMap
|
||||
# (CC-BY-SA), data © OpenStreetMap contributors + SRTM — the touch UI shows
|
||||
# that attribution when topo is selected. The 14-day disk cache means each
|
||||
# tile hits OpenTopoMap at most once per fortnight (their tile policy asks it).
|
||||
location ~ "^/opentopo/(?<z>[0-9]{1,2})/(?<x>[0-9]{1,7})/(?<y>[0-9]{1,7})\.jpg$" {
|
||||
proxy_pass http://127.0.0.1:5005/opentopo/$z/$x/$y.jpg;
|
||||
proxy_set_header Host "tiles.wadamesh.com";
|
||||
|
||||
proxy_cache wadamesh_tiles;
|
||||
proxy_cache_key "opentopo-jpg:$z/$x/$y";
|
||||
proxy_cache_valid 200 14d;
|
||||
proxy_cache_valid 404 1d;
|
||||
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||
proxy_cache_lock on;
|
||||
add_header X-Cache-Status $upstream_cache_status always;
|
||||
add_header Cache-Control "public, max-age=2592000" always;
|
||||
|
||||
proxy_buffering on;
|
||||
proxy_read_timeout 20s;
|
||||
proxy_connect_timeout 5s;
|
||||
}
|
||||
|
||||
# Explicit OpenStreetMap alias (identical to the default root path above).
|
||||
location ~ "^/osm/(?<z>[0-9]{1,2})/(?<x>[0-9]{1,7})/(?<y>[0-9]{1,7})\.jpg$" {
|
||||
proxy_pass http://127.0.0.1:5005/osm/$z/$x/$y.jpg;
|
||||
proxy_set_header Host "tiles.wadamesh.com";
|
||||
|
||||
proxy_cache wadamesh_tiles;
|
||||
proxy_cache_key "osm-jpg:$z/$x/$y";
|
||||
proxy_cache_valid 200 14d;
|
||||
proxy_cache_valid 404 1d;
|
||||
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||
proxy_cache_lock on;
|
||||
add_header X-Cache-Status $upstream_cache_status always;
|
||||
add_header Cache-Control "public, max-age=2592000" always;
|
||||
|
||||
proxy_buffering on;
|
||||
proxy_read_timeout 20s;
|
||||
proxy_connect_timeout 5s;
|
||||
}
|
||||
|
||||
# Elevation profile for the line-of-sight analyzer (same local service).
|
||||
location = /elev {
|
||||
proxy_pass http://127.0.0.1:5005;
|
||||
@@ -98,7 +142,7 @@ server {
|
||||
# Tiny landing so a human hitting the bare hostname isn't met with 404.
|
||||
location = / {
|
||||
default_type text/plain;
|
||||
return 200 "wadamesh tile proxy\n\nUsage: GET /{z}/{x}/{y}.jpg (HTTP only)\nUpstream: tile.openstreetmap.org (PNG transcoded to JPEG)\n";
|
||||
return 200 "wadamesh tile proxy (HTTP only)\n\n /{z}/{x}/{y}.jpg -> OpenStreetMap (default)\n /osm/{z}/{x}/{y}.jpg -> OpenStreetMap\n /opentopo/{z}/{x}/{y}.jpg -> OpenTopoMap (CC-BY-SA)\n\nUpstream PNG transcoded to JPEG.\n";
|
||||
}
|
||||
|
||||
location / { return 404; }
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 81 KiB |
@@ -0,0 +1,605 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WADAMESH — Documentation</title>
|
||||
<meta name="description" content="User guide for the WADAMESH touch firmware on the LilyGo T-Deck: home, chat, contacts, map, settings and the built-in apps. Real device screenshots.">
|
||||
<link rel="icon" href="wadamesh-badge.svg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#ffffff; --bg2:#f4f6f2; --ink:#15181e; --mut:#5d6770;
|
||||
--line:#e5e8e2; --teal:#15b6a6; --teal-ink:#0e8d80;
|
||||
color-scheme:light;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html{scroll-behavior:smooth}
|
||||
body{font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,sans-serif; background:var(--bg); color:var(--ink); -webkit-font-smoothing:antialiased}
|
||||
a{color:var(--teal-ink); text-decoration:none}
|
||||
|
||||
.top{position:sticky; top:0; z-index:30; display:flex; align-items:center; gap:14px; padding:12px 22px;
|
||||
background:rgba(255,255,255,.88); backdrop-filter:saturate(1.2) blur(8px); border-bottom:1px solid var(--line)}
|
||||
.top .word{font-weight:700; letter-spacing:2px; font-size:1.05rem; color:var(--ink)}
|
||||
.top .word .t{color:var(--teal)}
|
||||
.top .tag{font-family:'JetBrains Mono',monospace; font-size:.66rem; letter-spacing:.18em; text-transform:uppercase; color:var(--mut); border:1px solid var(--line); border-radius:100px; padding:4px 9px}
|
||||
.top .sp{flex:1}
|
||||
.top .home{display:inline-flex; align-items:center; gap:7px; color:var(--mut); font-size:.86rem; border:1px solid var(--line); border-radius:100px; padding:6px 13px; background:#fff; transition:background .15s,color .15s,border-color .15s}
|
||||
.top .home:hover{color:var(--teal-ink); background:var(--bg2); border-color:#bfe6e0}
|
||||
.top .home svg{width:16px;height:16px}
|
||||
|
||||
.shell{max-width:1200px; margin:0 auto; display:grid; grid-template-columns:220px 1fr; gap:34px; padding:0 22px}
|
||||
nav.toc{position:sticky; top:64px; align-self:start; height:calc(100vh - 64px); overflow:auto; padding:26px 0 40px}
|
||||
nav.toc .grp{font-family:'JetBrains Mono',monospace; font-size:.68rem; letter-spacing:.13em; text-transform:uppercase; color:var(--mut); margin:20px 0 7px}
|
||||
nav.toc .grp:first-child{margin-top:0}
|
||||
nav.toc a{display:block; padding:6px 12px; border-radius:9px; color:#46525c; font-size:.92rem; border-left:2px solid transparent}
|
||||
nav.toc a:hover{background:var(--bg2); color:var(--ink)}
|
||||
nav.toc a.on{color:var(--teal-ink); background:rgba(21,182,166,.09); border-left-color:var(--teal); font-weight:600}
|
||||
|
||||
main{padding:30px 0 90px; min-width:0}
|
||||
.hero h1{font-size:clamp(1.9rem,4.4vw,2.6rem); letter-spacing:-.5px; font-weight:750}
|
||||
.hero p{color:var(--mut); max-width:64ch; margin-top:10px; font-size:1.05rem}
|
||||
.hero .meta{display:flex; gap:8px; flex-wrap:wrap; margin-top:16px}
|
||||
.chip{display:inline-flex; align-items:center; gap:6px; font-size:.8rem; color:#3a444d; border:1px solid var(--line); border-radius:100px; padding:5px 11px; background:#fff}
|
||||
.chip svg{width:14px;height:14px;color:var(--teal)}
|
||||
|
||||
section.doc{padding:36px 0; border-top:1px solid var(--line); scroll-margin-top:74px}
|
||||
.doc h2{font-size:1.5rem; font-weight:730; letter-spacing:-.3px; display:flex; align-items:center; gap:10px}
|
||||
.doc h2 .ico{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;flex:none; background:rgba(21,182,166,.12);color:var(--teal)}
|
||||
.doc h2 .ico svg{width:18px;height:18px}
|
||||
.row{display:grid; grid-template-columns:minmax(0,1fr) 372px; gap:34px; align-items:start; margin-top:16px}
|
||||
@media(max-width:980px){.row{grid-template-columns:1fr}}
|
||||
.prose{color:#3c454d; min-width:0}
|
||||
.prose p{margin:0 0 12px}
|
||||
.prose h3{font-size:1.02rem; margin:18px 0 6px; color:var(--ink)}
|
||||
.prose ul{margin:4px 0 12px; padding-left:18px}
|
||||
.prose li{margin:4px 0}
|
||||
.prose b{color:var(--ink)}
|
||||
.prose code{font-family:'JetBrains Mono',monospace; font-size:.85em; background:var(--bg2); border:1px solid var(--line); border-radius:6px; padding:1px 6px}
|
||||
.shot{position:sticky; top:74px}
|
||||
.cap{text-align:center; font-family:'JetBrains Mono',monospace; font-size:.68rem; color:var(--mut); margin-top:10px; letter-spacing:.04em}
|
||||
|
||||
/* T-Deck frame: the real screenshot + a generated QWERTY keyboard (added in JS) */
|
||||
.device{width:344px; margin:0 auto; background:#16191d; border-radius:16px; padding:12px 12px 9px; border:1px solid #e9ece7; box-shadow:0 24px 60px -30px rgba(21,24,30,.5)}
|
||||
.device img.screen{display:block; width:320px; height:240px; border-radius:8px}
|
||||
.kbd{margin:9px auto 2px; display:flex; flex-direction:column; gap:4px; width:312px}
|
||||
.kr{display:flex; gap:4px; justify-content:center}
|
||||
.key{flex:1; max-width:28px; height:15px; background:#23282e; border-radius:4px; display:grid; place-items:center; font:8px/1 system-ui; color:#7d8893}
|
||||
.key.wide{max-width:140px; flex:4}
|
||||
|
||||
.gallery{display:grid; grid-template-columns:repeat(auto-fill,minmax(232px,1fr)); gap:20px; margin-top:18px}
|
||||
.mini{background:#16191d; border-radius:12px; padding:7px; border:1px solid #e9ece7}
|
||||
.mini img{display:block; width:100%; height:auto; border-radius:6px}
|
||||
.mini .cap{margin-top:6px}
|
||||
.webgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(178px,1fr)); gap:18px; margin-top:22px}
|
||||
figure.phone{margin:0; background:#16191d; border-radius:20px; padding:8px; border:1px solid #e9ece7; box-shadow:0 18px 44px -26px rgba(21,24,30,.5)}
|
||||
figure.phone img{display:block; width:100%; height:auto; border-radius:13px}
|
||||
figure.phone figcaption{text-align:center; font-family:'JetBrains Mono',monospace; font-size:.66rem; color:var(--mut); margin-top:8px; letter-spacing:.04em}
|
||||
.subh{font-size:1.06rem; font-weight:700; color:var(--ink); margin:30px 0 2px}
|
||||
|
||||
/* Settings-page detail cards: real screenshot + what the page does + its controls */
|
||||
.sgrp{font-family:'JetBrains Mono',monospace; font-size:.7rem; letter-spacing:.14em; text-transform:uppercase; color:var(--mut); margin:30px 0 0; padding-top:4px}
|
||||
.scards{display:grid; grid-template-columns:repeat(auto-fill,minmax(290px,1fr)); gap:20px; margin-top:13px}
|
||||
.scard{border:1px solid var(--line); border-radius:14px; background:#fff; padding:13px; display:flex; flex-direction:column}
|
||||
.scard .thumb{background:#16191d; border-radius:9px; padding:6px}
|
||||
.scard .thumb img{display:block; width:100%; height:auto; border-radius:5px}
|
||||
.scard .thumb.ph{aspect-ratio:4/3; display:grid; place-items:center; border:1px dashed #39424b; padding:0}
|
||||
.scard .thumb.ph span{font-family:'JetBrains Mono',monospace; font-size:.6rem; letter-spacing:.12em; color:#7d8893; text-transform:uppercase}
|
||||
.scard h3{font-size:1.04rem; margin:12px 0 2px; color:var(--ink); display:flex; align-items:center; gap:8px; flex-wrap:wrap}
|
||||
.scard h3 .tag{font-family:'JetBrains Mono',monospace; font-size:.58rem; letter-spacing:.05em; text-transform:uppercase; color:var(--teal-ink); background:rgba(21,182,166,.1); border-radius:5px; padding:2px 6px; font-weight:600}
|
||||
.scard .lede{color:var(--mut); font-size:.9rem; margin:2px 0 9px}
|
||||
.scard ul{margin:0; padding-left:17px}
|
||||
.scard li{margin:4px 0; font-size:.89rem; color:#3c454d; line-height:1.5}
|
||||
.scard li b{color:var(--ink); font-weight:600}
|
||||
|
||||
/* Caution callout (amber) */
|
||||
.callout{display:flex; gap:11px; margin-top:18px; padding:13px 15px; border-radius:11px; background:#fff7ed; border:1px solid #f6dcb8}
|
||||
.callout .cico{flex:none; width:20px; height:20px; color:#c2740b; margin-top:1px}
|
||||
.callout .cico svg{width:20px; height:20px}
|
||||
.callout b{display:block; color:#7c4a03; margin-bottom:3px; font-size:.94rem}
|
||||
.callout p{margin:0; color:#7a5320; font-size:.9rem; line-height:1.55}
|
||||
|
||||
footer{border-top:1px solid var(--line); padding:26px 22px; text-align:center; color:var(--mut); font-size:.85rem}
|
||||
@media(max-width:980px){
|
||||
.shell{grid-template-columns:1fr; gap:0}
|
||||
nav.toc{position:static; height:auto; border-bottom:1px solid var(--line); padding:14px 0; display:flex; gap:6px; overflow:auto; white-space:nowrap}
|
||||
nav.toc .grp{display:none} nav.toc a{border-left:0; flex:none}
|
||||
.shot{position:static; margin:0 auto 6px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top">
|
||||
<a class="word" href="index.html">WADA<span class="t">MESH</span></a>
|
||||
<span class="tag">Docs</span>
|
||||
<span class="sp"></span>
|
||||
<a class="home" href="index.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg> Back to site</a>
|
||||
</div>
|
||||
|
||||
<div class="shell">
|
||||
<nav class="toc" id="toc">
|
||||
<div class="grp">Start here</div>
|
||||
<a href="#overview">Overview</a>
|
||||
<div class="grp">The five tabs</div>
|
||||
<a href="#home">Home</a>
|
||||
<a href="#chat">Chat</a>
|
||||
<a href="#contacts">Contacts</a>
|
||||
<a href="#map">Map</a>
|
||||
<a href="#settings">Settings</a>
|
||||
<div class="grp">With your phone</div>
|
||||
<a href="#companion">Companion mode</a>
|
||||
<a href="#remote">Remote & web app</a>
|
||||
<a href="#web">Web browser</a>
|
||||
<div class="grp">Reference</div>
|
||||
<a href="#updates">Updates & test builds</a>
|
||||
<a href="#settings-pages">Settings pages</a>
|
||||
<a href="#apps">Apps</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="hero" id="overview">
|
||||
<h1>WADAMESH user guide</h1>
|
||||
<p>WADAMESH turns a LilyGo T-Deck — its landscape screen, BlackBerry-style keyboard and trackball — into a complete front-end for your MeshCore radio: chat, channels, contacts, a live map and deep radio settings, all on the device. (It also runs on the Heltec V4 and Tanmatsu.) Every screen below is a real screenshot from the device.</p>
|
||||
<div class="meta">
|
||||
<span class="chip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg> No phone required</span>
|
||||
<span class="chip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12.55a11 11 0 0 1 14 0"/><path d="M8.5 16.4a5 5 0 0 1 7 0"/><line x1="12" y1="20" x2="12" y2="20"/></svg> LoRa + Wi-Fi + Bluetooth</span>
|
||||
<span class="chip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg> Keyboard + touch</span>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<span class="cico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h11l5 5v11a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V4z"/><path d="M9 4v4M13 4v4"/></svg></span>
|
||||
<div>
|
||||
<b>Got a microSD slot? Keep a card inserted.</b>
|
||||
<p>On devices with a microSD slot (the LilyGo T-Deck and Tanmatsu), wadamesh is best used with a card in place. It keeps map tiles, your contacts and your chat history on the card, which is roomier and far more reliable than the small internal flash, so a card prevents memory issues and stops that data being lost on reboot. The Heltec V4 has no card slot and manages fine on its internal storage.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="doc" id="home">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg></span> Home</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>The home tab is your <b>app launcher</b> — a grid of everything on the device:</p>
|
||||
<ul>
|
||||
<li><b>Cmdr</b> — the command centre overview.</li>
|
||||
<li><b>Chats</b>, <b>Contacts</b>, <b>Map</b>, <b>Mentions</b> — jump straight to those.</li>
|
||||
<li><b>Advertise</b> — announce yourself to the mesh (manual or on a timer).</li>
|
||||
<li><b>Signal</b> and <b>Monitor</b> — live RX signal and a repeater-style RF activity view.</li>
|
||||
<li><b>Spectrum</b> — sweep the LoRa band for interference.</li>
|
||||
<li><b>Settings</b>, <b>Terminal</b> (MeshCore CLI), <b>Files</b> (microSD).</li>
|
||||
</ul>
|
||||
<p>The bar along the bottom is always there — it switches the five main tabs (Chat, Contacts, Home, Map, Settings) from anywhere. The top bar shows unread count, time, signal and battery.</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/home.png" alt="Home app launcher"></div><div class="cap">Home — app launcher</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="chat">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span> Chat</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>Every conversation in one list, newest first — both <b>channels</b> (group chats like <code>#wadamesh</code>, <code>Public</code>) and <b>direct messages</b>. An unread badge shows the count.</p>
|
||||
<h3>On each row</h3>
|
||||
<ul>
|
||||
<li><b>Tap</b> a row to open the conversation, then type on the keyboard to reply.</li>
|
||||
<li>The <b>gear</b> on the right opens that chat's settings — mute, region & scope, share secret, blocked users, remove — the same screen as the cog inside a chat.</li>
|
||||
</ul>
|
||||
<p>The top-right buttons add a contact, mark everything read, and show your QR code.</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/chat.png" alt="Chat channel list"></div><div class="cap">Chat — channels & DMs</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="contacts">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg></span> Contacts</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>Everyone your node knows — name, type and how recently you heard them. Tap a contact to message it or open its details: signal history, a route trace, telemetry, and share/favourite options.</p>
|
||||
<p>The <b>overflow menu</b> (top right) holds Search, the <b>Discovered</b> list (nodes heard but not yet added), Add contact, Auto-add settings, and the Blocked list. Auto-add can pull new nodes in from their adverts, or you can add them by hand.</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/contacts.png" alt="Contacts list"></div><div class="cap">Contacts</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="map">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15"/><path d="M15 6v15"/></svg></span> Map</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>An OpenStreetMap / OpenTopoMap view with your position and your contacts plotted as markers, plus link lines from you to each node. Tiles are fetched over Wi-Fi through the wadamesh proxy and cached to flash (or microSD), so areas you've viewed work offline.</p>
|
||||
<p>It also has a line-of-sight analyzer: pick a contact and it uses SRTM elevation to show whether terrain blocks the path.</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/map.png?v=2" alt="Map"></div><div class="cap">Map</div></div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
<h3>Bring your own tiles (offline maps)</h3>
|
||||
<p>You don't need Wi-Fi to see the map. Copy a standard OpenStreetMap tile pack onto the T-Deck's microSD card and the map reads straight off the card, fully offline.</p>
|
||||
<p><b>Where they go.</b> Use the usual slippy-map <code>z/x/y</code> folder layout, in either of these locations on the card:</p>
|
||||
<ul>
|
||||
<li><code>/maps/osm/<z>/<x>/<y>.png</code> — the Meshtastic / MeshCore standard layout (recommended, so packs are interchangeable between apps).</li>
|
||||
<li><code>/tiles/<z>/<x>/<y>.png</code> — also read. This is the same folder the Wi-Fi download cache uses, so your own tiles and downloaded ones live side by side.</li>
|
||||
</ul>
|
||||
<p><b>Format.</b> 256×256 <code>.png</code> tiles (<code>.jpg</code> also works). <code>z</code> is the zoom level and <code>x</code>/<code>y</code> the tile column/row at that zoom — the same scheme OSM's own tile server and every XYZ tile downloader use, so most ready-made packs drop in unchanged.</p>
|
||||
<p><b>Turn it on.</b> Open the map's options (the gear button on the map) and switch on <b>Tiles from SD card</b>; the status line confirms <code>Map tiles: microSD</code>. Turn it back off to fetch live tiles over Wi-Fi again.</p>
|
||||
<p><b>Making a pack.</b> Any XYZ / slippy-map tile downloader works — point it at OpenStreetMap for your area and a sensible zoom range, then copy the resulting <code>z/x/y</code> folders onto the card. Each extra zoom level roughly quadruples the tile count, so grab only the levels you'll actually use.</p>
|
||||
<div class="callout">
|
||||
<span class="cico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
|
||||
<div>
|
||||
<b>Board differences</b>
|
||||
<p>The "Tiles from SD card" toggle is a T-Deck feature. The Heltec V4 has no microSD slot, so it caches downloaded tiles to a small internal partition (no offline packs there). On the Tanmatsu, tiles — your own or downloaded — live at <code>/tiles/<z>/<x>/<y>.jpg</code> on the microSD card; drop JPEG tiles there and they show alongside the downloaded ones.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="settings">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-2.4.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 8 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-2.4 1.65 1.65 0 0 0-1.51-1H1a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 3 8z"/></svg></span> Settings</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>A tile for each category. Tap one to open its page; the title bar carries a back chevron. The full set is in <a href="#settings-pages">Settings pages</a> below.</p>
|
||||
<ul>
|
||||
<li><b>Profile</b> — your name and node identity.</li>
|
||||
<li><b>Radio & Mesh</b> — frequency, spreading factor, region scope, high-gain receiver, signal probe, auto-advert.</li>
|
||||
<li><b>Auto-add, Wi-Fi, Bluetooth, GPS</b> — discovery and connectivity.</li>
|
||||
<li><b>Clock, Battery, Sensors, Display, Keyboard, Sound, Quick replies, Lock screen</b> — the device experience.</li>
|
||||
<li><b>Language</b> (twelve UI languages), <b>Backups</b>, and <b>About</b> (firmware version, update check, diagnostics).</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/settings.png" alt="Settings categories"></div><div class="cap">Settings categories</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="companion">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="11" y1="18" x2="13" y2="18"/></svg></span> Companion mode</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>WADAMESH runs everything on its own, but it can also be the radio for the <b>official MeshCore app</b> on your phone (iOS and Android). Your phone brings the big screen and full keyboard; the device does the LoRa. Best of all it works <b>alongside</b> the on-device UI — you can keep using the touch screen while your phone is connected, and your contacts, channels and messages stay in sync between them.</p>
|
||||
<h3>Three ways to connect</h3>
|
||||
<ul>
|
||||
<li><b>USB</b> — plug the device into an Android phone or a computer; the app connects over the cable. Nothing to set up on the device.</li>
|
||||
<li><b>Bluetooth</b> — turn on Bluetooth in <b>Settings → Bluetooth</b>. The device appears in the app as <code>MeshCore-<your node name></code>; pair with the 6-digit code (default <code>123456</code>, changeable on the same page).</li>
|
||||
<li><b>Wi-Fi</b> — put the device on your network in <b>Settings → Wi-Fi</b>. On the same Wi-Fi, the app connects to the device's IP address on port <code>5000</code> (the Wi-Fi page shows the IP).</li>
|
||||
</ul>
|
||||
<div class="callout">
|
||||
<span class="cico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>
|
||||
<div>
|
||||
<b>Run Bluetooth and Wi-Fi together only when you need to</b>
|
||||
<p>Bluetooth and Wi-Fi can be on at the same time, but each one reserves a good chunk of RAM. On the Heltec V4 especially — it has the least free memory of the three boards — keeping both on at once leaves little headroom and can lead to instability or odd behaviour. If you only want the phone app, pick one transport (Bluetooth or Wi-Fi) and leave the other off; if you mostly use the device on its own, keep both off.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/set_bluetooth.png" alt="Bluetooth pairing page"></div><div class="cap">Bluetooth — pairing code</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="remote">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"/><line x1="2" y1="20" x2="2.01" y2="20"/></svg></span> Remote & web app</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>Over Wi-Fi, a WADAMESH device can put its screen — or a whole browser app — on your phone or laptop. There is nothing to install: the device serves a web page you open in any browser on the same network. Turn it on from <b>Home → Remote</b> (or the Remote screen in Settings), pick a mode, and open the address it shows.</p>
|
||||
<h3>Three ways to connect</h3>
|
||||
<ul>
|
||||
<li><b>Remote app</b> (recommended) — a full control panel, native to the browser: <b>Chats</b>, <b>Contacts</b>, a <b>Terminal</b> and <b>Settings</b>, with live message notifications. Run the device entirely from your phone.</li>
|
||||
<li><b>Screen mirror (VNC)</b> — streams the device's live screen to the browser and sends your taps and key presses back, so you see and drive exactly what is on the device, in real time.</li>
|
||||
<li><b>Remote UI</b> — renders the interface off-screen at desktop size (a wide landscape layout) and streams that instead of the small panel, for a roomier view on a big screen.</li>
|
||||
</ul>
|
||||
<p>Screen mirror and Remote UI both show the same interface you already know from the device — mirrored, or drawn larger. The <b>Remote app</b> is different: a browser-native panel built for a phone, shown below.</p>
|
||||
<h3>Turning it on</h3>
|
||||
<p>Connect the device to Wi-Fi first, then open <b>Home → Remote</b>. The screen shows the address to type into your browser (like <code>http://192.168.1.42</code>) and a switch for each mode. Only one streaming mode runs at a time, and the phone and the device must be on the same Wi-Fi network.</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/app_remote.png" alt="Remote access screen on the device"></div><div class="cap">Remote — turn it on, then open the address</div></div>
|
||||
</div>
|
||||
|
||||
<div class="subh">The remote app in a browser</div>
|
||||
<div class="prose" style="max-width:70ch"><p>The remote app is phone-first. It carries the same status icons as the device (signal, Wi-Fi, Bluetooth, battery), pops up a notification when a message arrives, and refreshes an open chat on its own as replies come in — so you rarely need to touch the device itself.</p></div>
|
||||
<div class="webgrid">
|
||||
<figure class="phone"><img src="docs-img/web_chats.png" alt="Chats tab in the web app"><figcaption>Chats — DMs, channels, unread badges</figcaption></figure>
|
||||
<figure class="phone"><img src="docs-img/web_chat.png" alt="A conversation in the web app"><figcaption>A conversation, with delivery ticks</figcaption></figure>
|
||||
<figure class="phone"><img src="docs-img/web_contacts.png" alt="Contacts tab in the web app"><figcaption>Contacts — search, sort, filter</figcaption></figure>
|
||||
<figure class="phone"><img src="docs-img/web_contact_sheet.png" alt="Contact actions in the web app"><figcaption>Tap a contact for actions</figcaption></figure>
|
||||
<figure class="phone"><img src="docs-img/web_discovered.png" alt="Discovered nodes in the web app"><figcaption>Discovered nodes — add with a tap</figcaption></figure>
|
||||
<figure class="phone"><img src="docs-img/web_settings.png" alt="Settings in the web app"><figcaption>Settings — including live radio</figcaption></figure>
|
||||
<figure class="phone"><img src="docs-img/web_terminal.png" alt="Terminal in the web app"><figcaption>Terminal — the MeshCore CLI</figcaption></figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="web">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span> Web browser</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>WADAMESH can read the web on its own screen. Open the <b>Web</b> app from the home launcher, type an address, and the device fetches the page and shows it as plain, readable text — articles, wikis, docs, simple sites. There is no proxy in the middle and nothing is tracked: it pulls the page straight from the site and strips it down to the words.</p>
|
||||
<ul>
|
||||
<li><b>Text only</b> — no JavaScript, no images, no ads. Pages load small and read clean.</li>
|
||||
<li><b>Tappable links</b> — follow a link to the next page, with <b>back</b>, <b>forward</b> and <b>refresh</b> in the toolbar.</li>
|
||||
<li><b>Direct & private</b> — the device connects to the site itself over Wi-Fi (HTTPS); no server of ours sees what you read.</li>
|
||||
</ul>
|
||||
<p>Reading the web on-device takes more memory than the smallest board carries, so the Web app is on the <b>T-Deck</b> and <b>Tanmatsu</b>. On the 2 MB Heltec V4 it is hidden — that board can't complete the secure handshake — but you can still bounce any link to your phone (below).</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/app_web.png" alt="The on-device Web app"></div><div class="cap">Web — open an address, read it as text</div></div>
|
||||
</div>
|
||||
|
||||
<div class="subh">Links in chat</div>
|
||||
<div class="prose" style="max-width:70ch"><p>A link that arrives in a message is now tappable. Tap it for a small menu: <b>Open in web</b> loads the page in the on-device browser (T-Deck and Tanmatsu), and <b>Create QR</b> puts a scannable QR code on screen so you can open the link on your phone. Create QR works on <em>every</em> board, including the Heltec V4 — so even where the browser isn't available, a link is one tap from your phone.</p></div>
|
||||
<div class="gallery">
|
||||
<div class="mini"><img src="docs-img/app_web_links.png" alt="Tapping a link in chat opens a menu"><div class="cap">Tap a link — Open in web, or Create QR</div></div>
|
||||
<div class="mini"><img src="docs-img/app_web_qr.png" alt="A QR code for a link from chat"><div class="cap">Create QR — scan to open on your phone</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="updates">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg></span> Updates & test builds</h2>
|
||||
<div class="row">
|
||||
<div class="prose">
|
||||
<p>On the boards that update over Wi-Fi (the Heltec V4 and the standalone T-Deck), WADAMESH checks for new firmware on its own. When one is available, <b>Settings → About</b> shows it and you can flash it straight from the device with <b>Install update</b>, no computer needed. (Under Launcher and on the Tanmatsu you update through Launcher or the app store instead.)</p>
|
||||
<h3>Two channels: Stable and Test</h3>
|
||||
<ul>
|
||||
<li><b>Stable</b> is the default. It is the build that has been tested and is recommended for everyday use. Leave your device on this unless you want to help test.</li>
|
||||
<li><b>Test (beta)</b> gets new features and fixes earlier, before they are proven. It can be rough, so it is meant for trying things early and reporting bugs, not for a device you rely on.</li>
|
||||
</ul>
|
||||
<h3>Turn on test builds</h3>
|
||||
<p>Flip <b>Settings → About → "Get test builds (beta)"</b> on. From then on, both the update check and <b>Install update</b> follow the test channel, so the device offers the newest test build over Wi-Fi. Turn the switch back off to return to stable. You can switch either way at any time.</p>
|
||||
<p>Prefer to start on a test build from scratch? Pick <b>Beta · testing</b> when you flash from <a href="https://flasher.wadamesh.com" target="_blank" rel="noopener">flasher.wadamesh.com</a>.</p>
|
||||
</div>
|
||||
<div class="shot"><div class="device"><img class="screen" src="docs-img/set_about.png" alt="About page"></div><div class="cap">The About page</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="settings-pages">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg></span> Settings pages</h2>
|
||||
<div class="prose" style="max-width:68ch"><p>WADAMESH splits its settings into nineteen pages. Here is what each one does and the controls you will find on it. Open any page from the Settings tab; a back chevron in the title bar returns you to the grid.</p></div>
|
||||
|
||||
<div class="sgrp">Identity & radio</div>
|
||||
<div class="scards">
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_profile.png" alt="Profile settings"></div>
|
||||
<h3>Profile</h3>
|
||||
<div class="lede">Who you are on the mesh.</div>
|
||||
<ul>
|
||||
<li><b>Node name</b> — your callsign on the network</li>
|
||||
<li><b>Advert location</b> plus a share-in-advert toggle</li>
|
||||
<li><b>Identity key</b> and <b>Share QR</b> to hand out your contact</li>
|
||||
<li><b>Export / Import</b> your whole setup as a JSON backup</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_radio___mesh.png" alt="Radio and Mesh settings"></div>
|
||||
<h3>Radio & Mesh</h3>
|
||||
<div class="lede">Every LoRa and mesh parameter, split into RADIO / MESH / SIGNAL.</div>
|
||||
<ul>
|
||||
<li><b>Radio</b> — preset, frequency, bandwidth, spreading factor, coding rate, TX power, with a live duty-cycle and airtime-per-packet readout</li>
|
||||
<li><b>Mesh</b> — answer-telemetry, path-hash size, region scope tag, scope direct messages</li>
|
||||
<li><b>Signal</b> — high-gain receiver (Heltec V4.3), signal probe, and a shortcut to the Advertise app</li>
|
||||
<li><b>Experimental</b> — multi-ACKs, client repeat, RX boost, duty meter</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_auto_add.png" alt="Auto-add settings"></div>
|
||||
<h3>Auto-add</h3>
|
||||
<div class="lede">Which nodes join Contacts automatically from their adverts.</div>
|
||||
<ul>
|
||||
<li>Per-type toggles: <b>chat</b>, <b>repeater</b>, <b>room</b>, <b>sensor</b></li>
|
||||
<li><b>Overwrite oldest</b> when the contact list is full</li>
|
||||
<li><b>Notify</b> when a new contact is added</li>
|
||||
<li><b>Max hops</b> filter for how far away a node can be</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sgrp">Connectivity</div>
|
||||
<div class="scards">
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_wi_fi.png" alt="Wi-Fi settings"></div>
|
||||
<h3>Wi-Fi</h3>
|
||||
<div class="lede">For map tiles, clock sync and update checks.</div>
|
||||
<ul>
|
||||
<li>On / off with a live status line (IP, signal)</li>
|
||||
<li><b>Scan</b> and tap an SSID, or type one in by hand</li>
|
||||
<li><b>Save & reconnect</b> applies live — no reboot</li>
|
||||
<li>Saved network slots for one-tap quick-connect</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_bluetooth.png" alt="Bluetooth settings"></div>
|
||||
<h3>Bluetooth</h3>
|
||||
<div class="lede">Pair with the MeshCore phone app over BLE.</div>
|
||||
<ul>
|
||||
<li><b>Enable Bluetooth</b> with a live mode status line</li>
|
||||
<li>Set a 6-digit <b>pairing code</b> (applies after a reboot)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_gps.png" alt="GPS settings"></div>
|
||||
<h3>GPS</h3>
|
||||
<div class="lede">The on-board GPS receiver.</div>
|
||||
<ul>
|
||||
<li>On / off with a live <b>fix status</b> (acquiring, 2D/3D, age)</li>
|
||||
<li><b>Serial baud</b> rate (applies after a reboot)</li>
|
||||
<li>Reads "no GPS module" on devices without one</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_mqtt_bridge.png" alt="MQTT bridge settings"></div>
|
||||
<h3>MQTT bridge <span class="tag">experimental</span></h3>
|
||||
<div class="lede">Forward received messages to your own MQTT broker.</div>
|
||||
<ul>
|
||||
<li>Publishes the <b>text, sender and time</b> of messages your node hears to an MQTT broker</li>
|
||||
<li>Broker <b>host, port and topic</b>, plus an <b>encryption key</b></li>
|
||||
<li>Stays off until you <b>accept the privacy note</b> — anyone who can read the broker sees the messages, so use one you control, never a public broker</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sgrp">Time & power</div>
|
||||
<div class="scards">
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_clock___time.png" alt="Clock and time settings"></div>
|
||||
<h3>Clock & time</h3>
|
||||
<div class="lede">Keep the clock right.</div>
|
||||
<ul>
|
||||
<li><b>Sync clock</b> from the system / NTP</li>
|
||||
<li><b>Time zone</b> picker, or a custom UTC offset with steppers</li>
|
||||
<li><b>12-hour clock</b> toggle</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_battery.png" alt="Battery settings"></div>
|
||||
<h3>Battery</h3>
|
||||
<div class="lede">Power readouts and tuning.</div>
|
||||
<ul>
|
||||
<li><b>Battery & power history</b> chart</li>
|
||||
<li><b>Calibrate</b> — tap to set 100% at the current voltage</li>
|
||||
<li><b>Battery saver</b> throttles the CPU when idle (T-Deck)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_sensors.png" alt="Sensors settings"></div>
|
||||
<h3>Sensors <span class="tag">Heltec V4</span></h3>
|
||||
<div class="lede">For the V4 Expansion Kit.</div>
|
||||
<ul>
|
||||
<li><b>Expansion Kit</b> configuration</li>
|
||||
<li><b>Show Sensors tab</b> in the bottom bar (applies after restart)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sgrp">Experience</div>
|
||||
<div class="scards">
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_display.png" alt="Display settings"></div>
|
||||
<h3>Display</h3>
|
||||
<div class="lede">Look and feel.</div>
|
||||
<ul>
|
||||
<li><b>Screen timeout</b> before it sleeps</li>
|
||||
<li><b>UI size</b> — Normal / Large / Huge</li>
|
||||
<li><b>Colourful chat bubbles</b>, distance in miles, hide device name</li>
|
||||
<li><b>Theme colour</b> picker</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_keyboard.png" alt="Keyboard settings"></div>
|
||||
<h3>Keyboard</h3>
|
||||
<div class="lede">Typing and on-device input.</div>
|
||||
<ul>
|
||||
<li><b>Secondary layouts</b> — Cyrillic, Greek, Arabic, French, German and more</li>
|
||||
<li><b>Accent popups</b> — long-press for accented letters</li>
|
||||
<li><b>Enter sends</b> the message (T-Deck)</li>
|
||||
<li><b>Flash</b> the screen and keys on a new message (T-Deck)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_sound.png" alt="Sound settings"></div>
|
||||
<h3>Sound</h3>
|
||||
<div class="lede">Notification audio.</div>
|
||||
<ul>
|
||||
<li>Master sound plus per-type toggles: <b>message</b>, <b>DM</b>, <b>@mention</b></li>
|
||||
<li>Custom <b>sound files</b> from microSD where supported</li>
|
||||
<li><b>Volume</b> control (T-Deck / Tanmatsu)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_quick_replies.png" alt="Quick replies settings"></div>
|
||||
<h3>Quick replies</h3>
|
||||
<div class="lede">One-tap canned messages.</div>
|
||||
<ul>
|
||||
<li>Edit <b>six</b> short replies</li>
|
||||
<li>Pick them from the composer to send instantly</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_lock_screen.png" alt="Lock screen settings"></div>
|
||||
<h3>Lock screen <span class="tag">T-Deck</span></h3>
|
||||
<div class="lede">A simple lock when idle.</div>
|
||||
<ul>
|
||||
<li><b>Wallpaper</b> picker and <b>lock text colour</b></li>
|
||||
<li><b>Lock when screen off</b>, then hold to unlock</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sgrp">System</div>
|
||||
<div class="scards">
|
||||
<div class="scard">
|
||||
<div class="thumb ph"><span>Screenshot pending</span></div>
|
||||
<h3>General</h3>
|
||||
<div class="lede">Device housekeeping.</div>
|
||||
<ul>
|
||||
<li><b>Send advert now</b></li>
|
||||
<li><b>Store data on SD</b> (reboot)</li>
|
||||
<li><b>Run setup again</b> from scratch</li>
|
||||
<li><b>Reboot device</b></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_backups.png" alt="Backups settings"></div>
|
||||
<h3>Backups</h3>
|
||||
<div class="lede">Save and restore your whole config.</div>
|
||||
<ul>
|
||||
<li><b>Export</b> a new JSON backup</li>
|
||||
<li>Restore from the <b>saved list</b></li>
|
||||
<li><b>Factory reset</b> in the danger zone</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_language.png" alt="Language settings"></div>
|
||||
<h3>Language</h3>
|
||||
<div class="lede">Twelve UI languages.</div>
|
||||
<ul>
|
||||
<li>Tap a language; the device reboots to apply</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="scard">
|
||||
<div class="thumb"><img src="docs-img/set_about.png" alt="About settings"></div>
|
||||
<h3>About</h3>
|
||||
<div class="lede">Version, updates and diagnostics.</div>
|
||||
<ul>
|
||||
<li><b>Firmware version</b> plus update check / install</li>
|
||||
<li><b>Get test builds (beta)</b> switch: opt into the <a href="#updates">test channel</a></li>
|
||||
<li>Full <b>system diagnostics</b> — uptime, chip, memory, flash, storage</li>
|
||||
<li><b>Device ID</b> and crash-report export</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="doc" id="apps">
|
||||
<h2><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg></span> Apps</h2>
|
||||
<div class="prose" style="max-width:64ch">
|
||||
<p>Tools from the home launcher. <b>RF Monitor</b> is a repeater-style view of recent packets and radio stats; <b>Spectrum</b> sweeps the band so you can spot interference or find a clear frequency.</p>
|
||||
</div>
|
||||
<div class="gallery">
|
||||
<div class="mini"><img src="docs-img/app_rfmonitor.png" alt="RF Monitor"><div class="cap">RF Monitor</div></div>
|
||||
<div class="mini"><img src="docs-img/app_spectrum.png" alt="Spectrum analyzer"><div class="cap">Spectrum analyzer</div></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>WADAMESH — open-source MeshCore touch firmware · <a href="index.html">wadamesh.com</a></footer>
|
||||
|
||||
<script>
|
||||
var KB = ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM'];
|
||||
document.querySelectorAll('.device').forEach(function(dev){
|
||||
var kb = document.createElement('div'); kb.className = 'kbd';
|
||||
KB.forEach(function(rowStr){
|
||||
var r = document.createElement('div'); r.className = 'kr';
|
||||
rowStr.split('').forEach(function(ch){ var k = document.createElement('span'); k.className = 'key'; k.textContent = ch; r.appendChild(k); });
|
||||
kb.appendChild(r);
|
||||
});
|
||||
var sp = document.createElement('div'); sp.className = 'kr';
|
||||
sp.innerHTML = '<span class="key">sym</span><span class="key wide"></span><span class="key">⏎</span>';
|
||||
kb.appendChild(sp);
|
||||
dev.appendChild(kb);
|
||||
});
|
||||
var links = Array.prototype.slice.call(document.querySelectorAll('nav.toc a'));
|
||||
var obs = new IntersectionObserver(function(es){
|
||||
es.forEach(function(e){ if (e.isIntersecting) {
|
||||
links.forEach(function(l){ l.classList.remove('on'); });
|
||||
var a = document.querySelector('nav.toc a[href="#' + e.target.id + '"]');
|
||||
if (a) a.classList.add('on');
|
||||
}});
|
||||
}, { rootMargin: '-25% 0px -65% 0px' });
|
||||
['overview','home','chat','contacts','map','settings','companion','settings-pages','apps'].forEach(function(id){ var el = document.getElementById(id); if (el) obs.observe(el); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WADAMESH — a touchscreen for your mesh radio</title>
|
||||
<meta name="description" content="Open-source MeshCore touch-UI firmware for the LilyGo T-Deck and Heltec V4. Map, chat, channels, contacts — install from your browser.">
|
||||
<meta name="description" content="Open-source MeshCore touch-UI firmware for the LilyGo T-Deck, Heltec V4 and Tanmatsu. Map, chat, channels, contacts — install from your browser.">
|
||||
<link rel="icon" href="wadamesh-badge.svg">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
@@ -42,13 +42,21 @@
|
||||
font-family:'JetBrains Mono',monospace; background:#fff; transition:border-color .15s, color .15s}
|
||||
.kicker:hover{border-color:var(--teal); color:var(--ink)}
|
||||
.freeline{margin:0 auto 24px; color:var(--mut); font-size:.92rem}
|
||||
.fab{position:fixed; top:16px; z-index:45; width:44px; height:44px; border-radius:50%; display:grid; place-items:center;
|
||||
.fab-rail{position:fixed; top:var(--fab-top,16px); z-index:45; display:flex; flex-direction:column; gap:12px}
|
||||
/* dismissible donate banner */
|
||||
.donate-bar{position:sticky; top:0; z-index:50; display:flex; align-items:center; justify-content:center; gap:7px 16px; flex-wrap:wrap;
|
||||
padding:9px 44px; background:var(--ink); color:#fff; font-size:.9rem; line-height:1.4; text-align:center}
|
||||
.donate-bar a{color:#fff; text-decoration:underline; text-underline-offset:2px}
|
||||
.donate-bar .donate-cta{background:var(--teal); color:#fff; text-decoration:none; padding:5px 14px; border-radius:100px; font-weight:650; white-space:nowrap}
|
||||
.donate-bar .donate-cta:hover{background:var(--cyan)}
|
||||
.donate-bar .x{position:absolute; right:12px; top:50%; transform:translateY(-50%); background:none; border:0; color:#fff; font-size:1.5rem; line-height:1; cursor:pointer; opacity:.75; padding:0 4px}
|
||||
.donate-bar .x:hover{opacity:1}
|
||||
.fab-rail.left{left:16px} .fab-rail.right{right:16px}
|
||||
.fab{width:44px; height:44px; border-radius:50%; display:grid; place-items:center; cursor:pointer;
|
||||
background:#fff; border:1px solid var(--line); color:#46525c; box-shadow:0 10px 26px -14px rgba(21,24,30,.45);
|
||||
transition:border-color .15s, color .15s, transform .15s, box-shadow .15s}
|
||||
.fab:hover{border-color:var(--teal); color:var(--teal-ink); transform:translateY(-2px); box-shadow:0 14px 30px -14px rgba(21,182,166,.5)}
|
||||
.fab svg{width:21px; height:21px}
|
||||
.fab-tg{left:16px}
|
||||
.fab-bug{right:16px}
|
||||
.brand{display:flex; align-items:center; justify-content:center; gap:clamp(12px,2.6vw,20px); flex-wrap:wrap; position:relative}
|
||||
.brand .mark{width:clamp(60px,13vw,88px); height:auto; flex:none}
|
||||
.brand .word{font-weight:700; font-size:clamp(2rem,7.5vw,3.35rem); letter-spacing:3px; color:var(--ink); line-height:1}
|
||||
@@ -135,21 +143,102 @@
|
||||
.step p{color:var(--mut); font-size:.92rem}
|
||||
.step code{background:#fff; border:1px solid var(--line); border-radius:6px; padding:1px 6px; font-size:.85em; font-family:'JetBrains Mono',monospace}
|
||||
|
||||
.boards{display:grid; gap:14px; grid-template-columns:1fr 1fr; max-width:760px; margin:18px auto 0}
|
||||
.boards{display:grid; gap:14px; grid-template-columns:repeat(auto-fit,minmax(210px,1fr)); max-width:760px; margin:18px auto 0}
|
||||
@media(max-width:560px){.boards{grid-template-columns:1fr}}
|
||||
.board{border:1px solid var(--line); background:var(--card); border-radius:14px; padding:18px; display:flex;
|
||||
flex-direction:column; gap:10px; align-items:flex-start; box-shadow:0 8px 26px -22px rgba(21,24,30,.5)}
|
||||
.board h4{font-size:1.02rem}
|
||||
.board p{color:var(--mut); font-size:.86rem}
|
||||
/* Per-board support-level badge + hover popover (same visual language as the hero pills). */
|
||||
.sup{position:relative; display:inline-flex; align-items:center; gap:6px; padding:3px 10px; border-radius:999px;
|
||||
font-size:.72rem; font-weight:700; letter-spacing:.2px; cursor:default; width:max-content}
|
||||
.sup .dot{width:7px; height:7px; border-radius:50%; flex:none}
|
||||
.sup.full{background:#e7f6ec; color:#1c7c3f; border:1px solid #bfe6cc}
|
||||
.sup.full .dot{background:#27a35a}
|
||||
.sup.part{background:#fdf3e4; color:#9a6410; border:1px solid #f0dcb4}
|
||||
.sup.part .dot{background:#e3a127}
|
||||
.sup .pop{position:absolute; top:calc(100% + 11px); left:0; transform:translateY(5px);
|
||||
width:min(272px,80vw); background:#fff; border:1px solid var(--line); border-radius:12px; padding:13px 15px;
|
||||
box-shadow:0 18px 44px -18px rgba(21,24,30,.32); text-align:left; font-size:.84rem; font-weight:400;
|
||||
letter-spacing:0; line-height:1.5; color:var(--mut); opacity:0; visibility:hidden;
|
||||
transition:opacity .16s ease, transform .16s ease; z-index:20; cursor:default}
|
||||
.sup .pop b{display:block; color:var(--ink); margin-bottom:4px; font-size:.92rem}
|
||||
.sup .pop::before{content:""; position:absolute; bottom:100%; left:18px; border:7px solid transparent; border-bottom-color:#fff}
|
||||
.sup:hover .pop,.sup:focus .pop,.sup:focus-within .pop{opacity:1; visibility:visible; transform:translateY(0)}
|
||||
esp-web-install-button{--esp-tools-button-color:var(--teal);--esp-tools-button-text-color:#fff}
|
||||
.dl{display:inline-flex;align-items:center;gap:8px;background:var(--teal);color:#fff;border-radius:9px;
|
||||
padding:9px 15px;font-weight:650;font-size:.9rem}
|
||||
.dl:hover{background:var(--teal-ink)}
|
||||
.dl svg{width:15px;height:15px}
|
||||
.dl2{display:inline-block;margin-top:-2px;text-decoration:none;line-height:1.3}
|
||||
.dl2 .dl2-row{display:inline-flex;align-items:center;gap:6px;font-size:.85rem;color:var(--teal);font-weight:700;white-space:nowrap}
|
||||
.dl2:hover .dl2-row{text-decoration:underline}
|
||||
.dl2 svg{width:14px;height:14px;flex:none}
|
||||
.dl2 .dlver{display:block;font-size:.72rem;color:var(--mut);font-weight:600;margin-top:2px;padding-left:20px;letter-spacing:.03em}
|
||||
.dl2 .dlver:empty{display:none}
|
||||
.hint{max-width:760px;margin:18px auto 0;color:var(--mut);font-size:.86rem;text-align:center}
|
||||
/* device selector — grouped by manufacturer, collapsible, searchable */
|
||||
.devsel{max-width:840px; margin:20px auto 0}
|
||||
.devsearch{position:relative; max-width:460px; margin:0 auto 18px}
|
||||
.devsearch>svg{position:absolute; left:15px; top:50%; transform:translateY(-50%); width:17px; height:17px; color:var(--mut); pointer-events:none}
|
||||
.devsearch input{width:100%; box-sizing:border-box; font:inherit; font-size:.95rem; padding:11px 42px; border:1px solid var(--line); border-radius:11px; background:#fff; color:var(--ink)}
|
||||
.devsearch input::-webkit-search-cancel-button{display:none}
|
||||
.devsearch input:focus{outline:none; border-color:var(--teal); box-shadow:0 0 0 3px rgba(21,182,166,.15)}
|
||||
.devsearch-x{position:absolute; right:8px; top:50%; transform:translateY(-50%); width:26px; height:26px; border:0; border-radius:50%; background:var(--bg2); color:var(--mut); font-size:1.15rem; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center}
|
||||
.devsearch-x:hover{color:var(--ink); background:var(--line)}
|
||||
.devsearch-empty{text-align:center; color:var(--mut); font-size:.9rem; margin:12px 0 0}
|
||||
.devsearch-empty button{font:inherit; font-size:.9rem; color:var(--teal-ink); background:none; border:0; padding:0; cursor:pointer; text-decoration:underline}
|
||||
.mfr{border:1px solid var(--line); border-radius:14px; background:var(--bg2); margin:0 auto 12px; overflow:hidden}
|
||||
.mfr[hidden]{display:none}
|
||||
.mfr>summary{list-style:none; cursor:pointer; user-select:none; display:flex; align-items:center; gap:11px; padding:15px 18px; font-weight:700; font-size:1.05rem; color:var(--ink)}
|
||||
.mfr>summary::-webkit-details-marker{display:none}
|
||||
.mfr>summary:hover{color:var(--teal-ink)}
|
||||
.mfr-name{flex:1 1 auto}
|
||||
.mfr-count{font-weight:600; font-size:.78rem; color:var(--mut); letter-spacing:.02em}
|
||||
.mfr .chev{width:18px; height:18px; color:var(--mut); flex:none; transition:transform .18s}
|
||||
.mfr[open]>summary .chev{transform:rotate(180deg)}
|
||||
.mfr-boards{max-width:none; margin:0; padding:2px 16px 18px}
|
||||
.board[hidden]{display:none}
|
||||
/* "install a previous version" picker */
|
||||
.verpick{max-width:760px; margin:16px auto 0; border:1px solid var(--line); border-radius:12px; background:var(--bg2); text-align:left}
|
||||
.verpick summary{cursor:pointer; padding:12px 16px; font-weight:600; font-size:.92rem; color:var(--ink); list-style:none; user-select:none}
|
||||
.verpick summary::-webkit-details-marker{display:none}
|
||||
.verpick summary::before{content:"\25B8"; color:var(--teal); margin-right:9px; display:inline-block; transition:transform .15s}
|
||||
.verpick[open] summary::before{transform:rotate(90deg)}
|
||||
.verpick-body{padding:2px 16px 16px}
|
||||
.verpick-row{display:flex; align-items:center; gap:10px}
|
||||
.verpick-row label{font-size:.9rem; color:var(--mut)}
|
||||
#verSel{font:inherit; font-size:.92rem; padding:7px 11px; border:1px solid var(--line); border-radius:8px; background:#fff; color:var(--ink); cursor:pointer}
|
||||
.verpick-note{margin:9px 0 0; font-size:.86rem; color:var(--teal-ink)}
|
||||
.rn{margin-top:12px; max-height:280px; overflow:auto; font-size:.88rem; color:var(--mut); line-height:1.55}
|
||||
.rn .rn-h{font-size:.92rem; color:var(--ink); font-weight:700; margin:13px 0 5px} .rn .rn-h:first-child{margin-top:0}
|
||||
.rn ul{margin:5px 0 0; padding-left:18px} .rn li{margin:3px 0}
|
||||
.rn p{margin:6px 0} .rn code{background:#fff; border:1px solid var(--line); border-radius:5px; padding:1px 5px; font-size:.86em}
|
||||
.rn a{color:var(--teal-ink)}
|
||||
.noserial{display:none;color:#b4631a;text-align:center;margin-top:14px}
|
||||
body.no-serial .noserial{display:block}
|
||||
|
||||
/* community videos */
|
||||
.vids{display:grid; gap:18px; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); margin-top:34px}
|
||||
.vid{border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--card); box-shadow:0 12px 30px -22px rgba(21,24,30,.5)}
|
||||
.vid-thumb{position:relative; aspect-ratio:16/9; width:100%; border:0; padding:0; cursor:pointer; display:block; background-size:cover; background-position:center}
|
||||
.vid-thumb::after{content:""; position:absolute; inset:0; background:rgba(21,24,30,.12); transition:background .15s}
|
||||
.vid-thumb:hover::after{background:rgba(21,24,30,0)}
|
||||
.vid-thumb .play{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:62px; height:44px; border-radius:12px;
|
||||
background:rgba(229,57,53,.94); display:grid; place-items:center; z-index:1; transition:transform .15s, background .15s}
|
||||
.vid-thumb:hover .play{transform:translate(-50%,-50%) scale(1.07); background:#e53935}
|
||||
.vid-thumb .play svg{width:24px; height:24px; color:#fff}
|
||||
.vid-embed{aspect-ratio:16/9; width:100%; border:0; display:block; background:#000}
|
||||
.vid-cap{padding:13px 15px; font-size:.92rem; color:var(--ink); font-weight:600}
|
||||
.vid-cap span{display:block; color:var(--mut); font-size:.82rem; font-weight:400; margin-top:2px}
|
||||
|
||||
/* toast (docs placeholder etc.) */
|
||||
.toast{position:fixed; left:50%; bottom:26px; transform:translateX(-50%) translateY(12px); z-index:70;
|
||||
background:var(--ink); color:#fff; padding:11px 20px; border-radius:100px; font-size:.92rem; font-weight:600;
|
||||
box-shadow:0 16px 36px -14px rgba(21,24,30,.55); opacity:0; visibility:hidden; pointer-events:none;
|
||||
transition:opacity .2s ease, transform .2s ease}
|
||||
.toast.show{opacity:1; visibility:visible; transform:translateX(-50%) translateY(0)}
|
||||
|
||||
footer{border-top:1px solid var(--line); margin-top:48px; padding:30px 0 56px; text-align:center; color:var(--mut); font-size:.86rem}
|
||||
footer a{color:var(--teal-ink)}
|
||||
.legal-links{margin-top:12px}
|
||||
@@ -173,15 +262,34 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="donate-bar" id="donateBar">
|
||||
<span>Like the project and want to donate? Help out the <b>MeshCore</b> founders instead. <a href="https://blog.meshcore.io/2026/07/04/help-us-save-meshcore" target="_blank" rel="noopener">Learn more</a></span>
|
||||
<a class="donate-cta" href="https://givealittle.co.nz/cause/help-us-save-meshcore" target="_blank" rel="noopener">Donate</a>
|
||||
<button class="x" id="donateX" type="button" aria-label="Dismiss">×</button>
|
||||
</div>
|
||||
<div id="vanta-bg"></div>
|
||||
|
||||
<!-- floating quick links -->
|
||||
<a class="fab fab-tg" href="https://t.me/+fHuhG67-RFJlNmFk" target="_blank" rel="noopener" aria-label="Join us on Telegram" title="Join us on Telegram">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M9.04 15.47 8.7 19.9c.5 0 .72-.22.98-.47l2.36-2.22 4.89 3.53c.9.5 1.53.24 1.77-.83l3.2-14.5c.3-1.33-.5-1.85-1.36-1.53L1.9 9.46c-1.3.5-1.28 1.22-.22 1.55l5.05 1.57L18.4 5.9c.55-.36 1.05-.16.64.2L9.04 15.47z"/></svg>
|
||||
</a>
|
||||
<a class="fab fab-bug" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
|
||||
</a>
|
||||
<div class="fab-rail left">
|
||||
<a class="fab" href="https://discord.gg/T8X2UaQ7Sm" target="_blank" rel="noopener" aria-label="Join us on Discord" title="Join us on Discord">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.369a19.79 19.79 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.249a18.27 18.27 0 0 0-5.487 0 12.6 12.6 0 0 0-.617-1.249.077.077 0 0 0-.079-.037A19.74 19.74 0 0 0 3.677 4.369a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.2 14.2 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.1 13.1 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .078-.01c3.927 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .079.009c.12.099.246.198.373.292a.077.077 0 0 1-.006.127 12.3 12.3 0 0 1-1.873.891.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.029zM8.02 15.331c-1.182 0-2.157-1.086-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.332-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.332-.946 2.418-2.157 2.418z"/></svg>
|
||||
</a>
|
||||
<a class="fab" href="#videos" aria-label="Community videos" title="Community videos">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="fab-rail right">
|
||||
<a class="fab" href="https://github.com/ALLFATHER-BV/wadamesh/issues" target="_blank" rel="noopener" aria-label="Report a bug on GitHub" title="Report a bug">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
|
||||
</a>
|
||||
<a class="fab" id="docsBtn" href="docs.html" aria-label="Documentation" title="Documentation">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v5h5"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
|
||||
</a>
|
||||
<a class="fab" href="https://t413.com/p/projects/MapTileDl/" target="_blank" rel="noopener" aria-label="Map tile downloader" title="Map tile downloader">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l-6 3V6l6-3 6 3 6-3v15l-6 3-6-3z"/><path d="M9 3v15"/><path d="M15 6v15"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
|
||||
<header>
|
||||
<div class="wrap">
|
||||
@@ -221,7 +329,7 @@
|
||||
<span class="byline">by meshcomod</span>
|
||||
</div>
|
||||
<p class="lede">A real touchscreen UI for your mesh radio.</p>
|
||||
<p class="sub">MeshCore MESHCOMOD firmware for the <b>LilyGo T-Deck</b> and <b>Heltec V4</b> — map, chat, channels and contacts on the device itself, <b>and</b> a companion radio for the <b>MeshCore app</b> at the same time.</p>
|
||||
<p class="sub">MeshCore MESHCOMOD firmware for the <b>LilyGo T-Deck</b>, <b>Heltec V4</b> and <b>Tanmatsu</b> — map, chat, channels and contacts on the device itself, <b>and</b> a companion radio for the <b>MeshCore app</b> at the same time.</p>
|
||||
<div class="pills">
|
||||
<span class="pill" tabindex="0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 20l-6-3V4l6 3 6-3 6 3v13l-6-3-6 3z"/><path d="M9 7v13M15 4v13"/></svg>Offline map
|
||||
<span class="pop"><b>Offline map</b>A real OpenStreetMap you can pan and zoom on the device. Pre-load tile packs to an SD card for fully offline use, or fetch over Wi-Fi — and see your contacts' GPS positions plotted on it.</span></span>
|
||||
@@ -266,6 +374,12 @@
|
||||
<button class="back" data-back>← choose a different path</button>
|
||||
<div class="guide-head"><h3>Standalone install</h3></div>
|
||||
<p class="p">Plug the device in over USB and flash it right here. Works in <b>Chrome</b> or <b>Edge</b> on desktop.</p>
|
||||
<div class="chtoggle" role="group" aria-label="Release channel" style="display:inline-flex;margin:14px 0 0;border:1px solid var(--line);border-radius:10px;overflow:hidden">
|
||||
<button type="button" data-wm-ch="stable" style="font:inherit;font-size:.85rem;font-weight:600;padding:7px 16px;border:0;background:#fff;color:var(--mut);cursor:pointer">Stable</button>
|
||||
<button type="button" data-wm-ch="beta" style="font:inherit;font-size:.85rem;font-weight:600;padding:7px 16px;border:0;border-left:1px solid var(--line);background:#fff;color:var(--mut);cursor:pointer">Beta · testing</button>
|
||||
</div>
|
||||
<p style="margin:12px auto 0;max-width:600px;text-align:left;font-size:.88rem;line-height:1.6"><b>Stable</b> is the tested default. Pick it unless you want to help test. <b>Beta · testing</b> gets new features and fixes earlier, before they are proven, so it can be rough. It is for trying things early and reporting bugs.<br>Already running WADAMESH? You do not have to reflash to switch channels: turn on <b>Settings → About → "Get test builds (beta)"</b> on the device and it updates to test builds over Wi-Fi. Switch it off to go back to stable.</p>
|
||||
<p data-wm-betahint hidden style="margin:8px auto 0;font-size:.84rem;color:#b4631a;max-width:560px">Beta builds are newer and less tested — for trying things early and reporting bugs. On a T-Deck under Launcher you download the .bin and side-load it.</p>
|
||||
<div data-whatsnew hidden style="margin:18px auto 0;max-width:560px;background:rgba(21,182,166,.07);border:1px solid rgba(21,182,166,.28);border-radius:12px;padding:13px 16px;text-align:left">
|
||||
<div style="font-weight:600">What's new in <span data-wn-tag style="color:var(--teal)">the latest build</span></div>
|
||||
<ul data-wn-notes style="margin:7px 0 0;padding-left:18px;font-size:.9rem;color:var(--mut);line-height:1.55"></ul>
|
||||
@@ -275,12 +389,109 @@
|
||||
<div class="step"><div class="num">2</div><div><h4>Pick your board & install</h4><p>Hit <b>Install</b> below, choose the serial port, and let it flash. It does a clean full install.</p></div></div>
|
||||
<div class="step"><div class="num">3</div><div><h4>You're on the mesh</h4><p>The device reboots into WADAMESH. Set your name + region in the first-run wizard.</p></div></div>
|
||||
</div>
|
||||
<div class="boards">
|
||||
<div class="board"><h4>Heltec V4 + TFT</h4><p>ESP32-S3 with CHSC6x touch.</p>
|
||||
<esp-web-install-button manifest="https://firmware.wadamesh.com/latest/manifest-heltec-v4-tft.json"></esp-web-install-button></div>
|
||||
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>ESP32-S3 keyboard handheld.</p>
|
||||
<esp-web-install-button manifest="https://firmware.wadamesh.com/latest/manifest-tdeck.json"></esp-web-install-button></div>
|
||||
<div class="devsel" id="devsel">
|
||||
<div class="devsearch">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-3.6-3.6"/></svg>
|
||||
<input type="search" id="devSearch" autocomplete="off" placeholder="Search by device or maker — T-Deck, Heltec, P4…" aria-label="Search devices">
|
||||
<button type="button" id="devClearX" class="devsearch-x" data-devclear hidden aria-label="Clear search">×</button>
|
||||
</div>
|
||||
<p class="devsearch-empty" data-devempty hidden>No devices match “<span data-devq></span>”. <button type="button" data-devclear>Clear search</button></p>
|
||||
|
||||
<details class="mfr" open data-mfr="LilyGo">
|
||||
<summary><span class="mfr-name">LilyGo</span><span class="mfr-count"></span><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></summary>
|
||||
<div class="boards mfr-boards">
|
||||
<div class="board" data-search="lilygo t-deck tdeck plus esp32-s3 keyboard handheld"><h4>LilyGo T-Deck / Plus</h4><p>ESP32-S3 keyboard handheld.</p>
|
||||
<span class="sup full" tabindex="0"><span class="dot"></span>Fully supported<span class="pop"><b>Fully supported</b>Everything wadamesh offers works here — this is one of the two primary development boards, tested on every release.</span></span>
|
||||
<esp-web-install-button id="inst-tdeck" manifest="https://firmware.wadamesh.com/latest/manifest-tdeck.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="tdeck" download href="https://firmware.wadamesh.com/latest/wadamesh-tdeck-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a></div>
|
||||
<div class="board" data-search="lilygo t-display p4 tdisplay amoled rm69a10 esp32-p4 touch handheld"><h4>LilyGo T-Display P4 <span style="font-weight:600;color:var(--mut)">(AMOLED)</span></h4><p>ESP32-P4 · AMOLED touch handheld.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>Early port — it's known that not everything works yet, but the core does: LoRa mesh communication, chat, map, GPS and the phone-app link over Wi-Fi or USB. Bluetooth pairing isn't available on this board (a hardware limitation of its stock radio firmware).</span></span>
|
||||
<esp-web-install-button id="inst-tdp4" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tdisplay-p4.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="tdisplay-p4" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-tdisplay-p4-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board: beta channel only until the next stable.</p></div>
|
||||
<div class="board" data-search="lilygo t-display p4 tdisplay lcd tft hi8561 esp32-p4 touch handheld"><h4>LilyGo T-Display P4 <span style="font-weight:600;color:var(--mut)">(LCD)</span></h4><p>ESP32-P4 · TFT-LCD touch handheld.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>The LCD-screen model of the T-Display P4 (the AMOLED variant is listed above). Early port — the core works: LoRa mesh, chat, map, GPS and the phone-app link over Wi-Fi or USB. Bluetooth pairing isn't available on this board. Flash with care and report back.</span></span>
|
||||
<esp-web-install-button id="inst-tdp4-lcd"></esp-web-install-button>
|
||||
<a class="dl2" id="dl-tdp4-lcd" download><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" id="dl-tdp4-lcd-ver"></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New screen variant, not yet hardware-tested — beta only. Flash with care and report back.</p></div>
|
||||
<div class="board" data-search="lilygo t-lora pager tlora esp32-s3 qwerty rotary encoder lr1121 sx1262"><h4>LilyGo T-LoRa Pager</h4><p>ESP32-S3 QWERTY + rotary encoder.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>New community port (thanks @codemonkeybr). The core works: LoRa mesh communication, chat, contacts, channels and the phone-app link. Pick the build that matches your radio chip.</span></span>
|
||||
<p style="color:var(--mut);font-size:.8rem;margin:0">Radio <b>LR1121</b>:</p>
|
||||
<esp-web-install-button id="inst-pager-lr1121" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tlora-pager-lr1121.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="tlora-pager-lr1121" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-tlora-pager-lr1121-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p style="color:var(--mut);font-size:.8rem;margin:8px 0 0">Radio <b>SX1262</b>:</p>
|
||||
<esp-web-install-button id="inst-pager-sx1262" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tlora-pager-sx1262.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="tlora-pager-sx1262" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-tlora-pager-sx1262-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board: beta channel only until the next stable.</p></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="mfr" open data-mfr="Heltec">
|
||||
<summary><span class="mfr-name">Heltec</span><span class="mfr-count"></span><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></summary>
|
||||
<div class="boards mfr-boards">
|
||||
<div class="board" data-search="heltec v4 tft esp32-s3 chsc6x touch"><h4>Heltec V4 + TFT</h4><p>ESP32-S3 with CHSC6x touch.</p>
|
||||
<span class="sup full" tabindex="0"><span class="dot"></span>Fully supported<span class="pop"><b>Fully supported</b>Everything wadamesh offers works here — this is one of the two primary development boards, tested on every release.</span></span>
|
||||
<esp-web-install-button id="inst-heltec" manifest="https://firmware.wadamesh.com/latest/manifest-heltec-v4-tft.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="heltec-v4-tft" download href="https://firmware.wadamesh.com/latest/wadamesh-heltec-v4-tft-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a></div>
|
||||
<div class="board" data-search="heltec v4-r8 v4 r8 esp32-s3 psram sd card experimental"><h4>Heltec V4-R8 <span style="font-weight:600;color:var(--mut)">(experimental)</span></h4><p>ESP32-S3 · 8 MB PSRAM · SD card.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>The core works: LoRa mesh communication, chat, contacts, channels and the phone-app link. This revision is still being hardware-verified — flash with care and report back.</span></span>
|
||||
<esp-web-install-button id="inst-v4r8" manifest="https://firmware.wadamesh.com/latest-beta/manifest-heltec-v4-r8-tft.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="heltec-v4-r8-tft" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-heltec-v4-r8-tft-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board, not yet hardware-tested — beta only. Please flash with care and report back.</p></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="mfr" data-mfr="Elecrow">
|
||||
<summary><span class="mfr-name">Elecrow</span><span class="mfr-count"></span><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></summary>
|
||||
<div class="boards mfr-boards">
|
||||
<div class="board" data-search="elecrow thinknode m9 esp32-s3 qwerty lr1110 handheld"><h4>ThinkNode M9</h4><p>ESP32-S3 QWERTY handheld (LR1110).</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>The core works: LoRa mesh communication, chat, contacts, channels and the phone-app link. Not every feature has been ported or hardware-verified on this board yet.</span></span>
|
||||
<esp-web-install-button id="inst-m9" manifest="https://firmware.wadamesh.com/latest-beta/manifest-thinknode-m9.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="thinknode-m9" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-thinknode-m9-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board: beta channel only until the next stable.</p></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="mfr" data-mfr="RAKwireless">
|
||||
<summary><span class="mfr-name">RAKwireless</span><span class="mfr-count"></span><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></summary>
|
||||
<div class="boards mfr-boards">
|
||||
<div class="board" data-search="rakwireless rak wismesh tap v2 esp32-s3 touch node"><h4>RAK WisMesh Tap V2</h4><p>ESP32-S3 touch node.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>The core works: LoRa mesh communication, chat, contacts, channels and the phone-app link. Not every feature has been ported or hardware-verified on this board yet.</span></span>
|
||||
<esp-web-install-button id="inst-rak" manifest="https://firmware.wadamesh.com/latest-beta/manifest-rak-tap-v2.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="rak-tap-v2" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-rak-tap-v2-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board: beta channel only until the next stable.</p></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="mfr" data-mfr="Nicolai Electronics">
|
||||
<summary><span class="mfr-name">Nicolai Electronics</span><span class="mfr-count"></span><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></summary>
|
||||
<div class="boards mfr-boards">
|
||||
<div class="board" data-search="nicolai electronics tanmatsu esp32-p4 keyboard handheld app store"><h4>Tanmatsu</h4><p>ESP32-P4 keyboard handheld.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>The core works: LoRa mesh communication, chat, contacts, channels and the phone-app link over Wi-Fi/USB. Some features are still settling on this board (Bluetooth pairing has known gaps).</span></span>
|
||||
<p style="color:var(--mut);font-size:.84rem;margin:0;line-height:1.5">Already have one? No USB flashing needed — install WADAMESH straight from the <b>Tanmatsu app store</b> on the device.</p>
|
||||
<a class="dl" href="https://shop.nicolaielectronics.nl/" target="_blank" rel="noopener"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/></svg>Get a Tanmatsu</a></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="mfr" data-mfr="Attaky">
|
||||
<summary><span class="mfr-name">Attaky</span><span class="mfr-count"></span><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg></summary>
|
||||
<div class="boards mfr-boards">
|
||||
<div class="board" data-search="attaky core esp32-s3 psram experimental"><h4>Attaky Core <span style="font-weight:600;color:var(--mut)">(experimental)</span></h4><p>ESP32-S3 · 16 MB · 8 MB PSRAM.</p>
|
||||
<span class="sup part" tabindex="0"><span class="dot"></span>Partially supported<span class="pop"><b>Partially supported</b>The core works: LoRa mesh communication, chat, contacts, channels and the phone-app link. This community-contributed board (PR #158) isn't hardware-verified by us yet — flash with care and report back.</span></span>
|
||||
<esp-web-install-button id="inst-attaky" manifest="https://firmware.wadamesh.com/latest-beta/manifest-attaky.json"></esp-web-install-button>
|
||||
<a class="dl2" data-dl-board="attaky" data-dl-betaonly download href="https://firmware.wadamesh.com/latest-beta/wadamesh-attaky-merged.bin"><span class="dl2-row"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</span><span class="dlver" data-dl-ver></span></a>
|
||||
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board, not yet hardware-tested — beta only. Please flash with care and report back.</p></div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<details class="verpick">
|
||||
<summary>Install a previous version</summary>
|
||||
<div class="verpick-body">
|
||||
<div class="verpick-row"><label for="verSel">Version</label><select id="verSel" aria-label="Firmware version"></select></div>
|
||||
<p class="verpick-note" id="verActive" hidden></p>
|
||||
<div id="verNotes" class="rn"></div>
|
||||
</div>
|
||||
</details>
|
||||
<div class="noserial">Your browser can't flash here — use <b>Chrome</b> or <b>Edge</b> on a desktop.</div>
|
||||
<p class="hint">T-Deck not detected? Hold the trackball while tapping reset to force download mode.</p>
|
||||
</div>
|
||||
@@ -290,6 +501,11 @@
|
||||
<div class="guide-head"><h3>Install with bmorcelli's Launcher</h3></div>
|
||||
<p class="p"><a href="https://bmorcelli.github.io/Launcher/" target="_blank" rel="noopener">Launcher</a> is a multi-app boot menu for ESP32 devices — keep it, and run WADAMESH as one of your apps.</p>
|
||||
<p class="p" style="margin-top:10px"><b>This path is for the LilyGo T-Deck only.</b> On the Heltec V4, install <a href="#standalone" onclick="choose('standalone');return false">Standalone</a> instead.</p>
|
||||
<div class="chtoggle" role="group" aria-label="Release channel" style="display:inline-flex;margin:14px 0 0;border:1px solid var(--line);border-radius:10px;overflow:hidden">
|
||||
<button type="button" data-wm-ch="stable" style="font:inherit;font-size:.85rem;font-weight:600;padding:7px 16px;border:0;background:#fff;color:var(--mut);cursor:pointer">Stable</button>
|
||||
<button type="button" data-wm-ch="beta" style="font:inherit;font-size:.85rem;font-weight:600;padding:7px 16px;border:0;border-left:1px solid var(--line);background:#fff;color:var(--mut);cursor:pointer">Beta · testing</button>
|
||||
</div>
|
||||
<p style="margin:8px auto 0;max-width:560px;font-size:.84rem;color:var(--mut)"><b>Stable</b> is the tested default; <b>Beta</b> gets new builds earlier. The download below follows this choice.</p>
|
||||
<div class="steps" style="margin-top:22px">
|
||||
<div class="step"><div class="num">1</div><div><h4>Install Launcher first</h4><p>Flash bmorcelli's Launcher to your device, following its own installer — <a href="https://bmorcelli.github.io/Launcher/" target="_blank" rel="noopener">bmorcelli.github.io/Launcher</a>.</p></div></div>
|
||||
<div class="step"><div class="num">2</div><div><h4>Add the WADAMESH app</h4><p>Grab the WADAMESH <b>app image</b> for your board below, then install it from Launcher (via its web UI / OTA, or copy the <code>.bin</code> to your SD card).</p></div></div>
|
||||
@@ -297,13 +513,22 @@
|
||||
</div>
|
||||
<div class="boards" style="grid-template-columns:1fr; max-width:380px">
|
||||
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>app image (no bootloader)</p>
|
||||
<a class="dl" href="https://firmware.wadamesh.com/latest/wadamesh-tdeck.bin"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</a></div>
|
||||
<a class="dl" id="dl-tdeck" download href="https://firmware.wadamesh.com/latest/wadamesh-tdeck.bin"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 11l5 5 5-5M5 21h14"/></svg>Download .bin</a>
|
||||
<p style="margin:11px 0 0;font-size:.84rem;color:var(--mut);line-height:1.5">You're downloading version <b data-wn-tag style="color:var(--teal)">…</b><span data-wm-chlabel></span>.<br>The file saves as <code data-dl-fname style="font-size:.82em">wadamesh-tdeck.bin</code> — rename it if you keep several.</p></div>
|
||||
</div>
|
||||
<p class="hint">New to Launcher? It's the easiest way to try WADAMESH without giving up your other firmware — and you can fully switch to Standalone any time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="videos">
|
||||
<div class="wrap">
|
||||
<h2 class="h2">Community videos</h2>
|
||||
<p class="p">Walkthroughs, demos and reviews from the community.</p>
|
||||
<div class="vids" id="vids"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="wrap">
|
||||
Built for the mesh · open source under GPL-3.0 ·
|
||||
@@ -404,21 +629,197 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
|
||||
try { if (!localStorage.getItem('wm-cookie-ack')) cbar.classList.add('show'); } catch(_) {}
|
||||
document.getElementById('cookieok').addEventListener('click', () => { cbar.classList.remove('show'); try { localStorage.setItem('wm-cookie-ack','1'); } catch(_) {} });
|
||||
|
||||
// ---- donate banner: dismissible; shifts the corner FABs down while shown ----
|
||||
(function(){
|
||||
const bar = document.getElementById('donateBar'); if (!bar) return;
|
||||
function setFabTop(){ document.documentElement.style.setProperty('--fab-top', (getComputedStyle(bar).display === 'none' ? 16 : bar.offsetHeight + 12) + 'px'); }
|
||||
try { if (localStorage.getItem('wm-donate-dismiss') === '1') bar.style.display = 'none'; } catch(_) {}
|
||||
setFabTop();
|
||||
addEventListener('resize', setFabTop);
|
||||
document.getElementById('donateX').addEventListener('click', () => { bar.style.display = 'none'; setFabTop(); try { localStorage.setItem('wm-donate-dismiss','1'); } catch(_) {} });
|
||||
})();
|
||||
|
||||
// Live release version + "what's new" — read from the firmware host so the
|
||||
// flasher always reflects the current build (the release pipeline updates
|
||||
// version.json; no site redeploy needed per release).
|
||||
fetch('https://firmware.wadamesh.com/latest/version.json', {cache:'no-store'})
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(v => {
|
||||
if (!v || !v.tag) return;
|
||||
document.querySelectorAll('[data-wn-tag]').forEach(e => e.textContent = v.tag);
|
||||
const notes = Array.isArray(v.notes) ? v.notes : [];
|
||||
document.querySelectorAll('[data-wn-notes]').forEach(ul => {
|
||||
ul.innerHTML = '';
|
||||
notes.forEach(n => { const li = document.createElement('li'); li.textContent = n; ul.appendChild(li); });
|
||||
});
|
||||
if (notes.length) document.querySelectorAll('[data-whatsnew]').forEach(b => b.hidden = false);
|
||||
}).catch(()=>{});
|
||||
// Release channel (Stable default / Beta testing). The toggle swaps the feed dir
|
||||
// on the firmware host — latest/ (stable) <-> latest-beta/ (beta) — and the install
|
||||
// manifests, "what's new", the Launcher .bin link and the version picker follow.
|
||||
const WM = (window.WM = { feed:'latest', arch:'TOUCH' });
|
||||
// ThinkNode M9 + RAK Tap V2 exist on the BETA feed only until the first stable
|
||||
// promote that includes them: their install buttons are pinned to latest-beta in
|
||||
// the HTML and deliberately NOT switched by wmSetChannel. When a stable ships
|
||||
// with their bins, add them here + to wmSetChannel and drop the pinned notes.
|
||||
const WM_MAN = { heltec:'manifest-heltec-v4-tft.json', tdeck:'manifest-tdeck.json' };
|
||||
window.wmManifest = board => 'https://firmware.wadamesh.com/' + WM.feed + '/' + WM_MAN[board];
|
||||
function wmWhatsNew(){
|
||||
const wn = document.querySelectorAll('[data-whatsnew]');
|
||||
fetch('https://firmware.wadamesh.com/' + WM.feed + '/version.json', {cache:'no-store'})
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(v => {
|
||||
if (!v || !v.tag) { wn.forEach(b => b.hidden = true); return; }
|
||||
document.querySelectorAll('[data-wn-tag]').forEach(e => e.textContent = v.tag);
|
||||
const notes = Array.isArray(v.notes) ? v.notes : [];
|
||||
document.querySelectorAll('[data-wn-notes]').forEach(ul => {
|
||||
ul.innerHTML = '';
|
||||
notes.forEach(n => { const li = document.createElement('li'); li.textContent = n; ul.appendChild(li); });
|
||||
});
|
||||
wn.forEach(b => b.hidden = !notes.length);
|
||||
}).catch(() => { wn.forEach(b => b.hidden = true); });
|
||||
}
|
||||
window.wmSetChannel = function(ch){
|
||||
WM.feed = ch === 'beta' ? 'latest-beta' : 'latest';
|
||||
WM.arch = ch === 'beta' ? 'BETA' : 'TOUCH';
|
||||
const bH = document.getElementById('inst-heltec'), bT = document.getElementById('inst-tdeck');
|
||||
if (bH) bH.setAttribute('manifest', wmManifest('heltec'));
|
||||
if (bT) bT.setAttribute('manifest', wmManifest('tdeck'));
|
||||
const dl = document.getElementById('dl-tdeck');
|
||||
if (dl) dl.href = 'https://firmware.wadamesh.com/' + WM.feed + '/wadamesh-tdeck.bin';
|
||||
document.querySelectorAll('[data-wm-chlabel]').forEach(e => e.textContent = ch === 'beta' ? ' · beta (testing)' : ' · stable');
|
||||
document.querySelectorAll('[data-wm-ch]').forEach(b => { const on = b.getAttribute('data-wm-ch') === ch;
|
||||
b.style.background = on ? 'var(--teal)' : '#fff'; b.style.color = on ? '#fff' : 'var(--mut)'; });
|
||||
document.querySelectorAll('[data-wm-betahint]').forEach(e => e.hidden = ch !== 'beta');
|
||||
const vs = document.getElementById('verSel');
|
||||
if (vs && vs.options.length) { vs.selectedIndex = 0; vs.dispatchEvent(new Event('change')); }
|
||||
wmUpdateDownloads();
|
||||
wmWhatsNew();
|
||||
};
|
||||
// Per-board "download the .bin" links on the standalone cards: follow the channel
|
||||
// toggle (beta-only boards stay pinned to latest-beta) and show the exact version
|
||||
// each feed serves — both channel tags are fetched once so every link is accurate.
|
||||
const WM_VER = {};
|
||||
// The T-Display P4 LCD-screen variant ships only in the per-tag BETA archive (no
|
||||
// rolling-feed bin, no server manifest), so wire its Install button to a manifest
|
||||
// built client-side (same blob trick as the previous-version picker) and point its
|
||||
// download at the immutable archive bin. Called once the beta tag is known.
|
||||
window.wmWireP4Lcd = function(tag){
|
||||
const bin = 'https://firmware.wadamesh.com/releases/BETA/' + tag + '/wadamesh-tdisplay-p4-lcd-merged.bin';
|
||||
const btn = document.getElementById('inst-tdp4-lcd');
|
||||
if (btn){ const m = { name:'wadamesh ' + tag + ' \u2014 T-Display P4 (LCD)', version:tag, new_install_prompt_erase:true,
|
||||
builds:[{ chipFamily:'ESP32-P4', parts:[{ path:bin, offset:0 }] }] };
|
||||
btn.setAttribute('manifest', URL.createObjectURL(new Blob([JSON.stringify(m)], {type:'application/json'}))); }
|
||||
const dl = document.getElementById('dl-tdp4-lcd');
|
||||
if (dl){ dl.href = bin; const v = document.getElementById('dl-tdp4-lcd-ver'); if (v) v.textContent = tag; }
|
||||
};
|
||||
window.wmUpdateDownloads = function(){
|
||||
document.querySelectorAll('[data-dl-board]').forEach(a => {
|
||||
const feed = a.hasAttribute('data-dl-betaonly') ? 'latest-beta' : WM.feed;
|
||||
a.href = 'https://firmware.wadamesh.com/' + feed + '/wadamesh-' + a.getAttribute('data-dl-board') + '-merged.bin';
|
||||
const v = a.querySelector('[data-dl-ver]'); if (v) v.textContent = WM_VER[feed] || '';
|
||||
});
|
||||
};
|
||||
['latest','latest-beta'].forEach(feed => fetch('https://firmware.wadamesh.com/' + feed + '/version.json', {cache:'no-store'})
|
||||
.then(r => r.ok ? r.json() : null).then(v => { if (v && v.tag) { WM_VER[feed] = v.tag; wmUpdateDownloads(); if (feed === 'latest-beta' && window.wmWireP4Lcd) wmWireP4Lcd(v.tag); } }).catch(()=>{}));
|
||||
document.querySelectorAll('[data-wm-ch]').forEach(b => b.addEventListener('click', () => wmSetChannel(b.getAttribute('data-wm-ch'))));
|
||||
wmSetChannel('stable');
|
||||
|
||||
// ---- device selector: fill counts, remember default open state, live search ----
|
||||
(function(){
|
||||
const root = document.getElementById('devsel'); if (!root) return;
|
||||
const input = document.getElementById('devSearch');
|
||||
const clearX = document.getElementById('devClearX');
|
||||
const empty = root.querySelector('[data-devempty]'), qout = root.querySelector('[data-devq]');
|
||||
const groups = Array.from(root.querySelectorAll('.mfr'));
|
||||
groups.forEach(g => { g.dataset.defopen = g.open ? '1' : '0';
|
||||
const n = g.querySelectorAll('.board').length, c = g.querySelector('.mfr-count');
|
||||
if (c) c.textContent = n + (n === 1 ? ' device' : ' devices'); });
|
||||
const txt = b => (b.getAttribute('data-search') || b.textContent || '').toLowerCase();
|
||||
function run(q){
|
||||
const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
let anyVisible = false;
|
||||
groups.forEach(g => { let vis = 0;
|
||||
g.querySelectorAll('.board').forEach(b => { const ok = !terms.length || terms.every(t => txt(b).includes(t));
|
||||
b.hidden = !ok; if (ok) vis++; });
|
||||
g.hidden = vis === 0;
|
||||
if (!g.hidden){ anyVisible = true; g.open = terms.length ? true : (g.dataset.defopen === '1'); } });
|
||||
if (empty) empty.hidden = !(terms.length && !anyVisible);
|
||||
if (qout) qout.textContent = q;
|
||||
if (clearX) clearX.hidden = !q;
|
||||
}
|
||||
function clr(){ if (input){ input.value = ''; input.focus(); } run(''); }
|
||||
if (input){ input.addEventListener('input', () => run(input.value));
|
||||
input.addEventListener('keydown', e => { if (e.key === 'Escape') clr(); }); }
|
||||
root.querySelectorAll('[data-devclear]').forEach(b => b.addEventListener('click', clr));
|
||||
})();
|
||||
|
||||
// ---- community videos — add an entry here to add a video ----
|
||||
const VIDEOS = [
|
||||
// newest first
|
||||
{ id: 'e63uKp9r65U', title: 'LilyGo T-Deck Plus + WADAMESH: The Firmware That Finally Makes It Worth It', by: 'Signal Trek' },
|
||||
{ id: '1I7Oe1SqFlk', title: 'WadaMesh Update! Supercharge Your T-Deck', by: 'KDHD Stuff' },
|
||||
{ id: 's-8pRsC0TYA', title: 'WadaMesh: The Feature That Makes T-Deck Plus Stand Out', by: 'BFGNeil' },
|
||||
{ id: 'V19od8jbFRc', title: 'WadaMesh: The Best T-Deck Firmware So Far', by: 'KDHD Stuff' },
|
||||
// { id: '<youtube-id>', title: '<title>', by: '<channel>' },
|
||||
];
|
||||
const vidsEl = document.getElementById('vids');
|
||||
if (vidsEl) VIDEOS.forEach(v => {
|
||||
const card = document.createElement('div'); card.className = 'vid';
|
||||
const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'vid-thumb';
|
||||
btn.setAttribute('aria-label', 'Play: ' + (v.title || 'community video'));
|
||||
btn.style.backgroundImage = 'url(https://i.ytimg.com/vi/' + v.id + '/hqdefault.jpg)';
|
||||
btn.innerHTML = '<span class="play"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg></span>';
|
||||
btn.addEventListener('click', () => {
|
||||
const f = document.createElement('iframe'); f.className = 'vid-embed';
|
||||
f.src = 'https://www.youtube-nocookie.com/embed/' + v.id + '?autoplay=1&rel=0';
|
||||
f.title = v.title || 'Community video';
|
||||
f.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
|
||||
f.allowFullscreen = true; f.loading = 'lazy';
|
||||
card.replaceChild(f, btn);
|
||||
});
|
||||
card.appendChild(btn);
|
||||
if (v.title) {
|
||||
const cap = document.createElement('div'); cap.className = 'vid-cap'; cap.textContent = v.title;
|
||||
if (v.by) { const s = document.createElement('span'); s.textContent = 'by ' + v.by; cap.appendChild(s); }
|
||||
card.appendChild(cap);
|
||||
}
|
||||
vidsEl.appendChild(card);
|
||||
});
|
||||
|
||||
// ---- toast + docs placeholder ----
|
||||
function toast(msg){ const t = document.getElementById('toast'); if (!t) return; t.textContent = msg; t.classList.add('show'); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove('show'), 2400); }
|
||||
// The documentation button is a plain link to docs.html (no handler needed).
|
||||
|
||||
// ---- standalone: install a previous version (latest 4 betas + release notes) ----
|
||||
(function(){
|
||||
const sel = document.getElementById('verSel'); if (!sel) return;
|
||||
const wrap = sel.closest('.verpick');
|
||||
const notesEl = document.getElementById('verNotes'), activeEl = document.getElementById('verActive');
|
||||
const btnH = document.getElementById('inst-heltec'), btnT = document.getElementById('inst-tdeck');
|
||||
const latestH = () => window.wmManifest('heltec'), latestT = () => window.wmManifest('tdeck');
|
||||
const num = t => parseInt((String(t).match(/beta_(\d+)/) || [])[1] || '-1', 10);
|
||||
let notes = {};
|
||||
const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
const inl = s => esc(s).replace(/\*\*(.+?)\*\*/g,'<b>$1</b>').replace(/`([^`]+?)`/g,'<code>$1</code>')
|
||||
.replace(/\[(.+?)\]\((https?:\/\/[^)\s]+)\)/g,'<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||
function md(t){ let out='', ul=false; const close=()=>{ if(ul){ out+='</ul>'; ul=false; } };
|
||||
(t||'').split(/\r?\n/).forEach(line=>{ line=line.trim(); let m;
|
||||
if(!line) close();
|
||||
else if(m=line.match(/^#{1,6}\s+(.*)/)){ close(); out+='<div class="rn-h">'+inl(m[1])+'</div>'; }
|
||||
else if(m=line.match(/^[-*]\s+(.*)/)){ if(!ul){ out+='<ul>'; ul=true; } out+='<li>'+inl(m[1])+'</li>'; }
|
||||
else { close(); out+='<p>'+inl(line)+'</p>'; } });
|
||||
close(); return out; }
|
||||
function showNotes(tag){ notesEl.innerHTML = notes[tag] ? md(notes[tag])
|
||||
: '<p>Release notes on <a href="https://github.com/ALLFATHER-BV/wadamesh/releases/tag/'+tag+'" target="_blank" rel="noopener">GitHub</a>.</p>'; }
|
||||
function blobManifest(tag, board){ const m = { name:'wadamesh '+tag, version:tag, new_install_prompt_erase:true,
|
||||
builds:[{ chipFamily:'ESP32-S3', parts:[{ path:'https://firmware.wadamesh.com/releases/'+((window.WM&&window.WM.arch)||'TOUCH')+'/'+tag+'/wadamesh-'+board+'-merged.bin', offset:0 }] }] };
|
||||
return URL.createObjectURL(new Blob([JSON.stringify(m)], {type:'application/json'})); }
|
||||
function apply(){ const tag = sel.value, isLatest = sel.selectedIndex === 0;
|
||||
if (isLatest){ if(btnH) btnH.setAttribute('manifest', latestH()); if(btnT) btnT.setAttribute('manifest', latestT()); activeEl.hidden = true; }
|
||||
else { if(btnH) btnH.setAttribute('manifest', blobManifest(tag,'heltec-v4-tft')); if(btnT) btnT.setAttribute('manifest', blobManifest(tag,'tdeck'));
|
||||
activeEl.hidden = false; activeEl.innerHTML = 'The <b>Install</b> buttons above now flash <b>'+tag+'</b>.'; }
|
||||
showNotes(tag); }
|
||||
// GitHub API gives version list + notes in one CORS-enabled call (the firmware
|
||||
// host listing has no CORS); bins are still pulled from the firmware host.
|
||||
fetch('https://api.github.com/repos/ALLFATHER-BV/wadamesh/releases?per_page=12').then(r=>r.ok?r.json():[]).then(rs=>{
|
||||
const rels = (Array.isArray(rs)?rs:[]).filter(r=>r&&/^beta_\d+$/.test(r.tag_name||''));
|
||||
rels.forEach(r=>{ notes[r.tag_name]=r.body||''; });
|
||||
const tags = rels.map(r=>r.tag_name).sort((a,b)=>num(b)-num(a)).slice(0,4);
|
||||
if (!tags.length){ if(wrap) wrap.hidden = true; return; }
|
||||
sel.innerHTML = tags.map((t,i)=>'<option value="'+t+'">'+t+(i===0?' — latest':'')+'</option>').join('');
|
||||
showNotes(tags[0]);
|
||||
sel.addEventListener('change', apply);
|
||||
}).catch(()=>{ if(wrap) wrap.hidden = true; });
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# WadaMesh
|
||||
|
||||
A full **[MeshCore](https://github.com/meshcore-dev/MeshCore)** client for the Tanmatsu.
|
||||
It runs the LoRa mesh, Wi-Fi and Bluetooth radios **all at the same time**, so the device
|
||||
works as a complete standalone communicator *and* as a phone companion at once — no
|
||||
mode-switching, no rebooting to swap radios.
|
||||
|
||||
MeshCore is an encrypted, infrastructure-free LoRa mesh network — messages hop from device
|
||||
to device with no cell tower, Wi-Fi or internet in between. WadaMesh gives it a full
|
||||
graphical front-end on the Tanmatsu's own screen and keyboard.
|
||||
|
||||
## Why WadaMesh
|
||||
|
||||
- **Multi-radio, simultaneously.** LoRa, Wi-Fi and Bluetooth all live at once. Chat on the
|
||||
mesh while Wi-Fi streams map tiles and a phone stays paired over Bluetooth — concurrently.
|
||||
- **Standalone *and* companion at the same time.** A complete client on its own screen and
|
||||
keyboard, while a phone app can connect over Bluetooth or Wi-Fi in parallel. You don't
|
||||
have to pick a role.
|
||||
- **Live mesh map.** Plot yourself and your contacts on a real OpenStreetMap from their
|
||||
shared GPS positions (tiles fetched and cached over Wi-Fi).
|
||||
- **Built for the keyboard.** Arrow-key navigation, type-to-edit and function-key shortcuts —
|
||||
designed around the Tanmatsu's hardware, not bolted on from a touchscreen.
|
||||
- **Polished and localised.** Full-colour emoji and 11 interface languages.
|
||||
|
||||
## Features
|
||||
|
||||
- **Messaging** — direct messages and channels with unread markers, a "new messages"
|
||||
divider and quick replies.
|
||||
- **Contacts & map** — contact list with live signal and distance, plus the mesh map.
|
||||
- **Radio control** — frequency / spreading factor / bandwidth / TX power, and GPS.
|
||||
- **Connectivity** — LoRa, Wi-Fi and Bluetooth at once; use it standalone or as a Wi-Fi +
|
||||
Bluetooth companion for a phone.
|
||||
|
||||
## Links
|
||||
|
||||
- Source & releases: <https://github.com/ALLFATHER-BV/wadamesh>
|
||||
- Web flasher & project info: <https://wadamesh.com>
|
||||
|
||||
## Licence
|
||||
|
||||
WadaMesh is distributed under **GPL-3.0** — see [LICENSE](LICENSE). It incorporates
|
||||
[MeshCore](https://github.com/meshcore-dev/MeshCore) (MIT, © Scott Powell /
|
||||
rippleradios.com); MeshCore-derived files keep their MIT notices, and the combined
|
||||
work is GPL (MIT is GPL-compatible). See [NOTICE](NOTICE) for third-party licenses.
|
||||
|
After Width: | Height: | Size: 604 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "WadaMesh",
|
||||
"description": "MeshCore client running LoRa Wi-Fi & Bluetooth at once. A standalone unit and phone companion in one.",
|
||||
"categories": [
|
||||
"tools",
|
||||
"utils",
|
||||
"hardware"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"icon": {
|
||||
"16x16": "icon16.png",
|
||||
"32x32": "icon32.png",
|
||||
"64x64": "icon64.png"
|
||||
},
|
||||
"author": "Kaj Schittecat - ALLFATHER BV",
|
||||
"license_type": "GPL-3.0",
|
||||
"license_file": "LICENSE",
|
||||
"application": [
|
||||
{
|
||||
"targets": [
|
||||
"tanmatsu",
|
||||
"konsool"
|
||||
],
|
||||
"revision": 1,
|
||||
"type": "appfs",
|
||||
"executable": "wadamesh.bin",
|
||||
"assets": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -39,8 +39,17 @@ except ImportError:
|
||||
sys.exit("ERROR: pip install flask pillow requests")
|
||||
|
||||
OSM_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
# OSM tile policy: identify yourself with a contactable UA. This is what OSM
|
||||
# sees — the device never talks to OSM directly.
|
||||
# OpenTopoMap (topographic / terrain-relief style) — an OPT-IN alternate style.
|
||||
# The firmware default stays OpenStreetMap; the device only requests /opentopo/
|
||||
# when the user enables it. Like OSM it serves PNG, so it goes through the same
|
||||
# fetch+transcode path. Legal: map style © OpenTopoMap (CC-BY-SA), underlying
|
||||
# data © OpenStreetMap contributors (ODbL) + SRTM — the touch UI shows that
|
||||
# attribution whenever topo is the selected style. OpenTopoMap's tile-usage
|
||||
# policy asks for a contactable User-Agent + caching; nginx caches results 14
|
||||
# days so each unique tile hits OpenTopoMap at most once per fortnight.
|
||||
OPENTOPO_URL = "https://tile.opentopomap.org/{z}/{x}/{y}.png"
|
||||
# Tile policy: identify yourself with a contactable UA. This is what the upstream
|
||||
# tile servers see — the device never talks to them directly.
|
||||
OSM_UA = "wadamesh-tile-proxy/1.0 (+https://wadamesh.com)"
|
||||
JPEG_QUALITY = 80 # slippy tiles compress well; 80 ≈ visually lossless
|
||||
MAX_TILE_BYTES = 256 * 1024
|
||||
@@ -57,40 +66,56 @@ _session = requests.Session()
|
||||
_session.headers.update({"User-Agent": OSM_UA, "Accept": "image/png,image/*"})
|
||||
|
||||
|
||||
@app.get("/<int:z>/<int:x>/<int:y>.jpg")
|
||||
def tile(z: int, x: int, y: int):
|
||||
# Range-check before touching OSM so we can't be turned into an open proxy.
|
||||
def _fetch_tile_png(upstream_url: str, z: int, x: int, y: int) -> bytes:
|
||||
# Range-check before touching the upstream so we can't be turned into an open proxy.
|
||||
if not (0 <= z <= 19):
|
||||
abort(404)
|
||||
n = 1 << z
|
||||
if not (0 <= x < n and 0 <= y < n):
|
||||
abort(404)
|
||||
|
||||
try:
|
||||
r = _session.get(OSM_URL.format(z=z, x=x, y=y), timeout=REQUEST_TIMEOUT,
|
||||
r = _session.get(upstream_url.format(z=z, x=x, y=y), timeout=REQUEST_TIMEOUT,
|
||||
stream=True)
|
||||
except requests.RequestException:
|
||||
abort(502)
|
||||
|
||||
if r.status_code != 200:
|
||||
# Pass OSM's status through (404 = empty/sea tile — nginx caches it).
|
||||
# Pass the upstream status through (404 = empty/sea tile — nginx caches it).
|
||||
abort(r.status_code if r.status_code in (404, 429) else 502)
|
||||
|
||||
raw = r.raw.read(MAX_TILE_BYTES + 1, decode_content=True)
|
||||
if len(raw) > MAX_TILE_BYTES:
|
||||
abort(502)
|
||||
return raw
|
||||
|
||||
|
||||
def _tile_response_from_png(raw: bytes) -> Response:
|
||||
try:
|
||||
img = Image.open(io.BytesIO(raw)).convert("RGB")
|
||||
out = io.BytesIO()
|
||||
img.save(out, "JPEG", quality=JPEG_QUALITY, optimize=True)
|
||||
except Exception:
|
||||
abort(502)
|
||||
|
||||
return Response(out.getvalue(), mimetype="image/jpeg",
|
||||
headers={"Cache-Control": "public, max-age=2592000"})
|
||||
|
||||
|
||||
# Default path the firmware uses → OpenStreetMap (the shipping default style).
|
||||
@app.get("/<int:z>/<int:x>/<int:y>.jpg")
|
||||
def tile(z: int, x: int, y: int):
|
||||
return _tile_response_from_png(_fetch_tile_png(OSM_URL, z, x, y))
|
||||
|
||||
|
||||
# Explicit OpenStreetMap alias (identical to the root path).
|
||||
@app.get("/osm/<int:z>/<int:x>/<int:y>.jpg")
|
||||
def tile_osm(z: int, x: int, y: int):
|
||||
return _tile_response_from_png(_fetch_tile_png(OSM_URL, z, x, y))
|
||||
|
||||
|
||||
# OpenTopoMap (topographic) — opt-in alternate style selected on the device.
|
||||
@app.get("/opentopo/<int:z>/<int:x>/<int:y>.jpg")
|
||||
def tile_opentopo(z: int, x: int, y: int):
|
||||
return _tile_response_from_png(_fetch_tile_png(OPENTOPO_URL, z, x, y))
|
||||
|
||||
|
||||
@app.get("/elev")
|
||||
def elev():
|
||||
"""Elevation profile for the line-of-sight analyzer.
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#define LV_COLOR_DEPTH 16
|
||||
#define LV_COLOR_16_SWAP 0
|
||||
|
||||
// NOTE: LV_USE_LARGE_COORD is intentionally OFF — enabling it caused a boot-time
|
||||
// LoadProhibited in LVGL on the T-Deck. Chat scroll heights >8191 px are handled
|
||||
// in UITask via int32 layout offsets + compressed LVGL scroll coords.
|
||||
|
||||
// Route LVGL's general allocator through PSRAM-preferred wrappers (defined in
|
||||
// LvglPsramAlloc.cpp). The board has 8 MB of PSRAM and the ~60 KB of widget
|
||||
// state LVGL grows during buildUiTree would otherwise come out of the 320 KB
|
||||
@@ -48,6 +52,15 @@
|
||||
#define LV_FONT_MONTSERRAT_12 1
|
||||
#define LV_FONT_MONTSERRAT_14 1
|
||||
#define LV_FONT_MONTSERRAT_16 1
|
||||
/* Larger Montserrat sizes for the large-screen boards' crisp "UI size" (Large/Huge) — render the
|
||||
* UI bigger at native resolution instead of upscaling a low-res frame. Gated to the CAP_LARGE_SCREEN
|
||||
* P4 boards (Tanmatsu + T-Display P4) so the flash-tighter S3 touch builds don't pay for fonts they
|
||||
* never use. (device_caps.h isn't included here, so match on the board macros directly.) */
|
||||
#if defined(HAS_TANMATSU) || defined(HAS_TDISPLAY_P4)
|
||||
#define LV_FONT_MONTSERRAT_18 1
|
||||
#define LV_FONT_MONTSERRAT_20 1
|
||||
#define LV_FONT_MONTSERRAT_24 1
|
||||
#endif
|
||||
/* 28 px Montserrat for the boot splash title — keeps the rest of the UI on
|
||||
* the smaller fonts so the .data cost stays modest. */
|
||||
#define LV_FONT_MONTSERRAT_28 1
|
||||
@@ -100,6 +113,7 @@
|
||||
* render full-colour emoji inline with text. The emoji set is pre-baked to
|
||||
* RGB565+alpha lv_img_dsc_t C-arrays (emoji_data.c) and wired as the tail of the
|
||||
* font fallback chain in UITask (montserrat -> extras -> emoji). */
|
||||
#define LV_USE_FONT_COMPRESSED 1 /* decode RLE-compressed extras/accent fonts (Tanmatsu size budget) */
|
||||
#define LV_USE_IMGFONT 1
|
||||
|
||||
/* Bi-directional text + Arabic/Persian contextual shaping. Lets Arabic (and
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# beta_11 — what's new (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Keyboard navigation (headline) ---
|
||||
New keyboard navigation — drive the whole UI from the keyboard, no touchscreen needed: the WASDZ cluster moves the on-screen selection in the actual direction you press (W up, A left, Z down, D right), S selects, Q goes back, and each bottom tab has its own hotkey (E Messages, R Contacts, T Home, U Map, I Settings) shown as a small letter on its icon. On by default; toggle it any time in Settings → Keyboard.
|
||||
Every navigation key is hotkey-bindable — the directions, select, back, the per-tab hotkeys, and the new scroll-up / scroll-down keys (default F / C) — remap them all under Settings → Keyboard.
|
||||
This lays the groundwork for upcoming keyboard-first devices like the Tanmatsu T-Pager that have no touchscreen, and it already gives every T-Deck a fast, reliable way to use wadamesh entirely from the keyboard.
|
||||
The selected item now shows as clean reverse-video (a bright outline for switches) instead of a coloured border, and the highlight only appears while you're actively navigating with the keys.
|
||||
The Home hotkey mirrors tapping the Home tab — press it again on Home to flip between the Commander screen and the app drawer.
|
||||
|
||||
# --- Stability ---
|
||||
Fixed a reboot that could occur when receiving a message, especially during advert/message floods — a use-after-free while a list rebuilt mid-scroll (#27).
|
||||
After a crash, the device now detects it on the next boot and offers to save the crash report so you can send it in, instead of you having to dig through Settings.
|
||||
|
||||
# --- Map ---
|
||||
Map options can now switch the zoom control between the slider and a +/- button pair, like older builds (#26).
|
||||
Route replay now opens on a wide overview so you can see where the hops start and the whole path at a glance — and you can pan and zoom the map freely while it plays.
|
||||
|
||||
# --- Home ---
|
||||
New "App drawer as home" option (in the app drawer's cog) makes the launcher your home screen instead of the Commander view, for people who prefer the app grid.
|
||||
|
||||
# --- Open source ---
|
||||
The in-progress Tanmatsu (ESP32-P4) port source is now part of the public tree.
|
||||
@@ -0,0 +1,8 @@
|
||||
# beta_12 — GPS hotfix (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- GPS fix (headline) ---
|
||||
Fixed GPS being slow to get a fix — or sometimes never acquiring at all — on the T-Deck. The GPS receives its data over a small serial buffer, and on the busy touch UI a heavy screen redraw could stall long enough to overflow it and drop the satellite data mid-stream, which forced the receiver to start over. The buffer is now 16x larger, so those redraws no longer cost you the fix — acquisition is back to normal (a fix in about a minute instead of several, or none).
|
||||
|
||||
# --- A note from Kaj ---
|
||||
More and more people are picking up wadamesh lately — which is genuinely awesome — and it's surfacing feature requests and bug reports fast, in big numbers.
|
||||
I can't get back to every post in every channel, but I'm on it: I'm prioritising the fixes and quality-of-life improvements, and I'll be working through them later today. Thanks for all the reports and for the patience!
|
||||
@@ -0,0 +1,29 @@
|
||||
# beta_13 — community drop (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- A note from Kaj (headline) ---
|
||||
This one's a community release — thank you! A bunch of the features below came from contributor pull requests, and the rest are the keyboard, typing and quality-of-life fixes I promised after the last drop. Huge thanks to everyone sending PRs, opening issues and reporting bugs: Yazutsu, tun0, Vybo and samuelcoustet all have their work in this build. Keep it coming.
|
||||
|
||||
# --- Typing & keyboard navigation (T-Deck + Tanmatsu) ---
|
||||
Open a chat or channel and the cursor is ready — you can type straight away. Every other text field now waits for Enter before it captures the keyboard, so the letter-navigation keys keep working until you actually want to type.
|
||||
Pressing Enter (or Esc / the back button) on an empty message box drops the cursor and re-selects the field, so you can navigate away with the buttons again.
|
||||
The menu-bar letter shortcuts no longer jump you to another page while you're typing in a field.
|
||||
New "Show menu-bar letters" setting under Settings -> Keyboard (off by default) — turn it on to see the tab hotkey letters on the bar.
|
||||
The accent pop-up no longer grabs the keyboard when it appears, so you can keep typing right through it.
|
||||
|
||||
# --- Composer ---
|
||||
New dedicated "Special characters" picker for symbols that aren't on the keyboard (% $ @ # & =, currency, maths, accents) — separate from the emoji picker.
|
||||
Long-press one of your sent messages to Resend it.
|
||||
|
||||
# --- Clock ---
|
||||
The 12-hour clock setting now applies everywhere — the top bar, the chat bubbles and the lock screen — not just the chat list.
|
||||
|
||||
# --- From contributors ---
|
||||
Room-server posts now show the actual author's name on each message instead of the room's own name. (Yazutsu, #40)
|
||||
New "Direct (0-hop)" filter for the contact list and the map — show only nodes you're hearing directly, with no repeater hops in between. (tun0, #39)
|
||||
Beer emoji in colour, tap the unread badge in the status bar to jump to your Chats, a new "notify on new contact" toggle, and Czech accents in the accent pop-up. (Vybo, #22)
|
||||
Optional trackball "edge scroll" — push the cursor past a screen edge to scroll the list underneath it (off by default, under Settings -> Keyboard). (tun0, #38)
|
||||
TCP now restarts immediately after you turn it off and on again, the OTA action is hidden when there's no update slot to use, and a TCP-toggle alert glitch is fixed. (samuelcoustet, #29)
|
||||
|
||||
# --- Fixes ---
|
||||
Heltec V4: removed the after-crash report prompt — that board has no SD card or reachable storage to save the report to, so it was a dead end. (The T-Deck still offers it.)
|
||||
When both Wi-Fi and GPS are off, the clock now sets itself from the timestamps on messages received over the mesh.
|
||||
@@ -0,0 +1,7 @@
|
||||
# beta_14 — hotfix (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Fix (headline) ---
|
||||
Fixed a bug introduced in beta_13: if you had turned keyboard navigation OFF (Settings -> Keyboard), the physical keyboard stopped typing into text fields entirely on the T-Deck and Tanmatsu. Typing now works again with keyboard navigation off; with it on, nothing changed. (The Heltec V4 was never affected.)
|
||||
|
||||
# --- Also in this release ---
|
||||
Includes everything from beta_13: room-server posts show the author's name, the Direct (0-hop) contact + map filter, beer emoji with a status-bar unread tap and a new-contact toast toggle and Czech accents, optional trackball edge-scroll, the dedicated special-characters picker, the 12-hour clock applied everywhere, and the keyboard navigation + typing improvements.
|
||||
@@ -0,0 +1,18 @@
|
||||
# beta_15 (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- New ---
|
||||
Power-saving now shows as an amber battery icon in the status bar — like iPhone's Low Power Mode — instead of a separate moon icon (T-Deck).
|
||||
New @-mention picker: type @ in the message box to pick a contact from a list and drop their name in (thanks robinshizuyo-dev, #42).
|
||||
|
||||
# --- Fixed ---
|
||||
Closing a settings item, menu or chat no longer snaps the list back to the top — it stays where you left it (thanks ZorkaTheDog, #45).
|
||||
Messages you send from the MeshCore companion app now also show up on the device, in the right conversation (thanks HonzaDandy, #46).
|
||||
Typing is more reliable on the T-Deck and Tanmatsu: tapping a text field — even one that's already highlighted — starts typing right away, and the accent pop-up no longer interrupts typing outside of chats.
|
||||
The text cursor now only appears when a field is actually ready for input, so it's a dependable "you can type now" cue.
|
||||
Smoother map panning: tile rendering no longer trips the watchdog and reboots during heavy scrolling.
|
||||
|
||||
# --- Changed ---
|
||||
Keyboard navigation is now OFF by default. If you prefer driving the UI with the trackball / arrow keys, switch it back on in Settings -> Keyboard (your choice sticks until you change it). The Tanmatsu keeps it on, since it has no touchscreen.
|
||||
|
||||
# --- Also in this release ---
|
||||
Includes everything from beta_14 and the earlier betas.
|
||||
@@ -0,0 +1,16 @@
|
||||
# beta_16 (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- New ---
|
||||
Heltec V4 Expansion Kit support: a dedicated Sensors tab (temperature, humidity, pressure, GPS and battery, with small history charts), a Home-screen environment readout, and an on-device "mesh console" for poking at the radio. It only turns up when a sensor board is actually attached, so a bare V4 looks exactly as it did before. This one was a real grind to land — it came in as a huge pull request built on an old beta, and getting it in cleanly meant re-doing most of it by hand against the current code and carefully fencing it all off so it can't bloat or disturb the T-Deck and Tanmatsu builds. Thanks to Samuel Coustet for the original work (#29).
|
||||
French, Dutch, German, Spanish and Italian keyboards, on-screen and on the T-Deck's physical keys. Choosing one of those as your interface language now auto-selects the matching layout. Also from Samuel Coustet (#29).
|
||||
Wi-Fi firmware updates are back: on a normally-flashed device (not the Launcher) you can update straight from Settings -> About, no cable needed.
|
||||
You can also roll back now — "Install a previous version" lets you reinstall one of the last few releases over Wi-Fi, with a confirmation first since it's a downgrade.
|
||||
|
||||
# --- Fixed ---
|
||||
The status-bar clock no longer overlaps the node name on the V4 (it had been crammed against it for a good while).
|
||||
Closing a dialog no longer lets a scroll carry through to the page behind it (thanks tun0, #50).
|
||||
The contact action sheet no longer overflows the screen in landscape (thanks tun0, #49).
|
||||
The per-channel region field now shows your default region as the hint, instead of a generic placeholder (thanks tun0, #48).
|
||||
|
||||
# --- Also ---
|
||||
Everything from beta_15 is included.
|
||||
@@ -0,0 +1,23 @@
|
||||
# beta_18 (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- New ---
|
||||
A topographic map style: turn on Map -> Options -> "Topographic map" for terrain-relief tiles (OpenStreetMap stays the default). Thanks to Samuel Coustet for the proxy work (#52).
|
||||
New emoji in the picker: soccer ball, american football, moai, and the transgender flag.
|
||||
Contacts can now sort ascending or descending, and there's a new "Nearest first" sort that orders contacts by distance from you (contacts without a shared location are left out).
|
||||
The Discovered list gets the same sorting — by most-recently-heard, name, or distance — from a new sort button in its header.
|
||||
The Chats header is tidied up: the add / mark-all-read / share-contact buttons are grouped together on the left, and sharing your contact now uses a proper QR icon.
|
||||
|
||||
# --- Fixed ---
|
||||
The USB/PC companion connection no longer drops or throws "bad state" errors mid-session. The same fix also clears up repeater admin mode disconnecting and not being able to log in to member rooms.
|
||||
Map tiles along the edges and corners of the view now fill in automatically as you pan, instead of needing a manual "reload tiles".
|
||||
Reclaimed about 41 KB of memory by moving a large internal buffer to PSRAM — this fixes the Heltec V4 running out of RAM and rebooting when it loaded map tiles, and leaves more headroom on every board.
|
||||
The map's "Show link lines" toggle is now remembered across reboots. Thanks to Yazutsu (#61).
|
||||
Settings category names (Bluetooth, Radio & Mesh, Quick replies, ...) now fit on one line instead of wrapping or splitting mid-word.
|
||||
Turning keyboard navigation on or off no longer snaps the settings list back to the top.
|
||||
Heltec V4: the node name in the status bar no longer covers the clock.
|
||||
T-Deck: the keyboard backlight no longer gets stuck on after flashing when it's set to Off.
|
||||
Crash reporting is more reliable (it only prompts after an actual crash, not on every restart), and there are fewer stray reboots while saving settings.
|
||||
Tanmatsu: changing the LoRa radio settings (frequency and so on) now takes effect immediately, without needing a reboot, and the app's settings now drive the radio.
|
||||
|
||||
# --- Also ---
|
||||
Everything from beta_16 (and the Tanmatsu's beta_17) is included.
|
||||
@@ -0,0 +1,14 @@
|
||||
# beta_19 (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- New ---
|
||||
Radio & Mesh settings now show an airtime / duty-cycle readout: roughly how much of the airtime budget your current spreading factor, bandwidth and coding rate use, plus the time-on-air per packet — handy for staying inside regional limits like EU 868.
|
||||
A new "Answer telemetry requests" toggle in Radio & Mesh settings controls whether other nodes can pull your battery and environment telemetry.
|
||||
The Radio & Mesh settings page is now split into clear RADIO / MESH / SIGNAL sections so the options are easier to find.
|
||||
|
||||
# --- Fixed ---
|
||||
Fixed a reboot that could happen when you closed a scrollable popup — the emoji picker, quick replies, the Wi-Fi scan list, the app drawer and similar — while it was still coasting from a flick. Thanks to everyone who sent in crash reports.
|
||||
The accent popup no longer lingers on screen after you send a message on the T-Deck keyboard.
|
||||
Heltec V4: the Expansion Kit environment line and mini chart on the Home screen no longer overlap the status text or run off the bottom of the display.
|
||||
|
||||
# --- Also ---
|
||||
Everything from beta_18 is included.
|
||||
@@ -0,0 +1,33 @@
|
||||
# beta_20 (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- New ---
|
||||
Spectrum analyzer: a new app in the app drawer that sweeps the LoRa band and draws a live spectrum with a waterfall under it, a colour scale on the side and a peak-frequency readout. Handy for spotting interference or finding a quiet part of the band before you settle on a frequency.
|
||||
Custom notification sounds: you can set your own notification sound from a 16-bit WAV file on the SD card, per event, instead of only the built-in chime. There is also a separate switch for the direct-message chime. Contributed by Yazutsu (#66).
|
||||
Saved Wi-Fi networks: the device remembers more than one network now and can auto-join them. Each saved network has its own auto-join switch and can be forgotten on its own.
|
||||
One chat settings screen: the gear on a chat or channel row, a long-press on that row, and the cog inside an open chat now all open the same screen. Everything is in one place: mark as read, mute messages, mute mentions, region and scope, share secret, blocked users, and remove or delete.
|
||||
Per-channel mute: silence the notification sound for a single channel while the rest keep chiming.
|
||||
High-gain receiver for the Heltec V4.3: the V4.3 front-end module has a roughly 17 dB receive amplifier that the firmware never switched on, so every V4.3 shipped with it bypassed. There is now a "High-gain receiver (FEM LNA)" toggle under Radio and Mesh, in the Signal section. Turning it on can noticeably improve weak-signal reception in quiet or remote spots; in a noisy area it can overload the front end, so it is off by default. V4.2 boards do not have this amplifier and do not show the toggle. Thanks to Wireless Rocks for the report and the measurements.
|
||||
Apps button on the Heltec V4 home screen: the V4 home screen now has an Apps button next to Send advert, so the app drawer (Spectrum, RF Monitor, Snake and the rest) is one tap away. The T-Deck and Tanmatsu already had this.
|
||||
Blocked list is easier to reach: open it from Contacts (the overflow menu) and from the chat settings screen, not only from where it used to live.
|
||||
Portuguese (Brazil) is now a UI language, which brings the total to twelve. Community translation.
|
||||
|
||||
# --- Fixed ---
|
||||
Adverts sent from the touch screen are now relayed by region-scoped repeaters. The advert went out without a region tag, so a repeater set to a single region dropped it, while the same advert sent from the phone app got through. The device now tags its flood advert the same way the phone companion does. (Issue #68, reported by EricMW61.)
|
||||
A message could be transmitted on the wrong channel. After you added or removed channels the cached channel index could drift, so a message typed in one channel was sent on another channel's key. It looked correct on your own screen but arrived in the other channel. The send path now matches the target channel by name at the moment of sending, so this can no longer happen.
|
||||
Room server logins could time out when the only route to the server was through a region-scoped repeater. A new opt-in toggle, "Scope direct messages to region" under Radio and Mesh, in the Signal section, tags your direct, login and admin messages with your region so a region-only repeater will relay them. It is off by default, so cross-region messaging is unchanged for anyone who does not need it. (Issue #64, reported by randelreiss.)
|
||||
The Wi-Fi scan could come back empty. If a scan ran while the device was already connected, the automatic reconnect could cancel it mid-sweep and the list returned zero networks. The scan and the reconnect no longer fight over the radio, and the SSID list loads reliably again.
|
||||
|
||||
# --- Under the hood ---
|
||||
Flood scoping was reworked so channel floods and direct floods are handled separately. Channel and group messages stay tagged with your region scope so public channels remain contained, while direct messages, logins and admin traffic stay unscoped by default to keep cross-region messaging working. The new region-scope toggle opts the direct traffic into the region tag (issue #64), and the advert path was moved onto the same scoping the phone companion already used (issue #68).
|
||||
The Spectrum app and the mesh share one radio. While Spectrum is open it takes ownership of the SX1262 so the mesh loop does not re-tune or re-arm receive underneath the sweep, and leaving the app restores the mesh radio settings and resumes normal receive. The sweep also waits out the chip's RSSI settle time after each retune and takes a short peak reading per step, so the trace shows real energy instead of the reset floor.
|
||||
A shared flag now tells the main loop when a Wi-Fi scan is running, so the periodic reconnect does not disconnect and reconnect during a sweep, and auto-reconnect is paused for the length of the scan.
|
||||
The Heltec V4.3 front-end module (KCT8103L) is now driven for receive with its low-noise amplifier behind the new setting. This is the large amplifier and it is separate from the SX1262's own internal RX-boost bit, which is only a fraction of a dB.
|
||||
The new settings (per-channel mute, custom sounds, saved Wi-Fi networks, the high-gain and region-scope toggles, and the trackball-navigation default) are stored with versioned migrations, so updating from an earlier beta keeps your existing settings.
|
||||
|
||||
# --- Changed ---
|
||||
Trackball and keypad navigation is off by default now and marked experimental, because it still has rough edges. With it off you get the on-screen cursor as before. When it is on it can now reach the top-bar buttons and the back and cog buttons on settings and tool pages.
|
||||
First-time setup no longer has a Wi-Fi step. Instead it explains that the device can run Wi-Fi and Bluetooth at the same time, as a standalone radio and a phone companion together, and that you set those up later in Settings.
|
||||
RF Monitor and Spectrum now use the title bar with a back button, the same as the settings pages.
|
||||
|
||||
# --- Also ---
|
||||
Everything from beta_19 is included.
|
||||
@@ -0,0 +1,16 @@
|
||||
# beta_21 (one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- New ---
|
||||
Stable and test update channels. beta_21 is the first stable build. On devices that update over Wi-Fi there is now a "Get test builds (beta)" switch on the About screen: leave it off to stay on stable releases, or turn it on to receive test builds earlier — it switches both the "update available" check and the firmware you install to the test channel. You can go back to stable at any time.
|
||||
MQTT bridge (highly experimental, off by default). An opt-in bridge that forwards the messages your node receives to an MQTT broker of your choice, so you can log your mesh or wire it into other tools. It is gated behind a privacy consent you have to tick, direct messages are excluded unless you deliberately enable them, and the payload can be sealed with an encryption key so only you can read it. Use a broker you control, never a public one. This is experimental and may change. Contributed by Samuel Coustet (@samuelcoustet, PR #71), reworked for privacy.
|
||||
Portugal (Narrow) region preset, so Portuguese users can pick the local defaults in one tap. (Issue #74, requested by Salgueiron.)
|
||||
Saved-contacts counter. The Contacts overflow menu now shows how many contacts you have saved out of the maximum it can hold (for example 42 / 2000). (Issue #72, asked by Buko84.)
|
||||
|
||||
# --- Fixed ---
|
||||
The signal probe never travels past the first node now. The "auto-discover" signal pulse, and the probe behind the Signal popup, are sent strictly zero-hop, so a repeater can no longer re-flood them across the mesh. The probe only measures the link to whatever hears it first, which is what it was always meant to do.
|
||||
Your most relevant contacts now show when you have a lot of them. The on-screen contact list is capped for performance, but it was trimming the list before sorting it, so some contacts could drop out of view. It now sorts first, shows the closest matches, and adds a "+N more — search to narrow the list" line at the bottom. (Issue #73, reported by 1nerdherder.)
|
||||
The blocked-users list opened from Contacts now shows the proper two-line title bar with a back button, instead of an unlabelled screen.
|
||||
The Contacts overflow popup fits the screen again after the saved-contacts counter was added.
|
||||
|
||||
# --- Also ---
|
||||
Everything from beta_20 is included.
|
||||
@@ -0,0 +1,8 @@
|
||||
# beta_22 (TEST build — one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Fixed ---
|
||||
The signal probe reads a real signal again. In beta_21 the probe was changed to a zero-hop advert so it could never be re-flooded across the mesh — but nothing answers a zero-hop advert, so the signal reading stopped updating. It now sends a directed trace "ping" to your nearest repeater instead: the repeater replies directly (it still never floods the mesh) and the device reads the link signal from that reply. So you get both — no mesh flooding, and a live signal again. Thanks to Tarmo for spotting this and suggesting the fix.
|
||||
Contacts discovered while your phone is connected now show on the device too. If the T-Deck was connected to the MeshCore phone app over Bluetooth and heard a new node, the node appeared on the phone but not on the device's own screen, and the Contacts list did not refresh. The device now updates its own Contacts list and notification whether or not a phone app is connected. (Issue #73, reported by 1nerdherder.)
|
||||
|
||||
# --- Also ---
|
||||
This is a test-channel build (opt in with "Get test builds" on the About screen). Everything from beta_21, the first stable, is included.
|
||||
@@ -0,0 +1,11 @@
|
||||
# beta_23 (TEST build — one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Fixed ---
|
||||
Bluetooth companion reliability, especially messages and repeater admin. When a phone or Mac was connected over Bluetooth, the device was sending it a log entry for every LoRa packet it heard — on a busy mesh that swamped the Bluetooth link and starved the frames that actually matter. The device no longer sends that packet log over Bluetooth (USB and Wi-Fi companions still get it), so incoming messages reach the app and repeater / room-server admin (Manage Regions, saving parameters) stops timing out and dropping the connection. (Issues #46 and #54.)
|
||||
|
||||
# --- Also in this test line (carried from beta_22) ---
|
||||
The signal probe reads a real signal again: it sends a directed trace "ping" to your nearest repeater, which replies, instead of a zero-hop advert that nothing answers. Thanks to Tarmo for spotting it.
|
||||
Contacts discovered while your phone is connected now also show on the device's own screen, not just in the phone app. (Issue #73.)
|
||||
|
||||
# --- Also ---
|
||||
This is a test-channel build (opt in with "Get test builds" on the About screen). Everything from the stable beta_21 is included.
|
||||
@@ -0,0 +1,7 @@
|
||||
# beta_24 (TEST build — one user-facing note per non-blank, non-# line; # lines are section comments)
|
||||
|
||||
# --- Fixed ---
|
||||
The signal probe now reads a real signal to your nearest repeater. It sends the standard mesh node-discovery request that repeaters actually answer, instead of the trace packet from the last test build that they ignored, so the signal reading updates again. This is the same request the Ultra and KiekR apps use. Thanks to Tarmo for decoding the exact packet.
|
||||
|
||||
# --- Also ---
|
||||
This is a test-channel build (opt in with "Get test builds" on the About screen). Everything from the stable beta_21 and the earlier test builds is included.
|
||||
@@ -0,0 +1,21 @@
|
||||
# beta_25 (STABLE — rolls up the beta_22..25 test builds on top of the previous stable, beta_21.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.)
|
||||
|
||||
# --- Fixed ---
|
||||
The signal probe reads a live signal to your nearest repeater again. It sends the standard mesh node-discovery request that repeaters actually answer (the same one the Ultra and KiekR apps use), so the signal reading updates. Thanks to Tarmo for decoding the exact packet.
|
||||
Bluetooth companion reliability, especially messages and repeater / room-server admin. The device no longer floods the Bluetooth link with a per-packet log (USB and Wi-Fi companions still get it), so incoming messages reach the app and admin actions like Manage Regions stop timing out. (Issues #46 and #54.)
|
||||
Contacts show up right away. A node you discover, even while you're on another screen or one the phone app adds over Bluetooth, now appears in your contact list immediately instead of only after you change the sort. Thanks to 1nerdherder for the report. (Issue #73.)
|
||||
Manual telemetry and status requests now work on the first tap instead of needing a second. Thanks to Dan Vybiral (@Vybo) for the fix. (Issue #81.)
|
||||
On the emoji picker and quick-chat replies, pressing Esc or the red X now closes just the popup instead of the whole chat screen.
|
||||
Lock screen: you can now unlock the device after the screen wakes from an incoming message.
|
||||
|
||||
# --- New ---
|
||||
New "Advertise on boot" option in the Advertise app (off by default): turn it on to send one advert when the device powers on, so nearby nodes refresh your details after a reflash. (Issue #76.)
|
||||
|
||||
# --- Tanmatsu ---
|
||||
Tanmatsu: Wi-Fi map tiles now download and display, cached to the microSD card.
|
||||
Tanmatsu: contacts and message history now survive a reboot (moved to the microSD card, so keep a card inserted).
|
||||
Tanmatsu: fixed the crash that stopped the app from booting; popups now fit the screen and scroll, and keyboard navigation stays inside an open popup.
|
||||
|
||||
# --- Also ---
|
||||
This is the new stable build, promoted from the beta_25 test channel. It includes everything from the beta_22, beta_23, beta_24 and beta_25 test builds.
|
||||
@@ -0,0 +1,8 @@
|
||||
# beta_26 (TEST) — storage-reliability fix on top of the current stable (beta_25).
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
Fixed a reboot loop that could hit while the device saved its contact list. Internal-flash housekeeping during a save could freeze the device long enough to trip the watchdog and restart it; that save no longer trips it. Thanks to the crash report that pinned it down.
|
||||
|
||||
# --- Improved ---
|
||||
On devices with a microSD card, contacts and channels are now kept on the card (like chat history and map tiles already are). That frees the small internal flash and puts the frequently-rewritten files on the more reliable card. Your existing contacts move to the card automatically the first time; your identity and settings stay in internal flash, so the node is unchanged.
|
||||
@@ -0,0 +1,15 @@
|
||||
# beta_27 (STABLE — rolls up the beta_26 + beta_27 test builds on top of the previous stable, beta_25.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.)
|
||||
|
||||
# --- Fixed ---
|
||||
Fixed a reboot loop that could hit while the device saved its contact list. Internal-flash housekeeping during a save could freeze the device long enough to trip the watchdog and restart it; that save no longer trips it. Thanks to the crash report that pinned it down.
|
||||
Devices installed under the Launcher no longer show the on-device "Install update" button. That update path only works on a standalone flash; under the Launcher it failed with "won't fit: Bad Size Given". The "update available" note stays, but you update through the Launcher instead. Thanks to Keith (KM1USR) for the report.
|
||||
|
||||
# --- New ---
|
||||
Keyboard backlight brightness control on the T-Deck: a live slider plus Off / 25 / 50 / 75 / Max presets in Settings > Keyboard. It dims smoothly (fine-grained at the low end), applies to the on and auto modes and the new-message flash, and survives a reboot. Thanks to Yazutsu (PR #85).
|
||||
|
||||
# --- Improved ---
|
||||
On devices with a microSD card, contacts and channels are now kept on the card (like chat history and map tiles already are). Your existing contacts move over automatically on first boot; your identity and settings stay in internal flash, so the node is unchanged. Keep the card inserted afterwards - the contact list lives on it, and an older firmware won't look there if you ever downgrade.
|
||||
|
||||
# --- Also ---
|
||||
This is the new stable build, promoted from the beta_27 test channel. It includes everything from the beta_26 and beta_27 test builds.
|
||||
@@ -0,0 +1,13 @@
|
||||
# beta_28 (TEST) — room-server session fix + the wyvern.red feedback batch, on top of stable beta_27.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
Room servers no longer go one-way "frozen". The device now keeps its room login session alive in the background and automatically logs back in when the session drops, so posts keep being delivered and new room messages keep arriving. There is also a new "Log in again" option in the room chat's menu, and a clear warning when the room only granted you read-only access (posts would previously just vanish). Thanks to EricMW61 for the report (issue #89).
|
||||
The screen now truly switches off in standby. Before, only the backlight went dark while the panel kept displaying the last image - the actual cause of the LCD ghosting / burn-in some of you noticed. The panel itself now sleeps, and on wake your screen is back instantly. Also: a lit lock screen now always dims on every board, a screen woken by a message re-darkens after 10 seconds, and the lock-screen clock drifts a few pixels to avoid ghosting. Thanks to wyvern.red for pinning this down.
|
||||
The on-device "Install update" now also hides on mixed or older partition layouts where an update could crash the device halfway through (found via a user crash report - thanks!).
|
||||
|
||||
# --- New ---
|
||||
Compact messages (IRC style): a new toggle in chat settings shows one dense line per message instead of bubbles, fitting two to three times more history on screen. Off by default. (wyvern.red's suggestion.)
|
||||
Clearer delivery ticks: an up-arrow means sent but not yet confirmed, a double check means the other side confirmed it, and a failed message now says "tap to resend" - one tap resends it. (wyvern.red)
|
||||
Keyboard navigation: the scroll keys (F/C) now zoom the map, and backspace in an empty message box exits typing mode so the navigation keys work again. (wyvern.red)
|
||||
"Reset path" is now also in the chat's own menu next to Blocked users, so you can force a fresh route without going through Contacts.
|
||||
@@ -0,0 +1,17 @@
|
||||
# beta_29 (TEST) — clock hardening for room servers, BLE repeats-heard fix, deeper SD chat history, one-screen popups. On top of beta_28.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
Room logins can no longer be silently locked out by the device clock. Room servers quietly reject anything older than the newest timestamp they have seen from you, so a clock that stepped backwards (a reboot without GPS or phone around, or a sync correcting a fast clock) black-holed every Join with zero feedback. The device now remembers the newest timestamp it has used across reboots so it can never fall behind itself, a Join that gets no reply shows a clear error after 20 seconds (including what the device thinks the time is) instead of spinning forever, and the device warns you when its clock and the room server's clock disagree. Thanks to EricMW61 for the follow-up testing on issue #89.
|
||||
"Repeats heard" works again over Bluetooth. The companion app counts repeats from the per-packet RX log, which beta_23 stopped sending over BLE to fix congestion. The firmware now recognises packets that are repeats of your own messages and lets exactly those through to BLE, so the counter works and the congestion fix stays. Thanks to daryldakine for the clean bisect (issue #94).
|
||||
Messages sent from the phone app through the device now also appear on the device itself, as your own outgoing message in that channel. This worked for direct messages but never for channels.
|
||||
Deleting a chat or channel now sticks. Deleted messages could survive in storage and resurrect the chat later, and removing a channel could miss the underlying channel entry so the thread came right back.
|
||||
Settings pages no longer jump down when opened on keyboard-navigation devices, and the red update dot can no longer steal the selection from the About button.
|
||||
The update check no longer shows "couldn't reach the update server" for six hours after one failed attempt. It now waits for Wi-Fi to actually have an address and retries within a minute.
|
||||
The Tanmatsu About page now shows the real firmware version instead of a stale hardcoded one.
|
||||
|
||||
# --- New ---
|
||||
Much deeper chat history on devices with an SD card: up to 5000 messages instead of 500, with date separators (Today, Yesterday, day names) between messages from different days. Note for testers: if you later downgrade below beta_29, the older build will not read the larger history file and will start with an empty history.
|
||||
Chat and channel rows in the messages tab now use the same compact style as the Contacts tab, fitting more conversations on screen.
|
||||
The chat or channel settings sheet (long-press or the gear) now fits on one screen, laid out like the contact menu, instead of scrolling.
|
||||
Compact chat mode is easier to read: messages alternate between two background tones.
|
||||
@@ -0,0 +1,11 @@
|
||||
# beta_30 (TEST) — the big-mesh sluggishness fix + a much faster Spectrum analyzer. On top of beta_29.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
The periodic multi-second freezes on busy meshes are gone. Saving the contact list to storage took up to 3 seconds at ~570 contacts and ran on the UI thread every couple of minutes, which is exactly the trackball and touch hesitation people felt since beta_25. The save now writes the same data in a few large chunks instead of thousands of tiny ones, cutting it to a fraction. Thanks to guinnessbear for the report and Yazutsu for the precise measurements that pinned this down (issue #82).
|
||||
The Contacts tab no longer rebuilds itself every 60 seconds just to refresh the "heard 2h ago" labels - they update in place now. That rebuild cost over a second on large contact lists and could yank the list while you were scrolling.
|
||||
A burst of adverts can no longer queue several back-to-back Contacts rebuilds; they are now coalesced.
|
||||
|
||||
# --- Improved ---
|
||||
The Spectrum analyzer feels live now: the waterfall gets a new line about four times per second instead of once per sweep, the trace advances across the band as it sweeps (like a bench analyzer), and a full sweep is about 20 percent faster.
|
||||
The Spectrum trace also scales itself to the signal now: the chart tracks the live minimum and maximum, jumping out instantly for a strong signal (no more clipping flat at the top) and easing back once it is gone.
|
||||
@@ -0,0 +1,9 @@
|
||||
# beta_31 (TEST) — the new chats tab: avatars, message previews, custom channel icons. On top of beta_30.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
The chats tab has a fresh look: each conversation now shows a round avatar in that chat's signature colour (the same colour its bubbles use) with the first two letters of the name, the last message previewed under the name, the time top right and the unread count below it. Compact chat mode keeps the dense single-line rows instead, so both styles are available.
|
||||
Channels can have a custom icon: open the channel's settings (long-press or the gear) and tap "Chat icon" to pick any emoji as its avatar. Long-press the same entry to go back to the automatic letters. The choice is remembered per channel.
|
||||
|
||||
# --- Fixed ---
|
||||
Long chat names no longer wrap onto the message preview underneath.
|
||||
@@ -0,0 +1,9 @@
|
||||
# beta_32 (TEST) — the Wi-Fi freeze fix: a stale phone/web connection can no longer stall the device. On top of beta_31.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
The 1-2 second freezes every ~10 seconds some users saw with Wi-Fi on are gone. When a phone app or web client was connected over Wi-Fi and that connection went stale (phone asleep, walked out of range), every received mesh packet made the device wait on the dead connection before the UI could continue. The device now only writes to a connection when it can accept data immediately, and a connection that stays unresponsive for 10 seconds straight is dropped so the client can reconnect cleanly.
|
||||
Saving chat history could briefly hitch the UI on busy meshes: the history file now writes in a few large chunks instead of thousands of tiny ones (the same treatment the contact list got in beta_30), and background saves are spaced further apart while messages stream in.
|
||||
|
||||
# --- New ---
|
||||
The About page now shows recent loop stalls (anything over 0.2 seconds, newest first), so freeze reports can be confirmed or ruled out at a glance.
|
||||
@@ -0,0 +1,7 @@
|
||||
# beta_33 (TEST) — the MQTT freeze fix + the About page no longer stalls itself. On top of beta_32.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
The freezes with MQTT enabled are gone. Connecting to the broker (DNS lookup, TCP connect, login) could block the device for 2-10 seconds and retried every 15 seconds while the broker was unreachable, all on the UI thread. Connection attempts now run fully in the background, and writes to a broker that stops responding are skipped instead of waited on (the same treatment beta_32 gave phone/web connections). Tip: if your bridge keeps reconnecting, check the broker address and credentials - a healthy connection does not retry.
|
||||
The About page caused a ~0.4 second hitch every second while it was open: computing the firmware image size checksums the whole ~3 MB app in flash and ran twice per refresh. It is now computed once per boot, so About (and its stall list) no longer disturbs the very measurements it shows.
|
||||
Favoriting a contact when the favorites list is full (16) now says "Favorites full (max 16)" instead of the incorrect "Removed from favorites".
|
||||
@@ -0,0 +1,11 @@
|
||||
# beta_34 (TEST) - buffered receive: the missed-messages fix, ON by default on this test build. On top of beta_33.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
This build changes how the device collects packets from the LoRa radio, and the new way is ON by default so the test channel can prove it. Background: the radio chip holds only one received packet at a time, and until now the firmware only collected it when the main loop got around to it, the same loop that draws the screen. If the interface kept the loop busy while two packets arrived back to back, the first one was silently overwritten. That is the "my device missed a message another device right next to it heard" problem some of you reported, and why it affected this firmware but not stock MeshCore, which has no screen to draw. A small dedicated background task now collects every packet within about a millisecond of it arriving, no matter what the interface is doing.
|
||||
Because the receive path is the heart of the device, this ships with a safety hatch: if you see anything odd with receiving, sending, or the Spectrum analyzer, turn off "Buffered receive (experimental)" in Settings > Radio & Mesh (applies instantly, no reboot) and you are back on the old path. Please report it either way, that is exactly what the test channel is for.
|
||||
The About page now shows LoRa receive counters (heard / read / late-lost). "late-lost" counts packets the radio heard but the firmware failed to collect in time. With buffered receive on it should stay at zero. If you turn the toggle off on a busy mesh you may watch it creep up, which is the old bug happening live.
|
||||
|
||||
# --- Fixed ---
|
||||
Messages sent by wadamesh no longer carry a few stray bytes at the end that stock MeshCore devices and some companion apps displayed as garbage characters after the text (issue #13).
|
||||
A GPS module reporting a bogus date can no longer drag the device clock back to the year 1902 (core clock guard).
|
||||
@@ -0,0 +1,11 @@
|
||||
# beta_35 (TEST) - V4 breathing room: freezes fixed, 30 KB of internal RAM back, crash-safe settings. On top of beta_34.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
Heltec V4: multi-second freezes right after receiving messages are gone, including the screen refusing to wake from dark mode. Saving chat history to the internal flash could stall the device for up to 6 seconds (the V4 has no SD card, and internal flash pauses for housekeeping); that save now runs on a background task, so the screen and touch stay responsive no matter what storage is doing.
|
||||
Turning Bluetooth on while Wi-Fi is running no longer reboots the device when memory is tight: the switch now checks free memory first and tells you "Not enough free memory for Bluetooth. Turn Wi-Fi off first." instead of crashing.
|
||||
The same protection applies the other way around: turning Wi-Fi on with Bluetooth active used to claim "Wi-Fi on" while the radio silently never started. It now either really starts or tells you why it cannot.
|
||||
Settings could vanish for a single boot after an unlucky reboot (the device came up with a default name once, then recovered). Settings writes are now crash-safe: a reboot or power cut at any moment leaves at least one intact copy on storage, the loader self-heals from it, and a failed save now says "Couldn't save the name to storage" instead of pretending it worked.
|
||||
|
||||
# --- Improved ---
|
||||
About 30 KB of the ESP32's scarce internal RAM has been freed by moving buffers to the external PSRAM and dropping unused USB components from the build. This is the headroom that makes Wi-Fi and Bluetooth run together comfortably again, especially on the Heltec V4.
|
||||
@@ -0,0 +1,13 @@
|
||||
# beta_36 (TEST) - Launcher SD storage really works now, Wi-Fi settings redesign, symbol passwords, lock-screen unread count. On top of beta_35.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
The "Store data on SD" toggle is now actually honored after a reboot (fix contributed by scratchdiver, PR #123). On Launcher installs the setting silently reverted to internal storage every boot, which also meant those devices kept the slow internal-flash writes. If you run wadamesh under the Launcher and your mesh is busy, this one is for you: flip the toggle once and contacts and settings really move to the SD card.
|
||||
Turning "Store data on SD" off works reliably now too, including on Launcher installs (follow-up to PR #123).
|
||||
Passwords with special characters can now be entered everywhere: long-press any settings field, including the Wi-Fi password, for the Cut / Copy / Paste / All / Sym menu. Sym opens a picker with the characters the on-screen keyboard does not have (issue #122). Double-tap selects a word.
|
||||
|
||||
# --- New ---
|
||||
The Wi-Fi network screens (join a network, hidden network, saved-network details) are full-screen pages now instead of small popups: same style as the other settings pages, title in the top bar, tap the bar to go back, and everything scrolls properly with the keyboard open.
|
||||
The lock screen shows an envelope with the number of unread messages under the clock, so you can check for news without unlocking (idea from issue #93). It disappears when there is nothing unread.
|
||||
For Launcher installs: a "Save update bin to SD" button on the About page downloads the latest firmware of your selected channel into a BINS folder on the SD card, named with the version and channel, ready to flash from the Launcher. The test-builds toggle is now visible on Launcher installs as well.
|
||||
New region preset "Australia (Mid)": 915.075 MHz, 125 kHz, SF9, CR5 (requested in issue #121).
|
||||
@@ -0,0 +1,15 @@
|
||||
# beta_37 (TEST) - Tanmatsu ALT accents + language switching, GPS quick reply, T-Deck touch self-heal, SD history reliability. On top of beta_36.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
Tanmatsu: hold ALT and press a letter to open the accent picker (a with ALT gives an accent popup, just like long-press on the touch boards). Keep ALT held and press the letter again to cycle through the options; release ALT to insert the highlighted one. Requested by jochentj in issue #129.
|
||||
Tanmatsu: double-tap the space bar to switch between your enabled keyboard languages, same as on the T-Deck.
|
||||
Accented letters on the Tanmatsu now render at full size at the Large and Huge UI scales instead of noticeably smaller (issue #129). Made possible by moving all boards to compressed UI fonts, which also frees a chunk of firmware space.
|
||||
Your GPS position is now the first row in the quick-reply picker, so you can send your current coordinates with one tap (contributed by Yazutsu, PR #125).
|
||||
While in a chat or channel, the top bar shows the active keyboard language next to the clock (only when more than one layout is enabled), so you can see what double-tap-space switched to.
|
||||
|
||||
# --- Fixed ---
|
||||
The keyboard-backlight "Off" preset now actually switches the backlight off and stays off, instead of leaving the previous mode running (contributed by Yazutsu, PR #88).
|
||||
Chat history saves to the SD card are more robust: files are written atomically and a fallback save path covers the background worker not being armed yet, so a badly timed reboot can no longer truncate your message history (contributed by scratchdiver, PR #135).
|
||||
T-Deck: if the touch controller glitches into a stuck "phantom finger" state (everything looked frozen, touch dead, trackball clicks eaten, and the screen never timed out), the driver now detects it and recovers within seconds. Previously only a full power cycle would clear it.
|
||||
T-Deck: touch initialisation at boot now retries for a few seconds instead of giving up after a single attempt. A slow-to-wake touch controller could previously cost you touch AND keyboard for the whole session.
|
||||
@@ -0,0 +1,17 @@
|
||||
# beta_38 (TEST) - Full-history chat scrolling, message delete, two new boards, storage integrity fixes. On top of beta_37.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
Chat scrolling is fully virtualized (contributed by scratchdiver, PR #130): a chat now scrolls through its ENTIRE stored history, up to 5000 messages on SD-card devices, smoothly in both bubble and compact mode, instead of only the most recent rows. Several LVGL crash fixes came along with it.
|
||||
Long-press any message for a new Delete option that removes just that message. The message menu now lays its buttons out two per row so it always fits on one screen.
|
||||
The chat and channel settings sheet gains "Delete history": wipes the whole conversation after a confirmation, while the channel membership or contact stays.
|
||||
The in-chat jump buttons are now slim arrows tucked against the right edge of the screen: fully visible while you scroll, fading to half opacity a second after you stop.
|
||||
ThinkNode M9 hardware support is complete (contributed by ded, PR #138): GPS, microSD, buzzer, backlight control, lock screen, long-press context menus, and a d-pad-driven setup wizard. His fixes for focus scrolling in nested pages and the lock-screen paint order benefit every keyboard-nav board.
|
||||
New board in the source tree: RAK WisMesh Tap V2 (contributed by Ethac.chen, PR #136), with a 30+ fps display driver. Prebuilt images follow once the web-flasher integration lands.
|
||||
Tanmatsu: the app now builds size-optimized, roughly 300 KB smaller, leaving real headroom under the launcher's app-size limit for future features.
|
||||
|
||||
# --- Fixed ---
|
||||
Messages no longer vanish when you reboot the device from the phone app (reported by Leon P). The history writer batches saves up to 30 seconds apart, and the app's reboot command skipped the final save that every on-device reboot path does - so the newest messages, read or unread, were lost. The reboot command now saves first.
|
||||
T-Deck: turning on "Store data on SD" now migrates your existing profile, settings and identity to the card FIRST. Upgrading to beta_36/37 with the toggle already on could adopt an empty card and boot as a brand-new node while your real profile sat orphaned on internal flash.
|
||||
T-Deck: for anyone already hit by that: Set -> General -> "Copy internal data to SD" restores the profile from internal flash onto the card (confirmation, then automatic reboot). Until you run it, toggling "Store data on SD" OFF and rebooting also brings the old profile back.
|
||||
Tanmatsu: profile name, settings and the node identity now persist reliably across reboots. They lived on the internal flash partition whose metadata layer is unreliable on this board; they now live on the SD card with everything else, and existing data is rescued automatically on first boot.
|
||||
@@ -0,0 +1,11 @@
|
||||
# beta_39 (TEST) - Translations, deletes that stick and are fast, emoji fixes. On top of beta_38.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
Big translation pass: hundreds of previously-English strings across menus, settings, dialogs and the setup flow are now translated into all 11 non-English languages (Dutch, German, French, Spanish, Italian, Russian, Ukrainian, Bulgarian, Serbian, Greek, Brazilian Portuguese). Menu rows that carry an icon now translate too, instead of silently falling back to English. Switch language under Settings.
|
||||
ThinkNode M9 and RAK WisMesh Tap V2 can now be installed straight from the web flasher.
|
||||
|
||||
# --- Fixed ---
|
||||
Deleting a message, or clearing a whole chat, now sticks: it survives a reboot or a hardware reset instead of coming back (reported by Leon P and MrWeavis). The delete used to sit in a save that was batched up to 30 seconds later, so a reset in that window undid it.
|
||||
Deleting is also much faster now. The chat history is written compactly - only the messages you actually have, instead of the full 5000-slot ring every time - so a delete, a history wipe, or a reboot no longer pauses the screen while it saves. Normal message saving is lighter too.
|
||||
Emoji picker: the food and object emoji that showed as empty boxes (umbrella, coffee, pizza, cake, rocket, camera, phone, laptop, calendar, antenna, e-mail, house, car, watch) now render properly - they were listed but never baked into the font.
|
||||
@@ -0,0 +1,32 @@
|
||||
# beta_40 (STABLE) - the first stable since beta_35. Rolls up everything from beta_36 through beta_40: reliable SD storage, message delete + full-history scrolling, a big translation pass, two new boards, and a pile of storage/perf/stability fixes.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
Big translation pass: hundreds of previously-English strings across menus, settings, dialogs and the setup flow are now translated into all 11 non-English languages (Dutch, German, French, Spanish, Italian, Russian, Ukrainian, Bulgarian, Serbian, Greek, Brazilian Portuguese). Menu rows that carry an icon translate too. Switch language under Settings.
|
||||
Chat scrolling is fully virtualized (contributed by scratchdiver, PR #130): a chat now scrolls through its ENTIRE stored history, up to 5000 messages on SD-card devices, smoothly in both bubble and compact mode, instead of only the most recent rows.
|
||||
Long-press any message for a Delete option that removes just that message; the message menu lays its buttons two per row so it fits on one screen.
|
||||
The chat and channel settings sheet gains "Delete history": wipes the whole conversation after a confirmation, while the channel membership or contact stays.
|
||||
The in-chat jump buttons are slim arrows tucked against the right edge: fully visible while you scroll, fading to half opacity a second after you stop.
|
||||
ThinkNode M9 hardware support is complete (contributed by ded, PR #138): GPS, microSD, buzzer, backlight control, lock screen, long-press context menus, and a d-pad-driven setup wizard. It can be installed straight from the web flasher.
|
||||
New board: RAK WisMesh Tap V2 (contributed by Ethac.chen, PR #136), with a 30+ fps display driver, also installable from the web flasher.
|
||||
The Wi-Fi network screens (join a network, hidden network, saved-network details) are full-screen pages now instead of small popups, matching the other settings pages and scrolling properly with the keyboard open.
|
||||
The lock screen shows an envelope with the number of unread messages under the clock, so you can check for news without unlocking (idea from issue #93).
|
||||
Passwords with special characters can be entered everywhere: long-press any settings field, including the Wi-Fi password, for a Cut / Copy / Paste / All / Sym menu. Sym opens a picker with the characters the on-screen keyboard lacks (issue #122). Double-tap selects a word.
|
||||
For Launcher installs: a "Save update bin to SD" button on the About page downloads the latest firmware of your selected channel into a BINS folder on the SD card, ready to flash from the Launcher. The test-builds toggle is visible on Launcher installs too.
|
||||
New region preset "Australia (Mid)": 915.075 MHz, 125 kHz, SF9, CR5 (requested in issue #121).
|
||||
Your GPS position is now the first row in the quick-reply picker, so you can send your current coordinates with one tap (contributed by Yazutsu, PR #125).
|
||||
Tanmatsu: hold ALT and press a letter to open the accent picker; keep ALT held and press again to cycle, release to insert (requested by jochentj, issue #129). Double-tap the space bar to switch between your enabled keyboard languages, same as the T-Deck.
|
||||
While in a chat, the top bar shows the active keyboard language next to the clock when more than one layout is enabled.
|
||||
|
||||
# --- Fixed ---
|
||||
The "Store data on SD" toggle is now actually honored after a reboot (fix contributed by scratchdiver, PR #123). On Launcher installs it silently reverted to internal storage every boot, which also kept the slow internal-flash writes. Turning it off works reliably now too.
|
||||
Turning on "Store data on SD" migrates your existing profile, settings and identity to the card FIRST, so upgrading no longer risks adopting an empty card and booting as a brand-new node. If you were already hit by that, Settings -> General -> "Copy internal data to SD" restores the profile from internal flash.
|
||||
Card-less devices (Heltec V4, and any board without an SD card) no longer freeze for several seconds at a time. A busy mesh re-writes the whole contacts file to internal flash on every advert refresh, and that flash garbage-collection stalled the whole device; contact saves are now coalesced so only real changes (a new or removed node) are written promptly.
|
||||
Deleting a message, or clearing a whole chat, now sticks: it survives a reboot or a hardware reset instead of coming back (reported by Leon P and MrWeavis). Deleting is also much faster - chat history is written compactly, only the messages you actually have, so a delete, a history wipe, or a reboot no longer pauses the screen while it saves.
|
||||
Messages no longer vanish when you reboot the device from the phone app (reported by Leon P): the app's reboot command now saves chat history first, like every on-device reboot path already did.
|
||||
Chat history saves to the SD card are more robust: files are written atomically with a fallback save path, so a badly timed reboot can no longer truncate your message history (contributed by scratchdiver, PR #135).
|
||||
Longer translated labels (French, German, etc.) that used to clip or wrap now shrink to fit: the message action menu, confirmation dialogs, and the app-drawer tiles all keep their text on one line. The signal card's "tap for details" hint no longer overlaps in other languages.
|
||||
Emoji picker: the food and object emoji that showed as empty boxes (umbrella, coffee, pizza, cake, rocket, camera, phone, laptop, calendar, antenna, e-mail, house, car, watch) now render properly.
|
||||
Tanmatsu: profile name, settings and the node identity persist reliably across reboots (moved off the unreliable internal-flash partition onto the SD card, with existing data rescued on first boot). Accented letters render at full size at the Large and Huge UI scales. The About page shows the correct firmware version now.
|
||||
T-Deck: if the touch controller glitches into a stuck "phantom finger" state (touch dead, trackball clicks eaten, screen never timing out), the driver detects it and recovers within seconds instead of needing a full power cycle. Touch initialisation at boot also retries for a few seconds instead of giving up after one attempt.
|
||||
The keyboard-backlight "Off" preset now actually switches the backlight off and stays off (contributed by Yazutsu, PR #88).
|
||||
@@ -0,0 +1,10 @@
|
||||
# beta_41 (TEST) - experimental web control: view and drive the wadamesh UI from a phone or desktop browser over Wi-Fi. Two new apps, VNC and REMOTE.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New ---
|
||||
New VNC app (in the app drawer, between Advertise and Signal): it mirrors the live device screen into a web browser over Wi-Fi and lets you drive it. Taps and typing in the browser control the device, and anything that changes on the device shows up in the browser. Turn it on, then open http://<device-ip>:8765/ from any browser on the same network. The app page itself is scrollable.
|
||||
New REMOTE app: a headless mode that renders the whole UI at a larger, browser-friendly resolution and serves it over Wi-Fi, while the device's own screen drops to a placeholder showing the address to open. Meant for driving a device as a web client, and as groundwork for screenless hardware. Turning it on or off reboots the device. On the web page a Rotate button flips between landscape (desktop) and portrait (phone); on the device, double-tap the space bar to leave remote mode.
|
||||
Both features are served by the device itself over plain Wi-Fi - no phone app, no account, no cloud, just a browser on the same network.
|
||||
|
||||
# --- Note ---
|
||||
This is an experimental first cut, and an honest heads-up from Kaj: for me the remote UI still feels too slow, and I will most likely move toward a dedicated, lighter remote client instead of streaming the entire live UI. It is in this test build mainly because community feedback on it would be genuinely useful - if you try it, let us know how it runs for you and what you would actually want from a web or remote client.
|
||||
@@ -0,0 +1,9 @@
|
||||
# beta_42 (TEST) - fixes for the beta_41 REMOTE mode: it works on the Heltec V4 now, and keyboard-less boards can leave it.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
REMOTE mode now works on the Heltec V4 TFT (and other 2 MB-PSRAM boards) instead of showing a black screen in the browser. Its off-screen renderer needed more free memory than those boards had, so nothing was streamed; the buffers are sized to fit now, and the render is a bit smaller and faster as a result. VNC was never affected and already worked.
|
||||
Keyboard-less touch boards (Heltec V4, RAK WisMesh Tap) can leave REMOTE mode again: touch and hold the device screen for about 3 seconds. Those boards have no SPACE key, which used to be the only way out on the device.
|
||||
|
||||
# --- New ---
|
||||
The web page has an "Exit remote" button (with a confirmation) that leaves REMOTE mode from any browser, on any board.
|
||||
@@ -0,0 +1,24 @@
|
||||
# beta_43 (TEST) - the browser control panel grows into a full remote app (Chats, Contacts, Discovered, Settings), plus a Heltec V4 map-tile fix.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New: web control panel ---
|
||||
The browser control panel is now a full remote app - you can run the device from your phone or laptop without ever picking up the T-Deck. Proper Chats, Contacts, Terminal and Settings tabs.
|
||||
Contacts tab: search, sort and filter your contacts, see each one's distance, coordinates and last-heard age, and tap a contact for the same actions you get on the device (favourite, block, reset path, remove).
|
||||
Discovered nodes show up in the browser too, each with a one-tap "Add" to save it as a contact - so you can build your whole contact list from the web app.
|
||||
Chats tab: pop-up notifications (with an optional beep) when a message arrives, and the open chat refreshes on its own as replies come in - no manual reload.
|
||||
Acknowledgements and repeats-heard now show for messages you send from the web app, the same as on the device.
|
||||
A Settings page for the everyday options that make sense remotely, including live radio changes.
|
||||
A quick-command button in the Terminal that drops common commands straight into the input box.
|
||||
|
||||
# --- New: device ---
|
||||
Map options has a "Tile debug overlay" toggle (off by default) that shows a live read-out of the map-tile pipeline, for diagnosing tile problems.
|
||||
|
||||
# --- Fixed ---
|
||||
Heltec V4: map tiles downloaded but only the top of the map rendered and the rest stayed black. The 2 MB-PSRAM V4 was running out of contiguous memory for the tile grid; tiles now reuse a small fixed pool and only load what is on screen, so the whole map fills in smoothly again.
|
||||
Heltec V4 in remote/VNC mode: typing from a physical keyboard in the browser now works (it previously only worked on the T-Deck).
|
||||
The remote-access screen on the device is clearer: the IP address is shown at the top, the explanation is shorter and more accurate, the toggles sit above it, and the address box only appears once remote access is on.
|
||||
The Terminal "help" command now lists every command, on both the device and the web terminal (it was missing a lot).
|
||||
|
||||
# --- Changed ---
|
||||
The web app header now shows the same signal, Wi-Fi, Bluetooth and battery icons as the device status bar, and the title reads WADAMESH.
|
||||
The contact and chat sort/filter controls open a menu to pick an option instead of cycling through them on each tap.
|
||||
@@ -0,0 +1,15 @@
|
||||
# beta_44 (TEST) - an on-device text web browser, tappable links in chat, and a French-keyboard spacebar fix.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New: on-device web browser ---
|
||||
New "Web" app: open a URL and read the page as plain text - articles, wikis, docs, plain sites - right on the device. No proxy, no tracking, no JavaScript or images; just the words. Links are tappable, and there are back, forward and refresh buttons.
|
||||
The Web app needs the memory only the 8 MB boards have (T-Deck, Tanmatsu), so it is hidden on the 2 MB Heltec V4 - that board can't complete an on-device HTTPS handshake.
|
||||
|
||||
# --- New: links in chat ---
|
||||
Links in a chat are now tappable. Tap one for a small menu: "Open in web" loads the page in the on-device browser, and "Create QR" shows a scannable QR code so you can open the link on your phone.
|
||||
Create QR works on every board, including the Heltec V4 - so even where the browser isn't available you can still bounce a link to your phone.
|
||||
|
||||
# --- Fixed ---
|
||||
French (AZERTY) on-screen keyboard: the spacebar is back in the normal letter view (along with the OK and keyboard-switch keys). You no longer have to switch to the symbols page just to type a space.
|
||||
Heltec V4: web page fetching and map tiles are more resilient when memory is tight.
|
||||
Home signal & traffic box: the "tap for details" cue is back at the top of the box.
|
||||
@@ -0,0 +1,29 @@
|
||||
# beta_45 (STABLE) — everything since the last stable (beta_40): browser-based remote control, an on-device web browser, three new boards, and a batch of fixes.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- New: control wadamesh from a browser (over Wi-Fi — no app, account or cloud) ---
|
||||
New web control panel: run the device from a phone or laptop browser over Wi-Fi, with full Chats, Contacts, Discovered, Terminal and Settings tabs — served by the device itself. Turn on the VNC app, then open http://<device-ip>:8765/ from any browser on the same network.
|
||||
VNC app: mirrors the live device screen into the browser and lets you drive it — taps and typing in the browser control the device, and anything that changes on the device shows up in the browser.
|
||||
REMOTE app: a headless mode that renders the whole UI at a larger, browser-friendly resolution over Wi-Fi while the device screen shows the address to open; a Rotate button flips between desktop (landscape) and phone (portrait) layouts.
|
||||
Web Contacts: search, sort and filter contacts, see each one's distance, coordinates and last-heard age, and get the same per-contact actions as on the device (favourite, block, reset path, remove). Discovered nodes appear too, each with a one-tap "Add".
|
||||
Web Chats: pop-up notifications (with an optional beep) when a message arrives, the open chat auto-refreshes as replies land, and acknowledgements + repeats-heard show for messages you send from the browser.
|
||||
|
||||
# --- New: on-device web browser + links in chat ---
|
||||
New "Web" app (8 MB boards — T-Deck, Tanmatsu): open a URL and read the page as plain readable text — no proxy, tracking, JavaScript or images — with tappable links and back/forward/refresh.
|
||||
Links in a chat are now tappable: "Open in web" loads the page in the on-device browser, and "Create QR" shows a scannable QR to open the link on your phone (works on every board, including the Heltec V4).
|
||||
|
||||
# --- New boards ---
|
||||
LilyGo T-LoRa Pager (ESP32-S3): a QWERTY-keyboard handheld with a rotary encoder and no touchscreen, fully driven from the keyboard + encoder; both radio variants (LR1121 and SX1262) supported. Contributed by @codemonkeybr (PR #149) — thank you!
|
||||
LilyGo T-Display P4 (ESP32-P4): an AMOLED touchscreen handheld. Early port ("partially supported") — LoRa mesh, chat, map, GPS and the phone-app companion over Wi-Fi/USB work; Bluetooth pairing isn't available on this board (stock radio-coprocessor limitation — pair over Wi-Fi or USB).
|
||||
Heltec WiFi LoRa 32 V4-R8 (8 MB PSRAM) with Expansion Kit V2 (microSD): "partially supported" while hardware testing finishes.
|
||||
|
||||
# --- Fixed ---
|
||||
Heltec V4: map tiles now fill the whole map instead of only the top — the 2 MB-PSRAM V4 was running out of contiguous memory for the tile grid, so tiles now reuse a small fixed pool and only load what's on screen.
|
||||
Heltec V4-R8: fixed random hard-freezes that needed a reset (the touch controller was wedging the shared I2C bus).
|
||||
Tanmatsu and other keyboard-only boards: you can now select and edit the Wi-Fi name and password fields in Wi-Fi settings.
|
||||
T-Display P4: the clock sets itself over Wi-Fi even where NTP is blocked, and won't get stuck on a bad date from the hardware clock.
|
||||
French (AZERTY) on-screen keyboard: the spacebar is back in the normal letter view.
|
||||
REMOTE mode now works on the Heltec V4 TFT (and other 2 MB-PSRAM boards); keyboard-less boards can leave REMOTE by holding the screen ~3 s, and the web page has an "Exit remote" button.
|
||||
|
||||
# --- Also ---
|
||||
The flasher site shows a support badge on every board (fully vs partially supported); hover it to see what works.
|
||||