Files
lucky-cat/docs/lcd-expressions.md
T

6.1 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 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.

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 Migration Direction

The target image playback path is a platform-owned direct LCD asset pipeline. The reference project at D:\Code\chaowan\lcd(1)\lcd uses compiled RLE image arrays plus a small decoder/write path. When this is ported:

  • RLE decode 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().
  • Large image data should not be blindly compiled into the app if it risks exceeding the OTA partition; prefer partition-backed resources for large frame sets.

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:

  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. Add or map the platform direct LCD asset in the platform LCD backend.
  5. Add or update the control_plane rule that creates domain_expression_request_t.
  6. 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 build through the repository-approved ESP-IDF activation path.
  • 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.