diff --git a/16m.csv b/16m.csv index f7ddaff..202fd1c 100644 --- a/16m.csv +++ b/16m.csv @@ -3,9 +3,9 @@ nvs, data, nvs, 0x9000, 0x4000, otadata, data, ota, 0xd000, 0x2000, phy_init, data, phy, 0xf000, 0x1000, -gifpack, data, littlefs, , 0x780000, -logfs, data, littlefs, , 0x80000, -ota_0, app, ota_0, , 0x330000, -ota_1, app, ota_1, , 0x330000, +gifpack, data, littlefs, , 0xa80000, +logfs, data, littlefs, , 0x40000, +ota_0, app, ota_0, , 0x1d0000, +ota_1, app, ota_1, , 0x1d0000, model, data, spiffs, , 0x180000, coredump, data, coredump,, 0x10000, diff --git a/components/control_plane/src/runtime/control_plane_system_events.c b/components/control_plane/src/runtime/control_plane_system_events.c index 8aceed4..773387e 100644 --- a/components/control_plane/src/runtime/control_plane_system_events.c +++ b/components/control_plane/src/runtime/control_plane_system_events.c @@ -134,6 +134,16 @@ static void control_plane_dispatch_key_event(const control_plane_key_event_t *ev ESP_LOGW(TAG, "SW2 single-click chouqian expression failed: %s", esp_err_to_name(expr_err)); } } + if (event->type == CONTROL_PLANE_KEY_EVENT_DOUBLE_CLICK) { + const domain_expression_request_t request = { + .id = DOMAIN_EXPRESSION_WUXING, + .trigger = DOMAIN_EXPRESSION_TRIGGER_SW2_DOUBLE_CLICK, + }; + esp_err_t expr_err = domain_runtime_request_expression(&request); + if (expr_err != ESP_OK && expr_err != ESP_ERR_INVALID_STATE) { + ESP_LOGW(TAG, "SW2 double-click wuxing expression failed: %s", esp_err_to_name(expr_err)); + } + } return; } diff --git a/components/domain/include/domain.h b/components/domain/include/domain.h index b640385..131a34e 100644 --- a/components/domain/include/domain.h +++ b/components/domain/include/domain.h @@ -89,6 +89,7 @@ typedef enum { DOMAIN_EXPRESSION_WEALTH_DIRECTION, DOMAIN_EXPRESSION_MUYU, DOMAIN_EXPRESSION_CHOUQIAN, + DOMAIN_EXPRESSION_WUXING, } domain_expression_id_t; typedef enum { @@ -102,6 +103,7 @@ typedef enum { DOMAIN_EXPRESSION_TRIGGER_DAILY_FORTUNE, DOMAIN_EXPRESSION_TRIGGER_PTT_DOUBLE_CLICK, DOMAIN_EXPRESSION_TRIGGER_SW2_SINGLE_CLICK, + DOMAIN_EXPRESSION_TRIGGER_SW2_DOUBLE_CLICK, } domain_expression_trigger_t; typedef struct { diff --git a/components/domain/src/runtime/domain_runtime_adapters.c b/components/domain/src/runtime/domain_runtime_adapters.c index 32ffdea..167d828 100644 --- a/components/domain/src/runtime/domain_runtime_adapters.c +++ b/components/domain/src/runtime/domain_runtime_adapters.c @@ -86,6 +86,21 @@ static const char *const s_chouqian_result_asset_dirs[] = { "chouqian_zhongshang", }; +typedef struct { + const char *asset_dir; + uint16_t icon_id; + uint16_t icon_x; + uint16_t icon_y; +} domain_wuxing_result_t; + +static const domain_wuxing_result_t s_wuxing_results[] = { + {.asset_dir = "shui", .icon_id = 0, .icon_x = 124, .icon_y = 8}, + {.asset_dir = "mu", .icon_id = 1, .icon_x = 217, .icon_y = 92}, + {.asset_dir = "huo", .icon_id = 2, .icon_x = 198, .icon_y = 171}, + {.asset_dir = "tu", .icon_id = 3, .icon_x = 46, .icon_y = 178}, + {.asset_dir = "jin", .icon_id = 4, .icon_x = 18, .icon_y = 102}, +}; + /* * Wealth-direction reveal asset directories, indexed parallel to the direction * catalog order in components/domain/src/direction/domain_direction_catalog.c @@ -104,6 +119,9 @@ static const char *const s_wealth_direction_asset_dirs[] = { #define DOMAIN_WEALTH_DIRECTION_FRAME_MS 100U #define DOMAIN_CHOUQIAN_FRAME_MS 500U +#define DOMAIN_WUXING_QIANZHI_FRAME_MS 100U +#define DOMAIN_WUXING_REVEAL_FRAME_MS 100U +#define DOMAIN_WUXING_HOLD_FRAME_MS 500U #define DOMAIN_AUTH_KEY_STORE_NAMESPACE "auth_key" #define DOMAIN_AUTH_STATE_STORE_NAMESPACE "auth" @@ -1262,31 +1280,37 @@ static esp_err_t domain_resolve_expression_platform_request( s_chouqian_steps[0] = (platform_lcd_presentation_step_t) { .asset_dir = "chouqian_qianzhi", .frame_index = 0, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, .delay_ms = DOMAIN_CHOUQIAN_FRAME_MS, }; s_chouqian_steps[1] = (platform_lcd_presentation_step_t) { .asset_dir = "chouqian_qianzhi", .frame_index = 1, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, .delay_ms = DOMAIN_CHOUQIAN_FRAME_MS, }; s_chouqian_steps[2] = (platform_lcd_presentation_step_t) { .asset_dir = "chouqian_qianzhi", .frame_index = 2, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, .delay_ms = DOMAIN_CHOUQIAN_FRAME_MS, }; s_chouqian_steps[3] = (platform_lcd_presentation_step_t) { .asset_dir = result_asset, .frame_index = 0, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, .delay_ms = DOMAIN_CHOUQIAN_FRAME_MS, }; s_chouqian_steps[4] = (platform_lcd_presentation_step_t) { .asset_dir = result_asset, .frame_index = 1, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, .delay_ms = DOMAIN_CHOUQIAN_FRAME_MS, }; s_chouqian_steps[5] = (platform_lcd_presentation_step_t) { .asset_dir = result_asset, .frame_index = 2, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, .delay_ms = DOMAIN_CHOUQIAN_FRAME_MS, }; @@ -1302,6 +1326,44 @@ static esp_err_t domain_resolve_expression_platform_request( return ESP_OK; } + if (request->id == DOMAIN_EXPRESSION_WUXING) { + static platform_lcd_presentation_step_t s_wuxing_steps[3]; + const size_t result_count = sizeof(s_wuxing_results) / sizeof(s_wuxing_results[0]); + const domain_wuxing_result_t *result = &s_wuxing_results[esp_random() % result_count]; + + s_wuxing_steps[0] = (platform_lcd_presentation_step_t) { + .asset_dir = "qianzhiwuxing", + .frame_index = 0, + .icon_id = result->icon_id, + .icon_x = result->icon_x, + .icon_y = result->icon_y, + .delay_ms = DOMAIN_WUXING_QIANZHI_FRAME_MS, + }; + s_wuxing_steps[1] = (platform_lcd_presentation_step_t) { + .asset_dir = result->asset_dir, + .frame_index = 0, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, + .delay_ms = DOMAIN_WUXING_REVEAL_FRAME_MS, + }; + s_wuxing_steps[2] = (platform_lcd_presentation_step_t) { + .asset_dir = result->asset_dir, + .frame_index = 1, + .icon_id = PLATFORM_LCD_PRESENTATION_NO_ICON, + .delay_ms = DOMAIN_WUXING_HOLD_FRAME_MS, + }; + + *out_platform_request = (platform_lcd_presentation_request_t) { + .kind = PLATFORM_LCD_PRESENTATION_STEP_SEQUENCE, + .priority = 125, + .interrupt_current = true, + .return_to_idle = true, + .steps = s_wuxing_steps, + .step_count = sizeof(s_wuxing_steps) / sizeof(s_wuxing_steps[0]), + }; + ESP_LOGI(TAG, "Wuxing result asset=%s", result->asset_dir); + return ESP_OK; + } + const domain_expression_catalog_entry_t *entry = domain_find_expression_entry(request->id); if (entry == NULL) { ESP_LOGW(TAG, "Unknown expression request id=%d trigger=%d", diff --git a/components/platform/CMakeLists.txt b/components/platform/CMakeLists.txt index b509946..1c362fe 100644 --- a/components/platform/CMakeLists.txt +++ b/components/platform/CMakeLists.txt @@ -91,6 +91,22 @@ set(platform_lcd_flash_assets chouqian_zhongshang0 chouqian_zhongshang1 chouqian_zhongshang2 + qianzhiwuxing0 + shui0 + shui1 + mu0 + mu1 + huo0 + huo1 + tu0 + tu1 + jin0 + jin1 + icon_shui0 + icon_mu0 + icon_huo0 + icon_tu0 + icon_jin0 ) set(platform_lcd_flash_asset_files) foreach(lcd_rle_asset IN LISTS platform_lcd_flash_assets) diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index 0b57cb9..2c6308c 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -351,6 +351,7 @@ esp_err_t platform_stop_lcd_render_loop(int timeout_ms); #define PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX 16 #define PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX 32 #define PLATFORM_LCD_PRESENTATION_STEP_MAX 12 +#define PLATFORM_LCD_PRESENTATION_NO_ICON 0xFFFFU typedef enum { PLATFORM_LCD_PRESENTATION_IDLE = 0, @@ -362,6 +363,9 @@ typedef enum { typedef struct { const char *asset_dir; uint8_t frame_index; + uint16_t icon_id; + uint16_t icon_x; + uint16_t icon_y; uint32_t delay_ms; } platform_lcd_presentation_step_t; diff --git a/components/platform/internal/lcd_direct_assets.h b/components/platform/internal/lcd_direct_assets.h index c59e812..7fcb8ee 100644 --- a/components/platform/internal/lcd_direct_assets.h +++ b/components/platform/internal/lcd_direct_assets.h @@ -21,6 +21,7 @@ typedef struct { typedef struct { const lcd_direct_rle_run_t *runs; uint32_t run_count; + const char *path; uint16_t width; uint16_t height; } lcd_direct_icon_t; diff --git a/components/platform/src/lcd_direct.c b/components/platform/src/lcd_direct.c index f921ff0..6ef9622 100644 --- a/components/platform/src/lcd_direct.c +++ b/components/platform/src/lcd_direct.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -272,6 +273,48 @@ static esp_err_t lcd_direct_draw_test_pattern(const lcd_direct_request_t *reques return ESP_OK; } +static lcd_direct_rle_run_t *lcd_direct_load_icon_runs(const lcd_direct_icon_t *icon) +{ + if (icon == NULL || icon->path == NULL || icon->run_count == 0U) { + return NULL; + } + + const size_t bytes_to_read = icon->run_count * sizeof(lcd_direct_rle_run_t); + lcd_direct_rle_run_t *runs = (lcd_direct_rle_run_t *)malloc(bytes_to_read); + if (runs == NULL) { + ESP_LOGW(TAG, "allocate LCD icon failed path=%s bytes=%u", icon->path, (unsigned)bytes_to_read); + return NULL; + } + + errno = 0; + int fd = open(icon->path, O_RDONLY); + if (fd < 0) { + ESP_LOGW(TAG, "open LCD icon failed path=%s errno=%d", icon->path, errno); + free(runs); + return NULL; + } + + size_t offset = 0; + while (offset < bytes_to_read) { + ssize_t bytes_read = read(fd, ((uint8_t *)runs) + offset, bytes_to_read - offset); + if (bytes_read <= 0) { + ESP_LOGW(TAG, + "read LCD icon failed path=%s requested=%u read=%d errno=%d", + icon->path, + (unsigned)bytes_to_read, + (int)bytes_read, + errno); + close(fd); + free(runs); + return NULL; + } + offset += (size_t)bytes_read; + } + + close(fd); + return runs; +} + static void lcd_direct_overlay_icons(uint16_t *pixel_buffer, uint16_t row_start, uint16_t row_count, @@ -286,13 +329,23 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer, 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) { + if (icon == NULL || icon->width == 0U || icon->height == 0U) { continue; } if (draw->x >= LCD_DIRECT_WIDTH || draw->y >= LCD_DIRECT_HEIGHT) { continue; } + lcd_direct_rle_run_t *loaded_runs = NULL; + const lcd_direct_rle_run_t *runs = icon->runs; + if (runs == NULL) { + loaded_runs = lcd_direct_load_icon_runs(icon); + runs = loaded_runs; + } + if (runs == NULL) { + continue; + } + uint16_t draw_width = icon->width; uint16_t draw_height = icon->height; if (draw_width > LCD_DIRECT_WIDTH - draw->x) { @@ -305,6 +358,7 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer, 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) { + free(loaded_runs); continue; } @@ -321,11 +375,11 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer, 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; + const uint32_t run_end = run_start + 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); + const uint16_t color = lcd_direct_swap_color(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; } @@ -333,6 +387,8 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer, icon_pixel_pos = run_end; } } + + free(loaded_runs); } } @@ -671,7 +727,20 @@ static esp_err_t lcd_direct_play_step_sequence(const lcd_direct_request_t *reque step->asset_dir != NULL ? step->asset_dir : "", (unsigned)step->frame_index); - ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0), + lcd_direct_icon_draw_t icon = {0}; + const lcd_direct_icon_draw_t *icons = NULL; + size_t icon_count = 0; + if (step->icon_id != PLATFORM_LCD_PRESENTATION_NO_ICON) { + icon = (lcd_direct_icon_draw_t) { + .icon_id = step->icon_id, + .x = step->icon_x, + .y = step->icon_y, + }; + icons = &icon; + icon_count = 1; + } + + ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, icons, icon_count), TAG, "draw step frame failed"); diff --git a/components/platform/src/lcd_direct_assets.c b/components/platform/src/lcd_direct_assets.c index dc841de..aad2f55 100644 --- a/components/platform/src/lcd_direct_assets.c +++ b/components/platform/src/lcd_direct_assets.c @@ -129,6 +129,101 @@ static const lcd_direct_image_t s_chouqian_zhongshang_frames[] = { }, }; +static const lcd_direct_image_t s_qianzhiwuxing_frames[] = { + { + .path = LCD_ASSET_BASE_PATH "qianzhiwuxing0.rle", + .run_count = 4157U, + }, +}; + +static const lcd_direct_image_t s_shui_frames[] = { + { + .path = LCD_ASSET_BASE_PATH "shui0.rle", + .run_count = 74146U, + }, + { + .path = LCD_ASSET_BASE_PATH "shui1.rle", + .run_count = 71868U, + }, +}; + +static const lcd_direct_image_t s_mu_frames[] = { + { + .path = LCD_ASSET_BASE_PATH "mu0.rle", + .run_count = 73537U, + }, + { + .path = LCD_ASSET_BASE_PATH "mu1.rle", + .run_count = 69337U, + }, +}; + +static const lcd_direct_image_t s_huo_frames[] = { + { + .path = LCD_ASSET_BASE_PATH "huo0.rle", + .run_count = 74455U, + }, + { + .path = LCD_ASSET_BASE_PATH "huo1.rle", + .run_count = 72474U, + }, +}; + +static const lcd_direct_image_t s_tu_frames[] = { + { + .path = LCD_ASSET_BASE_PATH "tu0.rle", + .run_count = 95829U, + }, + { + .path = LCD_ASSET_BASE_PATH "tu1.rle", + .run_count = 71964U, + }, +}; + +static const lcd_direct_image_t s_jin_frames[] = { + { + .path = LCD_ASSET_BASE_PATH "jin0.rle", + .run_count = 75151U, + }, + { + .path = LCD_ASSET_BASE_PATH "jin1.rle", + .run_count = 72012U, + }, +}; + +static const lcd_direct_icon_t s_wuxing_icons[] = { + { + .path = LCD_ASSET_BASE_PATH "icon_shui0.rle", + .run_count = 549U, + .width = 60U, + .height = 56U, + }, + { + .path = LCD_ASSET_BASE_PATH "icon_mu0.rle", + .run_count = 1005U, + .width = 86U, + .height = 50U, + }, + { + .path = LCD_ASSET_BASE_PATH "icon_huo0.rle", + .run_count = 565U, + .width = 56U, + .height = 58U, + }, + { + .path = LCD_ASSET_BASE_PATH "icon_tu0.rle", + .run_count = 754U, + .width = 75U, + .height = 42U, + }, + { + .path = LCD_ASSET_BASE_PATH "icon_jin0.rle", + .run_count = 605U, + .width = 65U, + .height = 44U, + }, +}; + static const lcd_direct_image_t *lcd_direct_frame_from_array(const lcd_direct_image_t *frames, size_t frame_count, uint8_t frame_index) @@ -198,13 +293,51 @@ const lcd_direct_image_t *lcd_direct_asset_frame(const char *asset_dir, uint8_t frame_index); } + if (strcmp(asset_dir, "qianzhiwuxing") == 0) { + return lcd_direct_frame_from_array(s_qianzhiwuxing_frames, + sizeof(s_qianzhiwuxing_frames) / sizeof(s_qianzhiwuxing_frames[0]), + frame_index); + } + + if (strcmp(asset_dir, "shui") == 0) { + return lcd_direct_frame_from_array(s_shui_frames, + sizeof(s_shui_frames) / sizeof(s_shui_frames[0]), + frame_index); + } + + if (strcmp(asset_dir, "mu") == 0) { + return lcd_direct_frame_from_array(s_mu_frames, + sizeof(s_mu_frames) / sizeof(s_mu_frames[0]), + frame_index); + } + + if (strcmp(asset_dir, "huo") == 0) { + return lcd_direct_frame_from_array(s_huo_frames, + sizeof(s_huo_frames) / sizeof(s_huo_frames[0]), + frame_index); + } + + if (strcmp(asset_dir, "tu") == 0) { + return lcd_direct_frame_from_array(s_tu_frames, + sizeof(s_tu_frames) / sizeof(s_tu_frames[0]), + frame_index); + } + + if (strcmp(asset_dir, "jin") == 0) { + return lcd_direct_frame_from_array(s_jin_frames, + sizeof(s_jin_frames) / sizeof(s_jin_frames[0]), + frame_index); + } + return NULL; } const lcd_direct_icon_t *lcd_direct_icon(uint16_t icon_id) { - (void)icon_id; - return NULL; + if (icon_id >= (sizeof(s_wuxing_icons) / sizeof(s_wuxing_icons[0]))) { + return NULL; + } + return &s_wuxing_icons[icon_id]; } const lcd_direct_icon_draw_t *lcd_direct_default_icon_sequence(size_t *out_count)