Compare commits

...
3 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 4.8 5e4f9a221e touch: beta_48 fixes - crash, boot hang, MQTT save, message times, keyboard, more
Chat typing crash (#98), SD boot hang after update (#142, #148), MQTT
settings not saving (#128), wrong message times (#100, #152), Dutch keys
1 and ! typing ij (#144, #164), compose length limit (#119), clipboard
truncation (#120), quick messages scroll (#151), Join screen name glyph
(#116), Tanmatsu emoji picker nav (#141).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 11:24:31 +02:00
Kaj SchittecatandClaude Opus 4.8 57f206220d meshamerica: add LilyGo T-Lora Pager tile (both radio variants)
The pager folds into Mesh America's existing 'LilyGo T-Lora Pager' tile
(exact name match). Generator now supports per-radio 'variants' — emits one
gui firmware entry per build under a single tile, [LR1121]/[SX1262] title
prefix matching their stock pager labelling. Stable (beta_46) bins verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 20:26:02 +02:00
Kaj SchittecatandClaude Opus 4.8 756bb9f821 site: group device selector by manufacturer + add search + P4-LCD card
Rework the homepage device picker into collapsible per-manufacturer boxes
(LilyGo / Heltec / Elecrow / RAKwireless / Nicolai Electronics / Attaky) with
a live search that filters by device name and manufacturer and auto-expands
matching groups. Add the T-Display P4 TFT-LCD (HI8561) card, wired to a
client-built ESP32-P4 manifest + the immutable per-tag BETA archive bin (no
feed change needed), and relabel the existing P4 card (AMOLED). All existing
install-button + download + channel-toggle wiring preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 19:30:40 +02:00
9 changed files with 358 additions and 91 deletions

No files matched your search

+32 -18
View File
@@ -42,40 +42,54 @@ if os.path.exists(nf):
notes = "\n".join(l.strip() for l in open(nf, encoding="utf-8") notes = "\n".join(l.strip() for l in open(nf, encoding="utf-8")
if l.strip() and not l.strip().startswith("#")) if l.strip() and not l.strip().startswith("#"))
# Both touch boards are ESP32-S3, single touch-UI ("gui") role. ota_0 sits at # Every wadamesh tile here is ESP32-S3, touch-UI ("gui") role. ota_0 sits at
# 0x10000 on both (partitions_t*_touch.csv), so flash-update@0x10000 is correct. # 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 # `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 # 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 # 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. # (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 = [ BOARDS = [
{"name": "Heltec v4 + Expansion Kit (Touch)", "slug": "heltec-v4-tft"}, {"name": "Heltec v4 + Expansion Kit (Touch)", "slug": "heltec-v4-tft"},
{"name": "LilyGo T-Deck", "slug": "tdeck"}, {"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): 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 { return {
"maker": "wadamesh", "maker": "wadamesh",
"class": "wadamesh", "class": "wadamesh",
"name": b["name"], "name": b["name"],
"type": "esp32", "type": "esp32",
"tooltip": "<img class='device' src='%s'>" % ICON, "tooltip": "<img class='device' src='%s'>" % ICON,
"firmware": [{ "firmware": [_firmware(v["title"], v["slug"]) for v in variants],
"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)"},
],
}},
}],
} }
# Optional "what's this?" blurb Mesh America shows on a hover tooltip next to our provider badge # Optional "what's this?" blurb Mesh America shows on a hover tooltip next to our provider badge
+55
View File
@@ -69,6 +69,61 @@
} }
} }
] ]
},
{
"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_46": {
"notes": "Redesigned control center: round quick-toggle buttons that spread to fill the panel, a frosted-glass look, real lock/bell icons, and a confirmation popup on every toggle.\nDo Not Disturb: set quiet hours in Settings > Sound to silence the incoming-message chime during a daily window, with a bell in the status bar while it's active. (community contribution by Tesso Codemonkey)\nDo Not Disturb quick-toggle in the control center — a bell that gains a slash while quiet hours are on.\nContacts no longer go missing between reboots on microSD devices: saves are now atomic (written then swapped in) and the card is mounted far more reliably at boot.\nHeltec V4-R8: the microSD card now mounts, and the freeze when waking the device from sleep is fixed.\nLilyGo T-Pager: the map no longer shows a \"storage error\" under the launcher — it caches tiles to the microSD card, like the T-Deck.\nHeltec V4 in portrait: the Bluetooth icon no longer overlaps the clock in the status bar, and the control-center buttons no longer clip when tapped.",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-tlora-pager-lr1121-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_46/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_46/wadamesh-tlora-pager-lr1121.bin",
"title": "Update (app only)"
}
]
}
}
},
{
"role": "gui",
"title": "[SX1262] Touch UI",
"version": {
"beta_46": {
"notes": "Redesigned control center: round quick-toggle buttons that spread to fill the panel, a frosted-glass look, real lock/bell icons, and a confirmation popup on every toggle.\nDo Not Disturb: set quiet hours in Settings > Sound to silence the incoming-message chime during a daily window, with a bell in the status bar while it's active. (community contribution by Tesso Codemonkey)\nDo Not Disturb quick-toggle in the control center — a bell that gains a slash while quiet hours are on.\nContacts no longer go missing between reboots on microSD devices: saves are now atomic (written then swapped in) and the card is mounted far more reliably at boot.\nHeltec V4-R8: the microSD card now mounts, and the freeze when waking the device from sleep is fixed.\nLilyGo T-Pager: the map no longer shows a \"storage error\" under the launcher — it caches tiles to the microSD card, like the T-Deck.\nHeltec V4 in portrait: the Bluetooth icon no longer overlaps the clock in the status bar, and the control-center buttons no longer clip when tapped.",
"files": [
{
"type": "flash-wipe",
"name": "wadamesh-tlora-pager-sx1262-merged.bin",
"url": "https://firmware.wadamesh.com/releases/TOUCH/beta_46/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_46/wadamesh-tlora-pager-sx1262.bin",
"title": "Update (app only)"
}
]
}
}
}
]
} }
] ]
} }
+160 -49
View File
@@ -177,6 +177,28 @@
.dl2 .dlver{display:block;font-size:.72rem;color:var(--mut);font-weight:600;margin-top:2px;padding-left:20px;letter-spacing:.03em} .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} .dl2 .dlver:empty{display:none}
.hint{max-width:760px;margin:18px auto 0;color:var(--mut);font-size:.86rem;text-align:center} .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 */ /* "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{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{cursor:pointer; padding:12px 16px; font-weight:600; font-size:.92rem; color:var(--ink); list-style:none; user-select:none}
@@ -367,55 +389,102 @@
<div class="step"><div class="num">2</div><div><h4>Pick your board &amp; 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">2</div><div><h4>Pick your board &amp; 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 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>
<div class="boards"> <div class="devsel" id="devsel">
<div class="board"><h4>Heltec V4 + TFT</h4><p>ESP32-S3 with CHSC6x touch.</p> <div class="devsearch">
<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> <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>
<esp-web-install-button id="inst-heltec" manifest="https://firmware.wadamesh.com/latest/manifest-heltec-v4-tft.json"></esp-web-install-button> <input type="search" id="devSearch" autocomplete="off" placeholder="Search by device or maker — T-Deck, Heltec, P4…" aria-label="Search devices">
<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> <button type="button" id="devClearX" class="devsearch-x" data-devclear hidden aria-label="Clear search">&times;</button>
<div class="board"><h4>LilyGo T-Deck / Plus</h4><p>ESP32-S3 keyboard handheld.</p> </div>
<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> <p class="devsearch-empty" data-devempty hidden>No devices match “<span data-devq></span>”. <button type="button" data-devclear>Clear search</button></p>
<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> <details class="mfr" open data-mfr="LilyGo">
<div class="board"><h4>ThinkNode M9</h4><p>ESP32-S3 QWERTY handheld (LR1110).</p> <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>
<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> <div class="boards mfr-boards">
<esp-web-install-button id="inst-m9" manifest="https://firmware.wadamesh.com/latest-beta/manifest-thinknode-m9.json"></esp-web-install-button> <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>
<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> <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>
<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> <esp-web-install-button id="inst-tdeck" manifest="https://firmware.wadamesh.com/latest/manifest-tdeck.json"></esp-web-install-button>
<div class="board"><h4>RAK WisMesh Tap V2</h4><p>ESP32-S3 touch node.</p> <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>
<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> <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 &middot; AMOLED touch handheld.</p>
<esp-web-install-button id="inst-rak" manifest="https://firmware.wadamesh.com/latest-beta/manifest-rak-tap-v2.json"></esp-web-install-button> <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>
<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> <esp-web-install-button id="inst-tdp4" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tdisplay-p4.json"></esp-web-install-button>
<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> <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>
<div class="board"><h4>Heltec V4-R8 <span style="font-weight:600;color:var(--mut)">(experimental)</span></h4><p>ESP32-S3 &middot; 8&nbsp;MB PSRAM &middot; SD card.</p> <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>
<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> <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 &middot; TFT-LCD touch handheld.</p>
<esp-web-install-button id="inst-v4r8" manifest="https://firmware.wadamesh.com/latest-beta/manifest-heltec-v4-r8-tft.json"></esp-web-install-button> <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>
<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> <esp-web-install-button id="inst-tdp4-lcd"></esp-web-install-button>
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board, not yet hardware-tested &mdash; beta only. Please flash with care and report back.</p></div> <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>
<div class="board"><h4>Attaky Core <span style="font-weight:600;color:var(--mut)">(experimental)</span></h4><p>ESP32-S3 &middot; 16&nbsp;MB &middot; 8&nbsp;MB PSRAM.</p> <p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New screen variant, not yet hardware-tested &mdash; beta only. Flash with care and report back.</p></div>
<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> <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>
<esp-web-install-button id="inst-attaky" manifest="https://firmware.wadamesh.com/latest-beta/manifest-attaky.json"></esp-web-install-button> <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>
<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 style="color:var(--mut);font-size:.8rem;margin:0">Radio <b>LR1121</b>:</p>
<p data-wm-betaonly style="color:var(--mut);font-size:.78rem;margin:6px 0 0">New board, not yet hardware-tested &mdash; beta only. Please flash with care and report back.</p></div> <esp-web-install-button id="inst-pager-lr1121" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tlora-pager-lr1121.json"></esp-web-install-button>
<div class="board"><h4>Tanmatsu</h4><p>ESP32-P4 keyboard handheld.</p> <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>
<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:.8rem;margin:8px 0 0">Radio <b>SX1262</b>:</p>
<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> <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="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> <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>
<div class="board"><h4>LilyGo T-Display P4</h4><p>ESP32-P4 AMOLED touch handheld.</p> <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>
<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> </div>
<esp-web-install-button id="inst-tdp4" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tdisplay-p4.json"></esp-web-install-button> </details>
<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> <details class="mfr" open data-mfr="Heltec">
<div class="board"><h4>LilyGo T-LoRa Pager</h4><p>ESP32-S3 QWERTY + rotary encoder.</p> <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>
<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, driven entirely by the keyboard and rotary encoder. Pick the build that matches your radio chip.</span></span> <div class="boards mfr-boards">
<p style="color:var(--mut);font-size:.8rem;margin:0">Radio <b>LR1121</b>:</p> <div class="board" data-search="heltec v4 tft esp32-s3 chsc6x touch"><h4>Heltec V4 + TFT</h4><p>ESP32-S3 with CHSC6x touch.</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> <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>
<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> <esp-web-install-button id="inst-heltec" manifest="https://firmware.wadamesh.com/latest/manifest-heltec-v4-tft.json"></esp-web-install-button>
<p style="color:var(--mut);font-size:.8rem;margin:8px 0 0">Radio <b>SX1262</b>:</p> <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>
<esp-web-install-button id="inst-pager-sx1262" manifest="https://firmware.wadamesh.com/latest-beta/manifest-tlora-pager-sx1262.json"></esp-web-install-button> <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 &middot; 8&nbsp;MB PSRAM &middot; SD card.</p>
<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> <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>
<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> <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 &mdash; 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 &middot; 16&nbsp;MB &middot; 8&nbsp;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 &mdash; beta only. Please flash with care and report back.</p></div>
</div>
</details>
</div> </div>
<details class="verpick"> <details class="verpick">
<summary>Install a previous version</summary> <summary>Install a previous version</summary>
<div class="verpick-body"> <div class="verpick-body">
<div class="verpick-row"><label for="verSel">Version</label><select id="verSel" aria-label="Firmware version"></select></div> <div class="verpick-row"><label for="verSel">Version</label><select id="verSel" aria-label="Firmware version"></select></div>
@@ -619,6 +688,19 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
// toggle (beta-only boards stay pinned to latest-beta) and show the exact version // 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. // each feed serves — both channel tags are fetched once so every link is accurate.
const WM_VER = {}; 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(){ window.wmUpdateDownloads = function(){
document.querySelectorAll('[data-dl-board]').forEach(a => { document.querySelectorAll('[data-dl-board]').forEach(a => {
const feed = a.hasAttribute('data-dl-betaonly') ? 'latest-beta' : WM.feed; const feed = a.hasAttribute('data-dl-betaonly') ? 'latest-beta' : WM.feed;
@@ -627,10 +709,39 @@ We use no tracking, analytics, or advertising cookies. Our CDN and security prov
}); });
}; };
['latest','latest-beta'].forEach(feed => fetch('https://firmware.wadamesh.com/' + feed + '/version.json', {cache:'no-store'}) ['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(); } }).catch(()=>{})); .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')))); document.querySelectorAll('[data-wm-ch]').forEach(b => b.addEventListener('click', () => wmSetChannel(b.getAttribute('data-wm-ch'))));
wmSetChannel('stable'); 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 ---- // ---- community videos — add an entry here to add a video ----
const VIDEOS = [ const VIDEOS = [
// newest first // newest first
+14
View File
@@ -0,0 +1,14 @@
# beta_48 - stability and fixes: crash, boot-stuck, MQTT, message times, keyboard and more
# One user-facing note per non-blank, non-# line; # lines are section comments.
# Fixed
Fixed a crash that could reboot the device while you were typing in a chat.
Fixed devices getting stuck on the boot screen after an update when a microSD card is fitted. The boot-time copy of your data to the card could hang on a slow or damaged card; it is now guarded and the device always boots.
MQTT bridge settings now save and survive a reboot. They were being written to the wrong storage and getting lost.
Message times now match the clock at the top of the screen, instead of showing 1910 or other wrong dates.
Dutch keyboard: the "1" and "!" keys were typing "ij" and "IJ". Fixed.
The message box now stops you at the real send limit instead of quietly dropping the end of a long message when you send.
Copy and paste no longer cuts off long text.
The quick messages list now scrolls when there are more entries than fit on screen.
Room server names that start with an unusual character no longer show an empty box on the Join screen.
Tanmatsu: you can now reach every emoji in the picker, not just the first half.
+5 -3
View File
@@ -6,7 +6,9 @@
MqttBridge mqtt_bridge; MqttBridge mqtt_bridge;
#if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) // ---- real PubSubClient/WiFiClient implementation; NOT built on the P4 boards ---- #if !defined(HAS_TANMATSU) && !defined(HAS_TDISPLAY_P4) // ---- real PubSubClient/WiFiClient implementation; NOT built on the P4 boards ----
#include <Preferences.h> #include "SdNvsPrefs.h" // NOT raw NVS Preferences: the touch firmware abandoned NVS (tiny, shared,
// doesn't survive Launcher) for a file-backed store. MQTT was the last setting
// still on NVS, so its writes silently failed / didn't persist (GH #128).
#include <WiFi.h> #include <WiFi.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -42,7 +44,7 @@ void MqttBridge::loadConfig() {
_host[0] = _user[0] = _pwd[0] = _psk[0] = '\0'; _host[0] = _user[0] = _pwd[0] = _psk[0] = '\0';
_port = 1883; _port = 1883;
Preferences p; SdNvsPrefs p;
if (p.begin("mqtt", true)) { if (p.begin("mqtt", true)) {
_enabled = p.getBool("en", false); _enabled = p.getBool("en", false);
_port = (uint16_t)p.getUInt("port", 1883); _port = (uint16_t)p.getUInt("port", 1883);
@@ -223,7 +225,7 @@ void MqttBridge::publishChannel(int channelIdx, const char* channelName,
void MqttBridge::saveConfig(const char* host, uint16_t port, void MqttBridge::saveConfig(const char* host, uint16_t port,
const char* user, const char* pwd, const char* user, const char* pwd,
bool pubDm, bool pubChannel, const char* psk, bool enable) { bool pubDm, bool pubChannel, const char* psk, bool enable) {
Preferences p; SdNvsPrefs p;
if (!p.begin("mqtt", false)) return; if (!p.begin("mqtt", false)) return;
p.putBool("en", enable); p.putBool("en", enable);
p.putString("host", host); p.putString("host", host);
+37 -2
View File
@@ -234,15 +234,24 @@ bool meshcomodMigrateSpiffsToSd(bool force) {
File s = SPIFFS.open(src, FILE_READ); File s = SPIFFS.open(src, FILE_READ);
File d = s ? SD.open(dst, FILE_WRITE) : File(); // FILE_WRITE truncates File d = s ? SD.open(dst, FILE_WRITE) : File(); // FILE_WRITE truncates
bool ok = s && d; bool ok = s && d;
size_t since_feed = 0;
while (ok && s.available()) { while (ok && s.available()) {
const size_t n = s.read(buf, sizeof buf); const size_t n = s.read(buf, sizeof buf);
if (n == 0 || d.write(buf, n) != n) ok = false; if (n == 0 || d.write(buf, n) != n) ok = false;
// Feed the task-WDT mid-file: a large history file on a slow/cold SD card can take
// longer than the 20 s task-WDT to copy in 4 KB chunks, and this runs on loopTask
// during setup() — without the feed the dog panics and reboot-loops the boot (GH
// #142/#148). The copy still can't tolerate a genuinely wedged FAT layer; the
// NVS breadcrumb at the call site handles that.
since_feed += n;
if (since_feed >= 32768) { esp_task_wdt_reset(); since_feed = 0; }
} }
if (s) s.close(); if (s) s.close();
if (d) d.close(); if (d) d.close();
if (ok) { ++copied; if (strcmp(src, "/identity/_main.id") == 0) identity_ok = true; } if (ok) { ++copied; if (strcmp(src, "/identity/_main.id") == 0) identity_ok = true; }
else { ++failed; Serial.printf("[BOOT] SD migrate FAILED: %s\n", src); if (SD.exists(dst)) SD.remove(dst); } else { ++failed; Serial.printf("[BOOT] SD migrate FAILED: %s\n", src); if (SD.exists(dst)) SD.remove(dst); }
f.close(); f.close();
esp_task_wdt_reset();
yield(); yield();
} }
root.close(); root.close();
@@ -253,6 +262,14 @@ bool meshcomodMigrateSpiffsToSd(bool force) {
copied, failed, identity_ok ? "ok" : "MISSING"); copied, failed, identity_ok ? "ok" : "MISSING");
return identity_ok; return identity_ok;
} }
// Clear the boot safe-mode latch (see the SPIFFS->SD migration above): called after a
// successful manual "Copy internal data to SD" so a deliberate retry re-arms boot-time
// auto-adoption. The boot path re-latches on its own if a later migration wedges. GH #142/#148.
void meshcomodClearSdMigLatch() {
Preferences _mp;
if (_mp.begin("touch", false)) { _mp.remove("sd_mig_busy"); _mp.end(); }
}
#endif #endif
void setup() { void setup() {
@@ -518,8 +535,26 @@ void setup() {
// rather than adopting a card without the identity on it. // rather than adopting a card without the identity on it.
bool adopt = true; bool adopt = true;
if (SPIFFS.exists("/identity/_main.id") && !SD.exists("/meshcomod/identity/_main.id")) { if (SPIFFS.exists("/identity/_main.id") && !SD.exists("/meshcomod/identity/_main.id")) {
adopt = meshcomodMigrateSpiffsToSd(false); // Boot safe-mode (GH #142/#148): a wedged or corrupt SD card can hang / WDT-reboot the
if (!adopt) Serial.println("[BOOT] SD migration incomplete -> staying on SPIFFS this boot"); // device mid-migration, stranding it on the boot screen EVERY boot (reset can't escape,
// only a downgrade could). Drop an NVS breadcrumb before migrating and clear it only if
// the copy RETURNS. If it's still set at the next boot, the last migration never finished
// -> skip it and boot from SPIFFS (the data is safe there); Settings > "Copy internal data
// to SD" re-arms a deliberate retry. A merely-slow (healthy) card completes thanks to the
// in-loop WDT feed, so it never latches here.
Preferences _mp;
const bool mp_ok = _mp.begin("touch", false);
const bool mig_busy = mp_ok && _mp.getBool("sd_mig_busy", false);
if (mig_busy) {
if (mp_ok) _mp.end();
adopt = false;
Serial.println("[BOOT] prior SD migration did not complete -> skipping (staying on SPIFFS); retry via Settings > Copy internal data to SD");
} else {
if (mp_ok) { _mp.putBool("sd_mig_busy", true); _mp.end(); }
adopt = meshcomodMigrateSpiffsToSd(false);
Preferences _mp2; if (_mp2.begin("touch", false)) { _mp2.remove("sd_mig_busy"); _mp2.end(); }
if (!adopt) Serial.println("[BOOT] SD migration incomplete -> staying on SPIFFS this boot");
}
} }
if (adopt) { if (adopt) {
sd_storage = store.useSdStorage(); sd_storage = store.useSdStorage();
+5 -4
View File
@@ -626,8 +626,9 @@ static const char* hw_de_upper[26] = {
static const char* hw_de_digits[10] = { nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr }; static const char* hw_de_digits[10] = { nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr };
static const char* hw_de_digits_shift[10] = { nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr }; static const char* hw_de_digits_shift[10] = { nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr };
/* Dutch keeps the standard alpha matrix but exposes the common IJ digraph on a /* Dutch keeps the standard alpha matrix. (The IJ digraph used to sit on a hardware
* digit slot so compact hardware still gets a language-specific shortcut. */ * digit slot, but slot 1 is the "1" key AND the back-mapped "!" key, so it broke both
* — GH #144/#164 — and the digraph now lives only on the on-screen keyboard's key.) */
static const char* hw_nl_lower[26] = { static const char* hw_nl_lower[26] = {
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
@@ -638,8 +639,8 @@ static const char* hw_nl_upper[26] = {
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z" "U", "V", "W", "X", "Y", "Z"
}; };
static const char* hw_nl_digits[10] = { nullptr, "ij", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; static const char* hw_nl_digits[10] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
static const char* hw_nl_digits_shift[10] = { nullptr, "IJ", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; static const char* hw_nl_digits_shift[10] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
/* Spanish stays close to US QWERTY; use the number row to expose n-tilde on /* Spanish stays close to US QWERTY; use the number row to expose n-tilde on
* compact hardware where the semicolon key is absent. */ * compact hardware where the semicolon key is absent. */
+49 -14
View File
@@ -2871,7 +2871,10 @@ static int s_nav_count = 0; // # focusable widgets collected
// Collected focusable widgets this rebuild, in tree order — used for 2D spatial // Collected focusable widgets this rebuild, in tree order — used for 2D spatial
// navigation (W/A/Z/D move to the nearest element in that physical direction, not // navigation (W/A/Z/D move to the nearest element in that physical direction, not
// just the next/prev in the linear list). // just the next/prev in the linear list).
static const int kNavMax = 64; static const int kNavMax = 160; // spatial-nav mirror capacity — MUST exceed the largest focusable
// grid or its tail is keypad-unreachable on the Tanmatsu: focusables
// past this cap land in the LVGL group but not s_nav_objs[], so
// navMoveDir() never reaches them (the 122-cell emoji picker — GH #141).
static lv_obj_t* s_nav_objs[kNavMax] = { nullptr }; static lv_obj_t* s_nav_objs[kNavMax] = { nullptr };
static lv_obj_t* s_nav_tabbar = nullptr; // the bottom tab bar (btnmatrix), added last to the group static lv_obj_t* s_nav_tabbar = nullptr; // the bottom tab bar (btnmatrix), added last to the group
static bool s_nav_want_tabbar = false; // after switching tabs from the bar, refocus the bar static bool s_nav_want_tabbar = false; // after switching tabs from the bar, refocus the bar
@@ -7194,7 +7197,12 @@ static void openQuickReplyPicker(LvChatPanel* p) {
lv_obj_set_style_border_color(card, lv_color_hex(0x18191A), LV_PART_MAIN); lv_obj_set_style_border_color(card, lv_color_hex(0x18191A), LV_PART_MAIN);
lv_obj_set_style_border_width(card, 1, LV_PART_MAIN); lv_obj_set_style_border_width(card, 1, LV_PART_MAIN);
lv_obj_set_style_pad_all(card, pad, LV_PART_MAIN); lv_obj_set_style_pad_all(card, pad, LV_PART_MAIN);
lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE); // The card is clamped to the visible height above, but its rows are absolutely
// positioned and can extend past that clamp (small screens + Large/Huge UI scale) —
// enable vertical scrolling so the lower quick-replies + hint stay reachable (GH #151).
lv_obj_add_flag(card, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_scroll_dir(card, LV_DIR_VER);
lv_obj_set_scrollbar_mode(card, LV_SCROLLBAR_MODE_AUTO);
addCloseXBadge(card, qrSheetCloseCb); addCloseXBadge(card, qrSheetCloseCb);
lv_obj_t* title = lv_label_create(card); lv_obj_t* title = lv_label_create(card);
@@ -7607,7 +7615,9 @@ static void tdeckModalAutoFocusAsync(void* root) {
// Mirrors MCterm's in-RAM clipboard. Anything the user long-presses to copy // Mirrors MCterm's in-RAM clipboard. Anything the user long-presses to copy
// lands here; long-press on a textarea pastes the contents at the cursor. // lands here; long-press on a textarea pastes the contents at the cursor.
// One global buffer keeps the code simple and avoids heap churn. // One global buffer keeps the code simple and avoids heap churn.
constexpr size_t CLIPBOARD_MAX = 192; constexpr size_t CLIPBOARD_MAX = 640; // must cover the largest copyable text — a full composer draft
// plus the 600-byte label-staging buffer (clipboardSet's callers);
// 192 silently truncated long copy/pastes (GH #120).
static char s_clipboard[CLIPBOARD_MAX] = {0}; static char s_clipboard[CLIPBOARD_MAX] = {0};
static void clipboardSet(const char* text, const char* tag) { static void clipboardSet(const char* text, const char* tag) {
@@ -10184,6 +10194,7 @@ static void useSdStorageToggleCb(lv_event_t* e) {
// empty card (which then minted a fresh identity). Overwrites the card's copies // empty card (which then minted a fresh identity). Overwrites the card's copies
// with EVERYTHING from internal flash, forces the SD pref on, reboots. // with EVERYTHING from internal flash, forces the SD pref on, reboots.
extern bool meshcomodMigrateSpiffsToSd(bool force); // main.cpp extern bool meshcomodMigrateSpiffsToSd(bool force); // main.cpp
extern void meshcomodClearSdMigLatch(); // main.cpp (GH #142/#148 boot safe-mode)
static void sdRestoreApply() { static void sdRestoreApply() {
if (!g_lv.task) return; if (!g_lv.task) return;
if (!fmSdTryMount()) { g_lv.task->showAlert(TR("No SD card"), 1600); return; } if (!fmSdTryMount()) { g_lv.task->showAlert(TR("No SD card"), 1600); return; }
@@ -10193,6 +10204,7 @@ static void sdRestoreApply() {
const bool ok = meshcomodMigrateSpiffsToSd(true); const bool ok = meshcomodMigrateSpiffsToSd(true);
enableLoopWDT(); enableLoopWDT();
if (!ok) { g_lv.task->showAlert(TR("Copy failed (no internal identity)"), 2200); return; } if (!ok) { g_lv.task->showAlert(TR("Copy failed (no internal identity)"), 2200); return; }
meshcomodClearSdMigLatch(); // manual copy succeeded -> re-arm boot auto-adoption (GH #142/#148)
touchPrefsSetUseSdStorage(true); // make sure the reboot adopts the card touchPrefsSetUseSdStorage(true); // make sure the reboot adopts the card
delay(400); // let the alert render before the restart delay(400); // let the alert render before the restart
board.reboot(); board.reboot();
@@ -13140,7 +13152,7 @@ static void mqttSaveCb(lv_event_t* e) {
bool pub_ch = !g_set_modal.mqtt_ch_sw || lv_obj_has_state(g_set_modal.mqtt_ch_sw, LV_STATE_CHECKED); bool pub_ch = !g_set_modal.mqtt_ch_sw || lv_obj_has_state(g_set_modal.mqtt_ch_sw, LV_STATE_CHECKED);
bool pub_dm = g_set_modal.mqtt_dm_sw && lv_obj_has_state(g_set_modal.mqtt_dm_sw, LV_STATE_CHECKED); bool pub_dm = g_set_modal.mqtt_dm_sw && lv_obj_has_state(g_set_modal.mqtt_dm_sw, LV_STATE_CHECKED);
MqttBridge::saveConfig(host, port, user, pwd, pub_dm, pub_ch, psk, en); MqttBridge::saveConfig(host, port, user, pwd, pub_dm, pub_ch, psk, en);
{ Preferences p; if (p.begin("mqtt", false)) { p.putBool("consent", consent); p.end(); } } { SdNvsPrefs p; if (p.begin("mqtt", false)) { p.putBool("consent", consent); p.end(); } } // file-backed, not NVS (GH #128)
mqtt_bridge.reloadConfig(); mqtt_bridge.reloadConfig();
closeSettingsModal(); closeSettingsModal();
} }
@@ -13293,7 +13305,7 @@ static void buildMqttSettings() {
// ---- Load current config ---- // ---- Load current config ----
{ {
Preferences p; SdNvsPrefs p; // file-backed, matches MqttBridge (GH #128)
bool cur_en = false, cur_dm = false, cur_ch = true, cur_consent = false; bool cur_en = false, cur_dm = false, cur_ch = true, cur_consent = false;
char cur_host[64] = {}, cur_port_s[8] = "1883", cur_user[32] = {}, cur_pwd[32] = {}, cur_psk[33] = {}; char cur_host[64] = {}, cur_port_s[8] = "1883", cur_user[32] = {}, cur_pwd[32] = {}, cur_psk[33] = {};
if (p.begin("mqtt", true)) { if (p.begin("mqtt", true)) {
@@ -14298,8 +14310,13 @@ static void openAdminLoginPrompt(const ContactInfo& c) {
}); });
const bool is_room = (c.type == ADV_TYPE_ROOM); const bool is_room = (c.type == ADV_TYPE_ROOM);
// Sanitize like every other name-display path: copyUtf8ReplacingMissingGlyphs swaps an
// unrenderable codepoint for '*'. This Join title skipped it and drew tofu (▯) for a name's
// leading unsupported codepoint, while the contacts list/details showed '*' (GH #116).
char safe_name[24];
copyUtf8ReplacingMissingGlyphs(&g_font_14, safe_name, sizeof(safe_name), c.name);
char hdr_buf[40]; char hdr_buf[40];
snprintf(hdr_buf, sizeof(hdr_buf), "%s %.20s", is_room ? "Join:" : "Login:", c.name); snprintf(hdr_buf, sizeof(hdr_buf), "%s %.20s", is_room ? "Join:" : "Login:", safe_name);
lv_obj_t* title = lv_label_create(card); lv_obj_t* title = lv_label_create(card);
lv_label_set_text(title, hdr_buf); lv_label_set_text(title, hdr_buf);
lv_obj_set_style_text_color(title, lv_color_hex(COLOR_TEXT), LV_PART_MAIN); lv_obj_set_style_text_color(title, lv_color_hex(COLOR_TEXT), LV_PART_MAIN);
@@ -27422,6 +27439,11 @@ static void makeChatDetail(LvChatPanel& p) {
// — the physical-keyboard CR and the on-screen OK are handled before any // — the physical-keyboard CR and the on-screen OK are handled before any
// newline can be inserted, so the composer never actually holds a '\n'. // newline can be inserted, so the composer never actually holds a '\n'.
lv_textarea_set_one_line(p.composer_ta, false); lv_textarea_set_one_line(p.composer_ta, false);
// Cap input at the LoRa text limit so the user can't type more than can be sent (GH #119).
// (LVGL caps by codepoint; for ASCII that's exactly MAX_MSG_TEXT bytes. The send buffer,
// _compose_buf, is sized MAX_MSG_TEXT+1 to match — it used to be 128, silently chopping
// messages at 127 bytes, shorter than this limit.)
lv_textarea_set_max_length(p.composer_ta, UITask::MAX_MSG_TEXT);
lv_obj_set_scrollbar_mode(p.composer_ta, LV_SCROLLBAR_MODE_OFF); lv_obj_set_scrollbar_mode(p.composer_ta, LV_SCROLLBAR_MODE_OFF);
lv_textarea_set_placeholder_text(p.composer_ta, TR("Type a message...")); lv_textarea_set_placeholder_text(p.composer_ta, TR("Type a message..."));
lv_obj_set_style_text_color(p.composer_ta, lv_color_hex(COLOR_TEXT), LV_PART_MAIN); lv_obj_set_style_text_color(p.composer_ta, lv_color_hex(COLOR_TEXT), LV_PART_MAIN);
@@ -33851,6 +33873,15 @@ if (g_lv.task && g_lv.task->isManualLock()) {
// The on-screen keyboard is never shown on the T-Deck, but a textarea is still // The on-screen keyboard is never shown on the T-Deck, but a textarea is still
// bound to it on focus — that binding is our target. // bound to it on focus — that binding is our target.
lv_obj_t* ta_focused = lv_keyboard_get_textarea(g_lv.keyboard); lv_obj_t* ta_focused = lv_keyboard_get_textarea(g_lv.keyboard);
// Guard a STALE binding (crash #98, decoded coredump: NULL spec_attr in lv_event_send).
// lv_keyboard_get_textarea() returns the last-bound composer; if that textarea was deleted
// without the keyboard being re-pointed (a chat-panel teardown that bypassed hideKb()), it's
// a dangling pointer and typing into it dereferences freed memory. Validate + self-heal the
// binding before anything below can insert into it.
if (ta_focused && !lv_obj_is_valid(ta_focused)) {
if (g_lv.keyboard) lv_keyboard_set_textarea(g_lv.keyboard, nullptr);
ta_focused = nullptr;
}
#if CAP_KEYPAD_NAV && !defined(TLORA_PAGER) #if CAP_KEYPAD_NAV && !defined(TLORA_PAGER)
// Edit mode (keyboard-nav ON only): a focused field becomes the typing target after // Edit mode (keyboard-nav ON only): a focused field becomes the typing target after
// select/Enter (below) — until then `ta` is null so the nav keys navigate. With // select/Enter (below) — until then `ta` is null so the nav keys navigate. With
@@ -41369,14 +41400,15 @@ int UITask::appendMessage(const char* thread, const char* sender, const char* te
int t_idx = findOrCreateThread(thread, channel); int t_idx = findOrCreateThread(thread, channel);
if (t_idx < 0) return -1; if (t_idx < 0) return -1;
UIMessage& m = _ui_msgs[_ui_msg_head]; UIMessage& m = _ui_msgs[_ui_msg_head];
// Prefer wall-clock (RTC) over uptime — bubble timestamps want HH:MM, and // Bubble timestamps must match the status-bar clock, which reads the NTP-synced ESP32
// last_ts/sort comparisons still work since RTC epoch (~1.7e9) outranks // system clock (time(nullptr)) — NOT the mesh RTC (getRTCClock()). On boards whose
// any plausible uptime-seconds value. If RTC is unset, fall back to // hardware RTC chip reads BCD/century garbage, getCurrentTime() returned wrong epochs
// uptime; the Info popup shows "—" when the value isn't an epoch. // that rendered as 1910-09-03 / 1994 (GH #100/#152). Use the system clock; the ~1.7e9
// epoch still outranks any uptime-seconds value for last_ts/sort, and we fall back to
// uptime when the clock is unset (the Info popup shows "—" for non-epoch values).
{ {
auto* rtc = the_mesh.getRTCClock(); time_t sys = time(nullptr);
uint32_t now_epoch = rtc ? rtc->getCurrentTime() : 0; m.ts = (sys > 1700000000) ? (uint32_t)sys : (uint32_t)(millis() / 1000);
m.ts = (now_epoch > 1700000000) ? now_epoch : (uint32_t)(millis() / 1000);
} }
m.channel = channel; m.channel = channel;
m.outgoing = outgoing; m.outgoing = outgoing;
@@ -43544,7 +43576,10 @@ void UITask::newMsgImpl(uint8_t path_len, const char* from_name, const char* tex
// (embedded ts ≈ now) are unaffected. Consume-once so it never leaks to the next. // (embedded ts ≈ now) are unaffected. Consume-once so it never leaks to the next.
{ {
uint32_t emb_ts = the_mesh.uiConsumeLastSenderTs(); uint32_t emb_ts = the_mesh.uiConsumeLastSenderTs();
if (emb_ts > 1700000000) _ui_msgs[msg_slot].ts = emb_ts; // Sanity WINDOW, not just a lower bound: a peer with a garbage-future clock sends e.g.
// ~2.42e9, which as a signed 32-bit time_t wraps to 1910-09-03 06:42:07 (GH #100/#152).
// Same window as MyMesh.cpp's advert-time guard (>1.7e9 && <2.0e9).
if (emb_ts > 1700000000 && emb_ts < 2000000000) _ui_msgs[msg_slot].ts = emb_ts;
} }
syncThreadMeshSlots(thread, channel); syncThreadMeshSlots(thread, channel);
#if defined(HAS_TDECK_GT911) #if defined(HAS_TDECK_GT911)
+1 -1
View File
@@ -167,7 +167,7 @@ private:
bool _composer_mode; bool _composer_mode;
int _composer_char_idx; int _composer_char_idx;
int _composer_action_idx; int _composer_action_idx;
char _compose_buf[128]; char _compose_buf[MAX_MSG_TEXT + 1]; // full LoRa text length; was 128 -> silently chopped sends at 127 bytes (GH #119)
unsigned long _next_mesh_thread_refresh; unsigned long _next_mesh_thread_refresh;
TouchUiScreen _touch_screen; TouchUiScreen _touch_screen;
bool _active_dm_contact_set; bool _active_dm_contact_set;