chore: move firmware skills in-repo + AGENTS.md

- Migrates tactility-firmware and tactility-bluetooth from
  ~/.hermes/skills/ into .claude/skills/ for repo co-location
- Adds AGENTS.md with local workstation context, board table,
  board-direct build rule, Hermes PYTHONPATH pitfall, common
  Tactility pitfalls, and in-repo skill index

Refs: personal Gitea mirror
This commit is contained in:
Adolfo Reyna
2026-07-17 09:51:08 -04:00
parent e7fb5fb38f
commit 12035f2f39
22 changed files with 2362 additions and 0 deletions
@@ -0,0 +1,60 @@
# WebServer + DevelopmentService Collision Debugging
Session: 2026-07-11 — Dashboard server started but MCP tools 404, dev service blocked.
## Symptoms
- Dashboard (port 80) loads, but POST /api/mcp returns 404 even with mcpEnabled=true
- Development service (6666 /info) not reachable
- MCP screen not appearing on RLCD board (Waveshare 4.2" monochrome ST7305)
## Root Causes Found
### 1. httpd ctrl_port collision
`HttpServer::startInternal()` used `HTTPD_DEFAULT_CONFIG()` → ctrl_port 32768 fixed for all instances.
When WebServer (80) + DevServer (6666) start near-simultaneously (service manager dispatches), second bind fails → `httpd_start` returns error → `httpServer` stays null → handlers never registered.
Fix: unique ctrl_port per server: `32768 + (port % 1000)` → 80→32848, 6666→33434.
Src: `Tactility/Source/network/HttpServer.cpp`
### 2. max URI handlers too low
ESP-IDF default CONFIG_HTTPD_MAX_URI_HANDLERS=8.
WebServer registers 7 handlers: `/`, `/filebrowser`, `/fs/*` GET, `/fs/*` POST, `/admin/*`, `/api/*` GET, `/api/*` POST, `/api/*` PUT, `/*` = 7, plus 2 internal slots → config.max_uri_handlers = 9.
If Kconfig max 8 < 9, `httpd_start` fails or later `httpd_register_uri_handler` fails → "no slots left".
Fix: `Buildscripts/sdkconfig/default.properties``CONFIG_HTTPD_MAX_URI_HANDLERS=20`.
### 3. DisplayIdle early return killed MCP on RLCD
`D3919344` added:
```cpp
if (!supportsBacklightDuty()) return true;
```
in `onStart()` — disables timer + settings load for ST7305 (no backlight fn). `startMcpScreensaver()` depends on settings + timer for update/auto-off.
Fix: always load settings + start timer; only skip auto-backlight logic via existing `if (supportsBacklightDuty())` guard in `tick()`.
Files: `Tactility/Source/service/displayidle/DisplayIdle.cpp:235-250`, `303-353`
### 4. Video stream task overflow
Stack 4096 + SPIRAM allocs + LVGL lock + socket loops → stack overflow → heap corruption blocking other tasks.
Also tight loop `vTaskDelay(5)` even idle.
Fix: 8192 + idle delay 50ms, busy 5ms.
## Verification Commands
```bash
source ~/esp/esp-idf/export.sh
cd firmware
python device.py waveshare-esp32-s3-rlcd --dev
idf.py build
idf.py -p /dev/cu.usbmodem101 flash monitor
# Monitor logs:
# - Started on port 80 (ctrl_port 32848)
# - Started on port 6666 (ctrl_port 33434)
# - Mono TCP Video stream server started on port 8081
# - Color TCP Video stream server started on port 8083
# - Monochrome/RLCD detected: idle timer will run but auto-backlight off disabled
curl http://<ip>/api/sysinfo # public
curl -X POST http://<ip>/api/mcp -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
curl http://<ip>:6666/info
```
## Related Incidents
- Upstream #547 merged 2026-07-04 changed settings load to check isFile first — more correct but didn't cause this.
- Board mis-ID: user has both ES3C28P color (240x320) and Waveshare RLCD mono (400x300). Wrong sdkconfig → wrong display driver → blank screen misattributed to MCP bug. Always verify `grep CONFIG_TT_DEVICE_ID sdkconfig`.