diff --git a/AGENTS.md b/AGENTS.md index 75f9104..293e257 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,7 +69,7 @@ Additional enforcement rules: - New expression semantics must be declared as `domain_expression_*` contracts in `components/domain/include/domain.h` and mapped in the domain runtime adapter catalog. - Upper layers must request expressions through `domain_runtime_request_expression()`; they must not call `platform_lcd_*` playback APIs directly. - Platform LCD code must expose playback primitives only and must not reference `DOMAIN_EXPRESSION_*` semantic IDs. -- Asset-backed expressions should place `.bin` frames under `components/platform/assets/lvgl_animations//`; custom playback order belongs in the domain catalog, not in duplicated asset files or control-plane logic. +- Asset-backed expressions should use the platform-owned direct LCD asset path documented in `docs/lcd-expressions.md`; custom playback order belongs in the domain catalog, not in duplicated asset files or control-plane logic. - Do not add one-off expression APIs for each emotion/state unless an ADR explicitly justifies a new contract family. ## Concurrency and Callback Safety diff --git a/components/app_composition/src/runtime/app_composition_default_runtime.c b/components/app_composition/src/runtime/app_composition_default_runtime.c index ea227ea..6817f65 100644 --- a/components/app_composition/src/runtime/app_composition_default_runtime.c +++ b/components/app_composition/src/runtime/app_composition_default_runtime.c @@ -100,10 +100,21 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void) if (lcd_ready) { app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_LCD_RENDER_LOOP, "lcd_render_loop", - "deferred_to_runtime_worker_task"); - app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_BOOT_EXPRESSION, - "boot_expression", - "deferred_until_runtime_worker_started"); + "control_plane_start_lcd_render_worker"); + esp_err_t lcd_worker_err = control_plane_start_lcd_render_worker(); + app_composition_log_stage_result(APP_COMPOSITION_BOOT_STAGE_LCD_RENDER_LOOP, + "lcd_render_loop", + lcd_worker_err); + if (lcd_worker_err != ESP_OK) { + lcd_ready = false; + app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_BOOT_EXPRESSION, + "boot_expression", + "lcd_render_worker_unavailable"); + } else { + app_composition_log_stage(APP_COMPOSITION_BOOT_STAGE_BOOT_EXPRESSION, + "boot_expression", + "deferred_until_runtime_worker_started"); + } } else { app_composition_log_stage_skipped(APP_COMPOSITION_BOOT_STAGE_LCD_RENDER_LOOP, "lcd_render_loop", diff --git a/components/control_plane/include/control_plane.h b/components/control_plane/include/control_plane.h index aeb946d..fb409db 100644 --- a/components/control_plane/include/control_plane.h +++ b/components/control_plane/include/control_plane.h @@ -217,6 +217,7 @@ void control_plane_stop_event_observer_runtime(void); esp_err_t control_plane_fortune_refresh_now(void); esp_err_t control_plane_init_lcd_device(void); +esp_err_t control_plane_start_lcd_render_worker(void); void control_plane_start_lcd_render_loop(void); esp_err_t control_plane_request_boot_expression(void); /** diff --git a/components/control_plane/src/runtime/control_plane_runtime_workers.c b/components/control_plane/src/runtime/control_plane_runtime_workers.c index 7a2018e..0cb630c 100644 --- a/components/control_plane/src/runtime/control_plane_runtime_workers.c +++ b/components/control_plane/src/runtime/control_plane_runtime_workers.c @@ -1469,6 +1469,34 @@ static void control_plane_daily_fortune_task(void *arg) } #endif +esp_err_t control_plane_start_lcd_render_worker(void) +{ +#if CONFIG_TQ_SCREEN_ENABLE + control_plane_set_runtime_stop_requested(false); + + if (control_plane_get_task_handle(&s_lcd_render_loop_task) != NULL) { + ESP_LOGI(TAG, "LCD render worker already running"); + return ESP_OK; + } + + esp_err_t task_err = control_plane_create_task(control_plane_lcd_render_loop_task, + "cp_lcd_render", + CONTROL_PLANE_LCD_RENDER_LOOP_TASK_STACK_SIZE, + 4, + CONTROL_PLANE_LOCAL_TASK_CORE, + &s_lcd_render_loop_task); + if (task_err != ESP_OK) { + return task_err; + } + + ESP_LOGI(TAG, "LCD render worker started"); + return ESP_OK; +#else + ESP_LOGI(TAG, "LCD render worker disabled by configuration"); + return ESP_ERR_NOT_SUPPORTED; +#endif +} + esp_err_t control_plane_start_runtime_workers(void) { control_plane_set_runtime_stop_requested(false); @@ -1513,17 +1541,10 @@ esp_err_t control_plane_start_runtime_workers(void) } #if CONFIG_TQ_SCREEN_ENABLE - if (control_plane_get_task_handle(&s_lcd_render_loop_task) == NULL) { - esp_err_t task_err = control_plane_create_task(control_plane_lcd_render_loop_task, - "cp_lcd_render", - CONTROL_PLANE_LCD_RENDER_LOOP_TASK_STACK_SIZE, - 4, - CONTROL_PLANE_LOCAL_TASK_CORE, - &s_lcd_render_loop_task); - if (task_err != ESP_OK) { - control_plane_stop_runtime_workers(); - return task_err; - } + esp_err_t lcd_worker_err = control_plane_start_lcd_render_worker(); + if (lcd_worker_err != ESP_OK) { + control_plane_stop_runtime_workers(); + return lcd_worker_err; } #else ESP_LOGI(TAG, "LCD render loop disabled by configuration"); diff --git a/components/platform/CMakeLists.txt b/components/platform/CMakeLists.txt index 4fe1e1b..fc6d26d 100644 --- a/components/platform/CMakeLists.txt +++ b/components/platform/CMakeLists.txt @@ -1,5 +1,4 @@ -idf_component_register( - SRCS +set(platform_srcs "src/runtime/platform_core_runtime.c" "src/runtime/platform_storage.c" "src/runtime/platform_time.c" @@ -9,19 +8,17 @@ idf_component_register( "src/runtime/platform_behavior_runtime.c" "src/runtime/platform_multimodal_runtime.c" "src/runtime/platform_lcd_runtime.c" - "src/lcd_face.c" "src/platform_stepper_driver.c" "src/platform_digital_input_driver.c" - "src/lcd.c" + "src/lcd_direct.c" + "src/lcd_direct_assets.c" "src/multimodal/audio_pipeline.c" "src/multimodal/transport_ws.c" "src/wifi_portal_bridge.cpp" "src/platform_behavior_stubs.c" - INCLUDE_DIRS - "include" - PRIV_INCLUDE_DIRS - "internal" - PRIV_REQUIRES +) + +set(platform_priv_requires 78__esp-wifi-connect cxx driver @@ -45,56 +42,57 @@ idf_component_register( json littlefs log - lvgl espressif__mdns mbedtls nvs_flash ) -set(platform_lvgl_animation_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lvgl_animations") -set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts") -set(platform_lvgl_animation_image_dir "${CMAKE_CURRENT_BINARY_DIR}/lvgl_animations_image") -set(platform_lvgl_animation_stage_stamp "${CMAKE_CURRENT_BINARY_DIR}/lvgl_animations_image.stamp") -file(GLOB_RECURSE platform_lvgl_animation_bin_files CONFIGURE_DEPENDS - "${platform_lvgl_animation_src_dir}/*.bin" +idf_component_register( + SRCS + ${platform_srcs} + INCLUDE_DIRS + "include" + PRIV_INCLUDE_DIRS + "internal" + PRIV_REQUIRES + ${platform_priv_requires} ) + +target_add_binary_data(${COMPONENT_LIB} + "assets/lcd/buyunqian1.rle" + BINARY +) + +set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts") +set(platform_asset_image_dir "${CMAKE_CURRENT_BINARY_DIR}/asset_image") +set(platform_asset_stage_stamp "${CMAKE_CURRENT_BINARY_DIR}/asset_image.stamp") file(GLOB_RECURSE platform_audio_prompt_files CONFIGURE_DEPENDS "${platform_audio_prompt_src_dir}/*.opus" ) -set(platform_lvgl_animation_stage_commands) -foreach(animation_file IN LISTS platform_lvgl_animation_bin_files) - file(RELATIVE_PATH animation_rel_path "${platform_lvgl_animation_src_dir}" "${animation_file}") - get_filename_component(animation_rel_dir "${animation_rel_path}" DIRECTORY) - list(APPEND platform_lvgl_animation_stage_commands - COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_lvgl_animation_image_dir}/${animation_rel_dir}" - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${animation_file}" - "${platform_lvgl_animation_image_dir}/${animation_rel_path}" - ) -endforeach() +set(platform_asset_stage_commands) foreach(audio_file IN LISTS platform_audio_prompt_files) file(RELATIVE_PATH audio_rel_path "${platform_audio_prompt_src_dir}" "${audio_file}") - list(APPEND platform_lvgl_animation_stage_commands - COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_lvgl_animation_image_dir}/audio_prompts" + list(APPEND platform_asset_stage_commands + COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_asset_image_dir}/audio_prompts" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${audio_file}" - "${platform_lvgl_animation_image_dir}/audio_prompts/${audio_rel_path}" + "${platform_asset_image_dir}/audio_prompts/${audio_rel_path}" ) endforeach() add_custom_command( - OUTPUT "${platform_lvgl_animation_stage_stamp}" - COMMAND ${CMAKE_COMMAND} -E rm -rf "${platform_lvgl_animation_image_dir}" - COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_lvgl_animation_image_dir}" - ${platform_lvgl_animation_stage_commands} - COMMAND ${CMAKE_COMMAND} -E touch "${platform_lvgl_animation_stage_stamp}" - DEPENDS ${platform_lvgl_animation_bin_files} ${platform_audio_prompt_files} + OUTPUT "${platform_asset_stage_stamp}" + COMMAND ${CMAKE_COMMAND} -E rm -rf "${platform_asset_image_dir}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_asset_image_dir}" + ${platform_asset_stage_commands} + COMMAND ${CMAKE_COMMAND} -E touch "${platform_asset_stage_stamp}" + DEPENDS ${platform_audio_prompt_files} ) littlefs_create_partition_image( gifpack - "${platform_lvgl_animation_image_dir}" + "${platform_asset_image_dir}" FLASH_IN_PROJECT - DEPENDS "${platform_lvgl_animation_stage_stamp}" + DEPENDS "${platform_asset_stage_stamp}" ) diff --git a/components/platform/assets/lcd/buyunqian1.rle b/components/platform/assets/lcd/buyunqian1.rle new file mode 100644 index 0000000..63c102b Binary files /dev/null and b/components/platform/assets/lcd/buyunqian1.rle differ diff --git a/components/platform/assets/lvgl_animations/default/.gitkeep b/components/platform/assets/lvgl_animations/default/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/default/default_0000.bin b/components/platform/assets/lvgl_animations/default/default_0000.bin deleted file mode 100644 index 4af365f..0000000 Binary files a/components/platform/assets/lvgl_animations/default/default_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/default/default_0001.bin b/components/platform/assets/lvgl_animations/default/default_0001.bin deleted file mode 100644 index ed4cf76..0000000 Binary files a/components/platform/assets/lvgl_animations/default/default_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/default/default_0002.bin b/components/platform/assets/lvgl_animations/default/default_0002.bin deleted file mode 100644 index dc2b769..0000000 Binary files a/components/platform/assets/lvgl_animations/default/default_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/default/default_0003.bin b/components/platform/assets/lvgl_animations/default/default_0003.bin deleted file mode 100644 index b098e4c..0000000 Binary files a/components/platform/assets/lvgl_animations/default/default_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/default/default_0004.bin b/components/platform/assets/lvgl_animations/default/default_0004.bin deleted file mode 100644 index 3ae52f4..0000000 Binary files a/components/platform/assets/lvgl_animations/default/default_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/default/default_0005.bin b/components/platform/assets/lvgl_animations/default/default_0005.bin deleted file mode 100644 index 79c3164..0000000 Binary files a/components/platform/assets/lvgl_animations/default/default_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/.gitkeep b/components/platform/assets/lvgl_animations/east/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/east/east_0000.bin b/components/platform/assets/lvgl_animations/east/east_0000.bin deleted file mode 100644 index 3a5ea1b..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0001.bin b/components/platform/assets/lvgl_animations/east/east_0001.bin deleted file mode 100644 index 1268478..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0002.bin b/components/platform/assets/lvgl_animations/east/east_0002.bin deleted file mode 100644 index 40e2773..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0003.bin b/components/platform/assets/lvgl_animations/east/east_0003.bin deleted file mode 100644 index 26a3ace..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0004.bin b/components/platform/assets/lvgl_animations/east/east_0004.bin deleted file mode 100644 index f536bd2..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0005.bin b/components/platform/assets/lvgl_animations/east/east_0005.bin deleted file mode 100644 index bc65500..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0006.bin b/components/platform/assets/lvgl_animations/east/east_0006.bin deleted file mode 100644 index 85c21e6..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0007.bin b/components/platform/assets/lvgl_animations/east/east_0007.bin deleted file mode 100644 index d8d520d..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0008.bin b/components/platform/assets/lvgl_animations/east/east_0008.bin deleted file mode 100644 index f6dee9c..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0009.bin b/components/platform/assets/lvgl_animations/east/east_0009.bin deleted file mode 100644 index abebc7b..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0010.bin b/components/platform/assets/lvgl_animations/east/east_0010.bin deleted file mode 100644 index 5cb3d73..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0011.bin b/components/platform/assets/lvgl_animations/east/east_0011.bin deleted file mode 100644 index 15acffe..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0012.bin b/components/platform/assets/lvgl_animations/east/east_0012.bin deleted file mode 100644 index a8ff4ae..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0013.bin b/components/platform/assets/lvgl_animations/east/east_0013.bin deleted file mode 100644 index 9cdb2fd..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0014.bin b/components/platform/assets/lvgl_animations/east/east_0014.bin deleted file mode 100644 index b26a4e6..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0015.bin b/components/platform/assets/lvgl_animations/east/east_0015.bin deleted file mode 100644 index 4d2d107..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0016.bin b/components/platform/assets/lvgl_animations/east/east_0016.bin deleted file mode 100644 index bed1a97..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0017.bin b/components/platform/assets/lvgl_animations/east/east_0017.bin deleted file mode 100644 index a44ebd2..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0018.bin b/components/platform/assets/lvgl_animations/east/east_0018.bin deleted file mode 100644 index bb1a7e4..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/east/east_0019.bin b/components/platform/assets/lvgl_animations/east/east_0019.bin deleted file mode 100644 index 3cd271b..0000000 Binary files a/components/platform/assets/lvgl_animations/east/east_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0000.bin b/components/platform/assets/lvgl_animations/north/north_0000.bin deleted file mode 100644 index 2750148..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0001.bin b/components/platform/assets/lvgl_animations/north/north_0001.bin deleted file mode 100644 index 576a3f0..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0002.bin b/components/platform/assets/lvgl_animations/north/north_0002.bin deleted file mode 100644 index 1de6c5f..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0003.bin b/components/platform/assets/lvgl_animations/north/north_0003.bin deleted file mode 100644 index 736de10..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0004.bin b/components/platform/assets/lvgl_animations/north/north_0004.bin deleted file mode 100644 index 912eaad..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0005.bin b/components/platform/assets/lvgl_animations/north/north_0005.bin deleted file mode 100644 index 3a79bb6..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0006.bin b/components/platform/assets/lvgl_animations/north/north_0006.bin deleted file mode 100644 index e5388b2..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0007.bin b/components/platform/assets/lvgl_animations/north/north_0007.bin deleted file mode 100644 index 9bdd7d7..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0008.bin b/components/platform/assets/lvgl_animations/north/north_0008.bin deleted file mode 100644 index 340422f..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0009.bin b/components/platform/assets/lvgl_animations/north/north_0009.bin deleted file mode 100644 index 8e30a59..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0010.bin b/components/platform/assets/lvgl_animations/north/north_0010.bin deleted file mode 100644 index 16a2bfc..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0011.bin b/components/platform/assets/lvgl_animations/north/north_0011.bin deleted file mode 100644 index a605f29..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0012.bin b/components/platform/assets/lvgl_animations/north/north_0012.bin deleted file mode 100644 index 6989d4b..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0013.bin b/components/platform/assets/lvgl_animations/north/north_0013.bin deleted file mode 100644 index 8972d10..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0014.bin b/components/platform/assets/lvgl_animations/north/north_0014.bin deleted file mode 100644 index d3cd996..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0015.bin b/components/platform/assets/lvgl_animations/north/north_0015.bin deleted file mode 100644 index 80080d5..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0016.bin b/components/platform/assets/lvgl_animations/north/north_0016.bin deleted file mode 100644 index 8858a40..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0017.bin b/components/platform/assets/lvgl_animations/north/north_0017.bin deleted file mode 100644 index b8ecc01..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0018.bin b/components/platform/assets/lvgl_animations/north/north_0018.bin deleted file mode 100644 index 3332c64..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/north/north_0019.bin b/components/platform/assets/lvgl_animations/north/north_0019.bin deleted file mode 100644 index 3e42e1b..0000000 Binary files a/components/platform/assets/lvgl_animations/north/north_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/.gitkeep b/components/platform/assets/lvgl_animations/northeast/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0000.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0000.bin deleted file mode 100644 index e6c6aef..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0001.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0001.bin deleted file mode 100644 index 81676f4..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0002.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0002.bin deleted file mode 100644 index dd85ce9..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0003.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0003.bin deleted file mode 100644 index 3c19b34..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0004.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0004.bin deleted file mode 100644 index deafd50..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0005.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0005.bin deleted file mode 100644 index 8af837f..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0006.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0006.bin deleted file mode 100644 index f0b9e22..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0007.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0007.bin deleted file mode 100644 index dad4625..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0008.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0008.bin deleted file mode 100644 index 7b51d4c..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0009.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0009.bin deleted file mode 100644 index bca9856..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0010.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0010.bin deleted file mode 100644 index fb0c00e..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0011.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0011.bin deleted file mode 100644 index 4c52e45..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0012.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0012.bin deleted file mode 100644 index 82e3e0e..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0013.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0013.bin deleted file mode 100644 index 60db228..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0014.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0014.bin deleted file mode 100644 index 6c39bff..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0015.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0015.bin deleted file mode 100644 index f496f8d..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0016.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0016.bin deleted file mode 100644 index 0763174..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0017.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0017.bin deleted file mode 100644 index d36802c..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0018.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0018.bin deleted file mode 100644 index bdd3852..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northeast/northeast_0019.bin b/components/platform/assets/lvgl_animations/northeast/northeast_0019.bin deleted file mode 100644 index 3a45376..0000000 Binary files a/components/platform/assets/lvgl_animations/northeast/northeast_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/.gitkeep b/components/platform/assets/lvgl_animations/northwest/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0000.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0000.bin deleted file mode 100644 index 62d02c7..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0001.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0001.bin deleted file mode 100644 index 1bb6110..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0002.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0002.bin deleted file mode 100644 index f9d6291..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0003.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0003.bin deleted file mode 100644 index 8366045..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0004.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0004.bin deleted file mode 100644 index 271540c..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0005.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0005.bin deleted file mode 100644 index 6d745cc..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0006.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0006.bin deleted file mode 100644 index 6846e22..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0007.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0007.bin deleted file mode 100644 index 48e1801..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0008.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0008.bin deleted file mode 100644 index 35ed373..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0009.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0009.bin deleted file mode 100644 index 94ee8ab..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0010.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0010.bin deleted file mode 100644 index 0ec4a02..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0011.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0011.bin deleted file mode 100644 index ce0dfe6..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0012.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0012.bin deleted file mode 100644 index b5b7037..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0013.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0013.bin deleted file mode 100644 index 3059545..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0014.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0014.bin deleted file mode 100644 index 66cff96..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0015.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0015.bin deleted file mode 100644 index c16357b..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0016.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0016.bin deleted file mode 100644 index dbab389..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0017.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0017.bin deleted file mode 100644 index 1289e03..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0018.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0018.bin deleted file mode 100644 index 455ace2..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/northwest/northwest_0019.bin b/components/platform/assets/lvgl_animations/northwest/northwest_0019.bin deleted file mode 100644 index 0b3f0d1..0000000 Binary files a/components/platform/assets/lvgl_animations/northwest/northwest_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/server_error/.gitkeep b/components/platform/assets/lvgl_animations/server_error/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0000.bin b/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0000.bin deleted file mode 100644 index acf62cf..0000000 Binary files a/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0001.bin b/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0001.bin deleted file mode 100644 index 4138d8a..0000000 Binary files a/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0002.bin b/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0002.bin deleted file mode 100644 index 1adb0c4..0000000 Binary files a/components/platform/assets/lvgl_animations/server_error/TQ_Cat_Server_Error_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/.gitkeep b/components/platform/assets/lvgl_animations/south/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/south/south_0000.bin b/components/platform/assets/lvgl_animations/south/south_0000.bin deleted file mode 100644 index e917356..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0001.bin b/components/platform/assets/lvgl_animations/south/south_0001.bin deleted file mode 100644 index ceb9cf6..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0002.bin b/components/platform/assets/lvgl_animations/south/south_0002.bin deleted file mode 100644 index a0a59a9..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0003.bin b/components/platform/assets/lvgl_animations/south/south_0003.bin deleted file mode 100644 index 1917ac8..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0004.bin b/components/platform/assets/lvgl_animations/south/south_0004.bin deleted file mode 100644 index f283bc1..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0005.bin b/components/platform/assets/lvgl_animations/south/south_0005.bin deleted file mode 100644 index 586a587..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0006.bin b/components/platform/assets/lvgl_animations/south/south_0006.bin deleted file mode 100644 index 6c2f36e..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0007.bin b/components/platform/assets/lvgl_animations/south/south_0007.bin deleted file mode 100644 index ef4393b..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0008.bin b/components/platform/assets/lvgl_animations/south/south_0008.bin deleted file mode 100644 index d4273cf..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0009.bin b/components/platform/assets/lvgl_animations/south/south_0009.bin deleted file mode 100644 index 4c7ce8f..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0010.bin b/components/platform/assets/lvgl_animations/south/south_0010.bin deleted file mode 100644 index 8c822f0..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0011.bin b/components/platform/assets/lvgl_animations/south/south_0011.bin deleted file mode 100644 index 435ea0c..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0012.bin b/components/platform/assets/lvgl_animations/south/south_0012.bin deleted file mode 100644 index 36bf756..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0013.bin b/components/platform/assets/lvgl_animations/south/south_0013.bin deleted file mode 100644 index 169ee18..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0014.bin b/components/platform/assets/lvgl_animations/south/south_0014.bin deleted file mode 100644 index f837461..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0015.bin b/components/platform/assets/lvgl_animations/south/south_0015.bin deleted file mode 100644 index da5236e..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0016.bin b/components/platform/assets/lvgl_animations/south/south_0016.bin deleted file mode 100644 index 8a7ca09..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0017.bin b/components/platform/assets/lvgl_animations/south/south_0017.bin deleted file mode 100644 index c2cf5ab..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0018.bin b/components/platform/assets/lvgl_animations/south/south_0018.bin deleted file mode 100644 index ea81dd8..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/south/south_0019.bin b/components/platform/assets/lvgl_animations/south/south_0019.bin deleted file mode 100644 index 769298e..0000000 Binary files a/components/platform/assets/lvgl_animations/south/south_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/.gitkeep b/components/platform/assets/lvgl_animations/southeast/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0000.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0000.bin deleted file mode 100644 index 8a08d59..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0001.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0001.bin deleted file mode 100644 index ec86759..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0002.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0002.bin deleted file mode 100644 index 0f13e1c..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0003.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0003.bin deleted file mode 100644 index 46f2ca4..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0004.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0004.bin deleted file mode 100644 index cbad89b..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0005.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0005.bin deleted file mode 100644 index ac1eaa7..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0006.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0006.bin deleted file mode 100644 index bf32cf2..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0007.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0007.bin deleted file mode 100644 index a3a9c0b..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0008.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0008.bin deleted file mode 100644 index 6b82540..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0009.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0009.bin deleted file mode 100644 index b1f0a86..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0010.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0010.bin deleted file mode 100644 index 234279b..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0011.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0011.bin deleted file mode 100644 index 58b0064..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0012.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0012.bin deleted file mode 100644 index c5b6e56..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0013.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0013.bin deleted file mode 100644 index 5ef8d95..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0014.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0014.bin deleted file mode 100644 index 1ed2d36..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0015.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0015.bin deleted file mode 100644 index d3f68c7..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0016.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0016.bin deleted file mode 100644 index 97d7ada..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0017.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0017.bin deleted file mode 100644 index d12d72e..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0018.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0018.bin deleted file mode 100644 index ee0181f..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southeast/southeast_0019.bin b/components/platform/assets/lvgl_animations/southeast/southeast_0019.bin deleted file mode 100644 index c3dc8bc..0000000 Binary files a/components/platform/assets/lvgl_animations/southeast/southeast_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/.gitkeep b/components/platform/assets/lvgl_animations/southwest/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0000.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0000.bin deleted file mode 100644 index 6f67813..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0001.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0001.bin deleted file mode 100644 index f54e4a4..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0002.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0002.bin deleted file mode 100644 index 5667e87..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0003.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0003.bin deleted file mode 100644 index ed56184..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0004.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0004.bin deleted file mode 100644 index 0018972..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0005.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0005.bin deleted file mode 100644 index b23e537..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0006.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0006.bin deleted file mode 100644 index 29461ce..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0007.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0007.bin deleted file mode 100644 index 0cde4b4..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0008.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0008.bin deleted file mode 100644 index 11bffd8..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0009.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0009.bin deleted file mode 100644 index c46b836..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0010.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0010.bin deleted file mode 100644 index ad0ae96..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0011.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0011.bin deleted file mode 100644 index 249f449..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0012.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0012.bin deleted file mode 100644 index 1cb2ff7..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0013.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0013.bin deleted file mode 100644 index 058c6ec..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0014.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0014.bin deleted file mode 100644 index 003d48c..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0015.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0015.bin deleted file mode 100644 index ab10e43..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0016.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0016.bin deleted file mode 100644 index f3711e4..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0017.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0017.bin deleted file mode 100644 index d61baeb..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0018.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0018.bin deleted file mode 100644 index c43d364..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/southwest/southwest_0019.bin b/components/platform/assets/lvgl_animations/southwest/southwest_0019.bin deleted file mode 100644 index f4ad7d5..0000000 Binary files a/components/platform/assets/lvgl_animations/southwest/southwest_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/.gitkeep b/components/platform/assets/lvgl_animations/spin/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/spin/spin_0000.bin b/components/platform/assets/lvgl_animations/spin/spin_0000.bin deleted file mode 100644 index 6a7c628..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0001.bin b/components/platform/assets/lvgl_animations/spin/spin_0001.bin deleted file mode 100644 index a527c27..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0002.bin b/components/platform/assets/lvgl_animations/spin/spin_0002.bin deleted file mode 100644 index 13cf02c..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0003.bin b/components/platform/assets/lvgl_animations/spin/spin_0003.bin deleted file mode 100644 index 25ec8d2..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0004.bin b/components/platform/assets/lvgl_animations/spin/spin_0004.bin deleted file mode 100644 index b4304b2..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0005.bin b/components/platform/assets/lvgl_animations/spin/spin_0005.bin deleted file mode 100644 index 968f4a9..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0006.bin b/components/platform/assets/lvgl_animations/spin/spin_0006.bin deleted file mode 100644 index aa1a957..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0007.bin b/components/platform/assets/lvgl_animations/spin/spin_0007.bin deleted file mode 100644 index 51e415d..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0008.bin b/components/platform/assets/lvgl_animations/spin/spin_0008.bin deleted file mode 100644 index 1437836..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0009.bin b/components/platform/assets/lvgl_animations/spin/spin_0009.bin deleted file mode 100644 index c9cc760..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0010.bin b/components/platform/assets/lvgl_animations/spin/spin_0010.bin deleted file mode 100644 index 651e7a9..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/spin/spin_0011.bin b/components/platform/assets/lvgl_animations/spin/spin_0011.bin deleted file mode 100644 index eb60608..0000000 Binary files a/components/platform/assets/lvgl_animations/spin/spin_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/.gitkeep b/components/platform/assets/lvgl_animations/west/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/west/west_0000.bin b/components/platform/assets/lvgl_animations/west/west_0000.bin deleted file mode 100644 index 447342d..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0001.bin b/components/platform/assets/lvgl_animations/west/west_0001.bin deleted file mode 100644 index 46a65b5..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0002.bin b/components/platform/assets/lvgl_animations/west/west_0002.bin deleted file mode 100644 index d995e58..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0003.bin b/components/platform/assets/lvgl_animations/west/west_0003.bin deleted file mode 100644 index b08b190..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0003.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0004.bin b/components/platform/assets/lvgl_animations/west/west_0004.bin deleted file mode 100644 index 5774fbc..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0004.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0005.bin b/components/platform/assets/lvgl_animations/west/west_0005.bin deleted file mode 100644 index d9f567a..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0005.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0006.bin b/components/platform/assets/lvgl_animations/west/west_0006.bin deleted file mode 100644 index 15e56df..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0006.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0007.bin b/components/platform/assets/lvgl_animations/west/west_0007.bin deleted file mode 100644 index 578858f..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0007.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0008.bin b/components/platform/assets/lvgl_animations/west/west_0008.bin deleted file mode 100644 index dca9e66..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0008.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0009.bin b/components/platform/assets/lvgl_animations/west/west_0009.bin deleted file mode 100644 index 47210bd..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0009.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0010.bin b/components/platform/assets/lvgl_animations/west/west_0010.bin deleted file mode 100644 index 681caeb..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0010.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0011.bin b/components/platform/assets/lvgl_animations/west/west_0011.bin deleted file mode 100644 index 84f79a9..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0011.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0012.bin b/components/platform/assets/lvgl_animations/west/west_0012.bin deleted file mode 100644 index 20bbeed..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0012.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0013.bin b/components/platform/assets/lvgl_animations/west/west_0013.bin deleted file mode 100644 index ab51207..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0013.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0014.bin b/components/platform/assets/lvgl_animations/west/west_0014.bin deleted file mode 100644 index 93a7e02..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0014.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0015.bin b/components/platform/assets/lvgl_animations/west/west_0015.bin deleted file mode 100644 index bcaad1c..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0015.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0016.bin b/components/platform/assets/lvgl_animations/west/west_0016.bin deleted file mode 100644 index a83ce5e..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0016.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0017.bin b/components/platform/assets/lvgl_animations/west/west_0017.bin deleted file mode 100644 index 82d49f6..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0017.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0018.bin b/components/platform/assets/lvgl_animations/west/west_0018.bin deleted file mode 100644 index a0c880e..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0018.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/west/west_0019.bin b/components/platform/assets/lvgl_animations/west/west_0019.bin deleted file mode 100644 index f8545a8..0000000 Binary files a/components/platform/assets/lvgl_animations/west/west_0019.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/wifi_disconnected/.gitkeep b/components/platform/assets/lvgl_animations/wifi_disconnected/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0000.bin b/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0000.bin deleted file mode 100644 index c68c56b..0000000 Binary files a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0000.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0001.bin b/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0001.bin deleted file mode 100644 index 0212798..0000000 Binary files a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0001.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0002.bin b/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0002.bin deleted file mode 100644 index 27c9f1c..0000000 Binary files a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0002.bin and /dev/null differ diff --git a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0003.bin b/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0003.bin deleted file mode 100644 index 7e0ffc8..0000000 Binary files a/components/platform/assets/lvgl_animations/wifi_disconnected/TQ_Cat_wifi_disconnected_0003.bin and /dev/null differ diff --git a/components/platform/idf_component.yml b/components/platform/idf_component.yml index 4ddb3bf..bc3ea83 100644 --- a/components/platform/idf_component.yml +++ b/components/platform/idf_component.yml @@ -1,6 +1,5 @@ dependencies: idf: '>=4.4' - lvgl/lvgl: 9.2.0 joltwallet/littlefs: 1.20.4 espressif/esp_websocket_client: ^1.6.1 espressif/esp_codec_dev: ^1.5.4 diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index 8d57224..8566201 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -322,6 +322,7 @@ esp_err_t platform_multimodal_audio_register_downstream_playback_callbacks( const platform_multimodal_downstream_playback_callbacks_t *callbacks); esp_err_t platform_init_lcd_device(void); +void platform_run_lcd_blocking_test(void); /** * @brief Start LCD render loop. @@ -405,7 +406,7 @@ uint32_t platform_lcd_presentation_completion_count(void); * @brief Block until a finite asset-sequence presentation completes. * * Semantics: - * - Blocks the calling task (never the LVGL render task) until the completion + * - Blocks the calling task until the completion * counter advances past `since_count`, or `timeout_ms` elapses. * - `timeout_ms == 0` polls once and returns without waiting. * - Returns `ESP_OK` on completion, `ESP_ERR_TIMEOUT` on timeout, and diff --git a/components/platform/internal/lcd.h b/components/platform/internal/lcd.h deleted file mode 100644 index 4528b8e..0000000 --- a/components/platform/internal/lcd.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "esp_err.h" -#include "platform.h" - -/** - * @brief Initialize the LCD device and LVGL display pipeline. - */ -esp_err_t lcd_init_device(void); - -/** - * @brief Enable LCD backlight. - */ -void lcd_backlight_on(void); - -/** - * @brief Start the LVGL render loop and show the default screen. - * - * This function is blocking and is expected to run in a dedicated task. - * Calling it while the render loop is already running is a no-op. - */ -void lcd_start_render_loop(void); - -/** - * @brief Request the running LVGL render loop to stop. - * - * This API is idempotent; calling it when the render loop is not running is safe. - * `timeout_ms < 0` is invalid; `0` requests async stop; positive values wait - * up to the bound and force-delete the render task on timeout. - */ -esp_err_t lcd_stop_render_loop(int timeout_ms); - -/** - * @brief Queue a LCD presentation request. - * - * This API is non-blocking and safe to call from non-LVGL tasks. - */ -esp_err_t lcd_request_presentation(const platform_lcd_presentation_request_t *request); - -/** - * @brief Queue a LCD presentation asset preload. - * - * This API is non-blocking and safe to call from non-LVGL tasks. - */ -esp_err_t lcd_preheat_presentation(const platform_lcd_presentation_request_t *request); - -/** - * @brief Read the LCD presentation completion counter. - * - * Incremented each time a finite asset-sequence presentation finishes its - * configured cycles. Safe to call from non-LVGL tasks. - */ -uint32_t lcd_presentation_completion_count(void); - -/** - * @brief Block until the completion counter advances past @p since_count or - * @p timeout_ms elapses. - * - * Pair with a lcd_presentation_completion_count() snapshot taken before queuing - * a finite asset sequence. Returns ESP_OK on completion, ESP_ERR_TIMEOUT on - * timeout, ESP_ERR_INVALID_STATE if the LCD is not running or is stopping. A - * zero timeout polls once. Safe to call from non-LVGL tasks; never call from - * the LVGL render task. - */ -esp_err_t lcd_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms); diff --git a/components/platform/internal/lcd_direct.h b/components/platform/internal/lcd_direct.h new file mode 100644 index 0000000..9687faa --- /dev/null +++ b/components/platform/internal/lcd_direct.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "esp_err.h" +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t lcd_direct_init_device(void); +void lcd_direct_run_blocking_test(void); +void lcd_direct_start_render_loop(void); +esp_err_t lcd_direct_stop_render_loop(int timeout_ms); +esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_request_t *request); +esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_request_t *request); +uint32_t lcd_direct_presentation_completion_count(void); +esp_err_t lcd_direct_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/components/platform/internal/lcd_direct_assets.h b/components/platform/internal/lcd_direct_assets.h new file mode 100644 index 0000000..4928c98 --- /dev/null +++ b/components/platform/internal/lcd_direct_assets.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t color; + uint16_t count; +} lcd_direct_rle_run_t; + +typedef struct { + const lcd_direct_rle_run_t *runs; + uint32_t run_count; +} lcd_direct_image_t; + +typedef struct { + const lcd_direct_rle_run_t *runs; + uint32_t run_count; + uint16_t width; + uint16_t height; +} lcd_direct_icon_t; + +typedef struct { + uint16_t icon_id; + uint16_t x; + uint16_t y; +} lcd_direct_icon_draw_t; + +#define LCD_DIRECT_NO_ICON 0xFFFFU +#define LCD_DIRECT_DEFAULT_ICON_COUNT 5U + +const lcd_direct_image_t *lcd_direct_default_image(void); +const lcd_direct_icon_t *lcd_direct_icon(uint16_t icon_id); +const lcd_direct_icon_draw_t *lcd_direct_default_icon_sequence(size_t *out_count); + +#ifdef __cplusplus +} +#endif diff --git a/components/platform/internal/lcd_face.h b/components/platform/internal/lcd_face.h deleted file mode 100644 index cac0bf4..0000000 --- a/components/platform/internal/lcd_face.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "esp_err.h" -#include "lvgl.h" - -typedef struct { - const char *asset_dir; - const uint8_t *frame_order; - size_t frame_order_count; - uint16_t repeat_count; - uint32_t frame_ms; - bool return_to_idle; -} lcd_face_asset_sequence_t; - -/** - * @brief Create the default cat-face idle screen on the active LVGL screen. - */ -esp_err_t lcd_face_create_idle_screen(lv_obj_t *screen); - -/** - * @brief Restore the default cat-face idle screen. - */ -esp_err_t lcd_face_show_idle(lv_obj_t *screen); - -/** - * @brief Play a frame sequence from an animation asset directory. - */ -esp_err_t lcd_face_play_asset_sequence(lv_obj_t *screen, const lcd_face_asset_sequence_t *sequence); - -/** - * @brief Preload a frame sequence into the animation cache. - * - * `decode_budget` limits how many compressed frames are decoded during this - * call. Pass `SIZE_MAX` to finish synchronously. - */ -esp_err_t lcd_face_preheat_asset_sequence(const lcd_face_asset_sequence_t *sequence, - size_t decode_budget, - bool *out_complete); - -/** - * @brief Consume a completed finite asset-sequence signal. - */ -bool lcd_face_take_completed(bool *out_return_to_idle); diff --git a/components/platform/src/lcd.c b/components/platform/src/lcd.c deleted file mode 100644 index f142811..0000000 --- a/components/platform/src/lcd.c +++ /dev/null @@ -1,1556 +0,0 @@ -/* - * LCD component: single ST7789V panel driven by esp_lcd + LVGL. - */ - -#include "lcd.h" - -#include -#include -#include - -#include "driver/gpio.h" -#include "driver/spi_master.h" -#include "esp_check.h" -#include "esp_err.h" -#include "esp_heap_caps.h" -#include "esp_lcd_panel_commands.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_log.h" -#include "esp_random.h" -#include "esp_timer.h" -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "freertos/task.h" -#include "lvgl.h" -#include "lcd_face.h" -#include "sdkconfig.h" -#include "soc/soc_caps.h" - -#define LCD_HOST SPI2_HOST -#define LCD_PIXEL_CLOCK_HZ CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ -#define LCD_SPI_MODE 0 - -#ifndef CONFIG_TQ_SCREEN_SPI_MISO_GPIO -#error "CONFIG_TQ_SCREEN_SPI_MISO_GPIO must be defined in Kconfig" -#endif -#ifndef CONFIG_TQ_SCREEN_SPI_MOSI_GPIO -#error "CONFIG_TQ_SCREEN_SPI_MOSI_GPIO must be defined in Kconfig" -#endif -#ifndef CONFIG_TQ_SCREEN_SPI_CLK_GPIO -#error "CONFIG_TQ_SCREEN_SPI_CLK_GPIO must be defined in Kconfig" -#endif -#ifndef CONFIG_TQ_SCREEN_SPI_CS_GPIO -#error "CONFIG_TQ_SCREEN_SPI_CS_GPIO must be defined in Kconfig" -#endif -#ifndef CONFIG_TQ_SCREEN_DC_GPIO -#error "CONFIG_TQ_SCREEN_DC_GPIO must be defined in Kconfig" -#endif -#ifndef CONFIG_TQ_SCREEN_RESET_PIN -#error "CONFIG_TQ_SCREEN_RESET_PIN must be defined in Kconfig" -#endif -#ifndef CONFIG_TQ_SCREEN_BACKLIGHT_GPIO -#error "CONFIG_TQ_SCREEN_BACKLIGHT_GPIO must be defined in Kconfig" -#endif - -#define PIN_NUM_MISO CONFIG_TQ_SCREEN_SPI_MISO_GPIO -#define PIN_NUM_MOSI CONFIG_TQ_SCREEN_SPI_MOSI_GPIO -#define PIN_NUM_CLK CONFIG_TQ_SCREEN_SPI_CLK_GPIO -#define PIN_NUM_CS CONFIG_TQ_SCREEN_SPI_CS_GPIO -#define PIN_NUM_DC CONFIG_TQ_SCREEN_DC_GPIO -#define PIN_NUM_RST CONFIG_TQ_SCREEN_RESET_PIN -#define PIN_NUM_BCKL CONFIG_TQ_SCREEN_BACKLIGHT_GPIO - -#if CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH -#define LCD_BK_LIGHT_ON_LEVEL 1 -#else -#define LCD_BK_LIGHT_ON_LEVEL 0 -#endif - -#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR -#define LCD_PANEL_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR -#else -#define LCD_PANEL_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB -#endif - -#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES -#define LCD_PANEL_DATA_ENDIAN LCD_RGB_DATA_ENDIAN_LITTLE -#define LCD_BOOT_SWAP_COLOR_BYTES true -#else -#define LCD_PANEL_DATA_ENDIAN LCD_RGB_DATA_ENDIAN_BIG -#define LCD_BOOT_SWAP_COLOR_BYTES false -#endif - -#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR -#define LCD_BOOT_COLOR_ORDER_BGR true -#else -#define LCD_BOOT_COLOR_ORDER_BGR false -#endif - -#if CONFIG_TQ_SCREEN_INVERT_COLOR -#define LCD_BOOT_INVERT_COLOR true -#else -#define LCD_BOOT_INVERT_COLOR false -#endif - -#if CONFIG_TQ_SCREEN_MIRROR_X -#define LCD_PANEL_MIRROR_X true -#else -#define LCD_PANEL_MIRROR_X false -#endif - -#if CONFIG_TQ_SCREEN_SWAP_XY -#define LCD_PANEL_SWAP_XY true -#else -#define LCD_PANEL_SWAP_XY false -#endif - -#define LCD_H_RES 320 -#define LCD_V_RES 240 -#define LCD_DRAW_BUF_LINES CONFIG_TQ_SCREEN_LVGL_DRAW_BUF_LINES -#define LCD_TICK_PERIOD_MS 2 -#define LCD_RENDER_TASK_DELAY_MAXMS 20 -#define ST7789_CMD_RAMCTRL 0xB0 -#define ST7789_RAMCTRL_PARAM1 0x00 -#define ST7789_RAMCTRL_PARAM2_BASE 0xF0 -#define ST7789_RAMCTRL_LE_BIT (1 << 3) -#define LCD_SLOT_REEL_COUNT 3 -#define LCD_SLOT_SYMBOL_COUNT 5 -#define LCD_SLOT_RESULT_HOLD_MS 600 -#define LCD_SLOT_STATUS_SPIN_PHASES 4 -#define LCD_PRESENTATION_DEFAULT_FRAME_MS 150 -#define LCD_PRESENTATION_WAIT_POLL_MS 20 -#define LCD_PREHEAT_DECODE_FRAMES_PER_LOOP 1 -#define LCD_BOOT_PREHEAT_ASSET_DIR "spin" - -typedef struct { - platform_lcd_presentation_kind_t kind; - uint8_t priority; - bool interrupt_current; - bool return_to_idle; - char asset_dir[PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX]; - uint8_t frame_order[PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX]; - size_t frame_order_count; - uint16_t repeat_count; - uint32_t frame_ms; -} lcd_presentation_request_t; - -static const char *TAG = "lcd"; - -static SemaphoreHandle_t s_lvgl_mutex; -static esp_lcd_panel_io_handle_t s_panel_io; -static esp_lcd_panel_handle_t s_panel; -static lv_display_t *s_display; -static esp_timer_handle_t s_lvgl_tick_timer; -static bool s_initialized; -static bool s_default_screen_ready; -static bool s_render_loop_running; -static bool s_render_loop_stop_requested; -static bool s_presentation_pending; -static bool s_presentation_active; -static bool s_preheat_pending; -static bool s_preheat_active; -static lcd_presentation_request_t s_pending_presentation; -static lcd_presentation_request_t s_active_presentation; -static lcd_presentation_request_t s_pending_preheat; -static lcd_presentation_request_t s_active_preheat; -static uint32_t s_presentation_completion_count; -static portMUX_TYPE s_presentation_lock = portMUX_INITIALIZER_UNLOCKED; -static SemaphoreHandle_t s_presentation_completion_sem; -static TaskHandle_t s_render_loop_task; -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST -static lv_obj_t *s_color_profile_label; -#endif - -typedef struct { - uint8_t cmd; - uint8_t data[16]; - uint8_t databytes; // Number of bytes in data; bit7 means delay after command; 0xFF marks end. -} lcd_init_cmd_t; - -typedef struct { - bool color_order_bgr; - bool swap_color_bytes; - bool invert_color; -} lcd_color_profile_t; - -static void lcd_log_heap_snapshot(const char *phase) -{ - size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - size_t largest_internal = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - size_t largest_dma = heap_caps_get_largest_free_block(MALLOC_CAP_DMA); -#if SOC_SPIRAM_SUPPORTED - size_t free_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - size_t largest_spiram = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - ESP_LOGI(TAG, - "Heap %s: free_internal=%u largest_internal=%u largest_dma=%u free_spiram=%u largest_spiram=%u", - phase != NULL ? phase : "", - (unsigned)free_internal, - (unsigned)largest_internal, - (unsigned)largest_dma, - (unsigned)free_spiram, - (unsigned)largest_spiram); -#else - ESP_LOGI(TAG, - "Heap %s: free_internal=%u largest_internal=%u largest_dma=%u", - phase != NULL ? phase : "", - (unsigned)free_internal, - (unsigned)largest_internal, - (unsigned)largest_dma); -#endif -} - -typedef struct { - lv_obj_t *panel; - lv_obj_t *top_label; - lv_obj_t *center_label; - lv_obj_t *bottom_label; - uint32_t start_index; - uint32_t current_index; - uint32_t final_index; - uint32_t stop_ms; - uint32_t step_interval_ms; - bool stopped; -} lcd_slot_reel_t; - -typedef struct { - lv_obj_t *overlay; - lv_obj_t *status_label; - int64_t start_ms; - int64_t hold_deadline_ms; - lcd_slot_reel_t reels[LCD_SLOT_REEL_COUNT]; -} lcd_slot_animation_state_t; - -static const lcd_color_profile_t s_boot_color_profile = { - .color_order_bgr = LCD_BOOT_COLOR_ORDER_BGR, - .swap_color_bytes = LCD_BOOT_SWAP_COLOR_BYTES, - .invert_color = LCD_BOOT_INVERT_COLOR, -}; - -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST -static const lcd_color_profile_t s_color_profiles[] = { - {.color_order_bgr = false, .swap_color_bytes = false, .invert_color = false}, - {.color_order_bgr = false, .swap_color_bytes = true, .invert_color = false}, - {.color_order_bgr = true, .swap_color_bytes = false, .invert_color = false}, - {.color_order_bgr = true, .swap_color_bytes = true, .invert_color = false}, - {.color_order_bgr = false, .swap_color_bytes = false, .invert_color = true}, - {.color_order_bgr = false, .swap_color_bytes = true, .invert_color = true}, - {.color_order_bgr = true, .swap_color_bytes = false, .invert_color = true}, - {.color_order_bgr = true, .swap_color_bytes = true, .invert_color = true}, -}; -#endif - -static const lcd_init_cmd_t s_st7789_init_cmds[] = { - {0x36, {(1 << 5) | (1 << 6)}, 1}, - {0x3A, {0x55}, 1}, - {0xB2, {0x0C, 0x0C, 0x00, 0x33, 0x33}, 5}, - {0xB7, {0x45}, 1}, - {0xBB, {0x2B}, 1}, - {0xC0, {0x2C}, 1}, - {0xC2, {0x01, 0xFF}, 2}, - {0xC3, {0x11}, 1}, - {0xC4, {0x20}, 1}, - {0xC6, {0x0F}, 1}, - {0xD0, {0xA4, 0xA1}, 2}, - {0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14}, - {0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14}, - {0x11, {0}, 0x80}, - {0x29, {0}, 0x80}, - {0, {0}, 0xFF} -}; - -static const char *s_slot_symbols[LCD_SLOT_SYMBOL_COUNT] = { - "7", - "BAR", - "CAT", - "GEM", - "WIN", -}; - -static const uint32_t s_slot_reel_stop_ms[LCD_SLOT_REEL_COUNT] = {1200, 1500, 1800}; -static const uint32_t s_slot_reel_step_interval_ms[LCD_SLOT_REEL_COUNT] = {70, 85, 100}; -static const lv_coord_t s_slot_reel_x_offsets[LCD_SLOT_REEL_COUNT] = {-100, 0, 100}; - -static lcd_slot_animation_state_t s_slot_animation; - -static const char *lcd_presentation_kind_name(platform_lcd_presentation_kind_t kind) -{ - switch (kind) { - case PLATFORM_LCD_PRESENTATION_IDLE: - return "idle"; - case PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE: - return "asset_sequence"; - case PLATFORM_LCD_PRESENTATION_SLOT_REELS: - return "slot_reels"; - default: - return "unknown"; - } -} - -static bool lcd_copy_presentation_request(lcd_presentation_request_t *out, - const platform_lcd_presentation_request_t *request) -{ - if (out == NULL || request == NULL) { - return false; - } - - memset(out, 0, sizeof(*out)); - out->kind = request->kind; - out->priority = request->priority; - out->interrupt_current = request->interrupt_current; - out->return_to_idle = request->return_to_idle; - out->repeat_count = request->repeat_count; - out->frame_ms = request->frame_ms > 0U ? request->frame_ms : LCD_PRESENTATION_DEFAULT_FRAME_MS; - - if (request->frame_order_count > PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX) { - return false; - } - - out->frame_order_count = request->frame_order_count; - if (request->frame_order_count > 0U) { - if (request->frame_order == NULL) { - return false; - } - memcpy(out->frame_order, request->frame_order, request->frame_order_count); - } - - if (request->kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - if (request->asset_dir == NULL || request->asset_dir[0] == '\0') { - return false; - } - - size_t asset_dir_len = strlen(request->asset_dir); - if (asset_dir_len >= sizeof(out->asset_dir)) { - return false; - } - memcpy(out->asset_dir, request->asset_dir, asset_dir_len + 1U); - } - - return true; -} - -static bool lcd_presentation_requests_match(const lcd_presentation_request_t *left, - const lcd_presentation_request_t *right) -{ - if (left == NULL || right == NULL || left->kind != right->kind) { - return false; - } - - if (left->kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - return strcmp(left->asset_dir, right->asset_dir) == 0 && - left->frame_order_count == right->frame_order_count && - memcmp(left->frame_order, right->frame_order, left->frame_order_count) == 0 && - left->repeat_count == right->repeat_count && - left->frame_ms == right->frame_ms; - } - - return true; -} - -static bool lcd_presentation_should_queue_locked(const lcd_presentation_request_t *request) -{ - if (s_presentation_pending) { - if (lcd_presentation_requests_match(request, &s_pending_presentation)) { - return false; - } - if (request->priority < s_pending_presentation.priority) { - return false; - } - if (request->priority == s_pending_presentation.priority && !request->interrupt_current) { - return false; - } - } - - if (s_presentation_active) { - if (lcd_presentation_requests_match(request, &s_active_presentation)) { - return false; - } - if (request->priority < s_active_presentation.priority) { - return false; - } - if (request->priority == s_active_presentation.priority && !request->interrupt_current) { - return false; - } - } - - return true; -} - -static void lcd_presentation_set_active(const lcd_presentation_request_t *request) -{ - portENTER_CRITICAL(&s_presentation_lock); - if (request != NULL && request->kind != PLATFORM_LCD_PRESENTATION_IDLE) { - s_active_presentation = *request; - s_presentation_active = true; - } else { - memset(&s_active_presentation, 0, sizeof(s_active_presentation)); - s_presentation_active = false; - } - portEXIT_CRITICAL(&s_presentation_lock); -} - -static bool lcd_presentation_take_pending(lcd_presentation_request_t *out) -{ - bool has_pending = false; - - portENTER_CRITICAL(&s_presentation_lock); - if (s_presentation_pending) { - *out = s_pending_presentation; - memset(&s_pending_presentation, 0, sizeof(s_pending_presentation)); - s_presentation_pending = false; - has_pending = true; - } - portEXIT_CRITICAL(&s_presentation_lock); - - return has_pending; -} - -static bool lcd_preheat_take_pending(lcd_presentation_request_t *out) -{ - bool has_pending = false; - - portENTER_CRITICAL(&s_presentation_lock); - if (s_preheat_pending) { - *out = s_pending_preheat; - memset(&s_pending_preheat, 0, sizeof(s_pending_preheat)); - s_preheat_pending = false; - has_pending = true; - } - portEXIT_CRITICAL(&s_presentation_lock); - - return has_pending; -} - -static bool lcd_presentation_get_active(lcd_presentation_request_t *out) -{ - bool is_active = false; - - portENTER_CRITICAL(&s_presentation_lock); - if (s_presentation_active) { - if (out != NULL) { - *out = s_active_presentation; - } - is_active = true; - } - portEXIT_CRITICAL(&s_presentation_lock); - - return is_active; -} - -static TaskHandle_t lcd_get_render_loop_task_handle(void) -{ - TaskHandle_t task = NULL; - - portENTER_CRITICAL(&s_presentation_lock); - task = s_render_loop_task; - portEXIT_CRITICAL(&s_presentation_lock); - - return task; -} - -static bool lcd_render_loop_mark_started(TaskHandle_t task) -{ - bool started = false; - - portENTER_CRITICAL(&s_presentation_lock); - if (!s_render_loop_running && s_render_loop_task == NULL) { - s_render_loop_task = task; - s_render_loop_running = true; - s_render_loop_stop_requested = false; - started = true; - } - portEXIT_CRITICAL(&s_presentation_lock); - - return started; -} - -static bool lcd_render_loop_stop_requested(void) -{ - bool stop_requested; - - portENTER_CRITICAL(&s_presentation_lock); - stop_requested = s_render_loop_stop_requested; - portEXIT_CRITICAL(&s_presentation_lock); - - return stop_requested; -} - -static bool lcd_render_loop_stopped(void) -{ - bool stopped; - - portENTER_CRITICAL(&s_presentation_lock); - stopped = !s_render_loop_running && s_render_loop_task == NULL; - portEXIT_CRITICAL(&s_presentation_lock); - - return stopped; -} - -static TaskHandle_t lcd_render_loop_request_stop(void) -{ - TaskHandle_t task; - - portENTER_CRITICAL(&s_presentation_lock); - if (s_render_loop_running || s_render_loop_task != NULL) { - s_render_loop_stop_requested = true; - } else { - s_render_loop_stop_requested = false; - } - task = s_render_loop_task; - portEXIT_CRITICAL(&s_presentation_lock); - - return task; -} - -static void lcd_render_loop_mark_stopped(TaskHandle_t task) -{ - portENTER_CRITICAL(&s_presentation_lock); - if (s_render_loop_task == task) { - s_render_loop_task = NULL; - } - s_render_loop_running = false; - s_render_loop_stop_requested = false; - portEXIT_CRITICAL(&s_presentation_lock); -} - -static void lcd_render_loop_force_stopped(TaskHandle_t task) -{ - portENTER_CRITICAL(&s_presentation_lock); - if (s_render_loop_task == task) { - s_render_loop_task = NULL; - } - s_render_loop_running = false; - s_render_loop_stop_requested = false; - portEXIT_CRITICAL(&s_presentation_lock); -} - -static void lcd_notify_render_task(void) -{ - TaskHandle_t task = lcd_get_render_loop_task_handle(); - if (task != NULL) { - xTaskNotifyGive(task); - } -} - -static uint32_t lcd_slot_wrap_index(int32_t index) -{ - int32_t wrapped = index % LCD_SLOT_SYMBOL_COUNT; - if (wrapped < 0) { - wrapped += LCD_SLOT_SYMBOL_COUNT; - } - return (uint32_t)wrapped; -} - -static void lcd_slot_set_reel_style(lcd_slot_reel_t *reel) -{ - if (reel == NULL || reel->panel == NULL || reel->center_label == NULL) { - return; - } - - lv_color_t border_color = reel->stopped ? lv_color_hex(0xD9A441) : lv_color_hex(0x4A4A4A); - lv_color_t center_color = reel->stopped ? lv_color_hex(0xFFE6A3) : lv_color_hex(0xFFFFFF); - - lv_obj_set_style_border_color(reel->panel, border_color, LV_PART_MAIN); - lv_obj_set_style_text_color(reel->center_label, center_color, LV_PART_MAIN); -} - -static void lcd_slot_update_reel_labels(lcd_slot_reel_t *reel, uint32_t center_index) -{ - if (reel == NULL || - reel->top_label == NULL || - reel->center_label == NULL || - reel->bottom_label == NULL) { - return; - } - - if (reel->current_index == center_index) { - return; - } - - reel->current_index = center_index; - lv_label_set_text(reel->top_label, - s_slot_symbols[lcd_slot_wrap_index((int32_t)center_index - 1)]); - lv_label_set_text(reel->center_label, - s_slot_symbols[lcd_slot_wrap_index((int32_t)center_index)]); - lv_label_set_text(reel->bottom_label, - s_slot_symbols[lcd_slot_wrap_index((int32_t)center_index + 1)]); -} - -static void lcd_slot_update_status_label(bool all_stopped, int64_t elapsed_ms) -{ - if (s_slot_animation.status_label == NULL) { - return; - } - - if (!all_stopped) { - static const char *const spin_status[LCD_SLOT_STATUS_SPIN_PHASES] = { - "SPIN", - "SPIN.", - "SPIN..", - "SPIN..." - }; - size_t phase = (size_t)((elapsed_ms / 180) % LCD_SLOT_STATUS_SPIN_PHASES); - lv_label_set_text(s_slot_animation.status_label, spin_status[phase]); - return; - } - - uint32_t left = s_slot_animation.reels[0].final_index; - uint32_t mid = s_slot_animation.reels[1].final_index; - uint32_t right = s_slot_animation.reels[2].final_index; - - if (left == mid && mid == right) { - lv_label_set_text(s_slot_animation.status_label, "JACKPOT"); - } else if (left == mid || mid == right || left == right) { - lv_label_set_text(s_slot_animation.status_label, "DOUBLE"); - } else { - lv_label_set_text(s_slot_animation.status_label, "SPIN COMPLETE"); - } -} - -static void lcd_slot_delete_overlay(void) -{ - if (s_slot_animation.overlay != NULL) { - lv_obj_delete(s_slot_animation.overlay); - } - - memset(&s_slot_animation, 0, sizeof(s_slot_animation)); -} - -static void lcd_slot_create_reel(size_t index) -{ - lcd_slot_reel_t *reel = &s_slot_animation.reels[index]; - - reel->panel = lv_obj_create(s_slot_animation.overlay); - if (reel->panel == NULL) { - return; - } - - lv_obj_set_size(reel->panel, 82, 124); - lv_obj_align(reel->panel, LV_ALIGN_CENTER, s_slot_reel_x_offsets[index], 6); - lv_obj_set_style_bg_color(reel->panel, lv_color_hex(0x151515), LV_PART_MAIN); - lv_obj_set_style_border_width(reel->panel, 2, LV_PART_MAIN); - lv_obj_set_style_radius(reel->panel, 10, LV_PART_MAIN); - lv_obj_set_style_pad_all(reel->panel, 0, LV_PART_MAIN); - lv_obj_clear_flag(reel->panel, LV_OBJ_FLAG_SCROLLABLE); - - lv_obj_t *marker = lv_obj_create(reel->panel); - if (marker != NULL) { - lv_obj_set_size(marker, 70, 34); - lv_obj_align(marker, LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_bg_color(marker, lv_color_hex(0x24374D), LV_PART_MAIN); - lv_obj_set_style_border_width(marker, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(marker, lv_color_hex(0xD9A441), LV_PART_MAIN); - lv_obj_set_style_radius(marker, 6, LV_PART_MAIN); - lv_obj_clear_flag(marker, LV_OBJ_FLAG_SCROLLABLE); - reel->center_label = lv_label_create(marker); - if (reel->center_label != NULL) { - lv_obj_center(reel->center_label); - } - } - - reel->top_label = lv_label_create(reel->panel); - if (reel->top_label != NULL) { - lv_obj_align(reel->top_label, LV_ALIGN_TOP_MID, 0, 14); - lv_obj_set_style_text_color(reel->top_label, lv_color_hex(0x6F6F6F), LV_PART_MAIN); - } - - reel->bottom_label = lv_label_create(reel->panel); - if (reel->bottom_label != NULL) { - lv_obj_align(reel->bottom_label, LV_ALIGN_BOTTOM_MID, 0, -14); - lv_obj_set_style_text_color(reel->bottom_label, lv_color_hex(0x6F6F6F), LV_PART_MAIN); - } - - reel->start_index = esp_random() % LCD_SLOT_SYMBOL_COUNT; - reel->current_index = UINT32_MAX; - reel->final_index = esp_random() % LCD_SLOT_SYMBOL_COUNT; - reel->stop_ms = s_slot_reel_stop_ms[index]; - reel->step_interval_ms = s_slot_reel_step_interval_ms[index]; - reel->stopped = false; - - lcd_slot_set_reel_style(reel); - lcd_slot_update_reel_labels(reel, reel->start_index); -} - -static esp_err_t lcd_slot_begin_animation(void) -{ - lcd_slot_delete_overlay(); - - s_slot_animation.overlay = lv_obj_create(lv_screen_active()); - if (s_slot_animation.overlay == NULL) { - ESP_LOGE(TAG, "slot overlay create failed"); - return ESP_ERR_NO_MEM; - } - - lv_obj_set_size(s_slot_animation.overlay, LCD_H_RES, LCD_V_RES); - lv_obj_align(s_slot_animation.overlay, LV_ALIGN_CENTER, 0, 0); - lv_obj_set_style_bg_color(s_slot_animation.overlay, lv_color_hex(0x050505), LV_PART_MAIN); - lv_obj_set_style_border_width(s_slot_animation.overlay, 0, LV_PART_MAIN); - lv_obj_set_style_radius(s_slot_animation.overlay, 0, LV_PART_MAIN); - lv_obj_set_style_pad_all(s_slot_animation.overlay, 0, LV_PART_MAIN); - lv_obj_clear_flag(s_slot_animation.overlay, LV_OBJ_FLAG_SCROLLABLE); - - lv_obj_t *title = lv_label_create(s_slot_animation.overlay); - if (title != NULL) { - lv_label_set_text(title, "SLOT MACHINE"); - lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20); - lv_obj_set_style_text_color(title, lv_color_hex(0xF6F1D3), LV_PART_MAIN); - } - - lv_obj_t *frame = lv_obj_create(s_slot_animation.overlay); - if (frame != NULL) { - lv_obj_set_size(frame, 292, 142); - lv_obj_align(frame, LV_ALIGN_CENTER, 0, 6); - lv_obj_set_style_bg_opa(frame, LV_OPA_TRANSP, LV_PART_MAIN); - lv_obj_set_style_border_width(frame, 2, LV_PART_MAIN); - lv_obj_set_style_border_color(frame, lv_color_hex(0x8E6A25), LV_PART_MAIN); - lv_obj_set_style_radius(frame, 12, LV_PART_MAIN); - lv_obj_clear_flag(frame, LV_OBJ_FLAG_SCROLLABLE); - } - - for (size_t i = 0; i < LCD_SLOT_REEL_COUNT; ++i) { - lcd_slot_create_reel(i); - } - - s_slot_animation.status_label = lv_label_create(s_slot_animation.overlay); - if (s_slot_animation.status_label != NULL) { - lv_label_set_text(s_slot_animation.status_label, "SPIN"); - lv_obj_align(s_slot_animation.status_label, LV_ALIGN_BOTTOM_MID, 0, -18); - lv_obj_set_style_text_color(s_slot_animation.status_label, lv_color_hex(0xF6F1D3), LV_PART_MAIN); - } - - s_slot_animation.start_ms = esp_timer_get_time() / 1000; - s_slot_animation.hold_deadline_ms = 0; - ESP_LOGI(TAG, "Slot animation started"); - return ESP_OK; -} - -static bool lcd_slot_update_animation(void) -{ - if (s_slot_animation.overlay == NULL) { - return true; - } - - int64_t now_ms = esp_timer_get_time() / 1000; - int64_t elapsed_ms = now_ms - s_slot_animation.start_ms; - bool all_stopped = true; - - for (size_t i = 0; i < LCD_SLOT_REEL_COUNT; ++i) { - lcd_slot_reel_t *reel = &s_slot_animation.reels[i]; - bool stopped = (uint32_t)elapsed_ms >= reel->stop_ms; - uint32_t center_index = reel->final_index; - - if (!stopped) { - uint32_t steps = (uint32_t)elapsed_ms / reel->step_interval_ms; - center_index = lcd_slot_wrap_index((int32_t)reel->start_index + (int32_t)steps); - all_stopped = false; - } - - if (reel->stopped != stopped) { - reel->stopped = stopped; - lcd_slot_set_reel_style(reel); - } - - lcd_slot_update_reel_labels(reel, center_index); - } - - lcd_slot_update_status_label(all_stopped, elapsed_ms); - - if (!all_stopped) { - return false; - } - - if (s_slot_animation.hold_deadline_ms == 0) { - s_slot_animation.hold_deadline_ms = now_ms + LCD_SLOT_RESULT_HOLD_MS; - ESP_LOGI(TAG, - "Slot animation finished: %s | %s | %s", - s_slot_symbols[s_slot_animation.reels[0].final_index], - s_slot_symbols[s_slot_animation.reels[1].final_index], - s_slot_symbols[s_slot_animation.reels[2].final_index]); - } - - if (now_ms >= s_slot_animation.hold_deadline_ms) { - lcd_slot_delete_overlay(); - return true; - } - - return false; -} - -static void lcd_presentation_cancel_active(void) -{ - lcd_presentation_request_t active = {0}; - if (!lcd_presentation_get_active(&active)) { - return; - } - - if (active.kind == PLATFORM_LCD_PRESENTATION_SLOT_REELS) { - lcd_slot_delete_overlay(); - } else if (active.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - (void)lcd_face_show_idle(lv_screen_active()); - } - - lcd_presentation_set_active(NULL); -} - -static esp_err_t lcd_presentation_start(const lcd_presentation_request_t *request) -{ - if (request == NULL) { - return ESP_ERR_INVALID_ARG; - } - - switch (request->kind) { - case PLATFORM_LCD_PRESENTATION_IDLE: - return lcd_face_show_idle(lv_screen_active()); - - case PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE: { - const lcd_face_asset_sequence_t sequence = { - .asset_dir = request->asset_dir, - .frame_order = request->frame_order, - .frame_order_count = request->frame_order_count, - .repeat_count = request->repeat_count, - .frame_ms = request->frame_ms, - .return_to_idle = request->return_to_idle, - }; - return lcd_face_play_asset_sequence(lv_screen_active(), &sequence); - } - - case PLATFORM_LCD_PRESENTATION_SLOT_REELS: - return lcd_slot_begin_animation(); - - default: - return ESP_ERR_INVALID_ARG; - } -} - -static void lcd_presentation_process_pending(void) -{ - lcd_presentation_request_t request = {0}; - if (!lcd_presentation_take_pending(&request)) { - return; - } - - lcd_presentation_cancel_active(); - - esp_err_t err = lcd_presentation_start(&request); - if (err == ESP_OK) { - ESP_LOGI(TAG, - "LCD presentation started: kind=%s priority=%u", - lcd_presentation_kind_name(request.kind), - (unsigned int)request.priority); - lcd_presentation_set_active(&request); - return; - } - - ESP_LOGW(TAG, - "LCD presentation start failed: kind=%s err=%s", - lcd_presentation_kind_name(request.kind), - esp_err_to_name(err)); - (void)lcd_face_show_idle(lv_screen_active()); - lcd_presentation_set_active(NULL); -} - -static void lcd_preheat_process_pending(void) -{ - lcd_presentation_request_t pending = {0}; - if (lcd_preheat_take_pending(&pending)) { - s_active_preheat = pending; - s_preheat_active = true; - } - - if (!s_preheat_active) { - return; - } - - if (s_active_preheat.kind != PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - memset(&s_active_preheat, 0, sizeof(s_active_preheat)); - s_preheat_active = false; - return; - } - - const lcd_face_asset_sequence_t sequence = { - .asset_dir = s_active_preheat.asset_dir, - .frame_order = s_active_preheat.frame_order, - .frame_order_count = s_active_preheat.frame_order_count, - .repeat_count = s_active_preheat.repeat_count, - .frame_ms = s_active_preheat.frame_ms, - .return_to_idle = s_active_preheat.return_to_idle, - }; - - bool complete = false; - esp_err_t err = lcd_face_preheat_asset_sequence(&sequence, - LCD_PREHEAT_DECODE_FRAMES_PER_LOOP, - &complete); - if (err != ESP_OK) { - ESP_LOGW(TAG, - "LCD preheat failed: asset=%s err=%s", - s_active_preheat.asset_dir, - esp_err_to_name(err)); - memset(&s_active_preheat, 0, sizeof(s_active_preheat)); - s_preheat_active = false; - return; - } - - if (complete) { - ESP_LOGI(TAG, "LCD preheat complete: asset=%s", s_active_preheat.asset_dir); - memset(&s_active_preheat, 0, sizeof(s_active_preheat)); - s_preheat_active = false; - } -} - -static void lcd_schedule_boot_preheat(void) -{ - memset(&s_active_preheat, 0, sizeof(s_active_preheat)); - s_active_preheat.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE; - s_active_preheat.asset_dir[0] = '\0'; - strlcpy(s_active_preheat.asset_dir, LCD_BOOT_PREHEAT_ASSET_DIR, sizeof(s_active_preheat.asset_dir)); - s_active_preheat.frame_ms = LCD_PRESENTATION_DEFAULT_FRAME_MS; - s_preheat_active = true; - ESP_LOGI(TAG, "LCD boot preheat scheduled: asset=%s", s_active_preheat.asset_dir); -} - -static void lcd_signal_presentation_completion(void) -{ - if (s_presentation_completion_sem != NULL) { - (void)xSemaphoreGive(s_presentation_completion_sem); - } -} - -static void lcd_clear_preheat_state(void) -{ - portENTER_CRITICAL(&s_presentation_lock); - s_preheat_pending = false; - memset(&s_pending_preheat, 0, sizeof(s_pending_preheat)); - portEXIT_CRITICAL(&s_presentation_lock); - - s_preheat_active = false; - memset(&s_active_preheat, 0, sizeof(s_active_preheat)); -} - -static void lcd_presentation_update_active(void) -{ - lcd_presentation_request_t active = {0}; - if (!lcd_presentation_get_active(&active)) { - return; - } - - if (active.kind == PLATFORM_LCD_PRESENTATION_SLOT_REELS) { - if (lcd_slot_update_animation()) { - lcd_presentation_set_active(NULL); - } - return; - } - - if (active.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - bool return_to_idle = false; - if (lcd_face_take_completed(&return_to_idle)) { - if (return_to_idle) { - (void)lcd_face_show_idle(lv_screen_active()); - } - lcd_presentation_set_active(NULL); - - /* Publish completion so blocking callers (e.g. the wealth spin - * intro) can gate on a finite asset sequence finishing its cycles. */ - portENTER_CRITICAL(&s_presentation_lock); - s_presentation_completion_count++; - portEXIT_CRITICAL(&s_presentation_lock); - lcd_signal_presentation_completion(); - } - } -} - -static bool lcd_lock(int timeout_ms) -{ - if (s_lvgl_mutex == NULL) { - return false; - } - return xSemaphoreTakeRecursive(s_lvgl_mutex, pdMS_TO_TICKS(timeout_ms)) == pdTRUE; -} - -static void lcd_unlock(void) -{ - if (s_lvgl_mutex != NULL) { - (void)xSemaphoreGiveRecursive(s_lvgl_mutex); - } -} - -static void lvgl_tick_cb(void *arg) -{ - (void)arg; - lv_tick_inc(LCD_TICK_PERIOD_MS); -} - -static esp_err_t lcd_apply_color_profile(const lcd_color_profile_t *profile) -{ - ESP_RETURN_ON_FALSE(profile != NULL, ESP_ERR_INVALID_ARG, TAG, "color profile is null"); - - uint8_t madctl = 0; - if (LCD_PANEL_MIRROR_X) { - madctl |= LCD_CMD_MX_BIT; - } - if (LCD_PANEL_SWAP_XY) { - madctl |= LCD_CMD_MV_BIT; - } - if (profile->color_order_bgr) { - madctl |= LCD_CMD_BGR_BIT; - } - - uint8_t ramctrl[] = {ST7789_RAMCTRL_PARAM1, ST7789_RAMCTRL_PARAM2_BASE}; - if (profile->swap_color_bytes) { - ramctrl[1] |= ST7789_RAMCTRL_LE_BIT; - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, LCD_CMD_MADCTL, &madctl, 1), - TAG, - "panel set MADCTL failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, ST7789_CMD_RAMCTRL, ramctrl, sizeof(ramctrl)), - TAG, - "panel set RAMCTRL failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_invert_color(s_panel, profile->invert_color), - TAG, - "panel set invert failed"); - - return ESP_OK; -} - -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST -static size_t lcd_find_matching_profile(const lcd_color_profile_t *target) -{ - for (size_t i = 0; i < sizeof(s_color_profiles) / sizeof(s_color_profiles[0]); i++) { - if (s_color_profiles[i].color_order_bgr == target->color_order_bgr && - s_color_profiles[i].swap_color_bytes == target->swap_color_bytes && - s_color_profiles[i].invert_color == target->invert_color) { - return i; - } - } - return 0; -} - -static void lcd_log_color_profile(size_t index, const lcd_color_profile_t *profile) -{ - ESP_LOGI(TAG, - "Color profile %u: bgr=%d swap_bytes=%d invert=%d mirror_x=%d swap_xy=%d x_gap=%d y_gap=%d", - (unsigned int)index, - profile->color_order_bgr, - profile->swap_color_bytes, - profile->invert_color, - LCD_PANEL_MIRROR_X, - LCD_PANEL_SWAP_XY, - CONFIG_TQ_SCREEN_X_GAP, - CONFIG_TQ_SCREEN_Y_GAP); -} - -static void lcd_update_color_profile_label(size_t index, const lcd_color_profile_t *profile) -{ - if (s_color_profile_label == NULL || profile == NULL) { - return; - } - lv_label_set_text_fmt(s_color_profile_label, - "profile=%u bgr=%d swap_bytes=%d invert=%d", - (unsigned int)index, - profile->color_order_bgr, - profile->swap_color_bytes, - profile->invert_color); -} - -static lv_obj_t *lcd_create_color_patch(lv_obj_t *parent, const char *name, uint32_t color_hex) -{ - lv_obj_t *patch = lv_obj_create(parent); - lv_obj_set_size(patch, 72, 56); - lv_obj_set_style_bg_color(patch, lv_color_hex(color_hex), LV_PART_MAIN); - lv_obj_set_style_border_width(patch, 1, LV_PART_MAIN); - lv_obj_set_style_border_color(patch, lv_color_hex(0x2A2A2A), LV_PART_MAIN); - lv_obj_set_style_radius(patch, 4, LV_PART_MAIN); - lv_obj_clear_flag(patch, LV_OBJ_FLAG_SCROLLABLE); - - lv_obj_t *label = lv_label_create(patch); - lv_label_set_text(label, name); - lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), LV_PART_MAIN); - lv_obj_center(label); - return patch; -} - -static void lcd_create_color_autotest_screen(void) -{ - lv_obj_t *screen = lv_screen_active(); - lv_obj_set_style_bg_color(screen, lv_color_hex(0x101010), LV_PART_MAIN); - lv_obj_set_style_pad_all(screen, 10, LV_PART_MAIN); - - lv_obj_t *title = lv_label_create(screen); - lv_label_set_text(title, "ST7789 Color Auto-Test"); - lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), LV_PART_MAIN); - lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 0); - - lv_obj_t *grid = lv_obj_create(screen); - lv_obj_set_size(grid, 312, 140); - lv_obj_align(grid, LV_ALIGN_TOP_MID, 0, 24); - lv_obj_set_style_bg_opa(grid, LV_OPA_TRANSP, LV_PART_MAIN); - lv_obj_set_style_border_width(grid, 0, LV_PART_MAIN); - lv_obj_set_style_pad_all(grid, 0, LV_PART_MAIN); - lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP); - lv_obj_set_flex_align(grid, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); - lv_obj_clear_flag(grid, LV_OBJ_FLAG_SCROLLABLE); - - (void)lcd_create_color_patch(grid, "RED", 0xFF0000); - (void)lcd_create_color_patch(grid, "GREEN", 0x00FF00); - (void)lcd_create_color_patch(grid, "BLUE", 0x0000FF); - (void)lcd_create_color_patch(grid, "WHITE", 0xFFFFFF); - (void)lcd_create_color_patch(grid, "YELLOW", 0xFFFF00); - (void)lcd_create_color_patch(grid, "CYAN", 0x00FFFF); - (void)lcd_create_color_patch(grid, "MAGENTA", 0xFF00FF); - (void)lcd_create_color_patch(grid, "BLACK", 0x000000); - - s_color_profile_label = lv_label_create(screen); - lv_label_set_text(s_color_profile_label, "profile=NA bgr=0 swap_bytes=0 invert=0"); - lv_obj_set_style_text_color(s_color_profile_label, lv_color_hex(0xFFFFFF), LV_PART_MAIN); - lv_obj_align(s_color_profile_label, LV_ALIGN_BOTTOM_MID, 0, -8); -} -#endif - -static esp_err_t lcd_apply_st7789_init_cmds(void) -{ - for (size_t i = 0; s_st7789_init_cmds[i].databytes != 0xFF; i++) { - const lcd_init_cmd_t *cmd = &s_st7789_init_cmds[i]; - size_t payload_len = cmd->databytes & 0x1F; - const uint8_t *payload = (payload_len > 0) ? cmd->data : NULL; - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, cmd->cmd, payload, payload_len), - TAG, - "panel custom init cmd 0x%02X failed", - cmd->cmd); - - if (cmd->databytes & 0x80) { - vTaskDelay(pdMS_TO_TICKS(120)); - } - } - - return ESP_OK; -} - -static bool on_color_trans_done(esp_lcd_panel_io_handle_t panel_io, - esp_lcd_panel_io_event_data_t *event_data, - void *user_ctx) -{ - (void)panel_io; - (void)event_data; - lv_display_t *display = (lv_display_t *)user_ctx; - if (display != NULL) { - lv_display_flush_ready(display); - } - return false; -} - -static void lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *color_map) -{ - esp_lcd_panel_handle_t panel = (esp_lcd_panel_handle_t)lv_display_get_user_data(display); - esp_err_t err = esp_lcd_panel_draw_bitmap(panel, - area->x1, - area->y1, - area->x2 + 1, - area->y2 + 1, - color_map); - if (err != ESP_OK) { - ESP_LOGE(TAG, "panel draw failed: %s", esp_err_to_name(err)); - lv_display_flush_ready(display); - } -} - -static esp_err_t lcd_backlight_gpio_init(void) -{ - gpio_config_t io_conf = { - .pin_bit_mask = (1ULL << PIN_NUM_BCKL), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - return gpio_config(&io_conf); -} - -static esp_err_t lcd_panel_init(void) -{ - spi_bus_config_t buscfg = { - .sclk_io_num = PIN_NUM_CLK, - .mosi_io_num = PIN_NUM_MOSI, - .miso_io_num = PIN_NUM_MISO, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .max_transfer_sz = LCD_H_RES * LCD_DRAW_BUF_LINES * sizeof(uint16_t), - }; - ESP_RETURN_ON_ERROR(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO), TAG, "spi bus init failed"); - - esp_lcd_panel_io_spi_config_t io_config = { - .dc_gpio_num = PIN_NUM_DC, - .cs_gpio_num = PIN_NUM_CS, - .pclk_hz = LCD_PIXEL_CLOCK_HZ, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .spi_mode = LCD_SPI_MODE, - .trans_queue_depth = 10, - .on_color_trans_done = NULL, - .user_ctx = NULL, - }; - ESP_RETURN_ON_ERROR( - esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &s_panel_io), - TAG, - "new panel io failed"); - - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = PIN_NUM_RST, - .rgb_ele_order = LCD_PANEL_RGB_ORDER, - .data_endian = LCD_PANEL_DATA_ENDIAN, - .bits_per_pixel = 16, - }; - ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(s_panel_io, &panel_config, &s_panel), TAG, "new panel failed"); - - ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG, "panel reset failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "panel init failed"); - ESP_RETURN_ON_ERROR(lcd_apply_st7789_init_cmds(), TAG, "apply custom panel init failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_swap_xy(s_panel, LCD_PANEL_SWAP_XY), TAG, "panel swap_xy failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_mirror(s_panel, LCD_PANEL_MIRROR_X, false), TAG, "panel mirror failed"); - ESP_RETURN_ON_ERROR(lcd_apply_color_profile(&s_boot_color_profile), TAG, "apply boot color profile failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_set_gap(s_panel, CONFIG_TQ_SCREEN_X_GAP, CONFIG_TQ_SCREEN_Y_GAP), TAG, "panel set gap failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_disp_on_off(s_panel, true), TAG, "panel display on failed"); - - return ESP_OK; -} - -static esp_err_t lcd_lvgl_init(void) -{ - lv_init(); - - size_t draw_buf_pixels = LCD_H_RES * LCD_DRAW_BUF_LINES; - size_t draw_buf_bytes = draw_buf_pixels * sizeof(uint16_t); - - lcd_log_heap_snapshot("before_lvgl_draw_buffers"); - void *buf1 = heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); - void *buf2 = heap_caps_malloc(draw_buf_bytes, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); - if (buf1 == NULL || buf2 == NULL) { - ESP_LOGE(TAG, - "LVGL draw buffer alloc failed (bytes_each=%u buf1=%p buf2=%p)", - (unsigned)draw_buf_bytes, - buf1, - buf2); - heap_caps_free(buf1); - heap_caps_free(buf2); - lcd_log_heap_snapshot("after_lvgl_draw_buffer_alloc_failed"); - return ESP_ERR_NO_MEM; - } - lcd_log_heap_snapshot("after_lvgl_draw_buffers"); - - s_display = lv_display_create(LCD_H_RES, LCD_V_RES); - if (s_display == NULL) { - heap_caps_free(buf1); - heap_caps_free(buf2); - ESP_LOGE(TAG, "lv display create failed"); - return ESP_FAIL; - } - - lv_display_set_color_format(s_display, LV_COLOR_FORMAT_RGB565); - lv_display_set_buffers(s_display, buf1, buf2, draw_buf_bytes, LV_DISPLAY_RENDER_MODE_PARTIAL); - lv_display_set_flush_cb(s_display, lvgl_flush_cb); - lv_display_set_user_data(s_display, s_panel); - - esp_lcd_panel_io_callbacks_t cbs = { - .on_color_trans_done = on_color_trans_done, - }; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_register_event_callbacks(s_panel_io, &cbs, s_display), - TAG, - "register io callbacks failed"); - - const esp_timer_create_args_t lvgl_tick_timer_args = { - .callback = lvgl_tick_cb, - .arg = NULL, - .dispatch_method = ESP_TIMER_TASK, - .name = "lvgl_tick", - .skip_unhandled_events = true, - }; - ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &s_lvgl_tick_timer), TAG, "tick timer create failed"); - ESP_RETURN_ON_ERROR(esp_timer_start_periodic(s_lvgl_tick_timer, LCD_TICK_PERIOD_MS * 1000), TAG, "tick timer start failed"); - - return ESP_OK; -} - -static void lcd_create_default_screen(void) -{ - if (s_default_screen_ready) { - return; - } - -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST - lcd_create_color_autotest_screen(); - ESP_LOGI(TAG, "LVGL color auto-test screen started"); -#else - lv_obj_t *screen = lv_screen_active(); - esp_err_t face_err = lcd_face_create_idle_screen(screen); - if (face_err == ESP_OK) { - ESP_LOGI(TAG, "LVGL cat face idle screen started"); - } else { - lv_obj_set_style_bg_color(screen, lv_color_hex(0x000000), LV_PART_MAIN); - - lv_obj_t *title = lv_label_create(screen); - lv_label_set_text(title, "Cat face init failed"); - lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), LV_PART_MAIN); - lv_obj_center(title); - ESP_LOGE(TAG, "Failed to create cat face screen: %s", esp_err_to_name(face_err)); - } -#endif - - s_default_screen_ready = true; -} - -esp_err_t lcd_init_device(void) -{ - if (s_initialized) { - return ESP_OK; - } - - s_lvgl_mutex = xSemaphoreCreateRecursiveMutex(); - ESP_RETURN_ON_FALSE(s_lvgl_mutex != NULL, ESP_ERR_NO_MEM, TAG, "lvgl mutex create failed"); - s_presentation_completion_sem = xSemaphoreCreateBinary(); - ESP_RETURN_ON_FALSE(s_presentation_completion_sem != NULL, - ESP_ERR_NO_MEM, - TAG, - "presentation completion semaphore create failed"); - - ESP_RETURN_ON_ERROR(lcd_backlight_gpio_init(), TAG, "backlight gpio init failed"); - ESP_RETURN_ON_ERROR(lcd_panel_init(), TAG, "panel init failed"); - ESP_RETURN_ON_ERROR(lcd_lvgl_init(), TAG, "lvgl init failed"); - - lcd_backlight_on(); - s_initialized = true; - - ESP_LOGI(TAG, - "LCD initialized with LVGL (single ST7789V, MOSI=%d, SCLK=%d, CS=%d, DC=%d, RST=%d, BL=%d)", - PIN_NUM_MOSI, - PIN_NUM_CLK, - PIN_NUM_CS, - PIN_NUM_DC, - PIN_NUM_RST, - PIN_NUM_BCKL); - - return ESP_OK; -} - -void lcd_backlight_on(void) -{ - gpio_set_level(PIN_NUM_BCKL, LCD_BK_LIGHT_ON_LEVEL); -} - -void lcd_start_render_loop(void) -{ -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST - size_t profile_index = lcd_find_matching_profile(&s_boot_color_profile); - int64_t next_switch_ms = 0; -#endif - - if (!s_initialized || s_display == NULL) { - ESP_LOGW(TAG, "LCD is not initialized, skip LVGL loop"); - return; - } - - if (!lcd_render_loop_mark_started(xTaskGetCurrentTaskHandle())) { - ESP_LOGI(TAG, "LCD render loop already running"); - return; - } - - if (lcd_lock(1000)) { - lcd_create_default_screen(); - lcd_schedule_boot_preheat(); -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST - ESP_LOGI(TAG, "Color auto-test enabled, interval=%dms", CONFIG_TQ_SCREEN_COLOR_AUTOTEST_INTERVAL_MS); - lcd_log_color_profile(profile_index, &s_color_profiles[profile_index]); - lcd_update_color_profile_label(profile_index, &s_color_profiles[profile_index]); - next_switch_ms = (esp_timer_get_time() / 1000) + CONFIG_TQ_SCREEN_COLOR_AUTOTEST_INTERVAL_MS; -#endif - lcd_unlock(); - } - - while (!lcd_render_loop_stop_requested()) { - uint32_t wait_ms = 5; - if (lcd_lock(50)) { - lcd_presentation_process_pending(); - lcd_presentation_update_active(); - wait_ms = lv_timer_handler(); - lcd_preheat_process_pending(); -#if CONFIG_TQ_SCREEN_COLOR_AUTOTEST - int64_t now_ms = esp_timer_get_time() / 1000; - if (now_ms >= next_switch_ms) { - profile_index = (profile_index + 1) % (sizeof(s_color_profiles) / sizeof(s_color_profiles[0])); - esp_err_t err = lcd_apply_color_profile(&s_color_profiles[profile_index]); - if (err == ESP_OK) { - lcd_log_color_profile(profile_index, &s_color_profiles[profile_index]); - lcd_update_color_profile_label(profile_index, &s_color_profiles[profile_index]); - } else { - ESP_LOGE(TAG, "Failed to apply color profile %u: %s", (unsigned int)profile_index, esp_err_to_name(err)); - } - next_switch_ms = now_ms + CONFIG_TQ_SCREEN_COLOR_AUTOTEST_INTERVAL_MS; - } -#endif - lcd_unlock(); - } - - if (wait_ms == 0 || wait_ms > LCD_RENDER_TASK_DELAY_MAXMS) { - wait_ms = LCD_RENDER_TASK_DELAY_MAXMS; - } - TickType_t wait_ticks = pdMS_TO_TICKS(wait_ms); - if (wait_ticks == 0) { - wait_ticks = 1; - } - (void)ulTaskNotifyTake(pdTRUE, wait_ticks); - } - - if (lcd_lock(1000)) { - lcd_presentation_cancel_active(); - lcd_clear_preheat_state(); - lcd_unlock(); - } - lcd_render_loop_mark_stopped(xTaskGetCurrentTaskHandle()); -} - -esp_err_t lcd_stop_render_loop(int timeout_ms) -{ - if (timeout_ms < 0) { - return ESP_ERR_INVALID_ARG; - } - - TaskHandle_t task = lcd_render_loop_request_stop(); - lcd_notify_render_task(); - if (task == NULL || timeout_ms == 0) { - return ESP_OK; - } - - TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); - if (timeout_ticks == 0) { - timeout_ticks = 1; - } - const TickType_t start_tick = xTaskGetTickCount(); - while (!lcd_render_loop_stopped() && (xTaskGetTickCount() - start_tick) < timeout_ticks) { - TickType_t wait_ticks = pdMS_TO_TICKS(10); - vTaskDelay(wait_ticks > 0 ? wait_ticks : 1); - } - - if (lcd_render_loop_stopped()) { - return ESP_OK; - } - - if (task == xTaskGetCurrentTaskHandle()) { - return ESP_ERR_TIMEOUT; - } - - ESP_LOGW(TAG, "LCD render task stop timed out after %d ms; force deleting", timeout_ms); - vTaskDelete(task); - lcd_render_loop_force_stopped(task); - return ESP_ERR_TIMEOUT; -} - -esp_err_t lcd_request_presentation(const platform_lcd_presentation_request_t *request) -{ - if (!s_initialized || s_display == NULL || lcd_render_loop_stop_requested()) { - return ESP_ERR_INVALID_STATE; - } - - lcd_presentation_request_t copied = {0}; - if (!lcd_copy_presentation_request(&copied, request)) { - return ESP_ERR_INVALID_ARG; - } - - portENTER_CRITICAL(&s_presentation_lock); - bool should_queue = lcd_presentation_should_queue_locked(&copied); - if (should_queue) { - s_pending_presentation = copied; - s_presentation_pending = true; - } - portEXIT_CRITICAL(&s_presentation_lock); - - if (should_queue) { - ESP_LOGI(TAG, - "LCD presentation queued: kind=%s priority=%u interrupt=%d", - lcd_presentation_kind_name(copied.kind), - (unsigned int)copied.priority, - copied.interrupt_current); - lcd_notify_render_task(); - } - - return ESP_OK; -} - -esp_err_t lcd_preheat_presentation(const platform_lcd_presentation_request_t *request) -{ - if (!s_initialized || s_display == NULL || lcd_render_loop_stop_requested()) { - return ESP_ERR_INVALID_STATE; - } - - lcd_presentation_request_t copied = {0}; - if (!lcd_copy_presentation_request(&copied, request) || - copied.kind != PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - return ESP_ERR_INVALID_ARG; - } - - bool should_queue = true; - portENTER_CRITICAL(&s_presentation_lock); - if (s_preheat_pending && - lcd_presentation_requests_match(&copied, &s_pending_preheat)) { - should_queue = false; - } - if (should_queue) { - s_pending_preheat = copied; - s_preheat_pending = true; - } - portEXIT_CRITICAL(&s_presentation_lock); - - if (should_queue) { - ESP_LOGI(TAG, "LCD preheat queued: asset=%s", copied.asset_dir); - lcd_notify_render_task(); - } - - return ESP_OK; -} - -uint32_t lcd_presentation_completion_count(void) -{ - uint32_t count; - portENTER_CRITICAL(&s_presentation_lock); - count = s_presentation_completion_count; - portEXIT_CRITICAL(&s_presentation_lock); - return count; -} - -esp_err_t lcd_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms) -{ - if (!s_initialized || s_display == NULL || lcd_render_loop_stop_requested()) { - return ESP_ERR_INVALID_STATE; - } - - const TickType_t start_tick = xTaskGetTickCount(); - const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); - - for (;;) { - uint32_t count; - portENTER_CRITICAL(&s_presentation_lock); - count = s_presentation_completion_count; - portEXIT_CRITICAL(&s_presentation_lock); - if (count != since_count) { - return ESP_OK; - } - - if ((xTaskGetTickCount() - start_tick) >= timeout_ticks) { - return ESP_ERR_TIMEOUT; - } - if (lcd_render_loop_stop_requested()) { - return ESP_ERR_INVALID_STATE; - } - - TickType_t elapsed_ticks = xTaskGetTickCount() - start_tick; - TickType_t remaining_ticks = timeout_ticks > elapsed_ticks ? timeout_ticks - elapsed_ticks : 0; - if (s_presentation_completion_sem != NULL && remaining_ticks > 0) { - (void)xSemaphoreTake(s_presentation_completion_sem, remaining_ticks); - } else { - TickType_t wait_ticks = pdMS_TO_TICKS(LCD_PRESENTATION_WAIT_POLL_MS); - if (wait_ticks == 0) { - wait_ticks = 1; - } - vTaskDelay(wait_ticks); - } - } -} diff --git a/components/platform/src/lcd_direct.c b/components/platform/src/lcd_direct.c new file mode 100644 index 0000000..2c863a7 --- /dev/null +++ b/components/platform/src/lcd_direct.c @@ -0,0 +1,821 @@ +/* + * Direct ST7789 LCD backend. + * + * This backend owns the low-level panel initialization, a serialized render + * loop, and a minimal presentation queue. + */ + +#include "lcd_direct.h" +#include "lcd_direct_assets.h" + +#include +#include +#include + +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_check.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +#define LCD_DIRECT_SPI_HOST SPI2_HOST +#define LCD_DIRECT_WIDTH 320U +#define LCD_DIRECT_HEIGHT 240U +#define LCD_DIRECT_ROWS_PER_TRANSFER 8U +#define LCD_DIRECT_PIXELS_PER_TRANSFER (LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER) +#define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U + +#define LCD_CMD_SWRESET 0x01 +#define LCD_CMD_SLPOUT 0x11 +#define LCD_CMD_COLMOD 0x3A +#define LCD_CMD_MADCTL 0x36 +#define LCD_CMD_CASET 0x2A +#define LCD_CMD_RASET 0x2B +#define LCD_CMD_RAMWR 0x2C +#define LCD_CMD_INVON 0x21 +#define LCD_CMD_INVOFF 0x20 +#define LCD_CMD_DISPON 0x29 +#define LCD_CMD_RAMCTRL 0xB0 +#define LCD_RAMCTRL_LE_BIT (1U << 3) + +static const char *TAG = "lcd_direct"; + +typedef struct { + platform_lcd_presentation_kind_t kind; + uint8_t priority; + bool interrupt_current; + bool return_to_idle; + char asset_dir[PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX]; + uint16_t repeat_count; + uint32_t frame_ms; +} lcd_direct_request_t; + +typedef struct { + uint8_t cmd; + uint8_t data[16]; + uint8_t databytes; +} lcd_direct_init_cmd_t; + +static const lcd_direct_init_cmd_t s_st7789_init_cmds[] = { + {0x36, {(1 << 5) | (1 << 6)}, 1}, + {0x3A, {0x55}, 1}, + {0xB2, {0x0C, 0x0C, 0x00, 0x33, 0x33}, 5}, + {0xB7, {0x45}, 1}, + {0xBB, {0x2B}, 1}, + {0xC0, {0x2C}, 1}, + {0xC2, {0x01, 0xFF}, 2}, + {0xC3, {0x11}, 1}, + {0xC4, {0x20}, 1}, + {0xC6, {0x0F}, 1}, + {0xD0, {0xA4, 0xA1}, 2}, + {0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14}, + {0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14}, + {0, {0}, 0xFF}, +}; + +static spi_device_handle_t s_spi; +static esp_lcd_panel_io_handle_t s_panel_io; +static esp_lcd_panel_handle_t s_panel; +static SemaphoreHandle_t s_lock; +static SemaphoreHandle_t s_completion_sem; +static TaskHandle_t s_render_task; +static bool s_initialized; +static bool s_render_loop_running; +static bool s_stop_requested; +static bool s_pending_valid; +static lcd_direct_request_t s_pending; +static uint32_t s_completion_count; +static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED; +static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER]; + +static uint16_t lcd_direct_swap_color(uint16_t color) +{ +#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES + return (uint16_t)((color >> 8) | (color << 8)); +#else + return color; +#endif +} + +static void lcd_direct_delay_ms(uint32_t ms) +{ + vTaskDelay(pdMS_TO_TICKS(ms)); +} + +static esp_err_t lcd_direct_spi_write(const void *data, uint32_t length) +{ + spi_transaction_t transaction = { + .length = length * 8U, + .tx_buffer = data, + }; + return spi_device_polling_transmit(s_spi, &transaction); +} + +static esp_err_t lcd_direct_write(uint8_t dc_level, uint16_t value, uint8_t length) +{ + uint8_t bytes[2] = { + (uint8_t)(value >> 8), + (uint8_t)value, + }; + const uint8_t *data = length == 1U ? &bytes[1] : bytes; + + gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, dc_level); + return lcd_direct_spi_write(data, length); +} + +static esp_err_t lcd_direct_write_bytes(uint8_t dc_level, const uint8_t *data, uint32_t length) +{ + gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, dc_level); + return lcd_direct_spi_write(data, length); +} + +static esp_err_t lcd_direct_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) +{ + ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_CASET, 1), TAG, "CASET failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(1, x0, 2), TAG, "CASET x0 failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(1, x1, 2), TAG, "CASET x1 failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_RASET, 1), TAG, "RASET failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(1, y0, 2), TAG, "RASET y0 failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(1, y1, 2), TAG, "RASET y1 failed"); + return lcd_direct_write(0, LCD_CMD_RAMWR, 1); +} + +static esp_err_t lcd_direct_write_pixels(const void *data, uint32_t length) +{ + gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, 1); + return lcd_direct_spi_write(data, length); +} + +static esp_err_t lcd_direct_draw_rows(uint16_t row_start, uint16_t row_count) +{ + ESP_RETURN_ON_FALSE(s_panel != NULL, ESP_ERR_INVALID_STATE, TAG, "panel is not ready"); + return esp_lcd_panel_draw_bitmap(s_panel, + 0, + row_start, + LCD_DIRECT_WIDTH, + row_start + row_count, + s_line_buffer); +} + +static esp_err_t lcd_direct_gpio_init(void) +{ + uint64_t pin_mask = (1ULL << CONFIG_TQ_SCREEN_BACKLIGHT_GPIO) | + (1ULL << CONFIG_TQ_SCREEN_DC_GPIO); + if (CONFIG_TQ_SCREEN_RESET_PIN >= 0) { + pin_mask |= (1ULL << CONFIG_TQ_SCREEN_RESET_PIN); + } + + gpio_config_t gpio_config_data = { + .pin_bit_mask = pin_mask, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_RETURN_ON_ERROR(gpio_config(&gpio_config_data), TAG, "gpio config failed"); + + gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_GPIO, + CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH ? 1 : 0); + gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, 0); + if (CONFIG_TQ_SCREEN_RESET_PIN >= 0) { + gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1); + } + return ESP_OK; +} + +static esp_err_t lcd_direct_spi_init(void) +{ + spi_bus_config_t bus_config = { + .mosi_io_num = CONFIG_TQ_SCREEN_SPI_MOSI_GPIO, + .miso_io_num = CONFIG_TQ_SCREEN_SPI_MISO_GPIO, + .sclk_io_num = CONFIG_TQ_SCREEN_SPI_CLK_GPIO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER * sizeof(uint16_t), + }; + + esp_err_t err = spi_bus_initialize(LCD_DIRECT_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "spi bus init failed: %s", esp_err_to_name(err)); + return err; + } + + spi_device_interface_config_t device_config = { + .clock_speed_hz = CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ, + .mode = 0, + .spics_io_num = CONFIG_TQ_SCREEN_SPI_CS_GPIO, + .queue_size = 8, + }; + ESP_RETURN_ON_ERROR(spi_bus_add_device(LCD_DIRECT_SPI_HOST, &device_config, &s_spi), + TAG, + "spi add device failed"); + return ESP_OK; +} + +static esp_err_t lcd_direct_panel_init(void) +{ + spi_bus_config_t bus_config = { + .mosi_io_num = CONFIG_TQ_SCREEN_SPI_MOSI_GPIO, + .miso_io_num = CONFIG_TQ_SCREEN_SPI_MISO_GPIO, + .sclk_io_num = CONFIG_TQ_SCREEN_SPI_CLK_GPIO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = LCD_DIRECT_PIXELS_PER_TRANSFER * sizeof(uint16_t), + }; + ESP_RETURN_ON_ERROR(spi_bus_initialize(LCD_DIRECT_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO), + TAG, + "esp_lcd spi bus init failed"); + + esp_lcd_panel_io_spi_config_t io_config = { + .dc_gpio_num = CONFIG_TQ_SCREEN_DC_GPIO, + .cs_gpio_num = CONFIG_TQ_SCREEN_SPI_CS_GPIO, + .pclk_hz = CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .spi_mode = 0, + .trans_queue_depth = 10, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_DIRECT_SPI_HOST, + &io_config, + &s_panel_io), + TAG, + "new panel io failed"); + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = CONFIG_TQ_SCREEN_RESET_PIN, +#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, +#else + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, +#endif +#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, +#else + .data_endian = LCD_RGB_DATA_ENDIAN_BIG, +#endif + .bits_per_pixel = 16, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(s_panel_io, &panel_config, &s_panel), + TAG, + "new st7789 panel failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG, "panel reset failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "panel init failed"); + + for (size_t i = 0; s_st7789_init_cmds[i].databytes != 0xFFU; ++i) { + const lcd_direct_init_cmd_t *cmd = &s_st7789_init_cmds[i]; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, + cmd->cmd, + cmd->databytes > 0U ? cmd->data : NULL, + cmd->databytes), + TAG, + "custom init cmd failed"); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_swap_xy(s_panel, CONFIG_TQ_SCREEN_SWAP_XY), + TAG, + "panel swap xy failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_mirror(s_panel, CONFIG_TQ_SCREEN_MIRROR_X, false), + TAG, + "panel mirror failed"); + + uint8_t madctl = 0; + if (CONFIG_TQ_SCREEN_MIRROR_X) { + madctl |= 0x40; + } + if (CONFIG_TQ_SCREEN_SWAP_XY) { + madctl |= 0x20; + } +#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR + madctl |= 0x08; +#endif + uint8_t ramctrl[] = {0x00, 0xF0}; +#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES + ramctrl[1] |= LCD_RAMCTRL_LE_BIT; +#endif + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, LCD_CMD_MADCTL, &madctl, 1), + TAG, + "panel MADCTL failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, LCD_CMD_RAMCTRL, ramctrl, sizeof(ramctrl)), + TAG, + "panel RAMCTRL failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_invert_color(s_panel, CONFIG_TQ_SCREEN_INVERT_COLOR), + TAG, + "panel invert failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_set_gap(s_panel, + CONFIG_TQ_SCREEN_X_GAP, + CONFIG_TQ_SCREEN_Y_GAP), + TAG, + "panel set gap failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_disp_on_off(s_panel, true), + TAG, + "panel display on failed"); + return ESP_OK; +} + +static void lcd_direct_reset(void) +{ + if (CONFIG_TQ_SCREEN_RESET_PIN < 0) { + return; + } + gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1); + lcd_direct_delay_ms(20); + gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 0); + lcd_direct_delay_ms(20); + gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1); + lcd_direct_delay_ms(120); +} + +static uint8_t lcd_direct_madctl_value(void) +{ + uint8_t value = 0; + if (CONFIG_TQ_SCREEN_MIRROR_X) { + value |= 0x40; + } + if (CONFIG_TQ_SCREEN_SWAP_XY) { + value |= 0x20; + } +#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR + value |= 0x08; +#endif + return value; +} + +static esp_err_t lcd_direct_controller_init(void) +{ + ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_SWRESET, 1), TAG, "SWRESET failed"); + lcd_direct_delay_ms(150); + + for (size_t i = 0; s_st7789_init_cmds[i].databytes != 0xFFU; ++i) { + const lcd_direct_init_cmd_t *cmd = &s_st7789_init_cmds[i]; + ESP_RETURN_ON_ERROR(lcd_direct_write(0, cmd->cmd, 1), TAG, "custom init cmd failed"); + if (cmd->databytes > 0U) { + ESP_RETURN_ON_ERROR(lcd_direct_write_bytes(1, cmd->data, cmd->databytes), + TAG, + "custom init param failed"); + } + } + + ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_MADCTL, 1), TAG, "MADCTL failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(1, lcd_direct_madctl_value(), 1), TAG, "MADCTL param failed"); + + uint8_t ramctrl[] = {0x00, 0xF0}; +#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES + ramctrl[1] |= LCD_RAMCTRL_LE_BIT; +#endif + ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_RAMCTRL, 1), TAG, "RAMCTRL failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write_bytes(1, ramctrl, sizeof(ramctrl)), TAG, "RAMCTRL param failed"); + + ESP_RETURN_ON_ERROR(lcd_direct_write(0, + CONFIG_TQ_SCREEN_INVERT_COLOR ? LCD_CMD_INVON : LCD_CMD_INVOFF, + 1), + TAG, + "invert command failed"); + ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_DISPON, 1), TAG, "DISPON failed"); + lcd_direct_delay_ms(120); + return ESP_OK; +} + +static uint16_t lcd_direct_color_for_row(uint16_t row, const lcd_direct_request_t *request) +{ + uint16_t band = row / 40U; + if (request != NULL && request->kind == PLATFORM_LCD_PRESENTATION_SLOT_REELS) { + static const uint16_t slot_colors[] = {0x001F, 0xF800, 0xFFE0, 0x07E0, 0xF81F, 0xFFFF}; + return slot_colors[band % (sizeof(slot_colors) / sizeof(slot_colors[0]))]; + } + if (request != NULL && request->kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { + uint32_t hash = 0; + for (size_t i = 0; request->asset_dir[i] != '\0'; ++i) { + hash = (hash * 33U) + (uint8_t)request->asset_dir[i]; + } + static const uint16_t asset_colors[] = {0x0000, 0x07FF, 0xF800, 0x07E0, 0x001F, 0xFFFF}; + return asset_colors[(hash + band) % (sizeof(asset_colors) / sizeof(asset_colors[0]))]; + } + static const uint16_t idle_colors[] = {0x0000, 0x39E7, 0x7BEF, 0xFFFF, 0x7BEF, 0x39E7}; + return idle_colors[band % (sizeof(idle_colors) / sizeof(idle_colors[0]))]; +} + +static esp_err_t lcd_direct_draw_test_pattern(const lcd_direct_request_t *request) +{ + for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) { + uint16_t row_count = LCD_DIRECT_HEIGHT - row_start; + if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) { + row_count = LCD_DIRECT_ROWS_PER_TRANSFER; + } + for (uint16_t y = 0; y < row_count; ++y) { + uint16_t color = lcd_direct_swap_color(lcd_direct_color_for_row(row_start + y, request)); + for (uint16_t x = 0; x < LCD_DIRECT_WIDTH; ++x) { + s_line_buffer[(uint32_t)y * LCD_DIRECT_WIDTH + x] = color; + } + } + ESP_RETURN_ON_ERROR(lcd_direct_draw_rows(row_start, row_count), + TAG, + "write pixels failed"); + } + return ESP_OK; +} + +static void lcd_direct_overlay_icons(uint16_t *pixel_buffer, + uint16_t row_start, + uint16_t row_count, + const lcd_direct_icon_draw_t *icons, + size_t icon_count) +{ + if (pixel_buffer == NULL || icons == NULL || icon_count == 0U) { + return; + } + + const uint16_t row_end = row_start + row_count; + for (size_t icon_index = 0; icon_index < icon_count; ++icon_index) { + const lcd_direct_icon_draw_t *draw = &icons[icon_index]; + const lcd_direct_icon_t *icon = lcd_direct_icon(draw->icon_id); + if (icon == NULL || icon->runs == NULL || icon->width == 0U || icon->height == 0U) { + continue; + } + if (draw->x >= LCD_DIRECT_WIDTH || draw->y >= LCD_DIRECT_HEIGHT) { + continue; + } + + uint16_t draw_width = icon->width; + uint16_t draw_height = icon->height; + if (draw_width > LCD_DIRECT_WIDTH - draw->x) { + draw_width = LCD_DIRECT_WIDTH - draw->x; + } + if (draw_height > LCD_DIRECT_HEIGHT - draw->y) { + draw_height = LCD_DIRECT_HEIGHT - draw->y; + } + + const uint16_t overlay_y0 = draw->y; + const uint16_t overlay_y1 = draw->y + draw_height; + if (overlay_y1 <= row_start || overlay_y0 >= row_end) { + continue; + } + + const uint16_t copy_y0 = overlay_y0 > row_start ? overlay_y0 : row_start; + const uint16_t copy_y1 = overlay_y1 < row_end ? overlay_y1 : row_end; + for (uint16_t y = copy_y0; y < copy_y1; ++y) { + const uint16_t local_y = y - row_start; + const uint16_t image_y = y - draw->y; + const uint32_t icon_pixel_start = (uint32_t)image_y * icon->width; + const uint32_t icon_pixel_end = icon_pixel_start + draw_width; + uint32_t icon_pixel_pos = 0; + + for (uint32_t run_index = 0; + run_index < icon->run_count && icon_pixel_pos < icon_pixel_end; + ++run_index) { + const uint32_t run_start = icon_pixel_pos; + const uint32_t run_end = run_start + icon->runs[run_index].count; + if (run_end > icon_pixel_start && run_start < icon_pixel_end) { + const uint32_t copy_start = run_start > icon_pixel_start ? run_start : icon_pixel_start; + const uint32_t copy_end = run_end < icon_pixel_end ? run_end : icon_pixel_end; + const uint16_t color = lcd_direct_swap_color(icon->runs[run_index].color); + for (uint32_t x = copy_start; x < copy_end; ++x) { + pixel_buffer[(uint32_t)local_y * LCD_DIRECT_WIDTH + draw->x + (x - icon_pixel_start)] = color; + } + } + icon_pixel_pos = run_end; + } + } + } +} + +static void lcd_direct_overlay_boot_marker(uint16_t *pixel_buffer, uint16_t row_start, uint16_t row_count) +{ + const uint16_t marker_color = lcd_direct_swap_color(0xFFFF); + const uint16_t row_end = row_start + row_count; + + for (uint16_t y = row_start; y < row_end; ++y) { + uint16_t local_y = y - row_start; + for (uint16_t x = 0; x < LCD_DIRECT_WIDTH; ++x) { + const bool border = y < 2U || y >= (LCD_DIRECT_HEIGHT - 2U) || + x < 2U || x >= (LCD_DIRECT_WIDTH - 2U); + const bool center_line = (x >= 158U && x <= 161U && y >= 100U && y <= 140U) || + (y >= 118U && y <= 121U && x >= 140U && x <= 180U); + if (border || center_line) { + pixel_buffer[(uint32_t)local_y * LCD_DIRECT_WIDTH + x] = marker_color; + } + } + } +} + +static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image, + const lcd_direct_icon_draw_t *icons, + size_t icon_count) +{ + ESP_RETURN_ON_FALSE(image != NULL && image->runs != NULL, + ESP_ERR_INVALID_ARG, + TAG, + "invalid image"); + uint32_t run_index = 0; + uint32_t run_remaining = 0; + uint16_t run_color = 0; + + for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) { + uint16_t row_count = LCD_DIRECT_HEIGHT - row_start; + if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) { + row_count = LCD_DIRECT_ROWS_PER_TRANSFER; + } + + const uint32_t pixels_needed = (uint32_t)LCD_DIRECT_WIDTH * row_count; + uint32_t buffered_pixels = 0; + while (buffered_pixels < pixels_needed) { + if (run_remaining == 0U) { + if (run_index >= image->run_count) { + break; + } + run_color = lcd_direct_swap_color(image->runs[run_index].color); + run_remaining = image->runs[run_index].count; + run_index++; + } + + uint32_t pixels_to_copy = run_remaining; + const uint32_t pixels_left = pixels_needed - buffered_pixels; + if (pixels_to_copy > pixels_left) { + pixels_to_copy = pixels_left; + } + for (uint32_t i = 0; i < pixels_to_copy; ++i) { + s_line_buffer[buffered_pixels + i] = run_color; + } + buffered_pixels += pixels_to_copy; + run_remaining -= pixels_to_copy; + } + + while (buffered_pixels < pixels_needed) { + s_line_buffer[buffered_pixels++] = 0; + } + + lcd_direct_overlay_icons(s_line_buffer, row_start, row_count, icons, icon_count); + lcd_direct_overlay_boot_marker(s_line_buffer, row_start, row_count); + ESP_RETURN_ON_ERROR(lcd_direct_draw_rows(row_start, row_count), + TAG, + "write image pixels failed"); + } + + return ESP_OK; +} + +static esp_err_t lcd_direct_draw_default_image(void) +{ + size_t icon_count = 0; + const lcd_direct_icon_draw_t *icons = lcd_direct_default_icon_sequence(&icon_count); + ESP_LOGI(TAG, "Drawing default LCD image icons=%u", (unsigned)icon_count); + return lcd_direct_draw_rle_image(lcd_direct_default_image(), icons, icon_count); +} + +void lcd_direct_run_blocking_test(void) +{ + if (!s_initialized) { + ESP_LOGW(TAG, "Direct LCD is not initialized, skip blocking test"); + return; + } + + ESP_LOGI(TAG, "Direct LCD blocking test started"); + lcd_direct_request_t color_test = { + .kind = PLATFORM_LCD_PRESENTATION_SLOT_REELS, + }; + esp_err_t color_err = lcd_direct_draw_test_pattern(&color_test); + ESP_LOGI(TAG, "Direct LCD color test draw result=%s", esp_err_to_name(color_err)); + lcd_direct_delay_ms(1000); + + esp_err_t image_err = lcd_direct_draw_default_image(); + ESP_LOGI(TAG, "Direct LCD default image draw result=%s", esp_err_to_name(image_err)); + + while (true) { + lcd_direct_delay_ms(1000); + } +} + +static void lcd_direct_signal_completion(void) +{ + portENTER_CRITICAL(&s_state_lock); + s_completion_count++; + portEXIT_CRITICAL(&s_state_lock); + if (s_completion_sem != NULL) { + (void)xSemaphoreGive(s_completion_sem); + } +} + +static bool lcd_direct_take_pending(lcd_direct_request_t *out) +{ + bool has_pending = false; + portENTER_CRITICAL(&s_state_lock); + if (s_pending_valid) { + *out = s_pending; + s_pending_valid = false; + memset(&s_pending, 0, sizeof(s_pending)); + has_pending = true; + } + portEXIT_CRITICAL(&s_state_lock); + return has_pending; +} + +esp_err_t lcd_direct_init_device(void) +{ + if (s_initialized) { + return ESP_OK; + } + + s_lock = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_NO_MEM, TAG, "lock create failed"); + s_completion_sem = xSemaphoreCreateBinary(); + ESP_RETURN_ON_FALSE(s_completion_sem != NULL, ESP_ERR_NO_MEM, TAG, "completion sem create failed"); + + ESP_RETURN_ON_ERROR(lcd_direct_gpio_init(), TAG, "gpio init failed"); + ESP_RETURN_ON_ERROR(lcd_direct_panel_init(), TAG, "esp_lcd panel init failed"); + + s_initialized = true; + ESP_LOGI(TAG, + "Direct LCD initialized (MOSI=%d SCLK=%d CS=%d DC=%d RST=%d BL=%d clock=%d)", + CONFIG_TQ_SCREEN_SPI_MOSI_GPIO, + CONFIG_TQ_SCREEN_SPI_CLK_GPIO, + CONFIG_TQ_SCREEN_SPI_CS_GPIO, + CONFIG_TQ_SCREEN_DC_GPIO, + CONFIG_TQ_SCREEN_RESET_PIN, + CONFIG_TQ_SCREEN_BACKLIGHT_GPIO, + CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ); + return ESP_OK; +} + +void lcd_direct_start_render_loop(void) +{ + if (!s_initialized) { + ESP_LOGW(TAG, "Direct LCD is not initialized, skip render loop"); + return; + } + + portENTER_CRITICAL(&s_state_lock); + if (s_render_loop_running) { + portEXIT_CRITICAL(&s_state_lock); + ESP_LOGI(TAG, "Direct LCD render loop already running"); + return; + } + s_render_loop_running = true; + s_stop_requested = false; + s_render_task = xTaskGetCurrentTaskHandle(); + portEXIT_CRITICAL(&s_state_lock); + + ESP_LOGI(TAG, "Direct LCD render loop started"); + esp_err_t default_draw_err = lcd_direct_draw_default_image(); + ESP_LOGI(TAG, "Direct LCD default image draw result=%s", esp_err_to_name(default_draw_err)); + + while (true) { + bool stop = false; + portENTER_CRITICAL(&s_state_lock); + stop = s_stop_requested; + portEXIT_CRITICAL(&s_state_lock); + if (stop) { + break; + } + + lcd_direct_request_t request = {0}; + if (lcd_direct_take_pending(&request)) { + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) { + esp_err_t draw_err = ESP_OK; + if (request.kind == PLATFORM_LCD_PRESENTATION_IDLE) { + draw_err = lcd_direct_draw_default_image(); + } else { + draw_err = lcd_direct_draw_test_pattern(&request); + } + xSemaphoreGive(s_lock); + if (draw_err == ESP_OK && request.repeat_count > 0U) { + lcd_direct_signal_completion(); + } + } + } + + (void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20)); + } + + portENTER_CRITICAL(&s_state_lock); + s_render_task = NULL; + s_render_loop_running = false; + s_stop_requested = false; + portEXIT_CRITICAL(&s_state_lock); + ESP_LOGI(TAG, "Direct LCD render loop stopped"); +} + +esp_err_t lcd_direct_stop_render_loop(int timeout_ms) +{ + if (timeout_ms < 0) { + return ESP_ERR_INVALID_ARG; + } + + TaskHandle_t task = NULL; + portENTER_CRITICAL(&s_state_lock); + task = s_render_task; + if (task != NULL) { + s_stop_requested = true; + } + portEXIT_CRITICAL(&s_state_lock); + + if (task == NULL || timeout_ms == 0) { + return ESP_OK; + } + xTaskNotifyGive(task); + + TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); + if (timeout_ticks == 0) { + timeout_ticks = 1; + } + TickType_t start = xTaskGetTickCount(); + while ((xTaskGetTickCount() - start) < timeout_ticks) { + bool stopped = false; + portENTER_CRITICAL(&s_state_lock); + stopped = !s_render_loop_running && s_render_task == NULL; + portEXIT_CRITICAL(&s_state_lock); + if (stopped) { + return ESP_OK; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + + if (task != xTaskGetCurrentTaskHandle()) { + ESP_LOGW(TAG, "Direct LCD render loop stop timed out after %d ms; force deleting", timeout_ms); + vTaskDelete(task); + portENTER_CRITICAL(&s_state_lock); + if (s_render_task == task) { + s_render_task = NULL; + } + s_render_loop_running = false; + s_stop_requested = false; + portEXIT_CRITICAL(&s_state_lock); + } + return ESP_ERR_TIMEOUT; +} + +esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_request_t *request) +{ + if (!s_initialized || request == NULL) { + return ESP_ERR_INVALID_STATE; + } + + lcd_direct_request_t copied = { + .kind = request->kind, + .priority = request->priority, + .interrupt_current = request->interrupt_current, + .return_to_idle = request->return_to_idle, + .repeat_count = request->repeat_count, + .frame_ms = request->frame_ms, + }; + if (request->asset_dir != NULL) { + strlcpy(copied.asset_dir, request->asset_dir, sizeof(copied.asset_dir)); + } + + TaskHandle_t task = NULL; + portENTER_CRITICAL(&s_state_lock); + s_pending = copied; + s_pending_valid = true; + task = s_render_task; + portEXIT_CRITICAL(&s_state_lock); + + if (task != NULL) { + xTaskNotifyGive(task); + } + ESP_LOGI(TAG, "Direct LCD presentation queued kind=%d asset=%s", copied.kind, copied.asset_dir); + return ESP_OK; +} + +esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_request_t *request) +{ + (void)request; + return s_initialized ? ESP_OK : ESP_ERR_INVALID_STATE; +} + +uint32_t lcd_direct_presentation_completion_count(void) +{ + uint32_t count; + portENTER_CRITICAL(&s_state_lock); + count = s_completion_count; + portEXIT_CRITICAL(&s_state_lock); + return count; +} + +esp_err_t lcd_direct_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms) +{ + if (!s_initialized) { + return ESP_ERR_INVALID_STATE; + } + + TickType_t start_tick = xTaskGetTickCount(); + TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms); + for (;;) { + if (lcd_direct_presentation_completion_count() != since_count) { + return ESP_OK; + } + if (timeout_ms == 0 || (xTaskGetTickCount() - start_tick) >= timeout_ticks) { + return ESP_ERR_TIMEOUT; + } + TickType_t elapsed = xTaskGetTickCount() - start_tick; + TickType_t remaining = timeout_ticks > elapsed ? timeout_ticks - elapsed : 0; + if (s_completion_sem != NULL && remaining > 0) { + (void)xSemaphoreTake(s_completion_sem, remaining); + } else { + vTaskDelay(pdMS_TO_TICKS(LCD_DIRECT_PRESENTATION_WAIT_POLL_MS)); + } + } +} diff --git a/components/platform/src/lcd_direct_assets.c b/components/platform/src/lcd_direct_assets.c new file mode 100644 index 0000000..6fa1413 --- /dev/null +++ b/components/platform/src/lcd_direct_assets.c @@ -0,0 +1,29 @@ +/* Generated from D:\Code\chaowan\lcd(1)\lcd\lcd\main\eye_image.c buyunqian1. */ +#include "lcd_direct_assets.h" + +extern const uint8_t _binary_buyunqian1_rle_start[] asm("_binary_buyunqian1_rle_start"); +extern const uint8_t _binary_buyunqian1_rle_end[] asm("_binary_buyunqian1_rle_end"); + +static const lcd_direct_image_t s_default_image = { + .runs = (const lcd_direct_rle_run_t *)_binary_buyunqian1_rle_start, + .run_count = 72552U, +}; + +const lcd_direct_image_t *lcd_direct_default_image(void) +{ + return &s_default_image; +} + +const lcd_direct_icon_t *lcd_direct_icon(uint16_t icon_id) +{ + (void)icon_id; + return NULL; +} + +const lcd_direct_icon_draw_t *lcd_direct_default_icon_sequence(size_t *out_count) +{ + if (out_count != NULL) { + *out_count = 0; + } + return NULL; +} diff --git a/components/platform/src/lcd_face.c b/components/platform/src/lcd_face.c deleted file mode 100644 index bc2e7aa..0000000 --- a/components/platform/src/lcd_face.c +++ /dev/null @@ -1,1337 +0,0 @@ -/* - * LCD cat-face idle screen. - */ - -#include "lcd_face.h" - -#include -#include -#include -#include -#include -#include - -#include "esp_attr.h" -#include "esp_check.h" -#include "esp_err.h" -#include "esp_heap_caps.h" -#include "esp_littlefs.h" -#include "esp_log.h" -#include "soc/soc_caps.h" -#include "draw/lv_draw_buf_private.h" -#include "draw/lv_image_decoder_private.h" -#include "widgets/animimage/lv_animimage_private.h" - -#define LCD_FACE_BG_COLOR 0x000000 -#define LCD_FACE_PRIMARY_COLOR 0xF5F1E8 -#define LCD_FACE_SECONDARY_COLOR 0xD8D2C8 -#define LCD_FACE_EYE_X_OFFSET 82 -#define LCD_FACE_EYE_Y_OFFSET (-36) -#define LCD_FACE_EYE_CONTAINER_W 96 -#define LCD_FACE_EYE_CONTAINER_H 40 -#define LCD_FACE_EYE_OPEN_W 82 -#define LCD_FACE_EYE_OPEN_H 26 -#define LCD_FACE_EYE_CLOSED_H 6 -#define LCD_FACE_NOSE_W 18 -#define LCD_FACE_NOSE_H 12 -#define LCD_FACE_BLINK_OPEN_MS 3000 -#define LCD_FACE_BLINK_CLOSED_MS 180 -#define LCD_FACE_ANIMATION_FRAME_MS 150 -#define LCD_FACE_ANIMATION_MAX 32 -#define LCD_FACE_ANIMATION_PATH_MAX 96 -#define LCD_FACE_ASSET_DIR_MAX 32 -#define LCD_FACE_LITTLEFS_BASE_PATH "/littlefs" -#define LCD_FACE_LITTLEFS_PARTITION "gifpack" -#define LCD_FACE_DEFAULT_ASSET_DIR "default" -#define LCD_FACE_LVGL_BASE_PATH "A:/littlefs" -#define LCD_FACE_ANIMATION_PROBE_MS 2000 -#define LCD_FACE_ANIMATION_TRACE_LIMIT 16 -#define LCD_FACE_SEQUENCE_CACHE_MAX 4 - -typedef enum { - LCD_FACE_ASSET_CACHE_EPHEMERAL = 0, - LCD_FACE_ASSET_CACHE_PREHEAT, - LCD_FACE_ASSET_CACHE_PINNED, -} lcd_face_asset_cache_policy_t; - -typedef struct { - bool prepared; - lcd_face_asset_cache_policy_t cache_policy; - char asset_dir[LCD_FACE_ASSET_DIR_MAX]; - uint8_t frame_order[LCD_FACE_ANIMATION_MAX]; - size_t frame_order_count; - char frame_paths[LCD_FACE_ANIMATION_MAX][LCD_FACE_ANIMATION_PATH_MAX]; - const void *frame_sources[LCD_FACE_ANIMATION_MAX]; - lv_draw_buf_t *frame_buffers[LCD_FACE_ANIMATION_MAX]; - size_t frame_count; - size_t loaded_count; - uint32_t last_used; -} lcd_face_sequence_cache_entry_t; - -typedef struct { - const char *asset_dir; - lcd_face_asset_cache_policy_t cache_policy; -} lcd_face_asset_manifest_entry_t; - -typedef struct { - lv_obj_t *root; - lv_obj_t *left_eye; - lv_obj_t *right_eye; - lv_obj_t *animimg; - lv_timer_t *blink_timer; - lv_timer_t *animation_probe_timer; - const void *frame_sources[LCD_FACE_ANIMATION_MAX]; - const char *frame_paths[LCD_FACE_ANIMATION_MAX]; - size_t frame_count; - lcd_face_sequence_cache_entry_t *cache_entry; - int32_t last_path_frame; - int32_t last_applied_frame; - int32_t last_probe_frame; - uint32_t path_call_count; - uint32_t path_frame_change_count; - uint32_t applied_frame_count; - uint32_t applied_skip_count; - uint32_t last_apply_tick; - bool completed; - bool return_to_idle; - bool eyes_closed; -} lcd_face_state_t; - -static const char *TAG = "lcd_face"; -static lcd_face_state_t s_face; -static EXT_RAM_BSS_ATTR lcd_face_sequence_cache_entry_t s_sequence_cache[LCD_FACE_SEQUENCE_CACHE_MAX]; -static lv_draw_buf_handlers_t s_psram_draw_buf_handlers; -static bool s_psram_draw_buf_handlers_ready; -static uint32_t s_sequence_cache_clock; -static bool s_littlefs_mount_attempted; -static esp_err_t s_littlefs_mount_result = ESP_ERR_INVALID_STATE; - -static const lcd_face_asset_manifest_entry_t s_asset_manifest[] = { - {.asset_dir = "default", .cache_policy = LCD_FACE_ASSET_CACHE_PINNED}, - {.asset_dir = "wifi_disconnected", .cache_policy = LCD_FACE_ASSET_CACHE_PINNED}, - {.asset_dir = "spin", .cache_policy = LCD_FACE_ASSET_CACHE_PREHEAT}, - {.asset_dir = "server_error", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "south", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "southwest", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "west", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "northwest", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "north", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "northeast", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "east", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, - {.asset_dir = "southeast", .cache_policy = LCD_FACE_ASSET_CACHE_EPHEMERAL}, -}; - -static const lv_point_precise_t s_left_upper_whisker[] = {{84, 0}, {0, 12}}; -static const lv_point_precise_t s_left_middle_whisker[] = {{88, 6}, {0, 6}}; -static const lv_point_precise_t s_left_lower_whisker[] = {{84, 12}, {0, 0}}; -static const lv_point_precise_t s_right_upper_whisker[] = {{0, 0}, {84, 12}}; -static const lv_point_precise_t s_right_middle_whisker[] = {{0, 6}, {88, 6}}; -static const lv_point_precise_t s_right_lower_whisker[] = {{0, 12}, {84, 0}}; -static const lv_point_precise_t s_mouth_stem[] = {{0, 0}, {0, 14}}; -static const lv_point_precise_t s_mouth_left[] = {{18, 0}, {0, 10}}; -static const lv_point_precise_t s_mouth_right[] = {{0, 0}, {18, 10}}; - -static void lcd_face_root_delete_cb(lv_event_t *event); - -static void lcd_face_init_transparent_box(lv_obj_t *obj) -{ - lv_obj_remove_style_all(obj); - lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, LV_PART_MAIN); - lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN); - lv_obj_set_style_pad_all(obj, 0, LV_PART_MAIN); - lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); -} - -static esp_err_t lcd_face_mount_littlefs(void) -{ - if (s_littlefs_mount_attempted) { - return s_littlefs_mount_result; - } - - esp_vfs_littlefs_conf_t conf = { - .base_path = LCD_FACE_LITTLEFS_BASE_PATH, - .partition_label = LCD_FACE_LITTLEFS_PARTITION, - .partition = NULL, - .format_if_mount_failed = false, - .read_only = true, - .dont_mount = false, - .grow_on_mount = false, - }; - - esp_err_t err = esp_vfs_littlefs_register(&conf); - if (err == ESP_ERR_INVALID_STATE && esp_littlefs_mounted(LCD_FACE_LITTLEFS_PARTITION)) { - err = ESP_OK; - } - - if (err == ESP_OK) { - size_t total_bytes = 0; - size_t used_bytes = 0; - esp_err_t info_err = esp_littlefs_info(LCD_FACE_LITTLEFS_PARTITION, &total_bytes, &used_bytes); - if (info_err == ESP_OK) { - ESP_LOGI(TAG, - "LittleFS mounted at %s, used=%u total=%u", - LCD_FACE_LITTLEFS_BASE_PATH, - (unsigned int)used_bytes, - (unsigned int)total_bytes); - } else { - ESP_LOGI(TAG, "LittleFS mounted at %s", LCD_FACE_LITTLEFS_BASE_PATH); - } - } else { - ESP_LOGW(TAG, "LittleFS mount failed: %s", esp_err_to_name(err)); - } - - s_littlefs_mount_attempted = true; - s_littlefs_mount_result = err; - return err; -} - -static bool lcd_face_has_bin_extension(const char *name) -{ - if (name == NULL) { - return false; - } - - size_t len = strlen(name); - return len > 4 && strcmp(name + len - 4, ".bin") == 0; -} - -static int lcd_face_compare_frame_path(const void *left, const void *right) -{ - return strcmp((const char *)left, (const char *)right); -} - -static void *lcd_face_draw_buf_malloc(size_t size_bytes, lv_color_format_t color_format) -{ - (void)color_format; - - size_t alloc_size = size_bytes + LV_DRAW_BUF_ALIGN - 1U; -#if SOC_SPIRAM_SUPPORTED - void *ptr = heap_caps_malloc(alloc_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - if (ptr != NULL) { - return ptr; - } -#endif - - return heap_caps_malloc(alloc_size, MALLOC_CAP_8BIT); -} - -static void lcd_face_draw_buf_free(void *buf) -{ - heap_caps_free(buf); -} - -static void *lcd_face_draw_buf_align(void *buf, lv_color_format_t color_format) -{ - return lv_draw_buf_align(buf, color_format); -} - -static uint32_t lcd_face_draw_buf_width_to_stride(uint32_t width, lv_color_format_t color_format) -{ - return lv_draw_buf_width_to_stride(width, color_format); -} - -static const lv_draw_buf_handlers_t *lcd_face_get_draw_buf_handlers(void) -{ - if (!s_psram_draw_buf_handlers_ready) { - lv_draw_buf_handlers_init(&s_psram_draw_buf_handlers, - lcd_face_draw_buf_malloc, - lcd_face_draw_buf_free, - lcd_face_draw_buf_align, - NULL, - NULL, - lcd_face_draw_buf_width_to_stride); - s_psram_draw_buf_handlers_ready = true; - } - - return &s_psram_draw_buf_handlers; -} - -static bool lcd_face_cache_entry_is_active(const lcd_face_sequence_cache_entry_t *entry) -{ - return entry != NULL && s_face.root != NULL && s_face.cache_entry == entry; -} - -static const char *lcd_face_cache_policy_name(lcd_face_asset_cache_policy_t policy) -{ - switch (policy) { - case LCD_FACE_ASSET_CACHE_PINNED: - return "pinned"; - case LCD_FACE_ASSET_CACHE_PREHEAT: - return "preheat"; - case LCD_FACE_ASSET_CACHE_EPHEMERAL: - default: - return "ephemeral"; - } -} - -static lcd_face_asset_cache_policy_t lcd_face_asset_cache_policy(const char *asset_dir) -{ - const char *resolved_dir = asset_dir != NULL && asset_dir[0] != '\0' ? - asset_dir : - LCD_FACE_DEFAULT_ASSET_DIR; - - for (size_t i = 0; i < sizeof(s_asset_manifest) / sizeof(s_asset_manifest[0]); ++i) { - if (strcmp(s_asset_manifest[i].asset_dir, resolved_dir) == 0) { - return s_asset_manifest[i].cache_policy; - } - } - - return LCD_FACE_ASSET_CACHE_EPHEMERAL; -} - -static void lcd_face_release_cache_entry(lcd_face_sequence_cache_entry_t *entry) -{ - if (entry == NULL) { - return; - } - - for (size_t i = 0; i < LCD_FACE_ANIMATION_MAX; ++i) { - if (entry->frame_buffers[i] != NULL) { - lv_draw_buf_destroy(entry->frame_buffers[i]); - } - } - - memset(entry, 0, sizeof(*entry)); -} - -static bool lcd_face_cache_entry_is_evictable(const lcd_face_sequence_cache_entry_t *entry) -{ - return entry != NULL && - entry->prepared && - entry->cache_policy != LCD_FACE_ASSET_CACHE_PINNED && - !lcd_face_cache_entry_is_active(entry); -} - -static int lcd_face_cache_policy_evict_rank(lcd_face_asset_cache_policy_t policy) -{ - switch (policy) { - case LCD_FACE_ASSET_CACHE_EPHEMERAL: - return 0; - case LCD_FACE_ASSET_CACHE_PREHEAT: - return 1; - case LCD_FACE_ASSET_CACHE_PINNED: - default: - return 2; - } -} - -static bool lcd_face_cache_entry_matches(const lcd_face_sequence_cache_entry_t *entry, - const char *asset_dir, - const uint8_t *frame_order, - size_t frame_order_count) -{ - if (entry == NULL || !entry->prepared || asset_dir == NULL) { - return false; - } - - if (strcmp(entry->asset_dir, asset_dir) != 0 || - entry->frame_order_count != frame_order_count) { - return false; - } - - if (frame_order_count == 0U) { - return true; - } - - return frame_order != NULL && - memcmp(entry->frame_order, frame_order, frame_order_count) == 0; -} - -static lcd_face_sequence_cache_entry_t *lcd_face_find_cache_entry( - const char *asset_dir, - const uint8_t *frame_order, - size_t frame_order_count) -{ - for (size_t i = 0; i < LCD_FACE_SEQUENCE_CACHE_MAX; ++i) { - if (lcd_face_cache_entry_matches(&s_sequence_cache[i], - asset_dir, - frame_order, - frame_order_count)) { - return &s_sequence_cache[i]; - } - } - - return NULL; -} - -static void lcd_face_touch_cache_entry(lcd_face_sequence_cache_entry_t *entry) -{ - if (entry == NULL) { - return; - } - - s_sequence_cache_clock++; - if (s_sequence_cache_clock == 0U) { - s_sequence_cache_clock++; - } - entry->last_used = s_sequence_cache_clock; -} - -static lcd_face_sequence_cache_entry_t *lcd_face_allocate_cache_entry(void) -{ - lcd_face_sequence_cache_entry_t *victim = NULL; - - for (size_t i = 0; i < LCD_FACE_SEQUENCE_CACHE_MAX; ++i) { - lcd_face_sequence_cache_entry_t *entry = &s_sequence_cache[i]; - if (!entry->prepared) { - return entry; - } - - if (!lcd_face_cache_entry_is_evictable(entry)) { - continue; - } - - if (victim == NULL) { - victim = entry; - continue; - } - - int entry_rank = lcd_face_cache_policy_evict_rank(entry->cache_policy); - int victim_rank = lcd_face_cache_policy_evict_rank(victim->cache_policy); - if (entry_rank < victim_rank || - (entry_rank == victim_rank && entry->last_used < victim->last_used)) { - victim = entry; - } - } - - if (victim == NULL) { - return NULL; - } - - ESP_LOGI(TAG, - "Evict animation cache entry: asset=%s policy=%s frames=%u", - victim->asset_dir, - lcd_face_cache_policy_name(victim->cache_policy), - (unsigned int)victim->frame_count); - lcd_face_release_cache_entry(victim); - return victim; -} - -static esp_err_t lcd_face_scan_frames(lcd_face_sequence_cache_entry_t *cache_entry, - const char *asset_dir, - const uint8_t *frame_order, - size_t frame_order_count) -{ - ESP_RETURN_ON_FALSE(cache_entry != NULL, ESP_ERR_INVALID_ARG, TAG, "cache entry is null"); - - memset(cache_entry->frame_paths, 0, sizeof(cache_entry->frame_paths)); - memset(cache_entry->frame_sources, 0, sizeof(cache_entry->frame_sources)); - cache_entry->frame_count = 0; - cache_entry->loaded_count = 0; - - ESP_RETURN_ON_FALSE(asset_dir != NULL && asset_dir[0] != '\0', - ESP_ERR_INVALID_ARG, - TAG, - "asset dir is empty"); - - if (frame_order_count > LCD_FACE_ANIMATION_MAX) { - ESP_LOGW(TAG, "Animation frame order too long: %u", (unsigned int)frame_order_count); - return ESP_ERR_INVALID_ARG; - } - - char fs_dir[LCD_FACE_ANIMATION_PATH_MAX]; - int dir_written = snprintf(fs_dir, sizeof(fs_dir), "%s/%s", LCD_FACE_LITTLEFS_BASE_PATH, asset_dir); - if (dir_written < 0 || dir_written >= (int)sizeof(fs_dir)) { - ESP_LOGW(TAG, "Animation asset dir path too long: %s", asset_dir); - return ESP_ERR_INVALID_ARG; - } - - DIR *dir = opendir(fs_dir); - if (dir == NULL) { - ESP_LOGW(TAG, "Animation directory unavailable: %s", fs_dir); - return ESP_ERR_NOT_FOUND; - } - - char scanned_paths[LCD_FACE_ANIMATION_MAX][LCD_FACE_ANIMATION_PATH_MAX] = {0}; - size_t scanned_count = 0; - struct dirent *entry = NULL; - while ((entry = readdir(dir)) != NULL) { - if (!lcd_face_has_bin_extension(entry->d_name)) { - continue; - } - - if (scanned_count >= LCD_FACE_ANIMATION_MAX) { - ESP_LOGW(TAG, "Too many animation frames, limit=%u", (unsigned int)LCD_FACE_ANIMATION_MAX); - break; - } - - int written = snprintf(scanned_paths[scanned_count], - sizeof(scanned_paths[scanned_count]), - "%s/%s/%s", - LCD_FACE_LVGL_BASE_PATH, - asset_dir, - entry->d_name); - if (written < 0 || written >= (int)sizeof(scanned_paths[scanned_count])) { - ESP_LOGW(TAG, "Animation frame path too long: %s", entry->d_name); - continue; - } - - scanned_count++; - } - - closedir(dir); - - if (scanned_count == 0) { - ESP_LOGW(TAG, "No .bin animation frames found under %s", fs_dir); - return ESP_ERR_NOT_FOUND; - } - - qsort(scanned_paths, - scanned_count, - sizeof(scanned_paths[0]), - lcd_face_compare_frame_path); - - if (frame_order_count > 0U) { - for (size_t i = 0; i < frame_order_count; ++i) { - uint8_t source_index = frame_order[i]; - if (source_index >= scanned_count) { - ESP_LOGW(TAG, - "Frame order index out of range: asset=%s order[%u]=%u frames=%u", - asset_dir, - (unsigned int)i, - (unsigned int)source_index, - (unsigned int)scanned_count); - return ESP_ERR_INVALID_ARG; - } - memcpy(cache_entry->frame_paths[cache_entry->frame_count], - scanned_paths[source_index], - sizeof(cache_entry->frame_paths[cache_entry->frame_count])); - cache_entry->frame_count++; - } - } else { - for (size_t i = 0; i < scanned_count; ++i) { - memcpy(cache_entry->frame_paths[cache_entry->frame_count], - scanned_paths[i], - sizeof(cache_entry->frame_paths[cache_entry->frame_count])); - cache_entry->frame_count++; - } - } - - for (size_t i = 0; i < cache_entry->frame_count; ++i) { - cache_entry->frame_sources[i] = cache_entry->frame_paths[i]; - ESP_LOGD(TAG, "Animation frame[%u]=%s", (unsigned int)i, cache_entry->frame_paths[i]); - } - - ESP_LOGI(TAG, - "Loaded %u animation frame paths from %s", - (unsigned int)cache_entry->frame_count, - asset_dir); - return ESP_OK; -} - -static esp_err_t lcd_face_validate_frame_decode(const char *path) -{ - lv_image_header_t header; - lv_result_t info_res = lv_image_decoder_get_info(path, &header); - if (info_res != LV_RESULT_OK) { - ESP_LOGW(TAG, "Animation frame header read failed: %s", path); - return ESP_ERR_INVALID_RESPONSE; - } - - if (header.w == 0 || header.h == 0) { - ESP_LOGW(TAG, "Animation frame has invalid dimensions: %s", path); - return ESP_ERR_INVALID_RESPONSE; - } - -#if !LV_BIN_DECODER_RAM_LOAD - if ((header.flags & LV_IMAGE_FLAGS_COMPRESSED) != 0) { - ESP_LOGW(TAG, - "Compressed animation frame requires CONFIG_LV_BIN_DECODER_RAM_LOAD: %s", - path); - return ESP_ERR_NOT_SUPPORTED; - } -#endif - -#if !LV_USE_LZ4 - if ((header.flags & LV_IMAGE_FLAGS_COMPRESSED) != 0) { - ESP_LOGW(TAG, "Compressed animation frame requires CONFIG_LV_USE_LZ4: %s", path); - return ESP_ERR_NOT_SUPPORTED; - } -#endif - - /* - * Header-only validation. The actual pixel decode happens once per frame in - * lcd_face_preload_frame(); fully decoding here as well would double the - * per-frame LZ4 work on every expression switch (idle->spin, spin->reveal). - * This runs in the LCD render task under the LVGL lock, so the extra decode - * froze the previous frame for the whole transition. Reading just the header - * still catches malformed/oversized frames and missing decoder config, and - * preload reports any real decode failure with the same fallback behavior. - */ - ESP_LOGD(TAG, - "Animation frame header ok: %s, %ux%u, cf=%u, flags=0x%04x", - path, - (unsigned int)header.w, - (unsigned int)header.h, - (unsigned int)header.cf, - (unsigned int)header.flags); - return ESP_OK; -} - -static esp_err_t lcd_face_validate_frames(const lcd_face_sequence_cache_entry_t *cache_entry) -{ - ESP_RETURN_ON_FALSE(cache_entry != NULL, ESP_ERR_INVALID_ARG, TAG, "cache entry is null"); - - for (size_t i = 0; i < cache_entry->frame_count; ++i) { - ESP_RETURN_ON_ERROR(lcd_face_validate_frame_decode(cache_entry->frame_paths[i]), - TAG, - "validate animation frame failed"); - } - - return ESP_OK; -} - -static esp_err_t lcd_face_preload_frame(lcd_face_sequence_cache_entry_t *cache_entry, size_t frame_index) -{ - ESP_RETURN_ON_FALSE(cache_entry != NULL, ESP_ERR_INVALID_ARG, TAG, "cache entry is null"); - ESP_RETURN_ON_FALSE(frame_index < cache_entry->frame_count, - ESP_ERR_INVALID_ARG, - TAG, - "preload frame index out of range"); - - if (cache_entry->frame_buffers[frame_index] != NULL) { - return ESP_OK; - } - - lv_image_decoder_dsc_t dsc; - const lv_image_decoder_args_t args = { - .stride_align = LV_DRAW_BUF_STRIDE_ALIGN != 1, - .premultiply = false, - .no_cache = true, - .use_indexed = false, - .flush_cache = false, - }; - - lv_result_t open_res = lv_image_decoder_open(&dsc, cache_entry->frame_paths[frame_index], &args); - if (open_res != LV_RESULT_OK || dsc.decoded == NULL) { - ESP_LOGW(TAG, - "Animation frame preload decode failed: %s, result=%d, decoded=%p", - cache_entry->frame_paths[frame_index], - (int)open_res, - (const void *)dsc.decoded); - if (open_res == LV_RESULT_OK) { - lv_image_decoder_close(&dsc); - } - return ESP_ERR_INVALID_RESPONSE; - } - - lv_draw_buf_t *frame_buffer = lv_draw_buf_dup_ex(lcd_face_get_draw_buf_handlers(), dsc.decoded); - lv_image_decoder_close(&dsc); - if (frame_buffer == NULL) { - ESP_LOGW(TAG, "Animation frame preload allocation failed: %s", cache_entry->frame_paths[frame_index]); - return ESP_ERR_NO_MEM; - } - - cache_entry->frame_buffers[frame_index] = frame_buffer; - cache_entry->frame_sources[frame_index] = frame_buffer; - ESP_LOGD(TAG, - "Preloaded animation frame[%u]: %s, %ux%u, %u bytes", - (unsigned int)frame_index, - cache_entry->frame_paths[frame_index], - (unsigned int)frame_buffer->header.w, - (unsigned int)frame_buffer->header.h, - (unsigned int)frame_buffer->data_size); - return ESP_OK; -} - -static esp_err_t lcd_face_decode_cache_entry(lcd_face_sequence_cache_entry_t *cache_entry, - size_t decode_budget, - bool *out_complete) -{ - ESP_RETURN_ON_FALSE(cache_entry != NULL && cache_entry->prepared, - ESP_ERR_INVALID_ARG, - TAG, - "cache entry is not prepared"); - - size_t decoded = 0; - while (cache_entry->loaded_count < cache_entry->frame_count && decoded < decode_budget) { - esp_err_t err = lcd_face_preload_frame(cache_entry, cache_entry->loaded_count); - if (err != ESP_OK) { - lcd_face_release_cache_entry(cache_entry); - return err; - } - - cache_entry->loaded_count++; - decoded++; - } - - bool complete = cache_entry->loaded_count >= cache_entry->frame_count; - if (complete) { - lcd_face_touch_cache_entry(cache_entry); - } - - if (out_complete != NULL) { - *out_complete = complete; - } - - return ESP_OK; -} - -static esp_err_t lcd_face_prepare_cache_entry(lcd_face_sequence_cache_entry_t *cache_entry, - const char *asset_dir, - const uint8_t *frame_order, - size_t frame_order_count) -{ - ESP_RETURN_ON_FALSE(cache_entry != NULL, ESP_ERR_INVALID_ARG, TAG, "cache entry is null"); - ESP_RETURN_ON_FALSE(asset_dir != NULL && asset_dir[0] != '\0', - ESP_ERR_INVALID_ARG, - TAG, - "asset dir is empty"); - ESP_RETURN_ON_FALSE(frame_order_count <= LCD_FACE_ANIMATION_MAX, - ESP_ERR_INVALID_ARG, - TAG, - "frame order too long"); - - size_t asset_dir_len = strlen(asset_dir); - ESP_RETURN_ON_FALSE(asset_dir_len < sizeof(cache_entry->asset_dir), - ESP_ERR_INVALID_ARG, - TAG, - "asset dir too long"); - if (frame_order_count > 0U) { - ESP_RETURN_ON_FALSE(frame_order != NULL, ESP_ERR_INVALID_ARG, TAG, "frame order is null"); - } - - lcd_face_release_cache_entry(cache_entry); - - cache_entry->cache_policy = lcd_face_asset_cache_policy(asset_dir); - memcpy(cache_entry->asset_dir, asset_dir, asset_dir_len + 1U); - cache_entry->frame_order_count = frame_order_count; - if (frame_order_count > 0U) { - memcpy(cache_entry->frame_order, frame_order, frame_order_count); - } - - esp_err_t err = lcd_face_scan_frames(cache_entry, asset_dir, frame_order, frame_order_count); - if (err != ESP_OK) { - lcd_face_release_cache_entry(cache_entry); - return err; - } - - err = lcd_face_validate_frames(cache_entry); - if (err != ESP_OK) { - lcd_face_release_cache_entry(cache_entry); - return err; - } - - cache_entry->prepared = true; - lcd_face_touch_cache_entry(cache_entry); - ESP_LOGI(TAG, - "Animation cache prepared: asset=%s policy=%s frames=%u", - cache_entry->asset_dir, - lcd_face_cache_policy_name(cache_entry->cache_policy), - (unsigned int)cache_entry->frame_count); - return ESP_OK; -} - -static esp_err_t lcd_face_get_cache_entry(const lcd_face_asset_sequence_t *sequence, - lcd_face_sequence_cache_entry_t **out_entry) -{ - ESP_RETURN_ON_FALSE(sequence != NULL, ESP_ERR_INVALID_ARG, TAG, "sequence is null"); - ESP_RETURN_ON_FALSE(out_entry != NULL, ESP_ERR_INVALID_ARG, TAG, "out entry is null"); - - const char *asset_dir = sequence->asset_dir != NULL ? sequence->asset_dir : LCD_FACE_DEFAULT_ASSET_DIR; - const uint8_t *frame_order = sequence->frame_order; - size_t frame_order_count = sequence->frame_order_count; - - lcd_face_sequence_cache_entry_t *cache_entry = - lcd_face_find_cache_entry(asset_dir, frame_order, frame_order_count); - if (cache_entry != NULL) { - lcd_face_touch_cache_entry(cache_entry); - *out_entry = cache_entry; - return ESP_OK; - } - - cache_entry = lcd_face_allocate_cache_entry(); - if (cache_entry == NULL) { - ESP_LOGW(TAG, "No animation cache entry available for %s", asset_dir); - return ESP_ERR_NO_MEM; - } - - esp_err_t err = lcd_face_prepare_cache_entry(cache_entry, asset_dir, frame_order, frame_order_count); - if (err != ESP_OK) { - return err; - } - - *out_entry = cache_entry; - return ESP_OK; -} - -static esp_err_t lcd_face_create_root(lv_obj_t *screen, lv_display_t *display) -{ - s_face.root = lv_obj_create(screen); - if (s_face.root == NULL) { - return ESP_ERR_NO_MEM; - } - - lcd_face_init_transparent_box(s_face.root); - lv_obj_set_size(s_face.root, - lv_display_get_horizontal_resolution(display), - lv_display_get_vertical_resolution(display)); - lv_obj_center(s_face.root); - lv_obj_add_event_cb(s_face.root, lcd_face_root_delete_cb, LV_EVENT_DELETE, NULL); - return ESP_OK; -} - -static int32_t lcd_face_animimg_frame_path(const lv_anim_t *animation) -{ - if (animation == NULL || animation->duration <= 0) { - return 0; - } - - s_face.path_call_count++; - - int32_t frame_count = animation->end_value - animation->start_value + 1; - if (frame_count <= 1) { - return animation->start_value; - } - - int32_t elapsed_ms = animation->act_time; - if (elapsed_ms < 0) { - elapsed_ms = 0; - } - - int32_t frame = (elapsed_ms * frame_count) / animation->duration; - if (frame >= frame_count) { - frame = frame_count - 1; - } - - int32_t result = animation->start_value + frame; - if (result != s_face.last_path_frame) { - s_face.last_path_frame = result; - s_face.path_frame_change_count++; - if (s_face.path_frame_change_count <= LCD_FACE_ANIMATION_TRACE_LIMIT || - (s_face.path_frame_change_count % 30) == 0) { - ESP_LOGD(TAG, - "animimg path frame=%d act_time=%ld duration=%ld calls=%lu changes=%lu", - (int)result, - (long)animation->act_time, - (long)animation->duration, - (unsigned long)s_face.path_call_count, - (unsigned long)s_face.path_frame_change_count); - } - } - - return result; -} - -static int32_t lcd_face_clamp_frame_index(int32_t frame) -{ - if (frame < 0) { - return 0; - } - - if (frame >= (int32_t)s_face.frame_count) { - return (int32_t)s_face.frame_count - 1; - } - - return frame; -} - -static void lcd_face_apply_animimg_frame(lv_obj_t *animimg, int32_t requested_frame, bool initial) -{ - if (animimg == NULL || s_face.frame_count == 0) { - return; - } - - int32_t frame = lcd_face_clamp_frame_index(requested_frame); - int32_t previous_frame = s_face.last_applied_frame; - int32_t next_frame = previous_frame < 0 ? frame : (previous_frame + 1) % (int32_t)s_face.frame_count; - bool repeated_frame = previous_frame == frame; - int32_t expected_frame = repeated_frame ? previous_frame : next_frame; - bool ordered_frame = previous_frame < 0 || repeated_frame || frame == next_frame; - - uint32_t now = lv_tick_get(); - uint32_t elapsed_ms = s_face.last_apply_tick == 0 ? 0 : lv_tick_elaps(s_face.last_apply_tick); - - lv_image_set_src(animimg, s_face.frame_sources[frame]); - - s_face.last_applied_frame = frame; - s_face.last_apply_tick = now; - if (!repeated_frame) { - s_face.applied_frame_count++; - } - - if (!ordered_frame) { - s_face.applied_skip_count++; - ESP_LOGW(TAG, - "animimg apply skipped order: requested=%d applied=%d previous=%d expected=%d elapsed=%lu skips=%lu", - (int)requested_frame, - (int)frame, - (int)previous_frame, - (int)expected_frame, - (unsigned long)elapsed_ms, - (unsigned long)s_face.applied_skip_count); - } - - if (initial || !repeated_frame || s_face.applied_frame_count <= LCD_FACE_ANIMATION_TRACE_LIMIT) { - if (s_face.applied_frame_count <= LCD_FACE_ANIMATION_TRACE_LIMIT || - (s_face.applied_frame_count % 30) == 0 || - !ordered_frame) { - ESP_LOGD(TAG, - "animimg apply frame=%d requested=%d previous=%d expected=%d changes=%lu elapsed=%lu src=%s", - (int)frame, - (int)requested_frame, - (int)previous_frame, - (int)expected_frame, - (unsigned long)s_face.applied_frame_count, - (unsigned long)elapsed_ms, - s_face.frame_paths[frame]); - } - } -} - -static void lcd_face_animimg_exec_cb(void *obj, int32_t frame) -{ - lcd_face_apply_animimg_frame((lv_obj_t *)obj, frame, false); -} - -static void lcd_face_animation_completed_cb(lv_anim_t *animation) -{ - (void)animation; - s_face.completed = true; -} - -static void lcd_face_configure_animimg(lv_obj_t *animimg, uint32_t frame_ms, uint16_t repeat_count) -{ - lv_animimg_t *animimg_state = (lv_animimg_t *)animimg; - lv_anim_t *animation = &animimg_state->anim; - uint32_t resolved_frame_ms = frame_ms > 0U ? frame_ms : LCD_FACE_ANIMATION_FRAME_MS; - uint32_t resolved_repeat_count = repeat_count > 0U ? repeat_count : LV_ANIM_REPEAT_INFINITE; - - lv_anim_set_values(animation, 0, (int32_t)s_face.frame_count - 1); - lv_anim_set_duration(animation, (uint32_t)s_face.frame_count * resolved_frame_ms); - lv_anim_set_path_cb(animation, lcd_face_animimg_frame_path); - lv_anim_set_exec_cb(animation, lcd_face_animimg_exec_cb); - lv_anim_set_completed_cb(animation, lcd_face_animation_completed_cb); - lv_anim_set_repeat_count(animation, resolved_repeat_count); - lv_anim_set_repeat_delay(animation, 0); - lv_anim_set_playback_duration(animation, 0); - lv_anim_set_playback_delay(animation, 0); -} - -static int32_t lcd_face_find_source_frame_index(const void *source) -{ - if (source == NULL) { - return -1; - } - - for (size_t i = 0; i < s_face.frame_count; ++i) { - if (source == s_face.frame_sources[i]) { - return (int32_t)i; - } - } - - if (lv_image_src_get_type(source) == LV_IMAGE_SRC_FILE) { - const char *source_path = (const char *)source; - for (size_t i = 0; i < s_face.frame_count; ++i) { - if (strcmp(source_path, s_face.frame_paths[i]) == 0) { - return (int32_t)i; - } - } - } - - return -1; -} - -static const char *lcd_face_source_label(const void *source, int32_t source_frame) -{ - if (source_frame >= 0 && source_frame < (int32_t)s_face.frame_count) { - return s_face.frame_paths[source_frame]; - } - - if (source == NULL) { - return "(null)"; - } - - if (lv_image_src_get_type(source) == LV_IMAGE_SRC_FILE) { - return (const char *)source; - } - - return "(variable image)"; -} - -static void lcd_face_animation_probe_timer_cb(lv_timer_t *timer) -{ - lcd_face_state_t *state = lv_timer_get_user_data(timer); - if (state == NULL || state->root == NULL || state->animimg == NULL) { - if (state != NULL) { - state->animation_probe_timer = NULL; - } - lv_timer_delete(timer); - return; - } - - lv_animimg_t *animimg_state = (lv_animimg_t *)state->animimg; - lv_anim_t *running_animation = lv_anim_get(state->animimg, animimg_state->anim.exec_cb); - lv_timer_t *animation_timer = lv_anim_get_timer(); - bool animation_timer_paused = animation_timer != NULL && lv_timer_get_paused(animation_timer); - - const void *current_source = lv_image_get_src(state->animimg); - int32_t source_frame = lcd_face_find_source_frame_index(current_source); - const char *source_label = lcd_face_source_label(current_source, source_frame); - state->last_probe_frame = source_frame; - - ESP_LOGD(TAG, - "animimg health running=%p anim_count=%u anim_timer_paused=%d path_calls=%lu path_frame=%d applied_frame=%d sample_src_frame=%d applied_changes=%lu applied_skips=%lu src=%s", - (void *)running_animation, - (unsigned int)lv_anim_count_running(), - animation_timer_paused, - (unsigned long)state->path_call_count, - (int)state->last_path_frame, - (int)state->last_applied_frame, - (int)source_frame, - (unsigned long)state->applied_frame_count, - (unsigned long)state->applied_skip_count, - source_label); - - if (running_animation == NULL) { - ESP_LOGW(TAG, "animimg probe: LVGL has no running animation for the animimg object"); - } else if (state->path_call_count > LCD_FACE_ANIMATION_TRACE_LIMIT && state->applied_frame_count <= 1) { - ESP_LOGW(TAG, "animimg probe: animation path is advancing, but image source is not changing"); - } else if (source_frame != state->last_applied_frame) { - ESP_LOGW(TAG, - "animimg probe: applied frame/source mismatch applied=%d src=%d", - (int)state->last_applied_frame, - (int)source_frame); - } -} - -static esp_err_t lcd_face_create_animation_screen(lv_obj_t *screen, - lv_display_t *display, - const lcd_face_asset_sequence_t *sequence) -{ - uint32_t frame_ms = sequence != NULL ? sequence->frame_ms : LCD_FACE_ANIMATION_FRAME_MS; - uint16_t repeat_count = sequence != NULL ? sequence->repeat_count : 0U; - - ESP_RETURN_ON_ERROR(lcd_face_mount_littlefs(), TAG, "mount LittleFS failed"); - - lcd_face_sequence_cache_entry_t *cache_entry = NULL; - ESP_RETURN_ON_ERROR(lcd_face_get_cache_entry(sequence, &cache_entry), TAG, "get animation cache failed"); - - bool cache_complete = false; - ESP_RETURN_ON_ERROR(lcd_face_decode_cache_entry(cache_entry, SIZE_MAX, &cache_complete), - TAG, - "complete animation cache failed"); - ESP_RETURN_ON_FALSE(cache_complete, ESP_FAIL, TAG, "animation cache did not complete"); - - esp_err_t err = lcd_face_create_root(screen, display); - if (err != ESP_OK) { - ESP_LOGE(TAG, "animation root create failed: %s", esp_err_to_name(err)); - return err; - } - - s_face.cache_entry = cache_entry; - s_face.frame_count = cache_entry->frame_count; - for (size_t i = 0; i < cache_entry->frame_count; ++i) { - s_face.frame_sources[i] = cache_entry->frame_sources[i]; - s_face.frame_paths[i] = cache_entry->frame_paths[i]; - } - s_face.last_path_frame = -1; - s_face.last_applied_frame = -1; - s_face.last_probe_frame = -1; - s_face.path_call_count = 0; - s_face.path_frame_change_count = 0; - s_face.applied_frame_count = 0; - s_face.applied_skip_count = 0; - s_face.last_apply_tick = 0; - s_face.completed = false; - s_face.return_to_idle = sequence != NULL && sequence->return_to_idle; - - s_face.animimg = lv_animimg_create(s_face.root); - if (s_face.animimg == NULL) { - lv_obj_delete(s_face.root); - return ESP_ERR_NO_MEM; - } - - lv_animimg_set_src(s_face.animimg, s_face.frame_sources, s_face.frame_count); - lcd_face_configure_animimg(s_face.animimg, frame_ms, repeat_count); - lcd_face_apply_animimg_frame(s_face.animimg, 0, true); - lv_obj_center(s_face.animimg); - lv_animimg_start(s_face.animimg); - - lv_timer_t *animation_timer = lv_anim_get_timer(); - if (animation_timer != NULL) { - lv_timer_resume(animation_timer); - lv_timer_ready(animation_timer); - } - - lv_animimg_t *animimg_state = (lv_animimg_t *)s_face.animimg; - ESP_LOGI(TAG, - "animimg started obj=%p running=%p anim_count=%u frames=%u duration=%lu timer=%p timer_paused=%d", - (void *)s_face.animimg, - (void *)lv_anim_get(s_face.animimg, animimg_state->anim.exec_cb), - (unsigned int)lv_anim_count_running(), - (unsigned int)s_face.frame_count, - (unsigned long)((uint32_t)s_face.frame_count * - (frame_ms > 0U ? frame_ms : LCD_FACE_ANIMATION_FRAME_MS)), - (void *)animation_timer, - animation_timer != NULL && lv_timer_get_paused(animation_timer)); - - s_face.animation_probe_timer = lv_timer_create(lcd_face_animation_probe_timer_cb, - LCD_FACE_ANIMATION_PROBE_MS, - &s_face); - if (s_face.animation_probe_timer == NULL) { - ESP_LOGW(TAG, "animimg probe timer create failed"); - } - - return ESP_OK; -} - -static void lcd_face_apply_eye_state(lcd_face_state_t *state) -{ - lv_coord_t eye_height = state->eyes_closed ? LCD_FACE_EYE_CLOSED_H : LCD_FACE_EYE_OPEN_H; - lv_coord_t eye_width = LCD_FACE_EYE_OPEN_W; - lv_coord_t eye_radius = state->eyes_closed ? (LCD_FACE_EYE_CLOSED_H / 2) : (LCD_FACE_EYE_OPEN_H / 2); - - lv_obj_set_size(state->left_eye, eye_width, eye_height); - lv_obj_set_size(state->right_eye, eye_width, eye_height); - lv_obj_set_style_radius(state->left_eye, eye_radius, LV_PART_MAIN); - lv_obj_set_style_radius(state->right_eye, eye_radius, LV_PART_MAIN); - lv_obj_center(state->left_eye); - lv_obj_center(state->right_eye); -} - -static void lcd_face_blink_timer_cb(lv_timer_t *timer) -{ - lcd_face_state_t *state = lv_timer_get_user_data(timer); - if (state == NULL || state->root == NULL || state->left_eye == NULL || state->right_eye == NULL) { - if (state != NULL) { - state->blink_timer = NULL; - } - lv_timer_delete(timer); - return; - } - - /* Alternate between a long open period and a short blink closure. */ - state->eyes_closed = !state->eyes_closed; - lcd_face_apply_eye_state(state); - lv_timer_set_period(timer, state->eyes_closed ? LCD_FACE_BLINK_CLOSED_MS : LCD_FACE_BLINK_OPEN_MS); -} - -static void lcd_face_root_delete_cb(lv_event_t *event) -{ - (void)event; - - if (s_face.blink_timer != NULL) { - lv_timer_delete(s_face.blink_timer); - } - - if (s_face.animation_probe_timer != NULL) { - lv_timer_delete(s_face.animation_probe_timer); - } - - if (s_face.animimg != NULL) { - lv_anim_delete(s_face.animimg, NULL); - } - - memset(&s_face, 0, sizeof(s_face)); -} - -static lv_obj_t *lcd_face_create_eye_shape(lv_obj_t *parent, lv_coord_t x_offset) -{ - lv_obj_t *container = lv_obj_create(parent); - if (container == NULL) { - return NULL; - } - - lcd_face_init_transparent_box(container); - lv_obj_set_size(container, LCD_FACE_EYE_CONTAINER_W, LCD_FACE_EYE_CONTAINER_H); - lv_obj_align(container, LV_ALIGN_CENTER, x_offset, LCD_FACE_EYE_Y_OFFSET); - - lv_obj_t *eye = lv_obj_create(container); - if (eye == NULL) { - return NULL; - } - - lcd_face_init_transparent_box(eye); - lv_obj_set_style_bg_opa(eye, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_bg_color(eye, lv_color_hex(LCD_FACE_PRIMARY_COLOR), LV_PART_MAIN); - lv_obj_center(eye); - return eye; -} - -static lv_obj_t *lcd_face_create_feature_box(lv_obj_t *parent, - lv_coord_t width, - lv_coord_t height, - lv_align_t align, - lv_coord_t x_offset, - lv_coord_t y_offset, - uint32_t color_hex, - lv_coord_t radius) -{ - lv_obj_t *obj = lv_obj_create(parent); - if (obj == NULL) { - return NULL; - } - - lcd_face_init_transparent_box(obj); - lv_obj_set_size(obj, width, height); - lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_bg_color(obj, lv_color_hex(color_hex), LV_PART_MAIN); - lv_obj_set_style_radius(obj, radius, LV_PART_MAIN); - lv_obj_align(obj, align, x_offset, y_offset); - return obj; -} - -static lv_obj_t *lcd_face_create_line(lv_obj_t *parent, - const lv_point_precise_t *points, - uint32_t point_count, - lv_coord_t x, - lv_coord_t y, - uint32_t color_hex, - lv_coord_t line_width) -{ - lv_obj_t *line = lv_line_create(parent); - if (line == NULL) { - return NULL; - } - - lv_line_set_points(line, points, point_count); - lv_obj_set_pos(line, x, y); - lv_obj_set_style_line_width(line, line_width, LV_PART_MAIN); - lv_obj_set_style_line_color(line, lv_color_hex(color_hex), LV_PART_MAIN); - lv_obj_set_style_line_rounded(line, true, LV_PART_MAIN); - return line; -} - -static void lcd_face_delete_current_root(void) -{ - if (s_face.root != NULL) { - lv_obj_delete(s_face.root); - } -} - -static esp_err_t lcd_face_prepare_screen(lv_obj_t *screen, lv_display_t **out_display) -{ - ESP_RETURN_ON_FALSE(screen != NULL, ESP_ERR_INVALID_ARG, TAG, "screen is null"); - - lv_display_t *display = lv_obj_get_display(screen); - ESP_RETURN_ON_FALSE(display != NULL, ESP_ERR_INVALID_STATE, TAG, "display is null"); - - lv_obj_set_style_bg_color(screen, lv_color_hex(LCD_FACE_BG_COLOR), LV_PART_MAIN); - lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, LV_PART_MAIN); - lv_obj_set_style_border_width(screen, 0, LV_PART_MAIN); - lv_obj_set_style_radius(screen, 0, LV_PART_MAIN); - lv_obj_set_style_pad_all(screen, 0, LV_PART_MAIN); - lv_obj_clear_flag(screen, LV_OBJ_FLAG_SCROLLABLE); - - if (out_display != NULL) { - *out_display = display; - } - - return ESP_OK; -} - -esp_err_t lcd_face_create_idle_screen(lv_obj_t *screen) -{ - lv_display_t *display = NULL; - ESP_RETURN_ON_ERROR(lcd_face_prepare_screen(screen, &display), TAG, "prepare face screen failed"); - - if (s_face.root != NULL) { - return ESP_OK; - } - - const lcd_face_asset_sequence_t idle_sequence = { - .asset_dir = LCD_FACE_DEFAULT_ASSET_DIR, - .frame_order = NULL, - .frame_order_count = 0, - .repeat_count = 0, - .frame_ms = LCD_FACE_ANIMATION_FRAME_MS, - .return_to_idle = false, - }; - esp_err_t animation_err = lcd_face_create_animation_screen(screen, display, &idle_sequence); - if (animation_err == ESP_OK) { - ESP_LOGI(TAG, "LVGL default animation idle screen started"); - return ESP_OK; - } - - ESP_LOGW(TAG, - "Default animation unavailable (%s), falling back to drawn cat face", - esp_err_to_name(animation_err)); - - ESP_RETURN_ON_ERROR(lcd_face_create_root(screen, display), TAG, "drawn face root create failed"); - - s_face.left_eye = lcd_face_create_eye_shape(s_face.root, -LCD_FACE_EYE_X_OFFSET); - s_face.right_eye = lcd_face_create_eye_shape(s_face.root, LCD_FACE_EYE_X_OFFSET); - if (s_face.left_eye == NULL || s_face.right_eye == NULL) { - lv_obj_delete(s_face.root); - return ESP_ERR_NO_MEM; - } - - if (lcd_face_create_feature_box(s_face.root, - LCD_FACE_NOSE_W, - LCD_FACE_NOSE_H, - LV_ALIGN_CENTER, - 0, - 8, - LCD_FACE_SECONDARY_COLOR, - LCD_FACE_NOSE_H / 2) == NULL || - lcd_face_create_line(s_face.root, s_mouth_stem, 2, 160, 128, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_mouth_left, 2, 142, 144, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_mouth_right, 2, 160, 144, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_left_upper_whisker, 2, 42, 112, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_left_middle_whisker, 2, 38, 128, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_left_lower_whisker, 2, 42, 144, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_right_upper_whisker, 2, 194, 112, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_right_middle_whisker, 2, 194, 128, LCD_FACE_SECONDARY_COLOR, 3) == NULL || - lcd_face_create_line(s_face.root, s_right_lower_whisker, 2, 194, 144, LCD_FACE_SECONDARY_COLOR, 3) == NULL) { - lv_obj_delete(s_face.root); - return ESP_ERR_NO_MEM; - } - - s_face.eyes_closed = false; - lcd_face_apply_eye_state(&s_face); - - s_face.blink_timer = lv_timer_create(lcd_face_blink_timer_cb, LCD_FACE_BLINK_OPEN_MS, &s_face); - if (s_face.blink_timer == NULL) { - lv_obj_delete(s_face.root); - return ESP_ERR_NO_MEM; - } - - return ESP_OK; -} - -esp_err_t lcd_face_show_idle(lv_obj_t *screen) -{ - lcd_face_delete_current_root(); - return lcd_face_create_idle_screen(screen); -} - -esp_err_t lcd_face_play_asset_sequence(lv_obj_t *screen, const lcd_face_asset_sequence_t *sequence) -{ - ESP_RETURN_ON_FALSE(sequence != NULL, ESP_ERR_INVALID_ARG, TAG, "sequence is null"); - - lv_display_t *display = NULL; - ESP_RETURN_ON_ERROR(lcd_face_prepare_screen(screen, &display), TAG, "prepare face screen failed"); - - lcd_face_delete_current_root(); - return lcd_face_create_animation_screen(screen, display, sequence); -} - -esp_err_t lcd_face_preheat_asset_sequence(const lcd_face_asset_sequence_t *sequence, - size_t decode_budget, - bool *out_complete) -{ - if (out_complete != NULL) { - *out_complete = false; - } - - ESP_RETURN_ON_FALSE(sequence != NULL, ESP_ERR_INVALID_ARG, TAG, "sequence is null"); - ESP_RETURN_ON_ERROR(lcd_face_mount_littlefs(), TAG, "mount LittleFS failed"); - - lcd_face_sequence_cache_entry_t *cache_entry = NULL; - ESP_RETURN_ON_ERROR(lcd_face_get_cache_entry(sequence, &cache_entry), TAG, "get animation cache failed"); - return lcd_face_decode_cache_entry(cache_entry, decode_budget, out_complete); -} - -bool lcd_face_take_completed(bool *out_return_to_idle) -{ - if (!s_face.completed) { - return false; - } - - if (out_return_to_idle != NULL) { - *out_return_to_idle = s_face.return_to_idle; - } - s_face.completed = false; - return true; -} diff --git a/components/platform/src/runtime/platform_lcd_runtime.c b/components/platform/src/runtime/platform_lcd_runtime.c index cf72675..b2e47de 100644 --- a/components/platform/src/runtime/platform_lcd_runtime.c +++ b/components/platform/src/runtime/platform_lcd_runtime.c @@ -5,40 +5,44 @@ */ #include "platform.h" - -#include "lcd.h" +#include "lcd_direct.h" esp_err_t platform_init_lcd_device(void) { - return lcd_init_device(); + return lcd_direct_init_device(); +} + +void platform_run_lcd_blocking_test(void) +{ + lcd_direct_run_blocking_test(); } void platform_start_lcd_render_loop(void) { - lcd_start_render_loop(); + lcd_direct_start_render_loop(); } esp_err_t platform_stop_lcd_render_loop(int timeout_ms) { - return lcd_stop_render_loop(timeout_ms); + return lcd_direct_stop_render_loop(timeout_ms); } esp_err_t platform_lcd_request_presentation(const platform_lcd_presentation_request_t *request) { - return lcd_request_presentation(request); + return lcd_direct_request_presentation(request); } esp_err_t platform_lcd_preheat_presentation(const platform_lcd_presentation_request_t *request) { - return lcd_preheat_presentation(request); + return lcd_direct_preheat_presentation(request); } uint32_t platform_lcd_presentation_completion_count(void) { - return lcd_presentation_completion_count(); + return lcd_direct_presentation_completion_count(); } esp_err_t platform_lcd_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms) { - return lcd_wait_presentation_completion(since_count, timeout_ms); + return lcd_direct_wait_presentation_completion(since_count, timeout_ms); } diff --git a/dependencies.lock b/dependencies.lock index 6738b26..63f0be0 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -69,13 +69,6 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 1.20.4 - lvgl/lvgl: - component_hash: 2409fdc84e9766d2a18afd84b051aef3f838348136cdb1c10ac2e8bcdb012bf6 - dependencies: [] - source: - registry_url: https://components.espressif.com/ - type: service - version: 9.2.0 direct_dependencies: - 78/esp-wifi-connect - espressif/esp_audio_codec @@ -84,7 +77,6 @@ direct_dependencies: - espressif/mdns - idf - joltwallet/littlefs -- lvgl/lvgl -manifest_hash: 25e7c87e44ac0d024d82e8b7ab761db0d899a3e38aefae70937002b82b370423 +manifest_hash: b7c0e21c22598b04ad72173217dbd4f7c984674b1b7df2f688b54eb19cff8a12 target: esp32s3 version: 2.0.0 diff --git a/docs/adr/0001-lcd-expression-presentation-ownership.md b/docs/adr/0001-lcd-expression-presentation-ownership.md index 2601e63..b3dc285 100644 --- a/docs/adr/0001-lcd-expression-presentation-ownership.md +++ b/docs/adr/0001-lcd-expression-presentation-ownership.md @@ -21,8 +21,8 @@ copying cross-layer contracts or coupling orchestration code to asset paths. `platform_lcd_presentation_*` in `components/platform/include/platform.h`. - Domain runtime adapters map semantic expressions to platform presentation requests. -- Platform LCD internals serialize presentation requests and perform all LVGL - object mutation inside the LCD render task. +- Platform LCD internals serialize presentation requests and perform all panel + drawing inside the LCD render task. ## Consequences diff --git a/docs/boot-sequence-plan-a.md b/docs/boot-sequence-plan-a.md index 0d4f831..0907899 100644 --- a/docs/boot-sequence-plan-a.md +++ b/docs/boot-sequence-plan-a.md @@ -46,7 +46,7 @@ power hold | 2 | Audio init | Wait with bounded timeout | Log and skip boot sound | Initializes the codec path needed for playback: I2C, I2S, codec, PA control, codec buffers, and decoder resources. | | 3 | Boot sound playback | Wait for completion or timeout | Stop playback and continue boot | The sound must not block startup indefinitely. Use a short product-defined timeout. | | 4 | Audio release | Wait for synchronous release | Continue if release reports a recoverable error | If Wi-Fi/TLS starts soon after, deinit or release idle audio resources to reduce DMA/internal heap pressure. | -| 5 | LCD device init | Wait | Continue with display disabled if init fails | Initializes SPI, panel, LVGL, draw buffers, tick timer, and backlight. | +| 5 | LCD device init | Wait | Continue with display disabled if init fails | Initializes SPI, ST7789 panel, direct draw buffers, render state, and backlight. | | 6 | LCD render loop | Start and continue | Continue with display disabled if task creation fails | Needed for the default boot image or animation to appear. | | 7 | Boot/default expression | Do not wait for animation completion | Ignore request failure when LCD is unavailable | This is the first visible boot state. | | 8 | Platform/domain init | Wait | Stop boot on unrecoverable error | Performs stepper electrical initialization and domain direction state setup. | @@ -91,7 +91,7 @@ storage/filesystem prerequisite must be moved before audio playback. TLS handshake, HTTPS requests, or fortune refresh. - Prefer releasing idle audio resources before Wi-Fi bootstrap to reduce internal heap and DMA-capable memory pressure. -- Do not initialize LCD/LVGL draw buffers concurrently with audio init unless +- Do not initialize LCD draw buffers concurrently with audio init unless memory measurements prove it is stable. - Boot sound playback must have bounded completion semantics. - Audio startup failure must not prevent display, calibration, or Wi-Fi startup. @@ -133,4 +133,3 @@ The boot sound API should define: - what happens after timeout, - whether audio resources remain initialized after playback, - idempotent behavior for repeated boot calls. - diff --git a/docs/lcd-expressions.md b/docs/lcd-expressions.md index 3d0690f..972722c 100644 --- a/docs/lcd-expressions.md +++ b/docs/lcd-expressions.md @@ -9,7 +9,7 @@ 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. +- `platform` owns LCD presentation primitives and direct ST7789 drawing. Do not bypass this path from upper layers. The expected flow is: @@ -17,22 +17,34 @@ 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 + -> 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 ST7789 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 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 | Behavior | +| Domain expression | Trigger owner | Playback kind | Current 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. | +| `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: @@ -45,92 +57,18 @@ not meaningful without Wi-Fi connectivity. Transport disconnect/error events are not server error expressions. -## Asset Management +## Asset Migration Direction -Expression assets live under: +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: -```text -components/platform/assets/lvgl_animations// -``` - -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//.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`). +- 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 @@ -149,42 +87,26 @@ 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. +- 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. -- 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). | +| Wealth spin / wealth-direction reveal | `110` | Yes | | 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: @@ -194,14 +116,11 @@ Follow this checklist for every new expression: 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//`. -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 +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`. -7. Build with ESP-IDF v5.5.3 and confirm the LittleFS image includes the asset - directory. +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 @@ -212,12 +131,9 @@ the unified presentation request path. 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. diff --git a/docs/lvgl_gif_reproduction_guide.md b/docs/lvgl_gif_reproduction_guide.md deleted file mode 100644 index 6d0d080..0000000 --- a/docs/lvgl_gif_reproduction_guide.md +++ /dev/null @@ -1,221 +0,0 @@ -# LVGL + GIF Reproduction Guide (ESP-IDF Project 1:1 Porting) - -This document tracks how to reproduce the `LVGL + ST7789V + GIF` runtime path in this repository, and records what is already implemented versus what is still missing. - -All claims should be traceable to file:line evidence. If evidence is not available in the repository, mark it as `UNKNOWN`. - -## Section 0: Current Implementation Status (2026-03-12) - -This section reflects the actual state of the repository and takes precedence over older assumptions. - -### 0.1 Scope approved in this round - -- Introduce LVGL using `lvgl/lvgl = 9.2.0`. -- Use a single ST7789V panel. -- Do not use shared SPI for multiple LCD panels. -- Follow LCD GPIO mapping from `docs/gpio-map.md`. -- Keep layered architecture unchanged: `app_composition -> control_plane -> domain -> platform`. - -### 0.2 Completed progress - -1. LVGL dependency was added and locked to `9.2.0`. - Evidence: `main/idf_component.yml:3`, `dependencies.lock:64`, `dependencies.lock:70`. -2. LCD runtime was migrated from JPEG demo rendering to `esp_lcd + lvgl`. - Evidence: `components/platform/CMakeLists.txt:13`, `components/platform/CMakeLists.txt:49`, `components/platform/CMakeLists.txt:58`, `components/platform/src/lcd.c:367`, `components/platform/src/lcd.c:401`, `components/platform/src/lcd.c:429`, `components/platform/src/lcd.c:543`. -3. Single ST7789V panel path is active. - Evidence: `components/platform/src/lcd.c:2`, `components/platform/src/lcd.c:401`, `components/platform/src/lcd.c:496`. -4. LCD pin mapping matches the GPIO map document. - Evidence: `docs/gpio-map.md:16`, `docs/gpio-map.md:18`, `docs/gpio-map.md:19`, `docs/gpio-map.md:20`, `docs/gpio-map.md:24`, `docs/gpio-map.md:26`, `components/platform/src/lcd.c:35`, `components/platform/src/lcd.c:36`, `components/platform/src/lcd.c:37`, `components/platform/src/lcd.c:38`, `components/platform/src/lcd.c:39`, `components/platform/src/lcd.c:40`, `sdkconfig.defaults:81`. -5. Build passed after integration. - Evidence: local run `idf.py fullclean && idf.py build` completed successfully. -6. No runtime `gc9a01` dependency remains under `components` and `main`. - Evidence: `rg -n "gc9a01|GC9A01" components main` returned no matches. - -### 0.3 Still missing - -1. GIF business pipeline is not implemented yet in the current codebase. - Missing modules include `components/gifdisplay/*`, GIF package loading, preload, and runtime switching. -2. Emotion-driven GIF switching path is not implemented (`components/emotion/*` is absent). -3. The current LVGL UI is a baseline default screen, not GIF playback UI. -4. Device-side GIF runtime logs are not available yet (`Switching gifs...` class logs are not present in runtime code). - -### 0.4 Differences vs the original baseline description - -1. Panel topology: - Original baseline: dual GC9A01. - Current implementation: single ST7789V. -2. LVGL version: - Original baseline: `9.4.0`. - Current implementation: `9.2.0` (explicitly requested in this round). -3. Display stack: - Original baseline references an older code layout and line map. - Current repository uses the layered runtime path and a rewritten `components/platform/src/lcd.c` implementation. -4. GIF status: - Original baseline assumes full GIF chain exists. - Current repository does not yet include GIF runtime components. - -### 0.5 Environment note - -- `AGENTS.md` requires ESP-IDF `5.5.2` setup instructions. - Evidence: `AGENTS.md:31`. -- The actual local verification in this round used ESP-IDF `5.5.1` because that environment path exists locally. - Evidence: `dependencies.lock:63`. - -## Section A: Reality Check + Runtime Call Graph (Current Repository) - -### A.1 Reality Check - -1. LVGL runtime path is now present in production LCD code (not only a test utility). - Evidence: `components/platform/src/lcd.c:415`, `components/platform/src/lcd.c:429`, `components/platform/src/lcd.c:479`, `components/platform/src/lcd.c:543`. -2. GIF playback path is not present in runtime code. - Evidence: `rg -n "gifdisplay|lv_gif|gifpack" components main` has no runtime hits. -3. `app_main` still starts LCD through layered interfaces. - Evidence: `main/main.c:285`, `components/app_composition/src/app_composition_runtime_support.c:65`, `components/control_plane/src/control_plane_runtime_support.c:39`, `components/domain/src/domain_runtime_support.c:39`, `components/platform/src/platform_runtime_support.c:176`. - -### A.2 Runtime Call Graph (`app_main` to panel flush) - -Current startup chain: - -`app_main` --> `app_composition_init_lcd_device` --> `control_plane_init_lcd_device` --> `domain_init_lcd_device` --> `platform_init_lcd_device` --> `platform_runtime_init_lcd_device` --> `lcd_init_device` --> `lcd_panel_init` --> `esp_lcd_new_panel_st7789` --> LVGL display setup --> `lv_timer_handler` loop --> `lvgl_flush_cb` --> `esp_lcd_panel_draw_bitmap` - -Evidence: -- `main/main.c:285` -- `components/app_composition/src/app_composition_runtime_support.c:65` -- `components/control_plane/src/control_plane_runtime_support.c:39` -- `components/domain/src/domain_runtime_support.c:39` -- `components/platform/src/platform_runtime_support.c:176` -- `components/platform/src/lcd.c:479` -- `components/platform/src/lcd.c:367` -- `components/platform/src/lcd.c:401` -- `components/platform/src/lcd.c:415` -- `components/platform/src/lcd.c:543` -- `components/platform/src/lcd.c:340` -- `components/platform/src/lcd.c:343` - -## Section B: LVGL Stack Details (Current) - -### B.1 Versions and dependencies - -- IDF in lock file: `5.5.1`. - Evidence: `dependencies.lock:63`. -- LVGL dependency: `lvgl/lvgl 9.2.0`. - Evidence: `main/idf_component.yml:3`, `dependencies.lock:64`, `dependencies.lock:70`. -- Platform component dependencies include `esp_lcd`, `esp_timer`, `lvgl`. - Evidence: `components/platform/CMakeLists.txt:49`, `components/platform/CMakeLists.txt:52`, `components/platform/CMakeLists.txt:58`. - -### B.2 Display driver stack (single ST7789V) - -- SPI host: `SPI2_HOST`. - Evidence: `components/platform/src/lcd.c:30`. -- Panel create API: `esp_lcd_new_panel_st7789`. - Evidence: `components/platform/src/lcd.c:401`. -- Flush callback: `lvgl_flush_cb` -> `esp_lcd_panel_draw_bitmap`. - Evidence: `components/platform/src/lcd.c:340`, `components/platform/src/lcd.c:343`. -- Flush done callback: `on_color_trans_done` -> `lv_display_flush_ready`. - Evidence: `components/platform/src/lcd.c:327`, `components/platform/src/lcd.c:335`, `components/platform/src/lcd.c:434`, `components/platform/src/lcd.c:437`. -- Tick source: `esp_timer` calling `lv_tick_inc` every 2 ms. - Evidence: `components/platform/src/lcd.c:89`, `components/platform/src/lcd.c:174`, `components/platform/src/lcd.c:177`, `components/platform/src/lcd.c:449`. -- LVGL loop: `lv_timer_handler` in blocking task loop. - Evidence: `components/platform/src/lcd.c:540`, `components/platform/src/lcd.c:543`. -- Locking: recursive mutex around LVGL operations. - Evidence: `components/platform/src/lcd.c:159`, `components/platform/src/lcd.c:164`, `components/platform/src/lcd.c:167`, `components/platform/src/lcd.c:170`, `components/platform/src/lcd.c:485`. - -### B.3 Key LCD/LVGL parameters - -- Resolution: `320x240`. - Evidence: `components/platform/src/lcd.c:86`, `components/platform/src/lcd.c:87`. -- Draw buffer lines: `20`. - Evidence: `components/platform/src/lcd.c:88`. -- Color format: `RGB565`. - Evidence: `components/platform/src/lcd.c:429`. -- SPI clock: `10 MHz`. - Evidence: `components/platform/src/lcd.c:31`. - - - -## Section C: GIF Pipeline Status - -### C.1 Current status - -- `UNKNOWN` as implemented feature in this repository branch: full GIF runtime pipeline. -- No `gifdisplay` component exists in current source tree. -- No `emotion` component exists in current source tree. - -### C.2 Required future implementation (not done yet) - -1. Add GIF runtime owner component (recommended: `components/gifdisplay`). -2. Add GIF asset packaging and partition-read path. -3. Add GIF widget playback path in LVGL (`lv_gif_set_src` or equivalent strategy). -4. Add business trigger path from runtime state to GIF switching. -5. Add startup preload / timeout / rollback strategy consistent with target behavior. - -## Section D: Layering and Dependency Compliance - -Current layered dependency chain remains valid: -- `app_composition` depends on `control_plane`. - Evidence: `components/app_composition/CMakeLists.txt:11`. -- `control_plane` depends on `domain`. - Evidence: `components/control_plane/CMakeLists.txt:13`. -- `domain` depends on `platform`. - Evidence: `components/domain/CMakeLists.txt:13`. -- `platform` owns runtime LCD integration directly (no separate `lcd` component dependency). - Evidence: `components/platform/CMakeLists.txt:13`, `components/platform/src/platform_runtime_support.c:11`, `components/platform/src/platform_runtime_support.c:176`. - -## Section E: Verification Matrix - -### E.1 Completed in this round - -| Check | Command | Result | -|---|---|---| -| Build | `idf.py fullclean && idf.py build` | Passed | -| LVGL dependency | lockfile inspection | `lvgl/lvgl = 9.2.0` | -| GC9A01 runtime residue | `rg -n "gc9a01|GC9A01" components main` | No matches | - -### E.2 Pending for next round - -| Check | Command | Expected | -|---|---|---| -| Device runtime LVGL stability | `idf.py -p flash monitor` | No frequent flush/panel failures | -| GIF startup and switching | Same monitor run with GIF triggers | GIF load and switching logs present | -| Long-run behavior | Continuous run test | Stable memory and frame updates | - -## Section F: Risks and Open Items - -1. The guide originally describes a complete GIF stack, but the current repository does not include those modules yet. -2. The current implementation is LVGL-base-only; GIF behavior parity is still pending. -3. Environment parity risk exists if strict ESP-IDF `5.5.2` compliance is mandatory. -4. Performance targets (FPS/CPU/memory thresholds) are not yet defined in code-level acceptance criteria. - -## Section G: Minimal File Set (Current State) - -Implemented in this round: -- `main/idf_component.yml` -- `dependencies.lock` -- `components/platform/CMakeLists.txt` -- `components/platform/internal/lcd.h` -- `components/platform/src/lcd.c` - -Not yet implemented but required for full GIF parity: -- `components/gifdisplay/*` -- `components/emotion/*` -- GIF assets and packaging scripts diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index e88d208..53f64b3 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -292,15 +292,6 @@ menu "Example Configuration" conservative production default; validate color stability and tearing on real hardware before trying 40000000. - config TQ_SCREEN_LVGL_DRAW_BUF_LINES - int "LVGL draw buffer lines" - default 40 - range 20 60 - help - Number of display rows per LVGL DMA draw buffer. The driver - allocates two internal DMA-capable RGB565 buffers, so 40 lines - uses about 50 KiB and 60 lines uses about 75 KiB. - config TQ_SCREEN_COLOR_AUTOTEST bool "Enable color auto-test loop" default n diff --git a/sdkconfig.defaults b/sdkconfig.defaults index cde04a2..8c66d57 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -76,6 +76,8 @@ CONFIG_MDNS_MEMORY_ALLOC_SPIRAM=y CONFIG_MULTIMODAL_DEVICE_ID="" CONFIG_MULTIMODAL_SERIAL_NUMBER="" +CONFIG_MULTIMODAL_WIFI_SSID="csy2" +CONFIG_MULTIMODAL_WIFI_PASSWORD="csy19881023" CONFIG_MULTIMODAL_DEVICE_TOKEN_URL="https://gz.api.talkingq.com/token/api/device/token" CONFIG_FORTUNE_ENABLE=y CONFIG_FORTUNE_API_URL="https://gz.api.talkingq.com/token/api/device/fortune/daily-wealth" @@ -172,25 +174,6 @@ CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y # CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y -# CONFIG_LV_BUILD_EXAMPLES is not set -# CONFIG_LV_USE_DEMO_WIDGETS is not set -# Route LVGL allocations through libc allocator so large buffers can live in PSRAM. -# CONFIG_LV_USE_BUILTIN_MALLOC is not set -CONFIG_LV_USE_CLIB_MALLOC=y -CONFIG_LV_USE_FS_STDIO=y -CONFIG_LV_FS_STDIO_LETTER=65 -CONFIG_LV_FS_STDIO_PATH="" -CONFIG_LV_FS_STDIO_CACHE_SIZE=0 -# Keep LVGL blend helpers in flash instead of reserving internal executable RAM. -# CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM is not set -CONFIG_LV_DEF_REFR_PERIOD=10 -# The default LVGL animation assets are compressed .bin files that require -# whole-image decode and the built-in LZ4 decoder before drawing. -CONFIG_LV_BIN_DECODER_RAM_LOAD=y -CONFIG_LV_USE_LZ4=y -CONFIG_LV_USE_LZ4_INTERNAL=y -# CONFIG_LV_USE_LZ4_EXTERNAL is not set - # Screen parameters aligned with validated reference project # CONFIG_TQ_SCREEN_COLOR_ORDER_BGR is not set CONFIG_TQ_SCREEN_INVERT_COLOR=y @@ -208,7 +191,6 @@ CONFIG_TQ_SCREEN_SPI_CS_GPIO=6 CONFIG_TQ_SCREEN_DC_GPIO=7 CONFIG_TQ_SCREEN_BACKLIGHT_GPIO=9 CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ=20000000 -CONFIG_TQ_SCREEN_LVGL_DRAW_BUF_LINES=40 CONFIG_TQ_SLOT_SWITCH_GPIO=45 CONFIG_TQ_SLOT_SWITCH_ACTIVE_LEVEL=0 CONFIG_TQ_SLOT_SWITCH_PULLUP=y