6.7 KiB
LCD Expressions
This document describes how LCD expressions are owned, requested, played, and extended.
Ownership Model
LCD expressions are split into semantic intent and low-level playback:
control_planedecides when an expression should be requested.domainowns semantic expression IDs and maps them to playback requests.platformowns LCD presentation primitives and direct ST7789 drawing.
Do not bypass this path from upper layers. The expected flow is:
control_plane condition
-> domain_runtime_request_expression()
-> platform_lcd_request_presentation()
-> LCD direct render task
control_plane must not call platform_lcd_* APIs directly. platform must
not expose domain expression IDs.
Current Backend State
The current LCD backend is components/platform/src/lcd_direct.c. It initializes
the 240x320 ST7789P3 panel directly through SPI using the
CONFIG_TQ_SCREEN_* pin and orientation settings, starts one render task, and
accepts the existing platform presentation requests. The 2026-07-24 board uses
MOSI GPIO18, clock GPIO8, CS GPIO15, D/C GPIO16, reset GPIO17, and backlight
GPIO7. The default X/Y swap exposes a 320x240 landscape drawing surface so the
existing LCD assets retain their current geometry.
The direct backend currently draws deterministic full-screen test patterns for presentation requests. Real image/resource playback is the next migration step. The previous widget-based display source files, display middleware dependency, and legacy animation assets have been removed from the build.
Current Expressions
| Domain expression | Trigger owner | Playback kind | Current behavior |
|---|---|---|---|
DOMAIN_EXPRESSION_IDLE |
LCD startup / restore path | PLATFORM_LCD_PRESENTATION_IDLE |
Draws the direct backend idle test pattern. |
DOMAIN_EXPRESSION_SERVER_ERROR |
Multimodal server failure events | PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE |
Draws a direct backend pattern selected from the mapped asset name. |
DOMAIN_EXPRESSION_SLOT_REELS |
Slot feature switch worker | PLATFORM_LCD_PRESENTATION_SLOT_REELS |
Draws a direct backend slot-pattern placeholder. |
DOMAIN_EXPRESSION_WIFI_DISCONNECTED |
Wi-Fi state monitor worker | PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE |
Draws a direct backend pattern selected from the mapped asset name. |
DOMAIN_EXPRESSION_WEALTH_SPIN |
Model-command direction worker | PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE |
Draws a direct backend pattern selected from spin. |
DOMAIN_EXPRESSION_WEALTH_DIRECTION |
Model-command direction worker | PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE |
Draws a direct backend pattern selected from the mapped direction asset name. |
DOMAIN_EXPRESSION_OTA_UPDATE |
OTA update worker | PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE |
Loops the three-frame ota sequence during firmware download and installation. |
DOMAIN_EXPRESSION_LCD_BOUNDARY_TEST |
Factory CLI lcd_test |
PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE |
Measures six preheated cache-resident frames for about 5 seconds, then measures all 16 frames under cache churn for about 5 seconds, using a 1 ms hold between full-screen draws before returning to idle. |
Server error currently triggers only from:
CONTROL_PLANE_MULTIMODAL_EVENT_TASK_FAILEDCONTROL_PLANE_MULTIMODAL_EVENT_OUTPUT_ERROR
Server error expression requests are suppressed while Wi-Fi STA is disconnected; the Wi-Fi disconnected expression owns that state because server reachability is not meaningful without Wi-Fi connectivity.
Transport disconnect/error events are not server error expressions.
Asset Migration Direction
The image playback path is a platform-owned direct LCD asset pipeline. Runtime
PNG files are embedded in each OTA application image, decoded through
lodepng, converted to RGB565 in PSRAM, and written to the ST7789 panel through
the existing bounded row-transfer buffer.
- PNG decode, the bounded four-entry LRU cache, and ST7789 writes stay inside
platform. - Domain continues to map semantic expressions to short asset IDs or names.
- Upper layers continue to use
domain_runtime_request_expression(). - Runtime LCD assets must be added to the explicit PNG allowlist in
components/platform/CMakeLists.txt; files that only exist in the source directory are not packaged automatically.
Requesting Expressions
Use domain_runtime_request_expression() from orchestration code:
const domain_expression_request_t request = {
.id = DOMAIN_EXPRESSION_SERVER_ERROR,
.trigger = DOMAIN_EXPRESSION_TRIGGER_SERVER_TASK_FAILED,
};
(void)domain_runtime_request_expression(&request);
Trigger IDs should describe why the expression was requested. They are useful for logs and future policy decisions, but the playback mapping is still owned by the domain catalog.
Playback Scheduling
The platform LCD presentation scheduler is non-blocking:
- Requests are copied at API entry.
- Panel drawing is serialized by the LCD render task.
- Same active or pending requests are coalesced.
- Higher-priority interrupting requests replace the active presentation.
- Lower-priority requests are ignored while a higher-priority presentation is active or pending.
Current priorities:
| Presentation | Priority | Interrupts current |
|---|---|---|
| Wi-Fi disconnected / idle restore | 120 |
Idle restore interrupts Wi-Fi disconnected. |
| Wealth spin / wealth-direction reveal | 110 |
Yes |
| Server error | 100 |
Yes |
| Slot reels | 20 |
No |
Adding a New Expression
Follow this checklist for every new expression:
- Add a semantic ID to
domain_expression_id_tincomponents/domain/include/domain.h. - Add a trigger reason to
domain_expression_trigger_tif the new expression needs a new trigger source. - Add a catalog entry in
components/domain/src/runtime/domain_runtime_adapters.c. - Add or map the platform direct LCD asset in the platform LCD backend.
- Add or update the
control_planerule that createsdomain_expression_request_t. - Build with the repository-approved ESP-IDF path and verify LCD logs on hardware.
Do not add one-off APIs such as domain_runtime_show_happy() or
platform_lcd_show_error(). Keep extension through the expression catalog and
the unified presentation request path.
Validation Checklist
Before merging expression changes:
- Run
idf.py buildthrough the repository-approved ESP-IDF activation path. - Verify
control_planedoes not includeplatform.hor callplatform_lcd_*. - Verify platform code does not refer to
DOMAIN_EXPRESSION_*. - For finite asset sequences, verify completion returns to idle when
return_to_idleis set. - For interrupting expressions, verify the active lower-priority presentation is cancelled cleanly.