/****************************************************************************** * @file app_led.c * @brief CBC2 灯效适配:充电红与待机绿可同时亮,不再 if-else 互斥 * @author cyWu <1917507415@qq.com> * @date 2026.09.10 * @version V2.5.0 * @history * - V1.0.0, 2026.08.21, cyWu, 首次发布 * - V1.1.0, 2026.08.25, cyWu, 开机绿灯只提示几秒 * - V2.0.0, 2026.08.25, cyWu, 改用 LED 事件表,红绿通道独立 * - V2.1.0, 2026.08.25, cyWu, 自己从 app_power/app_pair 取数并内部组装 * - V2.2.0, 2026.08.25, cyWu, 重启/清场逻辑各提一个小函数,把意图写进注释 * - V2.3.0, 2026.08.27, cyWu, 新增 OTA 红绿交替灯效 * - V2.4.0, 2026.08.27, cyWu, 新增空电关机红闪 5 下灯效 * - V2.5.0, 2026.09.10, cyWu, 切模式时保留待机绿,闪完不再多一闪 ******************************************************************************/ #include "app_led.h" #include "led_manager.h" #include "bsp_hw.h" #include "board_pin.h" #include "app_power.h" #include "app_pair.h" static uint8_t s_initialized; static uint8_t s_ota; /* 1=OTA 灯效中,app_led_run 不再叠其它灯 */ static uint8_t s_empty_off; /* 1=空电关机红闪中,只跑红灯 5 下 */ static void led_start_pair_result(uint8_t ok); /** * @brief 重新播放一次「一次性/限时」灯效,让它从第 0 帧重新开始 * @note led.c 的 LED_EventAdd 只有在“该优先级槽位当前不是这个事件”时才会把 * currentTime 重置为现在;如果动画正播到一半直接再 Add 一次,事件已经 * 占着槽位,不会重置,会接着上次的进度走。所以要先 Delete 清空槽位, * 再 Add 才能强制重新计时——这不是多余代码,是“重启”这个事件的手段。 * @param evt 要重启的事件(切模式闪、开机绿灯提示、配对结果) * @return 无 */ static void led_restart(LED_Event_t evt) { LED_EventDelete(evt); LED_EventAdd(evt); } /** * @brief 绿灯正被切模式/配对结果占用(不含配对快闪本身) * @return 1=绿灯忙 */ static uint8_t green_pattern_busy(void) { return (uint8_t)(LED_IsEventExist(LED_EVENT_MODE) || LED_IsEventExist(LED_EVENT_PAIR_OK) || LED_IsEventExist(LED_EVENT_PAIR_FAIL)); } /** * @brief 灯效初始化 * @return APP_LED_OK=成功 */ AppLed_Ret_t app_led_init(void) { if (s_initialized) { return APP_LED_OK; } user_led_init(); s_initialized = 1; return APP_LED_OK; } /** * @brief 查询模块是否已初始化 * @return APP_LED_OK=已初始化,APP_LED_ERR_NOT_INIT=未初始化 */ AppLed_Ret_t app_led_get_status(void) { return s_initialized ? APP_LED_OK : APP_LED_ERR_NOT_INIT; } /** * @brief 请求切模式绿灯闪两下(与配对灯同优先级,会盖住配对闪) * @return 无 */ void app_led_request_mode_blink(void) { if (!s_initialized || s_ota || s_empty_off) { return; } led_restart(LED_EVENT_MODE); } /** * @brief 重新挂待机绿灯(常亮或提示几秒,看 BOARD_LED_STANDBY_ON) * @return 无 */ void app_led_hint_power_on(void) { if (!s_initialized || s_ota || s_empty_off) { return; } led_restart(LED_EVENT_STANDBY); } /** * @brief 进入 OTA:清掉其它灯,挂红绿交替一直闪 * @return 无 */ void app_led_request_ota(void) { if (!s_initialized) { return; } s_ota = 1; LED_EventAllDelete(); bsp_led_all_off(); LED_EventAdd(LED_EVENT_OTA); } /** * @brief 查询是否处于 OTA 灯效 * @return 1=OTA 灯效中,0=否 */ uint8_t app_led_is_ota(void) { return s_ota; } /** * @brief 空电关机:清掉其它灯(含绿灯),只挂红灯闪 5 下 * @return 无 */ void app_led_request_empty_off(void) { if (!s_initialized || s_ota) { return; /* OTA 灯效优先,不打断升级提示 */ } s_empty_off = 1; LED_EventAllDelete(); bsp_led_all_off(); /* 先关绿灯 */ LED_EventAdd(LED_EVENT_EMPTY); } /** * @brief 查询是否处于空电关机灯效 * @return 1=空电红闪中,0=否 */ uint8_t app_led_is_empty_off(void) { return s_empty_off; } /** * @brief 短灯效还在播时不要进休眠 * @return 1=应禁睡,0=可以睡 */ uint8_t app_led_need_nosleep(void) { if (s_ota || s_empty_off) { return 1; } return (uint8_t)(LED_IsEventExist(LED_EVENT_PAIR_FAIL) || LED_IsEventExist(LED_EVENT_PAIR_OK) || LED_IsEventExist(LED_EVENT_MODE)); } /** * @brief 播放一次性配对结果灯 * @param ok 1=成功常绿,0=失败红绿交替 * @return 无 */ static void led_start_pair_result(uint8_t ok) { LED_EventDelete(LED_EVENT_PAIRING); /* 结果灯盖住配对快闪 */ led_restart(ok ? LED_EVENT_PAIR_OK : LED_EVENT_PAIR_FAIL); } /** * @brief 功能关闭(DP0=0)时清掉所有“功能相关”的灯事件 * @note 故意不用 LED_EventAllDelete():那会把优先级表全清空,连充电红/满灯 * (LED_EVENT_CHARGE / LED_EVENT_CHARGE_FULL)也会跟着灭掉。但充电状态 * 和 DP0 开关是两码事——设备功能关着也可能正在充电,充电灯必须继续亮, * 所以这里只挑功能相关的几个事件逐个删,充电事件不动它。 * @return 无 */ static void led_clear_function_events(void) { LED_EventDelete(LED_EVENT_STANDBY); LED_EventDelete(LED_EVENT_PAIRING); LED_EventDelete(LED_EVENT_MODE); LED_EventDelete(LED_EVENT_PAIR_OK); LED_EventDelete(LED_EVENT_PAIR_FAIL); LED_EventDelete(LED_EVENT_LOWBAT); } /** * @brief 按优先级刷新灯:充电/低电从 app_power 取,配对从 app_pair 取, * 只有“功能是否开”这一项由调用方传入 * @param dt 距上次调用的毫秒数 * @param app_power 1=功能开(DP0),0=功能关,功能灯全灭 * @return 无 */ void app_led_run(uint16_t dt, uint8_t app_power) { uint8_t charge_led; uint8_t low_bat; uint8_t pairing; uint8_t func_on; AppPairEvt_t pair_evt; if (!s_initialized) { return; } /* OTA 期间只跑红绿交替,充电/待机/配对都不要叠上去 */ if (s_ota) { LED_EventAdd(LED_EVENT_OTA); user_led_handle(); return; } /* 空电关机:绿灯已关,只跑红灯闪 5 下;事件在 request 时已挂上,这里不要再 Add, * 否则闪完被删后又会重新开始闪。 */ if (s_empty_off) { user_led_handle(); return; } /* 配对成功/失败是边沿事件,只在那一拍切灯效 */ pair_evt = app_pair_tick_1ms(dt); if (pair_evt == APP_PAIR_EVT_OK) { led_start_pair_result(1); } else if (pair_evt == APP_PAIR_EVT_FAIL) { led_start_pair_result(0); } charge_led = app_power_charge_led(); low_bat = app_power_low_bat(); func_on = app_power ? 1 : 0; pairing = (uint8_t)(app_pair_is_active() && func_on); /* 充电红 / 充满绿:和功能开关无关,关着设备也可能在充电 */ if (charge_led == 1) { LED_EventAdd(LED_EVENT_CHARGE); } else { LED_EventDelete(LED_EVENT_CHARGE); } if (charge_led == 2) { LED_EventAdd(LED_EVENT_CHARGE_FULL); } else { LED_EventDelete(LED_EVENT_CHARGE_FULL); } /* 绿灯正被切模式/配对结果占用时,不要再挂配对快闪,避免互相覆盖 */ if (pairing && !green_pattern_busy()) { LED_EventAdd(LED_EVENT_PAIRING); } else if (!pairing) { LED_EventDelete(LED_EVENT_PAIRING); } /* 低电红闪:只在功能开、没接电时才挂,充电红优先 */ if (func_on && low_bat && (charge_led == 0)) { LED_EventAdd(LED_EVENT_LOWBAT); } else { LED_EventDelete(LED_EVENT_LOWBAT); } if (!func_on) { led_clear_function_events(); } else { #if BOARD_LED_STANDBY_ON /* 配对/切模式/配对失败用 return 1 拦住待机,这里不用删。 * 充电红不占用绿灯:拦截只是不跑待机 handler,绿灯会停在上次的亮。 * 所以充电时要删待机,把绿灯掐灭。 */ if (charge_led == 1) { LED_EventDelete(LED_EVENT_STANDBY); } else { LED_EventAdd(LED_EVENT_STANDBY); } #endif } user_led_handle(); } /** * @brief 清空全部灯事件并灭灯 * @return 无 */ void app_led_clear(void) { LED_EventAllDelete(); bsp_led_all_off(); }