P0/P1/P2功能实现:接入业务组合根(按键/LED/过零/配网/OTA),联动、11B定时、运行记录、灯泡寿命(DP3/DP4/DP25)、CBR0 VM、512分包
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
/******************************************************************************
|
||||
* @file app_timer_task.c
|
||||
* @brief CBR0 定时引擎(规格书第 11 章):7 组 11B 定时窗 + RTC 门闩
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.04
|
||||
* @version V1.3.0
|
||||
* @history
|
||||
* - V1.3.0, 2026.09.08, cyWu, 改为灯罩 11B 布局(无模式字节,Byte1=类型)
|
||||
* - 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_loop(500ms,任务上下文)
|
||||
* 调用——窗判断分钟级精度足够,且单次自毁要写 VM(禁止中断上下文)。
|
||||
* 字节布局(规格书 11.1,无加热垫的模式字节):
|
||||
* Byte0=使能 Byte1=类型(0单次/1每天/2每周)
|
||||
* Byte2=星期位图(Bit0=周一…Bit6=周日,仅类型2)
|
||||
* Byte3-4=开始时分(时/分) Byte5-6=结束时分 Byte7-10=预留原样存。
|
||||
* 窗开始沿 → 请求开继电器;窗结束沿 → 请求关 + 立刻按 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 11 字节配置(规格书 11.1)
|
||||
* @param t 本地时间
|
||||
* @return 1=在窗内
|
||||
*/
|
||||
static uint8_t timer_in_window(const uint8_t *cfg, const struct sys_time *t)
|
||||
{
|
||||
uint8_t type = cfg[1];
|
||||
int16_t start = (int16_t)cfg[3] * 60 + cfg[4];
|
||||
int16_t end = (int16_t)cfg[5] * 60 + cfg[6];
|
||||
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[2] & 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[1] == 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, light=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, light=1\n", in_now);
|
||||
}
|
||||
} else if (s_timer.taken) {
|
||||
timer_window_close(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP12 定时总开关:存 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.2:time_en=0 立刻结束所有窗(关灯 + 10.3) */
|
||||
}
|
||||
#endif
|
||||
log_info("time_en=%d (stored)\n", en);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP13~19 定时配置:触发 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);
|
||||
}
|
||||
Reference in New Issue
Block a user