Files

281 lines
8.1 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* @file app_timer_task.c
* @brief CBR1 定时引擎(第 11 章):7 组 8B 定时窗 + RTC 门闩
* @author cyWu <1917507415@qq.com>
* @date 2026.09.04
* @version V1.2.1
* @history
* - V1.2.1, 2026.09.04, cyWu, RTC 失效/time_en=0 只停不毁单次(防开机吞单次)
* - V1.2.0, 2026.09.04, cyWu, P2 完整引擎(窗开始开/结束关 + RTC 门闩补窗)
* - V1.1.0, 2026.09.04, cyWu, P1 只存配置(mark_dirty
* - V1.0.0, 2026.09.04, cyWu, P0 骨架
*
* @note 运行上下文:app_timer_run 由 usr_timer_loop500ms,任务上下文)
* 调用——窗判断分钟级精度足够,且单次自毁要写 VM(禁止中断上下文)。
* 字节布局(规格书 11.1):Byte0=使能 Byte1=模式(本版忽略但原样
* 存 VMByte2=类型(0单次/1每天/2每周) Byte3=星期位图(Bit0=周一…
* Bit6=周日,仅类型2) Byte4-5=开始时分 Byte6-7=结束时分。
* 窗开始沿 → 请求开继电器;窗结束沿 → 请求关 + 立刻按 10.3 补判
* 联动(11.1)。跨午夜以开始时刻所在星期判断(11.1)。
* 仲裁(12 章):手动介入后本窗剩余时间不自动抢回,等下一窗开始沿
* 或对时补窗。
******************************************************************************/
#include "system/includes.h"
#include "app_timer_task.h"
#include "app_arbiter.h"
#include "app_linkage.h"
#include "app_relay.h"
#include "app_nvm.h"
#include "usr_rtc.h"
#include "jb_product.h"
#include "board_pin.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_TMR]"
#define LOG_INFO_ENABLE
#include "debug.h"
/** 定时引擎运行时 */
typedef struct {
uint8_t taken; /**< 1=本轮窗已接管(手动打断后不抢回,直到全部窗结束) */
uint8_t in_mask; /**< bit i = 第 i 组当前在窗内(重叠 + 单次自毁用) */
uint8_t last_rtc_ok; /**< 上次 RTC 是否有效(对时补窗) */
} TimerState_t;
static TimerState_t s_timer;
/**
* @brief 取第 idx 组定时配置(gDevData 里是 7 个独立数组字段)
* @param idx 0~6
* @return 配置指针;越界返回 NULL
*/
static uint8_t *timer_cfg_ptr(uint8_t idx)
{
switch (idx) {
case 0: return gDevData.time_config1;
case 1: return gDevData.time_config2;
case 2: return gDevData.time_config3;
case 3: return gDevData.time_config4;
case 4: return gDevData.time_config5;
case 5: return gDevData.time_config6;
case 6: return gDevData.time_config7;
default: return NULL;
}
}
/**
* @brief 计算某日期是星期几
* @param y 年
* @param m 月 1~12
* @param d 日
* @return 0=周一 … 6=周日(规格书 11.1 位图 Bit0=周一)
*/
static uint8_t timer_calc_dow(uint16_t y, uint8_t m, uint8_t d)
{
uint8_t mm = m;
uint16_t yy = y;
uint16_t k;
int h;
if (mm < 3) {
mm += 12;
yy--;
}
k = yy % 100;
/* 蔡勒公式(格里高利历):h=0 周六 … 6 周五 */
h = (d + 13 * (mm + 1) / 5 + k + k / 4 + (yy / 100) / 4 + 5 * (yy / 100)) % 7;
return (uint8_t)((h + 5) % 7); /* 0=周一 … 6=周日 */
}
/**
* @brief 当前时刻是否在窗口内
* @param cfg 8 字节配置
* @param t 本地时间
* @return 1=在窗内
*/
static uint8_t timer_in_window(const uint8_t *cfg, const struct sys_time *t)
{
uint8_t type = cfg[2];
int16_t start = (int16_t)cfg[4] * 60 + cfg[5];
int16_t end = (int16_t)cfg[6] * 60 + cfg[7];
int16_t now = (int16_t)t->hour * 60 + t->min;
if (start == end) {
return 0; /* 非法窗(零时长) */
}
if (type == 2) {
/* 每周:星期位图;跨午夜窗以"开始时刻所在本地星期"判断(11.1) */
uint8_t dow = timer_calc_dow(t->year, t->month, t->day);
uint8_t dow_bit;
if (start > end) {
/* 跨午夜窗:凌晨 [0,end) 属于昨晚的窗,用昨天星期 */
if (now < end) {
dow = (uint8_t)((dow + 6) % 7); /* 昨天 */
}
}
dow_bit = (uint8_t)(1u << dow);
if (!(cfg[3] & dow_bit)) {
return 0;
}
}
if (start < end) {
return ((now >= start) && (now < end)) ? 1 : 0;
}
return ((now >= start) || (now < end)) ? 1 : 0; /* 跨午夜 */
}
/**
* @brief 自然离开的窗:类型 0 单次清 Byte0 并落盘
* @param left_mask 本拍由在窗→不在窗的组
* @return 无
*/
static void timer_once_expire(uint8_t left_mask)
{
uint8_t i;
for (i = 0; i < 7; i++) {
uint8_t *cfg;
if (!(left_mask & (uint8_t)(1u << i))) {
continue;
}
cfg = timer_cfg_ptr(i);
if (cfg && cfg[0] && (cfg[2] == 0)) {
cfg[0] = 0;
app_nvm_mark_dirty();
log_info("timer[%d] once done, en cleared\n", i + 1);
}
}
}
/**
* @brief 全部窗结束:释放定时占用;仍占着则关加热并补判联动
* @param force_off 1=time_en=0,即使手动打断过也关再按 10.3 补判
* @return 无
*/
static void timer_window_close(uint8_t force_off)
{
s_timer.taken = 0;
s_timer.in_mask = 0;
if (app_arbiter_get_owner() == ARB_OWNER_TIMER) {
app_arbiter_timer_release();
app_relay_request(0);
log_info("timer window out, heat=0\n");
} else {
log_info("timer window out (manual taken)\n");
if (force_off) {
app_relay_request(0);
}
}
app_linkage_reeval();
}
/**
* @brief 初始化定时引擎
* @return 无
*/
void app_timer_init(void)
{
#if !BOARD_TIMER_ENABLE
log_info("timer engine disabled\n");
return;
#endif
s_timer.taken = 0;
s_timer.in_mask = 0;
s_timer.last_rtc_ok = 0;
log_info("timer init\n");
}
/**
* @brief 推进定时引擎(500ms,任务上下文,由 usr_timer_loop 调用)
* @param dt 距上次调用的毫秒数
* @return 无
*/
void app_timer_run(uint16_t dt)
{
#if !BOARD_TIMER_ENABLE
(void)dt;
return;
#endif
struct sys_time t;
uint8_t rtc_ok = usr_rtc_get_time(&t);
uint8_t in_now = 0;
uint8_t i;
(void)dt;
if (rtc_ok && !s_timer.last_rtc_ok) {
log_info("timer rtc synced, re-check window\n");
}
s_timer.last_rtc_ok = rtc_ok;
/* RTC 门闩 / time_en=0:停窗不毁单次(开机 RTC 必无效,不能把未到点的单次吞掉) */
if (!rtc_ok || !gDevData.time_en) {
if (s_timer.taken) {
timer_window_close(1); /* 11.2:未对时 / time_en=0 等同所有窗结束 */
}
s_timer.in_mask = 0;
return;
}
for (i = 0; i < 7; i++) {
const uint8_t *cfg = timer_cfg_ptr(i);
if (!cfg || (cfg[0] == 0)) {
continue;
}
if (timer_in_window(cfg, &t)) {
in_now |= (uint8_t)(1u << i);
}
}
/* 自然离开的组:单次自毁(重叠时只清离开的那组,其它组继续) */
timer_once_expire((uint8_t)(s_timer.in_mask & (uint8_t)~in_now));
s_timer.in_mask = in_now;
if (in_now) {
if (!s_timer.taken) {
s_timer.taken = 1;
app_arbiter_timer_take();
app_relay_request(1);
log_info("timer window in, mask=0x%02x, heat=1\n", in_now);
}
} else if (s_timer.taken) {
timer_window_close(0);
}
}
/**
* @brief APP 写 DP9 定时总开关:存 gDevData + 触发 VM 持久化
* @param en 0=关 1=开
* @note 引擎关闭时仍保存;time_en=0 立即收口(关加热 + 10.3
* @return 无
*/
void app_timer_set_en(uint8_t en)
{
app_nvm_mark_dirty();
#if BOARD_TIMER_ENABLE
if (!en && s_timer.taken) {
timer_window_close(1); /* 11.2time_en=0 立刻结束所有窗(关加热 + 10.3) */
}
#endif
log_info("time_en=%d (stored)\n", en);
}
/**
* @brief APP 写 DP10~16 定时配置:触发 VM 持久化(数据已在 gDevData
* @param idx 1~7
* @note 引擎关闭时仍保存
* @return 无
*/
void app_timer_set_config(uint8_t idx)
{
app_nvm_mark_dirty();
log_info("time_config[%d] stored\n", idx);
}