diff --git a/Doc/电子开关-加热灯罩_台上验收测试笔记.md b/Doc/电子开关-加热灯罩_台上验收测试笔记.md index 4c35344..f8b60fb 100644 --- a/Doc/电子开关-加热灯罩_台上验收测试笔记.md +++ b/Doc/电子开关-加热灯罩_台上验收测试笔记.md @@ -28,7 +28,7 @@ - [ ] 2.1 5s 配对后 APP 写 DP0 开/关灯泡:过零后灯与 DP0 上报一致(报**实际吸合态**) - [ ] 2.2 短按同步上报 DP0 - [ ] 2.3 VM:改开关后断电,重新上电按上次开关恢复(开则等过零再吸合) -- [ ] 2.4 DP1 / DP2 / DP5–8 写忽略;DP2 上报恒 0 +- [ ] 2.4 DP1 / DP2 / DP4 / DP5–8 写忽略;DP2 上报恒 0 - [ ] 2.5 DP9(WO):APP/服务器能写当前温度;设备全量上报**不含** DP9 - [ ] 2.6 定时 7 组 **11 字节** + `time_en` 能存(规格书 11.1 布局);`BOARD_TIMER_ENABLE=0` 不执行 - [ ] 2.7 `MAX_PACKAGE_LEN=512`:APP 能读完整 124B `run_record`(DP20,本阶段可为 0) @@ -50,10 +50,10 @@ - [ ] 3.12 过零超时 `fault_code=2`(DP23) - [ ] 3.13 仲裁表(第 12 章)用例 1–13(见下节) - [ ] 3.14 寿命:实际吸合才扣;到 0 停在 0、`alarm_code=1`、灯仍可亮(不互锁) -- [ ] 3.15 DP4 写 1:剩余寿命回 2,592,000、告警清 0、立刻上报 +- [ ] 3.15 DP4 写 1:**不改寿命**(本版忽略);换灯请写 DP3=2,592,000 - [ ] 3.16 APP 写 DP3:任意 uint32 生效;>0 清告警、=0 置告警;立刻落盘 - [ ] 3.17 按键 5s 清绑定 **不**改寿命 -- [ ] 3.18 点灯时 DP3 最多 1 分钟报一次;到期/复位/写 DP3 立刻报 +- [ ] 3.18 点灯时 DP3 最多 1 分钟报一次;到期/写 DP3 立刻报 - [ ] 3.19 掉电再上电:未到期接着扣;已到期仍 0 且 DP25=1 ## 4. 仲裁最小用例(灯泡语义) @@ -70,7 +70,7 @@ 8. 断连扫描丢包 → 不关灯(保持上一态)。 9. 已连接写 **DP9** 使 `TTmax` → 关。 10. 剩余寿命置 5,实际吸合 5 秒 → DP3=0、DP25=1,继电器保持开(若请求仍为开)。 -11. 用例 10 之后 DP4 写 1 → DP3=2,592,000、DP25=0。 +11. 用例 10 之后 APP 写 DP3=2,592,000 → DP25=0。写 DP4=1 **不得**改寿命。 12. APP 写 DP3=100 后立刻拔电再上电 → 仍约 100(允许未满边沿的秒误差,**不允许**回到 2,592,000)。 13. 定时 11 字节:Byte1=1(每天)、Byte3–6=时分、Byte0=1 → 到点开;若误用加热垫偏移(Byte2 当类型)则本条失败。 diff --git a/Doc/电子开关-加热灯罩_固件需求规格书.md b/Doc/电子开关-加热灯罩_固件需求规格书.md index 3a99cdd..0207ab8 100644 --- a/Doc/电子开关-加热灯罩_固件需求规格书.md +++ b/Doc/电子开关-加热灯罩_固件需求规格书.md @@ -7,7 +7,7 @@ | 工程 | `电子开关-加热灯罩_soc_JieLi-JL7016` | | 产品码 | ProdtCode=`CBR0`,productCode=`PRCBR0` | | 协议 | 见宝 SOC 55AA,V1.0.0 | -| 文档版本 | V1.0.0 | +| 文档版本 | V1.0.1 | | 日期 | 2026.09.08 | | 作者 | cyWu | | 读者 | WorkBuddy / 固件开发 | @@ -16,6 +16,8 @@ | 灯效参考 | `D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016`(红绿双通道、OTA 红绿交替) | | 加热垫规格 | `电子开关-加热垫_固件需求规格书.md` V1.0.1(同板行为以加热垫为准,本页只写灯罩差量与 DP 重编号) | +> **V1.0.1(2026-09-08):** DP4 灯泡复位本版写忽略。APP 换灯直接写 DP3(例如 2,592,000);出厂复位 DP26 仍把寿命拉回额定值。 +> > **V1.0.0(2026-09-08):** 首版共同理解。硬件、过零继电器、短按、联动、定时开停、31 天运行记录、故障码 2、出厂复位 **与加热垫相同**。差量:灯泡剩余寿命(额定 2,592,000 秒,按实际吸合每秒减 1,到 0 报粘性告警 1,灯仍可亮);DP9 当前温度改为 **WO**;定时配置 **11 字节且无模式字节**(使能/类型/星期/开始结束时分,Byte7–10 预留);DP2 总工作时长本版恒 0。 本文是 **已拍板的共同理解**,不是草稿。实现时以本文为准;DP 表以 `dp_definition.json` + 本文第 10.2 / 11.1 / 13.1 / 13.2 / 15 章为准。与加热垫冲突时:**引脚、过零、仲裁、联动回差跟加热垫;DP 号、定时 11 字节、寿命跟本文。** @@ -30,7 +32,7 @@ 4. **不要抄风扇的 PWM 风扇 / 数码管 / AHT20 / 通风强力。** 执行器是继电器 + 过零;温度来自扫描或 DP9,不是本机传感器。 5. **`MAX_PACKAGE_LEN` 必须为 512。** 生成值 128 装不下 DP20 `run_record`(124B),11×7 定时 + 寿命 DP 全量一包也会超过 256。禁止再生成回 128。 6. **DP9 `current_temp` 为 WO。** 生成 JSON 是 RO,必须改成 WO。已连接时服务器下发;设备不上报;断连扫描到的温度不填 DP9。 -7. **本版写忽略:** DP1 温控开关、DP2 总工作时长(上报恒 0)、DP5 温控时间、DP6 模式、DP7/8 最低最高温度。DP21 心跳忽略。 +7. **本版写忽略:** DP1 温控开关、DP2 总工作时长(上报恒 0)、**DP4 灯泡复位**、DP5 温控时间、DP6 模式、DP7/8 最低最高温度。DP21 心跳忽略。APP 换灯请写 DP3。 8. 每个模块必须能用 `board_pin.h` 的 `BOARD_xxx_ENABLE` **单独关掉** 仍能编过、能配网上报。寿命用 `BOARD_LIGHT_LIFE_ENABLE`。 9. 日志全部 **英文**。中文只出现在注释和本文。 10. 编码风格对齐加热垫 + 工程师规范:文件小写+下划线,`g_` / `s_` 前缀,枚举 `Pascal_t`,接口 `xxx_init` / `xxx_run` 或 tick / `xxx_get_status`。 @@ -291,6 +293,7 @@ OTA 进行中:忽略短按和再次 5s/12s。配对进行中:短按仍可切 |---|---| | 1 温控开关 | 写忽略 | | 2 灯泡总工作时长 | 写忽略,**上报恒 0**(不累计) | +| 4 灯泡复位 | 写忽略。APP 换灯直接写 DP3 | | 5 温控时间 | 写忽略 | | 6 模式 | 写忽略 | | 7 / 8 最低最高温度 | 写忽略(阈值只在 DP11 联动 20B 里) | @@ -476,7 +479,7 @@ VM magic 必须用灯罩自己的值(建议 `0xCB00`),**禁止**复用加 `BOARD_RUNREC_ENABLE=0`:不累加、DP20 报全 0。 -### 13.2 灯泡剩余寿命(DP2 / DP3 / DP4 / DP25) +### 13.2 灯泡剩余寿命(DP2 / DP3 / DP25;DP4 不用) 额定工时,不是日历: @@ -491,12 +494,12 @@ VM magic 必须用灯罩自己的值(建议 `0xCB00`),**禁止**复用加 | 到期后加热 | **不互锁**,开停仍走第 7 / 10 / 11 / 12 章 | | 告警推导 | `remain == 0` → `alarm_code = 1`,否则 `0`。`alarm_en` 不屏蔽 | | DP2 | 写忽略,上报恒 0 | -| DP3 写 | **立刻生效**,范围 0~4,294,967,295,不封顶 | -| DP4 写 1 | 剩余寿命 = 2,592,000;写 0 无动作;WO 不上报 | +| DP3 写 | **立刻生效**,范围 0~4,294,967,295,不封顶。**APP 换灯:直接写 DP3=2,592,000** | +| DP4 | 本版 **写忽略**,不上报 | | 按键 5s 清绑定 | 不碰寿命 | | 出厂复位 DP26 | 剩余寿命回到 2,592,000,告警 0(与第一次开机相同) | -**边沿副作用(必须同一套):** APP 写 DP3、DP4=1、扣到 0、出厂复位: +**边沿副作用(必须同一套):** APP 写 DP3、扣到 0、出厂复位: 1. 改 `gDevData.lighe_remain_time` 2. 按值置/清 `alarm_code` @@ -508,7 +511,7 @@ VM magic 必须用灯罩自己的值(建议 `0xCB00`),**禁止**复用加 **DP3 上报节流:** - 实际吸合且剩余 >0:距上次 DP3 上报满 **墙上 60 秒** 才再走变化上报 -- 到期、DP4 复位、APP 写 DP3、读请求、10 分钟全量:**立刻带上 DP3** +- 到期、APP 写 DP3、读请求、10 分钟全量:**立刻带上 DP3** - 其它 DP 仍 2 秒变化防抖;DP25 随告警边沿立刻报 `BOARD_LIGHT_LIFE_ENABLE=0`:不扣秒、不因寿命置告警;DP3 若 VM 里有值仍上报,否则 0。 @@ -536,7 +539,7 @@ VM magic 必须用灯罩自己的值(建议 `0xCB00`),**禁止**复用加 | 1 | temp_control_switch | 本版写忽略 | | 2 | light_work_time | 本版写忽略,上报恒 0 | | 3 | lighe_remain_time | 第 13.2 节。RW;固件扣秒;APP 写立刻生效 | -| 4 | light_reset | **WO**。写 1 把 DP3 置 2,592,000;不上报 | +| 4 | light_reset | **WO,本版写忽略。** APP 换灯直接写 DP3 | | 5 | temp_control_time | 本版写忽略 | | 6 | mode | 本版写忽略 | | 7 / 8 | min_temp / max_temp | 本版写忽略 | @@ -612,7 +615,7 @@ apps/usr_app/ - [ ] 5s 配对,APP 能写 DP0 开关灯泡(过零后灯和继电器一致,DP0 报实际态) - [ ] 短按同步上报 DP0 - [ ] VM:改开关后断电,重新上电按上次开关恢复(开则等过零再吸合) -- [ ] DP1 / DP2 / DP5–8 写忽略;DP2 上报 0 +- [ ] DP1 / DP2 / DP4 / DP5–8 写忽略;DP2 上报 0 - [ ] DP9 WO:APP/服务器能写下当前温度;设备全量上报 **不含** DP9 - [ ] 定时 7 组 11 字节 + `time_en` 能存;`BOARD_TIMER_ENABLE=0` 时不执行 - [ ] `MAX_PACKAGE_LEN=512`,APP 能读完整 124B 运行记录(本阶段记录可仍为 0) @@ -634,8 +637,8 @@ apps/usr_app/ - [ ] 过零超时 `fault_code=2`(DP23) - [ ] 仲裁表第 12 章逐条用例(与加热垫相同,DP 号按本文) - [ ] **寿命:** 实际吸合才扣;到 0 停在 0,`alarm_code=1`,灯仍可亮 -- [ ] **DP4=1:** 剩余寿命回到 2,592,000,告警清 0,立刻上报 -- [ ] **APP 写 DP3:** 任意 uint32 生效;>0 清告警,=0 置告警;立刻落盘 +- [ ] **DP4 写忽略:** 写 1 不改寿命 +- [ ] **APP 写 DP3:** 任意 uint32 生效;写 2,592,000 即换灯;>0 清告警,=0 置告警;立刻落盘 - [ ] **5s 清绑定不改寿命** - [ ] 点灯时 DP3 最多 1 分钟报一次;到期/复位/写 DP3 立刻报 - [ ] 掉电再上电:未到期接着扣;已到期仍为 0 且告警 1 @@ -645,7 +648,7 @@ apps/usr_app/ 与加热垫第 17 章用例 1–9 相同,把「加热」读成「灯泡」、DP6 读成 **DP9**。额外: 10. 剩余寿命置 5,实际吸合 5 秒 → DP3=0、DP25=1,继电器保持开(若请求仍为开)。 -11. 用例 10 之后 DP4 写 1 → DP3=2,592,000、DP25=0。 +11. 用例 10 之后 APP 写 DP3=2,592,000 → DP25=0。写 DP4=1 **不得**改寿命。 12. APP 写 DP3=100 后立刻拔电再上电 → 仍约 100(允许未满边沿的秒误差,不允许回到 2,592,000)。 13. 定时 11 字节:Byte1=1(每天)、Byte3–6=时分,Byte0=1 → 到点开;若误用加热垫偏移(Byte2 当类型)则本条失败。 @@ -709,6 +712,7 @@ apps/usr_app/ | 生成协议再导回 DP9=RO / LEN=128 | 禁止;以本文 + 改过的源码为准 | | 把加热垫 8 字节定时偏移用到灯罩 | 禁止。Byte1 是类型不是模式 | | 到期强制关灯 | 禁止。只告警 | +| 用 DP4 做换灯 | 禁止。本版 DP4 写忽略;APP 换灯直接写 DP3 | | 每秒写一次寿命到 Flash | 禁止。平时 600s,边沿立刻写 | | 点灯时每 2 秒报 DP3 | 禁止。最多 60s 一次(边沿除外) | | 改 `lighe_remain_time` 拼写 | 禁止 | @@ -734,7 +738,7 @@ apps/usr_app/ 6. P0:继电器 + 灯 + 按键验收。 7. P1:VM 恢复开关、忽略 DP、定时只存、首次 DP3=2,592,000。 8. P2:扫描/联动/定时引擎/运行记录/仲裁用例。 -9. 新建 `app_light_life`:扣秒、DP4、写 DP3、告警、60s 节流、边沿立刻落盘。 +9. 新建 `app_light_life`:扣秒、写 DP3、告警、60s 节流、边沿立刻落盘。DP4 写忽略。 每一步都保持对应 `BOARD_xxx_ENABLE` 可关。提交前对照第 0.1 节:一个模块一个 `s_xxx`,注释写为什么,不要排障日记。 @@ -748,7 +752,7 @@ apps/usr_app/ | 1 温控开关 | 1 | 忽略 | | — | 2 总工作时长 | 新增,恒 0 | | — | 3 剩余寿命 | 新增 | -| — | 4 灯泡复位 | 新增,WO | +| — | 4 灯泡复位 | 新增,WO,**本版忽略** | | 2 温控时间 | 5 | 忽略 | | 3 模式 | 6 | 忽略 | | 4 / 5 最低最高温度 | 7 / 8 | 忽略 | diff --git a/apps/usr_app/app_light_life.c b/apps/usr_app/app_light_life.c index 94f19ca..5e52c15 100644 --- a/apps/usr_app/app_light_life.c +++ b/apps/usr_app/app_light_life.c @@ -1,10 +1,10 @@ /****************************************************************************** * @file app_light_life.c - * @brief 灯泡剩余寿命(规格书 13.2 节):实际吸合扣秒 / DP4 复位 / - * 到期告警 / DP3 60s 节流上报 / 边沿立刻落盘 + * @brief 灯泡剩余寿命(规格书 13.2 节):实际吸合扣秒 / APP 写 DP3 / + * 到期告警 / 60s 节流上报 / 边沿立刻落盘 * @author cyWu <1917507415@qq.com> * @date 2026.09.08 - * @version V1.0.0 + * @version V1.0.1 * * @note 扣秒与运行记录同口径:只看 app_relay_get_actual()==1(过零后已吸合)。 * 上下文分工(避免 ISR 写 VM / 切 BLE): @@ -13,7 +13,7 @@ * - loop(500ms 任务):60s 节流同步 gDevData.lighe_remain_time; * 边沿立刻同步 + 推导 gDevData.alarm_code + 立刻落盘。 * BOARD_LIGHT_LIFE_ENABLE=0:不扣秒、不置到期告警;DP3 走 VM/默认 - * 值原样上报(规格书 0.4 条)。 + * 值原样上报(规格书 13.2 / 第 0 章第 8 条)。 ******************************************************************************/ #include @@ -54,8 +54,10 @@ static void life_sync_gdata(uint8_t immediate) gDevData.alarm_code = (s_life.remain == 0) ? 1 : 0; if (immediate) { - app_nvm_save_runrec(); /* 寿命随 runrec item 落盘(规格书 13.2 边沿) */ + local_irq_disable(); s_life.since_save = 0; + local_irq_enable(); + app_nvm_save_runrec(); /* 寿命随 runrec item 落盘(规格书 13.2 边沿) */ } } #endif @@ -147,15 +149,19 @@ void app_light_life_loop(void) } /* 平时落盘节奏:累计点灯满 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 / VM 恢复 / 出厂复位,任务上下文) + * @brief 设置剩余寿命(APP 写 DP3 / 出厂复位,任务上下文) * @param remain 剩余寿命秒,0~0xFFFFFFFF 不封顶 * @return 无 */ @@ -165,6 +171,7 @@ void app_light_life_set_remain(uint32_t remain) local_irq_disable(); s_life.remain = remain; s_life.since_sync = 0; + s_life.since_save = 0; s_life.ms = 0; local_irq_enable(); @@ -172,19 +179,39 @@ void app_light_life_set_remain(uint32_t remain) gDevData.lighe_remain_time = remain; gDevData.alarm_code = (remain == 0) ? 1 : 0; app_nvm_save_runrec(); - s_life.since_save = 0; log_info("life set remain=%lu alarm=%d\n", (unsigned long)remain, gDevData.alarm_code); #else - /* 模块关闭:仅按值回填(VM/默认原样上报,不扣秒不置告警,规格书 0.4) */ + /* 模块关闭:只回填 DP3,不因寿命置告警(规格书 13.2) */ gDevData.lighe_remain_time = remain; - if (remain == 0) { - gDevData.alarm_code = 1; - } #endif } /** - * @brief 灯泡复位(DP4 写 1 / 出厂复位):回额定值、告警清 0 + * @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) diff --git a/apps/usr_app/app_light_life.h b/apps/usr_app/app_light_life.h index 9518238..5cd8f3b 100644 --- a/apps/usr_app/app_light_life.h +++ b/apps/usr_app/app_light_life.h @@ -1,17 +1,16 @@ /****************************************************************************** * @file app_light_life.h - * @brief 灯泡剩余寿命(规格书 13.2 节,DP2/DP3/DP4/DP25) + * @brief 灯泡剩余寿命(规格书 13.2 节,DP2/DP3/DP25;DP4 本版不用) * @author cyWu <1917507415@qq.com> * @date 2026.09.08 - * @version V1.0.0 + * @version V1.0.1 * * @note 额定工时 2592000s(30 天),按继电器**实际吸合**每秒扣 1; * 扣到 0 停住不回绕,置 alarm_code=1(灯泡到期告警,粘住)但**不互锁**, * 灯仍可手动/定时/联动再开(第 12 章仲裁不参与)。 - * DP3 上报节流:平时满 60s 才同步一次 gDevData(协议层 2s 防抖后 - * 自然最多 60s 一包);到期 / DP4 / APP 写 DP3 / 出厂复位走边沿 - * **立刻**同步 + 落盘(任务上下文,禁止 ISR 写 VM)。 - * 剩余寿命持久化随运行记录 item(与 runrec 同写节奏 600s,边沿立刻)。 + * APP 换灯:直接写 DP3(通常写 2592000),不走 DP4。 + * DP3 上报节流:平时满 60s 才同步一次 gDevData;到期 / APP 写 DP3 / + * 出厂复位走边沿立刻同步 + 落盘(任务上下文,禁止 ISR 写 VM)。 ******************************************************************************/ #ifndef __APP_LIGHT_LIFE_H__ @@ -21,7 +20,7 @@ /** * @brief 初始化剩余寿命:RAM 复位为额定值,告警按值推导 - * @note 之后 app_nvm_load 若 VM 有效会调 app_light_life_set_remain 覆盖 + * @note 之后 app_nvm_load 若 VM 有效会调 app_light_life_restore 覆盖 * @return 无 */ void app_light_life_init(void); @@ -41,17 +40,25 @@ void app_light_life_tick(uint16_t dt); void app_light_life_loop(void); /** - * @brief 设置剩余寿命(APP 写 DP3 / VM 恢复 / 出厂复位共用,任务上下文) + * @brief 设置剩余寿命(APP 写 DP3 / 出厂复位,任务上下文) * @param remain 剩余寿命秒,范围 0~0xFFFFFFFF 不封顶 - * @note 立刻生效:同步 gDevData + 推导 alarm_code + 立刻落盘 + 变化上报由 - * 协议层差分自动发出(2s 防抖后);remain>0 清到期告警,=0 置告警 + * @note 立刻生效:同步 gDevData + 推导 alarm_code + 立刻落盘; + * remain>0 清到期告警,=0 置告警。上电灌 VM 请用 restore。 + * APP 换灯:写 DP3=2592000,不要写 DP4。 * @return 无 */ void app_light_life_set_remain(uint32_t remain); /** - * @brief 灯泡复位(DP4 写 1):剩余寿命回到额定值、告警清 0 - * @note WO 不上报;由 jb_product DP4 调用;边沿立刻落盘 + * @brief 上电从 VM 恢复剩余寿命(只改 RAM,不写 flash) + * @param remain VM 中的剩余寿命秒 + * @return 无 + */ +void app_light_life_restore(uint32_t remain); + +/** + * @brief 寿命回到额定值(仅出厂复位内部调用) + * @note APP 换灯请写 DP3,本函数不接 DP4 * @return 无 */ void app_light_life_reset(void); diff --git a/apps/usr_app/app_nvm.c b/apps/usr_app/app_nvm.c index e01716c..b35575e 100644 --- a/apps/usr_app/app_nvm.c +++ b/apps/usr_app/app_nvm.c @@ -1,25 +1,20 @@ /****************************************************************************** * @file app_nvm.c * @brief CBR0 VM 持久化:heat_switch / linkage 20B / time_en / - * time_config1~7 / run_record 31天 / fault_en / alarm_en + * time_config1~7 / run_record 31天 / 剩余寿命 / fault_en / alarm_en * @author cyWu <1917507415@qq.com> - * @date 2026.09.04 - * @version V1.1.0 + * @date 2026.09.08 + * @version V1.2.0 * @history + * - V1.2.0, 2026.09.08, cyWu, runrec item 含寿命 132B,比较缓冲改为 256 * - V1.1.0, 2026.09.04, cyWu, P1 完整实现(4 item 存取 + 加热请求恢复) * - V1.0.0, 2026.09.04, cyWu, P0 空骨架 * - * @note VM item 分配在 syscfg 用户自定义区 [1~49](syscfg_id.h 已占用 - * CFG_USER_PAIR_INFO=2 / TIM_FLAG=3 / VM_ALARM*=4~24 / - * DEVICE_STA=25 / PLAN_INFO1=26 / BATTERY_LEVEL=27), - * 本产品从 28 开始: - * CFG_USER_CBR0_STATE =28 状态(magic+ver+灯泡开关+使能,9B) - * CFG_USER_CBR0_LINKAGE =29 联动配置 linkage_config(20B,P2 写) - * CFG_USER_CBR0_TIME =30 定时配置 time_config[7][11](77B) - * CFG_USER_CBR0_RUNREC =31 运行记录 31×uint32 + slot0 日期 + - * 灯泡剩余寿命(132B,13.2 随 runrec 同写) - * 拆 4 个 item:改灯开关只擦写 9B 小 item,不重写配置区,减少 flash - * 磨损;首次上电(magic/ver 不符)保持 gDevData 全 0 默认。 + * @note VM item 从 28 起:STATE=28(10B)LINKAGE=29(20B) + * TIME=30(77B)RUNREC=31(124B 秒数 + 4B 日期 + 4B 寿命 = 132B)。 + * 比较缓冲必须 ≥ 最大 item;加热垫是 128B,灯罩寿命多 4B,必须 256。 + * 首次上电 magic 无效:开关/定时/记录为 0,剩余寿命由 + * app_light_life_init 置额定 2,592,000(不是全 0)。 * * 落盘基准 = 灯泡**请求**(app_relay_get_request,规格书 13 章"上次 * 灯泡开关请求"),不是实际吸合态;上电按它恢复请求、等过零再吸合。 @@ -58,6 +53,7 @@ #define CBR0_VM_MAGIC 0xCB00 #define CBR0_VM_VER 1 #define CBR0_VM_DEBOUNCE_MS 500 /* 灯泡/使能稳定变化防抖(规格书 13 章) */ +#define CBR0_VM_CMP_BUF_LEN 256 /* ≥ RUNREC 132B;加热垫 128 会把本 item 静默丢掉 */ /** 状态 item(10 字节) */ typedef struct { @@ -87,6 +83,8 @@ typedef struct { uint32_t lighe_remain_time; /**< 灯泡剩余寿命(随 runrec 同写节奏,13.2) */ } __packed Cbr0RunrecVm_t; +typedef char cbr0_runrec_vm_fits[(sizeof(Cbr0RunrecVm_t) <= CBR0_VM_CMP_BUF_LEN) ? 1 : -1]; + #if BOARD_NVM_ENABLE /** NVM 运行时:防抖落盘 + 已落盘状态快照(变化检测基准) */ typedef struct { @@ -122,13 +120,13 @@ static void nvm_collect_time(Cbr0TimeVm_t *out) * @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash * @param item_id VM item id * @param buf 新数据 - * @param len 数据长度(≤128,比较缓冲大小) + * @param len 数据长度(≤CBR0_VM_CMP_BUF_LEN) * @return 无 */ static void nvm_write_item(u16 item_id, void *buf, u16 len) { - /* static:比较缓冲不放栈上(省 128B 栈);本函数只在任务上下文调用,无重入 */ - static uint8_t old[128]; + /* static:比较缓冲不放栈上;只在任务上下文调用,无重入 */ + static uint8_t old[CBR0_VM_CMP_BUF_LEN]; if (len > sizeof(old)) { log_error("nvm item %d len %d too big\n", item_id, len); @@ -275,7 +273,8 @@ void app_nvm_save_runrec(void) /** * @brief 从 VM 加载并应用(开机由 usr_jb_init 调用) * @note 加热恢复 = 按上次**请求**恢复(等过零吸合,规格书 13 章 / 5.1); - * 联动/定时/运行记录回填 gDevData。VM 无效(首次上电)保持全 0 默认。 + * 联动/定时/运行记录回填 gDevData。VM 无效时开关/定时/记录保持 0, + * 剩余寿命保持 init 的额定值。 * @return 无 */ void app_nvm_load(void) @@ -316,8 +315,8 @@ void app_nvm_load(void) if (syscfg_read(CFG_USER_CBR0_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) { app_runrec_unpack(rf.run_record); app_runrec_set_slot0_date(rf.slot0_date); - /* 灯泡剩余寿命覆盖 RAM 默认额定值(13.2;值相同不擦写 flash) */ - app_light_life_set_remain(rf.lighe_remain_time); + /* 只灌 RAM,禁止走 set_remain(那会立刻再写 flash) */ + app_light_life_restore(rf.lighe_remain_time); } gDevData.time_en = st.time_en ? 1 : 0; @@ -338,7 +337,7 @@ void app_nvm_load(void) * @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = **硬件定时器中断上下文** * (异步接口,栈极小)。这里只允许做轻量比较与计时;真正的 * syscfg_write 由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。 - * 曾在此直接调 app_nvm_save 导致栈溢出软复位。 + * 禁止在此调 syscfg_write(中断栈不够)。 * @return 无 */ void app_nvm_tick(uint16_t dt) diff --git a/apps/usr_app/app_nvm.h b/apps/usr_app/app_nvm.h index a793796..0e26c48 100644 --- a/apps/usr_app/app_nvm.h +++ b/apps/usr_app/app_nvm.h @@ -4,13 +4,13 @@ * time_en / time_config1~7(11B) / run_record(31×uint32+日期+寿命) / * fault_en / alarm_en(规格书第 13 章) * @author cyWu <1917507415@qq.com> - * @date 2026.09.04 - * @version V1.1.0,P0 = 空骨架(BOARD_NVM_ENABLE=0) + * @date 2026.09.08 + * @version V1.2.0 * * @note 存:灯泡开关(DP0)、linkage_config(20B, DP11)、time_en(DP12)、 * time_config1~7(11B, DP13~19)、run_record(DP20:31×uint32 + * slot0 日期 + 灯泡剩余寿命 DP3)、fault_en(DP22)、alarm_en(DP24)。 - * 不存:DP1–6(温控/模式等易失)、DP9 当前温度(WO)、联动作废标志、 + * 不存:DP1/DP2/DP5–8(温控/模式等易失)、DP9 当前温度(WO)、联动作废标志、 * 过零 pending、linkage_delete(DP10 脉冲)、BLE 三元组(出厂不清)。 * 写 VM 时机:APP 改配置后(500ms 防抖);运行记录每累计 600s 一次、 * 跨天旋转立刻写;寿命随 runrec 同写(到期/复位/写 DP3 边沿立刻)。 @@ -70,7 +70,7 @@ void app_nvm_tick(uint16_t dt); void app_nvm_flush(void); /** - * @brief 标记业务字段有变化,防抖计时归零(档位/模式/配置变化时调用) + * @brief 标记业务字段有变化,防抖计时归零(定时/使能/故障告警使能变化时调用) * @return 无 */ void app_nvm_mark_dirty(void); diff --git a/apps/usr_app/led.h b/apps/usr_app/led.h index 63343e6..f8db2bb 100644 --- a/apps/usr_app/led.h +++ b/apps/usr_app/led.h @@ -56,7 +56,7 @@ typedef enum { /** * 优先级:高者覆盖低者(handler 返回 1 会拦下更低优先级)。 - * 加热垫红绿互斥:HEAT 只占最低槽,配对/结果/OTA 事件添加前先全清灯, + * 加热灯罩红绿互斥:HEAT 只占最低槽,配对/结果/OTA 事件添加前先全清灯, * 结束后 app_led_run 按继电器实际态自动重新挂 HEAT(无需显式恢复)。 */ typedef enum { diff --git a/apps/usr_jb_proto/Protocol/jb_product.c b/apps/usr_jb_proto/Protocol/jb_product.c index 356dd20..c417f63 100644 --- a/apps/usr_jb_proto/Protocol/jb_product.c +++ b/apps/usr_jb_proto/Protocol/jb_product.c @@ -15,7 +15,7 @@ * ⚠ DP0 上报**实际吸合态**:本文件的赋值只表达"手动请求",app_function_poll * (20ms)会把它覆盖为过零后的真值,协议层差分上报读到的即实际态。 * ⚠ DP3 剩余寿命 / DP25 告警由 app_light_life 维护并写 gDevData,本文件只做 - * APP 写 DP3/DP4 时的入口转发。 + * APP 写 DP3 时的入口转发。DP4 灯泡复位本版写忽略(APP 直接写 DP3)。 * ================================================================ */ #include "jb_product.h" @@ -26,6 +26,7 @@ #include "app_nvm.h" #include "app_function.h" #include "app_light_life.h" +#include "board_pin.h" #define LOG_TAG_CONST APP #define LOG_TAG "[JB_PRD]" @@ -41,7 +42,7 @@ static volatile uint32_t timerMs; /* ════════════════════════════════════════════════════════════════ * userInit — DP 默认值(剩余寿命/告警在 app_light_life_init 与 - * app_nvm_load 恢复,这里先置 0 与首上电语义一致) + * app_nvm_load 恢复,这里先置额定值与首上电语义一致) * ════════════════════════════════════════════════════════════════ */ void userInit(void) { @@ -50,8 +51,10 @@ void userInit(void) gDevData.heat_switch = 0; /* DP0 灯泡开关 0=关 1=开(由 VM 恢复) */ gDevData.temp_control_switch = 0; /* DP1 温控开关(本版写忽略) */ gDevData.light_work_time = 0; /* DP2 灯泡总工作时长(写忽略,上报恒 0) */ - gDevData.lighe_remain_time = 0; /* DP3 灯泡剩余寿命(app_light_life 维护) */ - gDevData.light_reset = 0; /* DP4 灯泡复位(WO,脉冲) */ +#if BOARD_LIGHT_LIFE_ENABLE + gDevData.lighe_remain_time = BOARD_LIGHT_RATED_SEC; /* DP3:先额定,VM 有效时 restore 覆盖 */ +#endif + gDevData.light_reset = 0; /* DP4 灯泡复位(WO,本版写忽略) */ memset(gDevData.temp_control_time, 0, 4); /* DP5 温控时间(本版写忽略) */ gDevData.mode = 0; /* DP6 模式(本版写忽略) */ gDevData.min_temp = 0.0f; /* DP7 最低温度(本版写忽略) */ @@ -116,10 +119,12 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) log_info("dp0 light request=%d\n", gDevData.heat_switch); break; - /* ── DP1/DP2/DP5~8:本版写忽略(规格书 9 章 / 15 章)。 - * 不更新 gDevData → 不动作、不回显、不落盘 ── */ + /* ── DP1/DP2/DP4/DP5~8:本版写忽略(规格书 9 章 / 15 章)。 + * 不更新 gDevData → 不动作、不回显、不落盘。 + * 灯泡复位走 APP 写 DP3(写 2592000 即换灯),不认 DP4 ── */ case DP_ID_TEMP_CONTROL_SWITCH: /* DP1 温控开关 */ case DP_ID_LIGHT_WORK_TIME: /* DP2 灯泡总工作时长(恒 0) */ + case DP_ID_LIGHT_RESET: /* DP4 灯泡复位(预留) */ case DP_ID_TEMP_CONTROL_TIME: /* DP5 温控时间 */ case DP_ID_MODE: /* DP6 模式 */ case DP_ID_MIN_TEMP: /* DP7 最低温度 */ @@ -132,15 +137,6 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) app_light_life_set_remain(ctrl->lighe_remain_time); break; - /* ── DP4 灯泡复位(WO,13.2):写 1 → 回额定值 2,592,000、告警 0、 - * 立刻落盘;写 0 无动作;WO 不上报,值清回 0 ── */ - case DP_ID_LIGHT_RESET: - if (ctrl->light_reset) { - app_light_life_reset(); - } - gDevData.light_reset = 0; - break; - /* ── DP9 当前温度(WO):服务器下发 → 联动用温度(10.2/8 章)。 * 协议层不打包不上报;联动引擎关时也只存 RAM ── */ case DP_ID_CURRENT_TEMP: diff --git a/apps/usr_jb_proto/Protocol/jb_protocol.c b/apps/usr_jb_proto/Protocol/jb_protocol.c index 41ae2be..f35c049 100644 --- a/apps/usr_jb_proto/Protocol/jb_protocol.c +++ b/apps/usr_jb_proto/Protocol/jb_protocol.c @@ -8,6 +8,7 @@ #include "jb_common.h" #include "jb_product.h" #include "jb_ringbuffer.h" +#include "app_light_life.h" #include "stdio.h" /* ════════════════════════════════════════════════════════════════ @@ -179,7 +180,8 @@ static uint16_t jbPackDps(const jb_data_point_t *dp, if (mask[DP_ID_LIGHE_REMAIN_TIME / 8] & (1 << (DP_ID_LIGHE_REMAIN_TIME % 8))) { bitmap[DP_ID_LIGHE_REMAIN_TIME / 8] |= (1 << (DP_ID_LIGHE_REMAIN_TIME % 8)); - jbWriteU32BE(&out[valIdx], (uint32_t)dp->lighe_remain_time); + /* 打包权威剩余寿命,避免 60s 节流导致读请求/全量同步最多落后 59s */ + jbWriteU32BE(&out[valIdx], app_light_life_get_remain()); valIdx += 4; } /* DP5 温控时间 (raw, 4B, RW) */ diff --git a/apps/usr_jb_proto/usr_jb_main.c b/apps/usr_jb_proto/usr_jb_main.c index 3422e77..f3aba3a 100644 --- a/apps/usr_jb_proto/usr_jb_main.c +++ b/apps/usr_jb_proto/usr_jb_main.c @@ -176,12 +176,12 @@ void usr_jb_le_recieve_data(u8 *data, u16 len) void usr_timer_loop(void) { userHandle(); + app_light_life_loop(); /* 先同步 DP3/告警,本拍 0x07 才能带上到期/60s */ jbProtocolHandle(&gDevData); app_th_scan_tick(); /* P2:断连时 500ms 重连广播/500ms 扫描交替 */ app_linkage_run(500); /* P2:联动引擎(任务上下文;仲裁见 app_arbiter) */ app_timer_run(500); /* P2:定时引擎(RTC 门闩 + 窗判断) */ app_runrec_rotate_if_needed(); /* P2:跨天旋转/落盘/灌 DP20 */ - app_light_life_loop(); /* 灯泡寿命:60s 节流同步 / 边沿立刻落盘(13.2) */ app_function_loop(); /* VM 防抖落盘 + 配对/OTA 动作落地 */ #if (BOARD_ZC_ENABLE && BOARD_ZC_DIAG_LOG) diff --git a/apps/usr_periph/board_pin.h b/apps/usr_periph/board_pin.h index 1287938..9a35b79 100644 --- a/apps/usr_periph/board_pin.h +++ b/apps/usr_periph/board_pin.h @@ -128,7 +128,7 @@ #define BOARD_RUN_RECORD_SAVE_SEC 600 /**< 累计吸合 10 分钟写一次 VM */ /*============================================================================*/ -/* 灯泡剩余寿命(规格书 13.2 节,灯罩新增:DP3/DP4/DP25) */ +/* 灯泡剩余寿命(规格书 13.2 节,灯罩新增:DP3/DP25;DP4 本版不用) */ /*============================================================================*/ #define BOARD_LIGHT_RATED_SEC 2592000u /**< 额定工时 30*24*3600(首次上电/复位值) */