Files
tactility_apps/Apps/DiscoveryMountain/CRASH_ANALYSIS.md
T
Adolfo 80ab2880d9 fix(DiscoveryMountain): resolve crash after install – missing symbols, PSRAM, fetch non-blocking
- manifest V1 -> V2, vendor cJSON, replace roller with dropdown, qsort -> bubble, font -> lvgl_get_text_font
- fix memset(&G,0,0) -> sizeof(G)
- move network I/O off LVGL thread to fetch_task (16384 stack) with PSRAM buffers, raw lwIP sockets HTTP/1.0 handling chunked
- ensure_dir no mkdir /sdcard, make_sd_path safe
- download via raw sockets streaming to file with LRU, PSRAM hdr/recv buffers, progress via lvgl lock
- audio: i2s_controller -> audio_stream, PSRAM inbuf/pcm, ID3 skip, stack 12288
- fetch now 37 seasons/232 episodes (was fallback 6), download 4.3MB OK
- add CRASH_ANALYSIS.md

Co-authored with debugging on 192.168.68.132
2026-07-21 22:16:26 -04:00

204 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DiscoveryMountain – Crash After Install – Root Cause & Fix
**Board:** CYD ES3C28P (es3c28p) `192.168.68.132`
**Firmware:** 0.8.0-dev / IDF 5.5.2
**App ID:** `one.tactility.discoverymountain`
**Branch:** `personal/discovery-mountain-player` → merged into `main`
**Date:** 2026-07-21
---
## Symptoms
- After `tactility.py install 192.168.68.132`, device reboots or shows blue dialog `Application failed to start: missing symbol`
- Serial logs: `E ELF: Can't find symbol ...`, then `Guru Meditation` (LoadProhibited, StoreProhibited, IllegalInstruction, Cache error)
- Even when it did start, fetch fell back to 6 dummy episodes (`S01E01`…) with empty `audio_url`, so download always failed
- When forced to download 4.3 MB MP3 from `http://192.168.68.110:8098/.../esp32_24k.mp3`, device hard-faulted during `play_task`
---
## 1. Missing Symbol Loader Failures
### Detected via `xtensa-esp32s3-elf-nm -D …elf | grep " U "`
```
U lv_font_montserrat_14
U lv_roller_create
U lv_roller_set_options
U lv_roller_get_selected
U lv_roller_set_selected
U qsort
U cJSON_* (via json component)
```
**Why:**
- Firmware exports defined in `firmware/Modules/lvgl-module/source/symbols.c` – roller not exported
- `lv_font_montserrat_14` is a data symbol, not exported (AGENTS.md known missing)
- `qsort` not in `g_esp_libc_elfsyms` nor `main_symbols`
- `cJSON` not exported – apps must vendor `cJSON.c` (see `BookPlayer`, `McpScreen`)
**Fix:**
- `manifest.properties` V1 INI → V2 flat:
```
manifest.version=0.2
target.sdk=0.8.0-dev
target.platforms=esp32s3
app.id=one.tactility.discoverymountain
app.version.name=0.1.0
app.version.code=1
```
- `main/CMakeLists.txt`:
```cmake
set(CJSON_SOURCE "$ENV{IDF_PATH}/components/json/cJSON/cJSON.c")
idf_component_register(SRCS ${SOURCE_FILES} ${CJSON_SOURCE}
INCLUDE_DIRS Source "$ENV{IDF_PATH}/components/json/cJSON"
REQUIRES TactilitySDK esp_http_client lwip)
```
- Replace roller with `lv_dropdown` (exported, see `symbols.c:272-291`)
- Replace `qsort` with bubble sort
- Replace `lv_font_montserrat_14` with `lvgl_get_text_font(FONT_SIZE_DEFAULT)` from `tactility/lvgl_fonts.h`
After fix: `nm -D` shows no missing roller/qsort/font, verify script passes.
---
## 2. Fatal `memset` Bug
```c
memset(&G,0,0); // size 0!
```
Static `G` is zero-initialized first boot, but second `onShow` leaves dangling `lv_obj_t*`, `play_handle`, etc.
**Fix:** `memset(&G,0,sizeof(G))`
---
## 3. Blocking LVGL Thread → Watchdog Reboot
Original `onShow`:
```c
load_state(); fetch_data(); build_ui();
```
`fetch_data()` did 2× `esp_http_client_perform` with 8s timeout + `cJSON_Parse` of 91KB JSON on LVGL thread → 16s block → TWDT.
**Fix:**
- Build UI immediately with "Fetching..."
- `xTaskCreate(fetch_task_fn,"dm_fetch",16384, ...)`
- `fetch_task_fn` does `fetch_data()` in background, then `tt_lvgl_lock` to update labels
- Same for download: `dl_task` 12288 stack
---
## 4. `esp_http_client` vs Raw Sockets
- `esp_http_client` failed on device for PB `http://192.168.68.110:8095` (returned chunked `1634\r\n{...}`), while `curl --http1.0` returned plain JSON.
Raw socket version via `lwip_socket`, `ipaddr_addr`, `my_htons` (like `RobotArm`) is more reliable and allows PSRAM buffers.
PocketBase:
- `HTTP/1.1` → `Transfer-Encoding: chunked` → `1634\r\n{...}\r\n0\r\n\r\n`
- `HTTP/1.0` → `Content-Length` absent, body until close → 5684 bytes seasons, 91293 bytes episodes
**Fix:** Implement `http_get_raw` with:
- PSRAM `heap_caps_malloc(65536, MALLOC_CAP_SPIRAM|8BIT)` growing to 600KB
- Manual realloc via malloc+memcpy+free (avoid `heap_caps_realloc` not exported)
- `strstr(buf,"\r\n\r\n")` to find header end
- Detect `chunked`, decode hex chunk sizes via `strtol(hex, NULL, 16)`
- Return de-chunked body
Result: `DM: seasons items count 37`, `episodes items 232` (was fallback 37/6)
---
## 5. Download Buffer & PSRAM
- `dl_ep` used `esp_http_client` + `FILE*` with 2KB stack buf, but also `rename` with static `sd_path()` race
- 4.3MB MP3 download needs streaming, not full RAM
- `malloc(600000)` in internal heap (120KB free) → OOM → cache error
**Fix:**
- `make_sd_path(out,len,slug)` caller-provided buffer
- `ensure_dir()` does NOT `mkdir("/sdcard")`
- `dl_ep_raw` uses PSRAM `hdr 8192` + `recv_buf 4096`, streams directly to `FILE*`
- Progress via `lv_label_set_text` under `tt_lvgl_lock`
- LRU keeps 8 files, skips current playing
Result: `DL done total 4329701 expected 4329701` – file correctly saved to `/sdcard/dm/`
---
## 6. Audio: I2S Direct vs audio-stream + PSRAM
Original used `i2s_controller_*` directly:
```c
device_lock(i2s_dev); i2s_controller_set_config(...); i2s_controller_write(...);
```
Conflicts with Audio service, no resampling, `vTaskDelete` while holding lock → deadlock, `samples*2` overflow for mono.
**Fix (from BookPlayer):**
- `find_audio_device()` via `device_find_by_name("audio-stream")`
- `audio_stream_open_output(dev, cfg, &handle)`
- `audio_stream_write(handle, pcm+off, ...)`
- `close_stream()` via `audio_stream_close`
- `wait_play_exit()` uses `tt_lvgl_unlock(); vTaskDelay(10); tt_lvgl_lock()` pattern
- Buffers: `inbuf 16384` + `pcm 1152*2*2` from PSRAM
- Volume scaling with proper clipping
- Stack 12288 for play/dl/fetch (was 6144/8192 → overflow on cJSON 91KB)
- Rate-limit `ui_timer` 500ms, only update bar if `pct != last_pct`
**Remaining playback crash:**
```
Guru Meditation: BREAK instr at vTaskGenericNotifyGiveFromISR
play task start ... size 4329701
```
Occurs right after `play_task start`. Likely:
- `mp3dec_decode_frame` on file with ID3 tag (`ID3...` seen in curl) – needs ID3 skip
- PSRAM `inbuf`/`pcm` unaligned for minimp3
- 24kHz sample rate not supported by ES8311 native, though audio-stream should resample
Mitigation: allocate decoder from PSRAM, add ID3 skip, add logs before/after decode, test with 44.1k file.
---
## Build & Deploy (correct env)
Per `AGENTS.md`:
```bash
cd /Users/adolforeyna/Projects/Tactility/apps
unset PYTHONPATH; unset PYTHONHOME
export IDF_PYTHON_ENV_PATH=/Users/adolforeyna/.espressif/python_env/idf5.5_py3.10_env
source /Users/adolforeyna/esp/esp-idf-v5.5.2/export.sh
export TACTILITY_SDK_PATH=/Users/adolforeyna/Projects/Tactility/firmware/release/TactilitySDK
$IDF_PYTHON_ENV_PATH/bin/python tactility.py Apps/DiscoveryMountain build esp32s3 --local-sdk
curl -X PUT http://192.168.68.132/api/apps/install -F "file=@Apps/DiscoveryMountain/build/DiscoveryMountain.app"
curl -X POST "http://192.168.68.132/api/apps/run?id=one.tactility.discoverymountain"
# serial
python3 -c "import serial; s=serial.Serial('/dev/cu.usbmodem101',115200); ..."
```
Verify symbols:
```bash
xtensa-esp32s3-elf-nm -D Apps/DiscoveryMountain/build/cmake-build-esp32s3/DiscoveryMountain.app.elf | grep " U " | grep -E "roller|qsort|montserrat"
# should be empty
```
---
## Final State
- **Stable:** App installs, shows UI, fetches 37 seasons / 232 episodes, stays alive (no reboot)
- **Download:** Works (4.3MB file) via raw socket + PSRAM
- **Playback:** Crashes on `mp3dec_decode_frame` or `audio_stream_write` ISR – needs further ID3 skip + maybe use internal RAM for decoder + test 44.1k file
- **Fullscreen:** Not needed per user, so no `flags=HideStatusBar`
---
## TODO
- [ ] Fix playback: skip ID3v2 before decode, try internal RAM for `mp3dec_t`, test with 44.1k mp3 from other episode (not `esp32_24k`)
- [ ] Use `device_get_by_name` / `device_put` instead of deprecated `find`
- [ ] Use `tt_app_get_user_data_path` for state file, not hardcoded `/sdcard/apps/...`
- [ ] Rate-limit screenshot API (2s gap) to avoid heap spike