- p4-release.sh builds and verifies the three revisions (V1.2, V1.1, V1.0)
from crowpanel_p4_101/ in the same loop as the T-Display P4 images, with
the same OTA-name and tag checks.
- Self-update is on (CAP_OTA 1): each revision updates from its own
wadamesh-crowpanel-p4-101-v1x.bin, so a V1.0 never takes a V1.2 image.
- gen-flasher-meta writes a manifest per revision; wadamesh.com gets a
CrowPanel Advanced 10.1 card with a V1.2 / V1.1 / V1.0 picker, and the
standalone flasher one card per revision. Both point at the beta feed,
so nothing shows until the next beta publishes the images.
- README, DEVICES.md and the site's board count (15).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Standalone ESP-IDF target (crowpanel_p4_101/) modelled on tdisplay_p4/, sharing its
ESP-IDF, components and esp-hosted 2.12.3 backend. Untested on hardware.
- variants/crowpanel_p4_101: EK79007 MIPI-DSI 1024x600 display (Elecrow timings,
LEDC backlight on GPIO31), GT911 touch on I2C0 45/46 (RST40/INT42), SX1262 on the
J9/J11 header with a real DIO1 IRQ, LDO3/LDO4 bring-up, C6_EN reset callback on
GPIO32, full-system-reset restart shim.
- Board revision is a build option: WADA_CP_REV=1.0|1.1|1.2 (default 1.2). V1.2 moves
LoRa DIO1/NRST from GPIO53/54 to 27/28; V1.0 has the C6 SDIO data lines reversed
(run 1-bit like Elecrow's V1.0 lesson). hostedSetPins() overrides Arduino's
generic esp32p4 SDIO pin defaults, which only match V1.0.
- LVGL at native 1024x600 with the large-screen layouts; fixed landscape.
- Shared src/: one CAP_* block in device_caps.h plus HAS_CROWPANEL_P4_101 at the P4
platform sites (storage law, SD_MMC, hosted BLE, no RTC_CNTL, no MQTT, fonts) and
the CrowPanel no-unlock-control sites. navOpenChatPanel/navGoToMainTab get
fallbacks for boards without CAP_KEYPAD_NAV.
- build.sh merge writes a 0x0 merged image per revision.
T-Display P4 still builds from the same tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Launches each catalog app (kAuditStore, generated by gen-radio-audit-apps.py
into the gitignored doc_radio_apps.h) and captures store_<id> for the
website's App Store page. Music gets two bench tracks and a play tap first.
Capture builds only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
apps.html is generated from deploy/apps/apps.json (the catalog the devices
read) plus the site-only write-ups in deploy/site/apps-info.json, by
scripts/build/gen-apps-page.py, which deploy-site.sh now runs on every
deploy. Linked from the home page (Apps button + resource card), the SDK
guide and the sitemap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
About (Build) and the boot log name the meshcomod release the core is:
"on meshcomod v1.18.0.1". release.sh reads it from the pinned core's own
examples/companion_radio/MyMesh.h, keeps the in-tree defaults in step and checks
every staged image carries it. Language store v26 (this build's new texts),
release notes for beta_95.
Bench (T-Deck, 1.18): device and self info, 404-contact sync, the companion CLI
(ver, board, get name/radio/tx/freq, unknown command), 4 packets received and a
zero-hop advert sent in 90 s, correct device time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pins move from core-v1.17.4 to core-v1.18.0, cut from the main meshcomod
companion-v1.18.0.1 was released from. The companion changes are ported from
meshcomod's rebase into wadamesh's own MyMesh:
- onCLICommandRecv: a contact's remote CLI command runs when the user granted
that contact remote CLI, and lands in the chat as a CLI message otherwise.
- CMD_RUN_CLI_COMMAND (66) / RESP_CODE_CLI_REPLY (29) and the local companion CLI
(name, pin, Wi-Fi, radio, af/dutycycle, rxgain, tx, rxdelay, path.hash.mode,
multi.acks, board, ver), also reachable from the serial CLI. Settings without a
prefs field answer "unsupported". NodePrefs layout unchanged.
- TXT_TYPE_CLI_COMMAND is accepted for sending; sendCommandData (and our queue-
preserving wrapper) carry the text type.
- Channel data is delivered to the app (onChannelDataRecv), unsolicited responses
(pushed telemetry) reach it as PUSH_CODE_BINARY_RESPONSE, and raw-data sends
decode multi-byte-hash paths.
Clock: 1.18 seeds an unsynced ESP32 clock at 1 Mar 2026, so ClockFloorRTC's
MIN_VALID_EPOCH follows it (a set equal to the seed must stay invalid).
Pager LR1121: the AGC reset re-applies the boosted-gain setting (new core arg).
IDF builds exclude the new CommonRadioPrefs.cpp (needs <target.h>/radio_driver).
The 2 MB Heltec V4 went to 100.0% with the CLI. Its built-in language table is now
one raw-deflated blob per language (895 KB -> 312 KB), inflated into PSRAM by the
S3 ROM on first use (~10 ms, ~75 KB for the active language): 91.7% flash. Checked
on a T-Deck forced onto the table (nl, hu, el) and by the post-link key check.
Builds: all 15 S3 touch envs, Tanmatsu (3,237,024 B), T-Display P4 SX1262 + LR2021.
T-Deck boots and runs on 1.18.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spectrum: stat tiles (peak, floor, our channel, busy), plot and waterfall drawn
at screen size (the lv_chart and zoomed waterfall were the page's render cost),
dark-to-yellow waterfall, peak hold, our-channel marker, Band/Channel and
Live/1 min/10 min chips. RF Monitor 1.4 (tiles, filled chart, typed feed, packet
detail card; the old type table did not match MeshCore payload types) and
Airtime 1.6 (big figure, tiles, duty line, rx/tx split).
Lua SDK: ui.panel, bold labels and a size-24 figure font, colors.warn/border,
app.on_audio (fires with the screen off), read-only wada.sd.list and sd: audio on
the SD_MMC boards (P4, Tanmatsu, Wio L2), caps panel/audio_events/epaper, a
"music" icon and an "audio" Store requirement. button:set() no longer writes
label fields into the button object (heap corruption, every earlier firmware).
The SDK page's input table described events the firmware never sent.
Music 1.0 (Store app): plays MP3/WAV from the card's Music folder, shuffle,
repeat, keeps going through the list with the screen off. Verified on the
T-Display P4 end to end.
Store: the update check also fetches the app catalog and badges the Store tile
with the number of installed apps that have a newer version. Baked Lua apps are
raw-deflated and inflated by the S3 ROM (14.6 KB back on the V4); the V4 language
table keeps only texts of apps it can run.
Fixes: the SD-card notice no longer fires when the card mounts fine (#667); the
Control Center clock keeps time while open (#666); M9 keyboard brightness (#664);
the battery widget's number no longer sits on a ring too small for it (Discord);
M9 shortcuts doc follows #669's composer order. New texts in all 14 languages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked for on Discord (prepper use: books and documentation offline on the card).
- The Reader opens a .txt or .md file on the card as a 16 KB window at an
offset carried in the URL (sd:/x.txt?o=49152), cut back to a line (or a whole
UTF-8 character), with Next page / Previous page / Back to the start links and
the position in percent. A book of any size pages through; Back works as usual.
- Files opens HTML on the card in the Reader (its links work there, so a folder of
pages browses offline) and text or markdown too big for the 8 KB editor; small
text files keep the editor.
- scripts/sdpack.py (standard library only): an EPUB becomes /library/<name>/ with
one page per chapter (long chapters split under 30 KB, linked in order) and a
contents page; .txt/.md are copied as they are; /home.htm lists everything, so
the Reader, which opens sd:/home.htm, starts on the library. Tested with a
generated EPUB.
ZIM (Wikipedia) files are not read on the device: their clusters are zstd/xz
blobs of 1-2 MB and the files run to tens of GB on FAT32; converting on a
computer keeps the device side small.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
beta_93 pinned the Heltec V4 to the beta_92 .lang files because the full table
no longer fit. lang_reach.py now works out which keys any source can look up:
a .lang key is kept when it equals a C string literal anywhere in the sources
(every #if branch, adjacent literals joined, LV_SYMBOL_*/UI_ICON_* and other
string macros expanded, escapes decoded as GCC does), or that literal with TR()'s
glyph prefix stripped, or a string in any Lua app. 1818 of 1925 keys survive; the
107 dropped look unused (old dropdown strings, texts the source has reworded).
The trimmed live table is 15.5 KB smaller than the pinned one (V4 4,025,365 B
used, 37.9 KB free) and carries every translation added since beta_92.
Guards: the generator refuses to trim when an input is missing (lv_symbol_def.h,
UITask.cpp, or under 80% kept) and leaves the header alone; after linking the V4,
--check-objects reads the strings the compiler emitted and fails the build if a
translated key among them was dropped (proved by dropping a live one). The
pre-build hook regenerates on source changes too, and the header is only
rewritten when its content changes. The pinned copy is gone (git history).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New env crowpanel_50_companion_radio_touch for the 800x480 ST7262 RGB panel,
GT911 touch and an SX1262 in the wireless slot. Board quirks follow Camillia-MT:
14 MHz pixel clock, internal-RAM draw band, PCA9557 (V1.0) / MCU (V1.1+)
backlight controller detection with GT911 reset at 0x5D or 0x14, NSS on GPIO8
(V1.2+), no SD/GPS/battery. Large-screen layouts and UI-size fonts; browser
file transfer to the internal tiles partition. Added to the upload helper,
release script, flasher manifests, site, DEVICES.md, README and NOTICE.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enable CAP_UI_SIZE and the font-only text-size presets (montserrat
12/14/16, 16/18/20, 18/20/24) for HAS_TDECK_PRO boards, compile the
18/20/24 px UI fonts for them, and size the status bar to match.
Docs list the T-Deck Pro and Max under Text size.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contact nicknames: TouchPrefsStore nick blob (48 x 38 B), display-only lookups
(contactShownName, threadShownName, senderShownName), Contact options >
Nickname on the shared text prompt (an empty OK clears it).
Control panel restyled in the launcher's tile language, with a Battery saver
tile; keyboard tooltips (navSetTip), an amber cursor and touch long-press
tooltips (the T-Deck touch read hid them on every poll).
T-LoRa Pager: M/C/A/S on Home, H/S tap and hold, Backspace = Back, N = jump to
new, held Enter = long press, per-tab focus memory, Power off confirm; LVGL
-O2 and IRAM, INT-gated keyboard, idle yield, status-bar setter guards, instant
tabs; PagerGps (u-blox software standby for rests, GPS rail cut when off),
panel sleep, amp off at boot, power-off parks the radio (NSS held), GPS, panel
and keyboard light.
Fixes: the DM duplicate check keys on the attempt and a path reset clears it,
the direct timeout masks the packed path byte, web remote hops (#636);
changelog over http (#638); Lua SDK send budgets and duplicate guard (#606);
GPS speed note (#613, CAP_GPS_BAUD_AUTO); emoji chat icon (#640); default
wallpaper (#626).
Heltec V4: Tetris, 2048 and Breakout unseeded and the built-in Lua stripped of
comments (identical bytecode), 27 KB freed; the baked language table is pinned
to beta_92's files (scripts/build/lang-pin-beta92) so it still fits.
Translations: 39 new keys in 14 languages, language store v24.
Site: theme, dark mode, aurora and guide refresh, SDK send limits, nicknames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib_deps takes RadioLib ^7.6.0, and a fresh fetch now brings 7.8.1. Its
LR11x0::config() wraps driveDiosInSleepMode in its own skip for LR1110
firmware below 0x0306, so the call sits one level deeper and the patch
script no longer found it. It failed closed, stopping every M9 build at
its pre-script.
The script now knows both shapes. The patch still matters on 7.8.1:
firmware 0x0306 and 0x0307 get past RadioLib's own skip, send the
command, answer CMD_PERR and would abort radio init with -706.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yt7fNCKmafm9LtRjyuUmS
The launcher's AppFS bootloader maps only the first 50 x 64 KB of an app
while it checks and starts it (appfsBlMmap in badgeteam/appfs 2.1.0).
beta_91 (3,329,568 B) and the first beta_92 build (3,331,120 B) were past
it, so neither would start on a Tanmatsu.
On the Tanmatsu the launcher updates the app (CAP_OTA 0) and the only OTA
slots are the launcher's own 2 MB ones, so the core's "ota start" upload
page and URL install could never work there. DISABLE_WIFI_OTA stubs them
("ERR: OTA not supported in this build"), and with them go ElegantOTA,
the async web server and AsyncTCP: the app drops to 3,200,864 B, 75,936 B
under the window.
tanmatsu-appstore-publish.sh now refuses a binary over the window, and
its PR text no longer has an em dash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frame rate: full-width PSRAM draw band (6 bands a frame instead of 14 on the
T-Deck), the still aurora drawn once and copied, LVGL at -O2 with its blend and
label loops in IRAM (scripts/build/perf_lvgl.py, 1 KB more loop stack), circle
and gradient caches, newlib memcpy. Rolled out to the M9, Attaky, RAK, Wio,
CrowPanel and V4-R8; the Pager gets the band only, the V4 the caches only.
Frame benchmark tour (DOC_PERF_BENCH) for measuring it.
Fixes: hop counts read from the packed path length (#621); Arabic shaping
rows patched in LVGL (patch_lvgl_arabic.py), Arabic 101 shift layer, kaf,
symbols, text sent as standard letters (#582); info popup colour runs that
never wrap (#602); keypad nav reaches the chat header (#623); the browser
opens the changelog (#622, gen-changelog.py at every site deploy); the
Theme background colour picker no longer edits the accent; flasher entries
for P4 units with factory ESP-AT Wi-Fi chips (#612, #619).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home is a grid of arrangeable widgets (24 kinds, presets, a layout per
orientation, Lua app widgets via wada.widget); Unread, Chat and Mentions
get a large size. Theme > Background puts an aurora (still by default, or
moving), a colour, a picture or nothing behind every screen, with glass
cards and a smoked-glass top bar. A GPS icon in the bar, a GPS battery
saver (u-blox GNSS stop/start where there is no power switch), and a
battery saver that no longer waits for Bluetooth and Wi-Fi to be off.
Also: navigation bar position, every settings page on the redesign's
cards, a faster app drawer, slower screensaver drift for burn-in, text
wrapping at spaces only, the V4-R8 Store Apps tab, and SDK Test marked
extended-SDK only (keeps the 2 MB V4 at 99.8%).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every screen redesigned to the Oct 2026 mockups, on one design system:
palette, type roles (generated Montserrat faces with Lucide line icons on the
LV_SYMBOL codepoints), a theme apply-cb that styles every LVGL widget, button
kinds, list rows, sheets as action lists, toasts and key-glyph avatars.
Rebuilt: status bar, Chats (filters, + and menu), conversation (route header,
hop meta, "Heard by N repeaters", delivery states, pill composer), lock screen
(mark, clock, 24 h activity, unread cards), Constellation screensaver, Home
launcher and launcher sheet, Status page, grouped Settings home, animated boot
splash, and the message glance as a notification card.
New: optional PIN lock (salted SHA-256, keyboard / touch pad / number pad per
board, backoff that survives reboot), screensaver on battery with its own
duration, T-Deck text sizes, trackball hold to switch Scroll and Cursor (Scroll
default, prefs v69/v70), hold Settings tab for the control panel. M9 bottom
notices removed. Every screen measured for the Large and Huge text sizes.
Fix: adverts over 2/3-byte path hashes never reached the recently heard table
(packed path_len compared as a byte count); ADVERT_PATH replies now carry the
whole multi-byte path.
English placeholders for the 204 untranslated keys in every .lang file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The T-Display P4 ships EIGHT images: panel (AMOLED/LCD) x radio (SX1262/LR2021)
x C6 stack (esp-hosted/ESP-AT). The C6 axis is new and it is the dangerous one,
because the two builds are identical apart from the artifact name they carry,
and flashing the wrong one at an older unit used to boot-loop the board.
scripts/build/p4-release.sh builds all eight and VERIFIES each image against the
OTA name baked into it before accepting it. That is the same string the device
builds its self-update URL from, so a disagreement with the filename is exactly
the failure that bricks people -- the T-Deck Max self-updating to Pro firmware,
and this table has been wrong three times. A mismatch aborts the release.
It earned that check immediately: building the eight by hand produced two
mislabelled images, and every mechanism meant to prevent it was itself broken.
* build.sh fullclean has NEVER worked from a plain shell. It ran `cmake -E
remove_directory` above the esp-idf/export.sh that puts cmake on PATH, so it
printed that it had removed the tree, exited 0, and removed nothing. Now a
plain rm -rf, and it verifies the directory is gone instead of assuming.
This matters beyond tidiness: the hosted and ESP-AT builds SHARE a build dir
(the C6 axis does not change P4_BUILD_TARGET), the backend is read from the
environment at CMake CONFIGURE time, and CMake does not reconfigure because
an env var changed -- so a clean that silently did nothing yields an
incremental build of the OTHER variant, with no configure output to show for
it. That is precisely how the two mislabelled images were produced.
* Deleting CMakeCache.txt alone is not a clean either; it leaves CMakeFiles/
behind and the next configure dies in CMakeDetermineCCompiler.
* Detecting failure by grepping a log for "error:" misses a failed CMake
configure, which prints "Configuring incomplete" and no such line -- and the
previous application.bin is still sitting in the directory to be picked up.
Exit codes now.
gen-flasher-meta.py gains the four ESP-AT entries, labelled "older unit, ESP-AT
C6": someone choosing in the web flasher has no serial log and needs to pick on
something they can check.
scripts/run-host-tests.sh runs test/ as one command. Nothing ever did, which is
why test_touch_prefs_schema had been failing to COMPILE since v64 appended
report_ping and report_done_n without updating its sums -- and a static_assert
that does not compile is not a guard. That one exists to catch the mid-struct
insert that broke v44 and shipped the poisoned beta_57 layout. Repaired, with a
note where the next person appending a field will read it. 17 pass, 0 fail, 2
skipped with their reasons stated rather than quietly dropped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things made a full harness run untrustworthy, and together they produced a
false accusation against a community app.
wada.fs was stubs. append threw the data away, remove always said true, and
read, write and list were not there at all — although the firmware registers
all five (LuaAppHost.cpp). Any app that writes a file and reads it back
therefore failed here while working perfectly on device. wardrive 1.2 is
exactly that app: it errors on a nil fs.read at its first log flush, long
before it renders anything, and the emoji scenario then reported the repeater
name as unrendered. Read as "1.2 regressed the beta_70 emoji fix", which it
did not. It passes untouched once fs is real.
wada.fs is now an in-memory filesystem following LuaAppHost's semantics: read
returns the data plus the file's TOTAL size so a caller can window a growing
log, an offset at or past the end gives an empty string rather than nil, a
missing file gives nil, and list is sorted so a hash order cannot make a test
flaky.
The second: the gpscompass scenario list was acting as the dispatch default,
so an app with no branch of its own was run against tests written for a
different app. Six store apps have no branch (2048, airtime, monitor, nearby,
sdktest, snake), so a full sweep came back with six red apps as its normal
state — and a real regression would not have stood out against that. Those
apps now say they have no scenarios and run none. They are untested, which is
worth saying out loud, but that is not the same as broken.
A sweep of every store app is now 8 apps passing their own scenarios, 6
reported untested, 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stacked on #585 and merged after it, as the PR asks. Two conflicts, both from
the stack base moving:
apps.json: unioned by id, so all fourteen apps survive.
harness.lua: the additive blocks came from #586. Where both sides edited the
same code, #585's is newer and wins: its ping scenarios pass cfg.gps as a
FUNCTION, which is what the harness calls (cfg.gps()); #586 still passed the
table and would have failed. Taking #586 there would have broken ping.
Then restored the two dispatch branches my first resolution dropped. The
breakout and tripodometer scenarios existed but nothing ran them, so both
apps silently fell through to the gpscompass scenarios and "failed".
All five new apps pass their own scenarios, and so do gpscompass and sdscan.
- New crowpanel_35 variant: display, SD, board and target files, plus board JSON
- Portrait mode is drawn rotated 180°; landscape stays at rotation 3
- Share the GT911 touch driver between Wio Tracker L2 and CrowPanel
(WioTrackerL2Touch.cpp -> src/helpers/input/LgfxGt911Touch.cpp)
- platformio envs, flasher/site entries, release and merge-bin support
- Tests for the CrowPanel SD codec and merge-bin
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132MkJ7RUV32mV9F7o4PJMm
Sun/Moon rise+set from GPS lat/lon + date (Spencer 1971), moon phase 0-29.
Chronometer with lap times, countdown timer with alarm beep and preset
cycling, altimeter with GPS alt_m, min/max persistence and trend graph.
Swipe left/right to switch tabs. 4 tabs fit any screen size.
test(harness): add 7 ProTreck scenarios (tdeck, v4, no_gps, chrono,
timer, alti, cost). ALL OK 7/7.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Send a DM to any contact, measure round-trip time. Both devices
exchange their GPS coordinates; the result shows RTT, distance and
cardinal bearing when both have a fix. The app also auto-replies to
incoming pings so two devices running Ping can time each other.
Harness additions: send_dm mock, wada.geo (haversine), deliver_dm
helper, and 6 scenarios (tdeck, v4, no_contacts, round_trip,
auto_reply, cost) — all pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tracks total distance (Haversine accumulation), elapsed time, current
speed, max speed, and altitude from live GPS fixes. Start/Stop button
pauses accumulation; Reset clears the trip. The current trip persists
across app restarts via wada.store.
Also adds 6 harness scenarios (no-fix, T-Deck, moving, persist, reset,
cost) and registers the app in apps.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ball + paddle + 8×5 brick grid. Swipe left/right (or arrow keys) to
move the paddle; swipe up / Enter / Space to launch. Angle on bounce
varies by paddle-hit position. Speed increases each level; high score
persists via wada.store.
Adds 6 harness scenarios (tdeck, v4, keyboard, logic, hiscore, cost).
Worst tick ~8k instructions, 8% of the 100k budget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects /tetris/ in the app path and routes to 6 dedicated scenarios:
- tetris_tdeck/v4: open + swipe input on touch screens
- tetris_keyboard: arrow + enter input on keyboard boards
- tetris_logic: hard-drop to game-over, verify restart resets timer to 700 ms
- tetris_hiscore: verify high score is stored as a number after game-over
- tetris_cost: 100 ticks, worst ~6000 instructions (budget 100000)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Current T-Display P4 V1 units ship the C6 with esp-hosted firmware
(2.12.3), and main's P4 build only spoke ESP-AT, so Wi-Fi could not come
up on them at all. This is a one-time import of the esp-hosted backend
- TDP4_C6_HOSTED build switch (default hosted; WADA_P4_LEGACY_AT=1 keeps
the ESP-AT path for older units), with the ESP-AT socket/WiFi facades
and the single-listener TCP router guarded to the legacy build.
- tdisplay_p4: build.sh hosted patches and fullclean handling,
idf_component.yml esp-hosted pin, sdkconfig, main.cpp hosted bring-up
and SNTP, c6_at updates, and the C6 reset hook in target.cpp.
- P4 Wi-Fi scan wait in UITask.
- TDISPLAY_P4_C6_FIRMWARE_PLAN.md.
Not imported from that commit: UI-scale default, GPS log lines, UI
build-upload-monitor.sh: run build.sh's own fullclean for the P4 (idf.py
fullclean rejects the patched managed components).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build-upload-monitor.sh gains --tdisplay-p4 (AMOLED) and
--tdisplay-p4-lcd (HI8561 LCD SKU). The P4 is an ESP-IDF app, so these
drive tdisplay_p4/build.sh (build/flash/monitor, --erase, --fullclean,
PORT= for the serial port) instead of pio; both appear in the interactive
menu and the --just-build sweep, which skips them when ESP-IDF is absent.
PlatformIO is only required when a PlatformIO board is chosen.
build-all-targets.sh also builds the LCD SKU. Both SKUs share
build/tdisplay_p4 and WADA_P4_LCD is read only at configure time, so each
P4 build reconfigures first; before this, the AMOLED entry rebuilt
whichever panel had been configured last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The matrix was only reachable from a corner button and a line buried in the
channel explainer. It now has a card in "Docs, tools and help", next to the
others, which is where people actually look.
The tracking issue is dropped: the matrix lives on the site and no write
credential for it exists anywhere. The publishing code stays in place and
dormant, so it is one line in /etc/wadamesh-reports.env if that changes, and
the service and deploy script now say publishes:false is the intended state
rather than a missing piece.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Settings, About gains "Report this build": works or a problem, five tick
boxes for what you actually exercised, how long you have run it, an optional
line. It posts to the report service and lands on wadamesh.com/beta, which is
what the next promote is decided on. Ticking matters: "it works" from somebody
who watched it boot and "it works" from somebody who messaged on it all week
are not the same claim, and a board only goes green on two of the second kind.
"Report a bug" draws a QR for a prefilled GitHub issue form. The issue is filed
from the reporter's phone under their own account, so no write token has to
exist in a public firmware image and the reporter gets the replies.
"Count this device" is off by default. It lets the update check say which board
and version it is running, so a board with no reports can be told apart from a
board nobody owns. Those need opposite responses and nothing could tell them
apart before.
Mechanics: the POST rides the existing core-0 tile/update worker and its
WiFiClient/HTTPClient, like the version check, because a second pair on that
~8 KB stack overflows it. The device id is a salted SHA-256 of the public key
truncated to 64 bits, so a second report replaces the first without saying who
sent it. Sending is two taps and the second lists every field that leaves.
Prefs v64 appends report_ping + report_done_n at the tail, which the schema's
trailing static_assert now checks.
Also here: scripts/build/matrix-check.sh prints what testers reported for a tag,
and release.sh runs it before a --promote. It never blocks; a board nobody owns
can never go green. With no reports at all it now says so rather than reporting
a clean bill of health, which is the habit this is meant to replace.
Built on all eleven S3 envs and the T-Display P4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A beta goes stable today because it has been out a while and nobody shouted.
With 13 board images, most of them in hands we never hear from, "nobody
shouted" mostly means nobody with that board was listening. This makes the
evidence explicit: testers report per board per tag from the device, and the
promote decision reads off a grid.
The device cannot talk to GitHub itself. TLS does not fit (mbedTLS wants ~30 KB
of heap for a handshake and ~5 KB survives Wi-Fi association on the 2 MB
boards), and a token in a GPL image is a token everybody has. So the split is:
* Structured test reports go to a small service on the firmware VPS over the
plain HTTP the device already speaks, and it holds the only write
credential. SQLite is the database; the per-tag tracking issue is rendered
from it and rewritten in place, so GitHub can be down or rebuilt at will.
* Bug reports do not come through here at all. The device draws a QR that
opens .github/ISSUE_TEMPLATE/bug.yml prefilled with board, version and
diagnostics, and the reporter files it under their own GitHub account:
right attribution, GitHub's own spam controls, no token.
Green needs two separate devices whose owners ran the build for a day or more.
A board nobody runs cannot go green and does not block a promote; a board
somebody runs and has not vouched for does. That rule is the whole point: it
distinguishes untested from unowned, which "nobody shouted" cannot.
wadamesh.com/beta.html renders the same JSON for the promote decision.
Two things found while deploying, both repo-vs-box drift that would have bitten
somebody eventually:
* The repo's copy of the firmware vhost was missing /apps/ and /bringup/,
which had been added on the box and never committed. Deploying it would
have 404'd the Lua app and language store for every device. The repo copy
is now taken from the box, and the deploy script refuses to push a vhost
that has fewer locations than the live one.
* The repo's tile-transcode unit named a venv interpreter that does not exist
on the box (python3.14 there has no ensurepip, so venv cannot bootstrap pip
at all). The live unit uses the system python; the repo now says so too.
The firmware side (the report page, the QR, the opt-in ping) is not in this
commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A board added mid-cycle (the T-Deck Max) has no images in an older tag, so
promoting that tag copied a file that does not exist and, with set -e, took
the whole promotion down. It is skipped with a note now, and the flasher
manifest generator leaves out any board with no image in the channel it is
writing, rather than handing the flasher a 404.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of #531 turned up four things that had to be fixed before release:
- An unusable region name no longer discards the radio settings. The save
returned early, so frequency, bandwidth, SF, CR and TX power were thrown
away, and the rule rejects names with capitals or a "$private" scope, so
anyone carrying an older region could not change their radio at all. On the
pager that path is the silent blur save, so it failed with nothing on screen.
Now only the region is skipped, and it says so.
- Restored useChainedFont() on the telemetry Show button: without the fallback
chain that label is boxes in Russian, Ukrainian, Bulgarian, Serbian, Greek.
- Region scanning installs unknown repeaters as transient contacts so their
encrypted replies can be matched. They were never removed, so each scan left
up to 16 nameless entries in the contact table (and in Contacts). They are
dropped when the scan ends or the page closes.
- One radio request per scan tick. The loop ran through all 16 repeaters in a
single 200 ms tick, which is that many key derivations back to back.
The V4-R8 also did not build (its src/ files see a vendored lv_conf.h, so the
new text-size fonts were missing) and then did not fit at 101.3%. It builds at
85.6% now: the board no longer carries the compiled-in translations it
inherited from the V4, which it never needed, since with 8 MB of PSRAM its
store works like any other 8 MB board.
T-Deck Max (#545) joins the release matrix: build list, flasher manifest,
DEVICES.md and the site's board list. Board names in the flasher lost their
em dashes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
M9 Back and Bluetooth-keyboard Esc stop at the locked app-drawer root, M9
focus fixes, optional M5Stack CardKB on the V4 and V4-R8, V4-R8 touch and
text-size accessibility, Wio L2 SD retry, high-contrast day and night themes
(#544), channel region discovery (#541) and the Czech translation
contributed in #540 by brebtatv.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A built-in USB Files app (T-Deck and Heltec V4 for now) lets the page at
files.wadamesh.com browse, upload, download, rename and delete files on the
SD card, internal storage and the map tiles over Web Serial. File level, not
USB mass storage: the device keeps its filesystems mounted, the radio keeps
running, and the firmware enforces what may change. Live data (identity,
contacts, settings, history; /meshcomod on the card) is download-only.
- UsbFilesProtocol.h: framed messages (E7 5A, CRC32), a resyncing parser,
path checks and the access policy; host tests in test/.
- UsbFilesSession: one request at a time (both Arduino USB serial drivers
drop bytes when their RX queue is full), RX queue sized per session, SD
data through a DMA-capable bounce buffer, FatFs listings with sizes, a
beacon so the page never talks first, SD Scan's malware verdict per file.
- The companion link and MyMesh's console step off USB while it runs.
- Website: labelled side buttons, a USB Files button and card; the
files.wadamesh.com page, vhost and deploy script.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An infected M9 card turned up with 373 findings. Every list row is a focus
stop on a keyboard board, and the firmware's key navigation collects at most
160 of them per screen, so in 1.0 the Remove all and Scan again buttons below
the list could not be reached on an M9 at all.
1.1 puts the buttons above the list, first in focus order, on the results
and failed-removal screens, and shows at most 60 rows plus an "and N more"
row (the title carries the full count). App-only: no firmware change.
Harness: a 373-finding scenario, and a check on every list screen that the
buttons come first and the focus stops stay under 160. It fails on 1.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Some ThinkNode M9 cards shipped with a dormant Windows worm (Elecrow
security advisory, September 2026). An infected card seen since carries
autorun.inf in the root, launching xlfqf.pif on open, explore and autoplay
with random-junk comment lines in between: the Sality autorun pattern.
- SD Scan store app (deploy/apps/sdscan/1.0, requires "sd", not seeded):
walks the card a small page per tick, lists what it finds and why, and
removes it after a confirmation screen with Cancel first. It says on
every screen that it only removes files it recognises and that
formatting the card is the safe fix. On older firmware it still finds
threats by name but cannot remove them.
- Firmware: wada.sd.check(path) and wada.sd.remove(path), plus paging for
wada.sd.list(path, start, max) and caps().sd_clean. What counts as a
threat lives in SdThreat.h: autorun.inf, Windows program, script and
shortcut extensions, or a real MZ+PE header under any name. remove()
classifies again in firmware and refuses anything else, so no app can
use it to delete tiles, backups or chat history. It clears read-only,
hidden and system first, because FAT refuses to delete a read-only file.
- A warning when a card with Windows malware in its top folder is mounted,
at boot or on insert, offering SD Scan (or the Store).
- Tests: test/test_sd_threat.cpp, and SD Scan harness scenarios including
the real infected card's root. Removal checked on a T-Deck.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pressing down past the last message page-scrolls the chat list with an
LVGL animation. In a long chat the list uses compressed scroll
coordinates, and chatVirtRemap1To1Scroll re-anchored the position on
every LV_EVENT_SCROLL with lv_obj_scroll_to_y(LV_ANIM_OFF). That deletes
the running animation from inside its own step. LVGL 8.4 reads the
animation again after the step, and when the step was also its last
(the UI loop was busy for longer than the animation) it finished the
freed animation and freed it a second time. By the next round that
memory belonged to something else, and anim_timer called a garbage
get_value_cb: the jump to 0x00020000 in the beta_79 and beta_80 M9 dumps,
both at the same call site.
- chatVirtRemap1To1Scroll follows a scroll that an animation drives and
leaves the re-anchor to chatVirtOnScrollEnd. The page scroll now also
completes on long chats; the early re-anchor stopped it after a frame.
- scripts/build/patch_lvgl_anim_uaf.py skips LVGL's completion check when
the animation list changed during the step, as LVGL 9 does. Applied to
every touch env as a pre-script and to the vendored P4 copy
(fetch-deps.sh, build.sh). Idempotent, fails closed on source drift.
- test/lvgl_anim_uaf/run.sh reproduces it on the host under
AddressSanitizer: stock LVGL with the old handler reports the
use-after-free in anim_timer, and either fix alone runs clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>