纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。 Co-authored-by: Cursor <cursoragent@cursor.com>
1272 lines
54 KiB
C
1272 lines
54 KiB
C
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <stdbool.h>
|
||
#include "esp_log.h"
|
||
#include "esp_timer.h"
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
|
||
#include "ble.h"
|
||
#include "ble_data_ansy.h"
|
||
#include "ble_send.h"
|
||
#include "ALGORITHM/crc.h"
|
||
#include "LVGL/lvgl_init.h"
|
||
#include "LVGL/lvgl_lottie.h"
|
||
#include "WIFI/wifi.h"
|
||
#include "MOTOR/motor.h"
|
||
#include "MOTOR/motor_control.h"
|
||
#include "remapping.h"
|
||
#include "event.h"
|
||
#include "OTA/ota_upgrade.h"
|
||
#include "ota_trigger.h"
|
||
#include "gui_guider.h"
|
||
#include "ui_cmd.h"
|
||
#include "UI_DISPATCH/ui_dispatch.h"
|
||
#include "events_init.h"
|
||
|
||
/* 统一亮度真源(定义在 main.c) */
|
||
extern void set_system_brightness(int percent);
|
||
|
||
// 确保必要的宏已定义
|
||
#ifndef BLE_SEND_EMOJI_USING
|
||
#error "BLE_SEND_EMOJI_USING not defined in ble.h"
|
||
#endif
|
||
#ifndef BLE_SEND_EMOJI_EXCHANGE_RESULT
|
||
#error "BLE_SEND_EMOJI_EXCHANGE_RESULT not defined in ble.h"
|
||
#endif
|
||
/*****************************************end of header file***************************************************************/
|
||
/** 模式指令过滤:500ms 内收到「同一条」列举指令(洗车/迎宾/露营/小憩/主动开启哨兵)只处理第一条,不同指令各自处理 */
|
||
#define MODE_INSTRUCTION_FILTER_MS 500
|
||
static uint32_t s_last_mode_instruction_key = 0; /* 0 = 尚未处理过;高16位 son_cmd,低16位 action_id */
|
||
static TickType_t s_last_mode_instruction_tick = 0;
|
||
|
||
/** 判断是否为需过滤的模式指令(play_sticker 下的 action_id) */
|
||
static inline bool is_filtered_play_sticker_mode(uint16_t action_id)
|
||
{
|
||
return (action_id == 0x000F) /* 露营模式 */
|
||
|| (action_id == 0x0010) /* 小憩模式 */
|
||
|| (action_id == 0x0011) /* 洗车模式 */
|
||
|| (action_id == 0x0012) /* 迎宾模式 */
|
||
|| (action_id == 0x0015) /* 哨兵模式 guard */
|
||
|| (action_id == 0x0016); /* 哨兵模式 protect */
|
||
}
|
||
|
||
/** 收到小憩(0x000E) 后短时内丢弃制冷/制热(0x0017/0x0018),避免车机顺带开空调表情盖住 snap */
|
||
#define REST_MODE_HVAC_SUPPRESS_MS 3000
|
||
static TickType_t s_rest_mode_hvac_suppress_start_tick = 0;
|
||
|
||
static inline void ble_arm_rest_mode_hvac_suppress_if_needed(uint16_t action_id)
|
||
{
|
||
if (action_id == emoji_event_welcome_mdoe)
|
||
{
|
||
s_rest_mode_hvac_suppress_start_tick = xTaskGetTickCount();
|
||
ESP_LOGI("BLE_ANSY", "小憩 0x000E:启动 %dms 制冷/制热表情抑制窗", REST_MODE_HVAC_SUPPRESS_MS);
|
||
}
|
||
}
|
||
|
||
static inline bool ble_should_drop_hvac_after_rest(uint16_t action_id)
|
||
{
|
||
if (action_id != emoji_event_welcome_mdoe && action_id != emoji_event_welcome_mdoe)
|
||
{
|
||
return false;
|
||
}
|
||
if (s_rest_mode_hvac_suppress_start_tick == 0)
|
||
{
|
||
return false;
|
||
}
|
||
const TickType_t elapsed = xTaskGetTickCount() - s_rest_mode_hvac_suppress_start_tick;
|
||
if (elapsed < pdMS_TO_TICKS(REST_MODE_HVAC_SUPPRESS_MS))
|
||
{
|
||
return true;
|
||
}
|
||
s_rest_mode_hvac_suppress_start_tick = 0;
|
||
return false;
|
||
}
|
||
|
||
/** 对「同一条」指令:500ms 内只处理第一条。key=(son_cmd<<16)|action_id。返回 true 表示应跳过 */
|
||
static bool mode_instruction_same_within_500ms(uint32_t key)
|
||
{
|
||
TickType_t now = xTaskGetTickCount();
|
||
if (s_last_mode_instruction_key == key && (s_last_mode_instruction_tick != 0) && ((now - s_last_mode_instruction_tick) < pdMS_TO_TICKS(MODE_INSTRUCTION_FILTER_MS)))
|
||
{
|
||
return true; /* 同一条指令且在 500ms 内,跳过 */
|
||
}
|
||
s_last_mode_instruction_key = key;
|
||
s_last_mode_instruction_tick = now;
|
||
return false;
|
||
}
|
||
|
||
/* 1.12 表情包下载:最近一次收到的 Target_pack 2 字节中的 ASCII (A/B/C/D),供下载 icar/X.zip 使用 */
|
||
static char s_emoji_download_target_pack = '0';
|
||
|
||
/** 延迟投递事件:NimBLE 回调中直接调用 set_system_event 可能无效,通过定时器在安全上下文执行 */
|
||
static esp_timer_handle_t s_defer_event_timer = NULL;
|
||
static uint32_t s_defer_event_bits = 0;
|
||
static void deferred_event_timer_cb(void *arg)
|
||
{
|
||
uint32_t bits = s_defer_event_bits;
|
||
s_defer_event_bits = 0;
|
||
ESP_LOGI("BLE_ANSY", "[DEBUG] 定时器回调执行 set_system_event(0x%lX)", (unsigned long)bits);
|
||
set_system_event(bits);
|
||
/* main_task 可能因 PSRAM 不足未创建,直接启动 OTA/表情包流程作为 fallback */
|
||
if (main_task_handle == NULL)
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "main_task 未创建,直接启动 ota_trigger");
|
||
if (bits == OTA_UPDATE_STARTED_BIT)
|
||
ota_trigger_start_ota();
|
||
else if (bits == DOWNLOAD_EMOJI_BIT)
|
||
ota_trigger_start_emoji(ble_get_emoji_download_target_pack());
|
||
}
|
||
}
|
||
static void defer_set_system_event(uint32_t bits)
|
||
{
|
||
s_defer_event_bits = bits;
|
||
if (s_defer_event_timer == NULL)
|
||
{
|
||
const esp_timer_create_args_t args = {
|
||
.callback = &deferred_event_timer_cb,
|
||
.arg = NULL,
|
||
.name = "ble_defer_evt",
|
||
};
|
||
if (esp_timer_create(&args, &s_defer_event_timer) != ESP_OK)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "[DEBUG] 创建延迟定时器失败,直接调用 set_system_event");
|
||
set_system_event(bits);
|
||
return;
|
||
}
|
||
}
|
||
ESP_LOGI("BLE_ANSY", "[DEBUG] 延迟投递事件 0x%lX,启动定时器", (unsigned long)bits);
|
||
esp_timer_start_once(s_defer_event_timer, 0); /* 0 = 尽快执行 */
|
||
}
|
||
|
||
static void time_temp_weather_updata(uint8_t timing, uint16_t temp, const char *weather);
|
||
static void ui_label_set_text_safe(lv_obj_t *obj, const char *text);
|
||
extern lv_ui guider_ui;
|
||
|
||
typedef struct
|
||
{
|
||
bool has_date;
|
||
bool has_time;
|
||
uint16_t year;
|
||
uint8_t month;
|
||
uint8_t day;
|
||
uint8_t week;
|
||
uint8_t hour;
|
||
uint8_t minute;
|
||
uint8_t second;
|
||
} ble_datetime_cache_t;
|
||
|
||
static ble_datetime_cache_t s_ble_datetime_cache = {0};
|
||
|
||
/** time_info 到达时刻(esp_timer 单调微秒),用于投递 UI 前按等待时长修正显示时刻 */
|
||
static int64_t s_ble_time_recv_us = 0;
|
||
static bool s_ble_time_recv_us_valid = false;
|
||
|
||
/** 投递前最多补偿的秒数,避免异常时间戳导致大步跳跃;正常上电等待远小于此值 */
|
||
#define BLE_TIME_LATENCY_COMP_MAX_SEC 300u
|
||
|
||
static void ble_time_mark_recv_mono_us(void)
|
||
{
|
||
s_ble_time_recv_us = esp_timer_get_time();
|
||
s_ble_time_recv_us_valid = true;
|
||
}
|
||
|
||
/* 将「帧内时分秒 + 自接收以来经过的整秒」折合到 0..86400-1,与 clock_count 的 24h 回绕一致。
|
||
* 返回值:实际加上的整秒数(未启用接收时间戳时为 0)。 */
|
||
static uint32_t ble_datetime_apply_latency_comp_to_hms(uint8_t *hour, uint8_t *minute, uint8_t *second)
|
||
{
|
||
if (!s_ble_time_recv_us_valid || hour == NULL || minute == NULL || second == NULL)
|
||
{
|
||
return 0u;
|
||
}
|
||
int64_t now_us = esp_timer_get_time();
|
||
int64_t d_us = now_us - s_ble_time_recv_us;
|
||
if (d_us < 0)
|
||
{
|
||
d_us = 0;
|
||
}
|
||
uint32_t add_sec = (uint32_t)(d_us / 1000000LL);
|
||
if (add_sec > BLE_TIME_LATENCY_COMP_MAX_SEC)
|
||
{
|
||
add_sec = BLE_TIME_LATENCY_COMP_MAX_SEC;
|
||
}
|
||
uint32_t base = (uint32_t)*hour * 3600u + (uint32_t)*minute * 60u + (uint32_t)*second;
|
||
uint32_t total = base + add_sec;
|
||
total %= (24u * 3600u);
|
||
*hour = (uint8_t)(total / 3600u);
|
||
*minute = (uint8_t)((total / 60u) % 60u);
|
||
*second = (uint8_t)(total % 60u);
|
||
return add_sec;
|
||
}
|
||
typedef struct
|
||
{
|
||
bool valid;
|
||
uint16_t temp;
|
||
char weather[32];
|
||
} ble_weather_slot_t;
|
||
|
||
static ble_weather_slot_t s_ble_weather_now = {0};
|
||
static ble_weather_slot_t s_ble_weather_plus2h = {0};
|
||
static ble_weather_slot_t s_ble_weather_plus4h = {0};
|
||
|
||
static bool ble_weather_ui_ready(void)
|
||
{
|
||
return (guider_ui.screen_home_weather_label_temp != NULL) &&
|
||
(guider_ui.screen_home_weather_img_1 != NULL) &&
|
||
(guider_ui.screen_home_weather_img_2 != NULL) &&
|
||
(guider_ui.screen_home_weather_img_3 != NULL) &&
|
||
(guider_ui.screen_home_weather_img_4 != NULL);
|
||
}
|
||
|
||
static ble_weather_slot_t *ble_weather_slot_by_timing(uint8_t timing)
|
||
{
|
||
if (timing == 0x00)
|
||
{
|
||
return &s_ble_weather_now;
|
||
}
|
||
if (timing == 0x02)
|
||
{
|
||
return &s_ble_weather_plus2h;
|
||
}
|
||
if (timing == 0x04)
|
||
{
|
||
return &s_ble_weather_plus4h;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void ble_weather_cache_update(uint8_t timing, uint16_t temp, const char *weather)
|
||
{
|
||
ble_weather_slot_t *slot = ble_weather_slot_by_timing(timing);
|
||
if (slot == NULL || weather == NULL)
|
||
{
|
||
return;
|
||
}
|
||
slot->temp = temp;
|
||
strncpy(slot->weather, weather, sizeof(slot->weather) - 1);
|
||
slot->weather[sizeof(slot->weather) - 1] = '\0';
|
||
slot->valid = true;
|
||
}
|
||
|
||
static bool ble_datetime_ui_ready(void)
|
||
{
|
||
return (guider_ui.screen_time_label_date != NULL) &&
|
||
(guider_ui.screen_time_label_week != NULL) &&
|
||
(guider_ui.screen_time_digital_clock_now != NULL);
|
||
}
|
||
|
||
static void ble_datetime_apply_cached_to_ui_if_ready(void)
|
||
{
|
||
if (s_ble_datetime_cache.has_date && ble_datetime_ui_ready())
|
||
{
|
||
if (ui_dispatch_set_date(s_ble_datetime_cache.year,
|
||
s_ble_datetime_cache.month,
|
||
s_ble_datetime_cache.day,
|
||
s_ble_datetime_cache.week)) {
|
||
ESP_LOGI("BLE_ANSY", "已投递缓存日期到 ui_dispatch: %04u-%02u-%02u",
|
||
(unsigned)s_ble_datetime_cache.year,
|
||
(unsigned)s_ble_datetime_cache.month,
|
||
(unsigned)s_ble_datetime_cache.day);
|
||
} else {
|
||
ESP_LOGW("BLE_ANSY", "日期未入队(ui_dispatch 未就绪或队列满): %04u-%02u-%02u",
|
||
(unsigned)s_ble_datetime_cache.year,
|
||
(unsigned)s_ble_datetime_cache.month,
|
||
(unsigned)s_ble_datetime_cache.day);
|
||
}
|
||
}
|
||
|
||
/* 时间须走 ui_dispatch_set_time:会更新全局时分秒并刷新时间页大时钟;
|
||
* 勿仅用 ui_cmd_weather_forecast_time_refresh(只刷首页 +2h/+4h 槽位,不改时间页)。 */
|
||
if (s_ble_datetime_cache.has_time)
|
||
{
|
||
const uint8_t h_frame = s_ble_datetime_cache.hour;
|
||
const uint8_t m_frame = s_ble_datetime_cache.minute;
|
||
const uint8_t s_frame = s_ble_datetime_cache.second;
|
||
uint8_t h = h_frame;
|
||
uint8_t m = m_frame;
|
||
uint8_t s = s_frame;
|
||
|
||
int64_t d_us = 0;
|
||
uint32_t raw_sec = 0;
|
||
bool comp_capped = false;
|
||
if (s_ble_time_recv_us_valid)
|
||
{
|
||
const int64_t now_us = esp_timer_get_time();
|
||
d_us = now_us - s_ble_time_recv_us;
|
||
if (d_us < 0)
|
||
{
|
||
d_us = 0;
|
||
}
|
||
raw_sec = (uint32_t)(d_us / 1000000LL);
|
||
comp_capped = (raw_sec > BLE_TIME_LATENCY_COMP_MAX_SEC);
|
||
}
|
||
|
||
const uint32_t add_sec = ble_datetime_apply_latency_comp_to_hms(&h, &m, &s);
|
||
ui_dispatch_set_time(h, m, s);
|
||
|
||
if (s_ble_time_recv_us_valid)
|
||
{
|
||
ESP_LOGI("BLE_ANSY",
|
||
"时间→ui_dispatch: 帧内=%02d:%02d:%02d | 接收→投递=%lld ms | 补偿整秒=%lu%s | 投递=%02d:%02d:%02d",
|
||
(int)h_frame, (int)m_frame, (int)s_frame, (long long)(d_us / 1000),
|
||
(unsigned long)add_sec, comp_capped ? "(封顶)" : "", (int)h, (int)m, (int)s);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "时间→ui_dispatch(无接收时间戳): 投递=%02d:%02d:%02d", (int)h, (int)m, (int)s);
|
||
}
|
||
}
|
||
}
|
||
|
||
void ble_datetime_flush_cached_to_ui(void)
|
||
{
|
||
ble_datetime_apply_cached_to_ui_if_ready();
|
||
}
|
||
|
||
void ble_weather_flush_cached_to_ui(void)
|
||
{
|
||
if (!ble_weather_ui_ready())
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (s_ble_weather_now.valid)
|
||
{
|
||
time_temp_weather_updata(0x00, s_ble_weather_now.temp, s_ble_weather_now.weather);
|
||
}
|
||
if (s_ble_weather_plus2h.valid)
|
||
{
|
||
time_temp_weather_updata(0x02, s_ble_weather_plus2h.temp, s_ble_weather_plus2h.weather);
|
||
}
|
||
if (s_ble_weather_plus4h.valid)
|
||
{
|
||
time_temp_weather_updata(0x04, s_ble_weather_plus4h.temp, s_ble_weather_plus4h.weather);
|
||
}
|
||
}
|
||
|
||
static void ui_label_set_text_safe(lv_obj_t *obj, const char *text)
|
||
{
|
||
if (obj == NULL || text == NULL) {
|
||
return;
|
||
}
|
||
/* 非UI线程:只投递命令,不直接调用LVGL */
|
||
ui_cmd_set_label_text(obj, text);
|
||
}
|
||
|
||
char ble_get_emoji_download_target_pack(void)
|
||
{
|
||
return s_emoji_download_target_pack;
|
||
}
|
||
|
||
int ble_data_ansy(uint8_t *input, size_t length)
|
||
{
|
||
uint16_t action_id;
|
||
if ((input == NULL) || (length == 0))
|
||
{
|
||
return -1;
|
||
}
|
||
else
|
||
{
|
||
/* 休眠态:不执行任何蓝牙指令(仍允许保持连接/写入,但业务逻辑全部丢弃) */
|
||
if (is_system_sleeping)
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "System sleeping, ignore BLE command frame (len=%d)", (int)length);
|
||
return 0;
|
||
}
|
||
|
||
// 打印接收到的完整数据帧(十六进制格式)
|
||
ESP_LOGI("BLE_ANSY", "解析蓝牙数据帧 [长度: %d]:", length);
|
||
char hex_str[512] = {0};
|
||
int pos = 0;
|
||
for (size_t i = 0; i < length && pos < sizeof(hex_str) - 4; i++)
|
||
{
|
||
pos += snprintf(hex_str + pos, sizeof(hex_str) - pos, "%02X ", input[i]);
|
||
}
|
||
ESP_LOGI("BLE_ANSY", "%s", hex_str);
|
||
|
||
if ((input[0] != 0XAA) || (input[length - 1] != 0X55))
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "帧头或帧尾错误: 帧头=0x%02X, 帧尾=0x%02X", input[0], input[length - 1]);
|
||
return -2;
|
||
}
|
||
else
|
||
{
|
||
uint16_t father_cmd = (uint16_t)input[1] << 8 | (uint16_t)input[2];
|
||
ESP_LOGI("BLE_ANSY", "Received frame: length=%d, father_cmd=0x%04X", length, father_cmd);
|
||
if (father_cmd != 0xFFFE)
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "收到非车机指令 father_cmd=0x%04X (期望 0xFFFE),已忽略", father_cmd);
|
||
return -3;
|
||
}
|
||
else
|
||
{
|
||
uint16_t son_cmd = (uint16_t)input[3] << 8 | (uint16_t)input[4];
|
||
uint16_t crc16_recv = (uint16_t)input[length - 3] << 8 | (uint16_t)input[length - 2];
|
||
uint16_t crc16_c = crc16_ccitt_calculate(input, length - 3);
|
||
/* 接收 CRC16:校验不通过则不执行后续处理 */
|
||
ESP_LOGI("BLE_ANSY", "接收 CRC16 计算值=0x%04X, 接收值=0x%04X (son_cmd=0x%04X)", crc16_c, crc16_recv, son_cmd);
|
||
if (crc16_c != crc16_recv)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "CRC16 校验不通过,丢弃本帧,不执行后续处理");
|
||
return -4;
|
||
}
|
||
ESP_LOGI("BLE_ANSY", "CRC16 校验通过");
|
||
/* 与触摸一致:有效车机帧刷新自动休眠倒计时 */
|
||
system_keep_alive();
|
||
if (!g_system_init_done)
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "系统初始化未完成,忽略 BLE 指令 son_cmd=0x%04X", son_cmd);
|
||
return 0;
|
||
}
|
||
if (lvgl_lottie_mindfulness_block_ble_for_ble())
|
||
{
|
||
ESP_LOGW("BLE_ANSY",
|
||
"mindfulness breath 播放中,丢弃 BLE 指令 son_cmd=0x%04X",
|
||
son_cmd);
|
||
return 0;
|
||
}
|
||
|
||
switch (son_cmd)
|
||
{
|
||
case play_sticker: // 播放表情
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: play_sticker (0x0001)");
|
||
/* 协议:
|
||
* 帧头(1) 类(2) 指令(2) 动作(Action_id,2) 方向(1) CRC16(2) 帧尾(1)
|
||
*/
|
||
if (length < 1 + 2 + 2 + 2 + 1 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "play_sticker 长度错误: length=%d, 期望>=11", (int)length);
|
||
break;
|
||
}
|
||
action_id = (uint16_t)input[5] << 8 | (uint16_t)input[6];
|
||
uint8_t direction = input[7];
|
||
ESP_LOGI("BLE_ANSY", "play_sticker action_id = 0x%04X, direction = 0x%02X", action_id, direction);
|
||
|
||
/* 500ms 内同一条模式指令只处理第一条(洗车/迎宾/露营/小憩/哨兵 guard·protect) */
|
||
if (is_filtered_play_sticker_mode(action_id))
|
||
{
|
||
uint32_t key = (uint32_t)play_sticker << 16 | action_id;
|
||
if (mode_instruction_same_within_500ms(key))
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "play_sticker 同条模式指令 0x%04X 在 500ms 内已处理过,本条忽略", action_id);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (ble_should_drop_hvac_after_rest(action_id))
|
||
{
|
||
ESP_LOGI("BLE_ANSY",
|
||
"play_sticker 忽略 0x%04X:小憩后 %dms 内抑制制冷/制热(避免覆盖 snap)",
|
||
action_id, REST_MODE_HVAC_SUPPRESS_MS);
|
||
break;
|
||
}
|
||
|
||
// 检查任务句柄是否有效
|
||
if (change_task_handle == NULL)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "change_task_handle is NULL!");
|
||
return -5;
|
||
}
|
||
|
||
/* 直通条件:
|
||
* - 0x001A/0x001B(均停止音乐循环):控制类必须立即生效
|
||
* - 7个驾驶模式表情:要求立即切换,不进入 pending 缓存
|
||
*/
|
||
bool is_pause_or_stop = (action_id == 0x001Au) || (action_id == 0x001Bu) || (action_id == emoji_event_welcome_mdoe);
|
||
/* 通话结束(0x000C)必须直通,否则在通话循环中会被 pending 卡住 */
|
||
bool is_call_hangup = (action_id == emoji_event_hangup_call);
|
||
bool is_drive_mode_immediate =
|
||
(action_id == emoji_event_welcome_mdoe) || /* 0x0007 运动模式 */
|
||
(action_id == emoji_event_welcome_mdoe) || /* 0x0008 经济模式 */
|
||
(action_id == emoji_event_welcome_mdoe) || /* 0x0009 个性模式 */
|
||
(action_id == emoji_event_welcome_mdoe) || /* 0x000A 雪地模式 */
|
||
(action_id == emoji_event_welcome_mdoe) || /* 0x000B 沙地模式 */
|
||
(action_id == emoji_event_welcome_mdoe) || /* 0x000C 越野模式 */
|
||
(action_id == emoji_event_welcome_mdoe); /* 0x000D 舒适模式 */
|
||
bool is_continuous_playing = is_playing_music || g_is_ktv_playing ||
|
||
lvgl_lottie_calling_active_for_ble() ||
|
||
lvgl_lottie_ai_respond_loop_active_for_ble();
|
||
|
||
if (is_pause_or_stop || is_call_hangup || is_drive_mode_immediate || is_continuous_playing)
|
||
{
|
||
if (!change_task_post_action(action_id))
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "直通指令失败: action_id=0x%04X", action_id);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI("BLE_STICKER_GUARD",
|
||
"DIRECT play_sticker=0x%04X (bypass pending, playing=%d, hangup=%d)",
|
||
action_id, (int)is_continuous_playing, (int)is_call_hangup);
|
||
ble_arm_rest_mode_hvac_suppress_if_needed(action_id);
|
||
}
|
||
const bool skip_motor = lvgl_lottie_should_skip_play_sticker_motor(action_id, direction);
|
||
/* 直通分支也要执行方向控制:否则音乐/KTV可打断场景下,带方向指令会丢电机动作 */
|
||
if (!skip_motor && direction <= 0x0A)
|
||
{
|
||
if (!motor_post_play_sticker_direction(direction))
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "直通分支电机入队失败: direction=0x%02X", direction);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "直通分支电机入队成功: direction=0x%02X", direction);
|
||
}
|
||
}
|
||
else if (skip_motor)
|
||
{
|
||
if (action_id == emoji_event_AI_respond && lvgl_lottie_ai_respond_loop_active_for_ble())
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "直通分支跳过方向下发: AI stop 由回默认流程统一回中");
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI("BLE_ANSY",
|
||
"直通分支跳过方向: AI 循环或插播恢复挂起且 direction=0,避免误回中");
|
||
}
|
||
}
|
||
}
|
||
else if (g_ble_play_sticker_active)
|
||
{
|
||
g_ble_play_sticker_pending_action_id = action_id;
|
||
g_ble_play_sticker_pending_direction = direction;
|
||
g_ble_play_sticker_pending_valid = true;
|
||
ble_arm_rest_mode_hvac_suppress_if_needed(action_id);
|
||
ESP_LOGI("BLE_STICKER_GUARD",
|
||
"QUEUE pending play_sticker=0x%04X dir=0x%02X (current playing, keep latest only)",
|
||
action_id, direction);
|
||
}
|
||
else
|
||
{
|
||
// 将 action_id 直接透传给表情/动作任务
|
||
// 使用 eSetValueWithOverwrite:始终写入最新的 action_id
|
||
// change_task 中用 xTaskNotifyWait(0, ULONG_MAX, &bits, ...) 每次取出并清空通知值
|
||
if (!change_task_post_action(action_id))
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "xTaskNotify failed for change_task_handle");
|
||
}
|
||
else
|
||
{
|
||
g_ble_play_sticker_active = true;
|
||
ESP_LOGI("BLE_STICKER_GUARD",
|
||
"SET g_ble_play_sticker_active=true (play_sticker notify ok, action_id=0x%04X)",
|
||
action_id);
|
||
ESP_LOGI("BLE_ANSY", "Successfully notified change_task with action_id=0x%04X", action_id);
|
||
ble_arm_rest_mode_hvac_suppress_if_needed(action_id);
|
||
}
|
||
|
||
/* 按协议方向控制电机;与直通分支同一套跳过规则 */
|
||
{
|
||
const bool skip_motor = lvgl_lottie_should_skip_play_sticker_motor(action_id, direction);
|
||
if (!skip_motor)
|
||
{
|
||
if (!motor_post_play_sticker_direction(direction))
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "电机入队失败 direction=0x%02X", direction);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "Successfully notified change_task with action_id=0x%04X", action_id);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "跳过方向: play_sticker 电机规则(含 direction=0 防误回中)");
|
||
}
|
||
}
|
||
}
|
||
|
||
ESP_LOGI("BLE_ANSY", "play_sticker processing completed");
|
||
break;
|
||
case sentinel_mode: // 哨兵模式
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: sentinel_mode (0x0002)");
|
||
/* 协议:
|
||
* 帧头(1) 类(2) 指令(2) 动作(Action_id,2) CRC16(2) 帧尾(1)
|
||
*/
|
||
if (length < 1 + 2 + 2 + 2 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "sentinel_mode 长度错误: length=%d, 期望>=10", (int)length);
|
||
break;
|
||
}
|
||
action_id = (uint16_t)input[5] << 8 | (uint16_t)input[6];
|
||
ESP_LOGI("BLE_ANSY", "sentinel_mode action_id = 0x%04X", action_id);
|
||
/* 主动开启哨兵(0x0003):500ms 内同一条只处理第一条 */
|
||
if (action_id == 0x0003)
|
||
{
|
||
uint32_t key = (uint32_t)sentinel_mode << 16 | 0x0003u;
|
||
if (mode_instruction_same_within_500ms(key))
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "sentinel_mode 打开哨兵(0x0003) 在 500ms 内已处理过,本条忽略");
|
||
break;
|
||
}
|
||
}
|
||
if (action_id == 0x0001)
|
||
{
|
||
xTaskNotify(sentinel_task_handle, SENTINEL_LISTEN_START_PULSE, eSetBits);
|
||
}
|
||
else if (action_id == 0x0002)
|
||
{
|
||
/* 停止监听脉冲:若正在哨兵模式则直接关闭哨兵 */
|
||
xTaskNotify(sentinel_task_handle, SENTINEL_LISTEN_STOP_PULSE | SENTINEL_TURN_OFF, eSetBits);
|
||
}
|
||
else if (action_id == 0x0003)
|
||
{
|
||
xTaskNotify(sentinel_task_handle, SENTINEL_TURN_ON, eSetBits);
|
||
}
|
||
else if (action_id == 0x0004)
|
||
{
|
||
xTaskNotify(sentinel_task_handle, SENTINEL_TURN_OFF, eSetBits);
|
||
}
|
||
break;
|
||
case exit_all_modes: /* 1.4.1 退出所有模式:仅退出哨兵模式,不处理持续播放音乐等(哨兵任务已禁用) */
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: exit_all_modes (0x0003) 退出所有模式(仅哨兵)");
|
||
// if (sentinel_task_handle)
|
||
// xTaskNotify(sentinel_task_handle, SENTINEL_TURN_OFF, eSetBits);
|
||
break;
|
||
case reboot: /* 0x0022 远程重启已关闭,仅吞帧避免误动作 */
|
||
// ESP_LOGI("BLE_ANSY", ">>> 收到指令: reboot (0x0022) 重启设备");
|
||
// ESP_LOGI("BLE_ANSY", "设备将重启...");
|
||
// vTaskDelay(500 / portTICK_PERIOD_MS);
|
||
// esp_restart();
|
||
break;
|
||
case set_brightness: // 设置亮度
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: set_brightness (0x0004)");
|
||
/* 协议:
|
||
* 帧头(1) 类(2) 指令(2) 亮度(2, 0-100) CRC16(2) 帧尾(1)
|
||
*/
|
||
if (length < 1 + 2 + 2 + 2 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "set_brightness 长度错误: length=%d", (int)length);
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
uint16_t brightness = (uint16_t)input[5] << 8 | (uint16_t)input[6];
|
||
if (brightness > 100)
|
||
{
|
||
brightness = 100;
|
||
}
|
||
/* 走系统统一接口,确保 BLE 设置与查询、UI、NVS 使用同一真源 */
|
||
set_system_brightness((int)brightness);
|
||
ESP_LOGI("BLE_ANSY", "亮度(十进制)=%u (0-100)", brightness);
|
||
}
|
||
break;
|
||
case date_info: // 日期信息
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: date_info (0x0005)");
|
||
/* 协议:
|
||
* 帧头(1) 类(2) 指令(2) 年(2) 月(1) 日(1) 星期(1) CRC16(2) 帧尾(1)
|
||
* 例如:2026-01-28 -> 0x07EA 0x01 0x1C 星期四0x04
|
||
*/
|
||
if (length < 1 + 2 + 2 + 2 + 1 + 1 + 2 + 1 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "date_info 长度错误: length=%d, 期望>=13", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
uint16_t year = (uint16_t)input[5] << 8 | (uint16_t)input[6];
|
||
uint8_t month = input[7];
|
||
uint8_t day = input[8];
|
||
uint8_t week = input[9];
|
||
s_ble_datetime_cache.has_date = true;
|
||
s_ble_datetime_cache.year = year;
|
||
s_ble_datetime_cache.month = month;
|
||
s_ble_datetime_cache.day = day;
|
||
s_ble_datetime_cache.week = week;
|
||
ESP_LOGI("BLE_ANSY", "日期: %04d年%02d月%02d日", year, month, day);
|
||
ble_datetime_apply_cached_to_ui_if_ready();
|
||
}
|
||
break;
|
||
case time_info: // 时间信息
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: time_info (0x0006)");
|
||
/* 协议:
|
||
* 帧头(1) 类(2) 指令(2) 时(1) 分(1) 秒(1) CRC16(2) 帧尾(1)
|
||
*/
|
||
if (length < 1 + 2 + 2 + 1 + 1 + 1 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "time_info 长度错误: length=%d, 期望>=11", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
uint8_t hour = input[5];
|
||
uint8_t min = input[6];
|
||
uint8_t sec = input[7];
|
||
s_ble_datetime_cache.has_time = true;
|
||
s_ble_datetime_cache.hour = hour;
|
||
s_ble_datetime_cache.minute = min;
|
||
s_ble_datetime_cache.second = sec;
|
||
ble_time_mark_recv_mono_us();
|
||
ESP_LOGI("BLE_ANSY", "时间: %02d时%02d分%02d秒", (int)hour, (int)min, (int)sec);
|
||
ble_datetime_apply_cached_to_ui_if_ready();
|
||
}
|
||
break;
|
||
// case city_info: // 城市信息
|
||
// ESP_LOGI("BLE_ANSY", ">>> 收到指令: city_info (0x0007)");
|
||
// /* 协议:帧头(1) 类(2) 指令(2) 城市名称长度(2) 城市名(x,UTF-8) CRC16(2) 帧尾(1),x 由长度字段指定且不能为0 */
|
||
// if (length < 1 + 2 + 2 + 2 + 1 + 2 + 1)
|
||
// {
|
||
// ESP_LOGE("BLE_ANSY", "city_info 长度错误: length=%d, 至少11字节", (int)length);
|
||
// break;
|
||
// }
|
||
// {
|
||
// uint16_t name_len = (uint16_t)input[5] << 8 | (uint16_t)input[6];
|
||
// if (name_len == 0)
|
||
// {
|
||
// ESP_LOGE("BLE_ANSY", "city_info 城市名长度不能为0");
|
||
// break;
|
||
// }
|
||
// if (1 + 2 + 2 + 2 + name_len + 2 + 1 != length)
|
||
// {
|
||
// ESP_LOGE("BLE_ANSY", "city_info 长度不匹配: name_len=%u, frame_len=%d", (unsigned)name_len, (int)length);
|
||
// break;
|
||
// }
|
||
// char city_name[256] = {0};
|
||
// size_t cpy_len = name_len;
|
||
// if (cpy_len >= sizeof(city_name))
|
||
// cpy_len = sizeof(city_name) - 1;
|
||
// memcpy(city_name, &input[7], cpy_len);
|
||
// ESP_LOGI("BLE_ANSY", "城市: %s", city_name);
|
||
// ui_label_set_text_safe(guider_ui.screen_home_weather_label_city1, city_name); // 城市
|
||
// ui_label_set_text_safe(guider_ui.screen_time_label_city2, city_name); // 城市
|
||
// }
|
||
// break;
|
||
case w_t_info: // 天气温度信息
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: w_t_info 天气温度 (0x0008)");
|
||
/* 协议:帧头(1) 类(2) 指令(2) 时机(1) 温度(2) 天气长度(2) 天气(x,UTF-8) CRC16(2) 帧尾(1),x 由长度指定且不能为0 */
|
||
if (length < 1 + 2 + 2 + 1 + 2 + 2 + 1 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "w_t_info 长度错误: length=%d, 至少14字节", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
uint8_t timing = input[5];
|
||
uint16_t temp_enc = (uint16_t)input[6] << 8 | (uint16_t)input[7];
|
||
uint16_t wlen = (uint16_t)input[8] << 8 | (uint16_t)input[9];
|
||
if (wlen == 0)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "w_t_info 天气长度不能为0");
|
||
break;
|
||
}
|
||
if (1 + 2 + 2 + 1 + 2 + 2 + wlen + 2 + 1 != length)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "w_t_info 长度不匹配: wlen=%u, frame_len=%d", (unsigned)wlen, (int)length);
|
||
break;
|
||
}
|
||
float temp = ((int)temp_enc - 1000) / 10.0f;
|
||
char weather[32] = {0};
|
||
size_t cpy_len = wlen;
|
||
if (cpy_len >= sizeof(weather))
|
||
cpy_len = sizeof(weather) - 1;
|
||
memcpy(weather, &input[10], cpy_len);
|
||
weather[cpy_len] = '\0';
|
||
ESP_LOGI("BLE_ANSY", "天气: 时机(十进制)=%u, 温度(十进制)=%.1f℃, 天气=%s", (unsigned)timing, (double)temp, weather);
|
||
time_temp_weather_updata(timing, temp_enc, weather);
|
||
}
|
||
break;
|
||
case wifi_config: // WiFi配置
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: wifi_config (0x0009)");
|
||
// notify_wifi_provision_signal_received(); // WiFi配网守卫功能已停用
|
||
/* 协议:帧头(1) 类(2) 指令(2) Wifi_info(1) 长度(2) SSID/密码(x) CRC16(2) 帧尾(1),x 由长度指定且不能为0 */
|
||
if (length < 1 + 2 + 2 + 1 + 2 + 1 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "wifi_config 长度错误: length=%d, 至少12字节", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
uint8_t info_type = input[5];
|
||
uint16_t slen = (uint16_t)input[6] << 8 | (uint16_t)input[7];
|
||
if (slen == 0)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "wifi_config 数据长度不能为0");
|
||
break;
|
||
}
|
||
if (1 + 2 + 2 + 1 + 2 + slen + 2 + 1 != length)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "wifi_config 长度不匹配: slen=%u, frame_len=%d", (unsigned)slen, (int)length);
|
||
break;
|
||
}
|
||
static char ssid_buf[32] = {0};
|
||
static char pwd_buf[64] = {0};
|
||
if (info_type == 0x00)
|
||
{
|
||
size_t cpy = slen;
|
||
if (cpy >= sizeof(ssid_buf))
|
||
cpy = sizeof(ssid_buf) - 1;
|
||
memset(ssid_buf, 0, sizeof(ssid_buf));
|
||
memcpy(ssid_buf, &input[8], cpy);
|
||
ESP_LOGI("BLE_ANSY", "WiFi配网 SSID: %s", ssid_buf);
|
||
}
|
||
else if (info_type == 0x01)
|
||
{
|
||
size_t cpy = slen;
|
||
if (cpy >= sizeof(pwd_buf))
|
||
cpy = sizeof(pwd_buf) - 1;
|
||
memset(pwd_buf, 0, sizeof(pwd_buf));
|
||
memcpy(pwd_buf, &input[8], cpy);
|
||
ESP_LOGI("BLE_ANSY", "WiFi配网 密码: %s", pwd_buf);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "wifi_config 未知 Wifi_info=0x%02X", info_type);
|
||
break;
|
||
}
|
||
|
||
if (ssid_buf[0] != '\0' && pwd_buf[0] != '\0')
|
||
{
|
||
/* 已连网场景下再次收到配网信息:立即回包告知“已连接”,
|
||
* 并跳过重复发起 wifi_main,避免无意义重连导致业务抖动。 */
|
||
if (wifi_is_connected_now())
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "WiFi 已连接,收到配网信息后立即回 1.10_A=0x01,跳过重复配网");
|
||
ble_send_wifi_state_request(1);
|
||
}
|
||
else
|
||
{
|
||
wifi_main(ssid_buf, pwd_buf);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case ota_request: // OTA请求
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: ota_request (0x000A)");
|
||
/* 指令帧 AA FF FE 00 0A ...:仅在 WiFi 已连接时才允许进入 OTA 流程 */
|
||
if (!wifi_is_connected_now())
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "WiFi 未连接,舍弃 ota_request 指令");
|
||
break;
|
||
}
|
||
ESP_LOGI("BLE_ANSY", "[DEBUG] 准备延迟投递 OTA_UPDATE_STARTED_BIT");
|
||
defer_set_system_event(OTA_UPDATE_STARTED_BIT);
|
||
ESP_LOGI("BLE_ANSY", "[DEBUG] defer_set_system_event 已调用");
|
||
break;
|
||
case ota_cancel: /* 1.13 OTA 升级取消:帧头(1)+类(2)+指令(2)+CRC16(2)+帧尾(1)=9 字节 */
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: ota_cancel (0x000C) OTA 升级取消");
|
||
if (length < 1 + 2 + 2 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "ota_cancel 长度错误: length=%d, 期望9字节", (int)length);
|
||
break;
|
||
}
|
||
if (ota_upgrade_cancel() == ESP_OK)
|
||
ESP_LOGI("BLE_ANSY", "OTA 升级已取消");
|
||
else
|
||
ESP_LOGW("BLE_ANSY", "OTA 取消失败或当前无升级任务");
|
||
break;
|
||
case emoji_pack_download: /* 1.12 表情包下载:帧头(1) 类(2) 指令(2) Target_pack(2) CRC16(2) 帧尾(1),共10字节;0x00 0x42 即 B */
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: emoji_pack_download (0x000B)");
|
||
if (g_emoji_download_in_progress)
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "表情包下载进行中,忽略新的 download 指令");
|
||
break;
|
||
}
|
||
if (length < 1 + 2 + 2 + 2 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "emoji_pack_download 长度错误: length=%d, 期望10字节", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
/* Target_pack 占 2 字节:input[5]=高字节 0x00, input[6]=低字节 ASCII 'A'~'D',如 0x42='B' -> B.zip */
|
||
char target_pack = (char)input[6];
|
||
s_emoji_download_target_pack = target_pack;
|
||
ESP_LOGI("BLE_ANSY", "表情包下载 目标系列: %c", target_pack);
|
||
ESP_LOGI("BLE_ANSY", "[DEBUG] 准备延迟投递 DOWNLOAD_EMOJI_BIT");
|
||
defer_set_system_event(DOWNLOAD_EMOJI_BIT);
|
||
}
|
||
break;
|
||
case get_sys_info:
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: get_sys_info (0x0010)");
|
||
// 触发系统信息上报(版本、亮度等),直接复用 BLE_SEND_VERSION 的逻辑
|
||
if (notification_task_handle)
|
||
{
|
||
xTaskNotify(notification_task_handle, BLE_SEND_VERSION, eSetBits);
|
||
}
|
||
break;
|
||
case clear_emojo:
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: clear_emojo 清除表情包 (0x0021)");
|
||
if (notification_task_handle)
|
||
{
|
||
xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_CLEAR_RESULT, eSetBits);
|
||
}
|
||
break;
|
||
case speed_gear:
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: speed_gear (0x000D) 档位与速度");
|
||
/* V2:AA FF FE 00 0D 档位(1) 速度高 速度低 CRC16 55;速度大端,单位 KM/h */
|
||
if (length < 1 + 2 + 2 + 1 + 2 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "speed_gear 长度错误: length=%d, 期望>=11", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
uint8_t gear = input[5];
|
||
uint16_t speed_kmh = (uint16_t)input[6] << 8 | (uint16_t)input[7];
|
||
const char *gear_name = "未知";
|
||
switch (gear)
|
||
{
|
||
case 0x00:
|
||
gear_name = "P";
|
||
break;
|
||
case 0x01:
|
||
gear_name = "R";
|
||
break;
|
||
case 0x02:
|
||
gear_name = "N";
|
||
break;
|
||
case 0x03:
|
||
gear_name = "D";
|
||
break;
|
||
case 0x04:
|
||
gear_name = "L";
|
||
break;
|
||
case 0x05:
|
||
gear_name = "S";
|
||
break;
|
||
case 0x06:
|
||
gear_name = "M";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
ESP_LOGI("BLE_ANSY",
|
||
"车机档位: 0x%02X (%s), 速度: %u KM/h (raw 0x%04X)",
|
||
(unsigned)gear, gear_name, (unsigned)speed_kmh, (unsigned)speed_kmh);
|
||
}
|
||
break;
|
||
case emojo_cmd:
|
||
ESP_LOGI("BLE_ANSY", ">>> 收到指令: emojo_cmd 表情包 (0x0020)");
|
||
if (length < 1 + 2 + 2 + 1 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "emojo_cmd 长度错误: length=%d", (int)length);
|
||
break;
|
||
}
|
||
{
|
||
uint8_t op = input[5];
|
||
if (op == 0x00)
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "表情包 查询已安装");
|
||
if (notification_task_handle)
|
||
xTaskNotify(notification_task_handle, BLE_SEND_DOWNLOADED_PACKS, eSetBits);
|
||
}
|
||
else if (op == 0x01)
|
||
{
|
||
ESP_LOGI("BLE_ANSY", "表情包 查询正在使用");
|
||
if (notification_task_handle)
|
||
xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_USING, eSetBits);
|
||
}
|
||
else if (op == 0x02)
|
||
{
|
||
char target_pack = (length >= 1 + 2 + 2 + 1 + 1 + 2 + 1) ? (char)input[6] : '0';
|
||
ESP_LOGI("BLE_ANSY", "表情包 切换到: %c", target_pack);
|
||
ble_set_emoji_switch_target(target_pack);
|
||
if (notification_task_handle)
|
||
xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_EXCHANGE_RESULT, eSetBits);
|
||
/* 切换包后立即可视化生效:直接播放目标包 default.json,
|
||
* 避免必须再额外发送 play_sticker 指令。 */
|
||
{
|
||
char default_path[32] = {0};
|
||
if (target_pack == 'A' || target_pack == 'B' || target_pack == 'C' || target_pack == 'D' || target_pack == '0')
|
||
{
|
||
snprintf(default_path, sizeof(default_path), "/extflash/%c/default.json", target_pack);
|
||
request_play_lottie_path(default_path);
|
||
ESP_LOGI("BLE_ANSY", "表情包切换后立即播放: %s", default_path);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "表情包切换目标非法: 0x%02X", (unsigned char)target_pack);
|
||
}
|
||
}
|
||
}
|
||
else if (op == 0x03) /* 3.4 删除指定的表情包:帧头(1)+类(2)+指令(2)+op(1)+Target_pack(1字节起)+CRC16(2)+帧尾(1),取首字节为 pack_id */
|
||
{
|
||
if (length < 1 + 2 + 2 + 1 + 1 + 2 + 1)
|
||
{
|
||
ESP_LOGE("BLE_ANSY", "emojo_cmd 删除 长度错误: length=%d, 至少10字节", (int)length);
|
||
break;
|
||
}
|
||
char target_pack = (char)input[6];
|
||
ESP_LOGI("BLE_ANSY", "表情包 删除: %c", target_pack);
|
||
ble_set_emoji_delete_target(target_pack);
|
||
if (notification_task_handle)
|
||
xTaskNotify(notification_task_handle, BLE_SEND_EMOJI_DELETE_RESULT, eSetBits);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "emojo_cmd 未知操作 op=0x%02X", op);
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
ESP_LOGW("BLE_ANSY", "未处理指令 son_cmd=0x%04X", son_cmd);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static void ui_image_set_src_safe(lv_obj_t *obj, const void *src)
|
||
{
|
||
if (obj == NULL || src == NULL)
|
||
{
|
||
return;
|
||
}
|
||
ui_cmd_set_image_src(obj, src);
|
||
}
|
||
|
||
/**
|
||
* 根据天气文案匹配图标(60x50尺寸)
|
||
* 支持8种天气类型:
|
||
* 1. 晴 → sun
|
||
* 2. 多云/阴 → cloudy
|
||
* 3. 雾/霾/浮尘/沙尘暴 → fog
|
||
* 4. 雨(小雨、中雨、暴雨、阵雨)→ rainy
|
||
* 5. 雷阵雨 → thunder
|
||
* 6. 雪(小雪、中雪、暴雪、阵雪)→ snow
|
||
* 7. 雨夹雪/冻雨 → sleet
|
||
* 8. 冰雹 → hail
|
||
*/
|
||
static const void *weather_text_to_img_src(const char *weather)
|
||
{
|
||
if (weather == NULL || weather[0] == '\0')
|
||
{
|
||
return &_sun_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级1: 雾霾沙尘类(雾/霾/浮尘/沙尘暴) */
|
||
if (strstr(weather, "雾") != NULL || strstr(weather, "霾") != NULL ||
|
||
strstr(weather, "浮尘") != NULL || strstr(weather, "沙尘") != NULL)
|
||
{
|
||
return &_fog_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级2: 冰雹(独立图标) */
|
||
if (strstr(weather, "冰雹") != NULL)
|
||
{
|
||
return &_hail_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级3: 雨夹雪/冻雨(混合降水) */
|
||
if (strstr(weather, "雨夹雪") != NULL || strstr(weather, "冻雨") != NULL)
|
||
{
|
||
return &_sleet_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级4: 雪类(小雪/中雪/大雪/暴雪/阵雪) */
|
||
if (strstr(weather, "雪") != NULL)
|
||
{
|
||
return &_snow_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级5: 雷阵雨(独立图标) */
|
||
if (strstr(weather, "雷") != NULL)
|
||
{
|
||
return &_thunder_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级6: 雨类(小雨/中雨/大雨/暴雨/阵雨) */
|
||
if (strstr(weather, "雨") != NULL)
|
||
{
|
||
return &_rainy_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级7: 多云/阴 */
|
||
if (strstr(weather, "云") != NULL || strstr(weather, "阴") != NULL)
|
||
{
|
||
return &_cloudy_RGB565_55x55;
|
||
}
|
||
|
||
/* 优先级8: 晴 */
|
||
if (strstr(weather, "晴") != NULL)
|
||
{
|
||
return &_sun_RGB565_55x55;
|
||
}
|
||
|
||
/* 默认: 晴天图标 */
|
||
return &_sun_RGB565_55x55;
|
||
}
|
||
|
||
/**
|
||
* 根据天气文案匹配大尺寸图标(130x130 RLE)
|
||
* 匹配逻辑与 weather_text_to_img_src 保持一致
|
||
*/
|
||
static const void *weather_text_to_img_src_130(const char *weather)
|
||
{
|
||
if (weather == NULL || weather[0] == '\0')
|
||
{
|
||
return &_sun_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级1: 雾霾沙尘类 */
|
||
if (strstr(weather, "雾") != NULL || strstr(weather, "霾") != NULL ||
|
||
strstr(weather, "浮尘") != NULL || strstr(weather, "沙尘") != NULL)
|
||
{
|
||
return &_fog_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级2: 冰雹 */
|
||
if (strstr(weather, "冰雹") != NULL)
|
||
{
|
||
return &_hail_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级3: 雨夹雪/冻雨 */
|
||
if (strstr(weather, "雨夹雪") != NULL || strstr(weather, "冻雨") != NULL)
|
||
{
|
||
return &_sleet_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级4: 雪类 */
|
||
if (strstr(weather, "雪") != NULL)
|
||
{
|
||
return &_snow_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级5: 雷阵雨 */
|
||
if (strstr(weather, "雷") != NULL)
|
||
{
|
||
return &_thunder_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级6: 雨类 */
|
||
if (strstr(weather, "雨") != NULL)
|
||
{
|
||
return &_rainy_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级7: 多云/阴 */
|
||
if (strstr(weather, "云") != NULL || strstr(weather, "阴") != NULL)
|
||
{
|
||
return &_cloudy_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 优先级8: 晴 */
|
||
if (strstr(weather, "晴") != NULL)
|
||
{
|
||
return &_sun_RGB565_130x130_RLE;
|
||
}
|
||
|
||
/* 默认: 晴天图标 */
|
||
return &_sun_RGB565_130x130_RLE;
|
||
}
|
||
|
||
static void time_temp_weather_updata(uint8_t timing, uint16_t temp, const char *weather)
|
||
{
|
||
ble_weather_cache_update(timing, temp, weather);
|
||
if (!ble_weather_ui_ready())
|
||
{
|
||
return;
|
||
}
|
||
|
||
const void *wimg = weather_text_to_img_src(weather);
|
||
const void *wimg_130 = weather_text_to_img_src_130(weather);
|
||
|
||
|
||
if (timing == 0x00)
|
||
{
|
||
int temp_tenth = (int)temp - 1000;
|
||
char temp_string[20] = {0};
|
||
int deg;
|
||
if (temp_tenth >= 0) {
|
||
deg = (temp_tenth + 5) / 10;
|
||
} else {
|
||
deg = -((-temp_tenth + 5) / 10);
|
||
}
|
||
snprintf(temp_string, sizeof(temp_string), "%d", deg);
|
||
ui_label_set_text_safe(guider_ui.screen_home_weather_label_temp, temp_string);
|
||
ui_image_set_src_safe(guider_ui.screen_home_weather_img_1, wimg_130);
|
||
ui_image_set_src_safe(guider_ui.screen_home_weather_img_2, wimg);
|
||
}
|
||
else if (timing == 0x02)
|
||
{
|
||
ui_image_set_src_safe(guider_ui.screen_home_weather_img_3, wimg);
|
||
}
|
||
else if (timing == 0x04)
|
||
{
|
||
ui_image_set_src_safe(guider_ui.screen_home_weather_img_4, wimg);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
int ble_wv_data_ansy2(uint8_t *input, size_t length){
|
||
if ((input == NULL) || (length < 5))
|
||
{
|
||
return -1;
|
||
}
|
||
else
|
||
{
|
||
/* 休眠态:不执行任何蓝牙指令(仍允许保持连接/写入,但业务逻辑全部丢弃) */
|
||
if (is_system_sleeping)
|
||
{
|
||
ESP_LOGW("BLE_ANSY", "System sleeping, ignore BLE command frame (len=%d)", (int)length);
|
||
return 0;
|
||
}
|
||
ESP_LOGI("BLE_ANSY", "解析蓝牙数据帧 [长度: %d]:", length);
|
||
uint32_t event_id = 0;
|
||
for (int i = 0; i < 5; i++) {
|
||
// 确保是数字字符,防止非法数据算出奇怪的值
|
||
if (input[i] < '0' || input[i] > '9') {
|
||
ESP_LOGW("TAG", "包含非数字字符,丢弃");
|
||
return 0;
|
||
}
|
||
event_id = event_id * 10 + (input[i] - '0');
|
||
}
|
||
switch (event_id) {
|
||
case 10301:
|
||
ESP_LOGI("BLE_ANSY", "导航出发");
|
||
//change_task_post_action(0x00);
|
||
//电机指令
|
||
break;
|
||
case 20201:
|
||
ESP_LOGI("BLE_ANSY", "小憩模式(非沉浸态)");
|
||
break;
|
||
case 20202:
|
||
ESP_LOGI("BLE_ANSY", "小憩模式(沉浸态)");
|
||
break;
|
||
case 20203:
|
||
ESP_LOGI("BLE_ANSY", "露营模式");
|
||
break;
|
||
case 20204:
|
||
ESP_LOGI("BLE_ANSY", "大床模式");
|
||
break;
|
||
case 20205:
|
||
ESP_LOGI("BLE_ANSY", "通用模式");
|
||
break;
|
||
case 40306:
|
||
ESP_LOGI("BLE_ANSY", "下雨天气");
|
||
break;
|
||
case 40307:
|
||
ESP_LOGI("BLE_ANSY", "下雪天气");
|
||
break;
|
||
case 40308:
|
||
ESP_LOGI("BLE_ANSY", "遥控泊车(RHP)");
|
||
break;
|
||
case 40309:
|
||
ESP_LOGI("BLE_ANSY", "车辆普通状态");
|
||
break;
|
||
case 10010:
|
||
ESP_LOGI("BLE_ANSY", "打开空调面板");
|
||
break;
|
||
case 40311:
|
||
ESP_LOGI("BLE_ANSY", "关闭空调面板");
|
||
break;
|
||
case 40312:
|
||
ESP_LOGI("BLE_ANSY", "表情特效");
|
||
break;
|
||
case 40313:
|
||
ESP_LOGI("BLE_ANSY", "电台播放");
|
||
break;
|
||
case 40314:
|
||
ESP_LOGI("BLE_ANSY", "收藏音乐");
|
||
break;
|
||
case 40315:
|
||
ESP_LOGI("BLE_ANSY", "到达目的地");
|
||
break;
|
||
default:
|
||
ESP_LOGW("BLE_ANSY", "未知指令: %d", event_id);
|
||
break;
|
||
}
|
||
}
|
||
return 1;
|
||
} |