Two reporters on card-less Heltec V4s saw the whole UI freeze for 5-60 s at a time with BLE, WiFi and GPS all off and no user activity, and BOTH of a reporter's two devices froze at the same moment. About -> "Loop stalls" named the culprit unambiguously: tag `mesh`, with peaks of 11451 / 12175 / 14592 / 18063 / 19113 ms. That is inside the_mesh.loop(), i.e. the advert receive path, and the cost is SPIFFS garbage collection on a partition kept permanently churned by writes that happen per advert. The simultaneity across two units is the giveaway: the trigger is a flooded advert both of them receive, after which each runs the same deterministic write. This is the residue of beta_40's "card-less contacts-save stall fix", which capped how OFTEN the contacts rewrite happens without reducing its cost, and left the highest-frequency writer untouched. Two frequency reductions, both keeping writes ATOMIC: 1. putBlobByKey now persists an advert blob once per key per boot. The core calls it on EVERY advert (outside the auto-add block, so for already-known contacts too) and each call is a full create+truncate+write. With ~200 known nodes re-advertising that is a flash write every few seconds, forever, purely to keep the raw advert packet fresh for the Share-contact feature. The content is effectively static, and getBlobByKey still reads the file we already wrote, so there is no cache to keep coherent. Steady state on the packet path becomes zero flash I/O. Costs 2 KB of DRAM. 2. A save triggered by an add/remove now has its own 30 s floor. It previously bypassed the 5-minute refresh window entirely, so on a growing mesh every newly-heard node forced its own full 30 KB rewrite (200 contacts x 152 B). A burst of new nodes now coalesces into one rewrite. Kept short on purpose: a new contact still needs to reach flash promptly, and losing one to an unclean power cut only costs a rediscovery on that node's next advert. Deliberately NOT done: making the contacts save incremental (seek + one 152-byte record). It is tempting since the file is a flat fixed-stride array, and it would cut churn far harder, but it abandons the tmp+rename atomicity that beta_46 added to fix real contact data loss. Both reporting devices show "Last reset: Brownout", so non-atomic in-place writes on that hardware would trade a stall for corrupted contact records. Frequency first, atomicity intact. Also seen in those same screenshots and NOT addressed here: ui:gps stalls of 3077 / 3769 / 3848 ms on a device whose owner says GPS is switched off, and ui:lvgl stalls up to 2227 ms. Separate issues, worth their own investigation. Verifiable by the same diagnostic that found it: the `mesh` entries in About -> Loop stalls should stop appearing. Builds clean on Heltec V4 TFT and T-Deck. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A real touchscreen UI for your mesh radio. · open source · GPL-3.0
Touch-UI MeshCore companion-radio firmware for the LilyGo T-Deck / T-Deck Plus and Heltec V4 + TFT (ESP32-S3).
An LVGL touch UI — map, chat, contacts, channels, settings — split out of
meshcomod. The app depends on a
MeshCore fork via PlatformIO lib_deps.
Boards
See 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
This repo holds only the app: the companion_radio glue, the ui-touch
LVGL UI, the two boards' glue/variants, and platformio.ini. The MeshCore
core is not vendored here — it's pulled as a library via lib_deps from the
ALLFATHER-BV/meshcomod monorepo
(the same repo as the non-touch firmware), pinned by a lean source-only core-*
git tag. The touch-app files this repo owns (TouchPrefsStore, WifiRuntimeStore,
the transports, …) are dropped from the lib via -DMC_VENDORED_TOUCH_APP so they
aren't compiled twice. The build is byte-identical to the original in-tree
meshcomod firmware.
Build
PlatformIO pulls the core fork and all libraries automatically:
pio run -e heltec_v4_tft_companion_radio_usb_tcp_touch # Heltec V4 TFT
pio run -e LilyGo_TDeck_companion_radio_touch # LilyGo T-Deck
# or just `pio run` to build both
Flash with the NVS-preserving 4-component chain (bootloader / partitions /
boot_app0 / firmware at 0x0 / 0x8000 / 0xe000 / 0x10000) so saved Wi-Fi
credentials survive — not a merged image, which 0xFF-pads and wipes NVS.
Contributing
Contributions are welcome — see CONTRIBUTING.md. One topic per PR; inbound contributions are accepted under the project's GPL-3.0 license.
License
GPL-3.0-or-later — see LICENSE. wadamesh is copyleft: anyone who distributes a build or a fork must also make their source available under the GPL. This keeps the UI open and concentrates community effort instead of fragmenting it into closed forks.
wadamesh incorporates and depends on MeshCore (MIT, © Scott Powell / rippleradios.com) and other third-party components — see NOTICE for the full list and their licenses. MeshCore-derived files keep their MIT notices; the combined work is distributed under the GPL (MIT is GPL-compatible). The MeshCore fork that wadamesh builds against stays MIT on purpose, so its Wi-Fi/BLE hooks remain upstreamable to MeshCore.