/****************************************************************************** * @file app_mode.c * @brief CBR3 工作模式实现(P1):通风 25/5 循环、强力 15min 一轮 * @author cyWu <1917507415@qq.com> * @date 2026.08.31 * @version V1.1.0,实现状态机(规格书第 9 章):通风 25/5、强力 15min 后停、 * 手动档快照、退出语义、开机从周期头 * * @note 模式激活时强制覆盖风扇档位(通风=1 档、强力=3 档、通风停段=0), * DP2 由 app_fan_get_gear 上报真实档位(app_function_poll 同步)。 * 退出方式(规格书 9 章):短按切档(app_mode_stop)、APP 写档位、 * APP 写 mode=0(恢复进入模式前的手动档快照)、强力自然到点(风扇 0)。 * 开机恢复不存相位:从 25min 开段 / 15min 从头计。 ******************************************************************************/ #include "app_mode.h" #include "app_fan.h" #include "app_nvm.h" #include "board_pin.h" #define LOG_TAG_CONST APP #define LOG_TAG "[APP_MODE]" #define LOG_INFO_ENABLE #include "debug.h" /** 通风循环段 */ typedef enum { MODE_VENT_ON = 0, /**< 开段 25min,强制 1 档 */ MODE_VENT_OFF /**< 停段 5min,风扇 0 */ } ModeVentPhase_t; /** 模式状态机上下文 */ typedef struct { uint8_t mode; /**< 0=关闭 1=通风 2=强力 */ uint8_t phase; /**< 通风段;其它模式恒 0 */ uint32_t run_ms; /**< 当前段已运行毫秒 */ uint8_t saved_gear;/**< 进入模式前的手动档快照(APP 写 mode=0 时恢复) */ } AppModeCtx_t; static AppModeCtx_t s_mode; /** * @brief 初始化模式状态机 * @return 无 */ void app_mode_init(void) { #if !BOARD_MODE_ENABLE log_info("mode disabled\n"); return; #endif s_mode.mode = 0; s_mode.phase = 0; s_mode.run_ms = 0; s_mode.saved_gear = 0; log_info("mode init\n"); } /** * @brief 推进模式状态机(每个业务 tick 调用) * @param dt 距上次调用的毫秒数 * @return 无 */ void app_mode_run(uint16_t dt) { #if !BOARD_MODE_ENABLE (void)dt; return; #endif switch (s_mode.mode) { case 1: /* 通风:开 25min 强制 1 档,停 5min 风扇 0,循环 */ if (s_mode.phase == MODE_VENT_ON) { app_fan_set_gear(1); s_mode.run_ms += dt; if (s_mode.run_ms >= BOARD_MODE_VENT_ON_MS) { s_mode.phase = MODE_VENT_OFF; s_mode.run_ms = 0; app_fan_set_gear(0); app_nvm_mark_dirty(); log_info("vent on 25min over, off 5min\n"); } } else { s_mode.run_ms += dt; if (s_mode.run_ms >= BOARD_MODE_VENT_OFF_MS) { s_mode.phase = MODE_VENT_ON; s_mode.run_ms = 0; app_fan_set_gear(1); app_nvm_mark_dirty(); log_info("vent off 5min over, on 25min\n"); } } break; case 2: /* 强力:强制 3 档,15min 到点风扇 0、mode 回 0 */ app_fan_set_gear(3); s_mode.run_ms += dt; if (s_mode.run_ms >= BOARD_MODE_POWER_ON_MS) { log_info("power 15min over, mode=0 fan=0\n"); s_mode.mode = 0; s_mode.phase = 0; s_mode.run_ms = 0; app_fan_set_gear(0); app_nvm_mark_dirty(); } break; default: break; } } /** * @brief APP 写 DP1 / 手动抢权 / 开机恢复:切到指定模式或退出 * @param mode 0=关闭模式(恢复手动档快照),1=通风,2=强力 * @note 模式 1/2 进入时快照当前实际档(即进入前的手动档),并立即强制档位; * mode=0 时若原在模式中则恢复快照档。短按退出走 app_mode_stop() * (不恢复快照,档位由短按逻辑立刻切到循环下一档)。 * @return 无 */ void app_mode_set_manual(uint8_t mode) { #if !BOARD_MODE_ENABLE (void)mode; return; #endif if (mode > 2) { mode = 0; } if (mode != 0) { s_mode.saved_gear = app_fan_get_gear(); /* 快照进入前的手动档 */ s_mode.mode = mode; s_mode.phase = MODE_VENT_ON; s_mode.run_ms = 0; /* 不存相位:开机/手动切入都从周期头计 */ app_fan_set_gear((mode == 1) ? 1 : 3); log_info("mode set %d, gear=%d\n", mode, app_fan_get_gear()); } else { if (s_mode.mode != 0) { /* 只在实际退出模式时恢复快照档,避免 load(0) 把恢复档清零 */ app_fan_set_gear(s_mode.saved_gear); log_info("mode exit, restore gear=%d\n", s_mode.saved_gear); } s_mode.mode = 0; s_mode.phase = 0; s_mode.run_ms = 0; } app_nvm_mark_dirty(); } /** * @brief 查询当前逻辑模式 * @return 0/1/2 */ uint8_t app_mode_get_active(void) { return s_mode.mode; } /** * @brief 停止当前模式(短按退模式用:只清模式状态,不动风扇档位, * 由调用方立刻切到循环下一档;关机流程则另行停风扇) * @return 无 */ void app_mode_stop(void) { #if !BOARD_MODE_ENABLE return; #endif if (s_mode.mode != 0) { app_nvm_mark_dirty(); } s_mode.mode = 0; s_mode.phase = 0; s_mode.run_ms = 0; }