/****************************************************************************** * @file app_function.c * @brief CBR3 功能总装实现 * @author cyWu <1917507415@qq.com> * @date 2026.08.31 * @version V2.2.0,上电即开机:短按切档;长按分级满 5s 提示→松手提交配对、 * 满 12s 到点进 OTA;配对/OTA 的实质动作(清绑定写 VM、 * usr_app_ota_init)统一放 app_function_loop(任务上下文)执行, * 中断层只置标志;DP0 power 恒为开机态(规格书 0 章第 5 条:预留 * 不当开关),已删除 soft_off/is_power_on 等围绕"可关机"的死代码 ******************************************************************************/ #include "system/includes.h" /* 必须先于 app_main.h:u8/u16/u32 等类型由 SDK 定义 */ #include "app_function.h" #include "app_pair.h" #include "app_mode.h" #include "app_fan.h" #include "app_nvm.h" #include "app_defer.h" #include "app_arbiter.h" #include "app_cond.h" #include "app_timer_task.h" #include "app_th.h" #include "app_led.h" #include "app_main.h" #include "usr_le_api.h" #include "jb_product.h" #include #define LOG_TAG_CONST APP #define LOG_TAG "[APP_FUNC]" #define LOG_INFO_ENABLE #include "debug.h" /** * @brief 初始化功能模块状态 * @note DP0 power 恒为开机态:规格书 0 章第 5 条"预留,不当开关", * 协议层 DP0 写入本就忽略(jb_product.c),本产品无软关机, * 这里写死一次即可,不需要额外状态字段 * @return 无 */ void app_function_init(void) { gDevData.power = 1; } /** * @brief 任务上下文周期处理(由 usr_timer_loop 500ms 调用) * @note ⚠ 本函数是唯一允许做 VM 落盘、清绑定、OTA 启动的地方: * usr_timer_loop 跑在 BLE 线程(sys_timer 同步接口,任务上下文,栈充足)。 * 20ms 的 usr_timer_tick 是硬件定时器中断上下文,做这些会栈溢出复位。 * @return 无 */ void app_function_loop(void) { app_nvm_flush(); /* 防抖满后真正写 VM(syscfg_write 必须在任务上下文) */ /* OTA 优先于配对:按满 12s 说明用户不是想配网,放弃本次清绑定 */ if (app_defer_take(APP_DEFER_OTA)) { app_defer_cancel(APP_DEFER_PAIR); usr_app_ota_init(); /* BLE 侧:置 usr_goto_updata + 1s 定时器切 OTA 广播 */ app_led_request_ota(); /* 200ms 闪,优先级最高 */ log_info("key hold 12s -> ota\n"); } else if (app_defer_take(APP_DEFER_PAIR)) { /* ⚠ 真正发起配对广播:usr_goto_pair_mode 内部会断连+清绑定(写VM)+重发绑定广播 * 必须在任务上下文调用(写 VM)。仅置 usr_var.usr_goto_pair 标志无效—— * SDK adv 模块只消费 usr_goto_updata,不消费该标志,不会开广播。 */ usr_goto_pair_mode(); app_pair_start(); /* 起 120s 配对会话,灯交由 app_led_run 接管 */ app_led_clear_pair_hint(); log_info("key hold 5s -> pair\n"); } } /** * @brief 同步 DP(fan_gear、mode、fault_code),每个业务 tick 调用 * @note DP3/DP4 温湿度在 userHandle(500ms)填;DP0 power 不在这里同步, * 恒为开机态,写死在 app_function_init * @return 无 */ void app_function_poll(void) { gDevData.fan_gear = app_fan_get_gear(); /* DP2 必须等于当前真实档位 */ gDevData.mode = app_mode_get_active(); /* DP22:传感器连续失败报 1;2 预留,温湿度比较异常只走显示闪烁不上报 */ gDevData.fault_code = app_th_is_fault() ? 1 : 0; } /** * @brief 短按:风扇档位循环 1→2→3→0→1…(规格书 5.3) * @note 立刻退出通风/强力改为手动档:用 app_mode_stop() 只清模式、 * 不动档位(若用 set_manual(0) 会恢复模式前快照档,导致通风 1 档 * 短按跳到旧档+1 而非 2 档);配对进行中短按仍可切档 * @return 无 */ void usr_on_key_short(void) { uint8_t g; app_arbiter_manual_intervene(); /* 手动动作:释放条件/定时占用(12 章) */ app_mode_stop(); /* 退模式(不动档位,下面立刻切循环下一档) */ g = app_fan_get_gear(); /* 当前实际档:手动=手动档;模式中=模式强制档(1/3) */ g = (uint8_t)((g + 1) % 4); /* 1→2→3→0→1 */ app_fan_set_gear(g); log_info("key short gear=%d\n", g); } /** * @brief 按住满 5s:切配对提示灯,提示"松手即配网,继续按到 12s 进 OTA" * @note 本回调在 usr_timer 20ms(硬件定时器中断上下文)执行,只准动灯效事件 * (纯 RAM)。清绑定写 VM 在 app_function_loop 做。 * @return 无 */ void usr_on_key_pair_hint(void) { if (app_led_is_ota()) { return; /* OTA 灯效最高优先级,不打断 */ } app_led_hint_pair(); log_info("key hold 5s, pair hint (release to pair, keep to 12s for ota)\n"); } /** * @brief 按满 5s 后松手:提交配对(只置标志) * @note ⚠ 本回调在 usr_timer 20ms(硬件定时器中断上下文)执行,只置标志。 * 清绑定(usr_clear_pair_info_noreset 会写 VM)放到任务上下文。 * @return 无 */ void usr_on_key_pair_start(void) { if (app_led_is_ota()) { return; } app_defer_request(APP_DEFER_PAIR); log_info("key hold 5s released, pair pending\n"); } /** * @brief 按住满 12s:进 OTA(只置标志,到点即触发不等松手) * @note ⚠ 本回调在 usr_timer 20ms(硬件定时器中断上下文)执行,只置标志。 * usr_app_ota_init 涉及 BLE 广播切换,放到任务上下文。 * @return 无 */ void usr_on_key_ota_start(void) { if (app_led_is_ota()) { return; /* 已在 OTA,不重复发起 */ } app_defer_request(APP_DEFER_OTA); log_info("key hold 12s, ota pending\n"); } /** * @brief 出厂重置:清业务 VM 与 RAM,保留 BLE 配对(规格书 14 章) * @return 无 */ void app_function_factory_reset(void) { uint8_t i; uint8_t *c[7] = { gDevData.cond_config1, gDevData.cond_config2, gDevData.cond_config3, gDevData.cond_config4, gDevData.cond_config5, gDevData.cond_config6, gDevData.cond_config7 }; uint8_t *tm[7] = { gDevData.time_config1, gDevData.time_config2, gDevData.time_config3, gDevData.time_config4, gDevData.time_config5, gDevData.time_config6, gDevData.time_config7 }; app_nvm_reset(); app_arbiter_init(); app_cond_init(); app_timer_init(); app_mode_stop(); app_fan_set_gear(0); gDevData.mode = 0; gDevData.fan_gear = 0; gDevData.cond_en = 0; gDevData.time_en = 0; gDevData.fault_en = 0; gDevData.alarm_en = 0; gDevData.fault_code = 0; gDevData.alarm_code = 0; for (i = 0; i < 7; i++) { memset(c[i], 0, 7); memset(tm[i], 0, 8); } log_info("factory reset: business vm cleared, pairing kept\n"); }