#include #include #include #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; }