Files
JBao_Heat_Lamp_FW/apps/usr_app/app_light_life.c
T
wuchuyuan e65b2b22b6 1、修复CBR0寿命与31天记录落盘失败(比较缓冲128→256)
2、APP换灯直接写DP3,DP4本版忽略;上电用restore灌RAM;读/全量打包权威剩余秒数
2026-09-08 12:29:02 +08:00

235 lines
7.2 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_light_life.c
* @brief 灯泡剩余寿命(规格书 13.2 节):实际吸合扣秒 / APP 写 DP3 /
* 到期告警 / 60s 节流上报 / 边沿立刻落盘
* @author cyWu <1917507415@qq.com>
* @date 2026.09.08
* @version V1.0.1
*
* @note 扣秒与运行记录同口径:只看 app_relay_get_actual()==1(过零后已吸合)。
* 上下文分工(避免 ISR 写 VM / 切 BLE):
* - tick20ms ISR):只折秒到 s_life.remain,标记 need_sync /
* need_edge(到期),不碰 gDevData / flash。
* - loop500ms 任务):60s 节流同步 gDevData.lighe_remain_time
* 边沿立刻同步 + 推导 gDevData.alarm_code + 立刻落盘。
* BOARD_LIGHT_LIFE_ENABLE=0:不扣秒、不置到期告警;DP3 走 VM/默认
* 值原样上报(规格书 13.2 / 第 0 章第 8 条)。
******************************************************************************/
#include <string.h>
#include "system/includes.h" /* local_irq_disable / local_irq_enable */
#include "app_light_life.h"
#include "app_relay.h"
#include "app_nvm.h"
#include "jb_product.h"
#include "board_pin.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[LIGHT]"
#define LOG_INFO_ENABLE
#include "debug.h"
/** 剩余寿命运行上下文 */
typedef struct {
volatile uint32_t remain; /**< 权威剩余寿命秒(ISR 扣减,任务读) */
volatile uint16_t ms; /**< 不足 1 秒的毫秒零头(仅 ISR) */
volatile uint16_t since_sync; /**< 距上次同步 gDevData 的秒数(60s 节流) */
volatile uint32_t since_save; /**< 距上次落盘的累计运行秒(600s 节奏) */
volatile uint8_t need_sync; /**< 1=ISR 要求任务同步 gDevData60s 到点) */
volatile uint8_t need_edge; /**< 1=边沿(到期)需立刻同步 + 落盘 */
} LightLifeCtx_t;
static LightLifeCtx_t s_life;
#if BOARD_LIGHT_LIFE_ENABLE
/**
* @brief 按当前 remain 推导告警并同步 gDevData(任务上下文)
* @param immediate 1=边沿立刻落盘;0=仅同步(60s 节流点,落盘随 600s)
* @return 无
*/
static void life_sync_gdata(uint8_t immediate)
{
gDevData.lighe_remain_time = s_life.remain;
gDevData.alarm_code = (s_life.remain == 0) ? 1 : 0;
if (immediate) {
local_irq_disable();
s_life.since_save = 0;
local_irq_enable();
app_nvm_save_runrec(); /* 寿命随 runrec item 落盘(规格书 13.2 边沿) */
}
}
#endif
/**
* @brief 初始化剩余寿命:默认额定值(VM 有效时 app_nvm_load 会覆盖)
* @return 无
*/
void app_light_life_init(void)
{
#if !BOARD_LIGHT_LIFE_ENABLE
log_info("light life disabled\n");
return;
#else
memset(&s_life, 0, sizeof(s_life));
s_life.remain = BOARD_LIGHT_RATED_SEC;
/* 首上电(VM 无效)DP3 即额定值、DP25=0(规格书 17 章 P1 末条);
* VM 有效时 app_nvm_load 会再调 set_remain 覆盖 */
gDevData.lighe_remain_time = BOARD_LIGHT_RATED_SEC;
gDevData.alarm_code = 0;
log_info("light life init, remain=%lu\n", (unsigned long)s_life.remain);
#endif
}
/**
* @brief 20ms 业务节拍:实际吸合才扣秒(与运行记录同口径)
* @param dt 距上次调用的毫秒数
* @return 无
*/
void app_light_life_tick(uint16_t dt)
{
#if !BOARD_LIGHT_LIFE_ENABLE
(void)dt;
return;
#else
if (!app_relay_get_actual()) {
s_life.ms = 0; /* 没在点灯:丢弃不足 1 秒的零头(13.2 同口径) */
return;
}
if (s_life.remain == 0) {
return; /* 已到期:停在 0,不回绕 */
}
s_life.ms = (uint16_t)(s_life.ms + dt);
while (s_life.ms >= 1000u) {
s_life.ms = (uint16_t)(s_life.ms - 1000u);
s_life.remain--;
s_life.since_sync++;
s_life.since_save++;
if (s_life.remain == 0) {
s_life.need_edge = 1; /* 到期边沿:任务立刻同步 + 落盘 + 上报 */
}
}
/* 60s 节流:点灯且未到期时满 60s 才允许同步一次 DP3(规格书 13.2) */
if ((s_life.since_sync >= BOARD_LIGHT_REPORT_MS / 1000u) && !s_life.need_edge) {
s_life.since_sync = 0;
s_life.need_sync = 1;
}
#endif
}
/**
* @brief 500ms 任务上下文:节流同步 / 边沿立刻同步 + 落盘
* @return 无
*/
void app_light_life_loop(void)
{
#if !BOARD_LIGHT_LIFE_ENABLE
return;
#else
uint8_t edge;
uint8_t sync;
local_irq_disable();
edge = s_life.need_edge;
sync = s_life.need_sync;
s_life.need_edge = 0;
s_life.need_sync = 0;
local_irq_enable();
if (edge) {
life_sync_gdata(1);
log_info("life expired, remain=0 alarm=1\n");
return;
}
if (sync) {
life_sync_gdata(0); /* 60s 节流点:只同步,flash 随 600s */
}
/* 平时落盘节奏:累计点灯满 600s 写一次(与运行记录同次可接受误差) */
local_irq_disable();
if (s_life.since_save >= BOARD_RUN_RECORD_SAVE_SEC) {
s_life.since_save = 0;
local_irq_enable();
app_nvm_save_runrec();
} else {
local_irq_enable();
}
#endif
}
/**
* @brief 设置剩余寿命(APP 写 DP3 / 出厂复位,任务上下文)
* @param remain 剩余寿命秒,0~0xFFFFFFFF 不封顶
* @return 无
*/
void app_light_life_set_remain(uint32_t remain)
{
#if BOARD_LIGHT_LIFE_ENABLE
local_irq_disable();
s_life.remain = remain;
s_life.since_sync = 0;
s_life.since_save = 0;
s_life.ms = 0;
local_irq_enable();
/* 边沿立刻生效:同步 gDevData + 推导告警 + 立刻落盘 */
gDevData.lighe_remain_time = remain;
gDevData.alarm_code = (remain == 0) ? 1 : 0;
app_nvm_save_runrec();
log_info("life set remain=%lu alarm=%d\n", (unsigned long)remain, gDevData.alarm_code);
#else
/* 模块关闭:只回填 DP3,不因寿命置告警(规格书 13.2) */
gDevData.lighe_remain_time = remain;
#endif
}
/**
* @brief 上电从 VM 灌回剩余寿命(只改 RAM,不写 flash)
* @param remain VM 中的剩余寿命秒
* @return 无
*/
void app_light_life_restore(uint32_t remain)
{
#if BOARD_LIGHT_LIFE_ENABLE
local_irq_disable();
s_life.remain = remain;
s_life.since_sync = 0;
s_life.since_save = 0;
s_life.ms = 0;
s_life.need_sync = 0;
s_life.need_edge = 0;
local_irq_enable();
gDevData.lighe_remain_time = remain;
gDevData.alarm_code = (remain == 0) ? 1 : 0;
log_info("life restore remain=%lu alarm=%d\n", (unsigned long)remain, gDevData.alarm_code);
#else
gDevData.lighe_remain_time = remain;
#endif
}
/**
* @brief 寿命回到额定值(仅出厂复位 DP26 调用;APP 换灯请写 DP3
* @return 无
*/
void app_light_life_reset(void)
{
app_light_life_set_remain(BOARD_LIGHT_RATED_SEC);
log_info("light reset to rated %lu\n", (unsigned long)BOARD_LIGHT_RATED_SEC);
}
/**
* @brief 查询剩余寿命
* @return 剩余寿命秒
*/
uint32_t app_light_life_get_remain(void)
{
#if !BOARD_LIGHT_LIFE_ENABLE
return gDevData.lighe_remain_time; /* 关闭时按上报值原样返回 */
#else
return s_life.remain;
#endif
}