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

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

1272 lines
54 KiB
C
Raw Permalink 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 <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;
}