This document describes how the custom Tab5 firmware is structured so contributors can extend it without breaking modular boundaries or performance assumptions.
- Hardware: M5Stack Tab5 (ESP32-P4, GT911 touch, ES8388 audio codec, IPS LCD 1280×720).
- Frameworks: ESP-IDF 5.x for runtime, LVGL for UI composition, MQTT for Home Assistant connectivity.
- Upstream code: The base demo from M5Stack lives under
app/; custom features must reside incustom/to keep merges with upstream simple.
custom/ui/: screen implementations, shared widgets, and theme tokens.custom/integration/: MQTT, Home Assistant, and Frigate bindings plus mock providers.custom/platform/: display init, touch, audio, power management, and diagnostics.custom/assets/: RGB565 images, fonts, and other binary resources registered with LVGL.docs/: source-of-truth specifications for layout, tokens, contracts, and AI workflows.
Place cross-cutting utilities (logging helpers, configuration structs) in their
own subdirectory under custom/ so they can be reused without polluting vendor
code.
- Boot sequence:
app_maininitializes ESP-IDF services, then calls intocustom::platformto configure display buffers, touch, Wi-Fi, and the audio codec. - UI bring-up:
custom::ui::Init()registers styles fromui_theme.h, builds persistent LVGL objects for each screen, and wires navigation events. - Data bindings: integration modules expose observer APIs that push state updates into the UI. UI interactions trigger command helpers that publish MQTT messages with optimistic feedback and rollback timers.
- Background services: tasks under
custom::integrationmanage MQTT sessions, Frigate stream lifecycles, and diagnostic telemetry. They communicate via queues or LVGL-safe callbacks to respect threading constraints.
- Store device credentials in NVS by default. Provide menuconfig options for initial provisioning but avoid hard-coding secrets in source control.
- Collect feature flags (mock data mode, diagnostics overlay, stream fallback overrides) in a shared configuration header so UI and integration code stay aligned.
- Follow the budgets listed in
docs/PERFORMANCE.md. Create LVGL objects for all primary screens at startup to avoid heap churn during navigation. - Use double-buffered rendering with carefully sized draw buffers (≤6 MB) and reuse animation timelines rather than re-creating them per interaction.
- Throttle background MQTT or Frigate work when the UI reports frame drops.
- Wrap MQTT access behind an abstraction so integration tests can swap in an in-memory broker.
- Expose metrics (FPS, heap, MQTT round-trip) through the diagnostics overlay to support manual and automated verification.
- Keep
idf.py build,idf.py lint, andnpx markdownlint docspassing before opening a PR.