1、喷淋器 P1/P2功能实现

2、红灯按连接态慢闪
This commit is contained in:
2026-09-09 19:57:00 +08:00
parent 074960eb02
commit 0dd65e1552
31 changed files with 2805 additions and 258 deletions
+384
View File
@@ -0,0 +1,384 @@
/******************************************************************************
* @file app_nvm.c
* @brief CBR2 VM 持久化:spray_config 8B / linkage 20B / plan_en / 7 条计划(各15B) /
* fault_en / alarm_en / 运行记录(规格书第 14 章)
* @author cyWu <1917507415@qq.com>
* @date 2026.09.09
* @version V1.0.0, 首次发布
*
* @note VM item 分配在 syscfg 用户自定义区 [1~49],本产品从 28 开始
* (每台设备 flash 独立,与其它产品 id 无冲突):
* CFG_USER_CBR2_CFG =28 状态(magic+ver+spray_config+使能,16B
* CFG_USER_CBR2_PLAN =29 计划 spray_plan1 + time_config2~7105B
* CFG_USER_CBR2_RUNREC =30 31 天运行记录
* CFG_USER_CBR2_LINKAGE =31 联动配置 20B
* 拆多个 item:改喷淋参数只擦写 16B 小 item,不重写计划区,减少
* flash 磨损;首次上电(magic/ver 不符)保持 userInit 的默认值。
*
* 打脏模型:本产品 P1 的全部写源都在 jbEventProcess(任务上下文),
* 直接 app_nvm_mark_dirty() 即可,tick 不做变化检测、只跑防抖计时;
* 真正写入由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
*
* ⚠ 上下文硬约束:usr_timer_add 回调在硬件定时器中断上下文(栈极
* 小),绝对禁止调 syscfg_writeVM 擦写 flash 栈用量大 → 栈溢出
* 软复位)。nvm_write_item 的比较缓冲必须 static(放栈上会撑爆中断
* 栈)。
******************************************************************************/
#include <string.h>
#include "app_nvm.h"
#include "app_defer.h"
#include "app_runrec.h"
#include "jb_product.h"
#include "board_pin.h"
#include "system/includes.h"
#include "syscfg_id.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_NVM]"
#define LOG_ERROR_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"
/* ── VM item id(用户区 1~49 内申请)── */
#define CFG_USER_CBR2_CFG 28
#define CFG_USER_CBR2_PLAN 29
#define CFG_USER_CBR2_RUNREC 30
#define CFG_USER_CBR2_LINKAGE 31
#define CBR2_VM_MAGIC 0xCB02
#define CBR2_VM_VER 1
#define CBR2_VM_DEBOUNCE_MS 500 /* 配置变化防抖(规格书 14 章) */
#define CBR2_PLAN_SLOTS 7
#define CBR2_PLAN_LEN 15
/** 状态 item16 字节) */
typedef struct {
uint16_t magic; /**< CBR2_VM_MAGIC */
uint8_t ver; /**< CBR2_VM_VER */
uint8_t spray_config[8];/**< DP7(大端:时长 u16 / 次数 u8 / 间隔 u16 */
uint8_t plan_en; /**< DP10 计划总开关 */
uint8_t fault_en; /**< DP20(原样存) */
uint8_t alarm_en; /**< DP22(原样存) */
uint8_t rsv[2]; /**< 对齐到 16B */
} __packed Cbr2CfgVm_t;
/** 计划 item105 字节:plan1 + time_config2~7 */
typedef struct {
uint8_t plan[CBR2_PLAN_SLOTS][CBR2_PLAN_LEN];
} __packed Cbr2PlanVm_t;
/** 运行记录 item124B 秒数 + 4B slot0 日期) */
typedef struct {
uint8_t run_record[APP_RUNREC_PACK_LEN]; /**< 31 × uint32 大端 */
uint32_t slot0_date; /**< slot[0] 对应本地日期 yyyymmdd */
} __packed Cbr2RunrecVm_t;
#if BOARD_NVM_ENABLE
/** NVM 运行时:防抖计时(打脏由写源主动 mark) */
typedef struct {
uint8_t dirty; /**< 1=防抖计时中 */
uint16_t deb_ms; /**< 防抖累计毫秒 */
} AppNvmCtx_t;
static AppNvmCtx_t s_nvm;
#endif
/**
* @brief 从 gDevData 收集 7 条计划到 VM 结构(plan1 + time_config2~7
* @param out 输出结构
* @return 无
*/
static void nvm_collect_plan(Cbr2PlanVm_t *out)
{
const uint8_t *src[CBR2_PLAN_SLOTS] = {
gDevData.spray_plan1, gDevData.time_config2, gDevData.time_config3,
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
gDevData.time_config7
};
uint8_t i;
for (i = 0; i < CBR2_PLAN_SLOTS; i++) {
memcpy(out->plan[i], src[i], CBR2_PLAN_LEN);
}
}
/**
* @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash
* @param item_id VM item id
* @param buf 新数据
* @param len 数据长度(≤128,比较缓冲大小)
* @return 无
*/
static void nvm_write_item(u16 item_id, void *buf, u16 len)
{
/* static:比较缓冲不放栈上;本函数只在任务上下文调用,无重入 */
static uint8_t old[128];
if (len > sizeof(old)) {
log_error("nvm item %d len %d too big\n", item_id, len);
return;
}
if (syscfg_read(item_id, old, len) == len) {
if (memcmp(old, buf, len) == 0) {
return; /* 没变化,不擦写 */
}
}
if (syscfg_write(item_id, buf, len) > 0) {
log_info("nvm item %d saved %dB\n", item_id, len);
} else {
log_error("nvm item %d write fail\n", item_id);
}
}
/**
* @brief 写状态 itemspray_config / plan_en / fault_en / alarm_en
* @return 无
*/
static void nvm_write_cfg(void)
{
Cbr2CfgVm_t st;
memset(&st, 0, sizeof(st));
st.magic = CBR2_VM_MAGIC;
st.ver = CBR2_VM_VER;
memcpy(st.spray_config, gDevData.spray_config, sizeof(st.spray_config));
st.plan_en = gDevData.spray_plan_en ? 1 : 0;
st.fault_en = gDevData.fault_en;
st.alarm_en = gDevData.alarm_en;
nvm_write_item(CFG_USER_CBR2_CFG, &st, sizeof(st));
}
/**
* @brief 写计划 item7 条 × 15B
* @return 无
*/
static void nvm_write_plan(void)
{
Cbr2PlanVm_t pf;
nvm_collect_plan(&pf);
nvm_write_item(CFG_USER_CBR2_PLAN, &pf, sizeof(pf));
}
/**
* @brief 写运行记录 item31 格秒数 + slot0 日期)
* @return 无
*/
static void nvm_write_runrec(void)
{
Cbr2RunrecVm_t rf;
app_runrec_pack(rf.run_record);
rf.slot0_date = app_runrec_get_slot0_date();
nvm_write_item(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf));
}
/**
* @brief 写联动配置 20B
* @return 无
*/
static void nvm_write_linkage(void)
{
nvm_write_item(CFG_USER_CBR2_LINKAGE, gDevData.linkage_config, 20);
}
/**
* @brief 初始化:校验 VM 有效性并打日志(数据加载在 app_nvm_load
* @return 无
*/
void app_nvm_init(void)
{
#if !BOARD_NVM_ENABLE
log_info("nvm disabled\n");
return;
#else
Cbr2CfgVm_t st;
int ret = syscfg_read(CFG_USER_CBR2_CFG, &st, sizeof(st));
if ((ret == (int)sizeof(st)) && (st.magic == CBR2_VM_MAGIC) && (st.ver == CBR2_VM_VER)) {
log_info("nvm init, vm valid v%d\n", st.ver);
} else {
log_info("nvm init, vm invalid (ret=%d), use defaults\n", ret);
}
memset(&s_nvm, 0, sizeof(s_nvm));
app_defer_cancel(APP_DEFER_NVM_SAVE);
#endif
}
/**
* @brief 立即保存全部业务字段
* @return 无
*/
void app_nvm_save(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
nvm_write_cfg();
nvm_write_plan();
nvm_write_linkage();
s_nvm.dirty = 0;
s_nvm.deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
#endif
}
/**
* @brief 从 VM 加载并回填 gDevData(开机由 usr_jb_init 调用)
* @note 只回填配置:DP1/泵保持关,作业不恢复(上电绝不自动喷)。
* VM 无效(首次上电)保持 gDevData 现值 = userInit 默认。
* @return 无
*/
void app_nvm_load(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
Cbr2CfgVm_t st;
Cbr2PlanVm_t pf;
Cbr2RunrecVm_t rf;
uint8_t i;
if ((syscfg_read(CFG_USER_CBR2_CFG, &st, sizeof(st)) != (int)sizeof(st))
|| (st.magic != CBR2_VM_MAGIC) || (st.ver != CBR2_VM_VER)) {
log_info("nvm load: no valid vm, keep defaults (dp7=10/3/3)\n");
return;
}
memcpy(gDevData.spray_config, st.spray_config, sizeof(st.spray_config));
gDevData.spray_plan_en = st.plan_en ? 1 : 0;
gDevData.fault_en = st.fault_en;
gDevData.alarm_en = st.alarm_en;
if (syscfg_read(CFG_USER_CBR2_PLAN, &pf, sizeof(pf)) == (int)sizeof(pf)) {
uint8_t *dst[CBR2_PLAN_SLOTS] = {
gDevData.spray_plan1, gDevData.time_config2, gDevData.time_config3,
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
gDevData.time_config7
};
for (i = 0; i < CBR2_PLAN_SLOTS; i++) {
memcpy(dst[i], pf.plan[i], CBR2_PLAN_LEN);
}
}
/* 运行记录回填 RAM(14.1:掉电再上电先把 31 格灌回来) */
if (syscfg_read(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) {
app_runrec_unpack(rf.run_record);
app_runrec_set_slot0_date(rf.slot0_date);
}
if (syscfg_read(CFG_USER_CBR2_LINKAGE, gDevData.linkage_config, 20) != 20) {
memset(gDevData.linkage_config, 0, 20);
}
log_info("nvm load v%d: spray cfg %us x%u +%us, plan_en=%d\n",
st.ver,
(unsigned)(((uint16_t)st.spray_config[0] << 8) | st.spray_config[1]),
(unsigned)st.spray_config[2],
(unsigned)(((uint16_t)st.spray_config[3] << 8) | st.spray_config[4]),
st.plan_en);
#endif
}
/**
* @brief 业务节拍:防抖计时,**不执行写入**
* @param dt 距上次调用的毫秒数
* @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = 硬件定时器中断上下文
* (栈极小)。只允许轻量计时;真正的 syscfg_write 由 app_nvm_flush()
* 在 usr_timer_loop(任务上下文)执行。
* @return 无
*/
void app_nvm_tick(uint16_t dt)
{
#if !BOARD_NVM_ENABLE
(void)dt;
return;
#else
if (!s_nvm.dirty) {
return;
}
s_nvm.deb_ms = (uint16_t)(s_nvm.deb_ms + dt);
if (s_nvm.deb_ms >= CBR2_VM_DEBOUNCE_MS) {
s_nvm.deb_ms = 0;
s_nvm.dirty = 0;
app_defer_request(APP_DEFER_NVM_SAVE); /* 只置标志:真正落盘在 app_nvm_flush */
}
#endif
}
/**
* @brief 任务上下文落盘:防抖满后真正写 VM(由 usr_timer_loop 500ms 调用)
* @note 必须且只能在非中断上下文调用(VM 擦写 flash 栈用量大)
* @return 无
*/
void app_nvm_flush(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
if (!app_defer_take(APP_DEFER_NVM_SAVE)) {
return;
}
app_nvm_save();
#endif
}
/**
* @brief 标记业务字段有变化,防抖计时归零(重新计满防抖窗口才写)
* @note 任务上下文调用(APP 下行改配置:DP7/计划/plan_en/DP20/22
* @return 无
*/
void app_nvm_mark_dirty(void)
{
#if BOARD_NVM_ENABLE
s_nvm.dirty = 1;
s_nvm.deb_ms = 0;
#endif
}
/**
* @brief 立即保存运行记录 item(满 600s / 跨天 / 首次对时,任务上下文)
* @return 无
*/
void app_nvm_save_runrec(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
nvm_write_runrec();
#endif
}
/**
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 15 章)
* @note 3 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
* BLE 配对三元组在 CFG_USER_PAIR_INFOitem 2),不受影响
* @return 无
*/
void app_nvm_reset(void)
{
#if !BOARD_NVM_ENABLE
return;
#else
Cbr2CfgVm_t st;
Cbr2PlanVm_t pf;
Cbr2RunrecVm_t rf;
uint8_t linkage[20];
memset(&st, 0, sizeof(st));
memset(&pf, 0, sizeof(pf));
memset(&rf, 0, sizeof(rf));
memset(linkage, 0, sizeof(linkage));
syscfg_write(CFG_USER_CBR2_CFG, &st, sizeof(st));
syscfg_write(CFG_USER_CBR2_PLAN, &pf, sizeof(pf));
syscfg_write(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf));
syscfg_write(CFG_USER_CBR2_LINKAGE, linkage, sizeof(linkage));
s_nvm.dirty = 0;
s_nvm.deb_ms = 0;
app_defer_cancel(APP_DEFER_NVM_SAVE);
log_info("nvm factory reset, business vm cleared (pairing kept)\n");
#endif
}