Files
lucky-cat/docs/lcd-expressions.md
T
2026-06-25 00:34:34 +08:00

8.8 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_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:

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:

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:

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:

components/platform/assets/lvgl_animations/server_error/

Sorted source frames:

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:

{0, 1, 2, 1, 0}

The Wi-Fi disconnected expression uses:

components/platform/assets/lvgl_animations/wifi_disconnected/

Sorted source frames:

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:

{0, 1, 2, 3, 2, 1, 0}

The wealth-direction reveal uses one shared spin intro plus one body per direction:

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:

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.