touch: drop the V4 memory/store diagnostics + fix a swallowed hex escape

The RAM investigation and the language-download debugging left Serial probes
across three files: the BOOTMEM/MESHMEM/MEMPROBE macros and their 48 call
sites, plus the [STORE]/[APPCAT]/[LANGDL]/[LANGSEL] traces. They have served
their purpose and were flooding the companion USB-CDC, so they all go. Two
locals in the .lang installer (wr, wr3) existed only to feed a trace and go
with it; the control-write repair path that actually rebuilds /lang stays.

Separately, the map tile-cache help text read "256\xC3\x97256": the 256 after
the multiplication sign is all hex digits, so the compiler folded it into the
escape and emitted one out-of-range byte instead of "256x256". Breaking the
literal after the escape terminates it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kaj Schittecat
2026-08-04 22:43:49 +02:00
co-authored by Claude Opus 4.8
parent f3d238c47a
commit e0663eb9ee
3 changed files with 7 additions and 87 deletions
-12
View File
@@ -3257,15 +3257,8 @@ void MyMesh::popChannelScope() {
_chan_scope_pushed = false;
}
#define MESHMEM(tag) do { \
multi_heap_info_t _h{}; heap_caps_get_info(&_h, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); \
Serial.printf("[MESHMEM] %-12s free=%6u psram=%7u\n", tag, \
(unsigned)_h.total_free_bytes, (unsigned)ESP.getFreePsram()); Serial.flush(); } while (0)
void MyMesh::begin(bool has_display) {
MESHMEM("enter");
BaseChatMesh::begin();
MESHMEM("post-base");
if (!_store->loadMainIdentity(self_id)) {
self_id = radio_new_identity(); // create new random identity
@@ -3288,9 +3281,7 @@ void MyMesh::begin(bool has_display) {
#endif
// load persisted prefs
MESHMEM("pre-prefs");
_store->loadPrefs(_prefs, sensors.node_lat, sensors.node_lon);
MESHMEM("post-prefs");
// sanitise bad pref values
_prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f);
@@ -3332,13 +3323,10 @@ void MyMesh::begin(bool has_display) {
#endif
resetContacts();
MESHMEM("pre-contacts");
_store->loadContacts(this);
MESHMEM("post-contacts");
bootstrapRTCfromContacts();
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
_store->loadChannels(this);
MESHMEM("post-channels");
applyRadioFromPrefs(); // freq/bw/sf/cr + TX power + RX-boost (shared with the live UI apply)
#if defined(DISPLAY_CLASS)
-35
View File
@@ -276,15 +276,6 @@ void meshcomodClearSdMigLatch() {
#endif
// Boot memory trace: 175 KB of internal RAM is gone before the UI starts, and
// nothing said where. Print free/largest at each init step so the consumer
// names itself instead of being guessed at.
#define BOOTMEM(tag) do { \
multi_heap_info_t _h{}; heap_caps_get_info(&_h, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); \
Serial.printf("[BOOTMEM] %-12s free=%6u largest=%6u psram=%7u\n", tag, \
(unsigned)_h.total_free_bytes, (unsigned)_h.largest_free_block, \
(unsigned)ESP.getFreePsram()); Serial.flush(); } while (0)
void setup() {
Serial.begin(115200);
#if defined(HAS_RAK_TAP_V2)
@@ -293,7 +284,6 @@ void setup() {
delay(200);
#endif
Serial.println("[BOOT] setup start");
BOOTMEM("setup-start");
// The SDK ships CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096: every allocation
// under 4 KB is forced into internal DRAM even when PSRAM is free. On the V4
// that is why internal sat at ~99% while 800+ KB of PSRAM went unused. Lower
@@ -301,7 +291,6 @@ void setup() {
// genuinely needs internal/DMA memory asks for it by capability and is
// unaffected. Tunable — raise it if something misbehaves.
heap_caps_malloc_extmem_enable(256);
BOOTMEM("post-extmem");
// Widen the task-watchdog grace period. The ~5 s default trips during a legitimate-but-slow flash
// burst — a SPIFFS garbage-collect, or a bulk save (DataStore issues ~12 flash ops per contact,
@@ -344,7 +333,6 @@ void setup() {
board.begin();
Serial.println("[BOOT] board ok");
BOOTMEM("post-board");
#if defined(HAS_RAK_TAP_V2)
// Quick PSRAM sanity check — silent crash before SPIFFS could be bad PSRAM config
@@ -392,9 +380,7 @@ void setup() {
}
#endif
BOOTMEM("pre-radio");
if (!radio_init()) { halt(); }
BOOTMEM("post-radio");
Serial.println("[BOOT] radio ok");
fast_rng.begin(radio_driver.getRngSeed());
@@ -440,10 +426,7 @@ void setup() {
ExtraFS.begin();
#endif
#endif
BOOTMEM("pre-store");
store.begin();
BOOTMEM("post-store");
BOOTMEM("pre-mesh");
the_mesh.begin(
#ifdef DISPLAY_CLASS
disp != NULL
@@ -457,15 +440,10 @@ void setup() {
#else
serial_interface.begin(Serial);
#endif
BOOTMEM("pre-iface");
the_mesh.startInterface(serial_interface);
BOOTMEM("post-iface");
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
BOOTMEM("pre-store");
store.begin();
BOOTMEM("post-store");
BOOTMEM("pre-mesh");
the_mesh.begin(
#ifdef DISPLAY_CLASS
disp != NULL
@@ -488,18 +466,14 @@ void setup() {
#else
serial_interface.begin(Serial);
#endif
BOOTMEM("pre-iface");
the_mesh.startInterface(serial_interface);
BOOTMEM("post-iface");
#elif defined(ESP32)
// Storage selection. SPIFFS by default; use the SD card under /meshcomod when
// SPIFFS is unavailable (e.g. installed under Launcher) OR the user opted in
// ("Store data on SD"). The SD shares the LoRa SPI bus, already brought up by
// radio_init() above, so SD.begin's spi.begin is a no-op. Graceful: any SD
// failure falls back to SPIFFS so the device always boots.
BOOTMEM("pre-spiffs");
bool spiffs_ok = SPIFFS.begin(false); // try first WITHOUT auto-format
BOOTMEM("post-spiffs");
bool sd_storage = false;
#if defined(HAS_TDECK_GT911) || defined(HELTEC_LORA_V4_R8) || defined(TLORA_PAGER)
{
@@ -668,14 +642,11 @@ void setup() {
Serial.println("[BOOT] touchPrefsReload ok"); Serial.flush();
#endif
#endif
BOOTMEM("pre-store");
store.begin();
BOOTMEM("post-store");
#if defined(HAS_RAK_TAP_V2)
Serial.println("[BOOT] store ok"); Serial.flush();
Serial.println("[BOOT] calling mesh.begin..."); Serial.flush();
#endif
BOOTMEM("pre-mesh");
the_mesh.begin(
#ifdef DISPLAY_CLASS
disp != NULL
@@ -697,17 +668,13 @@ void setup() {
#endif
#if defined(WIFI_SSID) || defined(MULTI_TRANSPORT_COMPANION)
BOOTMEM("pre-wifi-cfg");
wifiConfigBegin();
BOOTMEM("post-wifi-cfg");
Serial.println("[BOOT] wifiConfig ok");
#endif
#ifdef MULTI_TRANSPORT_COMPANION
board.setInhibitSleep(true);
BOOTMEM("pre-iface-srv");
serial_interface.begin(Serial, TCP_PORT, WS_PORT);
BOOTMEM("post-iface-srv");
Serial.println("[BOOT] serial_interface ok");
serial_interface.setBroadcastResponses(true); // RX log, channel messages, etc. go to all clients (USB + TCP + WS [+ BLE]), not only last sender
/* Pick BLE vs WiFi at boot. The ESP32-S3 doesn't have enough internal heap
@@ -794,9 +761,7 @@ void setup() {
#else
serial_interface.begin(Serial);
#endif
BOOTMEM("pre-iface");
the_mesh.startInterface(serial_interface);
BOOTMEM("post-iface");
#else
#error "need to define filesystem"
#endif
+7 -40
View File
@@ -1330,12 +1330,6 @@ static void statusBarSetTall(bool tall) {
void reserveTileFetchStack(); // fwd: claim the worker stack before Wi-Fi eats the heap
#define MEMPROBE(tag) do { \
multi_heap_info_t _hi{}; heap_caps_get_info(&_hi, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); \
Serial.printf("[MEM] %-14s free=%6u largest=%6u psram=%7u\n", tag, \
(unsigned)_hi.total_free_bytes, (unsigned)_hi.largest_free_block, \
(unsigned)ESP.getFreePsram()); } while (0)
// ---- AppPage: the shared full-screen app-page chrome (see AppPage.h) --------------
// Thin wrappers over the machinery just above, exported so the self-contained app
// modules (SnakeGame) build the same page as the in-file tool
@@ -24361,9 +24355,8 @@ static void luaStoreFetchCatalogWorker(WiFiClient& client, HTTPClient& http) {
s_luacat_buf = (char*)heap_caps_malloc(kLuaCatBufMax, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!s_luacat_buf) { return; }
s_luacat_buf[0] = 0;
int cn = luaStoreHttpGet(client, http, "http://firmware.wadamesh.com/apps/apps.json",
s_luacat_buf, kLuaCatBufMax);
Serial.printf("[APPCAT] n=%d wifi=%d\n", cn, (int)WiFi.status());
luaStoreHttpGet(client, http, "http://firmware.wadamesh.com/apps/apps.json",
s_luacat_buf, kLuaCatBufMax);
}
// Download <id>.lua + <id>.json (immutable per-version path) into /apps/.
@@ -24483,7 +24476,6 @@ static bool luaStoreLangDownloadWorker(WiFiClient& client, HTTPClient& http,
snprintf(url, sizeof url, "http://firmware.wadamesh.com/apps/lang/%s.lang", code);
int n = luaStoreHttpGet(client, http, url, buf, kCap);
bool ok = false;
size_t wr = 0;
bool opened = false;
if (n > 16) {
// Write to a TEMP file, verify every byte landed, then rename over the real
@@ -24499,7 +24491,7 @@ static bool luaStoreLangDownloadWorker(WiFiClient& client, HTTPClient& http,
File f = fs->open(tmp, "w");
opened = (bool)f;
if (f) {
wr = langWriteAll(f, (const uint8_t*)buf, (size_t)n);
langWriteAll(f, (const uint8_t*)buf, (size_t)n);
f.close();
ok = langVerifyInstall(fs, tmp, path, (size_t)n);
}
@@ -24515,7 +24507,6 @@ static bool luaStoreLangDownloadWorker(WiFiClient& client, HTTPClient& http,
File cf = fs->open(ctl, "w");
if (cf) { wr2 = langWriteAll(cf, (const uint8_t*)buf, (size_t)n); cf.close(); }
fs->remove(ctl);
size_t wr3 = 0;
if (wr2 == (size_t)n) {
// Healthy elsewhere -> nuke and recreate the /lang directory.
char ldir[48];
@@ -24536,17 +24527,12 @@ static bool luaStoreLangDownloadWorker(WiFiClient& client, HTTPClient& http,
fs->rmdir(ldir);
fs->mkdir(ldir);
File rf = fs->open(tmp, "w");
if (rf) { wr3 = langWriteAll(rf, (const uint8_t*)buf, (size_t)n); rf.close(); }
if (rf) { langWriteAll(rf, (const uint8_t*)buf, (size_t)n); rf.close(); }
ok = langVerifyInstall(fs, tmp, path, (size_t)n);
if (!ok) fs->remove(tmp);
}
Serial.printf("[LANGDL] repair ctl=%u relang=%u ok=%d\n", (unsigned)wr2, (unsigned)wr3, (int)ok);
}
}
char root[16];
luaHostAppPath(root, sizeof root, ""); // "" = internal flash, "/meshcomod" = SD
Serial.printf("[LANGDL] %s n=%d root=%s open=%d wr=%u ok=%d\n",
url, n, root[0] ? root : "(internal)", (int)opened, (unsigned)wr, (int)ok);
heap_caps_free(buf);
return ok;
}
@@ -24910,7 +24896,6 @@ static void tileFetchTaskFn(void* arg) {
// Lua app store: catalog fetch + app install (LUA_APPS.md Phase 2).
if (s_luacat_request) {
s_luacat_request = false;
Serial.println("[APPCAT] worker picked up the request");
luaStoreFetchCatalogWorker(client, http);
s_luacat_done = true;
continue;
@@ -26904,7 +26889,9 @@ static void mapOptInfoCb(lv_event_t* e) {
if (!credits) return; // OOM only: skip the credits text
snprintf(credits, CREDITS_SZ, "%s%s", attrib,
"How tiles work:\n"
"The map is built from 256\xC3\x97256 \"slippy\" tiles. Only the tiles for the "
// NOTE: the literal break after \x97 is REQUIRED — "\xC3\x97256" would parse the
// trailing 256 as part of the hex escape (all hex digits) and emit one garbage byte.
"The map is built from 256\xC3\x97" "256 \"slippy\" tiles. Only the tiles for the "
"area you're viewing are fetched \xE2\x80\x94 there is no bulk pre-download.\n\n"
"Because this device can't do HTTPS (not enough heap after Wi-Fi starts) "
"and decodes JPEG far more cheaply than PNG, tiles come from the wadamesh "
@@ -37907,13 +37894,7 @@ static void openLuaStorePage() {
s_luacat_done = false;
s_luacat_request = true;
}
MEMPROBE("store-open");
const bool worker_up = ensureTileFetchTaskRunning();
Serial.printf("[STORE] open: worker=%d spawn_ok=%d wifi=%d free_int=%u largest=%u free_psram=%u\n",
(int)worker_up, (int)s_tile_fetch_spawn_ok, (int)WiFi.status(),
(unsigned)ESP.getFreeHeap(),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT),
(unsigned)ESP.getFreePsram());
// The net worker needs an 8 KB contiguous internal block. When memory is too
// tight to start it, nothing will ever service the request — so fail visibly
// instead of spinning on "Loading catalog..." forever.
@@ -42059,18 +42040,13 @@ static void buildUiTree() {
}
// ---- Build tab contents ----
MEMPROBE("pre-home");
makeHome(tab_home);
MEMPROBE("tab-home");
g_lv.dm.channel_mode = false;
g_lv.ch.channel_mode = true;
makeChatList(tab_chats, g_lv.dm, false, true);
MEMPROBE("tab-chats");
makeContactsTab(tab_contacts);
MEMPROBE("tab-contacts");
makeMapTab(tab_map);
MEMPROBE("tab-map");
#if defined(HAS_EXPANSION_KIT)
if (tab_sensors) makeSensorsTab(tab_sensors);
#endif
@@ -42136,13 +42112,8 @@ static void buildUiTree() {
// Create full-screen detail overlays (hidden until a thread is tapped)
makeChatDetail(g_lv.dm);
makeChatDetail(g_lv.ch);
MEMPROBE("tab-chatdetail");
MEMPROBE("pre-settings");
MEMPROBE("pre-settings");
makeSettings(tab_settings);
MEMPROBE("tab-settings");
MEMPROBE("post-UI-build");
#if defined(HAS_TANMATSU)
navBuildTabKeyHints(); // coloured △□○♣◇ shapes on the tab bar = the physical F-keys
@@ -45997,8 +45968,6 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
// largest block, so the worker could never start and tiles, the update
// check and the app catalog were all silently dead.
reserveTileFetchStack();
MEMPROBE("post-reserve");
MEMPROBE("pre-lv_init");
lv_init();
initTouchFontFallbacks();
#if CAP_ROUND_CORNERS
@@ -46236,9 +46205,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
// when the base orientation is portrait. In hardware landscape the LVGL
// rotation is left at NONE so it never software-rotates on top of the panel.
g_lv.disp_drv.sw_rotate = 1;
MEMPROBE("pre-disp_reg");
lv_disp_drv_register(&g_lv.disp_drv);
MEMPROBE("post-disp_reg");
#if defined(HAS_TANMATSU)
// Apply the 270° software rotation now that the driver is registered: logical surface
// becomes 800x480 and lv_disp_get_hor/ver_res report landscape for every layout query.