v4: reserve the net worker's stack in begin() + boot memory probes
The worker's 8 KB internal stack was only ever claimed lazily, the first time the map or store asked for it — by which point the Wi-Fi driver has taken the internal heap. Its own comment always said 'allocated at UITask::begin() time' and explained why (grab it before Wi-Fi associates, then it stays ours); that call site never existed. Measured on the V4: 68 KB free / 59 KB largest block at end of boot, versus 664 bytes free and a 4-byte largest block once Wi-Fi was up — so tiles, the update check and the app catalog were all dead there, not just the store. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
edb5b1dbb5
commit
fbcd524a65
2 files changed
+96
-12
No files matched your search
@@ -275,6 +275,16 @@ 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)
|
||||
@@ -283,6 +293,7 @@ void setup() {
|
||||
delay(200);
|
||||
#endif
|
||||
Serial.println("[BOOT] setup start");
|
||||
BOOTMEM("setup-start");
|
||||
|
||||
// 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,
|
||||
@@ -325,6 +336,7 @@ 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
|
||||
@@ -372,7 +384,9 @@ void setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOTMEM("pre-radio");
|
||||
if (!radio_init()) { halt(); }
|
||||
BOOTMEM("post-radio");
|
||||
Serial.println("[BOOT] radio ok");
|
||||
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
@@ -405,6 +419,7 @@ void setup() {
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
BOOTMEM("pre-fs");
|
||||
InternalFS.begin();
|
||||
#if defined(QSPIFLASH)
|
||||
if (!QSPIFlash.begin()) {
|
||||
@@ -418,7 +433,9 @@ void setup() {
|
||||
ExtraFS.begin();
|
||||
#endif
|
||||
#endif
|
||||
BOOTMEM("pre-store");
|
||||
store.begin();
|
||||
BOOTMEM("pre-mesh");
|
||||
the_mesh.begin(
|
||||
#ifdef DISPLAY_CLASS
|
||||
disp != NULL
|
||||
@@ -432,9 +449,13 @@ void setup() {
|
||||
#else
|
||||
serial_interface.begin(Serial);
|
||||
#endif
|
||||
BOOTMEM("pre-iface");
|
||||
the_mesh.startInterface(serial_interface);
|
||||
BOOTMEM("post-iface");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
BOOTMEM("pre-littlefs");
|
||||
LittleFS.begin();
|
||||
BOOTMEM("post-littlefs");
|
||||
store.begin();
|
||||
the_mesh.begin(
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
+75
-12
@@ -1328,6 +1328,14 @@ static void statusBarSetTall(bool tall) {
|
||||
// NOT be called from here — it calls back into statusBarSetTall = recursion.)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -24340,8 +24348,9 @@ 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;
|
||||
luaStoreHttpGet(client, http, "http://firmware.wadamesh.com/apps/apps.json",
|
||||
s_luacat_buf, kLuaCatBufMax);
|
||||
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());
|
||||
}
|
||||
|
||||
// Download <id>.lua + <id>.json (immutable per-version path) into /apps/.
|
||||
@@ -24400,8 +24409,9 @@ static void luaStoreFetchLangCatalogWorker(WiFiClient& client, HTTPClient& http)
|
||||
s_langcat_buf = (char*)heap_caps_malloc(kCap, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (!s_langcat_buf) return;
|
||||
s_langcat_buf[0] = 0;
|
||||
luaStoreHttpGet(client, http, "http://firmware.wadamesh.com/apps/langs.json",
|
||||
s_langcat_buf, kCap);
|
||||
int ln = luaStoreHttpGet(client, http, "http://firmware.wadamesh.com/apps/langs.json",
|
||||
s_langcat_buf, kCap);
|
||||
Serial.printf("[LANGCAT] n=%d\n", ln);
|
||||
}
|
||||
|
||||
// Chunked writer through an internal bounce buffer, tolerant of transient
|
||||
@@ -24886,6 +24896,7 @@ 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;
|
||||
@@ -37588,7 +37599,9 @@ static void luaStoreRebuildList() {
|
||||
if (s_lua_cat_n <= 0) {
|
||||
lv_obj_t* card = lv_obj_create(s_luastore_list);
|
||||
lv_obj_remove_style_all(card);
|
||||
lv_obj_set_size(card, W - 8, lh14 + lh12 + 22);
|
||||
// Height from the wrapped hint, not a one-line guess — on a 240 px panel
|
||||
// the second/third line used to be cut off by the card's own bottom edge.
|
||||
lv_obj_set_size(card, W - 8, lh14 + lh12 * (narrow ? 3 : 2) + 22);
|
||||
lv_obj_set_style_bg_color(card, lv_color_hex(COLOR_PANEL), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_opa(card, LV_OPA_COVER, LV_PART_MAIN);
|
||||
lv_obj_set_style_radius(card, 10, LV_PART_MAIN);
|
||||
@@ -37600,9 +37613,22 @@ static void luaStoreRebuildList() {
|
||||
lv_obj_set_style_text_color(t, lv_color_hex(s_lua_cat_n < 0 ? 0xE08080 : COLOR_TEXT), LV_PART_MAIN);
|
||||
lv_obj_set_pos(t, 0, 0);
|
||||
lv_obj_t* h = lv_label_create(card);
|
||||
lv_label_set_text(h, s_lua_cat_n < 0
|
||||
? TR("Turn Wi-Fi on, then tap refresh. Apps already installed still work.")
|
||||
: TR("Fetching the app list from firmware.wadamesh.com"));
|
||||
if (s_lua_cat_n < 0 && s_tile_fetch_task == nullptr) {
|
||||
// The network worker could not start: report the numbers that decide it
|
||||
// (it needs one contiguous 8 KB internal block) right on screen, so this
|
||||
// is diagnosable from the device instead of a serial cable.
|
||||
char mem[128];
|
||||
snprintf(mem, sizeof mem,
|
||||
"%s\nfree %u KB, largest block %u KB (needs 8 KB)",
|
||||
TR("Not enough free memory to fetch it. Reboot and try again."),
|
||||
(unsigned)(ESP.getFreeHeap() / 1024),
|
||||
(unsigned)(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) / 1024));
|
||||
lv_label_set_text(h, mem);
|
||||
} else {
|
||||
lv_label_set_text(h, s_lua_cat_n < 0
|
||||
? TR("Turn Wi-Fi on, then tap refresh. Apps already installed still work.")
|
||||
: TR("Fetching the app list from firmware.wadamesh.com"));
|
||||
}
|
||||
lv_obj_set_style_text_font(h, &g_font_12, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_color(h, lv_color_hex(COLOR_SUB), LV_PART_MAIN);
|
||||
lv_obj_set_width(h, W - 26);
|
||||
@@ -37781,7 +37807,7 @@ static void openLuaStorePage() {
|
||||
lv_obj_set_flex_flow(s_luastore_list, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(s_luastore_list, 6, LV_PART_MAIN);
|
||||
|
||||
appPageBeginSlim(TR("Lua Store"), &closeLuaStorePage); // one-line bar; the tab bar is the page header
|
||||
appPageBeginSlim(TR("Store"), &closeLuaStorePage); // one-line bar; the tab bar is the page header
|
||||
|
||||
// Open instantly from the cached install list; the card re-listing (sideload
|
||||
// pickup) runs on the worker and the poll timer repaints when it lands.
|
||||
@@ -37791,7 +37817,22 @@ static void openLuaStorePage() {
|
||||
s_luacat_done = false;
|
||||
s_luacat_request = true;
|
||||
}
|
||||
ensureTileFetchTaskRunning();
|
||||
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.
|
||||
if (!worker_up) {
|
||||
s_lua_cat_n = -1;
|
||||
s_luacat_request = false;
|
||||
s_langcat_n = -1;
|
||||
s_langcat_request = false;
|
||||
}
|
||||
if (s_luastore_tab == 2) { // reopened straight onto Languages
|
||||
luaStoreScanLangs();
|
||||
if (s_langcat_n <= 0) {
|
||||
@@ -38507,7 +38548,7 @@ static void openAppDrawer() {
|
||||
#if !CAP_LUA_APPS
|
||||
{ nullptr, "Snake", APPACT_SNAKE, 0, 0x53C06B }, // native snake (Lua boards install it from the Store)
|
||||
#else
|
||||
{ LV_SYMBOL_DOWNLOAD, "Lua Store", APPACT_STORE, 0, 0x15B6A6 }, // Lua app store (brand teal)
|
||||
{ LV_SYMBOL_DOWNLOAD, "WADA Store", APPACT_STORE, 0, 0x15B6A6 }, // Lua app store (brand teal)
|
||||
#endif
|
||||
{ LV_SYMBOL_POWER, "Power", APPACT_POWER, 0, 0xE05544 }, // power red
|
||||
};
|
||||
@@ -41999,7 +42040,9 @@ static void buildUiTree() {
|
||||
makeChatDetail(g_lv.dm);
|
||||
makeChatDetail(g_lv.ch);
|
||||
|
||||
MEMPROBE("pre-settings");
|
||||
makeSettings(tab_settings);
|
||||
MEMPROBE("post-UI-build");
|
||||
|
||||
#if defined(HAS_TANMATSU)
|
||||
navBuildTabKeyHints(); // coloured △□○♣◇ shapes on the tab bar = the physical F-keys
|
||||
@@ -45846,6 +45889,16 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#if defined(HAS_TOUCH_UI)
|
||||
g_lv.task = this;
|
||||
if (!g_lv.ready) {
|
||||
// Claim the net worker's 8 KB internal stack NOW, while the heap is still
|
||||
// whole. Its own comment always said "allocated at UITask::begin() time",
|
||||
// but the only caller was the fallback inside ensureTileFetchTaskRunning()
|
||||
// — which runs when the user first opens the map/store, by which point the
|
||||
// Wi-Fi driver has taken the internal heap. On the V4 that left a 4-byte
|
||||
// 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
|
||||
@@ -46015,6 +46068,14 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#endif
|
||||
// Accent-popup picker (both boards: on-screen + physical keyboard). Default on.
|
||||
s_accent_popups = touchPrefsGetAccentPopups();
|
||||
{
|
||||
multi_heap_info_t hi{};
|
||||
heap_caps_get_info(&hi, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
Serial.printf("[MEM] boot int: total=%u free=%u largest=%u minfree=%u | psram free=%u\n",
|
||||
(unsigned)(hi.total_free_bytes + hi.total_allocated_bytes),
|
||||
(unsigned)hi.total_free_bytes, (unsigned)hi.largest_free_block,
|
||||
(unsigned)hi.minimum_free_bytes, (unsigned)ESP.getFreePsram());
|
||||
}
|
||||
i18nSetLang(touchPrefsGetUiLang()); // active UI translation language (before the UI builds)
|
||||
#if CAP_LUA_APPS
|
||||
uiLangFileBootLoad(); // overlay the active .lang file (mounts data storage a bit earlier)
|
||||
@@ -46075,7 +46136,9 @@ 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;
|
||||
lv_disp_drv_register(&g_lv.disp_drv);
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user