313 lines
8.9 KiB
C
313 lines
8.9 KiB
C
/******************************************************************************
|
||
* @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_tick(20ms 中断):只把在转的毫秒折成 pending 秒,
|
||
* **不碰 s_rr.rec[]、不打日志、不写 flash**(跨天旋转在任务侧)。
|
||
* - app_runrec_rotate_if_needed(500ms 任务):收下 pending 加到
|
||
* slot[0]、每拍灌 DP15、跨天旋转、满 10 分钟 / 跨天才写 VM。
|
||
* DP15 每 500ms 更新到 gDevData:0x05 读立刻拿到当前秒;
|
||
* 差分满 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] 对应本地日期 yyyymmdd;0=未对时 */
|
||
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 yyyymmdd;0=未对时
|
||
*/
|
||
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 yyyymmdd;0=未对时
|
||
* @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
|
||
}
|