Files
P4-USB-transfer/main/LVGL/lvgl_init.c
T
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <[email protected]>
2026-10-13 21:54:50 +08:00

1889 lines
88 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*****************************************************include files **************************************************************************/
#include "config.h"
#include "ui.h"
#include "lvgl.h"
#include "FLASH/flash.h"
#include "driver/rtc_io.h"
#include "esp_heap_caps.h"
#include <inttypes.h>
#include <errno.h>
#include <string.h>
#include "LVGL/lvgl_src.h"
#include "LVGL/lvgl_init.h"
#include "remapping.h"
#include <stdio.h>
#include "EMOJI_STORAGE/emoji_storage.h"
#include <sys/stat.h>
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "driver/periph_ctrl.h"
#include "driver/gpio.h"
#include "BLE/ble_send.h"
#include "BLE/ble_data_ansy.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "events_init.h"
#include "ui_cmd.h"
#include "UI_DISPATCH/ui_dispatch.h"
#include "custom.h"
#include "font_runtime.h"
#include "main.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "MOTOR/motor.h"
#include "SCREEN/screen_init.h"
#include "SPEECH/speech_app.h"
#include "lvgl_lottie.h"
#include "MOTOR/motor_control.h"
/******************************************************* variable declaration ************************************************************************/
lv_obj_t *new_lottie = NULL;
const unsigned char lottie_data[] = { //测试显示屏的数组类型动画
0x7b, 0x22, 0x76, 0x22, 0x3a, 0x22, 0x35, 0x2e, 0x36, 0x2e, 0x31, 0x30, 0x22, 0x2c, 0x22, 0x66,
0x72, 0x22, 0x3a, 0x33, 0x30, 0x2c, 0x22, 0x69, 0x70, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6f, 0x70,
0x22, 0x3a, 0x39, 0x30, 0x2c, 0x22, 0x77, 0x22, 0x3a, 0x35, 0x30, 0x32, 0x2c, 0x22, 0x68, 0x22,
0x3a, 0x34, 0x31, 0x30, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x64, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x22, 0x2c, 0x22, 0x64, 0x64, 0x64, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x61, 0x73, 0x73,
0x65, 0x74, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22,
0x3a, 0x5b, 0x7b, 0x22, 0x64, 0x64, 0x64, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x6e, 0x64, 0x22,
0x3a, 0x31, 0x2c, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x34, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22,
0x22, 0x2c, 0x22, 0x73, 0x72, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x6b, 0x73, 0x22, 0x3a, 0x7b, 0x22,
0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x31, 0x30,
0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x31, 0x7d, 0x2c, 0x22, 0x72, 0x22, 0x3a, 0x7b,
0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22,
0x3a, 0x31, 0x30, 0x7d, 0x2c, 0x22, 0x70, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31, 0x2c,
0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30,
0x2e, 0x36, 0x36, 0x37, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a,
0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30,
0x7d, 0x2c, 0x22, 0x74, 0x22, 0x3a, 0x34, 0x31, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x31, 0x38,
0x36, 0x2e, 0x32, 0x30, 0x31, 0x2c, 0x32, 0x32, 0x33, 0x2e, 0x36, 0x38, 0x39, 0x2c, 0x30, 0x5d,
0x2c, 0x22, 0x74, 0x6f, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x31, 0x2e, 0x36, 0x36, 0x37, 0x2c, 0x30,
0x5d, 0x2c, 0x22, 0x74, 0x69, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x7d, 0x2c,
0x7b, 0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x36, 0x36, 0x37, 0x2c,
0x22, 0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a,
0x30, 0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22, 0x74, 0x22,
0x3a, 0x34, 0x34, 0x2e, 0x32, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x31, 0x38, 0x36, 0x2e, 0x32,
0x30, 0x31, 0x2c, 0x32, 0x33, 0x33, 0x2e, 0x36, 0x38, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x74,
0x6f, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x74, 0x69, 0x22, 0x3a,
0x5b, 0x30, 0x2c, 0x31, 0x2e, 0x36, 0x36, 0x37, 0x2c, 0x30, 0x5d, 0x7d, 0x2c, 0x7b, 0x22, 0x74,
0x22, 0x3a, 0x34, 0x37, 0x2e, 0x34, 0x30, 0x30, 0x33, 0x39, 0x30, 0x36, 0x32, 0x35, 0x2c, 0x22,
0x73, 0x22, 0x3a, 0x5b, 0x31, 0x38, 0x36, 0x2e, 0x32, 0x30, 0x31, 0x2c, 0x32, 0x32, 0x33, 0x2e,
0x36, 0x38, 0x39, 0x2c, 0x30, 0x5d, 0x7d, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x32, 0x7d,
0x2c, 0x22, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a,
0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x7d, 0x2c,
0x22, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b,
0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78,
0x22, 0x3a, 0x36, 0x7d, 0x7d, 0x2c, 0x22, 0x61, 0x6f, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 0x68,
0x61, 0x70, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x67, 0x72,
0x22, 0x2c, 0x22, 0x69, 0x74, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x6e, 0x64, 0x22, 0x3a, 0x30,
0x2c, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x73, 0x68, 0x22, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a,
0x31, 0x2c, 0x22, 0x6b, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x6b,
0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x36,
0x36, 0x37, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22,
0x78, 0x22, 0x3a, 0x30, 0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30, 0x7d, 0x2c,
0x22, 0x74, 0x22, 0x3a, 0x34, 0x31, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22,
0x3a, 0x5b, 0x5b, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30,
0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30,
0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x2e, 0x35, 0x31, 0x37, 0x2c, 0x2d, 0x30, 0x2e, 0x34, 0x39, 0x31,
0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x34, 0x2e, 0x38, 0x38,
0x35, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b, 0x5b,
0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x35, 0x2e, 0x35, 0x33, 0x39, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x34, 0x2e, 0x38, 0x36, 0x36, 0x2c, 0x30, 0x2e,
0x34, 0x33, 0x33, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e,
0x32, 0x30, 0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x34, 0x32, 0x2e, 0x37,
0x39, 0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x32, 0x2e, 0x37,
0x39, 0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32,
0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e,
0x32, 0x32, 0x32, 0x2c, 0x2d, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x38, 0x5d, 0x2c, 0x5b, 0x2d, 0x34,
0x31, 0x2e, 0x39, 0x35, 0x34, 0x2c, 0x2d, 0x33, 0x37, 0x2e, 0x39, 0x39, 0x33, 0x5d, 0x2c, 0x5b,
0x34, 0x33, 0x2e, 0x36, 0x32, 0x36, 0x2c, 0x2d, 0x33, 0x30, 0x2e, 0x33, 0x37, 0x39, 0x5d, 0x2c,
0x5b, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x2d, 0x32, 0x30, 0x2e, 0x39, 0x38, 0x34, 0x5d,
0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39, 0x5d,
0x5d, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x5d, 0x7d, 0x2c, 0x7b, 0x22,
0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x36, 0x36, 0x37, 0x2c, 0x22, 0x79,
0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e,
0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22, 0x74, 0x22, 0x3a, 0x34,
0x34, 0x2e, 0x32, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x5b, 0x5b,
0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b,
0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b,
0x2d, 0x35, 0x2e, 0x35, 0x31, 0x37, 0x2c, 0x2d, 0x30, 0x2e, 0x34, 0x39, 0x31, 0x5d, 0x2c, 0x5b,
0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x34, 0x2e, 0x38, 0x38, 0x35, 0x5d, 0x2c,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b, 0x5b, 0x30, 0x2c, 0x30,
0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c,
0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x35, 0x2e, 0x35, 0x33, 0x39, 0x5d, 0x2c, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x34, 0x2e, 0x38, 0x36, 0x36, 0x2c, 0x30, 0x2e, 0x34, 0x33, 0x33,
0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39,
0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x34, 0x32, 0x2e, 0x37, 0x31, 0x34, 0x2c,
0x31, 0x35, 0x2e, 0x35, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x32, 0x2e, 0x38, 0x36, 0x36,
0x2c, 0x31, 0x35, 0x2e, 0x35, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32, 0x39,
0x39, 0x2c, 0x36, 0x2e, 0x30, 0x39, 0x39, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x30, 0x35,
0x31, 0x2c, 0x36, 0x2e, 0x34, 0x30, 0x32, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x31, 0x2e, 0x37, 0x38,
0x33, 0x2c, 0x2d, 0x32, 0x2e, 0x39, 0x39, 0x33, 0x5d, 0x2c, 0x5b, 0x34, 0x33, 0x2e, 0x35, 0x35,
0x2c, 0x2d, 0x32, 0x2e, 0x38, 0x37, 0x39, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x31, 0x34, 0x36,
0x2c, 0x36, 0x2e, 0x35, 0x31, 0x36, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x31, 0x34, 0x36, 0x2c,
0x36, 0x2e, 0x30, 0x39, 0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65,
0x7d, 0x5d, 0x7d, 0x2c, 0x7b, 0x22, 0x74, 0x22, 0x3a, 0x34, 0x37, 0x2e, 0x34, 0x30, 0x30, 0x33,
0x39, 0x30, 0x36, 0x32, 0x35, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a,
0x5b, 0x5b, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d,
0x2c, 0x5b, 0x2d, 0x35, 0x2e, 0x35, 0x31, 0x37, 0x2c, 0x2d, 0x30, 0x2e, 0x34, 0x39, 0x31, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x34, 0x2e, 0x38, 0x38, 0x35,
0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b,
0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x35, 0x2e, 0x35, 0x33, 0x39, 0x5d, 0x2c,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x34, 0x2e, 0x38, 0x36, 0x36, 0x2c, 0x30, 0x2e, 0x34,
0x33, 0x33, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32,
0x30, 0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x34, 0x32, 0x2e, 0x37, 0x39,
0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x32, 0x2e, 0x37, 0x39,
0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32, 0x32,
0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32,
0x32, 0x32, 0x2c, 0x2d, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x38, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x31,
0x2e, 0x39, 0x35, 0x34, 0x2c, 0x2d, 0x33, 0x37, 0x2e, 0x39, 0x39, 0x33, 0x5d, 0x2c, 0x5b, 0x34,
0x33, 0x2e, 0x36, 0x32, 0x36, 0x2c, 0x2d, 0x33, 0x30, 0x2e, 0x33, 0x37, 0x39, 0x5d, 0x2c, 0x5b,
0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x2d, 0x32, 0x30, 0x2e, 0x39, 0x38, 0x34, 0x5d, 0x2c,
0x5b, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39, 0x5d, 0x5d,
0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x5d, 0x7d, 0x5d, 0x2c, 0x22, 0x69,
0x78, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x20, 0x31, 0x22, 0x2c,
0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63, 0x74, 0x6f,
0x72, 0x20, 0x53, 0x68, 0x61, 0x70, 0x65, 0x20, 0x2d, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22,
0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x7d, 0x2c, 0x7b, 0x22, 0x74,
0x79, 0x22, 0x3a, 0x22, 0x66, 0x6c, 0x22, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22,
0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2e, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x35, 0x39, 0x36, 0x2c, 0x31, 0x2c, 0x30, 0x2e, 0x37, 0x31, 0x33, 0x37, 0x32, 0x35,
0x35, 0x30, 0x37, 0x32, 0x35, 0x39, 0x2c, 0x31, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x34,
0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22,
0x3a, 0x31, 0x30, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x35, 0x7d, 0x2c, 0x22, 0x72, 0x22,
0x3a, 0x31, 0x2c, 0x22, 0x62, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22,
0x20, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56,
0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x2d, 0x20,
0x46, 0x69, 0x6c, 0x6c, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65,
0x7d, 0x2c, 0x7b, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x74, 0x72, 0x22, 0x2c, 0x22, 0x70, 0x22,
0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30,
0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x61, 0x22, 0x3a, 0x7b, 0x22,
0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22,
0x69, 0x78, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x5d, 0x2c,
0x22, 0x69, 0x78, 0x22, 0x3a, 0x33, 0x7d, 0x2c, 0x22, 0x72, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22,
0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x36, 0x7d,
0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a,
0x31, 0x30, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x37, 0x7d, 0x2c, 0x22, 0x73, 0x6b, 0x22,
0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69,
0x78, 0x22, 0x3a, 0x34, 0x7d, 0x2c, 0x22, 0x73, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x35, 0x7d, 0x2c,
0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22,
0x22, 0x2c, 0x22, 0x6e, 0x70, 0x22, 0x3a, 0x32, 0x2c, 0x22, 0x63, 0x69, 0x78, 0x22, 0x3a, 0x32,
0x2c, 0x22, 0x62, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x2c, 0x22,
0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72,
0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c,
0x73, 0x65, 0x7d, 0x5d, 0x2c, 0x22, 0x69, 0x70, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6f, 0x70, 0x22,
0x3a, 0x39, 0x30, 0x2c, 0x22, 0x73, 0x74, 0x22, 0x3a, 0x34, 0x2c, 0x22, 0x62, 0x6d, 0x22, 0x3a,
0x30, 0x7d, 0x2c, 0x7b, 0x22, 0x64, 0x64, 0x64, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x6e, 0x64,
0x22, 0x3a, 0x32, 0x2c, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x34, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a,
0x22, 0x22, 0x2c, 0x22, 0x73, 0x72, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x6b, 0x73, 0x22, 0x3a, 0x7b,
0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x31,
0x30, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x31, 0x7d, 0x2c, 0x22, 0x72, 0x22, 0x3a,
0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78,
0x22, 0x3a, 0x31, 0x30, 0x7d, 0x2c, 0x22, 0x70, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31,
0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a,
0x30, 0x2e, 0x36, 0x36, 0x37, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22,
0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a,
0x30, 0x7d, 0x2c, 0x22, 0x74, 0x22, 0x3a, 0x34, 0x31, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x33,
0x31, 0x36, 0x2e, 0x35, 0x31, 0x39, 0x2c, 0x32, 0x32, 0x33, 0x2e, 0x36, 0x38, 0x39, 0x2c, 0x30,
0x5d, 0x2c, 0x22, 0x74, 0x6f, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x31, 0x2e, 0x36, 0x36, 0x37, 0x2c,
0x30, 0x5d, 0x2c, 0x22, 0x74, 0x69, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x7d,
0x2c, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x36, 0x36, 0x37,
0x2c, 0x22, 0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22,
0x3a, 0x30, 0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22, 0x74,
0x22, 0x3a, 0x34, 0x34, 0x2e, 0x32, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x33, 0x31, 0x36, 0x2e,
0x35, 0x31, 0x39, 0x2c, 0x32, 0x33, 0x33, 0x2e, 0x36, 0x38, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x22,
0x74, 0x6f, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x74, 0x69, 0x22,
0x3a, 0x5b, 0x30, 0x2c, 0x31, 0x2e, 0x36, 0x36, 0x37, 0x2c, 0x30, 0x5d, 0x7d, 0x2c, 0x7b, 0x22,
0x74, 0x22, 0x3a, 0x34, 0x37, 0x2e, 0x34, 0x30, 0x30, 0x33, 0x39, 0x30, 0x36, 0x32, 0x35, 0x2c,
0x22, 0x73, 0x22, 0x3a, 0x5b, 0x33, 0x31, 0x36, 0x2e, 0x35, 0x31, 0x39, 0x2c, 0x32, 0x32, 0x33,
0x2e, 0x36, 0x38, 0x39, 0x2c, 0x30, 0x5d, 0x7d, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x32,
0x7d, 0x2c, 0x22, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22,
0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x7d,
0x2c, 0x22, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a,
0x5b, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x5d, 0x2c, 0x22, 0x69,
0x78, 0x22, 0x3a, 0x36, 0x7d, 0x7d, 0x2c, 0x22, 0x61, 0x6f, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73,
0x68, 0x61, 0x70, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x67,
0x72, 0x22, 0x2c, 0x22, 0x69, 0x74, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x6e, 0x64, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x73, 0x68, 0x22, 0x2c, 0x22, 0x69, 0x78, 0x22,
0x3a, 0x31, 0x2c, 0x22, 0x6b, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31, 0x2c, 0x22,
0x6b, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e,
0x36, 0x36, 0x37, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b,
0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30, 0x7d,
0x2c, 0x22, 0x74, 0x22, 0x3a, 0x34, 0x31, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69,
0x22, 0x3a, 0x5b, 0x5b, 0x2d, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x5d, 0x2c, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x35, 0x2e, 0x35, 0x31, 0x37, 0x2c, 0x2d, 0x30, 0x2e, 0x34, 0x39,
0x31, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x34, 0x2e, 0x38,
0x38, 0x35, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x35, 0x2e, 0x35, 0x33, 0x39, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x2e, 0x38, 0x36, 0x36, 0x2c, 0x30,
0x2e, 0x34, 0x33, 0x33, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35,
0x2e, 0x32, 0x30, 0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x2d, 0x34, 0x32,
0x2e, 0x37, 0x39, 0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x34, 0x32, 0x2e,
0x37, 0x39, 0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x32,
0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x32,
0x32, 0x32, 0x2c, 0x2d, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x38, 0x5d, 0x2c, 0x5b, 0x34, 0x31, 0x2e,
0x39, 0x35, 0x34, 0x2c, 0x2d, 0x33, 0x37, 0x2e, 0x39, 0x39, 0x33, 0x5d, 0x2c, 0x5b, 0x2d, 0x34,
0x33, 0x2e, 0x36, 0x32, 0x36, 0x2c, 0x2d, 0x33, 0x30, 0x2e, 0x33, 0x37, 0x39, 0x5d, 0x2c, 0x5b,
0x2d, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x2d, 0x32, 0x30, 0x2e, 0x39, 0x38, 0x34, 0x5d,
0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39,
0x5d, 0x5d, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x5d, 0x7d, 0x2c, 0x7b,
0x22, 0x69, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30, 0x2e, 0x36, 0x36, 0x37, 0x2c, 0x22,
0x79, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x78, 0x22, 0x3a, 0x30,
0x2e, 0x33, 0x33, 0x33, 0x2c, 0x22, 0x79, 0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x22, 0x74, 0x22, 0x3a,
0x34, 0x34, 0x2e, 0x32, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x5b,
0x5b, 0x2d, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d,
0x2c, 0x5b, 0x35, 0x2e, 0x35, 0x31, 0x37, 0x2c, 0x2d, 0x30, 0x2e, 0x34, 0x39, 0x31, 0x5d, 0x2c,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x34, 0x2e, 0x38, 0x38, 0x35, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b, 0x5b, 0x30, 0x2c,
0x30, 0x5d, 0x2c, 0x5b, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c,
0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x35, 0x2e, 0x35, 0x33, 0x39, 0x5d, 0x2c, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x2e, 0x38, 0x36, 0x36, 0x2c, 0x30, 0x2e, 0x34, 0x33,
0x33, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30,
0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x2d, 0x34, 0x32, 0x2e, 0x35, 0x34,
0x39, 0x2c, 0x31, 0x35, 0x2e, 0x35, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x34, 0x33, 0x2e, 0x30, 0x33,
0x31, 0x2c, 0x31, 0x35, 0x2e, 0x35, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x34, 0x36,
0x33, 0x2c, 0x36, 0x2e, 0x30, 0x39, 0x39, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x34, 0x34, 0x35,
0x2c, 0x35, 0x2e, 0x34, 0x30, 0x32, 0x5d, 0x2c, 0x5b, 0x34, 0x32, 0x2e, 0x31, 0x37, 0x37, 0x2c,
0x2d, 0x33, 0x2e, 0x39, 0x39, 0x33, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x33, 0x2e, 0x36, 0x34, 0x34,
0x2c, 0x2d, 0x34, 0x2e, 0x33, 0x37, 0x39, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32, 0x34,
0x31, 0x2c, 0x35, 0x2e, 0x30, 0x31, 0x36, 0x5d, 0x2c, 0x5b, 0x2d, 0x35, 0x31, 0x2e, 0x39, 0x38,
0x31, 0x2c, 0x36, 0x2e, 0x30, 0x39, 0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72,
0x75, 0x65, 0x7d, 0x5d, 0x7d, 0x2c, 0x7b, 0x22, 0x74, 0x22, 0x3a, 0x34, 0x37, 0x2e, 0x34, 0x30,
0x30, 0x33, 0x39, 0x30, 0x36, 0x32, 0x35, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69,
0x22, 0x3a, 0x5b, 0x5b, 0x2d, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x5d, 0x2c, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x35, 0x2e, 0x35, 0x31, 0x37, 0x2c, 0x2d, 0x30, 0x2e, 0x34, 0x39,
0x31, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x34, 0x2e, 0x38,
0x38, 0x35, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x35, 0x2e, 0x32, 0x30, 0x39, 0x2c, 0x30, 0x5d, 0x2c,
0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x2d, 0x35, 0x2e, 0x35, 0x33, 0x39, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x2d, 0x34, 0x2e, 0x38, 0x36, 0x36, 0x2c, 0x30,
0x2e, 0x34, 0x33, 0x33, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x35,
0x2e, 0x32, 0x30, 0x39, 0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x2d, 0x34, 0x32,
0x2e, 0x37, 0x39, 0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x34, 0x32, 0x2e,
0x37, 0x39, 0x2c, 0x33, 0x38, 0x2e, 0x30, 0x33, 0x31, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x32,
0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39, 0x5d, 0x2c, 0x5b, 0x35, 0x32, 0x2e, 0x32,
0x32, 0x32, 0x2c, 0x2d, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x38, 0x5d, 0x2c, 0x5b, 0x34, 0x31, 0x2e,
0x39, 0x35, 0x34, 0x2c, 0x2d, 0x33, 0x37, 0x2e, 0x39, 0x39, 0x33, 0x5d, 0x2c, 0x5b, 0x2d, 0x34,
0x33, 0x2e, 0x36, 0x32, 0x36, 0x2c, 0x2d, 0x33, 0x30, 0x2e, 0x33, 0x37, 0x39, 0x5d, 0x2c, 0x5b,
0x2d, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x2d, 0x32, 0x30, 0x2e, 0x39, 0x38, 0x34, 0x5d,
0x2c, 0x5b, 0x2d, 0x35, 0x32, 0x2e, 0x32, 0x32, 0x32, 0x2c, 0x32, 0x38, 0x2e, 0x35, 0x39, 0x39,
0x5d, 0x5d, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x5d, 0x7d, 0x5d, 0x2c,
0x22, 0x69, 0x78, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x20, 0x31,
0x22, 0x2c, 0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63,
0x74, 0x6f, 0x72, 0x20, 0x53, 0x68, 0x61, 0x70, 0x65, 0x20, 0x2d, 0x20, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x7d, 0x2c, 0x7b,
0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x66, 0x6c, 0x22, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x7b, 0x22,
0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2e, 0x34, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x35, 0x39, 0x36, 0x2c, 0x31, 0x2c, 0x30, 0x2e, 0x37, 0x31, 0x33, 0x37,
0x32, 0x35, 0x35, 0x30, 0x37, 0x32, 0x35, 0x39, 0x2c, 0x31, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22,
0x3a, 0x34, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22,
0x6b, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x35, 0x7d, 0x2c, 0x22,
0x72, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x62, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6e, 0x6d, 0x22,
0x3a, 0x22, 0x20, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45,
0x20, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20,
0x2d, 0x20, 0x46, 0x69, 0x6c, 0x6c, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c,
0x73, 0x65, 0x7d, 0x2c, 0x7b, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x74, 0x72, 0x22, 0x2c, 0x22,
0x70, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30,
0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x61, 0x22, 0x3a,
0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x5d,
0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61,
0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30,
0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x33, 0x7d, 0x2c, 0x22, 0x72, 0x22, 0x3a, 0x7b, 0x22,
0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a,
0x36, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b,
0x22, 0x3a, 0x31, 0x30, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x37, 0x7d, 0x2c, 0x22, 0x73,
0x6b, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c,
0x22, 0x69, 0x78, 0x22, 0x3a, 0x34, 0x7d, 0x2c, 0x22, 0x73, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61,
0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x35,
0x7d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x22, 0x7d, 0x5d, 0x2c, 0x22, 0x6e, 0x6d, 0x22,
0x3a, 0x22, 0x22, 0x2c, 0x22, 0x6e, 0x70, 0x22, 0x3a, 0x32, 0x2c, 0x22, 0x63, 0x69, 0x78, 0x22,
0x3a, 0x32, 0x2c, 0x22, 0x62, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31,
0x2c, 0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63, 0x74,
0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66,
0x61, 0x6c, 0x73, 0x65, 0x7d, 0x5d, 0x2c, 0x22, 0x69, 0x70, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6f,
0x70, 0x22, 0x3a, 0x39, 0x30, 0x2c, 0x22, 0x73, 0x74, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x62, 0x6d,
0x22, 0x3a, 0x30, 0x7d, 0x2c, 0x7b, 0x22, 0x64, 0x64, 0x64, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69,
0x6e, 0x64, 0x22, 0x3a, 0x33, 0x2c, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x34, 0x2c, 0x22, 0x6e, 0x6d,
0x22, 0x3a, 0x22, 0x64, 0x6f, 0x74, 0x22, 0x2c, 0x22, 0x73, 0x72, 0x22, 0x3a, 0x31, 0x2c, 0x22,
0x6b, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c,
0x22, 0x6b, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x31, 0x7d,
0x2c, 0x22, 0x72, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x30, 0x7d, 0x2c, 0x22, 0x70, 0x22, 0x3a, 0x7b,
0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x32, 0x35, 0x30, 0x2e, 0x39,
0x37, 0x37, 0x2c, 0x32, 0x30, 0x35, 0x2e, 0x33, 0x39, 0x33, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69,
0x78, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30,
0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78,
0x22, 0x3a, 0x31, 0x7d, 0x2c, 0x22, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c,
0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30,
0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x36, 0x7d, 0x7d, 0x2c, 0x22, 0x61, 0x6f, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x73, 0x68, 0x61, 0x70, 0x65, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x74, 0x79,
0x22, 0x3a, 0x22, 0x67, 0x72, 0x22, 0x2c, 0x22, 0x69, 0x74, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x69,
0x6e, 0x64, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x73, 0x68, 0x22, 0x2c,
0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x6b, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22,
0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x7b, 0x22, 0x69, 0x22, 0x3a, 0x5b, 0x5b, 0x30, 0x2c,
0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b,
0x30, 0x2c, 0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x5b, 0x5b, 0x30, 0x2c, 0x30, 0x5d,
0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x5b, 0x30, 0x2c,
0x30, 0x5d, 0x5d, 0x2c, 0x22, 0x76, 0x22, 0x3a, 0x5b, 0x5b, 0x32, 0x35, 0x31, 0x2c, 0x32, 0x30,
0x35, 0x5d, 0x2c, 0x5b, 0x2d, 0x32, 0x35, 0x31, 0x2c, 0x32, 0x30, 0x35, 0x5d, 0x2c, 0x5b, 0x2d,
0x32, 0x35, 0x31, 0x2c, 0x2d, 0x32, 0x30, 0x35, 0x5d, 0x2c, 0x5b, 0x32, 0x35, 0x31, 0x2c, 0x2d,
0x32, 0x30, 0x35, 0x5d, 0x5d, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x2c,
0x22, 0x69, 0x78, 0x22, 0x3a, 0x32, 0x7d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x20, 0x31,
0x22, 0x2c, 0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63,
0x74, 0x6f, 0x72, 0x20, 0x53, 0x68, 0x61, 0x70, 0x65, 0x20, 0x2d, 0x20, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x7d, 0x2c, 0x7b,
0x22, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x66, 0x6c, 0x22, 0x2c, 0x22, 0x63, 0x22, 0x3a, 0x7b, 0x22,
0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x2c, 0x30, 0x2c,
0x31, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x34, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b,
0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x2c, 0x22, 0x69,
0x78, 0x22, 0x3a, 0x35, 0x7d, 0x2c, 0x22, 0x72, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x62, 0x6d, 0x22,
0x3a, 0x30, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x20, 0x31, 0x22, 0x2c, 0x22, 0x6d, 0x6e,
0x22, 0x3a, 0x22, 0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x47,
0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x2d, 0x20, 0x46, 0x69, 0x6c, 0x6c, 0x22, 0x2c, 0x22,
0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x7d, 0x2c, 0x7b, 0x22, 0x74, 0x79, 0x22,
0x3a, 0x22, 0x74, 0x72, 0x22, 0x2c, 0x22, 0x70, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30,
0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a,
0x32, 0x7d, 0x2c, 0x22, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b,
0x22, 0x3a, 0x5b, 0x30, 0x2c, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x7d, 0x2c,
0x22, 0x73, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x5b,
0x31, 0x30, 0x30, 0x2c, 0x31, 0x30, 0x30, 0x5d, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x33, 0x7d,
0x2c, 0x22, 0x72, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a,
0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x36, 0x7d, 0x2c, 0x22, 0x6f, 0x22, 0x3a, 0x7b, 0x22,
0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x31, 0x30, 0x30, 0x2c, 0x22, 0x69, 0x78,
0x22, 0x3a, 0x37, 0x7d, 0x2c, 0x22, 0x73, 0x6b, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30,
0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x34, 0x7d, 0x2c, 0x22,
0x73, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6b, 0x22, 0x3a, 0x30,
0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x35, 0x7d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x22,
0x7d, 0x5d, 0x2c, 0x22, 0x6e, 0x6d, 0x22, 0x3a, 0x22, 0x20, 0x31, 0x22, 0x2c, 0x22, 0x6e, 0x70,
0x22, 0x3a, 0x32, 0x2c, 0x22, 0x63, 0x69, 0x78, 0x22, 0x3a, 0x32, 0x2c, 0x22, 0x62, 0x6d, 0x22,
0x3a, 0x30, 0x2c, 0x22, 0x69, 0x78, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x6d, 0x6e, 0x22, 0x3a, 0x22,
0x41, 0x44, 0x42, 0x45, 0x20, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x47, 0x72, 0x6f, 0x75,
0x70, 0x22, 0x2c, 0x22, 0x68, 0x64, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x7d, 0x5d, 0x2c,
0x22, 0x69, 0x70, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x6f, 0x70, 0x22, 0x3a, 0x39, 0x30, 0x2c, 0x22,
0x73, 0x74, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x62, 0x6d, 0x22, 0x3a, 0x30, 0x7d, 0x5d, 0x2c, 0x22,
0x6d, 0x61, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d
};
const unsigned int lottie_data_size = 5308;
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
TaskHandle_t change_task_handle = NULL;
static QueueHandle_t g_result_que = NULL;
static volatile int task_flag = 0;
// 阻止自动休眠的标志 (用于 OTA、哨兵模式等不能中断的场景)
bool block_auto_sleep = false;
const char *default_emoji_path = "/extflash/0/default.json";
const char *cold_emoji_path = "/extflash/0/cold.json";
volatile uint16_t g_ble_play_sticker_pending_action_id = 0;
volatile uint8_t g_ble_play_sticker_pending_direction = 0x00;
int lottie_id = 0;
int last_lottie_id = 0;
bool back2default = false;
bool first_display = true;
uint32_t g_current_emoji_event = 0; // 记录当前播放的事件类型
/* 当前播放的 action_id,用于去抖;在播放状态重置后清零,允许同动作再次触发 */
uint16_t current_emoji_action_id = 0;
/* completed_cb 回灌 pending 时,允许同 action_id 重播一次,避免被去抖误吞 */
// static volatile bool g_allow_same_action_replay_once = false;
static TaskHandle_t lvgl_task_handle = NULL;
bool lvgl_port_task_is_current(void)
{
return (lvgl_task_handle != NULL && xTaskGetCurrentTaskHandle() == lvgl_task_handle);
}
static esp_timer_handle_t lvgl_tick_timer_handle = NULL;
static void *lvgl_draw_buf1 = NULL;
static void *lvgl_draw_buf2 = NULL;
static bool lvgl_deinitialized = false;
static bool g_auto_sleep_checker_inited = false;
volatile char *emoji_file_path = {0};
char g_play_lottie_path[128] = {0};
static volatile bool g_lottie_switch_in_progress = false;
typedef struct {
char path[128];
} lottie_switch_req_t;
static QueueHandle_t g_lottie_switch_queue = NULL;
static QueueHandle_t g_change_action_queue = NULL;
// LVGL 库不是线程安全的,本示例将从不同的任务调用 LVGL API,因此使用互斥锁来保护它
_lock_t lvgl_api_lock;
/* 注:else 分支处理所有 play_sticker(非音乐/非哨兵),统一设 last_lottie_id=0 阻止播完立即回 default */
char pending_sentinel_switch_path[128] = {0}; /* NOTIFY_SENTINEL_DO_SWITCH 时使用 */
char g_current_lottie_path[128] = {0};
/* music_listen 总播放遍数(每个大循环 intro 之后);2 表示连播两遍 */
#define MUSIC_LISTEN_TOTAL_PASSES 8
#if (MUSIC_LISTEN_TOTAL_PASSES < 1)
#error "MUSIC_LISTEN_TOTAL_PASSES must be >= 1"
#endif
#define CHANGE_ACTION_QUEUE_LEN 8
static int64_t last_time = 0;
static int64_t now = 0;
extern uint8_t g_current_brightness;
extern struct stat file_stat;
extern int current_page_idx;
extern const char *cmd_phoneme[5];
extern lv_obj_t *lottie;
extern lv_obj_t *current_lottie;
extern uint8_t *canvas_fb;
extern qspi_amoled_vendor_config_t vendor_config;
#define LOTTIE_TARGET_FPS 30
#if EXAMPLE_USE_TOUCH
extern esp_lcd_touch_handle_t tp;
#endif
/******************************************************* functions declaration ************************************************************************/
// 外部函数声明
extern void show_random_fortune_result(void);
extern void perform_draw(void);
extern volatile bool is_playing_music;
extern void toggle_sleep_mode(bool enter_sleep);
extern volatile bool is_system_sleeping;
extern void load_timeout_from_nvs(void);
extern void auto_sleep_checker_cb(lv_timer_t *timer);
/******************************************************* functions definition ************************************************************************/
//给change_task 投递一条动作指令 确保最新的指令能进去并通知任务处理
bool change_task_post_action(uint16_t action_id)
{
if (g_change_action_queue == NULL)
{
ESP_LOGW(TAG_LVGL, "post action failed: g_change_action_queue is NULL (action=0x%04X)", action_id);
return false;
}
BaseType_t qret = xQueueSend(g_change_action_queue, &action_id, 0);
if (qret != pdPASS)
{
uint16_t dropped = 0;
(void)xQueueReceive(g_change_action_queue, &dropped, 0);
qret = xQueueSend(g_change_action_queue, &action_id, 0);
ESP_LOGW(TAG_LVGL,
"action queue full, drop oldest=0x%04X enqueue newest=0x%04X ret=%d",
dropped, action_id, (int)qret);
}
if (change_task_handle != NULL)
{
(void)xTaskNotify(change_task_handle, (uint32_t)NOTIFY_ACTION_QUEUE_READY, eSetValueWithOverwrite);
}
return (qret == pdPASS);
}
/**
* default / calling / music_listen 等在 switch 里常设为无限循环且 completed_cb=NULL,
* lottie_anim_completed_cb 不会跑,BLE 侧 pending 无法被消费;remapping 对未知 id(如 0x0000)
* 回落到 default.json 时还会把 g_ble_play_sticker_active 永久置 true,后续指令只能进 pending。
*/
static void ble_flush_pending_on_idle_infinite_lottie(const char *file_path)
{
if (g_ble_play_sticker_pending_valid && change_task_handle)
{
uint16_t next_action_id = g_ble_play_sticker_pending_action_id;
uint8_t next_direction = g_ble_play_sticker_pending_direction;
g_ble_play_sticker_pending_valid = false;
g_ble_play_sticker_pending_action_id = 0;
g_ble_play_sticker_pending_direction = 0x00;
g_ble_play_sticker_active = true;
ESP_LOGI("BLE_STICKER_GUARD",
"idle_inf_loop: consume pending play_sticker=0x%04X dir=0x%02X (no lottie completed_cb)",
next_action_id, next_direction);
current_emoji_action_id = 0;
(void)change_task_post_action(next_action_id);
if (next_direction <= 0x07 && !lvgl_lottie_should_skip_play_sticker_motor(next_action_id, next_direction))
(void)motor_post_play_sticker_direction(next_direction);
return;
}
if (file_path && strstr(file_path, "/default.json") != NULL)
g_ble_play_sticker_active = false;
}
//LCD 刷新完成回调,通知 LVGL 一帧 flush 已结束。
bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
last_time = now;
now = esp_timer_get_time();
lv_display_t *disp = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp);
return false;
}
//LVGL 刷新回调函数 刷新好一部分画面之后发给屏幕
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
panel_handle = lv_display_get_user_data(disp);
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
esp_err_t ret;
#if LV_COLOR_DEPTH == 16
lv_draw_sw_rgb565_swap(px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1));
ret = esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
#elif LV_COLOR_DEPTH == 24
ret = esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
#endif
if (ret != ESP_OK)
{
ESP_LOGE(TAG_LVGL, "draw_bitmap failed (%s), manually completing flush to prevent UI freeze", esp_err_to_name(ret));
lv_display_flush_ready(disp);
}
}
//将刷新区域对齐到偶数边界,满足硬件/驱动要求。
static void example_lvgl_rounder_cb(lv_event_t *e)
{
lv_area_t *area = lv_event_get_param(e);
uint16_t x1 = area->x1;
uint16_t x2 = area->x2;
uint16_t y1 = area->y1;
uint16_t y2 = area->y2;
area->x1 = (x1 >> 1) << 1;
area->y1 = (y1 >> 1) << 1;
area->x2 = ((x2 >> 1) << 1) + 1;
area->y2 = ((y2 >> 1) << 1) + 1;
}
#if EXAMPLE_USE_TOUCH
extern bool is_anim_paused;
//触摸输入读取回调;同时处理休眠唤醒、动画暂停/恢复、活跃时间刷新。
static void example_lvgl_touch_cb(lv_indev_t *indev, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)lv_indev_get_user_data(indev);
esp_lcd_touch_point_data_t point_data;
uint8_t tp_cnt = 0;
esp_lcd_touch_read_data(tp);
esp_err_t err = esp_lcd_touch_get_data(tp, &point_data, &tp_cnt, 1);
if (err == ESP_OK && tp_cnt > 0)
{
data->point.x = point_data.x;
data->point.y = point_data.y;
data->state = LV_INDEV_STATE_PRESSED;
/* 如果处于”系统休眠”,点击屏幕触发唤醒并切回默认表情 */
bool woke_up = false;
if (is_system_sleeping)
{
/* 先切回默认页(屏尚黑,无动画),再开屏,避免休眠界残影 */
back2default = true;
ui_change_page(0, LV_SCR_LOAD_ANIM_NONE);
toggle_sleep_mode(false);
/* 重置输入设备状态,清除唤醒触摸残留,保证下次手势能被正确识别 */
lv_indev_reset(indev, NULL);
woke_up = true;
}
/* 任何触摸都刷新自动休眠活跃时间,避免唤醒后立即再次休眠 */
system_keep_alive();
ui_reset_idle_timer();
/* 刚唤醒时不要立刻暂停动画,避免看不到默认表情播放 */
if (!woke_up && !is_anim_paused && current_lottie != NULL)
{
lv_anim_t *a = lv_lottie_get_anim(current_lottie);
if (a)
{
lv_anim_pause(a);
is_anim_paused = true;
}
}
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
if (is_anim_paused && current_lottie != NULL)
{
lv_anim_t *a = lv_lottie_get_anim(current_lottie);
if (a)
{
lv_anim_resume(a);
is_anim_paused = false;
// ESP_LOGI(TAG_LVGL, "抬起:恢复渲染");
}
}
}
}
#endif
//周期递增 LVGL tick(时间基准)。
static void example_increase_lvgl_tick(void *arg)
{
/* 告诉 LVGL 已经过去了多少毫秒 */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
//LVGL 主循环任务:执行 ui_cmd_drain + lv_timer_handler。
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG_LVGL, "Starting LVGL task");
uint32_t time_till_next_ms = 0;
if (canvas_fb == NULL)
{
canvas_fb = heap_caps_malloc(LOTTIE_ANIM_WIDTH * LOTTIE_ANIM_HEIGHT * 4, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
if (canvas_fb == NULL)
{
ESP_LOGE(TAG_LVGL, "Failed to allocate canvas_fb buffer");
return;
}
}
lv_obj_t * ui_Screen_Lottie = lv_obj_create(NULL);
lv_obj_remove_flag(ui_Screen_Lottie, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_bg_color(ui_Screen_Lottie, lv_color_black(), 0);
lv_obj_set_style_bg_opa(ui_Screen_Lottie, LV_OPA_COVER, 0);
new_lottie = lv_lottie_create(ui_Screen_Lottie);
lv_lottie_set_src_data(new_lottie, lottie_data, lottie_data_size);
lv_lottie_set_buffer(new_lottie, LOTTIE_ANIM_WIDTH, LOTTIE_ANIM_HEIGHT, canvas_fb);
lv_anim_t *anim = lv_lottie_get_anim(new_lottie);
lv_anim_set_repeat_count(anim, LV_ANIM_REPEAT_INFINITE);
lv_screen_load(ui_Screen_Lottie);
while (1)
{
if (last_time != 0) {
int64_t delta = now - last_time; // 单位 us
float fps = 1000000.0f / delta /6;
ESP_LOGI(TAG_LVGL, "FPS %.2f delta=%lld us", fps, delta);
}
_lock_acquire(&lvgl_api_lock);
/* 执行来自其它任务的 UI 更新命令(必须在 LVGL 线程上下文执行) */
//ui_cmd_drain();
time_till_next_ms = lv_timer_handler();
_lock_release(&lvgl_api_lock);
// 如果触发任务看门狗超时
time_till_next_ms = MAX(time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS);
// 如果 lvgl 显示尚未准备好
time_till_next_ms = MIN(time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS);
usleep(1000 * time_till_next_ms);
}
}
//初始化 LVGL 创建显示 缓冲 触摸输入 tick 定时器 LVGL 任务
esp_err_t app_lvgl_init(void)
{
/* OTA/other shutdown may set this flag; always reset before re-init */
lvgl_deinitialized = false;
/* lv_deinit() 会清掉 LVGL timer,重建后允许重新创建自动休眠检查 timer */
g_auto_sleep_checker_inited = false;
ESP_LOGI(TAG_LVGL, "Initialize LVGL library");
ESP_LOGW(TAG_LVGL, "PATCH_MARKER: ui_stability_guards + extflash_switch_guard");
lv_init();
ui_cmd_init();
ESP_ERROR_CHECK(ui_dispatch_init());
// 使用内部 SRAM + DMA 标志,避免 SPI DMA 分配 bounce buffer 导致内部 SRAM 耗尽
// 条带高度必须与 config.h 中 EXAMPLE_LVGL_BUF_HEIGHT 一致(screen_init QSPI max_transfer_size 同源)
size_t draw_buffer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_BUF_HEIGHT * sizeof(lv_color_t);
void *buf1 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (!buf1) {
ESP_LOGE(TAG_LVGL, "buf1 DMA alloc failed, fallback to SPIRAM");
buf1 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_SPIRAM);
}
assert(buf1);
lvgl_draw_buf1 = buf1;
void *buf2 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (!buf2) {
ESP_LOGE(TAG_LVGL, "buf2 DMA alloc failed, fallback to SPIRAM");
buf2 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_SPIRAM);
}
assert(buf2);
lvgl_draw_buf2 = buf2; // 双缓冲模式使用 buf2
ESP_LOGI(TAG_LVGL, "Register display driver to LVGL (double buffer mode)");
lv_display_t *disp = lv_display_create(512, 410);
lv_display_set_buffers(disp, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_user_data(disp, panel_handle);
lv_display_set_color_format(disp, LV_COLOR_FORMAT);
lv_display_set_flush_cb(disp, example_lvgl_flush_cb);
lv_display_add_event_cb(disp, example_lvgl_rounder_cb, LV_EVENT_INVALIDATE_AREA, disp);
ESP_LOGI(TAG_LVGL, "Install LVGL tick timer");
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
lvgl_tick_timer_handle = lvgl_tick_timer;
ESP_LOGI(TAG_LVGL, "Register io panel event callback for LVGL flush ready notification");
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = example_notify_lvgl_flush_ready,
};
/* Register done callback */
ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, disp));
uint32_t internal_sram_total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
uint32_t internal_sram_free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
uint32_t internal_sram_used = internal_sram_total - internal_sram_free;
ESP_LOGI(TAG_LVGL, "Internal SRAM - Total: %" PRIu32 " bytes, Free: %" PRIu32 " bytes, Used: %" PRIu32 " bytes", internal_sram_total, internal_sram_free, internal_sram_used);
/*
#if EXAMPLE_USE_TOUCH
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_display(indev, disp);
lv_indev_set_read_cb(indev, example_lvgl_touch_cb);
lv_indev_set_user_data(indev, tp);
#endif
*/
ESP_LOGI(TAG_LVGL, "Create LVGL task");
lvgl_task_stack = heap_caps_malloc(LVGL_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
ESP_LOGI(TAG_LVGL,"Stack buffer: %p, size bytes: %d\n", lvgl_task_stack, LVGL_TASK_STACK_SIZE);
lvgl_task_handle = xTaskCreateStaticPinnedToCore(example_lvgl_port_task, "LVGL", LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, lvgl_task_stack, &lvgl_task_tcb, 1);
return ESP_OK;
}
//为WIFI /OTA 场景释放LVGL资源和显示资源,回收内存并停止相关任务
void lvgl_deinit_for_wifi(void)
{
if (lvgl_deinitialized)
return;
lvgl_deinitialized = true;
ESP_LOGW(TAG_LVGL, "=== WiFi连接前: 释放LVGL及显示资源以腾出内部SRAM ===");
uint32_t free_before = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
ESP_LOGI(TAG_LVGL, "释放前内部SRAM剩余: %" PRIu32 " bytes", free_before);
_lock_acquire(&lvgl_api_lock);
if (change_task_handle)
{
vTaskDelete(change_task_handle);
change_task_handle = NULL;
}
if (lvgl_task_handle)
{
vTaskDelete(lvgl_task_handle);
lvgl_task_handle = NULL;
}
_lock_release(&lvgl_api_lock);
if (lvgl_tick_timer_handle)
{
esp_timer_stop(lvgl_tick_timer_handle);
esp_timer_delete(lvgl_tick_timer_handle);
lvgl_tick_timer_handle = NULL;
}
ui_font_runtime_deinit();
/* Fully teardown LVGL core/timers/disp objects.
* Otherwise re-init may redraw with stale display/buffers and corrupt heap. */
lv_deinit();
vTaskDelay(pdMS_TO_TICKS(100));
/* Reset LVGL-related globals so our guards can safely short-circuit. */
ui_Screen_Lottie = NULL;
lottie = NULL;
current_lottie = NULL;
clear_music_preload();
if (canvas_fb)
{
heap_caps_free(canvas_fb);
canvas_fb = NULL;
}
if (panel_handle)
{
esp_lcd_panel_disp_on_off(panel_handle, false);
vTaskDelay(pdMS_TO_TICKS(50));
}
if (panel_handle)
{
esp_lcd_panel_del(panel_handle);
panel_handle = NULL;
}
if (io_handle)
{
esp_lcd_panel_io_del(io_handle);
io_handle = NULL;
}
spi_bus_free(LCD_HOST);
if (lvgl_task_stack)
{
heap_caps_free(lvgl_task_stack);
lvgl_task_stack = NULL;
}
if (change_task_stack)
{
heap_caps_free(change_task_stack);
change_task_stack = NULL;
}
if (lvgl_draw_buf1)
{
heap_caps_free(lvgl_draw_buf1);
lvgl_draw_buf1 = NULL;
}
if (lvgl_draw_buf2)
{
heap_caps_free(lvgl_draw_buf2);
lvgl_draw_buf2 = NULL;
}
/* 清空路径缓存:deinit 后 panel/change_task 已为 NULL,若仍保留旧路径,
* request_play_lottie_path 会因 strcmp 与 g_current_lottie_path 一致而“跳过通知”,
* 主任务 finalize 却看到句柄为空,误以为需整段重建。 */
g_play_lottie_path[0] = '\0';
pending_sentinel_switch_path[0] = '\0';
lvgl_clear_current_lottie_path();
uint32_t free_after = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
ESP_LOGW(TAG_LVGL, "=== LVGL资源释放完成 ===");
ESP_LOGI(TAG_LVGL, "释放后内部SRAM剩余: %" PRIu32 " bytes (回收 %" PRIu32 " bytes)",
free_after, free_after - free_before);
}
//动画播放完之后的统一回调:处理首显亮屏、音乐恢复 pending指令持续 回默认/结果页等等
void lottie_anim_completed_cb(lv_anim_t *anim)//当前正在运行的动画指针 动画跑完之后调用这个回调函数
{
ESP_LOGI(TAG_LVGL, "[TS] lottie_anim_completed_cb enter t=%lums", (unsigned long)esp_log_timestamp());
if(anim == NULL || anim->var == NULL) {
ESP_LOGW(TAG_LVGL, "lottie_anim_completed_cb: anim or var is null");
return;
}
if(current_lottie == NULL || !lv_obj_is_valid(current_lottie)) {
ESP_LOGW(TAG_LVGL, "lottie_anim_completed_cb: current_lottie invalid, skip");
return;
}
bool heap_ok_before = heap_caps_check_integrity_all(true);
if(!heap_ok_before) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed before lottie_anim_completed_cb");
}
ESP_LOGI(TAG_LVGL,
"lottie_anim_completed_cb called (hidden=%d visible=%d active_screen=%p lottie_screen=%p is_ui_mode=%d last_lottie_id=%d)",
(int)lv_obj_has_flag(current_lottie, LV_OBJ_FLAG_HIDDEN),
(int)lv_obj_is_visible(current_lottie),
(void *)lv_screen_active(),
(void *)ui_Screen_Lottie,
(int)is_ui_mode,
last_lottie_id);
/* Debug: 动画结束时也采样 canvas_fb,验证最终帧是否写入缓冲区 */
if (canvas_fb != NULL)
{
const uint32_t *u32 = (const uint32_t *)canvas_fb;
uint32_t s0 = u32[0];
uint32_t idx = (LOTTIE_ANIM_WIDTH * LOTTIE_ANIM_HEIGHT) / 4;
if (idx == 0) idx = 1;
uint32_t s1 = u32[idx];
ESP_LOGI(TAG_LVGL,
"[EMOJI_DEBUG] canvas_fb sample on completed: path=%s s0=0x%08X s1=0x%08X",
g_current_lottie_path, (unsigned)s0, (unsigned)s1);
}
if (first_display)
{
ESP_LOGI(TAG_LVGL, "开始渐变亮屏");
first_display = false;
// 1. 加载亮度配置
load_brightness_from_nvs();
// 2. 应用亮度 (在创建显示驱动后)
// if (panel_handle)
// {
// panel_qspi_amoled_set_brightness(panel_handle, g_current_brightness);
// }
// ESP_ERROR_CHECK(panel_qspi_amoled_set_brightness(panel_handle, 255));
lv_anim_t brightness_anim;
lv_anim_init(&brightness_anim);
lv_anim_set_var(&brightness_anim, panel_handle);
lv_anim_set_exec_cb(&brightness_anim, (lv_anim_exec_xcb_t)panel_qspi_amoled_set_brightness);
lv_anim_set_values(&brightness_anim, 0, g_current_brightness);
lv_anim_set_time(&brightness_anim, 1000); // 渐变时间1秒
lv_anim_set_path_cb(&brightness_anim, lv_anim_path_ease_out);
lv_anim_start(&brightness_anim);
ESP_LOGI(TAG_LVGL, "亮度渐变动画已启动:0 -> %d", g_current_brightness);
// back2default = true;
bool heap_ok_after_first = heap_caps_check_integrity_all(true);
if(!heap_ok_after_first) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed after first-display branch");
}
return;
}
(void)anim; // 标记未使用参数,避免编译器警告
/* 通话中插播表情时挂起的挂断:插播播完后再回默认(须先于音乐/通话恢复分支) */
if (lvgl_lottie_completed_try_deferred_hangup())
{
return;
}
/* 持续播放音乐时被其他指令打断:该指令播完后切回音乐循环(在 LVGL 回调中不能持锁,通知 change_task 执行) */
if (resume_music_loop_after_oneshot)
{
resume_music_loop_after_oneshot = false;
/* 插播期间 BLE 可能已下发 0x01/0x02 等侧向;恢复 music_listen 前补物理回中,避免头停在侧向 */
(void)motor_post_play_sticker_direction(0x0Au);
if (change_task_handle)
xTaskNotify(change_task_handle, (uint32_t)NOTIFY_RESUME_MUSIC_LOOP, eSetValueWithOverwrite);
ESP_LOGI(TAG_LVGL, "音乐插播: 播完,通知恢复到此前播放态");
bool heap_ok_after_resume = heap_caps_check_integrity_all(true);
if(!heap_ok_after_resume) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed after resume-music branch");
}
return;
}
if (resume_calling_loop_after_oneshot)
{
resume_calling_loop_after_oneshot = false;
if (change_task_handle)
xTaskNotify(change_task_handle, (uint32_t)NOTIFY_RESUME_CALLING_LOOP, eSetValueWithOverwrite);
ESP_LOGI(TAG_LVGL, "通话插播: 播完,通知恢复到通话循环段");
bool heap_ok_after_call_resume = heap_caps_check_integrity_all(true);
if (!heap_ok_after_call_resume) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed after resume-calling branch");
}
return;
}
if (lvgl_lottie_completed_try_resume_ai_respond_loop())
{
return;
}
if (lvgl_lottie_completed_try_start_ai_respond_loop())
{
return;
}
/* BLE play_sticker 单槽覆盖队列:当前动画播完后,优先切到最后一条待播指令 */
if (g_ble_play_sticker_pending_valid && change_task_handle)
{
uint16_t next_action_id = g_ble_play_sticker_pending_action_id;
uint8_t next_direction = g_ble_play_sticker_pending_direction;
g_ble_play_sticker_pending_valid = false;
g_ble_play_sticker_pending_action_id = 0;
g_ble_play_sticker_pending_direction = 0x00;
g_ble_play_sticker_active = true; /* 继续处于 BLE 播放态,直到最终回默认 */
ESP_LOGI("BLE_STICKER_GUARD",
"completed_cb: consume pending play_sticker=0x%04X dir=0x%02X",
next_action_id, next_direction);
/* pending 回灌若与当前 action_id 相同,需要放行一次,避免被去抖吞掉后卡在 active=true */
current_emoji_action_id = 0;
(void)change_task_post_action(next_action_id);
/* 单槽队列补发电机方向:00~07 都执行对应转向动作(左右切换时自动先回中) */
if (next_direction <= 0x07 && !lvgl_lottie_should_skip_play_sticker_motor(next_action_id, next_direction))
(void)motor_post_play_sticker_direction(next_direction);
return;
}
// 判断是否需要切换回默认动画
if (last_lottie_id == 0)
{
/* 特殊事件(星座运势、抽签)需要跳过 last_lottie_id==0 检查 */
if (g_current_emoji_event == emoji_event_horoscope ||
g_current_emoji_event == emoji_event_bless)
{
// ESP_LOGI(TAG_LVGL, "【抽签调试】特殊事件 0x%04X,跳过 last_lottie_id==0 检查,继续执行", (unsigned int)g_current_emoji_event);
// 不返回,继续执行后续逻辑
}
else
{
/* play_sticker 会故意 last_lottie_id=0 以保持表情且不立刻 back2default;
* 单次播完仍会进本分支,此处需清 g_ble_play_sticker_active,否则 IMU 幸运等一直被挡 */
if (g_ble_play_sticker_active)
{
ESP_LOGI("BLE_STICKER_GUARD",
"completed_cb: CLEAR g_ble_play_sticker_active (last_lottie_id==0, BLE play_sticker 单次播完)");
g_ble_play_sticker_active = false;
}
bool heap_ok_after_default = heap_caps_check_integrity_all(true);
if(!heap_ok_after_default) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed on default-animation branch");
}
return; // 当前就是默认动画,不需要切换
}
}
// 根据事件类型调用对应的结果显示函数
// ESP_LOGI(TAG_LVGL, "【抽签调试】动画完成回调: g_current_emoji_event=0x%04X, last_lottie_id=%d",
// (unsigned int)g_current_emoji_event, last_lottie_id);
if (g_current_emoji_event == emoji_event_horoscope)
{
ESP_LOGI(TAG_LVGL, "星座运势动画播放完成,显示结果界面");
show_random_fortune_result();
g_current_emoji_event = 0; // 清除事件标志
bool heap_ok_after_horoscope = heap_caps_check_integrity_all(true);
if(!heap_ok_after_horoscope) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed after horoscope result");
}
return; // 不回到默认表情
}
else if (g_current_emoji_event == emoji_event_bless)
{
ESP_LOGI(TAG_LVGL, "抽签动画播放完成,显示结果界面");
perform_draw();
g_current_emoji_event = 0; // 清除事件标志
bool heap_ok_after_sortition = heap_caps_check_integrity_all(true);
if(!heap_ok_after_sortition) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed after sortition result");
}
return; // 不回到默认表情
}
back2default = true;
bool heap_ok_after = heap_caps_check_integrity_all(true);
if(!heap_ok_after) {
ESP_LOGE(TAG_LVGL, "Heap integrity failed after lottie_anim_completed_cb");
}
}
//创建独立lottie 屏 初始化页面与字体 ,加载默认表情 注册自动休眠检测
void lottie_init(void)
{
ESP_LOGE(TAG_LVGL, "lottie_init");
// 1. 创建独立的屏幕对象
ui_Screen_Lottie = lv_obj_create(NULL);
lv_obj_remove_flag(ui_Screen_Lottie, LV_OBJ_FLAG_SCROLLABLE);
// 2. 设置黑色背景
lv_obj_set_style_bg_color(ui_Screen_Lottie, lv_color_black(), 0);
lv_obj_set_style_bg_opa(ui_Screen_Lottie, LV_OPA_COVER, 0);
/* Ensure runtime fonts are loaded before creating GUI screens. */
ui_font_runtime_init();
ui_pages_create(); // 创建其他页面
/* BLE 的 date/time 可能早于页面创建到达,这里在 UI 就绪后补刷一次缓存值 */
ble_datetime_flush_cached_to_ui();
/* BLE 的天气可能早于页面创建到达,这里在 UI 就绪后补刷一次缓存值 */
ble_weather_flush_cached_to_ui();
// 给当前屏幕注册手势... (这里要注意,可能需要给 ui_Screen_Lottie 也注册,或者全局注册)
// 建议给 ui_Screen_Lottie 也注册一下手势,否则在动画页没法下滑
lv_obj_add_event_cb(ui_Screen_Lottie, ui_event_gesture_cb, LV_EVENT_GESTURE, NULL);
// 3. 初始加载这个独立屏幕
lv_screen_load(ui_Screen_Lottie);
/* 若上次已切换到 A/B/C/D,开机直接使用该系列的 default.json */
char cur_pack = emoji_storage_get_using_pack();
/* 注意:调用方已持有锁,必须先释放再切,避免死锁;亮度渐变移到循环外执行 */
_lock_release(&lvgl_api_lock);
if (cur_pack == 'A' || cur_pack == 'B' || cur_pack == 'C' || cur_pack == 'D')
{
char pack_default[48];
snprintf(pack_default, sizeof(pack_default), "/extflash/%c/default.json", cur_pack);
struct stat st;
if (stat(pack_default, &st) == 0)
{
switch_lottie_animation_cross_path(pack_default);
}
else
{
switch_lottie_animation(0);
}
}
else
{
switch_lottie_animation(0);
}
/* first_display:默认表情首帧就绪后由 schedule_first_display_clear_after_default_ready() 清除 */
/* 注册自动休眠巡检(1s 一次),支撑“30s/5min/10min”超时休眠 */
if (!g_auto_sleep_checker_inited)
{
lv_timer_t *sleep_checker = lv_timer_create(auto_sleep_checker_cb, 1000, NULL);
if (sleep_checker != NULL)
{
g_auto_sleep_checker_inited = true;
ESP_LOGI(TAG_LVGL, "自动休眠检查 timer 已创建 (1s)");
}
else
{
ESP_LOGE(TAG_LVGL, "自动休眠检查 timer 创建失败");
}
}
_lock_acquire(&lvgl_api_lock);
}
/** 默认表情无限循环无 completed_cb:在 LVGL 下一拍渲染后再清 first_display,表示首帧已就绪 */
static void first_display_clear_after_default_ready_cb(lv_timer_t *t)
{
(void)t;
if (first_display) {
first_display = false;
}
}
static void schedule_first_display_clear_after_default_ready(void)
{
if (!first_display) {
return;
}
lv_timer_t *tm = lv_timer_create(first_display_clear_after_default_ready_cb, 1, NULL);
if (tm == NULL) {
ESP_LOGW(TAG_LVGL, "first_display 清除定时器创建失败,立即清除");
first_display = false;
return;
}
lv_timer_set_repeat_count(tm, 1);
lv_timer_set_auto_delete(tm, true);
}
//切换动画
void switch_lottie_animation(int anim_index)
{
ESP_LOGI(TAG_LVGL, "Free heap before switch: %" PRIu32 " bytes", esp_get_free_heap_size());
/* During OTA we may temporarily deinit LVGL; IMU/BMI must not touch LVGL then. */
if (lvgl_deinitialized || ui_Screen_Lottie == NULL)
return;
const char *file_path = lottie_files[anim_index];
struct stat file_stat;
if (stat(file_path, &file_stat) != 0)
{
ESP_LOGE(TAG_LVGL, "Lottie file not found: %s", file_path);
return;
}
/* 先创建并配置新 lottie,再删除旧的,避免 IMU 触发切换时黑屏 */
lv_obj_t *old_lottie = current_lottie;
lv_obj_t *new_lottie = NULL;
if (canvas_fb == NULL)
{
canvas_fb = heap_caps_malloc(LOTTIE_ANIM_WIDTH * LOTTIE_ANIM_HEIGHT * 4, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
if (canvas_fb == NULL)
{
ESP_LOGE(TAG_LVGL, "Failed to allocate canvas_fb buffer");
return;
}
}
_lock_acquire(&lvgl_api_lock);
new_lottie = lv_lottie_create(ui_Screen_Lottie);
if (new_lottie == NULL)
{
_lock_release(&lvgl_api_lock);
ESP_LOGE(TAG_LVGL, "Failed to create Lottie object");
return;
}
lv_lottie_set_buffer(new_lottie, LOTTIE_ANIM_WIDTH, LOTTIE_ANIM_HEIGHT, canvas_fb);
lv_obj_align(new_lottie, LV_ALIGN_CENTER, 4, 0);
if (strncmp(file_path, "/extflash/", 10) == 0)
extflash_lock();
lv_lottie_set_src_file(new_lottie, file_path);
if (strncmp(file_path, "/extflash/", 10) == 0)
extflash_unlock();
lv_anim_t *anim = lv_lottie_get_anim(new_lottie);
if (anim)
{
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] lottie anim loaded: path=%s raw_duration=%ld", file_path, (long)anim->duration);
anim->duration = anim->duration * 2;
if (first_display)
{
/* 开机时 anim_index==0 为默认表情,循环播放;亮屏逻辑在 lottie_init 中统一执行 */
if (anim_index == 0)
{
anim->repeat_cnt = LV_ANIM_REPEAT_INFINITE;
anim->completed_cb = NULL;
}
else
{
anim->repeat_cnt = 0;
anim->completed_cb = lottie_anim_completed_cb;
}
}
else if (anim_index == 0 || anim_index == music_listen_emojo || anim_index == music_listen_emojo)
{
anim->repeat_cnt = LV_ANIM_REPEAT_INFINITE;
anim->completed_cb = NULL;
}
else
{
anim->repeat_cnt = 0;
anim->completed_cb = lottie_anim_completed_cb;
}
}
/* 新 lottie 已就绪,删除旧的(无黑屏) */
if (old_lottie != NULL && lv_obj_is_valid(old_lottie))
{
if (old_lottie == g_music_preload_lottie)
{
if (g_music_preload_fb)
{
heap_caps_free(g_music_preload_fb);
g_music_preload_fb = NULL;
}
g_music_preload_lottie = NULL;
g_music_preload_path[0] = '\0';
g_music_preload_ready = false;
}
lv_anim_t *old_anim = lv_lottie_get_anim(old_lottie);
if (old_anim)
lv_anim_del(old_anim, NULL);
lv_obj_del(old_lottie);
}
current_lottie = new_lottie;
_lock_release(&lvgl_api_lock);
last_lottie_id = anim_index;
strncpy(g_current_lottie_path, file_path, sizeof(g_current_lottie_path) - 1);
g_current_lottie_path[sizeof(g_current_lottie_path) - 1] = '\0';
ESP_LOGI(TAG_LVGL, "Switched to animation: %s", file_path);
ESP_LOGI(TAG_LVGL, "Free heap after switch: %" PRIu32 " bytes", esp_get_free_heap_size());
if (anim_index == 0) {
schedule_first_display_clear_after_default_ready();
}
}
//获取当前播放动画路径
const char *lvgl_get_current_lottie_path(void)
{
return g_current_lottie_path;
}
//清空当前播放路径缓存
void lvgl_clear_current_lottie_path(void)
{
g_current_lottie_path[0] = '\0';
}
//请求按路径播放动画
void request_play_lottie_path(const char *path)
{
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] request_play_lottie_path 被调用, path=%s", path ? path : "NULL");
if (path == NULL)
{
ESP_LOGW(TAG_LVGL, "[EMOJI_DEBUG] request_play_lottie_path: path 为 NULL, 直接返回");
return;
}
strncpy(g_play_lottie_path, path, sizeof(g_play_lottie_path) - 1);
g_play_lottie_path[sizeof(g_play_lottie_path) - 1] = '\0';
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] g_play_lottie_path 已设置: %s", g_play_lottie_path);
if(g_current_lottie_path[0] != '\0' && strcmp(g_current_lottie_path, g_play_lottie_path) == 0) {
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] 目标路径与当前播放一致,跳过通知");
return;
}
if (change_task_handle)
{
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] 发送 NOTIFY_PLAY_BY_PATH (0x%04X) 到 change_task", NOTIFY_PLAY_BY_PATH);
BaseType_t ret = xTaskNotify(change_task_handle, (uint32_t)NOTIFY_PLAY_BY_PATH, eSetValueWithOverwrite);
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] xTaskNotify 返回: %s", ret == pdPASS ? "pdPASS" : "pdFAIL");
}
else
{
ESP_LOGE(TAG_LVGL, "[EMOJI_DEBUG] change_task_handle 为 NULL! 无法发送通知");
}
}
//按任意路径切动画的内部实现(包含 文件校验 fallback 并发保护)
static void switch_lottie_animation_cross_path_internal(const char *emoji_path)
{
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] switch_lottie_animation_cross_path 被调用, emoji_path=%s", emoji_path ? emoji_path : "NULL");
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] Free heap before switch: %" PRIu32 " bytes", esp_get_free_heap_size());
/* During OTA we may temporarily deinit LVGL; change_task/IMU must not touch LVGL then. */
if (lvgl_deinitialized || ui_Screen_Lottie == NULL)
return;
if(emoji_path == NULL || emoji_path[0] == '\0') {
ESP_LOGW(TAG_LVGL, "[EMOJI_DEBUG] 空路径,跳过切换");
return;
}
if(g_lottie_switch_in_progress) {
ESP_LOGW(TAG_LVGL, "[EMOJI_DEBUG] 切换进行中,忽略本次请求: %s", emoji_path);
return;
}
/* 解压进行中时延后动画切换,避免与 NAND 写竞争 */
if (extflash_is_extracting()) {
ESP_LOGW(TAG_LVGL, "[EMOJI_DEBUG] 解压进行中,跳过本次切换: %s", emoji_path);
return;
}
g_lottie_switch_in_progress = true;
const char *file_path = emoji_path;
struct stat file_stat;
bool fell_back_to_default = false;
const bool is_extflash = (strncmp(file_path, "/extflash/", 10) == 0);
if (is_extflash) extflash_lock();
if (stat(file_path, &file_stat) != 0)
{
ESP_LOGE(TAG_LVGL, "[EMOJI_DEBUG] Lottie 文件不存在: %s (errno=%d)", file_path, errno);
file_path = default_emoji_path;
fell_back_to_default = true;
if (stat(file_path, &file_stat) != 0)
{
ESP_LOGE(TAG_LVGL, "[EMOJI_DEBUG] 默认动画文件也不存在: %s", file_path);
if (is_extflash) extflash_unlock();
g_lottie_switch_in_progress = false;
return;
}
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] 文件缺失,回落到默认动画: %s", file_path);
}
else
{
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] 文件存在: %s, 大小=%ld bytes", file_path, (long)file_stat.st_size);
}
/* 不跳过相同路径:用户再次发送同一指令时需重播动画,播完后的最后一帧不算“正在播放” */
/* 先创建并配置新 lottie,再删除旧的,避免切换时黑屏 */
lv_obj_t *old_lottie = current_lottie;
lv_obj_t *new_lottie = NULL;
lv_anim_t *anim = NULL;
bool music_listen_promoted = false;
if (canvas_fb == NULL)
{
canvas_fb = heap_caps_malloc(LOTTIE_BUF_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
if (canvas_fb == NULL)
{
ESP_LOGE(TAG_LVGL, "Failed to allocate canvas_fb buffer");
if (is_extflash) extflash_unlock();
g_lottie_switch_in_progress = false;
return;
}
ESP_LOGI(TAG_LVGL, "Allocated canvas_fb: %d bytes", LOTTIE_BUF_SIZE);
}
if (is_extflash) extflash_unlock();
_lock_acquire(&lvgl_api_lock);
const bool promote_listen = (!fell_back_to_default && g_music_preload_ready && g_music_preload_lottie != NULL &&
lv_obj_is_valid(g_music_preload_lottie) && strcmp(file_path, g_music_preload_path) == 0 &&
strstr(file_path, "music_listen") != NULL);
if (promote_listen) {
new_lottie = g_music_preload_lottie;
lv_obj_remove_flag(new_lottie, LV_OBJ_FLAG_HIDDEN);
lv_lottie_set_buffer(new_lottie, LOTTIE_ANIM_WIDTH, LOTTIE_ANIM_HEIGHT, canvas_fb);
uint8_t *obsolete_preload_fb = g_music_preload_fb;
g_music_preload_lottie = NULL;
g_music_preload_fb = NULL;
g_music_preload_ready = false;
g_music_preload_path[0] = '\0';
if (obsolete_preload_fb)
heap_caps_free(obsolete_preload_fb);
lv_obj_align(new_lottie, LV_ALIGN_CENTER, 4, 0);
music_listen_promoted = true;
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] music_listen promoted from preload (no re-parse): %s", file_path);
}
else {
new_lottie = lv_lottie_create(ui_Screen_Lottie);
if (new_lottie == NULL && file_path != default_emoji_path && stat(default_emoji_path, &file_stat) == 0)
{
file_path = default_emoji_path;
new_lottie = lv_lottie_create(ui_Screen_Lottie);
}
if (new_lottie == NULL)
{
_lock_release(&lvgl_api_lock);
ESP_LOGE(TAG_LVGL, "Failed to create Lottie object");
g_lottie_switch_in_progress = false;
return;
}
lv_lottie_set_buffer(new_lottie, LOTTIE_ANIM_WIDTH, LOTTIE_ANIM_HEIGHT, canvas_fb);
lv_obj_align(new_lottie, LV_ALIGN_CENTER, 4, 0);
if (is_extflash) {
extflash_lock();
}
lv_lottie_set_src_file(new_lottie, file_path);
if (is_extflash) extflash_unlock();
}
anim = lv_lottie_get_anim(new_lottie);
if (anim)
{
ESP_LOGI(TAG_LVGL,
"[EMOJI_DEBUG] lottie loaded: path=%s duration(ms)=%ld end_value(frames)=%ld act_time(ms)=%ld start_value=%ld hidden=%d visible=%d promoted=%d active_screen=%p lottie_screen=%p page_idx=%d is_ui_mode=%d",
file_path,
(long)anim->duration,
(long)anim->end_value,
(long)anim->act_time,
(long)anim->start_value,
(int)lv_obj_has_flag(new_lottie, LV_OBJ_FLAG_HIDDEN),
(int)lv_obj_is_visible(new_lottie),
(int)music_listen_promoted,
(void *)lv_screen_active(),
(void *)ui_Screen_Lottie,
current_page_idx,
(int)is_ui_mode);
if (anim->duration <= 0 || anim->end_value <= 0)
{
ESP_LOGE(TAG_LVGL, "[EMOJI_DEBUG] lottie parse seems invalid (duration=%ld, end_value=%ld): %s",
(long)anim->duration, (long)anim->end_value, file_path);
}
if (!music_listen_promoted)
anim->duration = anim->duration * 2;
if (first_display)
{
/* 开机时若是默认表情也循环播放,亮屏逻辑在 lottie_init 中统一执行 */
if (strstr(file_path, "/default.json") != NULL)
{
anim->repeat_cnt = LV_ANIM_REPEAT_INFINITE;
anim->completed_cb = NULL;
}
else
{
anim->repeat_cnt = 0;
anim->completed_cb = lottie_anim_completed_cb;
}
}
else
{
/* 任意表情包的默认动画(路径含 /default.json)以及音乐/通话等循环播放;guard/protect(0x0015/16) 与其它表情一致只播一遍 */
if ((strstr(file_path, "/default.json") != NULL) ||
(strstr(file_path, "music_listen") != NULL) ||
(strstr(file_path, "music2") != NULL) ||
(strstr(file_path, "calling") != NULL))
{
anim->repeat_cnt = LV_ANIM_REPEAT_INFINITE;
anim->completed_cb = NULL;
}
else
{
anim->repeat_cnt = 0;
anim->completed_cb = lottie_anim_completed_cb;
}
}
}
/* 新 lottie 已就绪,删除旧的 */
if (old_lottie != NULL && lv_obj_is_valid(old_lottie))
{
if (old_lottie == g_music_preload_lottie)
{
if (g_music_preload_fb)
{
heap_caps_free(g_music_preload_fb);
g_music_preload_fb = NULL;
}
g_music_preload_lottie = NULL;
g_music_preload_path[0] = '\0';
g_music_preload_ready = false;
}
lv_anim_t *old_anim = lv_lottie_get_anim(old_lottie);
if (old_anim)
lv_anim_del(old_anim, NULL);
lv_obj_del(old_lottie);
}
current_lottie = new_lottie;
/* music_listen:解锁前钳到 40%~75% 并立刻渲染区间首帧,避免解锁后先刷第 0 帧(常被误认为 default 待机脸) */
if (anim != NULL && strstr(file_path, "music_listen") != NULL) {
int32_t total = anim->end_value;
int32_t start_frame = (int32_t)(0.4f * (float)total);
int32_t end_frame = (int32_t)(0.75f * (float)total);
if (start_frame < 0) start_frame = 0;
if (end_frame > total) end_frame = total;
if (start_frame < end_frame) {
anim->start_value = start_frame;
anim->end_value = end_frame;
anim->act_time = 0;
anim->duration = (end_frame - start_frame) * 1000 / LOTTIE_TARGET_FPS;
anim->repeat_cnt = LV_ANIM_REPEAT_INFINITE;
anim->completed_cb = NULL;
anim->current_value = start_frame;
if (anim->exec_cb) anim->exec_cb(anim->var, start_frame);
}
}
_lock_release(&lvgl_api_lock);
strncpy(g_current_lottie_path, file_path, sizeof(g_current_lottie_path) - 1);
g_current_lottie_path[sizeof(g_current_lottie_path) - 1] = '\0';
g_lottie_switch_in_progress = false;
if (fell_back_to_default)
{
/* 缺文件时直接显示默认待机,不应继续占用 BLE 播放态,否则后续指令会一直 pending。 */
g_ble_play_sticker_active = false;
g_ble_play_sticker_pending_valid = false;
g_ble_play_sticker_pending_action_id = 0;
g_ble_play_sticker_pending_direction = 0x00;
/* 否则 current_emoji_action_id 仍为本次指令,default 又常无限循环无 completed_cb,去抖会永久丢弃同 action_id */
current_emoji_action_id = 0;
}
/* 非循环动画(如 welcome-left)播完后需回默认,设置 last_lottie_id 供 completed_cb 触发 back2default */
if (strstr(file_path, "/default.json") == NULL &&
strstr(file_path, "music_listen") == NULL && strstr(file_path, "music2") == NULL &&
strstr(file_path, "calling") == NULL)
last_lottie_id = 1; /* 非 0 即可,completed_cb 会设 back2default */
else
last_lottie_id = 0;
if (last_lottie_id == 0 && file_path &&
(strstr(file_path, "/default.json") != NULL || g_ble_play_sticker_pending_valid))
ble_flush_pending_on_idle_infinite_lottie(file_path);
ESP_LOGI(TAG_LVGL, "Switched to animation: %s", file_path);
ESP_LOGI(TAG_LVGL, "Free heap after switch: %" PRIu32 " bytes", esp_get_free_heap_size());
if (strstr(file_path, "/default.json") != NULL) {
schedule_first_display_clear_after_default_ready();
}
}
//对外入口:非change_task 上下文时先入队,再由change_task 执行切换
void switch_lottie_animation_cross_path(const char *emoji_path)
{
if (emoji_path == NULL || emoji_path[0] == '\0') {
return;
}
TaskHandle_t cur = xTaskGetCurrentTaskHandle();
if (change_task_handle != NULL && cur != change_task_handle && g_lottie_switch_queue != NULL) {
lottie_switch_req_t req = {0};
strncpy(req.path, emoji_path, sizeof(req.path) - 1);
req.path[sizeof(req.path) - 1] = '\0';
xQueueOverwrite(g_lottie_switch_queue, &req);
xTaskNotify(change_task_handle, (uint32_t)NOTIFY_SWITCH_BY_QUEUE, eSetValueWithOverwrite);
ESP_LOGI(TAG_LVGL, "[EMOJI_DEBUG] 外部上下文请求已入队: %s", req.path);
return;
}
switch_lottie_animation_cross_path_internal(emoji_path);
}
/* ==================== change_task 重构辅助函数 ==================== */
/** 清理所有播放状态(音乐、KTV、哨兵、BLE队列) */
/* ==================== 重构后的 change_task ==================== */
volatile uint16_t remapping_index = 0;
/** 「中」:音乐/通话/AI/哨兵等内部通知在切 Lottie 时与 BLE 队列一致,重置待机随机计时 */
static bool change_task_internal_notify_resets_random_idle(uint32_t bits)
{
switch (bits) {
case NOTIFY_PLAY_BY_PATH:
case NOTIFY_MUSIC_INTRO_DONE:
case NOTIFY_RESUME_MUSIC_LOOP:
case NOTIFY_SENTINEL_DO_SWITCH:
case NOTIFY_RESUME_CALLING_LOOP:
case NOTIFY_START_AI_RESPOND_LOOP:
case NOTIFY_RESUME_AI_RESPOND_LOOP:
return true;
default:
return false;
}
}
static void change_task(void *arg)
{
ESP_LOGI(TAG_LVGL, "Starting Change task");
static int last_active_page = 0;
static uint32_t random_check_counter = 0;
// 初始化
_lock_acquire(&lvgl_api_lock);
lottie_init();
_lock_release(&lvgl_api_lock);
/* 亮度渐变:lv_anim 必须在 LVGL 线程互斥域内注册,否则与 lv_timer_handler 竞态,易表现为“一跳到位” */
_lock_acquire(&lvgl_api_lock);
lv_anim_t brightness_anim;
lv_anim_init(&brightness_anim);
lv_anim_set_var(&brightness_anim, panel_handle);
lv_anim_set_exec_cb(&brightness_anim, (lv_anim_exec_xcb_t)panel_qspi_amoled_set_brightness);
lv_anim_set_values(&brightness_anim, 0, g_current_brightness);
lv_anim_set_time(&brightness_anim, 1000);
lv_anim_set_path_cb(&brightness_anim, lv_anim_path_ease_out);
lv_anim_start(&brightness_anim);
_lock_release(&lvgl_api_lock);
ESP_LOGI(TAG_LVGL, "渐变亮屏已启动:0 -> %d", g_current_brightness);
while (1)
{
uint32_t bits = 0;
BaseType_t notify_ret = pdFAIL;
bool from_change_action_queue = false;
// 优先从队列取(BLE play_sticker / pending 回灌 / idle 补消费)
uint16_t queued_action = 0;
if (g_change_action_queue && xQueueReceive(g_change_action_queue, &queued_action, 0) == pdTRUE)
{
bits = (uint32_t)queued_action;
notify_ret = pdPASS;
from_change_action_queue = true;
}
else
{
notify_ret = xTaskNotifyWait(0, ULONG_MAX, &bits, pdMS_TO_TICKS(100));
}
if (notify_ret != pdPASS)
{
// 检查回默认标记
if (back2default)
{
ESP_LOGI("recv_change_task", "[TS] change_task back2default branch t=%lums", (unsigned long)esp_log_timestamp());
back2default = false;
reset_all_playback_state();
switch_to_default_emoji();
_lock_acquire(&lvgl_api_lock);
last_lottie_id = 0;
(void)motor_post_cmd(0x0Au);
if (last_active_page != 0)
{
ui_change_page(last_active_page, LV_SCR_LOAD_ANIM_FADE_ON);
last_active_page = 0;
}
_lock_release(&lvgl_api_lock);
}
// 随机待机动画 2min一次
else if (last_lottie_id == 0 && !first_display && !is_ui_mode)
{
random_check_counter++;
if (random_check_counter >= 1091) //1091*100ms=109.1s=1.818min
{
random_check_counter = 0;
uint16_t n = esp_random() %19;
random_emoji(n);
}
}
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
ESP_LOGI("recv_change_task", "收到通知: 0x%04X", (unsigned int)bits);
// 记录事件类型(用于回调)
if (bits < 0xFFF0) // 不是内部命令
{
g_current_emoji_event = bits;
remapping_index = bits;
}
/* ========== 第一层:内部命令 ========== */
if (handle_internal_commands(bits, &last_active_page)) {
if (change_task_internal_notify_resets_random_idle(bits)) {
random_check_counter = 0;
}
continue;
}
/* ========== 第二层:普通表情指令 ========== */
uint16_t action_id = (uint16_t)bits;
const bool is_ai_stop_cmd_in_loop =
(action_id == emoji_event_AI_respond) && lvgl_lottie_ai_respond_loop_active_for_ble();
/* BLE 队列投递:在去抖前重置待机随机计时(去抖未实际播放也重置) */
if (from_change_action_queue) {
random_check_counter = 0;
}
// 相同指令去抖
// if (action_id == current_emoji_action_id && ! g_allow_same_action_replay_once)
if ((action_id == current_emoji_action_id) && !is_ai_stop_cmd_in_loop)
{
ESP_LOGI(TAG_LVGL, "表情去抖: 已在播放 0x%04X,忽略", action_id);
continue;
}
current_emoji_action_id = action_id;
// g_allow_same_action_replay_once = false;
// 停止音乐
if (handle_stop_music(action_id, &last_active_page))
continue;
// 获取表情路径(支持 A/B/C/D 包)
char *path_result = remapping(action_id);
static char pack_path_buf[80];
const char *path_to_play = path_result;
char cur_pack = emoji_storage_get_using_pack();
if (cur_pack == 'A' || cur_pack == 'B' || cur_pack == 'C' || cur_pack == 'D')
{
const char *basename = strrchr(path_result, '/');
basename = basename ? (basename + 1) : path_result;
snprintf(pack_path_buf, sizeof(pack_path_buf), "/extflash/%c/%s", cur_pack, basename);
struct stat st;
if (stat(pack_path_buf, &st) == 0)
path_to_play = pack_path_buf;
}
ESP_LOGI("recv_change_task", "表情路径: %s", path_to_play);
if (!path_to_play || !path_to_play[0])
{
ESP_LOGE("recv_change_task", "无效路径,跳过");
current_emoji_action_id = 0;
continue;
}
// 特殊模式处理
if (handle_play_music(action_id, path_to_play, &last_active_page))
continue;
if (handle_sentinel_mode(action_id, path_to_play, &last_active_page))
continue;
handle_normal_sticker(action_id, path_to_play, &last_active_page);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
esp_err_t app_change_init(void)
{
if (g_lottie_switch_queue == NULL) {
g_lottie_switch_queue = xQueueCreate(1, sizeof(lottie_switch_req_t));
}
if (g_change_action_queue == NULL) {
g_change_action_queue = xQueueCreate(CHANGE_ACTION_QUEUE_LEN, sizeof(uint16_t));
}
change_task_stack = heap_caps_malloc(CHANGE_TASK_STACK_SIZE, MALLOC_CAP_SPIRAM);
// 静态任务创建函数
change_task_handle = xTaskCreateStaticPinnedToCore(change_task, "CHANGE", CHANGE_TASK_STACK_SIZE, NULL, CHANGE_TASK_PRIORITY, change_task_stack, &change_task_tcb, 1); // 1
return ESP_OK;
}
void random_emoji(uint16_t n)
{
switch (n)
{
case 0:
switch_lottie_animation(liuliuliu_emojo);
break;
case 1:
switch_lottie_animation(calm_down_emojo);
break;
case 2:
switch_lottie_animation(love_emojo);
break;
case 3:
switch_lottie_animation(sing_emojo);
break;
case 4:
switch_lottie_animation(eating_emojo);
break;
case 5:
switch_lottie_animation(cheer_emojo);
break;
case 6:
switch_lottie_animation(money_emojo);
break;
case 7:
switch_lottie_animation(like_emojo);
break;
case 8:
switch_lottie_animation(shy_emojo);
break;
case 9:
switch_lottie_animation(curious_emojo);
break;
case 10:
switch_lottie_animation(holiday_emojo);
break;
case 11:
switch_lottie_animation(happy_emojo);
break;
case 12:
switch_lottie_animation(cry_emojo);
break;
case 13:
switch_lottie_animation(becool_emojo);
break;
case 14:
switch_lottie_animation(boring_emojo);
break;
case 15:
switch_lottie_animation(thinking_emojo);
break;
// case 16:
// switch_lottie_animation(sing_emojo);
// break;
// case 17:
// switch_lottie_animation(break_emojo);
// break;
// case 18:
// switch_lottie_animation(money_emojo);
// break;
default:
switch_lottie_animation(default_emojo);
break;
}
}