/****************************************************************************** * @file app_function.c * @brief CBR3 功能总装实现 * @author cyWu <1917507415@qq.com> * @date 2026.08.29 * @version V1.1.0 * @history * - V1.0.0, 2026.08.29, cyWu, 首次发布 * - V1.1.0, 2026.08.31, cyWu, 长按关机改为延后到任务上下文执行:按键在 * usr_timer 20ms(硬件定时器中断上下文)里处理,直接调 * app_power_request_poweroff 会在中断里跑 VM 落盘(栈溢出复位); * 现改为置 off_pending,由 app_function_loop(usr_timer_loop, * BLE 线程)先落盘再关机 ******************************************************************************/ #include "app_function.h" #include "app_power.h" #include "app_pair.h" #include "app_mode.h" #include "app_fan.h" #include "app_nvm.h" #include "app_arbiter.h" #include "app_cond.h" #include "app_timer_task.h" #include "app_th.h" #include "app_disp.h" #include "app_led.h" #include "jb_product.h" #include "system/includes.h" #include #define LOG_TAG_CONST APP #define LOG_TAG "[APP_FUNC]" #define LOG_INFO_ENABLE #include "debug.h" /** 功能模块状态 */ typedef struct { uint8_t app_power; /**< DP0:1=开机态(本产品无 APP 软关) */ uint8_t off_pending; /**< 1=长按已触发关机,等任务上下文落盘后再关机 */ } AppFunctionCtx_t; static AppFunctionCtx_t s_func; /** * @brief 初始化功能模块状态 * @return 无 */ void app_function_init(void) { s_func.app_power = 0; s_func.off_pending = 0; } /** * @brief 任务上下文周期处理(由 usr_timer_loop 500ms 调用) * @note ⚠ 本函数是唯一允许做 VM 落盘与关机收口的地方:usr_timer_loop 跑在 * BLE 线程(sys_timer 同步接口,任务上下文,栈充足)。20ms 的 * usr_timer_tick 是硬件定时器中断上下文,禁止做这两件事。 * @return 无 */ void app_function_loop(void) { app_nvm_flush(); /* 防抖满后真正写 VM(syscfg_write 必须在任务上下文) */ if (s_func.off_pending) { s_func.off_pending = 0; app_power_request_poweroff(); /* 落盘后再走关机流程 */ } } /** * @brief 同步 DP(power 恒 1、fan_gear、mode、fault_code),每个业务 tick 调用 * @note DP3/DP4 温湿度在 userHandle(500ms)填;DP0 写忽略,运行中恒上报 1 * @return 无 */ void app_function_poll(void) { gDevData.power = (s_func.app_power) ? 1 : 0; gDevData.fan_gear = app_fan_get_gear(); /* DP2 必须等于当前真实档位 */ gDevData.mode = app_mode_get_active(); /* 规格书 8.2/15:传感器故障优先 2;否则温湿度比较成立报 1 */ if (app_th_is_fault()) { gDevData.fault_code = 2; } else if (app_disp_is_alarm()) { gDevData.fault_code = 1; } else { gDevData.fault_code = 0; } } /** * @brief 停手动 + 模式,风扇关(关机请求 / 软关时用) * @return 无 */ void app_function_stop_all(void) { app_mode_stop(); app_fan_set_gear(0); } /** * @brief 软关:风扇停、DP0=0(本产品 DP0 写忽略,软关仅按键 3s 触发) * @return 无 */ void app_function_soft_off(void) { s_func.app_power = 0; gDevData.power = 0; app_function_stop_all(); log_info("soft off\n"); } /** * @brief 上电默认开启功能(DP0=1),不带开机灯效提示 * @return 无 */ void app_function_power_on_silent(void) { s_func.app_power = 1; gDevData.power = 1; } /** * @brief 查询功能开关状态(DP0) * @return 1=开,0=关 */ uint8_t app_function_is_power_on(void) { return s_func.app_power; } /** * @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; if (app_power_poweroff_pending()) { return; } if (!s_func.app_power) { /* 软关态短按只唤醒,不切档(P0 无 APP 软关,保留分支) */ s_func.app_power = 1; gDevData.power = 1; app_led_hint_power_on(); return; } 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 长按 3s:请求软关机(配对中忽略,避免配到一半睡死,规格书 5.3) * @note ⚠ 本回调在 usr_timer 20ms(硬件定时器中断上下文)执行,禁止直接 * 调 app_power_request_poweroff(其钩子会做 VM 落盘 → 栈溢出)。 * 只置 off_pending,由 app_function_loop(任务上下文)落盘后关机。 * @return 无 */ void usr_on_key_long(void) { if (app_power_poweroff_pending()) { return; } if (app_pair_is_active()) { log_info("key hold 3s ignored, pairing\n"); return; } log_info("key hold 3s, poweroff pending (wait vm flush)\n"); s_func.off_pending = 1; } /** * @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"); }