224 lines
8.8 KiB
Markdown
224 lines
8.8 KiB
Markdown
# 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_plane` decides when an expression should be requested.
|
|
- `domain` owns semantic expression IDs and maps them to playback requests.
|
|
- `platform` owns LCD presentation primitives and LVGL playback.
|
|
|
|
Do not bypass this path from upper layers. The expected flow is:
|
|
|
|
```text
|
|
control_plane condition
|
|
-> domain_runtime_request_expression()
|
|
-> platform_lcd_request_presentation()
|
|
-> LCD render task / LVGL
|
|
```
|
|
|
|
`control_plane` must not call `platform_lcd_*` APIs directly. `platform` must
|
|
not expose domain expression IDs.
|
|
|
|
## Current Expressions
|
|
|
|
| Domain expression | Trigger owner | Playback kind | Behavior |
|
|
| --- | --- | --- | --- |
|
|
| `DOMAIN_EXPRESSION_IDLE` | LCD startup / restore path | `PLATFORM_LCD_PRESENTATION_IDLE` | Shows the default cat idle screen. |
|
|
| `DOMAIN_EXPRESSION_SERVER_ERROR` | Multimodal server failure events | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Plays `server_error` for 5 cycles, then returns to idle. |
|
|
| `DOMAIN_EXPRESSION_SLOT_REELS` | Slot feature switch worker | `PLATFORM_LCD_PRESENTATION_SLOT_REELS` | Plays the procedural slot reels presentation. |
|
|
| `DOMAIN_EXPRESSION_WIFI_DISCONNECTED` | Wi-Fi state monitor worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Loops `wifi_disconnected` while STA is unavailable; saved credentials are retried with backoff, then idle is restored after reconnect. |
|
|
| `DOMAIN_EXPRESSION_WEALTH_SPIN` | Model-command direction worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Plays `spin` exactly twice as a fixed intro while the cat stays still; the worker blocks until it completes, then the reveal and motor rotation start together. |
|
|
| `DOMAIN_EXPRESSION_WEALTH_DIRECTION` | Model-command direction worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Plays the matched direction's reveal once, then returns to idle. Parametric on `direction_index`; the asset directory is resolved from the domain direction catalog order. |
|
|
|
|
Server error currently triggers only from:
|
|
|
|
- `CONTROL_PLANE_MULTIMODAL_EVENT_TASK_FAILED`
|
|
- `CONTROL_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 Management
|
|
|
|
Expression assets live under:
|
|
|
|
```text
|
|
components/platform/assets/lvgl_animations/<asset_dir>/
|
|
```
|
|
|
|
The platform build copies all `.bin` files under this tree into the `gifpack`
|
|
LittleFS partition while preserving relative directories. Runtime LVGL paths are
|
|
resolved as:
|
|
|
|
```text
|
|
A:/littlefs/<asset_dir>/<file>.bin
|
|
```
|
|
|
|
Asset rules:
|
|
|
|
- Use LVGL `.bin` images compatible with the active LCD color configuration.
|
|
- RGB565 LZ4 compressed assets are supported by the existing decoder path when
|
|
`LV_BIN_DECODER_RAM_LOAD` and `LV_USE_LZ4` are enabled.
|
|
- File discovery is lexicographic. Name files with zero-padded numeric suffixes.
|
|
- If an expression needs a non-linear playback sequence, define `frame_order` in
|
|
the domain catalog instead of duplicating files.
|
|
- Keep `asset_dir` names short enough for `PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX`.
|
|
|
|
The current server error expression uses:
|
|
|
|
```text
|
|
components/platform/assets/lvgl_animations/server_error/
|
|
```
|
|
|
|
Sorted source frames:
|
|
|
|
```text
|
|
TQ_Cat_Server_Error_0000.bin
|
|
TQ_Cat_Server_Error_0001.bin
|
|
TQ_Cat_Server_Error_0002.bin
|
|
```
|
|
|
|
Playback order is declared in the domain catalog as:
|
|
|
|
```c
|
|
{0, 1, 2, 1, 0}
|
|
```
|
|
|
|
The Wi-Fi disconnected expression uses:
|
|
|
|
```text
|
|
components/platform/assets/lvgl_animations/wifi_disconnected/
|
|
```
|
|
|
|
Sorted source frames:
|
|
|
|
```text
|
|
TQ_Cat_wifi_disconnected_0000.bin
|
|
TQ_Cat_wifi_disconnected_0001.bin
|
|
TQ_Cat_wifi_disconnected_0002.bin
|
|
TQ_Cat_wifi_disconnected_0003.bin
|
|
```
|
|
|
|
Playback order is declared in the domain catalog as:
|
|
|
|
```c
|
|
{0, 1, 2, 3, 2, 1, 0}
|
|
```
|
|
|
|
The wealth-direction reveal uses one shared spin intro plus one body per
|
|
direction:
|
|
|
|
```text
|
|
components/platform/assets/lvgl_animations/spin/ (fixed two-cycle spin intro)
|
|
components/platform/assets/lvgl_animations/south/
|
|
components/platform/assets/lvgl_animations/southwest/
|
|
components/platform/assets/lvgl_animations/west/
|
|
components/platform/assets/lvgl_animations/northwest/
|
|
components/platform/assets/lvgl_animations/north/
|
|
components/platform/assets/lvgl_animations/northeast/
|
|
components/platform/assets/lvgl_animations/east/
|
|
components/platform/assets/lvgl_animations/southeast/
|
|
```
|
|
|
|
These play all frames in sorted order (`frame_order = NULL`). The direction body
|
|
asset is resolved from `direction_index` via `s_wealth_direction_asset_dirs[]` in
|
|
`components/domain/src/runtime/domain_runtime_adapters.c`, whose order must match
|
|
the direction catalog (`components/domain/src/direction/domain_direction_catalog.c`).
|
|
|
|
## Requesting Expressions
|
|
|
|
Use `domain_runtime_request_expression()` from orchestration code:
|
|
|
|
```c
|
|
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.
|
|
|
|
Slot reel playback uses the same expression path:
|
|
`domain_runtime_request_expression()` with `DOMAIN_EXPRESSION_SLOT_REELS`.
|
|
|
|
## Playback Scheduling
|
|
|
|
The platform LCD presentation scheduler is non-blocking:
|
|
|
|
- Requests are copied at API entry.
|
|
- LVGL objects are created, deleted, and mutated only inside 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.
|
|
- Asset-backed expressions are cached after their frames are decoded. The LCD
|
|
task preheats the `spin` asset after startup and preheats the selected wealth
|
|
direction reveal while the fixed spin intro is playing.
|
|
- Preheat requests do not change the active display. They decode incrementally
|
|
during the render loop, and playback finishes any remaining decode work
|
|
synchronously if a reveal is requested before preheat completes.
|
|
|
|
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 (spin interrupts current; the reveal follows after the spin completes). |
|
|
| Server error | `100` | Yes |
|
|
| Slot reels | `20` | No |
|
|
|
|
The wealth spin/reveal pair is requested by the model-command direction worker
|
|
(`control_plane_model_command_task`): the spin plays exactly twice as a blocking
|
|
intro while the selected reveal is preheated, then the reveal is requested and
|
|
the motor rotation starts together. At priority `110` a Wi-Fi-disconnected
|
|
screen (`120`) still wins, so the reveal is suppressed while STA is down —
|
|
consistent with the server-error suppression rule above.
|
|
|
|
## Adding a New Expression
|
|
|
|
Follow this checklist for every new expression:
|
|
|
|
1. Add a semantic ID to `domain_expression_id_t` in
|
|
`components/domain/include/domain.h`.
|
|
2. Add a trigger reason to `domain_expression_trigger_t` if the new expression
|
|
needs a new trigger source.
|
|
3. Add a catalog entry in `components/domain/src/runtime/domain_runtime_adapters.c`.
|
|
4. For asset-backed expressions, place `.bin` frames under
|
|
`components/platform/assets/lvgl_animations/<asset_dir>/`.
|
|
5. If the expression needs a custom frame order, define a static `uint8_t`
|
|
frame-order array in the domain adapter and reference it from the catalog.
|
|
6. Add or update the `control_plane` rule that creates
|
|
`domain_expression_request_t`.
|
|
7. Build with ESP-IDF v5.5.3 and confirm the LittleFS image includes the asset
|
|
directory.
|
|
|
|
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 build` through the repository-approved ESP-IDF activation path.
|
|
- Confirm new asset files appear in the `gifpack` build output.
|
|
- Verify `control_plane` does not include `platform.h` or call `platform_lcd_*`.
|
|
- Verify platform code does not refer to `DOMAIN_EXPRESSION_*`.
|
|
- For finite asset sequences, verify completion returns to idle when
|
|
`return_to_idle` is set.
|
|
- For interrupting expressions, verify the active lower-priority presentation is
|
|
cancelled cleanly.
|
|
- For wealth-direction playback, verify default -> spin and spin -> reveal
|
|
transitions use cached/preheated assets without visible transition stalls.
|