Files
JBao_Temp_Humi_Fan_FW/apps/usr_app/app_runrec.c
T

313 lines
8.9 KiB
C
Raw 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_runrec.c
* @brief CBR3 31 天运行记录实现(规格书第 13.1 节)
* @author cyWu <1917507415@qq.com>
* @date 2026.09.02
* @version V1.0.0,随规格书 V3.0.0 新建
*
* @note 累加源是 app_fan_get_gear():档位≠0 才是「实际在转」,通风停
* 5 分钟那段 gear=0 自然不计入(13.1)。
*
* ⚠ 上下文分工(2026-08-31 事故定论,勿再踩):
* - app_runrec_tick20ms 中断):只把在转的毫秒折成 pending 秒,
* **不碰 s_rr.rec[]、不打日志、不写 flash**(跨天旋转在任务侧)。
* - app_runrec_rotate_if_needed500ms 任务):收下 pending 加到
* slot[0]、每拍灌 DP15、跨天旋转、满 10 分钟 / 跨天才写 VM。
* DP15 每 500ms 更新到 gDevData0x05 读立刻拿到当前秒;
* 差分满 1 分钟才 0x07;10min 全量仍带。
* 拔电没有软关机钩子,接受最多丢不满 10 分钟的秒数(13.1 已拍板)。
******************************************************************************/
#include <string.h>
#include "system/includes.h" /* local_irq_disable / local_irq_enable */
#include "app_runrec.h"
#include "app_fan.h"
#include "app_nvm.h"
#include "usr_rtc.h"
#include "jb_product.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_RREC]"
#define LOG_INFO_ENABLE
#include "debug.h"
#if BOARD_RUNREC_ENABLE
/** 运行记录上下文:31 格日历 + ISR/任务两侧的折秒状态 */
typedef struct {
uint32_t rec[BOARD_RUN_RECORD_DAYS]; /**< slot[0]=今天 … slot[30]=前 30 天(任务侧) */
uint32_t ymd; /**< slot[0] 对应本地日期 yyyymmdd0=未对时 */
uint16_t ms; /**< 不足 1 秒的毫秒零头(仅 ISR) */
volatile uint16_t pending_sec; /**< ISR 折秒,任务收下后加到 slot[0] */
uint16_t unsaved; /**< 距上次落盘累计的运行秒数(仅任务) */
} AppRunrecCtx_t;
static AppRunrecCtx_t s_rr;
#endif
/**
* @brief 公历日期 → 天数序号(Howard Hinnant 的 days_from_civil
* @param y 年(如 2026
* @param m 月 1~12
* @param d 日 1~31
* @return 距 1970-01-01 的天数(可为负)
*/
static int32_t runrec_days_from_civil(int y, int m, int d)
{
int era;
int yoe;
int doy;
int doe;
y -= (m <= 2) ? 1 : 0;
era = ((y >= 0) ? y : (y - 399)) / 400;
yoe = y - era * 400; /* [0,399] */
doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2) / 5 + d - 1; /* [0,365] */
doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; /* [0,146096]*/
return (int32_t)(era * 146097L + (long)doe - 719468L);
}
/**
* @brief yyyymmdd 压缩日期 → 天数序号
* @param ymd yyyymmdd(如 20260902
* @return 天数序号
*/
static int32_t runrec_ymd_to_days(uint32_t ymd)
{
return runrec_days_from_civil((int)(ymd / 10000u),
(int)((ymd / 100u) % 100u),
(int)(ymd % 100u));
}
/**
* @brief 初始化运行记录
* @return 无
*/
void app_runrec_init(void)
{
#if !BOARD_RUNREC_ENABLE
log_info("runrec disabled\n");
return;
#else
memset(&s_rr, 0, sizeof(s_rr));
app_runrec_pack(gDevData.run_record); /* DP21 报全 0 */
log_info("runrec init, %u days\n", (unsigned)BOARD_RUN_RECORD_DAYS);
#endif
}
#if BOARD_RUNREC_ENABLE
/**
* @brief 把 ISR 折好的秒数加到 slot[0],满 10 分钟则请求写 VM
* @return 1=需要立刻写运行记录 item
*/
static uint8_t runrec_apply_pending(void)
{
uint16_t sec;
uint8_t need_vm = 0;
local_irq_disable();
sec = s_rr.pending_sec;
s_rr.pending_sec = 0;
local_irq_enable();
if (sec == 0) {
return 0;
}
s_rr.rec[0] += sec;
s_rr.unsaved = (uint16_t)(s_rr.unsaved + sec);
if (s_rr.unsaved >= BOARD_RUN_RECORD_SAVE_SEC) {
s_rr.unsaved = (uint16_t)(s_rr.unsaved % BOARD_RUN_RECORD_SAVE_SEC);
need_vm = 1;
}
if (need_vm) {
log_info("runrec %us reached, save vm\n", (unsigned)BOARD_RUN_RECORD_SAVE_SEC);
}
return need_vm;
}
#endif /* BOARD_RUNREC_ENABLE */
/**
* @brief 20ms 中断上下文节拍:风扇在转才折秒到 pending,不碰 s_rr.rec
* @param dt 距上次调用的毫秒数
* @return 无
*/
void app_runrec_tick(uint16_t dt)
{
#if !BOARD_RUNREC_ENABLE
(void)dt;
return;
#else
if (app_fan_get_gear() == 0) {
/* 没在转:不累加,并丢弃不足 1 秒的零头(13.1:通风停段不计入) */
s_rr.ms = 0;
return;
}
s_rr.ms = (uint16_t)(s_rr.ms + dt);
while (s_rr.ms >= 1000u) {
s_rr.ms = (uint16_t)(s_rr.ms - 1000u);
s_rr.pending_sec++; /* 任务侧再加到 slot[0],避免与跨天旋转打架 */
}
#endif
}
/**
* @brief 500ms 任务上下文:收下 pending、灌 DP21、跨天旋转、必要时写 VM
* @return 无
*/
void app_runrec_rotate_if_needed(void)
{
#if !BOARD_RUNREC_ENABLE
return;
#else
struct sys_time t;
uint32_t ymd;
int32_t diff;
int i;
uint8_t need_vm = runrec_apply_pending();
if (usr_rtc_get_time(&t)) {
ymd = (uint32_t)t.year * 10000u + (uint32_t)t.month * 100u + (uint32_t)t.day;
if (s_rr.ymd == 0) {
/* 首次对时:slot[0] 已是当天累计值,只登记日期,不旋转不清零 */
s_rr.ymd = ymd;
need_vm = 1;
log_info("runrec rtc synced, ymd=%lu\n", (unsigned long)ymd);
} else if (ymd != s_rr.ymd) {
diff = runrec_ymd_to_days(ymd) - runrec_ymd_to_days(s_rr.ymd);
if (diff <= 0) {
s_rr.ymd = ymd; /* 时钟回拨/异常:只纠正日期,不乱移数据 */
} else {
if (diff > BOARD_RUN_RECORD_DAYS) {
diff = BOARD_RUN_RECORD_DAYS; /* 跨太多天:整表作废 */
}
/* 左移 N 天:old[i] → new[i+N],前面 N 格清零 */
for (i = (int)BOARD_RUN_RECORD_DAYS - 1; i >= diff; i--) {
s_rr.rec[i] = s_rr.rec[i - diff];
}
for (i = 0; i < diff; i++) {
s_rr.rec[i] = 0;
}
s_rr.ymd = ymd;
need_vm = 1;
log_info("runrec rotate %ld day(s), ymd=%lu\n",
(long)diff, (unsigned long)ymd);
}
}
}
/* 每拍灌 DP15:0x05 读立刻拿到当前秒;差分满 1 分钟才 0x07;10min 全量仍带 */
app_runrec_pack(gDevData.run_record);
if (need_vm) {
app_nvm_save_runrec(); /* 满 10 分钟或跨天 / 首次对时才写 flash */
}
#endif
}
/**
* @brief 取某天的运行秒数
* @param day 0=今天 … 30=前 30 天
* @return 秒数
*/
uint32_t app_runrec_get(uint8_t day)
{
#if !BOARD_RUNREC_ENABLE
(void)day;
return 0;
#else
if (day >= BOARD_RUN_RECORD_DAYS) {
return 0;
}
return s_rr.rec[day];
#endif
}
/**
* @brief 打包成 DP21 的 124B 载荷(31 × uint32 大端)
* @param out 输出缓冲
* @return 无
*/
void app_runrec_pack(uint8_t *out)
{
uint8_t i;
#if !BOARD_RUNREC_ENABLE
memset(out, 0, APP_RUNREC_PACK_LEN); /* 13.1:关闭时报全 0 */
return;
#else
for (i = 0; i < BOARD_RUN_RECORD_DAYS; i++) {
uint32_t v = s_rr.rec[i];
out[(uint8_t)(i * 4u) + 0u] = (uint8_t)(v >> 24);
out[(uint8_t)(i * 4u) + 1u] = (uint8_t)(v >> 16);
out[(uint8_t)(i * 4u) + 2u] = (uint8_t)(v >> 8);
out[(uint8_t)(i * 4u) + 3u] = (uint8_t)(v & 0xFFu);
}
#endif
}
/**
* @brief 从 VM 的 124B 载荷灌回 RAM
* @param in 输入缓冲
* @return 无
*/
void app_runrec_unpack(const uint8_t *in)
{
uint8_t i;
#if !BOARD_RUNREC_ENABLE
(void)in;
return;
#else
for (i = 0; i < BOARD_RUN_RECORD_DAYS; i++) {
s_rr.rec[i] = ((uint32_t)in[(uint8_t)(i * 4u) + 0u] << 24)
| ((uint32_t)in[(uint8_t)(i * 4u) + 1u] << 16)
| ((uint32_t)in[(uint8_t)(i * 4u) + 2u] << 8)
| (uint32_t)in[(uint8_t)(i * 4u) + 3u];
}
app_runrec_pack(gDevData.run_record); /* 开机立刻灌 DP21,不等 500ms */
#endif
}
/**
* @brief slot[0] 对应的本地日期
* @return yyyymmdd0=未对时
*/
uint32_t app_runrec_get_slot0_date(void)
{
#if !BOARD_RUNREC_ENABLE
return 0;
#else
return s_rr.ymd;
#endif
}
/**
* @brief 设置 slot[0] 对应日期
* @param ymd yyyymmdd0=未对时
* @return 无
*/
void app_runrec_set_slot0_date(uint32_t ymd)
{
#if BOARD_RUNREC_ENABLE
s_rr.ymd = ymd;
#else
(void)ymd;
#endif
}
/**
* @brief 清零 31 格与日期(出厂重置)
* @return 无
*/
void app_runrec_reset(void)
{
#if !BOARD_RUNREC_ENABLE
return;
#else
memset(&s_rr, 0, sizeof(s_rr));
app_runrec_pack(gDevData.run_record);
#endif
}