删除条件配置功能,修改成联动配置DP,并且增加31天运行记录,修改程序需求
This commit is contained in:
+6
-2
@@ -796,10 +796,14 @@
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="apps/usr_periph/bsp_aht20.h" />
|
||||
<Unit filename="apps/usr_app/app_cond.c">
|
||||
<Unit filename="apps/usr_app/app_linkage.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="apps/usr_app/app_cond.h" />
|
||||
<Unit filename="apps/usr_app/app_linkage.h" />
|
||||
<Unit filename="apps/usr_app/app_runrec.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
<Unit filename="apps/usr_app/app_runrec.h" />
|
||||
<Unit filename="apps/usr_app/app_disp.c">
|
||||
<Option compilerVar="CC" />
|
||||
</Unit>
|
||||
|
||||
+193
-120
@@ -6,13 +6,16 @@
|
||||
| 芯片 | JL7016 / AC7016C(BR28) |
|
||||
| 工程 | `温湿度风扇_soc_JieLi-JL7016` |
|
||||
| 产品码 | ProdtCode=`CBR3`,productCode=`PRCBR3` |
|
||||
| 协议 | 见宝 SOC 55AA,V1.0.0 |
|
||||
| 文档版本 | V2.2.0 |
|
||||
| 日期 | 2026.08.31 |
|
||||
| 协议 | 见宝 SOC 55AA,V1.0.0(2026-09-02 重生:删条件 DP,加联动 / 运行记录 / 心跳预留) |
|
||||
| 文档版本 | V3.0.0 |
|
||||
| 日期 | 2026.09.02 |
|
||||
| 作者 | cyWu |
|
||||
| 读者 | WorkBuddy / 固件开发 |
|
||||
| 参考工程 | `D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016` |
|
||||
| 新协议生成工程 | `D:\Jianbao\Project\产品\爬宠\温湿度风扇\温湿度风扇_soc_JieLi-JL7016_新协议` |
|
||||
|
||||
> **V3.0.0 变更(2026-09-02):** 协议 DP 重生。删除 `cond_en` / `cond_config1~7`(7 组条件引擎整段作废)。新增 DP5 `linkage_delete`、DP6 `linkage_config`(20B,缸体 + 温湿度 min/max)、DP21 `run_record`(31 天运行秒数)、DP22 `heartbeat`(本版不做)。故障/告警/复位号后移,`factory_reset` 改为 DP27。联动按实时温湿度回差开 1 档 / 关风扇,不改 mode;仲裁改为手动 > 定时 > 联动。运行记录按风扇实际在转累加秒,满 10 分钟写 VM。生成协议 `MAX_PACKAGE_LEN` 必须改回 **256**(124B 的 `run_record` 装不进 128)。旧条件 VM **不迁移**。
|
||||
>
|
||||
> **V2.2.0 变更(2026-08-31):** 顺着 V2.1.0 删 `app_power` 的思路,继续清掉同一类"保留但无调用者"的死代码:删除 `app_function_soft_off()` / `app_function_is_power_on()` / `app_function_power_on_silent()`,`gDevData.power` 恒为 1、写死在 `app_function_init()`(不再需要 `s_func.app_power` 状态字段);`usr_on_key_short()` 里对应的"软关态唤醒"死分支一并删除。
|
||||
>
|
||||
> **V2.1.0 变更(2026-08-31):** 关机功能确认彻底不存在(关机=拔电),**整模块删除 `app_power.c/.h`**(原 request/finalize 深睡钩子已无任何触发源,纯累赘代码);`usr_jb_main.c`/`app_function.c`/`app_disp.c` 里所有 `app_power_poweroff_pending()` 判断一并清理。同时把配对/OTA/NVM 落盘的"ISR 打标志"统一收进新模块 `app_defer.c/.h`(见 16 章)。
|
||||
@@ -23,13 +26,13 @@
|
||||
>
|
||||
> **V1.1.0 变更(2026-08-31):** IO 分离。CT1642 改走 **PG0(CLK)/PG1(DATA)**,AHT20 独占 **PC4(SCL)/PC5(SDA)**,两路物理隔离,**删除 `bsp_bus` 共线分时机制**(原 8.3 节整体替换为"IO 独立")。同时把 2.2 节显示协议修正为实测 **18bit 帧**。
|
||||
|
||||
本文是 **已拍板的共同理解**,不是草稿。实现时以本文 + `Doc\温湿度风扇DP配置.xlsx` 为准。Excel 与工程代码冲突时:**Excel 字节布局优先,本文行为优先**。
|
||||
本文是 **已拍板的共同理解**,不是草稿。实现时以本文为准;DP 表以 `dp_definition.json` + 本文第 10.2 / 11.1 / 15 章为准。Excel / JSON 描述与本文冲突时:**多字节字段的偏移与开停行为以本文为准**(JSON 对 DP6 只写了类型摘要,min/max 顺序以 10.2 为准)。
|
||||
|
||||
---
|
||||
|
||||
## 0. WorkBuddy 开干前必读(硬约束)
|
||||
|
||||
1. **先改协议再写业务。** Excel 里 `cond_config1~7` 已是 **7 字节**,当前工程 `jb_protocol.*` / `dp_definition.json` 仍是 6 字节。必须按 Excel 重新生成协议,禁止继续按 6 字节解析。
|
||||
1. **先改协议再写业务。** 从 `_新协议` 工程拷贝已生成的 `jb_protocol.*` / `dp_definition.json` / `jb_product` 骨架,再把现有 `jb_product.c` 业务回调迁过去(禁止用空壳 TODO 覆盖业务)。拷贝后立刻把 `MAX_PACKAGE_LEN` 从生成值 **128 改回 256**,否则 DP21 `run_record`(124B)组不出完整帧。
|
||||
2. **不要把业务算法写进 `jb_product.c`。** 只把 DP 写入 `gDevData` 并转调 `app_*`。组合根是 `usr_jb_main.c`(照门夹)。
|
||||
3. **不要抄门夹的电池 / 低电拒绝开机。** 本机 USB 常电、无电池。**上电即开机**(V2.0.0 起,不再长按 3s 才开机);拔电即关,无软关机深睡。
|
||||
4. **不要把调试 UART 放在 PC5。** 调试 UART TX = **PG6**(`IO_PORTG_06`)。PC5 是 AHT20 SDA(独立使用)。
|
||||
@@ -38,12 +41,14 @@
|
||||
7. 每个模块必须能用 `board_pin.h` 的 `BOARD_xxx_ENABLE` **单独关掉** 仍能编过、能配网上报(关掉的模块返回空操作)。
|
||||
8. 日志全部 **英文**。中文只出现在注释和本文。
|
||||
9. 编码风格对齐门夹 + 工程师规范:文件小写+下划线,`g_` / `s_` 前缀,枚举 `Pascal_t`,接口 `xxx_init` / `xxx_run` 或 tick / `xxx_get_status`。
|
||||
10. **禁止继续实现 7 组条件引擎。** `app_cond` 整模块替换为联动(`app_linkage`);旧 `CFG_USER_CBR3_COND` 作废,不读旧 49B。
|
||||
11. **DP22 心跳本版忽略。** 温湿度仍按协议 2s 变化防抖 / 10min 全量上报,不因 12s 无心跳停报。
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品一句话
|
||||
|
||||
USB 供电的爬宠温湿度风扇:本地按键开关机和调档,双两位一体数码管显示整数温湿度,BLE 连 APP;APP 可下发通风/强力模式、7 组温湿度条件、7 组定时。条件与定时仅在设备处于开机态执行;定时还必须 RTC 已对时。
|
||||
USB 供电的爬宠温湿度风扇:本地按键调档,双两位一体数码管显示整数温湿度,BLE 连 APP;APP 可下发通风/强力模式、1 条缸体联动配置、7 组定时。联动根据实时温湿度与 min/max 回差开 1 档或关风扇;定时还必须 RTC 已对时。风扇实际在转的秒数记 31 天运行记录。
|
||||
|
||||
---
|
||||
|
||||
@@ -63,7 +68,7 @@ USB 供电的爬宠温湿度风扇:本地按键开关机和调档,双两位
|
||||
| 调试 UART TX | **PG6** `IO_PORTG_06` | 只发打印,不占用 PC5 |
|
||||
| USB | Type-C → LDO 3.3V | 无电池、无充电管理 |
|
||||
|
||||
板级文件仍指向 `CONFIG_BOARD_JL701N_DEMO`。产品 GPIO **全部集中到新建** `apps/usr_periph/board_pin.h`,并改 `board_jl701n_demo_cfg.h`:
|
||||
板级文件仍指向 `CONFIG_BOARD_JL701N_DEMO`。产品 GPIO **全部集中到** `apps/usr_periph/board_pin.h`,并改 `board_jl701n_demo_cfg.h`:
|
||||
|
||||
```c
|
||||
#define TCFG_UART0_TX_PORT IO_PORTG_06
|
||||
@@ -107,7 +112,7 @@ apps/earphone/app_main.c
|
||||
app_fan_init() / app_disp_init()
|
||||
usr_timer_add(app_disp_scan, 4ms, priority=1) ← CT1642 扫描,常驻
|
||||
app_th_init() / app_mode_init() / app_arbiter_init()
|
||||
app_cond_init() / app_timer_init()
|
||||
app_linkage_init() / app_timer_init() / app_runrec_init()
|
||||
app_nvm_init() + app_nvm_load() ← 恢复 VM
|
||||
app_function_init()
|
||||
user_key_init() + lock_if_held
|
||||
@@ -119,11 +124,12 @@ apps/earphone/app_main.c
|
||||
1. `jbTimerIrq()` × dt
|
||||
2. `user_key_scan` + `user_key_handle`
|
||||
3. `app_th_poll`(内部 2s 才真正采;IO 分离后采集与显示互不干扰)
|
||||
4. `app_mode_run`(通风/强力状态机;条件/定时引擎在 500ms 任务上下文,见下)
|
||||
4. `app_mode_run`(通风/强力状态机;联动/定时引擎在 500ms 任务上下文,见下)
|
||||
5. `app_disp_run`(闪烁、刷新)
|
||||
6. `app_function_poll`(同步 DP)
|
||||
7. `app_nvm_tick`(变化检测 + 500ms 防抖计时,不写 flash)
|
||||
8. `app_led_run`
|
||||
7. `app_runrec_tick`(风扇在转则累加秒;满 10 分钟只打脏,不写 flash)
|
||||
8. `app_nvm_tick`(变化检测 + 500ms 防抖计时,不写 flash)
|
||||
9. `app_led_run`
|
||||
|
||||
**priority 固定 1、只注册一次,不动态重挂**:本产品 Type-C 长供电,不需要低功耗;
|
||||
且 `app_disp_scan` 本身就以 priority=1 常驻,系统本来就永远无法进入低功耗——
|
||||
@@ -131,8 +137,9 @@ apps/earphone/app_main.c
|
||||
(原 `usr_timer_pick_mode`/`usr_timer_rearm`,规格书 V2.3.0 起废弃)。
|
||||
|
||||
`usr_timer_loop()`(BLE 500ms 任务上下文):`userHandle()` 填温湿度 DP、
|
||||
`jbProtocolHandle()`、`app_cond_run()`/`app_timer_run()`(条件/定时引擎,
|
||||
仲裁见第 12 章)、`app_function_loop()`(VM 落盘 + 配对/OTA 落地)。
|
||||
`jbProtocolHandle()`、`app_linkage_run()`/`app_timer_run()`(联动/定时引擎,
|
||||
仲裁见第 12 章)、`app_runrec_rotate_if_needed()`、`app_function_loop()`
|
||||
(VM 落盘 + 配对/OTA 落地)。
|
||||
|
||||
可直接复用门夹源码(拷贝后改引脚/事件表,不要改框架语义):
|
||||
|
||||
@@ -144,7 +151,7 @@ apps/earphone/app_main.c
|
||||
| 配对会话 | `app_pair.c` | 120s 超时,灯效改单灯 |
|
||||
| ~~软关机~~ | ~~`app_power.c`~~ | **V2.1.0 已整模块删除**:无电池、无充电,关机=拔电,无软件关机路径 |
|
||||
| RTC | `usr_rtc.c` | 原样 |
|
||||
| 协议骨架 | `usr_jb_proto` | 重生 7 字节条件 |
|
||||
| 协议骨架 | `usr_jb_proto` | 从 `_新协议` 工程拷贝后改 `MAX_PACKAGE_LEN=256`,迁回业务回调 |
|
||||
|
||||
**不要拷贝:** `app_battery.c`、充电 GPIO、电机 `app_mode` 步进表(本产品 mode 是通风/强力,重写)。
|
||||
|
||||
@@ -161,8 +168,9 @@ apps/earphone/app_main.c
|
||||
#define BOARD_DISP_ENABLE 1
|
||||
#define BOARD_TH_ENABLE 1
|
||||
#define BOARD_MODE_ENABLE 1 /* 通风/强力状态机 */
|
||||
#define BOARD_COND_ENABLE 1 /* 条件引擎;P0/P1 可先 0 */
|
||||
#define BOARD_LINKAGE_ENABLE 1 /* 联动引擎;替换原 BOARD_COND_ENABLE */
|
||||
#define BOARD_TIMER_ENABLE 1 /* 定时引擎;P0/P1 可先 0 */
|
||||
#define BOARD_RUNREC_ENABLE 1 /* 31 天运行记录 */
|
||||
#define BOARD_NVM_ENABLE 1 /* VM 持久化;P0 可 0 */
|
||||
```
|
||||
|
||||
@@ -173,7 +181,7 @@ apps/earphone/app_main.c
|
||||
- 都为 1:**两路各走各的**,无锁、无分时(见 8.3)。
|
||||
- 都为 0:两组脚保持输入/高阻,可单独调 KEY/LED/FAN/BLE。
|
||||
|
||||
P0 建议先 `MODE/COND/TIMER/NVM=0`,台上只验按键、灯、PWM、数码管、传感器。
|
||||
P0 建议先 `MODE/LINKAGE/TIMER/RUNREC/NVM=0`,台上只验按键、灯、PWM、数码管、传感器。
|
||||
|
||||
---
|
||||
|
||||
@@ -223,7 +231,7 @@ P0 建议先 `MODE/COND/TIMER/NVM=0`,台上只验按键、灯、PWM、数码
|
||||
|
||||
| 事件 | 行为 |
|
||||
|---|---|
|
||||
| 短按(<1s 松开) | 风扇档位循环:1(50%) → 2(75%) → 3(100%) → 0(关风扇) → 1 … **立刻退出通风/强力**,改为手动档。设备仍开机,数码管继续刷 |
|
||||
| 短按(<1s 松开) | 风扇档位循环:1(50%) → 2(75%) → 3(100%) → 0(关风扇) → 1 … **立刻退出通风/强力**,改为手动档;**联动本轮作废**(第 12 章)。设备仍开机,数码管继续刷 |
|
||||
| 按住 5s 后松开 | 清绑定 + 进配对(见 5.2) |
|
||||
| 按住 12s | 进 OTA(见 5.2) |
|
||||
| ~~长按 3s 软关机~~ | **V2.0.0 已取消**,拔电即关 |
|
||||
@@ -282,10 +290,14 @@ APP DP3 仍是 0.1℃ 精度 float;显示不跟小数。
|
||||
|
||||
### 8.2 告警闪烁(与吹风解耦)
|
||||
|
||||
- 任一 **已使能** 且 `cond_en=1` 的条件组,其温度比较 **当前已成立**(**不等待** Byte4 时长)→ 温度管 **500ms 亮 / 500ms 灭**。
|
||||
- 湿度同理。两路可同时闪。
|
||||
- 比较不再成立 → 该侧停闪,常亮显示数值。
|
||||
- 温湿度比较异常只驱动对应管子闪烁,**不上报** `fault_code`。
|
||||
有 **有效联动配置**(第 10.1 节)时,按 **是否落在 [min, max] 闭区间内** 闪,**不等风扇真的开**:
|
||||
|
||||
- 温度管:`T > Tmax` **或** `T < Tmin` → **500ms 亮 / 500ms 灭**。`Tmin ≤ T ≤ Tmax`(含等于)→ 常亮。
|
||||
- 湿度管:`H > Hmax` **或** `H < Hmin` → 同样 500ms 闪。`Hmin ≤ H ≤ Hmax` → 常亮。
|
||||
- 两路可同时闪。过冷、过干 **只闪不开风扇**(风扇开停见 10.3)。
|
||||
- 跟联动是否被手动作废无关:超范围仍闪,方便看见超标。
|
||||
- 无有效联动:不因温湿度闪。
|
||||
- 温湿度出范围只驱动对应管子闪烁,**不上报** `fault_code`。
|
||||
- 传感器连续失败 3 次:显示 `--`,同步 `fault_code=1`。`fault_code=2` 预留。
|
||||
- `alarm_code` 预留,不上报具体码(保持 0)。
|
||||
- `BOARD_DISP_ENABLE=0` 时不闪、不驱动管子,fault 仍可上报。
|
||||
@@ -313,11 +325,11 @@ CT1642 用 **PG0(CLK)/PG1(DATA)**(推挽,`bsp_ct1642_init()` 配置),AHT
|
||||
- 等待 AHT20 的 80ms 由 `app_th_poll` 状态机跨 tick 完成(期间 `clr_wdt`),不阻塞 20ms 主节拍。
|
||||
- 共线时代遗留约束(已作废,勿再套用):原"锁只包裹真实硬件交互窗口、长等待必放锁"适用于 PC4/PC5 共线方案,IO 分离后不需要。
|
||||
|
||||
### 8.4 采样失败与条件计时
|
||||
### 8.4 采样失败与联动
|
||||
|
||||
- 条件「持续时长」只在 **本次采样成功且比较成立** 时累加。
|
||||
- 采样失败:不累加、不清零已累计时长;显示保持上次有效值,直到连续 3 次失败改 `--`。
|
||||
- `--` 期间条件比较视为不成立(不误触发吹风)。
|
||||
- 联动只在 **本次采样成功** 时用新值比较;采样失败保持上一有效值继续判(与显示一致),直到连续 3 次失败改 `--`。
|
||||
- `--` 期间联动视为不成立:若当前是联动占着风扇 → **关掉**(`fan_gear=0`),避免盲吹。
|
||||
- 过冷、过干可以闪管子,**不会因为过冷/过干去开风扇**。
|
||||
|
||||
---
|
||||
|
||||
@@ -325,7 +337,7 @@ CT1642 用 **PG0(CLK)/PG1(DATA)**(推挽,`bsp_ct1642_init()` 配置),AHT
|
||||
|
||||
| 值 | 名称 | 行为 |
|
||||
|---|---|---|
|
||||
| 0 | 关闭模式 | 不按模式表驱动风扇;风扇跟手动档 / 其它仲裁胜者 |
|
||||
| 0 | 关闭模式 | 不按模式表驱动风扇;风扇跟手动档 / 定时 / 联动 |
|
||||
| 1 | 通风 | 强制 1 档,开 **25min**,停 **5min**,循环,直到退出 |
|
||||
| 2 | 强力 | 强制 3 档,连续 **15min** 后:风扇 0,mode 回到 0 |
|
||||
|
||||
@@ -335,7 +347,8 @@ CT1642 用 **PG0(CLK)/PG1(DATA)**(推挽,`bsp_ct1642_init()` 配置),AHT
|
||||
- APP 写 `fan_gear`
|
||||
- APP 写 `mode=0`
|
||||
- 定时窗结束(第 11 章)
|
||||
- 条件恢复快照(第 10 章,若当前模式是条件开的)
|
||||
|
||||
联动 **不改 `mode`**:开只把 `fan_gear` 写成 1,关只写成 0。通风/强力仍只由手动和定时启动。
|
||||
|
||||
**开机恢复(不存相位):** VM 里 `mode=1` 则从 25min 开段 **从头** 计;`mode=2` 则从 15min **从头** 计。
|
||||
|
||||
@@ -343,51 +356,62 @@ DP 同步:`mode` = 逻辑模式;`fan_gear` = 实际 PWM 档(通风停段
|
||||
|
||||
---
|
||||
|
||||
## 10. 条件引擎(P2,7 组)
|
||||
## 10. 联动引擎(替换原 7 组条件)
|
||||
|
||||
### 10.1 总开关
|
||||
无 `cond_en`。设备只有 **1 条** 20 字节联动配置(DP6)。不切通风/强力,不恢复档位快照。
|
||||
|
||||
- `cond_en=0`:不启动新条件;若正在因条件跑模式,视为条件结束,**按 Q9 A3 恢复触发前快照**。
|
||||
- `cond_en=1`:扫描 config1~7。
|
||||
### 10.1 有效配置
|
||||
|
||||
### 10.2 7 字节布局(Excel 枚举列;备注里 Byte5 位域是笔误,以本表 Byte6 为准)
|
||||
同时满足才跑联动开停(闪烁仍见 8.2,只要有效配置就判闪):
|
||||
|
||||
- 缸体编号 ≠ 0(Byte0–1,uint16 大端)
|
||||
- `Tmin ≤ Tmax` 且 `Hmin ≤ Hmax`
|
||||
- `BOARD_LINKAGE_ENABLE=1`
|
||||
|
||||
否则视为无联动:不自动开 1 档;若原先是联动占着风扇则关掉。
|
||||
|
||||
缸体编号只存、不参与开停判断(本机一路 AHT20)。配置版本(Byte6)只存不解释。Byte7–19 预留:整包 20B 原样落盘,控制逻辑忽略。
|
||||
|
||||
### 10.2 20 字节布局(DP6 `linkage_config`)
|
||||
|
||||
| 偏移 | 字段 | 编码 |
|
||||
|---|---|---|
|
||||
| Byte0 | 使能 | 0=关,非 0=开 |
|
||||
| Byte1–2 | 温度阈值 | **有符号 int16 大端**,传输值 = ℃ × 10。例 30.5℃ → `0x01 0x31`;−5.0℃ → `0xFF 0xCE` |
|
||||
| Byte3 | 湿度阈值 | 0~100,单位 % |
|
||||
| Byte4 | 持续分钟 | 1~255 按分钟计;**0 = 比较一成立立即触发** |
|
||||
| Byte5 | 动作 | **模式**:0=关闭模式,1=通风,2=强力。**不是档位** |
|
||||
| Byte6 | 条件 | Bit7–5 温度比较,Bit4–2 湿度比较,Bit1–0 保留 |
|
||||
| Byte0–1 | 缸体编号 | uint16 **大端** |
|
||||
| Byte2 | 最低温度 `Tmin` | 有符号 int8,单位 ℃ |
|
||||
| Byte3 | 最高温度 `Tmax` | 有符号 int8,单位 ℃ |
|
||||
| Byte4 | 最低湿度 `Hmin` | uint8,单位 % |
|
||||
| Byte5 | 最高湿度 `Hmax` | uint8,单位 % |
|
||||
| Byte6 | 配置版本 | uint8,只存不用 |
|
||||
| Byte7–19 | 预留 | 只存不用 |
|
||||
|
||||
比较编码(3 bit):
|
||||
温度比较用设备内部 0.1℃(`temp_x10`):`T > Tmax` 等价 `temp_x10 > Tmax * 10`。湿度用整数 %。**没有等于比较**:`T == Tmax` 不算超最高,也不算低于最低。
|
||||
|
||||
| 值 | 含义 |
|
||||
### 10.3 开停(回差 + 超最高优先)
|
||||
|
||||
在有效配置、未被手动作废、且仲裁允许联动占风扇时,每个 500ms 任务 tick 判一次:
|
||||
|
||||
**开(一侧满足即可):** `T > Tmax` **或** `H > Hmax` → `fan_gear = 1`,**不改 `mode`**。一直保持到关条件成立。
|
||||
|
||||
**关(一侧满足即可,但超最高优先):**
|
||||
|
||||
1. 若仍有一侧 `> max` → **保持开**(例:33℃ 且湿度已低于 `Hmin` → 继续吹)。
|
||||
2. 否则,若至少一侧 `< min` → `fan_gear = 0`。
|
||||
3. 否则(两侧都落在 `[min, max]` 闭区间,含等于)→ **保持上一态**。
|
||||
|
||||
过冷、过干 **不开风扇**(`T < Tmin` 或 `H < Hmin` 只参与「可以关」和数码管闪)。
|
||||
|
||||
不恢复触发前档位:联动开 = 写 1,联动关 = 写 0。
|
||||
|
||||
### 10.4 联动删除(DP5)
|
||||
|
||||
| 写入 | 行为 |
|
||||
|---|---|
|
||||
| 000 | 无条件(忽略该侧) |
|
||||
| 001 | < |
|
||||
| 010 | <= |
|
||||
| 011 | == |
|
||||
| 100 | >= |
|
||||
| 101 | > |
|
||||
| 110/111 | 视为无条件 |
|
||||
| 0 | 无操作 |
|
||||
| 1 | 清 RAM + VM 里 20 字节配置(全 0);若当前是联动占着风扇 → `fan_gear=0`;然后把 `linkage_delete` **清回 0** 再上报(脉冲) |
|
||||
|
||||
两侧都「无条件」的组视为无效,不触发。
|
||||
两侧都有效时 **AND**。一侧无条件则只判另一侧。
|
||||
无回差,比较式怎么写怎么判。
|
||||
### 10.5 APP 重写 DP6
|
||||
|
||||
### 10.3 触发与保持
|
||||
|
||||
1. 组使能且 `cond_en=1`,比较成立,开始累加持续时间(采样周期 2s,满 `Byte4` 分钟)。
|
||||
2. 未满时长时比较变不成立 → 该组计时清零。
|
||||
3. 满时长 → **触发**:快照当前 `mode`+`fan_gear`(若尚无「条件占用」),然后切到 Byte5 模式。
|
||||
4. 触发后比较仍成立 → **保持**(C2)。强力 15min 到点若仍成立 → **立即再开一轮 15min 强力**(Q16 A1),不要恢复快照。
|
||||
5. 比较变不成立 → 本轮条件结束,**恢复快照**(Q9 A3),该组重新进入「需满时长」。
|
||||
6. 另一组后触发 → **覆盖** 当前条件模式,**不回退条件栈**(D1)。快照仍是 **第一次条件抢权之前** 的用户/定时状态(后覆盖不改快照)。后者失效且比较不成立 → 恢复这份原始快照,即使前者比较仍成立也不自动回去(D1)。前者若要再开,必须重新满时长。
|
||||
7. 手动/APP 介入:见第 12 章,本轮条件作废(RAM 标志,**不写 VM**)。必须先比较不成立,再成立并满时长,才允许再次触发。
|
||||
|
||||
通风由条件启动时:按 25/5 循环,直到第 5/6/7 条退出。
|
||||
`memcpy` 20 字节 → 存 VM → **清手动作废标志** → 立刻按新 min/max 重判(该开开、该关关)。缸体编号写成 0:按无效配置处理,等同删除(联动占着则关)。
|
||||
|
||||
---
|
||||
|
||||
@@ -411,7 +435,7 @@ DP 同步:`mode` = 逻辑模式;`fan_gear` = 实际 PWM 档(通风停段
|
||||
- 类型 2:仅位图置位的星期(跨午夜的窗,以 **开始时刻所在的本地星期** 判断是否该跑)。
|
||||
- 类型 0 单次:结束时刻过后把 **Byte0 清 0 并写 VM**,不再跑。
|
||||
|
||||
### 11.2 RTC 门闩(Q20,强制)
|
||||
### 11.2 RTC 门闩(强制)
|
||||
|
||||
```
|
||||
usr_rtc_get_time() 失败 → 全部定时不执行(等同 time_en=0)
|
||||
@@ -425,11 +449,10 @@ usr_rtc_get_time() 失败 → 全部定时不执行(等同 time_en=0)
|
||||
|
||||
### 11.3 窗内行为
|
||||
|
||||
- 进入窗:切到 Byte1 模式(通风循环或强力一轮)。
|
||||
- 强力:只跑 **一轮 15min**,自行停下(mode=0,fan=0),**等到结束时刻再做一次收口**(已经停则保持停)。窗内不循环强力(Q16 B1)。
|
||||
- 进入窗:切到 Byte1 模式(通风循环或强力一轮)。窗内 **联动不插嘴**。
|
||||
- 强力:只跑 **一轮 15min**,自行停下(mode=0,fan=0),**等到结束时刻再做一次收口**(已经停则保持停)。窗内不循环强力。
|
||||
- 通风:窗内一直 25/5,直到结束时刻。
|
||||
- 结束时刻:风扇 0,mode=0。
|
||||
- 结束时若条件比较仍成立:**不要** 立刻让条件接手,必须重新满时长(第 12 章)。
|
||||
- 结束时刻:风扇 0,mode=0。然后 **立刻按第 10.3 节判一次联动**(不是手动作废):仍超最高 → 开 1 档。
|
||||
|
||||
---
|
||||
|
||||
@@ -439,75 +462,111 @@ usr_rtc_get_time() 失败 → 全部定时不执行(等同 time_en=0)
|
||||
|
||||
1. **手动**:短按、APP 写 `fan_gear`、APP 写 `mode`
|
||||
2. **定时窗**(RTC 有效且 `time_en=1` 且当前落在窗内)
|
||||
3. **条件**(`cond_en=1` 且已触发保持中)
|
||||
3. **联动**(有效配置且已因超最高占着风扇,或本 tick 满足开条件)
|
||||
|
||||
规则:
|
||||
|
||||
| 冲突 | 胜者 | 副作用 |
|
||||
|---|---|---|
|
||||
| 手动 vs 正在跑的模式(无论谁开的) | 手动 | 立刻退模式;若原是条件开的 → 条件本轮作废(RAM) |
|
||||
| 手动 vs 正在跑的模式 / 联动 | 手动 | 立刻退模式;若原是联动占着 → **本轮联动作废**(RAM,不写 VM) |
|
||||
| 手动 vs 定时窗 | 手动 | 本窗剩余时间不再自动抢回;等到下一窗开始沿或下次对时补窗 |
|
||||
| 定时窗 vs 条件 | 定时 | 条件即使已满时长也不开;窗结束后条件要重新计时长 |
|
||||
| 条件 vs 空闲 | 条件 | 快照空闲态的 mode/gear |
|
||||
| 两路条件 | 后触发覆盖 | 不回栈 |
|
||||
| 定时窗 vs 联动 | 定时 | 窗内联动不开、不改档;窗结束收口后立刻按 10.3 重判(见 11.3) |
|
||||
| 联动 vs 空闲 | 联动 | 开则写 `fan_gear=1`,关则写 0;不改 mode、不快照 |
|
||||
|
||||
作废标志不存 VM。冷启动后条件从 0 重新计时(即使拔电前用户刚按停)。
|
||||
作废标志不存 VM。
|
||||
|
||||
`cond_en`/`time_en` 运行中被写成 0:立刻停对应自动化;条件停 → 恢复快照;定时停 → 窗结束语义。
|
||||
**手动作废后的再武装:** 必须先走到第 10.3 节的「关」条件(没有一侧还超最高,且至少一侧 `< min`),再重新出现 `T > Tmax` 或 `H > Hmax`,才允许再开 1 档。作废期间数码管仍按 8.2 闪。
|
||||
|
||||
**冷启动:** 作废标志是 RAM,上电为空。VM 里即使恢复的是手动 2/3 档,只要已超最高且无定时窗占用,**允许联动立刻改成 1 档**。用户不想吹再短按,那一次才作废。
|
||||
|
||||
`time_en` 运行中被写成 0:立刻停定时(窗结束语义)。无联动总开关;无效配置 / DP5 删除即停联动。
|
||||
|
||||
---
|
||||
|
||||
## 13. 持久化(VM)
|
||||
|
||||
存(冷启动 / 软关都保留):
|
||||
存(冷启动保留):
|
||||
|
||||
- `mode`、`fan_gear`
|
||||
- `cond_en`、`cond_config1~7`(各 7B)
|
||||
- `linkage_config`(20B)
|
||||
- `time_en`、`time_config1~7`(各 8B)
|
||||
- `fault_en`、`alarm_en`(若有写入)
|
||||
- `run_record`:31 × uint32 秒 + slot[0] 对应的本地日期(对时后才有意义)
|
||||
|
||||
不存:
|
||||
|
||||
- DP0(忽略)
|
||||
- 条件作废标志、模式已运行秒数、AHT20 原始采样
|
||||
- 联动作废标志、模式已运行秒数、AHT20 原始采样
|
||||
- `linkage_delete`(脉冲,RAM 瞬态)
|
||||
- BLE 三元组(走现有 `CFG_USER_PAIR_INFO`,出厂重置 **不清**)
|
||||
|
||||
上电(V2.0.0 起无需按键)后:加载上述字段,**按上次 mode/gear 从周期起点继续工作**。
|
||||
上电后:加载上述字段,**按上次 mode/gear 从周期起点继续工作**,再让联动按第 12 章冷启动规则判一次。
|
||||
|
||||
首次上电(VM 无效):mode=0,gear=0,条件/定时全 0,`cond_en=time_en=0`。
|
||||
首次上电(VM 无效):mode=0,gear=0,联动 20B 全 0,定时全 0,`time_en=0`,31 天运行记录全 0。
|
||||
|
||||
写 VM 时机:APP 改配置后、单次定时自毁使能时、档位/模式稳定变化时(可做 500ms 防抖,避免短按循环打爆 Flash)。
|
||||
旧 `CFG_USER_CBR3_COND`(49B 条件)**作废不读**。联动 / 运行记录换新 VM key 或升 `ver`,当新设备。
|
||||
|
||||
写 VM 时机:
|
||||
|
||||
- APP 改联动/定时、单次定时自毁、档位/模式稳定变化:500ms 防抖(避免短按循环打爆 Flash)
|
||||
- 运行记录:风扇累计转满 **10 分钟** 写一次;**跨天旋转时立刻写**(见 13.1)
|
||||
|
||||
### 13.1 运行记录(DP21 `run_record`,RO,124B)
|
||||
|
||||
31 天 × 4 字节,每格 uint32 **大端**,单位秒:
|
||||
|
||||
| 偏移 | 含义 |
|
||||
|---|---|
|
||||
| Byte0–3 | 今天风扇运行总秒数 |
|
||||
| Byte4–7 | 昨天 |
|
||||
| … | … |
|
||||
| Byte120–123 | 前 30 天 |
|
||||
|
||||
**什么叫在运行:** 风扇实际在转(PWM 占空比 > 0 / `fan_gear != 0` 的真实输出)。手动、通风开段、强力、定时、联动都算。通风停 5 分钟那段 **不算**。
|
||||
|
||||
**日期轴:**
|
||||
|
||||
- 未对时:只往 slot[0] 累加,**不旋转**。
|
||||
- 对时成功:按本地日历把数组左移 N 天(跨了几天就移几天),新今天从 0 或当天已累加值继续。
|
||||
- 掉电再上电:先把 VM 里 31 格灌进 `gDevData.run_record`;未对时继续往 slot[0] 加。
|
||||
|
||||
**落盘:** 每累计运行满 600 秒写一次 VM。跨天立刻写。拔电没有软关机钩子,**接受最多丢不满 10 分钟的秒数**。
|
||||
|
||||
`BOARD_RUNREC_ENABLE=0`:不累加、DP21 报全 0。
|
||||
|
||||
---
|
||||
|
||||
## 14. 出厂重置(DP25 写 1)
|
||||
## 14. 出厂重置(DP27 写 1)
|
||||
|
||||
- **清全部业务 VM**(第 13 章列表)。
|
||||
- **清全部业务 VM**(第 13 章列表,含联动 20B 与 31 天运行记录)。
|
||||
- **保留 BLE 配对,不断开、不软复位。**
|
||||
- RAM 中 mode=0,gear=0,风扇停,条件/定时停止。
|
||||
- RAM 中 mode=0,gear=0,风扇停,联动/定时停止,作废标志清掉。
|
||||
- 换机:运行中 **按住 5s 后松手** 清配对进绑定广播(见 5.2,无需关机)。
|
||||
|
||||
---
|
||||
|
||||
## 15. BLE / DP 行为摘要
|
||||
|
||||
身份:`CBR3` / `PRCBR3`。帧格式见已生成 `jb_protocol.h`(重生后以新头文件为准)。
|
||||
身份:`CBR3` / `PRCBR3`。帧格式见已生成 `jb_protocol.h`(以 `_新协议` 拷贝后、`MAX_PACKAGE_LEN=256` 的头文件为准)。DP 号 7–12 空置,不要填回条件配置。
|
||||
|
||||
| DP | 处理 |
|
||||
|---|---|
|
||||
| 0 power | 预留。运行中上报 1,写忽略 |
|
||||
| 1 mode | 写则手动抢权,切通风/强力/关 |
|
||||
| 2 fan_gear | 写则手动抢权,退模式 |
|
||||
| 2 fan_gear | 写则手动抢权,退模式,联动作废 |
|
||||
| 3/4 温湿度 | 仅上报。变化防抖 2s,全量 10min(协议已有) |
|
||||
| 5 cond_en | 见第 10/12 章 |
|
||||
| 6~12 cond_config | 7 字节 memcpy + 存 VM + 条件状态机复位该组计时 |
|
||||
| 5 linkage_delete | 第 10.4 节,脉冲 |
|
||||
| 6 linkage_config | 20 字节 memcpy + 存 VM + 第 10.5 节立刻重判 |
|
||||
| 13 time_en | 见第 11 章 |
|
||||
| 14~20 time_config | 8 字节 + 存 VM |
|
||||
| 21/23 en | 原样存,P0 不做位级过滤 |
|
||||
| 22 fault_code | 0 无 / 1 传感器故障 / 2 预留 |
|
||||
| 24 alarm_code | 保持 0 |
|
||||
| 25 factory_reset | 第 14 章 |
|
||||
| 26~28 | 存 RAM/VM 可不实现动作 |
|
||||
| 21 run_record | RO,由 `app_runrec` 填 124B,禁止 APP 写 |
|
||||
| 22 heartbeat | 本版忽略(写了也不停温湿度上报) |
|
||||
| 23 fault_en | 原样存,P0 不做位级过滤 |
|
||||
| 24 fault_code | 0 无 / 1 传感器故障 / 2 预留 |
|
||||
| 25 alarm_en | 原样存 |
|
||||
| 26 alarm_code | 保持 0 |
|
||||
| 27 factory_reset | 第 14 章 |
|
||||
| 28~30 | 存 RAM,可不实现动作 |
|
||||
|
||||
OTA:运行态按住 12s 走门夹 `usr_app_ota_init`(在 `app_function_loop` 任务上下文调用,灯效 200ms 闪)。`jbOnOtaNotify` 等协议 OTA **P2 可继续 stub**,注释写明未实现,禁止假装升级成功后擦用户区。
|
||||
|
||||
@@ -535,14 +594,15 @@ apps/usr_app/
|
||||
app_disp.c/.h # 数值、闪烁、--
|
||||
app_th.c/.h # 2s 采样、失败计数
|
||||
app_mode.c/.h # 通风/强力(重写,勿用门夹电机表)
|
||||
app_cond.c/.h # P2
|
||||
app_linkage.c/.h # 替换 app_cond;DP5/DP6 + 回差开停
|
||||
app_runrec.c/.h # 31 天秒数、10 分钟落盘、跨天旋转
|
||||
app_timer_task.c/.h # P2,避免与 usr_rtc 文件名混
|
||||
app_nvm.c/.h
|
||||
app_nvm.c/.h # 换掉 cond 49B,改为 linkage 20B + run_record
|
||||
app_function.c/.h # DP 与按键总装
|
||||
app_arbiter.c/.h # 可选:把第 12 章收成一处,避免 mode/cond/timer 互调成环
|
||||
app_arbiter.c/.h # 第 12 章:COND owner 改为 LINKAGE
|
||||
```
|
||||
|
||||
`usr_jb_main.c` 只编排,不写算法。
|
||||
`usr_jb_main.c` 只编排,不写算法。删除或抽空 `app_cond.c/.h`,禁止两套引擎并存。
|
||||
|
||||
---
|
||||
|
||||
@@ -565,36 +625,44 @@ apps/usr_app/
|
||||
- [ ] 12s 能进 OTA 广播(可先只打日志 + LED)
|
||||
- [ ] 长按进配对/OTA 后不复位(验证动作确实走的任务上下文)
|
||||
|
||||
**P0 关闭:** `BOARD_MODE/COND/TIMER/NVM_ENABLE=0`
|
||||
**P0 关闭:** `BOARD_MODE/LINKAGE/TIMER/RUNREC/NVM_ENABLE=0`
|
||||
|
||||
### P1 — 最小产品闭环
|
||||
|
||||
- [ ] 5s 配对,APP 能读温湿度,能写档位/模式
|
||||
- [ ] 通风 25/5、强力 15min 后停;短按立即退模式
|
||||
- [ ] 超限闪(可用调试宏灌假温度)、失败 `--`
|
||||
- [ ] 失败 `--`;有联动配置时超范围闪(可用调试宏灌假温度)
|
||||
- [ ] DP0 写 0 风扇不变、设备不关
|
||||
- [ ] VM:改档/改模式/写下条件定时后断电,重新上电从周期头恢复 mode
|
||||
- [ ] VM:改档/改模式/写下联动与定时后断电,重新上电从周期头恢复 mode
|
||||
- [ ] 定时引擎可只存不跑(`BOARD_TIMER_ENABLE=0` 仍能保存 DP)
|
||||
- [ ] 新协议 DP 号:读不到旧的 cond_config;能读写 DP6 20B、能读 DP21
|
||||
|
||||
### P2 — 自动化
|
||||
|
||||
- [ ] 条件 7 字节解析与 AND、时长、快照恢复、后覆盖不回栈
|
||||
- [ ] 条件强力循环 15min(仍超限再来一轮)
|
||||
- [ ] 手动作废条件,须回安全再满时长
|
||||
- [ ] DP6 按 10.2 解析;缸体号 0 / min>max 不跑联动
|
||||
- [ ] 超最高开 1 档;回差保持;超最高优先(热着湿已低于最低仍吹)
|
||||
- [ ] 过冷过干不吹,但出范围闪对应管子
|
||||
- [ ] 手动作废后须先到「关」条件再超最高才再开
|
||||
- [ ] `--` 时联动占着则关风扇
|
||||
- [ ] 定时 RTC 门闩:未对时绝不跑;对时后补窗
|
||||
- [ ] 跨午夜、单次自毁使能
|
||||
- [ ] 定时强力只一轮;窗结束收口;结束后条件重新计时
|
||||
- [ ] 出厂重置清业务留配对
|
||||
- [ ] 定时强力只一轮;窗结束收口后立刻把联动判一次
|
||||
- [ ] DP5 脉冲删除;DP6 重写立刻重判
|
||||
- [ ] 运行记录:转 10 分钟落盘;对时后跨天左移;通风停段不计入
|
||||
- [ ] 出厂重置清业务(含运行记录)留配对
|
||||
- [ ] `MAX_PACKAGE_LEN=256`,APP 能读完整 124B 运行记录
|
||||
- [ ] 仲裁表第 12 章逐条用例(见下)
|
||||
|
||||
### P2 仲裁最小用例(必须写进测试笔记)
|
||||
|
||||
1. 手动 2 档 → 条件触发通风 → 应切通风;短按到 0 → 风扇停且保持停,即使仍超限。
|
||||
2. 用例 1 之后温湿度回到安全再超限,满时长才再次通风。
|
||||
3. 定时窗启动强力 → 15min 停 → 窗未结束前保持停 → 结束时刻仍停。
|
||||
4. 定时窗内再短按切 1 档 → 保持 1 档到窗结束也不要被定时抢回。
|
||||
5. 未对时,把设备时钟拨乱或不对时 → 定时配置使能也不转。对时后若在窗内立即进模式。
|
||||
6. 两组条件先后触发,后者强力覆盖通风;后者失效恢复 **手动快照** 而不是第一组通风。
|
||||
1. 空闲超最高 → 风扇 1 档;短按到 0 → 保持停,即使仍超最高。
|
||||
2. 用例 1 之后先到「关」条件(至少一侧 `< min` 且没有一侧超最高),再超最高,才再次 1 档。
|
||||
3. 定时窗启动强力 → 15min 停 → 窗未结束前保持停 → 结束时刻仍停;若此时仍超最高 → **开 1 档**(联动接手)。
|
||||
4. 定时窗内再短按切 2 档 → 保持 2 档到窗结束也不要被定时抢回;窗结束若仍超最高且已被手动作废 → **不要**被联动立刻改回 1 档。
|
||||
5. 未对时,定时配置使能也不转。对时后若在窗内立即进模式。
|
||||
6. 上电 VM 为 2 档且已超最高、无定时窗 → 联动立刻改 1 档。
|
||||
7. 33℃ + 湿度已低于 `Hmin` → 风扇保持 1 档(超最高优先)。
|
||||
8. 20℃、湿度在带内、风扇已关 → 保持关(过冷不吹),温度管闪。
|
||||
|
||||
---
|
||||
|
||||
@@ -637,6 +705,9 @@ apps/usr_app/
|
||||
#define BOARD_MODE_VENT_ON_MS (25u * 60u * 1000u)
|
||||
#define BOARD_MODE_VENT_OFF_MS (5u * 60u * 1000u)
|
||||
#define BOARD_MODE_POWER_ON_MS (15u * 60u * 1000u)
|
||||
|
||||
#define BOARD_RUN_RECORD_DAYS 31
|
||||
#define BOARD_RUN_RECORD_SAVE_SEC 600 /* 累计运行 10 分钟写一次 VM */
|
||||
```
|
||||
|
||||
---
|
||||
@@ -645,30 +716,32 @@ apps/usr_app/
|
||||
|
||||
| 风险 | 要求 |
|
||||
|---|---|
|
||||
| Excel 备注 Byte5 vs 枚举 Byte5/6 | 以本文第 10.2 节为准 |
|
||||
| JSON 对 DP6 只写了「温度 int8、湿度/版本 uint8」 | 以本文第 10.2 节 **先 min 后 max** 为准,不要猜成单阈值 |
|
||||
| 生成协议 `MAX_PACKAGE_LEN=128` | **必须改 256**,否则 DP21 组帧失败 |
|
||||
| 旧条件 49B VM | 不迁移、不解析;当无联动 |
|
||||
| CT1642 鬼影/异显微亮 | IO 独立不解决:单锁存 + 移位实时输出的固有特性,软件靠"值不变不刷新"/提速分时压;残余微亮需 Q2-Q9 加 1~10k 硬件下拉 |
|
||||
| 10k 上拉偏弱 | 软件 I2C 放慢;不行再提硬件改 4.7k(SDA 建议外部 4.7k) |
|
||||
| 条件强力循环可能把箱体吹干 | 已拍板(Q16 A1),不要擅自改成通风兜底 |
|
||||
| 联动开 1 档可能一直吹 | 已拍板回差 + 超最高优先,不要擅自加时长或改切通风 |
|
||||
| 门夹 USB 插入直通开机 | **禁止** 移植 |
|
||||
| 协议 OTA stub 返回 OK | 注释标明未升级;P2 不要装作已刷写 |
|
||||
| `Makefile` 引用缺失 board 文件 | 产品只编实际存在的 `board_jl701n_demo.c` |
|
||||
| 负温度段码 | P0 扫段后在 `app_disp` 注释画真值表 |
|
||||
| 心跳 12s 停报 | 本版明确不做,不要「顺手实现」 |
|
||||
|
||||
---
|
||||
|
||||
## 20. 给 WorkBuddy 的推荐开工顺序
|
||||
|
||||
1. 按 Excel 重生 7 字节 `jb_protocol` / `dp_definition.json`。
|
||||
2. 新建 `board_pin.h`,UART 改 PG6,关掉 PC3/4/5 上的 demo 外设。
|
||||
3. 移植 key/led/boot/power/pair(删电池与 USB 直通)。
|
||||
4. `bsp_hw` 风扇 PWM + LED + KEY。
|
||||
5. `bsp_ct1642`(PG0/PG1)写死 25/60。
|
||||
6. `bsp_aht20` 串口打印,再接到显示。
|
||||
7. P1:`app_mode` + DP 同步 + NVM。
|
||||
8. P2:`app_cond` + `app_timer_task` + 仲裁用例。
|
||||
1. 从 `_新协议` 拷贝 `jb_protocol.*` / `dp_definition.json`,`MAX_PACKAGE_LEN` 改 256;把现有 `jb_product.c` 业务迁到新 DP 号(不要用空壳覆盖)。
|
||||
2. `app_nvm`:丢掉 cond 49B,改为 linkage 20B + run_record;升版本。
|
||||
3. 新建 `app_linkage`(替换 `app_cond`),仲裁 owner 改为 LINKAGE。
|
||||
4. 新建 `app_runrec`:20ms 累加、10 分钟脏、对时旋转、灌 DP21。
|
||||
5. `app_disp` 闪烁改 8.2(出 [min,max] 就闪)。
|
||||
6. `jbEventProcess`:DP5 脉冲删除、DP6 重写重判、DP22 忽略、DP27 出厂重置。
|
||||
7. 按第 17 章 P2 用例逐条验。
|
||||
|
||||
每一步都保持对应 `BOARD_xxx_ENABLE` 可关。
|
||||
|
||||
---
|
||||
|
||||
**文档结束。** 行为争议以本文第 5~12 章为准;字节布局以第 10.2 / 11.1 节与 Excel「枚举/值定义」列为准。
|
||||
**文档结束。** 行为争议以本文第 5~13 章为准;字节布局以第 10.2 / 11.1 / 13.1 节为准。
|
||||
|
||||
@@ -462,7 +462,8 @@ c_SRC_FILES := \
|
||||
apps/usr_app/app_fan.c \
|
||||
apps/usr_app/app_disp.c \
|
||||
apps/usr_app/app_th.c \
|
||||
apps/usr_app/app_cond.c \
|
||||
apps/usr_app/app_linkage.c \
|
||||
apps/usr_app/app_runrec.c \
|
||||
apps/usr_app/app_timer_task.c \
|
||||
apps/usr_app/app_arbiter.c \
|
||||
apps/usr_app/app_nvm.c \
|
||||
|
||||
+47
-80
@@ -1,38 +1,32 @@
|
||||
/******************************************************************************
|
||||
* @file app_arbiter.c
|
||||
* @brief CBR3 模式仲裁器实现(规格书第 12 章:手动 > 定时 > 条件)
|
||||
* @brief CBR3 模式仲裁器实现(规格书第 12 章:手动 > 定时 > 联动)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.0.0,首次发布(P2)
|
||||
* @date 2026.09.02
|
||||
* @version V2.0.0(规格书 V3.0.0):owner COND → LINKAGE,删除档位快照
|
||||
*
|
||||
* @note 本模块是唯一模式占用源,运行在 usr_timer_loop(任务上下文)。
|
||||
* @note 本模块是唯一占用源,运行在 usr_timer_loop(任务上下文)。
|
||||
* 规则落地(规格书 12 章):
|
||||
* - 手动动作 → manual_intervene():owner=NONE;
|
||||
* 条件引擎感知后作废本轮(RAM,须回安全再满时长才重新触发);
|
||||
* - 手动动作 → manual_intervene():owner=NONE + 转调
|
||||
* app_linkage_void()(联动作废本轮,RAM,不写 VM);
|
||||
* 定时引擎感知后本窗剩余时间不自动抢回(等下一窗开始沿)。
|
||||
* - 定时窗开始 → timer_take()(定时 > 条件,条件即使满时长也不开);
|
||||
* - 条件触发 → cond_take()(仅 owner!=TIMER 允许;快照只存第一次);
|
||||
* - 两路条件后触发覆盖 → 仍走 cond_take(),快照不变(不回栈)。
|
||||
* - 定时窗开始 → timer_take()(定时 > 联动,窗内联动不插嘴);
|
||||
* - 联动触发 → linkage_take()(owner==TIMER 时返回 0);
|
||||
* 占用成功**不存档位快照**(联动开=写1、关=写0,不恢复)。
|
||||
* - 联动自然失效 → linkage_release()。
|
||||
* 作废标志不存 VM;再武装由 app_linkage 走到 10.3「关」条件后完成。
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_arbiter.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_fan.h"
|
||||
#include "app_linkage.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[ARB]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 仲裁器上下文 */
|
||||
typedef struct {
|
||||
ArbOwner_t owner; /**< 当前占用者 */
|
||||
uint8_t snap_valid; /**< 条件快照是否有效 */
|
||||
uint8_t snap_mode; /**< 快照模式(第一次条件抢权前) */
|
||||
uint8_t snap_gear; /**< 快照档位 */
|
||||
} AppArbiterCtx_t;
|
||||
|
||||
static AppArbiterCtx_t s_arb;
|
||||
/** 仲裁器上下文:V2.0.0 起只有 owner(快照整段删除) */
|
||||
static uint8_t s_owner;
|
||||
|
||||
/**
|
||||
* @brief 初始化仲裁器
|
||||
@@ -40,118 +34,91 @@ static AppArbiterCtx_t s_arb;
|
||||
*/
|
||||
void app_arbiter_init(void)
|
||||
{
|
||||
s_arb.owner = ARB_OWNER_NONE;
|
||||
s_arb.snap_valid = 0;
|
||||
s_arb.snap_mode = 0;
|
||||
s_arb.snap_gear = 0;
|
||||
s_owner = ARB_OWNER_NONE;
|
||||
log_info("arbiter init\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 当前模式占用者
|
||||
* @brief 当前风扇占用者
|
||||
* @return ArbOwner_t
|
||||
*/
|
||||
uint8_t app_arbiter_get_owner(void)
|
||||
{
|
||||
return (uint8_t)s_arb.owner;
|
||||
return s_owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动动作统一入口:释放一切自动化占用
|
||||
* @note 条件/定时引擎下个 tick 查 owner 感知后各自作废;不恢复快照
|
||||
* (手动已接管,快照对应的用户状态已过时)
|
||||
* @brief 手动动作统一入口:释放一切自动化占用 + 作废本轮联动
|
||||
* @note 联动的作废标志放在 app_linkage(本是联动自己的状态),这里只是
|
||||
* **唯一转调点**:手动介入的三个入口(短按 / APP 写 mode / APP 写档)
|
||||
* 都只调本函数,业务规则就不会被后来的功能漏掉。
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_manual_intervene(void)
|
||||
{
|
||||
if (s_arb.owner != ARB_OWNER_NONE) {
|
||||
log_info("manual intervene, owner %d -> none\n", s_arb.owner);
|
||||
if (s_owner != ARB_OWNER_NONE) {
|
||||
log_info("manual intervene, owner %d -> none\n", s_owner);
|
||||
}
|
||||
s_arb.owner = ARB_OWNER_NONE;
|
||||
s_owner = ARB_OWNER_NONE;
|
||||
app_linkage_void(); /* 12 章规则 1:手动 vs 联动 → 手动,本轮联动作废 */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件引擎尝试占用
|
||||
* @return 1=成功;0=定时占用中(规格书 12:定时 vs 条件 → 定时胜)
|
||||
* @brief 联动引擎尝试占用
|
||||
* @return 1=成功;0=定时占用中(规格书 12:定时 vs 联动 → 定时胜)
|
||||
*/
|
||||
uint8_t app_arbiter_cond_take(void)
|
||||
uint8_t app_arbiter_linkage_take(void)
|
||||
{
|
||||
if (s_arb.owner == ARB_OWNER_TIMER) {
|
||||
return 0; /* 定时窗内条件不触发 */
|
||||
if (s_owner == ARB_OWNER_TIMER) {
|
||||
return 0; /* 定时窗内联动不开、不改档(11.3 / 12 章) */
|
||||
}
|
||||
if (!s_arb.snap_valid) {
|
||||
/* 第一次条件抢权前快照用户状态;后覆盖/多次触发不改快照(10.3-6) */
|
||||
s_arb.snap_mode = app_mode_get_active();
|
||||
s_arb.snap_gear = app_fan_get_gear();
|
||||
s_arb.snap_valid = 1;
|
||||
log_info("cond take, snapshot mode=%d gear=%d\n", s_arb.snap_mode, s_arb.snap_gear);
|
||||
if (s_owner != ARB_OWNER_LINKAGE) {
|
||||
log_info("linkage take\n");
|
||||
}
|
||||
s_arb.owner = ARB_OWNER_COND;
|
||||
s_owner = ARB_OWNER_LINKAGE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件是否仍在占用
|
||||
* @return 1=owner==COND
|
||||
* @brief 联动是否仍在占用
|
||||
* @return 1=owner==LINKAGE
|
||||
*/
|
||||
uint8_t app_arbiter_cond_active(void)
|
||||
uint8_t app_arbiter_linkage_active(void)
|
||||
{
|
||||
return (s_arb.owner == ARB_OWNER_COND) ? 1 : 0;
|
||||
return (s_owner == ARB_OWNER_LINKAGE) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件自然失效:恢复快照后调用(清 owner 与快照)
|
||||
* @brief 联动自然失效:让出占用(档位由调用方写)
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_cond_release(void)
|
||||
void app_arbiter_linkage_release(void)
|
||||
{
|
||||
if (s_arb.owner == ARB_OWNER_COND) {
|
||||
s_arb.owner = ARB_OWNER_NONE;
|
||||
if (s_owner == ARB_OWNER_LINKAGE) {
|
||||
s_owner = ARB_OWNER_NONE;
|
||||
}
|
||||
s_arb.snap_valid = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取条件快照
|
||||
* @param mode 输出模式
|
||||
* @param gear 输出档位
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_cond_get_snapshot(uint8_t *mode, uint8_t *gear)
|
||||
{
|
||||
*mode = s_arb.snap_mode;
|
||||
*gear = s_arb.snap_gear;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件被手动打断:只清快照(不恢复、不清 owner)
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_cond_snapshot_clear(void)
|
||||
{
|
||||
s_arb.snap_valid = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 定时窗开始占用(优先级高于条件)
|
||||
* @brief 定时窗开始占用(优先级高于联动)
|
||||
* @return 1=成功
|
||||
*/
|
||||
uint8_t app_arbiter_timer_take(void)
|
||||
{
|
||||
if (s_arb.owner != ARB_OWNER_TIMER) {
|
||||
if (s_owner != ARB_OWNER_TIMER) {
|
||||
log_info("timer take\n");
|
||||
}
|
||||
s_arb.owner = ARB_OWNER_TIMER;
|
||||
s_owner = ARB_OWNER_TIMER;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 定时窗结束释放(调用方负责收口:风扇 0、mode 0)
|
||||
* @brief 定时窗结束释放(调用方负责收口:风扇 0、mode 0 + 联动重判)
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_timer_release(void)
|
||||
{
|
||||
if (s_arb.owner == ARB_OWNER_TIMER) {
|
||||
s_arb.owner = ARB_OWNER_NONE;
|
||||
if (s_owner == ARB_OWNER_TIMER) {
|
||||
s_owner = ARB_OWNER_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
+37
-46
@@ -1,21 +1,24 @@
|
||||
/******************************************************************************
|
||||
* @file app_arbiter.h
|
||||
* @brief CBR3 模式仲裁器(规格书第 12 章):手动 > 定时 > 条件
|
||||
* @brief CBR3 模式仲裁器(规格书第 12 章):手动 > 定时 > 联动
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.0.0,首次发布(P2)
|
||||
* @date 2026.09.02
|
||||
* @version V2.0.0(规格书 V3.0.0):owner COND → LINKAGE;**删除档位快照**
|
||||
*
|
||||
* @note 把"谁在驱动模式"收成一处,避免 mode/cond/timer 互调成环。
|
||||
* 唯一状态源是 owner(NONE/COND/TIMER)+ 条件快照;
|
||||
* 条件/定时引擎各自维护占用细节,通过查询 owner 感知被打断:
|
||||
* - 手动动作(短按/APP 写档/mode)→ app_arbiter_manual_intervene()
|
||||
* 清 owner;条件引擎感知后作废本轮(RAM),定时引擎感知后本窗
|
||||
* 不再抢回(规格书 12 章规则 1/2)。
|
||||
* - 条件自然失效(比较不成立)→ 条件引擎恢复快照后
|
||||
* app_arbiter_cond_release()。
|
||||
* - 定时窗结束 → app_arbiter_timer_release() + 收口。
|
||||
* 快照:第一次条件抢权前保存用户 mode+gear(规格书 10.3-3),
|
||||
* 后覆盖/手动介入均不更新它(10.3-6),只在条件自然失效恢复后清空。
|
||||
* @note 把"谁在驱动风扇"收成一处,避免 mode/linkage/timer 互调成环。
|
||||
* 唯一状态源是 owner(NONE/LINKAGE/TIMER)。
|
||||
* - 手动动作(短按/APP 写档/写 mode)→ app_arbiter_manual_intervene():
|
||||
* 清 owner 并转调 app_linkage_void() 作废本轮联动(RAM,不写 VM);
|
||||
* 定时引擎下个 tick 感知 owner 后本窗剩余时间不再抢回。
|
||||
* - 定时窗开始 → app_arbiter_timer_take()(定时 > 联动);
|
||||
* - 联动触发 → app_arbiter_linkage_take();定时占用中返回 0。
|
||||
* - 联动自然失效(关条件成立)→ app_arbiter_linkage_release()。
|
||||
*
|
||||
* ⚠ V2.0.0 与旧条件引擎的关键差别(规格书 12 章):
|
||||
* **联动不恢复档位快照**——开只写 fan_gear=1,关只写 0,不存也不恢复
|
||||
* 进入联动前的 mode/gear。原 cond_get_snapshot / snapshot_clear
|
||||
* 一组接口整段删除,不要再加回来。
|
||||
* 作废标志是 RAM、不写 VM,再武装要靠走到第 10.3 节的「关」条件。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_ARBITER_H__
|
||||
@@ -23,11 +26,11 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** 当前模式占用者 */
|
||||
/** 当前风扇占用者 */
|
||||
typedef enum {
|
||||
ARB_OWNER_NONE = 0, /**< 空闲 / 手动接管 */
|
||||
ARB_OWNER_COND = 1, /**< 条件引擎占用 */
|
||||
ARB_OWNER_TIMER = 2 /**< 定时引擎占用 */
|
||||
ARB_OWNER_NONE = 0, /**< 空闲 / 手动接管 */
|
||||
ARB_OWNER_LINKAGE = 1, /**< 联动引擎占用 */
|
||||
ARB_OWNER_TIMER = 2 /**< 定时引擎占用 */
|
||||
} ArbOwner_t;
|
||||
|
||||
/**
|
||||
@@ -37,65 +40,53 @@ typedef enum {
|
||||
void app_arbiter_init(void);
|
||||
|
||||
/**
|
||||
* @brief 当前模式占用者
|
||||
* @brief 当前风扇占用者
|
||||
* @return ArbOwner_t
|
||||
*/
|
||||
uint8_t app_arbiter_get_owner(void);
|
||||
|
||||
/**
|
||||
* @brief 手动动作统一入口(短按 / APP 写 fan_gear / APP 写 mode)
|
||||
* @note 立即释放一切自动化占用;条件/定时引擎下个 tick 感知后各自作废
|
||||
* @note 立即释放一切自动化占用,并作废本轮联动(12 章规则 1)。
|
||||
* 定时引擎下个 tick 感知 owner 后本窗剩余时间不再抢回(规则 2)
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_manual_intervene(void);
|
||||
|
||||
/* ═══ 条件引擎接口 ═══ */
|
||||
/* ═══ 联动引擎接口 ═══ */
|
||||
|
||||
/**
|
||||
* @brief 条件引擎尝试占用(触发模式前调用)
|
||||
* @note 定时占用中返回 0(规格书 12:定时窗 vs 条件 → 定时胜,条件不开);
|
||||
* 占用成功时若尚无快照则保存当前用户 mode+gear(仅第一次)
|
||||
* @brief 联动引擎尝试占用(超最高要开 1 档前调用)
|
||||
* @note 定时占用中返回 0(12 章:定时窗 vs 联动 → 定时胜,窗内联动不插嘴);
|
||||
* 占用成功时**不保存任何档位快照**(联动不恢复快照)
|
||||
* @return 1=占用成功,0=被定时占用拒绝
|
||||
*/
|
||||
uint8_t app_arbiter_cond_take(void);
|
||||
uint8_t app_arbiter_linkage_take(void);
|
||||
|
||||
/**
|
||||
* @brief 条件是否仍在占用
|
||||
* @return 1=owner==COND
|
||||
* @brief 联动是否仍在占用
|
||||
* @return 1=owner==LINKAGE
|
||||
*/
|
||||
uint8_t app_arbiter_cond_active(void);
|
||||
uint8_t app_arbiter_linkage_active(void);
|
||||
|
||||
/**
|
||||
* @brief 条件自然失效:恢复快照后调用(清 owner 与快照)
|
||||
* @brief 联动自然失效:让出风扇(只清 owner,档位由调用方写)
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_cond_release(void);
|
||||
|
||||
/**
|
||||
* @brief 取条件快照(恢复用)
|
||||
* @param mode 输出快照模式
|
||||
* @param gear 输出快照档位
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_cond_get_snapshot(uint8_t *mode, uint8_t *gear);
|
||||
|
||||
/**
|
||||
* @brief 条件被手动打断:只清快照(快照对应的用户状态已过时),
|
||||
* 不恢复、不清 owner(owner 已被 manual_intervene 清掉)
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_cond_snapshot_clear(void);
|
||||
void app_arbiter_linkage_release(void);
|
||||
|
||||
/* ═══ 定时引擎接口 ═══ */
|
||||
|
||||
/**
|
||||
* @brief 定时引擎窗开始占用
|
||||
* @return 1=成功(恒成功,定时优先级高于条件)
|
||||
* @return 1=成功(恒成功,定时优先级高于联动)
|
||||
*/
|
||||
uint8_t app_arbiter_timer_take(void);
|
||||
|
||||
/**
|
||||
* @brief 定时窗结束释放
|
||||
* @note 调用方负责收口(风扇 0、mode 0),并按 11.3 调
|
||||
* app_linkage_reeval() 让联动立刻重判
|
||||
* @return 无
|
||||
*/
|
||||
void app_arbiter_timer_release(void);
|
||||
|
||||
@@ -1,460 +0,0 @@
|
||||
/******************************************************************************
|
||||
* @file app_cond.c
|
||||
* @brief CBR3 条件引擎(P2):7 组 7 字节条件,比较成立满时长触发模式
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V2.0.0,完整条件引擎(规格书第 10 章):7 字节解析/AND 比较/时长
|
||||
* 累计(采样成功才累计)/触发/保持/条件强力循环/后覆盖不回栈/手动
|
||||
* 作废(RAM)/快照恢复/闪烁喂真值
|
||||
*
|
||||
* @note 运行上下文:app_cond_run 由 usr_timer_loop(500ms,任务上下文)调用
|
||||
* (规格书 8.4 时长按采样周期 2s 计,500ms 粒度足够;且触发时可能
|
||||
* 要写 VM——单次定时在 app_timer_task,条件本身不写 VM,但保持
|
||||
* 任务上下文与仲裁器同侧,避免跨上下文竞态)。
|
||||
* 字节布局(规格书 10.2):Byte0=使能 Byte1-2=温度阈值(int16 大端×10)
|
||||
* Byte3=湿度 Byte4=持续分钟(0=立即) Byte5=动作模式
|
||||
* Byte6=Bit7-5 温度比较 / Bit4-2 湿度比较 / Bit1-0 保留。
|
||||
* 比较编码:1=< 2=<= 3-== 4->= 5-> 0/6/7=无条件。
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_cond.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_fan.h"
|
||||
#include "app_th.h"
|
||||
#include "app_disp.h"
|
||||
#include "app_nvm.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_COND]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 每组运行时状态(配置本体在 gDevData.cond_config1~7) */
|
||||
typedef struct {
|
||||
uint32_t acc_ms; /**< 比较成立且采样成功的累计毫秒(未满时长) */
|
||||
uint8_t voided; /**< 1=被手动作废(RAM,不写 VM,须回安全再满时长) */
|
||||
uint8_t trig_by; /**< 触发占用时 Byte5 动作(0=未在占用) */
|
||||
} CondState_t;
|
||||
|
||||
static CondState_t s_cond[7]; /**< [0]~[6] 对应 config1~7 */
|
||||
static uint8_t s_occupy_idx;/**< 当前占用组 0~6,0xFF=无 */
|
||||
|
||||
/**
|
||||
* @brief 比较编码是否有效(1~5;0/6/7 视为无条件)
|
||||
* @param cmp 3bit 比较编码
|
||||
* @return 1=有效,0=无条件
|
||||
*/
|
||||
static uint8_t cond_cmp_valid(uint8_t cmp)
|
||||
{
|
||||
return ((cmp >= 1) && (cmp <= 5)) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 温度比较(当前温度 ×10 vs 阈值 ×10)
|
||||
* @param cmp 比较编码
|
||||
* @param th 温度阈值 ×10
|
||||
* @return 1=成立
|
||||
*/
|
||||
static uint8_t cond_cmp_temp(uint8_t cmp, int16_t th)
|
||||
{
|
||||
int16_t cur = app_th_get_temp_x10();
|
||||
|
||||
switch (cmp) {
|
||||
case 1: return (cur < th) ? 1 : 0;
|
||||
case 2: return (cur <= th) ? 1 : 0;
|
||||
case 3: return (cur == th) ? 1 : 0;
|
||||
case 4: return (cur >= th) ? 1 : 0;
|
||||
case 5: return (cur > th) ? 1 : 0;
|
||||
default: return 1; /* 无条件 */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 湿度比较(当前 % vs 阈值 %)
|
||||
* @param cmp 比较编码
|
||||
* @param th 湿度阈值 %
|
||||
* @return 1=成立
|
||||
*/
|
||||
static uint8_t cond_cmp_humi(uint8_t cmp, uint8_t th)
|
||||
{
|
||||
uint8_t cur = app_th_get_humi();
|
||||
|
||||
switch (cmp) {
|
||||
case 1: return (cur < th) ? 1 : 0;
|
||||
case 2: return (cur <= th) ? 1 : 0;
|
||||
case 3: return (cur == th) ? 1 : 0;
|
||||
case 4: return (cur >= th) ? 1 : 0;
|
||||
case 5: return (cur > th) ? 1 : 0;
|
||||
default: return 1; /* 无条件 */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取第 idx 组条件配置(gDevData 里是 7 个独立数组字段)
|
||||
* @param idx 0~6
|
||||
* @return 配置指针;越界返回 NULL
|
||||
*/
|
||||
static const uint8_t *cond_cfg_ptr(uint8_t idx)
|
||||
{
|
||||
const uint8_t *tbl[7];
|
||||
|
||||
tbl[0] = gDevData.cond_config1;
|
||||
tbl[1] = gDevData.cond_config2;
|
||||
tbl[2] = gDevData.cond_config3;
|
||||
tbl[3] = gDevData.cond_config4;
|
||||
tbl[4] = gDevData.cond_config5;
|
||||
tbl[5] = gDevData.cond_config6;
|
||||
tbl[6] = gDevData.cond_config7;
|
||||
return (idx < 7) ? tbl[idx] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 整组比较是否成立(两侧 AND / 单侧 / 双侧无条件=无效组)
|
||||
* @param cfg 7 字节配置
|
||||
* @return 1=成立,0=不成立或无效组
|
||||
*/
|
||||
static uint8_t cond_group_match(const uint8_t *cfg)
|
||||
{
|
||||
uint8_t temp_cmp = (uint8_t)((cfg[6] >> 5) & 0x7);
|
||||
uint8_t humi_cmp = (uint8_t)((cfg[6] >> 2) & 0x7);
|
||||
uint8_t temp_ok = cond_cmp_valid(temp_cmp);
|
||||
uint8_t humi_ok = cond_cmp_valid(humi_cmp);
|
||||
int16_t temp_th = (int16_t)((cfg[1] << 8) | cfg[2]); /* int16 大端 ×10 */
|
||||
uint8_t humi_th = cfg[3];
|
||||
|
||||
/* 规格书 8.4:-- 或尚无有效采样时比较视为不成立,避免用旧值误触发 */
|
||||
if (app_th_is_fault() || !app_th_get_valid()) {
|
||||
return 0;
|
||||
}
|
||||
if (!temp_ok && !humi_ok) {
|
||||
return 0; /* 两侧都无条件:无效组,不触发(10.2) */
|
||||
}
|
||||
if (temp_ok && humi_ok) {
|
||||
return (cond_cmp_temp(temp_cmp, temp_th) && cond_cmp_humi(humi_cmp, humi_th)) ? 1 : 0;
|
||||
}
|
||||
if (temp_ok) {
|
||||
return cond_cmp_temp(temp_cmp, temp_th);
|
||||
}
|
||||
return cond_cmp_humi(humi_cmp, humi_th);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 触发时长(毫秒);Byte4=0 立即触发
|
||||
* @param cfg 7 字节配置
|
||||
* @return 时长 ms
|
||||
*/
|
||||
static uint32_t cond_duration_ms(const uint8_t *cfg)
|
||||
{
|
||||
return (uint32_t)cfg[4] * 60000u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清全部组累计(定时窗抢权后须重新满时长,规格书 12 章)
|
||||
* @return 无
|
||||
*/
|
||||
static void cond_reset_acc_all(void)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
s_cond[i].acc_ms = 0;
|
||||
s_cond[i].trig_by = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 条件动作切模式。Byte5=0 是关风扇,不能走 set_manual(0)(那会恢复旧档)
|
||||
* @param mode 0=关闭,1=通风,2=强力
|
||||
* @return 无
|
||||
*/
|
||||
static void cond_apply_mode(uint8_t mode)
|
||||
{
|
||||
if (mode == 0) {
|
||||
app_mode_stop();
|
||||
app_fan_set_gear(0);
|
||||
} else {
|
||||
app_mode_set_manual(mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 恢复快照并释放条件占用(比较不成立时,10.3-5)
|
||||
* @return 无
|
||||
*/
|
||||
static void cond_restore_and_release(void)
|
||||
{
|
||||
uint8_t idx = s_occupy_idx;
|
||||
uint8_t m, g;
|
||||
|
||||
app_arbiter_cond_get_snapshot(&m, &g);
|
||||
app_mode_set_manual(m); /* 恢复快照模式(mode=0 时不动档位) */
|
||||
app_fan_set_gear(g); /* 恢复快照档位 */
|
||||
app_arbiter_cond_release();
|
||||
s_cond[idx].acc_ms = 0;
|
||||
s_cond[idx].trig_by = 0;
|
||||
s_occupy_idx = 0xFF;
|
||||
log_info("cond[%d] release, restore mode=%d gear=%d\n", idx + 1, m, g);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 一组触发(占用 + 快照 + 切模式)
|
||||
* @param idx 组索引 0~6
|
||||
* @return 1=已触发,0=被定时占用拒绝
|
||||
*/
|
||||
static uint8_t cond_fire(uint8_t idx)
|
||||
{
|
||||
const uint8_t *cfg = cond_cfg_ptr(idx);
|
||||
|
||||
if (!app_arbiter_cond_take()) {
|
||||
return 0; /* 定时占用中,条件不开(12 章) */
|
||||
}
|
||||
s_occupy_idx = idx;
|
||||
s_cond[idx].trig_by = cfg[5];
|
||||
s_cond[idx].acc_ms = 0;
|
||||
cond_apply_mode(cfg[5]);
|
||||
log_info("cond[%d] fire mode=%d\n", idx + 1, cfg[5]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 扫描阶段:找满时长的组触发(owner==NONE 时)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
static void cond_scan_trigger(uint16_t dt)
|
||||
{
|
||||
uint8_t idx;
|
||||
|
||||
for (idx = 0; idx < 7; idx++) {
|
||||
const uint8_t *cfg = cond_cfg_ptr(idx);
|
||||
uint8_t ok;
|
||||
|
||||
if (!cfg || (cfg[0] == 0)) {
|
||||
continue; /* 组未使能 */
|
||||
}
|
||||
if (s_cond[idx].voided) {
|
||||
/* 作废组:等比较回安全(不成立)才解除作废(10.3-7) */
|
||||
if (!cond_group_match(cfg)) {
|
||||
s_cond[idx].voided = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ok = app_th_last_sample_ok(); /* 8.4:只在采样成功时累加 */
|
||||
if (cond_group_match(cfg)) {
|
||||
if (ok) {
|
||||
s_cond[idx].acc_ms += dt;
|
||||
if (s_cond[idx].acc_ms >= cond_duration_ms(cfg)) {
|
||||
if (cond_fire(idx)) {
|
||||
return; /* 一拍只触发一个,下一拍进保持 */
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 采样失败且比较成立:不累加、不清零(8.4) */
|
||||
} else {
|
||||
s_cond[idx].acc_ms = 0; /* 比较不成立:计时清零(10.3-2) */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保持阶段:扫描其它组后覆盖(10.3-6,不回栈、快照不变)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
static void cond_scan_override(uint16_t dt)
|
||||
{
|
||||
uint8_t idx;
|
||||
|
||||
for (idx = 0; idx < 7; idx++) {
|
||||
const uint8_t *cfg = cond_cfg_ptr(idx);
|
||||
|
||||
if (idx == s_occupy_idx) {
|
||||
continue;
|
||||
}
|
||||
if (!cfg || (cfg[0] == 0) || s_cond[idx].voided) {
|
||||
continue;
|
||||
}
|
||||
if (cond_group_match(cfg)) {
|
||||
if (app_th_last_sample_ok()) {
|
||||
s_cond[idx].acc_ms += dt;
|
||||
if (s_cond[idx].acc_ms >= cond_duration_ms(cfg)) {
|
||||
/* 后触发覆盖当前条件模式,不回栈(D1) */
|
||||
s_occupy_idx = idx;
|
||||
s_cond[idx].trig_by = cfg[5];
|
||||
s_cond[idx].acc_ms = 0;
|
||||
cond_apply_mode(cfg[5]);
|
||||
log_info("cond[%d] override mode=%d\n", idx + 1, cfg[5]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s_cond[idx].acc_ms = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新告警闪烁输入(规格书 8.2:任一使能组温度/湿度比较成立 → 对应管闪)
|
||||
* @note 只看单侧比较成立,不看整组 AND;fault "--" 优先由 app_disp 处理
|
||||
* @return 无
|
||||
*/
|
||||
static void cond_update_disp_alarm(void)
|
||||
{
|
||||
uint8_t t_alarm = 0;
|
||||
uint8_t h_alarm = 0;
|
||||
uint8_t idx;
|
||||
|
||||
if (app_th_is_fault() || !app_th_get_valid() || !gDevData.cond_en) {
|
||||
app_disp_set_temp_alarm(0);
|
||||
app_disp_set_humi_alarm(0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (idx = 0; idx < 7; idx++) {
|
||||
const uint8_t *cfg = cond_cfg_ptr(idx);
|
||||
|
||||
if (!cfg || (cfg[0] == 0) || s_cond[idx].voided) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
uint8_t temp_cmp = (uint8_t)((cfg[6] >> 5) & 0x7);
|
||||
uint8_t humi_cmp = (uint8_t)((cfg[6] >> 2) & 0x7);
|
||||
int16_t temp_th = (int16_t)((cfg[1] << 8) | cfg[2]);
|
||||
|
||||
if (cond_cmp_valid(temp_cmp) && cond_cmp_temp(temp_cmp, temp_th)) {
|
||||
t_alarm = 1;
|
||||
}
|
||||
if (cond_cmp_valid(humi_cmp) && cond_cmp_humi(humi_cmp, cfg[3])) {
|
||||
h_alarm = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
app_disp_set_temp_alarm(t_alarm);
|
||||
app_disp_set_humi_alarm(h_alarm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化条件引擎
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_init(void)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
#if !BOARD_COND_ENABLE
|
||||
log_info("cond engine disabled\n");
|
||||
return;
|
||||
#endif
|
||||
for (i = 0; i < 7; i++) {
|
||||
s_cond[i].acc_ms = 0;
|
||||
s_cond[i].voided = 0;
|
||||
s_cond[i].trig_by = 0;
|
||||
}
|
||||
s_occupy_idx = 0xFF;
|
||||
log_info("cond init\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推进条件引擎(500ms,任务上下文,由 usr_timer_loop 调用)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_run(uint16_t dt)
|
||||
{
|
||||
#if !BOARD_COND_ENABLE
|
||||
(void)dt;
|
||||
return;
|
||||
#endif
|
||||
/* 总开关:cond_en=0 不扫描;若正在跑视为条件结束恢复快照(10.1) */
|
||||
if (!gDevData.cond_en) {
|
||||
if (s_occupy_idx != 0xFF) {
|
||||
cond_restore_and_release();
|
||||
}
|
||||
app_disp_set_temp_alarm(0);
|
||||
app_disp_set_humi_alarm(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_occupy_idx != 0xFF) {
|
||||
/* ── 占用保持阶段 ── */
|
||||
if (!app_arbiter_cond_active()) {
|
||||
uint8_t vidx = s_occupy_idx;
|
||||
|
||||
if (app_arbiter_get_owner() == ARB_OWNER_TIMER) {
|
||||
/* 定时抢走:重新计时长,不作废(规格书 12:窗结束须重新满时长) */
|
||||
cond_reset_acc_all();
|
||||
s_occupy_idx = 0xFF;
|
||||
log_info("cond[%d] preempted by timer, re-time\n", vidx + 1);
|
||||
} else {
|
||||
/* 手动/APP 介入:本轮作废,须回安全再满时长(10.3-7) */
|
||||
s_cond[vidx].voided = 1;
|
||||
app_arbiter_cond_snapshot_clear();
|
||||
s_occupy_idx = 0xFF;
|
||||
log_info("cond[%d] voided by manual\n", vidx + 1);
|
||||
}
|
||||
cond_update_disp_alarm();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 其它组满时长 → 后触发覆盖(不回栈) */
|
||||
cond_scan_override(dt);
|
||||
|
||||
/* 当前占用组:成立保持 / 强力到点再开一轮 / 不成立恢复快照 */
|
||||
{
|
||||
const uint8_t *cfg = cond_cfg_ptr(s_occupy_idx);
|
||||
|
||||
if (cfg && cond_group_match(cfg)) {
|
||||
/* 条件强力:15min 到点(mode 已回 0)且仍成立 → 立即再开一轮(10.3-4) */
|
||||
if ((s_cond[s_occupy_idx].trig_by == 2) && (app_mode_get_active() == 0)) {
|
||||
app_mode_set_manual(2);
|
||||
log_info("cond[%d] power 15min over, restart\n", s_occupy_idx + 1);
|
||||
}
|
||||
/* 通风 25/5 循环由 app_mode 自理 */
|
||||
} else {
|
||||
cond_restore_and_release(); /* 比较不成立 → 恢复快照,重新计时 */
|
||||
}
|
||||
}
|
||||
cond_update_disp_alarm();
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── 空闲扫描触发阶段 ── */
|
||||
if (app_arbiter_get_owner() == ARB_OWNER_TIMER) {
|
||||
/* 定时占用中:条件即使满时长也不开;累计清零,窗结束重新计(12 章) */
|
||||
cond_reset_acc_all();
|
||||
cond_update_disp_alarm();
|
||||
return;
|
||||
}
|
||||
cond_scan_trigger(dt);
|
||||
cond_update_disp_alarm();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP5 条件总开关:存 gDevData + 触发 VM 持久化
|
||||
* @param en 0=关,1=开
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_set_en(uint8_t en)
|
||||
{
|
||||
app_nvm_mark_dirty();
|
||||
log_info("cond_en=%d (stored)\n", en);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP6~12 条件配置:触发 VM 持久化 + 复位该组计时(15 章)
|
||||
* @param idx 1~7
|
||||
* @note 引擎关闭时仍保存;作废标志不回安全不主动清(10.3-7 语义)
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_set_config(uint8_t idx)
|
||||
{
|
||||
if ((idx >= 1) && (idx <= 7)) {
|
||||
s_cond[idx - 1].acc_ms = 0; /* 条件状态机复位该组计时(15 章) */
|
||||
}
|
||||
app_nvm_mark_dirty();
|
||||
log_info("cond_config[%d] stored\n", idx);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/******************************************************************************
|
||||
* @file app_cond.h
|
||||
* @brief CBR3 条件引擎(P2):7 组 7 字节条件,比较成立满时长触发模式
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.29
|
||||
* @version V1.0.0,首次发布(P0 仅接口与 stub)
|
||||
*
|
||||
* @note P2 实现(BOARD_COND_ENABLE=1)。字节布局见规格书 10.2:
|
||||
* Byte0=使能 Byte1-2=温度阈值(int16 大端 ×10) Byte3=湿度阈值
|
||||
* Byte4=持续分钟(0=立即) Byte5=动作模式 Byte6=Bit7-5 温度比较 /
|
||||
* Bit4-2 湿度比较 / Bit1-0 保留。比较编码:1=< 2=<= 3-== 4->= 5->
|
||||
* 0/6/7=无条件;两侧都无条件=无效组;两侧有效=AND。
|
||||
* ⚠ app_cond_run 由 usr_timer_loop(500ms,任务上下文)调用,
|
||||
* 与仲裁器/定时引擎同侧;不放在 20ms usr_timer_tick(中断上下文)。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_COND_H__
|
||||
#define __APP_COND_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief 初始化条件引擎
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_init(void);
|
||||
|
||||
/**
|
||||
* @brief 推进条件引擎,每个业务 tick 调用一次
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_run(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP5 条件总开关
|
||||
* @param en 0=关,1=开
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_set_en(uint8_t en);
|
||||
|
||||
/**
|
||||
* @brief APP 写 DP6~12 条件配置(7 字节)
|
||||
* @param idx 1~7
|
||||
* @return 无
|
||||
*/
|
||||
void app_cond_set_config(uint8_t idx);
|
||||
|
||||
#endif /* __APP_COND_H__ */
|
||||
+65
-61
@@ -2,17 +2,18 @@
|
||||
* @file app_disp.c
|
||||
* @brief CBR3 数码管显示实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,动态扫描(20ms 算段码写缓冲 + BOARD_DISP_SCAN_MS 快节拍
|
||||
* 轮询点亮 1 位)+ 告警闪烁(温度/湿度管独立 500ms 亮灭,
|
||||
* BOARD_DISP_ALARM_TEST 宏可强制模拟超限;fault 时 "--" 优先)
|
||||
* @date 2026.09.02
|
||||
* @version V2.0.0(规格书 V3.0.0):闪烁按第 8.2 节改——有**有效联动配置**
|
||||
* 就按是否落在 [min,max] 闭区间闪(温度/湿度管独立 500ms 亮灭),
|
||||
* 出范围就闪、与风扇是否在转、联动是否被手动作废无关;无有效
|
||||
* 联动不因温湿度闪;fault 时 "--" 优先。闪烁源由 app_linkage
|
||||
* (500ms 任务上下文)喂 app_disp_set_temp/humi_alarm。
|
||||
*
|
||||
* @note ⚠ CT1642 内部只有单个 12 位锁存器(4 位选 + 8 段码),无按位显示 RAM、
|
||||
* 不自动扫描,一次只点亮当前锁存的那 1 位。要让 4 位都亮且不闪,必须由 MCU
|
||||
* 持续轮询:本模块用 app_disp_run()(20ms)算好 4 位段码放进 s_disp_buf,
|
||||
* 持续轮询:本模块用 app_disp_run()(20ms)算好 4 位段码放进 s_disp.buf,
|
||||
* 用 app_disp_scan()(BOARD_DISP_SCAN_MS)每次点亮 1 位、4 位轮询。
|
||||
* 告警闪烁(规格书 8.2):比较成立侧 500ms 亮/500ms 灭,与吹风解耦;
|
||||
* 闪烁触发源由 app_disp_set_temp/humi_alarm 喂入(P2 条件引擎),
|
||||
* 传感器故障(连续 3 次失败)优先显示 "--" 不闪。
|
||||
******************************************************************************/
|
||||
|
||||
@@ -26,21 +27,22 @@
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
static uint8_t s_inited;
|
||||
/** 显示运行时:段码缓冲 + 扫描指针 + 8.2 告警闪烁 */
|
||||
typedef struct {
|
||||
uint8_t inited;
|
||||
uint8_t buf[4]; /* 段码,下标 = com 顺序:0 温度十 / 1 温度个 / 2 湿度十 / 3 湿度个 */
|
||||
uint8_t scan_idx; /* 动态扫描轮询指针 0..3 */
|
||||
uint8_t temp_alarm; /* 1=温度超范围(app_linkage 喂) */
|
||||
uint8_t humi_alarm; /* 1=湿度超范围(app_linkage 喂) */
|
||||
uint8_t blink_phase; /* 0=亮半周期,1=灭半周期 */
|
||||
uint16_t blink_ms; /* 半周期内累计毫秒 */
|
||||
} AppDispCtx_t;
|
||||
|
||||
/* 4 位段码缓冲,下标顺序 = com 顺序:0 温度十 / 1 温度个 / 2 湿度十 / 3 湿度个 */
|
||||
static uint8_t s_disp_buf[4];
|
||||
static AppDispCtx_t s_disp;
|
||||
static const uint8_t s_com_tbl[4] = {
|
||||
BOARD_DISP_COM_TEMP_TEN, BOARD_DISP_COM_TEMP_ONE,
|
||||
BOARD_DISP_COM_HUMI_TEN, BOARD_DISP_COM_HUMI_ONE
|
||||
};
|
||||
static uint8_t s_scan_idx; /* 动态扫描轮询指针 0..3 */
|
||||
|
||||
/* 告警闪烁(P1,规格书 8.2):比较成立侧 500ms 亮/500ms 灭 */
|
||||
static uint8_t s_temp_alarm; /* 1=温度比较成立(P2 条件引擎喂) */
|
||||
static uint8_t s_humi_alarm; /* 1=湿度比较成立(P2 条件引擎喂) */
|
||||
static uint8_t s_blink_phase; /* 0=亮半周期,1=灭半周期 */
|
||||
static uint16_t s_blink_ms; /* 半周期内累计毫秒 */
|
||||
|
||||
/* 段码索引:bsp_ct1642_seg(0~9)=数字,10='-',11=灭 */
|
||||
#define SEG_MINUS 10
|
||||
@@ -104,24 +106,24 @@ void app_disp_init(void)
|
||||
log_info("disp disabled\n");
|
||||
return;
|
||||
#endif
|
||||
if (s_inited) {
|
||||
if (s_disp.inited) {
|
||||
return;
|
||||
}
|
||||
s_inited = 1;
|
||||
s_scan_idx = 0;
|
||||
s_disp.inited = 1;
|
||||
s_disp.scan_idx = 0;
|
||||
bsp_ct1642_init(); /* PG0(CLK)/PG1(DATA) 推挽输出 */
|
||||
/* 默认写死 25 / 60,供 P0 台上扫段验证(规格书 P0 验收) */
|
||||
s_disp_buf[0] = bsp_ct1642_seg(2);
|
||||
s_disp_buf[1] = bsp_ct1642_seg(5);
|
||||
s_disp_buf[2] = bsp_ct1642_seg(6);
|
||||
s_disp_buf[3] = bsp_ct1642_seg(0);
|
||||
s_disp.buf[0] = bsp_ct1642_seg(2);
|
||||
s_disp.buf[1] = bsp_ct1642_seg(5);
|
||||
s_disp.buf[2] = bsp_ct1642_seg(6);
|
||||
s_disp.buf[3] = bsp_ct1642_seg(0);
|
||||
log_info("disp init, scan %ums\n", (unsigned)BOARD_DISP_SCAN_MS);
|
||||
/* 注意:快节拍动态扫描定时器在 usr_jb_init 里注册(usr_timer_add 声明在那) */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 业务节拍(20ms):计算 4 位要显示什么,写入段码缓冲(不直接打管)
|
||||
* @note 软关机由 app_disp_scan 处理。本函数只更新 s_disp_buf。
|
||||
* @note 软关机由 app_disp_scan 处理。本函数只更新 s_disp.buf。
|
||||
* @return 无
|
||||
*/
|
||||
void app_disp_run(uint16_t dt)
|
||||
@@ -130,21 +132,21 @@ void app_disp_run(uint16_t dt)
|
||||
(void)dt;
|
||||
return;
|
||||
#endif
|
||||
if (!s_inited) {
|
||||
if (!s_disp.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* 闪烁相位推进:满 BOARD_DISP_BLINK_MS 切换亮/灭半周期 */
|
||||
s_blink_ms = (uint16_t)(s_blink_ms + dt);
|
||||
if (s_blink_ms >= BOARD_DISP_BLINK_MS) {
|
||||
s_blink_ms = 0;
|
||||
s_blink_phase ^= 1;
|
||||
s_disp.blink_ms = (uint16_t)(s_disp.blink_ms + dt);
|
||||
if (s_disp.blink_ms >= BOARD_DISP_BLINK_MS) {
|
||||
s_disp.blink_ms = 0;
|
||||
s_disp.blink_phase ^= 1;
|
||||
}
|
||||
|
||||
#if BOARD_DISP_ALARM_TEST == 1
|
||||
s_temp_alarm = 1; /* P1 调试:强制温度管闪 */
|
||||
s_disp.temp_alarm = 1; /* P1 调试:强制温度管闪 */
|
||||
#elif BOARD_DISP_ALARM_TEST == 2
|
||||
s_humi_alarm = 1; /* P1 调试:强制湿度管闪 */
|
||||
s_disp.humi_alarm = 1; /* P1 调试:强制湿度管闪 */
|
||||
#endif
|
||||
|
||||
#if BOARD_DISP_SCAN_TEST == 1
|
||||
@@ -160,45 +162,45 @@ void app_disp_run(uint16_t dt)
|
||||
step = (uint8_t)((step + 1) % (sizeof(seg_seq) / sizeof(seg_seq[0])));
|
||||
}
|
||||
uint8_t s = seg_seq[step];
|
||||
s_disp_buf[0] = s; s_disp_buf[1] = s; s_disp_buf[2] = s; s_disp_buf[3] = s;
|
||||
s_disp.buf[0] = s; s_disp.buf[1] = s; s_disp.buf[2] = s; s_disp.buf[3] = s;
|
||||
return;
|
||||
}
|
||||
#elif BOARD_DISP_SCAN_TEST == 2
|
||||
/* 位扫描:4 位分别显示 1/2/3/4,定性 C1~C4 与物理管位对应 */
|
||||
s_disp_buf[0] = bsp_ct1642_seg(1);
|
||||
s_disp_buf[1] = bsp_ct1642_seg(2);
|
||||
s_disp_buf[2] = bsp_ct1642_seg(3);
|
||||
s_disp_buf[3] = bsp_ct1642_seg(4);
|
||||
s_disp.buf[0] = bsp_ct1642_seg(1);
|
||||
s_disp.buf[1] = bsp_ct1642_seg(2);
|
||||
s_disp.buf[2] = bsp_ct1642_seg(3);
|
||||
s_disp.buf[3] = bsp_ct1642_seg(4);
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (app_th_is_fault()) {
|
||||
/* 连续失败 3 次:双管 "--"(规格书 8.1;故障优先于闪烁,8.2) */
|
||||
uint8_t m = bsp_ct1642_seg(SEG_MINUS);
|
||||
s_disp_buf[0] = m; s_disp_buf[1] = m; s_disp_buf[2] = m; s_disp_buf[3] = m;
|
||||
s_disp.buf[0] = m; s_disp.buf[1] = m; s_disp.buf[2] = m; s_disp.buf[3] = m;
|
||||
} else if (app_th_get_valid()) {
|
||||
/* 有有效采样:显示真实值;比较成立且当前在灭半周期 → 该管熄灭 */
|
||||
uint8_t t1, t0, h1, h0;
|
||||
disp_split_temp(app_th_get_temp_x10(), &t1, &t0);
|
||||
disp_split_humi(app_th_get_humi(), &h1, &h0);
|
||||
if (s_temp_alarm && s_blink_phase) {
|
||||
if (s_disp.temp_alarm && s_disp.blink_phase) {
|
||||
t1 = SEG_OFF;
|
||||
t0 = SEG_OFF;
|
||||
}
|
||||
if (s_humi_alarm && s_blink_phase) {
|
||||
if (s_disp.humi_alarm && s_disp.blink_phase) {
|
||||
h1 = SEG_OFF;
|
||||
h0 = SEG_OFF;
|
||||
}
|
||||
s_disp_buf[0] = bsp_ct1642_seg(t1);
|
||||
s_disp_buf[1] = bsp_ct1642_seg(t0);
|
||||
s_disp_buf[2] = bsp_ct1642_seg(h1);
|
||||
s_disp_buf[3] = bsp_ct1642_seg(h0);
|
||||
s_disp.buf[0] = bsp_ct1642_seg(t1);
|
||||
s_disp.buf[1] = bsp_ct1642_seg(t0);
|
||||
s_disp.buf[2] = bsp_ct1642_seg(h1);
|
||||
s_disp.buf[3] = bsp_ct1642_seg(h0);
|
||||
} else {
|
||||
/* 尚未有有效采样:写死 25 / 60,供 P0 台上扫段验证(规格书 P0 验收) */
|
||||
s_disp_buf[0] = bsp_ct1642_seg(2);
|
||||
s_disp_buf[1] = bsp_ct1642_seg(5);
|
||||
s_disp_buf[2] = bsp_ct1642_seg(6);
|
||||
s_disp_buf[3] = bsp_ct1642_seg(0);
|
||||
s_disp.buf[0] = bsp_ct1642_seg(2);
|
||||
s_disp.buf[1] = bsp_ct1642_seg(5);
|
||||
s_disp.buf[2] = bsp_ct1642_seg(6);
|
||||
s_disp.buf[3] = bsp_ct1642_seg(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,46 +216,48 @@ void app_disp_scan(void)
|
||||
#if !BOARD_DISP_ENABLE
|
||||
return;
|
||||
#endif
|
||||
if (!s_inited) {
|
||||
if (!s_disp.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if BOARD_DISP_SCAN_TEST == 1
|
||||
/* 段扫描:4 位同显一段。⭐ 值变化才刷(无鬼影),com=0xF 四位全选同显。 */
|
||||
static uint8_t seg = 0;
|
||||
if (seg == s_disp_buf[0]) {
|
||||
if (seg == s_disp.buf[0]) {
|
||||
return;
|
||||
}
|
||||
seg = s_disp_buf[0];
|
||||
seg = s_disp.buf[0];
|
||||
|
||||
bsp_ct1642_send_frame(s_disp_buf[s_scan_idx], 0xF);
|
||||
s_scan_idx = (uint8_t)((s_scan_idx + 1) % 4);
|
||||
bsp_ct1642_send_frame(s_disp.buf[s_disp.scan_idx], 0xF);
|
||||
s_disp.scan_idx = (uint8_t)((s_disp.scan_idx + 1) % 4);
|
||||
#else
|
||||
/* 正常显示(0) / 位选验证(2):4 位分显不同数字,必须分时轮询。
|
||||
* 鬼影(切位杂段)靠 ct1642_delay 8→2 提速缩短杂段窗口来减轻。 */
|
||||
bsp_ct1642_send_frame(s_disp_buf[s_scan_idx], s_com_tbl[s_scan_idx]);
|
||||
s_scan_idx = (uint8_t)((s_scan_idx + 1) % 4);
|
||||
bsp_ct1642_send_frame(s_disp.buf[s_disp.scan_idx], s_com_tbl[s_disp.scan_idx]);
|
||||
s_disp.scan_idx = (uint8_t)((s_disp.scan_idx + 1) % 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置温度比较成立标志:成立时温度管 500ms 闪烁(P2 条件引擎喂)
|
||||
* @param en 1=成立(闪),0=不成立(常亮数值)
|
||||
* @brief 设置温度超范围标志:超范围时温度管 500ms 闪烁(app_linkage 喂,
|
||||
* 规格书 8.2:T>Tmax 或 T<Tmin;含等于不算超)
|
||||
* @param en 1=超范围(闪),0=带内(常亮数值)
|
||||
* @return 无
|
||||
*/
|
||||
void app_disp_set_temp_alarm(uint8_t en)
|
||||
{
|
||||
s_temp_alarm = en ? 1 : 0;
|
||||
s_disp.temp_alarm = en ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置湿度比较成立标志:成立时湿度管 500ms 闪烁(P2 条件引擎喂)
|
||||
* @param en 1=成立(闪),0=不成立(常亮数值)
|
||||
* @brief 设置湿度超范围标志:超范围时湿度管 500ms 闪烁(app_linkage 喂,
|
||||
* 规格书 8.2:H>Hmax 或 H<Hmin)
|
||||
* @param en 1=超范围(闪),0=带内(常亮数值)
|
||||
* @return 无
|
||||
*/
|
||||
void app_disp_set_humi_alarm(uint8_t en)
|
||||
{
|
||||
s_humi_alarm = en ? 1 : 0;
|
||||
s_disp.humi_alarm = en ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,5 +266,5 @@ void app_disp_set_humi_alarm(uint8_t en)
|
||||
*/
|
||||
uint8_t app_disp_is_alarm(void)
|
||||
{
|
||||
return (s_temp_alarm || s_humi_alarm) ? 1 : 0;
|
||||
return (s_disp.temp_alarm || s_disp.humi_alarm) ? 1 : 0;
|
||||
}
|
||||
|
||||
+11
-9
@@ -2,15 +2,15 @@
|
||||
* @file app_disp.h
|
||||
* @brief CBR3 数码管显示:温度/湿度整数、负号、-- 故障、告警闪烁
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,加告警闪烁:温度/湿度管独立 500ms 亮灭,由
|
||||
* app_disp_set_temp/humi_alarm 驱动(P2 条件引擎喂真值,也可用
|
||||
* BOARD_DISP_ALARM_TEST 宏强制验证)
|
||||
* @date 2026.09.02
|
||||
* @version V2.0.0(规格书 V3.0.0):闪烁改第 8.2 节——有有效联动配置时按
|
||||
* 「是否落在 [min,max]」闪,出范围就闪、与被作废/是否吹风无关;
|
||||
* 喂入方是 app_linkage(也可用 BOARD_DISP_ALARM_TEST 宏强制验证)
|
||||
*
|
||||
* @note 两块 2 位 7 段数码管:温度管整数 ℃、湿度管整数 %。DP 恒灭。
|
||||
* 0~99 显示绝对值;<0 显示 -9~-1(G 段负号);<-9 钳位 -9;>99 钳位 99。
|
||||
* 连续失败 3 次双管 "--"(优先于闪烁)。告警闪烁(规格书 8.2)
|
||||
* 与吹风解耦:比较成立侧 500ms 亮/500ms 灭,不成立侧常亮数值。
|
||||
* 与吹风解耦:超范围侧 500ms 亮/500ms 灭,带内侧常亮数值。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_DISP_H__
|
||||
@@ -42,15 +42,17 @@ void app_disp_run(uint16_t dt);
|
||||
void app_disp_scan(void);
|
||||
|
||||
/**
|
||||
* @brief 设置温度比较成立标志:成立时温度管 500ms 闪烁(P2 条件引擎喂)
|
||||
* @param en 1=成立(闪),0=不成立(常亮数值)
|
||||
* @brief 设置温度超范围标志:超范围时温度管 500ms 闪烁(app_linkage 喂,
|
||||
* 规格书 8.2:T>Tmax 或 T<Tmin;含等于不算超)
|
||||
* @param en 1=超范围(闪),0=带内(常亮数值)
|
||||
* @return 无
|
||||
*/
|
||||
void app_disp_set_temp_alarm(uint8_t en);
|
||||
|
||||
/**
|
||||
* @brief 设置湿度比较成立标志:成立时湿度管 500ms 闪烁(P2 条件引擎喂)
|
||||
* @param en 1=成立(闪),0=不成立(常亮数值)
|
||||
* @brief 设置湿度超范围标志:超范围时湿度管 500ms 闪烁(app_linkage 喂,
|
||||
* 规格书 8.2:H>Hmax 或 H<Hmin)
|
||||
* @param en 1=超范围(闪),0=带内(常亮数值)
|
||||
* @return 无
|
||||
*/
|
||||
void app_disp_set_humi_alarm(uint8_t en);
|
||||
|
||||
+18
-20
@@ -2,12 +2,9 @@
|
||||
* @file app_function.c
|
||||
* @brief CBR3 功能总装实现
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V2.2.0,上电即开机:短按切档;长按分级满 5s 提示→松手提交配对、
|
||||
* 满 12s 到点进 OTA;配对/OTA 的实质动作(清绑定写 VM、
|
||||
* usr_app_ota_init)统一放 app_function_loop(任务上下文)执行,
|
||||
* 中断层只置标志;DP0 power 恒为开机态(规格书 0 章第 5 条:预留
|
||||
* 不当开关),已删除 soft_off/is_power_on 等围绕"可关机"的死代码
|
||||
* @date 2026.09.02
|
||||
* @version V3.0.0(规格书 V3.0.0):app_cond → app_linkage/app_runrec;
|
||||
* 出厂重置改清联动 20B + 31 天运行记录(DP27,规格书第 14 章)
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h" /* 必须先于 app_main.h:u8/u16/u32 等类型由 SDK 定义 */
|
||||
@@ -18,13 +15,15 @@
|
||||
#include "app_nvm.h"
|
||||
#include "app_defer.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_cond.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_runrec.h"
|
||||
#include "app_timer_task.h"
|
||||
#include "app_th.h"
|
||||
#include "app_led.h"
|
||||
#include "app_main.h"
|
||||
#include "usr_le_api.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
#include <string.h>
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
@@ -82,7 +81,7 @@ void app_function_poll(void)
|
||||
{
|
||||
gDevData.fan_gear = app_fan_get_gear(); /* DP2 必须等于当前真实档位 */
|
||||
gDevData.mode = app_mode_get_active();
|
||||
/* DP22:传感器连续失败报 1;2 预留,温湿度比较异常只走显示闪烁不上报 */
|
||||
/* DP24 fault_code:传感器连续失败报 1;2 预留,温湿度出范围只走显示闪烁不上报(8.2) */
|
||||
gDevData.fault_code = app_th_is_fault() ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -97,7 +96,7 @@ void usr_on_key_short(void)
|
||||
{
|
||||
uint8_t g;
|
||||
|
||||
app_arbiter_manual_intervene(); /* 手动动作:释放条件/定时占用(12 章) */
|
||||
app_arbiter_manual_intervene(); /* 手动动作:释放联动/定时占用 + 本轮联动作废(12 章) */
|
||||
app_mode_stop(); /* 退模式(不动档位,下面立刻切循环下一档) */
|
||||
g = app_fan_get_gear(); /* 当前实际档:手动=手动档;模式中=模式强制档(1/3) */
|
||||
g = (uint8_t)((g + 1) % 4); /* 1→2→3→0→1 */
|
||||
@@ -151,17 +150,15 @@ void usr_on_key_ota_start(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 出厂重置:清业务 VM 与 RAM,保留 BLE 配对(规格书 14 章)
|
||||
* @brief 出厂重置:清业务 VM 与 RAM,保留 BLE 配对(规格书第 14 章)
|
||||
* @note 清联动 20B、定时 7 组、31 天运行记录(VM 由 app_nvm_reset 全清);
|
||||
* RAM:mode/gear/fan 停、联动作废清掉(app_linkage_init)、
|
||||
* 运行记录清零。不断开、不软复位。
|
||||
* @return 无
|
||||
*/
|
||||
void app_function_factory_reset(void)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t *c[7] = {
|
||||
gDevData.cond_config1, gDevData.cond_config2, gDevData.cond_config3,
|
||||
gDevData.cond_config4, gDevData.cond_config5, gDevData.cond_config6,
|
||||
gDevData.cond_config7
|
||||
};
|
||||
uint8_t *tm[7] = {
|
||||
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
|
||||
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||||
@@ -169,21 +166,22 @@ void app_function_factory_reset(void)
|
||||
};
|
||||
|
||||
app_nvm_reset();
|
||||
app_arbiter_init();
|
||||
app_cond_init();
|
||||
app_timer_init();
|
||||
app_arbiter_init(); /* owner=NONE */
|
||||
app_linkage_init(); /* 联动配置 RAM 清零 + 作废标志清掉(init 不清配置,这里显式清) */
|
||||
memset(gDevData.linkage_config, 0, BOARD_LINKAGE_CFG_LEN);
|
||||
app_runrec_reset(); /* 31 天运行记录清零(14 章) */
|
||||
app_timer_init(); /* 定时引擎状态复位 */
|
||||
app_mode_stop();
|
||||
app_fan_set_gear(0);
|
||||
gDevData.mode = 0;
|
||||
gDevData.fan_gear = 0;
|
||||
gDevData.cond_en = 0;
|
||||
gDevData.linkage_delete = 0;
|
||||
gDevData.time_en = 0;
|
||||
gDevData.fault_en = 0;
|
||||
gDevData.alarm_en = 0;
|
||||
gDevData.fault_code = 0;
|
||||
gDevData.alarm_code = 0;
|
||||
for (i = 0; i < 7; i++) {
|
||||
memset(c[i], 0, 7);
|
||||
memset(tm[i], 0, 8);
|
||||
}
|
||||
log_info("factory reset: business vm cleared, pairing kept\n");
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
* @file app_function.h
|
||||
* @brief CBR3 功能总装:按键动作(短按切档 / 长按 5s 配对 / 长按 12s OTA)+ DP 同步
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V2.2.0,上电即开机:长按分级回调 usr_on_key_pair_hint/pair_start/
|
||||
* ota_start;DP0 power 恒为开机态(无软关机),已删除
|
||||
* app_function_soft_off/is_power_on 等死代码
|
||||
* @date 2026.09.02
|
||||
* @version V3.0.0(规格书 V3.0.0):app_cond → app_linkage/app_runrec;
|
||||
* 出厂重置改 DP27、清联动 20B 与 31 天运行记录
|
||||
*
|
||||
* @note 本模块只做"按键 → 动作"和"状态 → DP",不写业务算法。
|
||||
* 档位/模式/条件的执行在各自 app_* 模块(规格书第 0 章第 2 条)。
|
||||
* 档位/模式/联动/运行记录的执行在各自 app_* 模块(规格书 0 章第 2 条)。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_FUNCTION_H__
|
||||
|
||||
+23
-19
@@ -3,8 +3,7 @@
|
||||
* @brief CBR3 灯效适配:单灯 PC2,OTA/配对/常亮
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,新增 s_pair_hint:长按满 5s 到松手提交配对这段窗口内维持
|
||||
* 配对快闪,避免被 app_led_run 叠上开机常亮
|
||||
* @version V1.1.1,运行时收进 AppLedCtx_t(inited / ota / pair_hint)
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_led.h"
|
||||
@@ -13,9 +12,14 @@
|
||||
#include "board_pin.h"
|
||||
#include "app_pair.h"
|
||||
|
||||
static uint8_t s_initialized;
|
||||
static uint8_t s_ota; /* 1=OTA 灯效中,app_led_run 不再叠其它灯 */
|
||||
static uint8_t s_pair_hint; /* 1=长按已满 5s 但还没松手提交配对,维持配对快闪 */
|
||||
/** 灯效运行时:初始化门闩 + OTA 独占 + 长按配网提示 */
|
||||
typedef struct {
|
||||
uint8_t inited; /**< 1=已初始化 */
|
||||
uint8_t ota; /**< 1=OTA 灯效中,app_led_run 不再叠其它灯 */
|
||||
uint8_t pair_hint; /**< 1=长按已满 5s 但还没松手提交配对,维持配对快闪 */
|
||||
} AppLedCtx_t;
|
||||
|
||||
static AppLedCtx_t s_led;
|
||||
|
||||
/**
|
||||
* @brief 重新播放一次「一次性/限时」灯效,让它从第 0 帧重新开始
|
||||
@@ -35,11 +39,11 @@ static void led_restart(LED_Event_t evt)
|
||||
*/
|
||||
AppLed_Ret_t app_led_init(void)
|
||||
{
|
||||
if (s_initialized) {
|
||||
if (s_led.inited) {
|
||||
return APP_LED_OK;
|
||||
}
|
||||
user_led_init();
|
||||
s_initialized = 1;
|
||||
s_led.inited = 1;
|
||||
return APP_LED_OK;
|
||||
}
|
||||
|
||||
@@ -49,7 +53,7 @@ AppLed_Ret_t app_led_init(void)
|
||||
*/
|
||||
AppLed_Ret_t app_led_get_status(void)
|
||||
{
|
||||
return s_initialized ? APP_LED_OK : APP_LED_ERR_NOT_INIT;
|
||||
return s_led.inited ? APP_LED_OK : APP_LED_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +62,7 @@ AppLed_Ret_t app_led_get_status(void)
|
||||
*/
|
||||
void app_led_hint_power_on(void)
|
||||
{
|
||||
if (!s_initialized || s_ota) {
|
||||
if (!s_led.inited || s_led.ota) {
|
||||
return;
|
||||
}
|
||||
led_restart(LED_EVENT_STANDBY);
|
||||
@@ -70,10 +74,10 @@ void app_led_hint_power_on(void)
|
||||
*/
|
||||
void app_led_hint_pair(void)
|
||||
{
|
||||
if (!s_initialized || s_ota) {
|
||||
if (!s_led.inited || s_led.ota) {
|
||||
return;
|
||||
}
|
||||
s_pair_hint = 1;
|
||||
s_led.pair_hint = 1;
|
||||
LED_EventAllDelete();
|
||||
bsp_led_set(0);
|
||||
LED_EventAdd(LED_EVENT_PAIRING);
|
||||
@@ -85,7 +89,7 @@ void app_led_hint_pair(void)
|
||||
*/
|
||||
void app_led_clear_pair_hint(void)
|
||||
{
|
||||
s_pair_hint = 0;
|
||||
s_led.pair_hint = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,11 +98,11 @@ void app_led_clear_pair_hint(void)
|
||||
*/
|
||||
void app_led_request_ota(void)
|
||||
{
|
||||
if (!s_initialized) {
|
||||
if (!s_led.inited) {
|
||||
return;
|
||||
}
|
||||
s_ota = 1;
|
||||
s_pair_hint = 0; /* 已改判 OTA,撤销配网提示 */
|
||||
s_led.ota = 1;
|
||||
s_led.pair_hint = 0; /* 已改判 OTA,撤销配网提示 */
|
||||
LED_EventAllDelete();
|
||||
bsp_led_set(0);
|
||||
LED_EventAdd(LED_EVENT_OTA);
|
||||
@@ -110,7 +114,7 @@ void app_led_request_ota(void)
|
||||
*/
|
||||
uint8_t app_led_is_ota(void)
|
||||
{
|
||||
return s_ota;
|
||||
return s_led.ota;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,12 +127,12 @@ void app_led_run(uint16_t dt, uint8_t power_on)
|
||||
{
|
||||
AppPairEvt_t pair_evt;
|
||||
|
||||
if (!s_initialized) {
|
||||
if (!s_led.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* OTA 期间只跑 200ms 闪 */
|
||||
if (s_ota) {
|
||||
if (s_led.ota) {
|
||||
LED_EventAdd(LED_EVENT_OTA);
|
||||
user_led_handle();
|
||||
return;
|
||||
@@ -147,7 +151,7 @@ void app_led_run(uint16_t dt, uint8_t power_on)
|
||||
if (!power_on) {
|
||||
LED_EventDelete(LED_EVENT_STANDBY);
|
||||
LED_EventDelete(LED_EVENT_PAIRING);
|
||||
} else if (s_pair_hint || app_pair_is_active()) {
|
||||
} else if (s_led.pair_hint || app_pair_is_active()) {
|
||||
LED_EventAdd(LED_EVENT_PAIRING);
|
||||
LED_EventDelete(LED_EVENT_STANDBY);
|
||||
} else if (!LED_IsEventExist(LED_EVENT_PAIR_OK) && !LED_IsEventExist(LED_EVENT_PAIR_FAIL)) {
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
/******************************************************************************
|
||||
* @file app_linkage.c
|
||||
* @brief CBR3 联动引擎实现(规格书第 10 章 + 第 12 章仲裁 + 第 8.2 闪烁)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.02
|
||||
* @version V3.0.0,随规格书 V3.0.0 新建(整模块替换 app_cond)
|
||||
*
|
||||
* @note 判定跑在 usr_timer_loop(500ms,任务上下文),与定时引擎同侧。
|
||||
* 每拍做两件独立的事:
|
||||
* 1) 第 8.2 节闪烁:有有效配置就按「是否落在 [min,max] 闭区间」判,
|
||||
* 与风扇是否在转、联动是否被手动作废、仲裁谁占着都无关。
|
||||
* 2) 第 10.3 节开停:回差 + 超最高优先,只写 fan_gear,不改 mode。
|
||||
* 仲裁(12 章):手动 > 定时 > 联动。定时占用(ARB_OWNER_TIMER)时
|
||||
* 联动完全不插嘴;手动作废标志作废本轮直到走到「关」条件。
|
||||
* ⚠ 本模块不进 20ms 中断上下文(没有 tick 接口)。
|
||||
******************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "app_linkage.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_disp.h"
|
||||
#include "app_fan.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_nvm.h"
|
||||
#include "app_th.h"
|
||||
#include "board_pin.h"
|
||||
#include "jb_product.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_LKG]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/** 20B 配置解析结果(规格书 10.2;版本/预留只存不用) */
|
||||
typedef struct {
|
||||
uint16_t tank_id; /**< Byte0-1 大端,0=无效 */
|
||||
int8_t tmin; /**< Byte2 ℃ */
|
||||
int8_t tmax; /**< Byte3 ℃ */
|
||||
uint8_t hmin; /**< Byte4 % */
|
||||
uint8_t hmax; /**< Byte5 % */
|
||||
} LinkageCfg_t;
|
||||
|
||||
/** 联动运行时(RAM,不作废标志不落 VM) */
|
||||
typedef struct {
|
||||
uint8_t voided; /**< 1=被手动作废,须走到「关」条件才再武装 */
|
||||
} LinkageCtx_t;
|
||||
|
||||
static LinkageCtx_t s_lk;
|
||||
|
||||
/**
|
||||
* @brief 解析 DP6 20B 并判定是否有效(第 10.1 / 10.2 节)
|
||||
* @param c 20B 配置
|
||||
* @param out 解析结果
|
||||
* @return 1=有效(缸体号≠0 且 Tmin≤Tmax 且 Hmin≤Hmax)
|
||||
*/
|
||||
static uint8_t lk_parse(const uint8_t *c, LinkageCfg_t *out)
|
||||
{
|
||||
out->tank_id = (uint16_t)(((uint16_t)c[0] << 8) | c[1]);
|
||||
out->tmin = (int8_t)c[2];
|
||||
out->tmax = (int8_t)c[3];
|
||||
out->hmin = c[4];
|
||||
out->hmax = c[5];
|
||||
if (out->tank_id == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (out->tmin > out->tmax) {
|
||||
return 0;
|
||||
}
|
||||
if (out->hmin > out->hmax) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 联动让出风扇:仅当联动正占着时才关(手动/定时占着不动)
|
||||
* @return 无
|
||||
*/
|
||||
static void lk_stop(void)
|
||||
{
|
||||
if (app_arbiter_linkage_active()) {
|
||||
app_arbiter_linkage_release();
|
||||
app_fan_set_gear(0);
|
||||
log_info("linkage stop, fan=0\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 第 8.2 节:喂数码管告警闪烁(出 [min,max] 就闪,与被作废无关)
|
||||
* @param cfg 已解析的配置
|
||||
* @param valid 配置是否有效
|
||||
* @return 无
|
||||
*/
|
||||
static void lk_feed_blink(const LinkageCfg_t *cfg, uint8_t valid)
|
||||
{
|
||||
/* 传感器故障 "--" 优先于闪烁(8.2):故障期不闪 */
|
||||
if (!valid || !app_th_get_valid() || app_th_is_fault()) {
|
||||
app_disp_set_temp_alarm(0);
|
||||
app_disp_set_humi_alarm(0);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int16_t tx10 = app_th_get_temp_x10();
|
||||
uint8_t h = app_th_get_humi();
|
||||
int16_t tmax = (int16_t)cfg->tmax * 10;
|
||||
int16_t tmin = (int16_t)cfg->tmin * 10;
|
||||
|
||||
/* 没有等于比较:T==Tmax 不算超最高,T==Tmin 不算低于最低 */
|
||||
app_disp_set_temp_alarm(((tx10 > tmax) || (tx10 < tmin)) ? 1 : 0);
|
||||
app_disp_set_humi_alarm(((h > cfg->hmax) || (h < cfg->hmin)) ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化联动引擎
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_init(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
log_info("linkage engine disabled\n");
|
||||
return;
|
||||
#endif
|
||||
memset(&s_lk, 0, sizeof(s_lk));
|
||||
app_disp_set_temp_alarm(0);
|
||||
app_disp_set_humi_alarm(0);
|
||||
log_info("linkage init\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 立刻按第 10.3 节重判一次(核心判定,幂等)
|
||||
* @note 步骤:8.4 故障 → 关;8.2 喂闪烁;10.1 无效配置 → 关;
|
||||
* 12 章再武装(走到关条件才清作废);仲裁;10.3 开/关/保持。
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_reeval(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
return;
|
||||
#endif
|
||||
LinkageCfg_t cfg;
|
||||
uint8_t valid = lk_parse(gDevData.linkage_config, &cfg);
|
||||
uint8_t over_max;
|
||||
uint8_t under_min;
|
||||
|
||||
/* ── 8.4:采样失败 "--" 期间联动视为不成立;若联动占着 → 关掉,避免盲吹 ── */
|
||||
if (app_th_is_fault()) {
|
||||
app_disp_set_temp_alarm(0);
|
||||
app_disp_set_humi_alarm(0);
|
||||
lk_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── 8.2:闪烁与开停解耦,有有效配置就判 ── */
|
||||
lk_feed_blink(&cfg, valid);
|
||||
|
||||
/* ── 10.1:无效配置 → 不自动开;原先联动占着则关掉 ── */
|
||||
if (!valid) {
|
||||
lk_stop();
|
||||
return;
|
||||
}
|
||||
if (!app_th_get_valid()) {
|
||||
/* 从未采到过有效值:无可比数据,联动不成立 */
|
||||
lk_stop();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int16_t tx10 = app_th_get_temp_x10();
|
||||
uint8_t h = app_th_get_humi();
|
||||
|
||||
over_max = ((tx10 > (int16_t)cfg.tmax * 10) || (h > cfg.hmax)) ? 1 : 0;
|
||||
under_min = ((tx10 < (int16_t)cfg.tmin * 10) || (h < cfg.hmin)) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ── 12 章再武装:必须先走到「关」条件(没有一侧超最高,且至少一侧 < min)
|
||||
* 才清手动作废;之后重新出现超最高才允许再开 1 档 ── */
|
||||
if (!over_max && under_min) {
|
||||
if (s_lk.voided) {
|
||||
log_info("linkage rearmed (close condition reached)\n");
|
||||
}
|
||||
s_lk.voided = 0;
|
||||
}
|
||||
|
||||
if (s_lk.voided) {
|
||||
return; /* 作废期间只闪不动风扇 */
|
||||
}
|
||||
|
||||
/* ── 仲裁:模式引擎每 20ms 强制档位(通风/强力),联动不与之抢 ── */
|
||||
if (app_mode_get_active() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (over_max) {
|
||||
/* 10.3 开:任一超最高 → 1 档,不改 mode。定时窗占用则 take 失败 */
|
||||
if (!app_arbiter_linkage_take()) {
|
||||
return; /* 12 章:定时窗 vs 联动 → 定时胜,窗内联动不插嘴 */
|
||||
}
|
||||
if (app_fan_get_gear() != BOARD_LINKAGE_OPEN_GEAR) {
|
||||
app_fan_set_gear(BOARD_LINKAGE_OPEN_GEAR);
|
||||
log_info("linkage open, gear=%d\n", BOARD_LINKAGE_OPEN_GEAR);
|
||||
}
|
||||
} else if (under_min) {
|
||||
/* 10.3 关:没有一侧超最高且至少一侧 < min → 关 */
|
||||
lk_stop();
|
||||
}
|
||||
/* 否则:两侧都在 [min,max] 闭区间(含等于)→ 保持上一态(回差) */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推进联动引擎(500ms,任务上下文)
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_run(uint16_t dt)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
(void)dt;
|
||||
return;
|
||||
#endif
|
||||
(void)dt; /* 本模块不计时:每拍完整重判,2s 采样周期由 app_th 决定 */
|
||||
app_linkage_reeval();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DP5 linkage_delete 写 1:清配置并让出风扇
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_delete(void)
|
||||
{
|
||||
/* 引擎关掉也要落盘(与定时一致:P1 只存不跑) */
|
||||
memset(gDevData.linkage_config, 0, BOARD_LINKAGE_CFG_LEN);
|
||||
app_nvm_mark_dirty();
|
||||
#if BOARD_LINKAGE_ENABLE
|
||||
s_lk.voided = 0;
|
||||
lk_stop(); /* 10.4:联动若占着风扇 → fan_gear=0 */
|
||||
#endif
|
||||
log_info("linkage config deleted\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DP6 重写完成:存 VM + 清手动作废 + 立刻按新 min/max 重判
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_config(void)
|
||||
{
|
||||
/* 引擎关掉也要落盘;开停判定仅在 BOARD_LINKAGE_ENABLE=1 时跑 */
|
||||
app_nvm_mark_dirty();
|
||||
#if BOARD_LINKAGE_ENABLE
|
||||
s_lk.voided = 0; /* 10.5:清手动作废标志 */
|
||||
app_linkage_reeval(); /* 该开开、该关关 */
|
||||
#endif
|
||||
log_info("linkage_config stored\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动作废本轮联动
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_void(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
return;
|
||||
#endif
|
||||
if (!s_lk.voided) {
|
||||
s_lk.voided = 1;
|
||||
log_info("linkage voided by manual\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 当前配置是否有效(第 10.1 节)
|
||||
* @return 1=有效
|
||||
*/
|
||||
uint8_t app_linkage_is_valid(void)
|
||||
{
|
||||
#if !BOARD_LINKAGE_ENABLE
|
||||
return 0;
|
||||
#endif
|
||||
LinkageCfg_t cfg;
|
||||
return lk_parse(gDevData.linkage_config, &cfg);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************
|
||||
* @file app_linkage.h
|
||||
* @brief CBR3 联动引擎(替换原 7 组条件):DP5 删除 + DP6 20B 配置,
|
||||
* 按实时温湿度回差开 1 档 / 关 0 档
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.02
|
||||
* @version V3.0.0,随规格书 V3.0.0 新建(删除 app_cond)
|
||||
*
|
||||
* @note 规格书第 10 章(行为)+ 第 12 章(仲裁)。与条件引擎的关键差别:
|
||||
* - 只有 **1 条** 20B 配置(无 cond_en 总开关);
|
||||
* - **不改 mode**:开只写 fan_gear=1,关只写 0,不切通风/强力;
|
||||
* - **不恢复档位快照**(原条件引擎的快照语义整段作废);
|
||||
* - 手动作废标志是 RAM,不写 VM,需走到「关」条件才再武装。
|
||||
* 字节布局(规格书 10.2,以本文为准,不看 JSON 摘要):
|
||||
* Byte0-1 缸体编号 uint16 大端(≠0 才有效,只存不用)
|
||||
* Byte2 Tmin int8 ℃ Byte3 Tmax int8 ℃
|
||||
* Byte4 Hmin uint8 % Byte5 Hmax uint8 %
|
||||
* Byte6 配置版本(只存不用) Byte7-19 预留(只存不用)
|
||||
* 温度比较用设备内部 0.1℃(temp_x10),**没有等于比较**:
|
||||
* T==Tmax 不算超最高,也不算低于最低。
|
||||
*
|
||||
* ⚠ app_linkage_run / app_linkage_reeval 只能在 usr_timer_loop
|
||||
* (500ms,任务上下文)调用;20ms usr_timer_tick 是中断上下文,
|
||||
* 本模块没有任何 tick 接口,不要在中断里调。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_LINKAGE_H__
|
||||
#define __APP_LINKAGE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief 初始化联动引擎(清 RAM 状态与数码管告警标志)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_init(void);
|
||||
|
||||
/**
|
||||
* @brief 推进联动引擎(500ms,任务上下文,由 usr_timer_loop 调用)
|
||||
* @param dt 距上次调用的毫秒数(本模块不计时,仅透传)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_run(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief 立刻按第 10.3 节重判一次(不是手动作废)
|
||||
* @note 供定时窗结束收口后调用(规格书 11.3:窗结束仍超最高 → 联动接手
|
||||
* 开 1 档)。本函数是 app_linkage_run 的核心,幂等。
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_reeval(void);
|
||||
|
||||
/**
|
||||
* @brief DP5 linkage_delete 写 1:清 RAM + VM 的 20B 配置
|
||||
* @note 联动若正占着风扇 → fan_gear=0;作废标志一并清掉(配置已不存在)。
|
||||
* 调用方(jb_product)负责把 linkage_delete 清回 0 再上报(脉冲)
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_delete(void);
|
||||
|
||||
/**
|
||||
* @brief DP6 linkage_config 重写完成:存 VM + 清手动作废 + 立刻重判
|
||||
* @note 数据已由 jb_product memcpy 进 gDevData.linkage_config。
|
||||
* BOARD_LINKAGE_ENABLE=0 时仍 mark_dirty 落盘,只是不跑开停。
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_set_config(void);
|
||||
|
||||
/**
|
||||
* @brief 手动作废本轮联动(短按 / APP 写 mode / APP 写 fan_gear)
|
||||
* @note RAM 标志,不写 VM(规格书 12 章)。作废期间数码管仍按 8.2 闪。
|
||||
* 再武装只能靠走到第 10.3 节的「关」条件(没有一侧超最高,
|
||||
* 且至少一侧 < min),然后重新出现超最高。
|
||||
* @return 无
|
||||
*/
|
||||
void app_linkage_void(void);
|
||||
|
||||
/**
|
||||
* @brief 当前 20B 配置是否为有效联动配置(第 10.1 节)
|
||||
* @return 1=有效(缸体号≠0 且 Tmin≤Tmax 且 Hmin≤Hmax)
|
||||
*/
|
||||
uint8_t app_linkage_is_valid(void);
|
||||
|
||||
#endif /* __APP_LINKAGE_H__ */
|
||||
+137
-116
@@ -1,22 +1,25 @@
|
||||
/******************************************************************************
|
||||
* @file app_nvm.c
|
||||
* @brief CBR3 VM 持久化实现(P1):mode/fan_gear/条件/定时/使能
|
||||
* @brief CBR3 VM 持久化实现:mode/fan_gear/联动/定时/运行记录/使能
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,实现:3 个 VM item + 防抖 + 立即写
|
||||
* @date 2026.09.02
|
||||
* @version V2.0.0(规格书 V3.0.0):删除 CFG_USER_CBR3_COND(49B),改为
|
||||
* CFG_USER_CBR3_LINKAGE(20B) + CFG_USER_CBR3_RUNREC(128B);
|
||||
* **CBR3_VM_VER 1→2**,旧条件数据不读不迁移(当新设备)
|
||||
*
|
||||
* @note VM item 分配在 syscfg 用户自定义区 [1~49](syscfg_id.h 已占用
|
||||
* CFG_USER_PAIR_INFO=2 / CFG_USER_TIM_FLAG=3 / VM_ALARM*=4~24 /
|
||||
* CFG_USER_DEVICE_STA=25 / CFG_USER_PLAN_INFO1=26 /
|
||||
* CFG_USER_BATTERY_LEVEL=27),本产品从 28 开始:
|
||||
* CFG_USER_CBR3_STATE=28 状态(magic+ver+mode+gear+en 等,10B)
|
||||
* CFG_USER_CBR3_COND =29 条件配置 cond_config[7][7](49B)
|
||||
* CFG_USER_CBR3_TIME =30 定时配置 time_config[7][8](56B)
|
||||
* 拆 3 个 item:改档/改模式只擦写 10B 小 item,不重写 105B 配置区,
|
||||
* 减少 flash 磨损;首次上电(magic 不符)保持 gDevData 全 0 默认。
|
||||
* CFG_USER_CBR3_STATE =28 状态(magic+ver+mode+gear+en,10B)
|
||||
* CFG_USER_CBR3_LINKAGE =29 联动配置 linkage_config(20B)
|
||||
* CFG_USER_CBR3_TIME =30 定时配置 time_config[7][8](56B)
|
||||
* CFG_USER_CBR3_RUNREC =31 运行记录 31×uint32 + slot0 日期(128B)
|
||||
* 拆 4 个 item:改档/改模式只擦写 10B 小 item,不重写配置区,
|
||||
* 减少 flash 磨损;首次上电(magic/ver 不符)保持 gDevData 全 0 默认。
|
||||
*
|
||||
* 写入时机(规格书第 13 章):APP 改配置后(mark_dirty)、档位/模式
|
||||
* 稳定变化(500ms 防抖)。
|
||||
* 写入时机(规格书第 13 章):APP 改联动/定时、单次定时自毁、
|
||||
* 档位/模式稳定变化(500ms 防抖);运行记录满 10 分钟 + 跨天立刻写。
|
||||
*
|
||||
* ⚠⚠ 上下文硬约束(2026-08-31 复位事故定论,勿再踩):
|
||||
* usr_timer_add() 是**异步接口**,回调在**硬件定时器中断上下文**执行
|
||||
@@ -25,15 +28,18 @@
|
||||
* - app_nvm_tick() 跑在 20ms 中断上下文 → **只能做变化检测与计时,
|
||||
* 绝对不能调 syscfg_write**(VM 擦写 flash 栈用量大且中断里做
|
||||
* flash 操作 → 栈溢出软复位;实测 APP 下发数据后 500ms 必崩)。
|
||||
* - 真正写入由 app_nvm_flush() 在 usr_timer_loop()(BLE 线程、
|
||||
* 任务上下文、栈充足)执行。
|
||||
* 另:nvm_write_item 的比较缓冲改 static,再省 128B 栈。
|
||||
* - 真正写入由 app_nvm_flush() / app_nvm_save_runrec() 在
|
||||
* usr_timer_loop()(BLE 线程、任务上下文、栈充足)执行。
|
||||
* 另:nvm_write_item 的比较缓冲必须是 static(放栈上会撑爆中断栈)。
|
||||
******************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "app_nvm.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_fan.h"
|
||||
#include "app_defer.h"
|
||||
#include "app_runrec.h"
|
||||
#include "jb_product.h"
|
||||
#include "board_pin.h"
|
||||
#include "system/includes.h"
|
||||
@@ -47,12 +53,13 @@
|
||||
|
||||
/* ── VM item id(用户区 1~49 内申请)── */
|
||||
#define CFG_USER_CBR3_STATE 28
|
||||
#define CFG_USER_CBR3_COND 29
|
||||
#define CFG_USER_CBR3_LINKAGE 29
|
||||
#define CFG_USER_CBR3_TIME 30
|
||||
#define CFG_USER_CBR3_RUNREC 31
|
||||
|
||||
#define CBR3_VM_MAGIC 0xCB03
|
||||
#define CBR3_VM_VER 1
|
||||
#define CBR3_VM_DEBOUNCE_MS 500 /* 档位/模式稳定变化防抖(规格书 13 章) */
|
||||
#define CBR3_VM_VER 2 /* V3.0.0 起:条件→联动+运行记录,旧数据不迁移 */
|
||||
#define CBR3_VM_DEBOUNCE_MS 500 /* 档位/模式稳定变化防抖(规格书 13 章) */
|
||||
|
||||
/** 状态 item(10 字节) */
|
||||
typedef struct {
|
||||
@@ -60,55 +67,41 @@ typedef struct {
|
||||
uint8_t ver; /**< CBR3_VM_VER */
|
||||
uint8_t mode; /**< 逻辑模式 0/1/2 */
|
||||
uint8_t fan_gear; /**< 真实档位 0~3(模式未激活时即手动档) */
|
||||
uint8_t cond_en; /**< DP5 条件总开关 */
|
||||
uint8_t time_en; /**< DP13 定时总开关 */
|
||||
uint8_t fault_en; /**< DP21 */
|
||||
uint8_t alarm_en; /**< DP23 */
|
||||
uint8_t fault_en; /**< DP23 */
|
||||
uint8_t alarm_en; /**< DP25 */
|
||||
uint8_t rsv[2]; /**< 对齐到 10B(原 cond_en 位置作废) */
|
||||
} __packed Cbr3StateVm_t;
|
||||
|
||||
/** 条件配置 item(49 字节) */
|
||||
/** 联动配置 item(20 字节,DP6 原样落盘) */
|
||||
typedef struct {
|
||||
uint8_t cond_config[7][7];
|
||||
} __packed Cbr3CondVm_t;
|
||||
uint8_t linkage_config[BOARD_LINKAGE_CFG_LEN];
|
||||
} __packed Cbr3LinkageVm_t;
|
||||
|
||||
/** 定时配置 item(56 字节) */
|
||||
typedef struct {
|
||||
uint8_t time_config[7][8];
|
||||
} __packed Cbr3TimeVm_t;
|
||||
|
||||
/** 运行记录 item(124B 秒数 + 4B slot0 日期) */
|
||||
typedef struct {
|
||||
uint8_t run_record[APP_RUNREC_PACK_LEN]; /**< 31 × uint32 大端 */
|
||||
uint32_t slot0_date; /**< slot[0] 对应本地日期 yyyymmdd */
|
||||
} __packed Cbr3RunrecVm_t;
|
||||
|
||||
#if BOARD_NVM_ENABLE
|
||||
static uint8_t s_dirty; /**< 1=防抖计时中 */
|
||||
static uint16_t s_deb_ms; /**< 防抖计时 */
|
||||
/* 防抖已满时用 app_defer_request(APP_DEFER_NVM_SAVE) 打标志,真正落盘
|
||||
* 由 app_nvm_flush() 在任务上下文里 app_defer_take() 消费,见文件头说明 */
|
||||
/** NVM 运行时:防抖落盘 + 已落盘档位/模式快照(变化检测基准) */
|
||||
typedef struct {
|
||||
uint8_t dirty; /**< 1=防抖计时中 */
|
||||
uint16_t deb_ms; /**< 防抖累计毫秒 */
|
||||
uint8_t last_gear; /**< 上次已同步到 VM 的档位 */
|
||||
uint8_t last_mode; /**< 上次已同步到 VM 的模式 */
|
||||
uint8_t last_time_en; /**< 上次已同步到 VM 的定时总开关 */
|
||||
} AppNvmCtx_t;
|
||||
|
||||
/* 上次已同步到 VM 的状态快照:变化检测基准(覆盖按键/APP/模式引擎所有入口) */
|
||||
static uint8_t s_last_gear;
|
||||
static uint8_t s_last_mode;
|
||||
static uint8_t s_last_cond_en;
|
||||
static uint8_t s_last_time_en;
|
||||
static AppNvmCtx_t s_nvm;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 从 gDevData 收集条件配置到 VM 结构
|
||||
* @note gDevData.cond_config1~7 是 7 个独立数组字段,用指针表统一搬运
|
||||
* @param out 输出结构
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_collect_cond(Cbr3CondVm_t *out)
|
||||
{
|
||||
const uint8_t *src[7] = {
|
||||
gDevData.cond_config1, gDevData.cond_config2, gDevData.cond_config3,
|
||||
gDevData.cond_config4, gDevData.cond_config5, gDevData.cond_config6,
|
||||
gDevData.cond_config7
|
||||
};
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
memcpy(out->cond_config[i], src[i], 7);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 gDevData 收集定时配置到 VM 结构
|
||||
* @param out 输出结构
|
||||
@@ -132,7 +125,7 @@ static void nvm_collect_time(Cbr3TimeVm_t *out)
|
||||
* @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash
|
||||
* @param item_id VM item id
|
||||
* @param buf 新数据
|
||||
* @param len 数据长度
|
||||
* @param len 数据长度(≤128,比较缓冲大小)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_item(u16 item_id, void *buf, u16 len)
|
||||
@@ -164,27 +157,27 @@ static void nvm_write_state(void)
|
||||
{
|
||||
Cbr3StateVm_t st;
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
st.magic = CBR3_VM_MAGIC;
|
||||
st.ver = CBR3_VM_VER;
|
||||
st.mode = app_mode_get_active();
|
||||
st.fan_gear = app_fan_get_gear();
|
||||
st.cond_en = gDevData.cond_en ? 1 : 0;
|
||||
st.time_en = gDevData.time_en ? 1 : 0;
|
||||
st.fault_en = gDevData.fault_en ? 1 : 0;
|
||||
st.alarm_en = gDevData.alarm_en ? 1 : 0;
|
||||
st.fault_en = gDevData.fault_en;
|
||||
st.alarm_en = gDevData.alarm_en;
|
||||
nvm_write_item(CFG_USER_CBR3_STATE, &st, sizeof(st));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写条件配置 item
|
||||
* @brief 写联动配置 item(20B 原样落盘)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_cond(void)
|
||||
static void nvm_write_linkage(void)
|
||||
{
|
||||
Cbr3CondVm_t cf;
|
||||
Cbr3LinkageVm_t lf;
|
||||
|
||||
nvm_collect_cond(&cf);
|
||||
nvm_write_item(CFG_USER_CBR3_COND, &cf, sizeof(cf));
|
||||
memcpy(lf.linkage_config, gDevData.linkage_config, BOARD_LINKAGE_CFG_LEN);
|
||||
nvm_write_item(CFG_USER_CBR3_LINKAGE, &lf, sizeof(lf));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,6 +192,19 @@ static void nvm_write_time(void)
|
||||
nvm_write_item(CFG_USER_CBR3_TIME, &tf, sizeof(tf));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 写运行记录 item(31 格秒数 + slot0 日期)
|
||||
* @return 无
|
||||
*/
|
||||
static void nvm_write_runrec(void)
|
||||
{
|
||||
Cbr3RunrecVm_t rf;
|
||||
|
||||
app_runrec_pack(rf.run_record);
|
||||
rf.slot0_date = app_runrec_get_slot0_date();
|
||||
nvm_write_item(CFG_USER_CBR3_RUNREC, &rf, sizeof(rf));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化:校验 VM 有效性并打日志(加载在 app_nvm_load)
|
||||
* @return 无
|
||||
@@ -213,13 +219,13 @@ void app_nvm_init(void)
|
||||
int ret = syscfg_read(CFG_USER_CBR3_STATE, &st, sizeof(st));
|
||||
|
||||
if ((ret == (int)sizeof(st)) && (st.magic == CBR3_VM_MAGIC) && (st.ver == CBR3_VM_VER)) {
|
||||
log_info("nvm init, vm valid (mode=%d gear=%d cond_en=%d time_en=%d)\n",
|
||||
st.mode, st.fan_gear, st.cond_en, st.time_en);
|
||||
log_info("nvm init, vm valid v%d (mode=%d gear=%d time_en=%d)\n",
|
||||
st.ver, st.mode, st.fan_gear, st.time_en);
|
||||
} else {
|
||||
/* ver 不符 = 旧条件固件留下的 VM(规格书 13 章:不迁移,当新设备) */
|
||||
log_info("nvm init, vm invalid (ret=%d), use defaults\n", ret);
|
||||
}
|
||||
s_dirty = 0;
|
||||
s_deb_ms = 0;
|
||||
memset(&s_nvm, 0, sizeof(s_nvm));
|
||||
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||
#endif
|
||||
}
|
||||
@@ -231,15 +237,14 @@ void app_nvm_init(void)
|
||||
static void nvm_sync_snapshot(void)
|
||||
{
|
||||
#if BOARD_NVM_ENABLE
|
||||
s_last_gear = app_fan_get_gear();
|
||||
s_last_mode = app_mode_get_active();
|
||||
s_last_cond_en = gDevData.cond_en ? 1 : 0;
|
||||
s_last_time_en = gDevData.time_en ? 1 : 0;
|
||||
s_nvm.last_gear = app_fan_get_gear();
|
||||
s_nvm.last_mode = app_mode_get_active();
|
||||
s_nvm.last_time_en = gDevData.time_en ? 1 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 立即保存全部业务字段(软关机前调用)
|
||||
* @brief 立即保存全部业务字段
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_save(void)
|
||||
@@ -248,20 +253,34 @@ void app_nvm_save(void)
|
||||
return;
|
||||
#else
|
||||
nvm_write_state();
|
||||
nvm_write_cond();
|
||||
nvm_write_linkage();
|
||||
nvm_write_time();
|
||||
s_dirty = 0;
|
||||
s_deb_ms = 0;
|
||||
nvm_write_runrec();
|
||||
s_nvm.dirty = 0;
|
||||
s_nvm.deb_ms = 0;
|
||||
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||
nvm_sync_snapshot();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载并应用(开机 3s 后由 usr_jb_init 调用)
|
||||
* @brief 立即保存运行记录项(跨天旋转用,任务上下文)
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_save_runrec(void)
|
||||
{
|
||||
#if !BOARD_NVM_ENABLE
|
||||
return;
|
||||
#else
|
||||
nvm_write_runrec();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载并应用(开机由 usr_jb_init 调用)
|
||||
* @note 顺序:先恢复档位再恢复模式(app_mode_set_manual 内部会快照当前档、
|
||||
* 并让通风/强力从周期头开跑,规格书第 9 章"开机恢复不存相位")。
|
||||
* VM 无效时保持 gDevData 全 0(首次上电默认 mode=0 gear=0)。
|
||||
* VM 无效(magic/ver 不符,含旧条件固件)时保持 gDevData 全 0 默认。
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_load(void)
|
||||
@@ -269,9 +288,10 @@ void app_nvm_load(void)
|
||||
#if !BOARD_NVM_ENABLE
|
||||
return;
|
||||
#else
|
||||
Cbr3StateVm_t st;
|
||||
Cbr3CondVm_t cf;
|
||||
Cbr3TimeVm_t tf;
|
||||
Cbr3StateVm_t st;
|
||||
Cbr3LinkageVm_t lf;
|
||||
Cbr3TimeVm_t tf;
|
||||
Cbr3RunrecVm_t rf;
|
||||
uint8_t i;
|
||||
|
||||
if ((syscfg_read(CFG_USER_CBR3_STATE, &st, sizeof(st)) != (int)sizeof(st))
|
||||
@@ -280,17 +300,12 @@ void app_nvm_load(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* 条件/定时配置回填 gDevData(只存不跑,P2 引擎再消费) */
|
||||
if (syscfg_read(CFG_USER_CBR3_COND, &cf, sizeof(cf)) == (int)sizeof(cf)) {
|
||||
uint8_t *dst[7] = {
|
||||
gDevData.cond_config1, gDevData.cond_config2, gDevData.cond_config3,
|
||||
gDevData.cond_config4, gDevData.cond_config5, gDevData.cond_config6,
|
||||
gDevData.cond_config7
|
||||
};
|
||||
for (i = 0; i < 7; i++) {
|
||||
memcpy(dst[i], cf.cond_config[i], 7);
|
||||
}
|
||||
/* 联动配置回填 gDevData(20B 原样;引擎由 BOARD_LINKAGE_ENABLE 决定是否跑) */
|
||||
if (syscfg_read(CFG_USER_CBR3_LINKAGE, &lf, sizeof(lf)) == (int)sizeof(lf)) {
|
||||
memcpy(gDevData.linkage_config, lf.linkage_config, BOARD_LINKAGE_CFG_LEN);
|
||||
}
|
||||
|
||||
/* 定时配置回填 gDevData(只存不跑时也要回填,P1 验收) */
|
||||
if (syscfg_read(CFG_USER_CBR3_TIME, &tf, sizeof(tf)) == (int)sizeof(tf)) {
|
||||
uint8_t *dst[7] = {
|
||||
gDevData.time_config1, gDevData.time_config2, gDevData.time_config3,
|
||||
@@ -302,10 +317,15 @@ void app_nvm_load(void)
|
||||
}
|
||||
}
|
||||
|
||||
gDevData.cond_en = st.cond_en ? 1 : 0;
|
||||
/* 运行记录回填 RAM(13.1:掉电再上电先把 31 格灌回来) */
|
||||
if (syscfg_read(CFG_USER_CBR3_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) {
|
||||
app_runrec_unpack(rf.run_record);
|
||||
app_runrec_set_slot0_date(rf.slot0_date);
|
||||
}
|
||||
|
||||
gDevData.time_en = st.time_en ? 1 : 0;
|
||||
gDevData.fault_en = st.fault_en ? 1 : 0;
|
||||
gDevData.alarm_en = st.alarm_en ? 1 : 0;
|
||||
gDevData.fault_en = st.fault_en;
|
||||
gDevData.alarm_en = st.alarm_en;
|
||||
|
||||
/* 先档位后模式;mode 激活时引擎会从周期头强制档位 */
|
||||
if (st.fan_gear > 3) {
|
||||
@@ -314,8 +334,8 @@ void app_nvm_load(void)
|
||||
app_fan_set_gear(st.fan_gear);
|
||||
app_mode_set_manual(st.mode);
|
||||
nvm_sync_snapshot(); /* 恢复值即为已落盘值,同步基准 */
|
||||
log_info("nvm load: mode=%d gear=%d cond_en=%d time_en=%d\n",
|
||||
st.mode, st.fan_gear, st.cond_en, st.time_en);
|
||||
log_info("nvm load v%d: mode=%d gear=%d time_en=%d\n",
|
||||
st.ver, st.mode, st.fan_gear, st.time_en);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -336,26 +356,24 @@ void app_nvm_tick(uint16_t dt)
|
||||
#else
|
||||
uint8_t gear = app_fan_get_gear();
|
||||
uint8_t mode = app_mode_get_active();
|
||||
uint8_t cond_en = gDevData.cond_en ? 1 : 0;
|
||||
uint8_t time_en = gDevData.time_en ? 1 : 0;
|
||||
|
||||
if ((gear != s_last_gear) || (mode != s_last_mode)
|
||||
|| (cond_en != s_last_cond_en) || (time_en != s_last_time_en)) {
|
||||
s_last_gear = gear;
|
||||
s_last_mode = mode;
|
||||
s_last_cond_en = cond_en;
|
||||
s_last_time_en = time_en;
|
||||
s_dirty = 1;
|
||||
s_deb_ms = 0; /* 变化重新计满防抖窗口 */
|
||||
if ((gear != s_nvm.last_gear) || (mode != s_nvm.last_mode)
|
||||
|| (time_en != s_nvm.last_time_en)) {
|
||||
s_nvm.last_gear = gear;
|
||||
s_nvm.last_mode = mode;
|
||||
s_nvm.last_time_en = time_en;
|
||||
s_nvm.dirty = 1;
|
||||
s_nvm.deb_ms = 0; /* 变化重新计满防抖窗口 */
|
||||
}
|
||||
|
||||
if (!s_dirty) {
|
||||
if (!s_nvm.dirty) {
|
||||
return;
|
||||
}
|
||||
s_deb_ms = (uint16_t)(s_deb_ms + dt);
|
||||
if (s_deb_ms >= CBR3_VM_DEBOUNCE_MS) {
|
||||
s_deb_ms = 0;
|
||||
s_dirty = 0;
|
||||
s_nvm.deb_ms = (uint16_t)(s_nvm.deb_ms + dt);
|
||||
if (s_nvm.deb_ms >= CBR3_VM_DEBOUNCE_MS) {
|
||||
s_nvm.deb_ms = 0;
|
||||
s_nvm.dirty = 0;
|
||||
app_defer_request(APP_DEFER_NVM_SAVE); /* 只置标志:真正落盘在 app_nvm_flush */
|
||||
}
|
||||
#endif
|
||||
@@ -386,14 +404,14 @@ void app_nvm_flush(void)
|
||||
void app_nvm_mark_dirty(void)
|
||||
{
|
||||
#if BOARD_NVM_ENABLE
|
||||
s_dirty = 1;
|
||||
s_deb_ms = 0;
|
||||
s_nvm.dirty = 1;
|
||||
s_nvm.deb_ms = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 14 章)
|
||||
* @note 3 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
|
||||
* @note 4 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
|
||||
* BLE 配对三元组在 CFG_USER_PAIR_INFO(item 2),不受影响
|
||||
* @return 无
|
||||
*/
|
||||
@@ -402,18 +420,21 @@ void app_nvm_reset(void)
|
||||
#if !BOARD_NVM_ENABLE
|
||||
return;
|
||||
#else
|
||||
Cbr3StateVm_t st;
|
||||
Cbr3CondVm_t cf;
|
||||
Cbr3TimeVm_t tf;
|
||||
Cbr3StateVm_t st;
|
||||
Cbr3LinkageVm_t lf;
|
||||
Cbr3TimeVm_t tf;
|
||||
Cbr3RunrecVm_t rf;
|
||||
|
||||
memset(&st, 0, sizeof(st));
|
||||
memset(&cf, 0, sizeof(cf));
|
||||
memset(&lf, 0, sizeof(lf));
|
||||
memset(&tf, 0, sizeof(tf));
|
||||
memset(&rf, 0, sizeof(rf));
|
||||
syscfg_write(CFG_USER_CBR3_STATE, &st, sizeof(st));
|
||||
syscfg_write(CFG_USER_CBR3_COND, &cf, sizeof(cf));
|
||||
syscfg_write(CFG_USER_CBR3_LINKAGE, &lf, sizeof(lf));
|
||||
syscfg_write(CFG_USER_CBR3_TIME, &tf, sizeof(tf));
|
||||
s_dirty = 0;
|
||||
s_deb_ms = 0;
|
||||
syscfg_write(CFG_USER_CBR3_RUNREC, &rf, sizeof(rf));
|
||||
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
|
||||
|
||||
+25
-10
@@ -1,14 +1,21 @@
|
||||
/******************************************************************************
|
||||
* @file app_nvm.h
|
||||
* @brief CBR3 VM 持久化(P1 起):mode/fan_gear/条件/定时/使能
|
||||
* @brief CBR3 VM 持久化(P1 起):mode/fan_gear/联动/定时/运行记录/使能
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,实现:3 个 VM item(状态/条件/定时)+ 500ms 防抖写入 +
|
||||
* 立即写接口
|
||||
* @date 2026.09.02
|
||||
* @version V2.0.0(规格书 V3.0.0):删除 CFG_USER_CBR3_COND(49B 条件配置)
|
||||
* 与 cond_en;新增 linkage 20B + run_record 124B;**VM 版本升到 2**,
|
||||
* 旧条件数据**不迁移**(当新设备,走首次上电默认值)
|
||||
*
|
||||
* @note 存:mode、fan_gear、cond_en、cond_config1~7、time_en、time_config1~7、
|
||||
* fault_en、alarm_en。不存:DP0、条件作废标志、模式已运行秒数、原始采样。
|
||||
* 写 VM 时机:APP 改配置后、单次定时自毁、档位/模式稳定变化(500ms 防抖)。
|
||||
* @note 存:mode、fan_gear、linkage_config(20B)、time_en、time_config1~7、
|
||||
* run_record(31×uint32 + slot0 日期)、fault_en、alarm_en。
|
||||
* 不存:DP0、联动作废标志、模式已运行秒数、原始采样、
|
||||
* linkage_delete(脉冲,RAM 瞬态)、BLE 三元组。
|
||||
* 写 VM 时机:APP 改配置后、单次定时自毁、档位/模式稳定变化
|
||||
* (500ms 防抖);运行记录每累计 10 分钟一次、跨天旋转立刻写。
|
||||
*
|
||||
* ⚠ 写 flash 只能在任务上下文(app_nvm_flush / app_nvm_save /
|
||||
* app_nvm_save_runrec),20ms 中断上下文只允许 app_nvm_tick 打脏。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_NVM_H__
|
||||
@@ -30,8 +37,16 @@ void app_nvm_init(void);
|
||||
void app_nvm_save(void);
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载业务字段并应用(开机 3s 后调用):
|
||||
* mode/fan_gear 恢复、条件/定时配置回填 gDevData;mode 从周期头开跑
|
||||
* @brief 立即保存运行记录 item(跨天旋转时用,任务上下文)
|
||||
* @note 与 app_nvm_save 的区别:只写 31 天记录这一项,避免跨天时把
|
||||
* 状态/配置一起无谓擦写一遍
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_save_runrec(void);
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载业务字段并应用(开机调用):
|
||||
* mode/fan_gear 恢复、联动/定时/运行记录回填 gDevData;mode 从周期头开跑
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_load(void);
|
||||
@@ -60,7 +75,7 @@ void app_nvm_mark_dirty(void);
|
||||
|
||||
/**
|
||||
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 14 章)
|
||||
* @note 由 jb_product DP25 调用;RAM 复位由调用方完成
|
||||
* @note 由 jb_product DP27 调用;RAM 复位由调用方完成
|
||||
* @return 无
|
||||
*/
|
||||
void app_nvm_reset(void);
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
/******************************************************************************
|
||||
* @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]、每拍灌 DP21、跨天旋转、满 10 分钟 / 跨天才写 VM。
|
||||
* DP21 每 500ms 更新到 gDevData,供读请求和 10min 全量上报;
|
||||
* 差分变化上报不带 DP21(避免每秒推 124B)。
|
||||
* 拔电没有软关机钩子,接受最多丢不满 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 每拍灌 DP21:读请求 / 10min 全量能拿到当前秒数;差分上报不带本 DP */
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/******************************************************************************
|
||||
* @file app_runrec.h
|
||||
* @brief CBR3 31 天运行记录(DP21 run_record,RO,124B)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.09.02
|
||||
* @version V1.0.0,随规格书 V3.0.0 新建
|
||||
*
|
||||
* @note 规格书第 13.1 节。31 格 uint32 **大端**,单位秒:
|
||||
* slot[0]=今天 slot[1]=昨天 … slot[30]=前 30 天。
|
||||
* 「在运行」= 风扇实际在转(fan_gear != 0 的真实输出)。手动、
|
||||
* 通风开段、强力、定时、联动都算;**通风停 5 分钟那段不算**。
|
||||
* 日期轴:未对时只往 slot[0] 累加、不旋转;对成功后按本地日历左移
|
||||
* N 天;掉电上电先把 VM 的 31 格灌回 RAM。
|
||||
* 落盘:每累计运行满 BOARD_RUN_RECORD_SAVE_SEC(600s) 写一次 VM;
|
||||
* 跨天旋转**立刻写**。拔电无软关机钩子,接受丢不满 10 分钟的秒数。
|
||||
* DP21:任务上下文每 500ms 灌进 gDevData(读请求 / 10min 全量用);
|
||||
* 差分变化上报不带本 DP,避免每秒推 124B。
|
||||
*
|
||||
* ⚠ 上下文分工(规格书第 0/13 章,2026-08-31 事故定论):
|
||||
* - app_runrec_tick() = 20ms **中断上下文**:只折 pending 秒,
|
||||
* 禁止改 s_rec[]、禁止 syscfg_write。
|
||||
* - app_runrec_rotate_if_needed() = 500ms **任务上下文**:收下
|
||||
* pending、跨天旋转、灌 DP21、必要时立刻写 VM。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_RUNREC_H__
|
||||
#define __APP_RUNREC_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "board_pin.h"
|
||||
|
||||
/** DP21 载荷长度:31 × 4 = 124 字节 */
|
||||
#define APP_RUNREC_PACK_LEN (BOARD_RUN_RECORD_DAYS * 4u)
|
||||
|
||||
/**
|
||||
* @brief 初始化运行记录(RAM 清零,DP21 灌全 0)
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_init(void);
|
||||
|
||||
/**
|
||||
* @brief 20ms 中断上下文节拍:风扇在转就把毫秒折成 pending 秒
|
||||
* @param dt 距上次调用的毫秒数
|
||||
* @note ⚠ 不改 31 格数组、不写 flash。未对时也照常折秒,由任务加到 slot[0]。
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_tick(uint16_t dt);
|
||||
|
||||
/**
|
||||
* @brief 500ms 任务上下文:收下 pending、每拍灌 DP21、跨天旋转、满 10 分钟写 VM
|
||||
* @note 由 usr_timer_loop 调用。对时成功后首次读到日历只登记日期不旋转
|
||||
* (slot[0] 已是当天累计值,继续累加)。
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_rotate_if_needed(void);
|
||||
|
||||
/**
|
||||
* @brief 取某天的运行秒数
|
||||
* @param day 0=今天,1=昨天,… 30=前 30 天;越界返回 0
|
||||
* @return 秒数
|
||||
*/
|
||||
uint32_t app_runrec_get(uint8_t day);
|
||||
|
||||
/**
|
||||
* @brief 打包成 DP21 的 124B 载荷(31 × uint32 大端)
|
||||
* @param out 输出缓冲,长度 APP_RUNREC_PACK_LEN
|
||||
* @note BOARD_RUNREC_ENABLE=0 时输出全 0(13.1)
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_pack(uint8_t *out);
|
||||
|
||||
/**
|
||||
* @brief 从 VM 的 124B 载荷灌回 RAM(开机 app_nvm_load 调用)
|
||||
* @param in 输入缓冲,长度 APP_RUNREC_PACK_LEN
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_unpack(const uint8_t *in);
|
||||
|
||||
/**
|
||||
* @brief slot[0] 对应的本地日期(yyyymmdd 十进制压缩),0=未对时
|
||||
* @return 日期
|
||||
*/
|
||||
uint32_t app_runrec_get_slot0_date(void);
|
||||
|
||||
/**
|
||||
* @brief 设置 slot[0] 对应日期(开机 app_nvm_load 调用)
|
||||
* @param ymd yyyymmdd 十进制压缩;0=未对时
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_set_slot0_date(uint32_t ymd);
|
||||
|
||||
/**
|
||||
* @brief 清零 31 格与日期(出厂重置用,规格书第 14 章)
|
||||
* @return 无
|
||||
*/
|
||||
void app_runrec_reset(void);
|
||||
|
||||
#endif /* __APP_RUNREC_H__ */
|
||||
@@ -4,6 +4,7 @@
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,发令阶段 nack 也计入连续失败;fault_code 由上层映射为 1
|
||||
* (规格书 V3.0.0:fault 编号不变,DP 号随协议右移为 DP24)
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_th.h"
|
||||
@@ -35,7 +36,7 @@ typedef struct {
|
||||
uint8_t humi; /**< 最近有效湿度 % */
|
||||
uint8_t valid; /**< 1=有有效采样 */
|
||||
uint8_t fail_cnt; /**< 连续失败次数 */
|
||||
uint8_t last_ok; /**< 最近一次采样是否成功(P2 条件引擎 8.4 用) */
|
||||
uint8_t last_ok; /**< 最近一次采样是否成功(app_linkage 8.4 用) */
|
||||
} AppThCtx_t;
|
||||
|
||||
static AppThCtx_t s_th;
|
||||
@@ -173,7 +174,7 @@ uint8_t app_th_is_fault(void)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 最近一次采样是否成功(P2 条件引擎 8.4:时长只在采样成功时累加)
|
||||
* @brief 最近一次采样是否成功(规格书 8.4:联动只用本次成功值判)
|
||||
* @return 1=成功,0=失败/未采样过
|
||||
*/
|
||||
uint8_t app_th_last_sample_ok(void)
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
* @date 2026.08.31
|
||||
* @version V1.1.0,发令阶段 nack 也计入连续失败
|
||||
*
|
||||
* @note 规格书 8.4:条件"持续时长"只在本次采样成功且比较成立时累加;
|
||||
* 采样失败不累加、不清零;连续 3 次失败显示 "--",DP22 fault_code=1。
|
||||
* fault_code=2 预留,本模块只给出 is_fault,由 app_function 映射。
|
||||
* @note 规格书 8.4:联动只在本次采样成功时用新值比较;采样失败保持
|
||||
* 上一有效值继续判(与显示一致),直到连续 3 次失败显示 "--"
|
||||
* (DP24 fault_code=1)。fault_code=2 预留,本模块只给出
|
||||
* is_fault,由 app_function 映射。
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_TH_H__
|
||||
@@ -59,7 +60,7 @@ uint8_t app_th_get_fail_cnt(void);
|
||||
uint8_t app_th_is_fault(void);
|
||||
|
||||
/**
|
||||
* @brief 最近一次采样是否成功(P2 条件引擎用,规格书 8.4)
|
||||
* @brief 最近一次采样是否成功(app_linkage / app_disp 用,规格书 8.4)
|
||||
* @return 1=成功,0=失败/未采样过
|
||||
*/
|
||||
uint8_t app_th_last_sample_ok(void);
|
||||
|
||||
@@ -2,22 +2,23 @@
|
||||
* @file app_timer_task.c
|
||||
* @brief CBR3 定时引擎(P2):7 组 8 字节定时窗,RTC 门闩
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V2.0.0,完整定时引擎(规格书第 11 章):RTC 门闩(未对时一律不
|
||||
* 跑)/对时补窗/7 组窗判断(跨午夜、星期位图)/窗内切模式/结束收口
|
||||
* (风扇 0 mode 0)/单次自毁清使能写 VM
|
||||
* @date 2026.09.02
|
||||
* @version V2.1.0(规格书 V3.0.0):窗结束收口后**立刻按 10.3 判一次联动**
|
||||
* (app_linkage_reeval,非手动作废);仲裁措辞 条件 → 联动
|
||||
*
|
||||
* @note 运行上下文:app_timer_run 由 usr_timer_loop(500ms,任务上下文)
|
||||
* 调用——窗判断分钟级精度足够,且单次自毁要写 VM(禁止中断上下文)。
|
||||
* 字节布局(规格书 11.1):Byte0=使能 Byte1=模式 Byte2=类型(0单次/
|
||||
* 1每天/2每周) Byte3=星期位图(Bit0=周一…Bit6=周日,仅类型2)
|
||||
* Byte4-5=开始时分 Byte6-7=结束时分。
|
||||
* 12 章仲裁:定时 > 条件;手动介入后本窗剩余时间不自动抢回,
|
||||
* 等下一窗开始沿或对时补窗;窗结束时若定时仍在占用才收口。
|
||||
* 12 章仲裁:定时 > 联动;手动介入后本窗剩余时间不自动抢回,
|
||||
* 等下一窗开始沿或对时补窗;窗结束时若定时仍在占用才收口,
|
||||
* 收口后让联动立刻重判(11.3)。
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_timer_task.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_fan.h"
|
||||
#include "app_nvm.h"
|
||||
@@ -123,9 +124,11 @@ static uint8_t timer_in_window(const uint8_t *cfg, const struct sys_time *t)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 窗结束收口:释放定时占用 + 风扇 0 / mode 0 + 单次自毁
|
||||
* @brief 窗结束收口:释放定时占用 + 风扇 0 / mode 0 + 单次自毁 + 联动重判
|
||||
* @note 仅定时仍在占用时才收口(手动打断后不干预,12 章规则 2);
|
||||
* 单次(类型 0)无论谁占用,结束时刻后清 Byte0 并写 VM(11.1)
|
||||
* 单次(类型 0)无论谁占用,结束时刻后清 Byte0 并写 VM(11.1);
|
||||
* 收口后**立刻按 10.3 判一次联动**(不是手动作废):仍超最高 → 开 1 档
|
||||
* (规格书 11.3;手动打断过的窗结束时若仍超最高且已被手动作废则不开)
|
||||
* @return 无
|
||||
*/
|
||||
static void timer_window_close(void)
|
||||
@@ -150,6 +153,9 @@ static void timer_window_close(void)
|
||||
app_nvm_mark_dirty();
|
||||
log_info("timer[%d] once done, en cleared\n", idx + 1);
|
||||
}
|
||||
|
||||
/* 11.3:窗结束收口后立刻按 10.3 判一次联动(不是手动作废) */
|
||||
app_linkage_reeval();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,7 +222,7 @@ void app_timer_run(uint16_t dt)
|
||||
if (win_idx != 0xFF) {
|
||||
/* ── 窗内 ── */
|
||||
if (!s_timer.taken) {
|
||||
/* 窗开始沿:接管(定时优先级最高,条件让位由条件引擎感知) */
|
||||
/* 窗开始沿:接管(定时优先级最高,联动让位由联动引擎感知) */
|
||||
const uint8_t *cfg = timer_cfg_ptr(win_idx);
|
||||
|
||||
app_arbiter_timer_take();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ================================================================
|
||||
* jb_product.c — 用户层实现(薄适配:只写 DP,不写业务算法)
|
||||
* 设备: 温湿度风扇 | 生成时间: 2026-08-29(cond_config 已按 Excel 重生 7 字节)
|
||||
* 设备: 温湿度风扇 | 生成: 2026-09-02(V3.0.0:删条件 DP,加联动/运行记录)
|
||||
*
|
||||
* ══ 开发流程 ══
|
||||
* 1. 实现硬件抽象: jbGetTimerCount(定时器由 SOC SDK 提供)
|
||||
@@ -8,14 +8,17 @@
|
||||
* 2. userInit() 设置 DP 默认值
|
||||
* 3. userHandle() 填温湿度 DP(500ms)
|
||||
* 4. jbEventProcess() APP 下行:只写 gDevData 并转调 app_*,不在这里写业务
|
||||
* ================================================================
|
||||
*/
|
||||
*
|
||||
* ⚠ 规格书第 0 章第 2 条:**业务算法一律不进本文件**。
|
||||
* 联动开停 → app_linkage;31 天秒数 → app_runrec;定时窗 → app_timer_task。
|
||||
* ================================================================ */
|
||||
|
||||
#include "jb_product.h"
|
||||
#include "app_th.h"
|
||||
#include "app_fan.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_cond.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_runrec.h"
|
||||
#include "app_timer_task.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_nvm.h"
|
||||
@@ -45,14 +48,8 @@ void userInit(void)
|
||||
gDevData.fan_gear = 0; /* FAN_GEAR_0 (关闭风扇) */
|
||||
gDevData.current_temp = 0.0f; /* DP3 当前温度 (float) */
|
||||
gDevData.current_humi = 0; /* DP4 当前湿度 */
|
||||
gDevData.cond_en = 0; /* DP5 条件使能 0=关 1=开 */
|
||||
memset(gDevData.cond_config1, 0, 7); /* DP6 条件配置1(7 字节) */
|
||||
memset(gDevData.cond_config2, 0, 7); /* DP7 条件配置2 */
|
||||
memset(gDevData.cond_config3, 0, 7); /* DP8 条件配置3 */
|
||||
memset(gDevData.cond_config4, 0, 7); /* DP9 条件配置4 */
|
||||
memset(gDevData.cond_config5, 0, 7); /* DP10 条件配置5 */
|
||||
memset(gDevData.cond_config6, 0, 7); /* DP11 条件配置6 */
|
||||
memset(gDevData.cond_config7, 0, 7); /* DP12 条件配置7 */
|
||||
gDevData.linkage_delete = 0; /* DP5 联动删除(脉冲,写 1 触发后清回 0) */
|
||||
memset(gDevData.linkage_config, 0, 20); /* DP6 联动配置(20B,10.2 布局) */
|
||||
gDevData.time_en = 0; /* DP13 定时使能 0=关 1=开 */
|
||||
memset(gDevData.time_config1, 0, 8); /* DP14 定时配置1 */
|
||||
memset(gDevData.time_config2, 0, 8); /* DP15 定时配置2 */
|
||||
@@ -61,19 +58,22 @@ void userInit(void)
|
||||
memset(gDevData.time_config5, 0, 8); /* DP18 定时配置5 */
|
||||
memset(gDevData.time_config6, 0, 8); /* DP19 定时配置6 */
|
||||
memset(gDevData.time_config7, 0, 8); /* DP20 定时配置7 */
|
||||
gDevData.fault_en = 0; /* DP21 设备故障使能 */
|
||||
gDevData.fault_code = 0; /* DP22 设备故障 */
|
||||
gDevData.alarm_en = 0; /* DP23 设备告警使能 */
|
||||
gDevData.alarm_code = 0; /* DP24 设备告警(保持 0) */
|
||||
gDevData.factory_reset = 0; /* DP25 恢复出厂设置 0=关 1=开 */
|
||||
gDevData.reserva_dp1 = 0; /* DP26 预留DP1 */
|
||||
gDevData.reserva_dp2 = 0; /* DP27 预留DP2 */
|
||||
gDevData.reserva_dp3 = 0; /* DP28 预留DP3 */
|
||||
memset(gDevData.run_record, 0, 124); /* DP21 运行记录(RO,由 app_runrec 填) */
|
||||
gDevData.heartbeat = 0; /* DP22 心跳(本版忽略) */
|
||||
gDevData.fault_en = 0; /* DP23 设备故障使能 */
|
||||
gDevData.fault_code = 0; /* DP24 设备故障 */
|
||||
gDevData.alarm_en = 0; /* DP25 设备告警使能 */
|
||||
gDevData.alarm_code = 0; /* DP26 设备告警(保持 0) */
|
||||
gDevData.factory_reset = 0; /* DP27 恢复出厂设置(WO,写 1 触发) */
|
||||
gDevData.reserva_dp1 = 0; /* DP28 预留DP1 */
|
||||
gDevData.reserva_dp2 = 0; /* DP29 预留DP2 */
|
||||
gDevData.reserva_dp3 = 0; /* DP30 预留DP3 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* userHandle — 用户数据采集(500ms 由 usr_timer_loop 调用)
|
||||
* 只填温湿度上报 DP;档位/模式/fault 由 app_function_poll(20ms)同步
|
||||
* 只填温湿度上报 DP;档位/模式/fault 由 app_function_poll(20ms)同步;
|
||||
* DP21 运行记录由 app_runrec 在任务上下文灌
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void userHandle(void)
|
||||
{
|
||||
@@ -105,55 +105,34 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
case DP_ID_POWER:
|
||||
break;
|
||||
|
||||
/* ── DP1 模式:写则手动抢权(仲裁器释放自动化占用)── */
|
||||
/* ── DP1 模式:写则手动抢权(仲裁器释放自动化占用 + 联动作废)── */
|
||||
case DP_ID_MODE:
|
||||
gDevData.mode = ctrl->mode;
|
||||
app_arbiter_manual_intervene(); /* 手动动作:条件/定时让位 */
|
||||
app_arbiter_manual_intervene(); /* 手动动作:联动/定时让位 */
|
||||
app_mode_set_manual(ctrl->mode);
|
||||
break;
|
||||
|
||||
/* ── DP2 风速挡位:写则手动抢权,退模式 ── */
|
||||
case DP_ID_FAN_GEAR:
|
||||
gDevData.fan_gear = ctrl->fan_gear;
|
||||
app_arbiter_manual_intervene(); /* 手动动作:条件/定时让位 */
|
||||
app_arbiter_manual_intervene(); /* 手动动作:联动/定时让位 */
|
||||
app_mode_set_manual(0);
|
||||
app_fan_set_gear(ctrl->fan_gear);
|
||||
break;
|
||||
|
||||
/* ── DP5 条件使能 ── */
|
||||
case DP_ID_COND_EN:
|
||||
gDevData.cond_en = ctrl->cond_en ? 1 : 0;
|
||||
app_cond_set_en(gDevData.cond_en);
|
||||
/* ── DP5 联动删除:脉冲(10.4)。写 1 → 清配置并让出风扇,
|
||||
* 然后把 linkage_delete 清回 0 再上报;写 0 = 无操作 ── */
|
||||
case DP_ID_LINKAGE_DELETE:
|
||||
if (ctrl->linkage_delete) {
|
||||
app_linkage_delete();
|
||||
gDevData.linkage_delete = 0; /* 脉冲:清回 0 供上报 */
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP6~12 条件配置(7 字节 memcpy + 条件状态机复位该组计时)── */
|
||||
case DP_ID_COND_CONFIG1:
|
||||
memcpy(gDevData.cond_config1, ctrl->cond_config1, 7);
|
||||
app_cond_set_config(1);
|
||||
break;
|
||||
case DP_ID_COND_CONFIG2:
|
||||
memcpy(gDevData.cond_config2, ctrl->cond_config2, 7);
|
||||
app_cond_set_config(2);
|
||||
break;
|
||||
case DP_ID_COND_CONFIG3:
|
||||
memcpy(gDevData.cond_config3, ctrl->cond_config3, 7);
|
||||
app_cond_set_config(3);
|
||||
break;
|
||||
case DP_ID_COND_CONFIG4:
|
||||
memcpy(gDevData.cond_config4, ctrl->cond_config4, 7);
|
||||
app_cond_set_config(4);
|
||||
break;
|
||||
case DP_ID_COND_CONFIG5:
|
||||
memcpy(gDevData.cond_config5, ctrl->cond_config5, 7);
|
||||
app_cond_set_config(5);
|
||||
break;
|
||||
case DP_ID_COND_CONFIG6:
|
||||
memcpy(gDevData.cond_config6, ctrl->cond_config6, 7);
|
||||
app_cond_set_config(6);
|
||||
break;
|
||||
case DP_ID_COND_CONFIG7:
|
||||
memcpy(gDevData.cond_config7, ctrl->cond_config7, 7);
|
||||
app_cond_set_config(7);
|
||||
/* ── DP6 联动配置:20B memcpy + 存 VM + 清手动作废 + 立刻重判(10.5)── */
|
||||
case DP_ID_LINKAGE_CONFIG:
|
||||
memcpy(gDevData.linkage_config, ctrl->linkage_config, 20);
|
||||
app_linkage_set_config();
|
||||
break;
|
||||
|
||||
/* ── DP13 定时使能 ── */
|
||||
@@ -192,7 +171,16 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
app_timer_set_config(7);
|
||||
break;
|
||||
|
||||
/* ── DP21/23 使能:原样存,P0 不做位级过滤 ── */
|
||||
/* ── DP21 运行记录:RO,禁止 APP 写(规格书第 15 章)── */
|
||||
case DP_ID_RUN_RECORD:
|
||||
break;
|
||||
|
||||
/* ── DP22 心跳:本版忽略(规格书 0 章第 11 条 / 15 章)。
|
||||
* 写了也不停温湿度上报,不做 12s 停报 ── */
|
||||
case DP_ID_HEARTBEAT:
|
||||
break;
|
||||
|
||||
/* ── DP23/25 使能:原样存,P0 不做位级过滤 ── */
|
||||
case DP_ID_FAULT_EN:
|
||||
gDevData.fault_en = ctrl->fault_en;
|
||||
break;
|
||||
@@ -200,7 +188,8 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
gDevData.alarm_en = ctrl->alarm_en;
|
||||
break;
|
||||
|
||||
/* ── DP25 恢复出厂设置:清业务 VM(留配对)+ RAM 复位,不断开不软复位(14 章)── */
|
||||
/* ── DP27 恢复出厂设置(原 DP25):清业务 VM(留配对)+ RAM 复位,
|
||||
* 不断开不软复位(规格书第 14 章)── */
|
||||
case DP_ID_FACTORY_RESET:
|
||||
gDevData.factory_reset = ctrl->factory_reset ? 1 : 0;
|
||||
if (gDevData.factory_reset) {
|
||||
@@ -208,7 +197,7 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP26~28 预留:存 RAM,可不实现动作 ── */
|
||||
/* ── DP28~30 预留:存 RAM,可不实现动作 ── */
|
||||
case DP_ID_RESERVA_DP1:
|
||||
gDevData.reserva_dp1 = ctrl->reserva_dp1;
|
||||
break;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* 版本管理
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
/* MCU software version: Vmajor.minor.patch */
|
||||
#define JB_MCU_SW_VERSION_MAJOR 1
|
||||
#define JB_MCU_SW_VERSION_MAJOR 2
|
||||
#define JB_MCU_SW_VERSION_MINOR 0
|
||||
#define JB_MCU_SW_VERSION_PATCH 0
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ================================================================
|
||||
* jb_protocol.c — 见宝 IOT 协议实现(自动生成)
|
||||
* 设备: 温湿度风扇 | 生成时间: 2026-08-29 10:31:50
|
||||
* 设备: 温湿度风扇 | 生成时间: 2026-09-02 16:01:44
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
@@ -188,60 +188,18 @@ static uint16_t jbPackDps(const jb_data_point_t *dp,
|
||||
bitmap[DP_ID_CURRENT_HUMI / 8] |= (1 << (DP_ID_CURRENT_HUMI % 8));
|
||||
out[valIdx++] = (uint8_t)dp->current_humi;
|
||||
}
|
||||
/* DP5 条件使能 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_COND_EN / 8] & (1 << (DP_ID_COND_EN % 8)))
|
||||
/* DP5 联动删除 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_LINKAGE_DELETE / 8] & (1 << (DP_ID_LINKAGE_DELETE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_EN / 8] |= (1 << (DP_ID_COND_EN % 8));
|
||||
out[valIdx++] = dp->cond_en ? 1 : 0;
|
||||
bitmap[DP_ID_LINKAGE_DELETE / 8] |= (1 << (DP_ID_LINKAGE_DELETE % 8));
|
||||
out[valIdx++] = dp->linkage_delete ? 1 : 0;
|
||||
}
|
||||
/* DP6 条件配置1 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG1 / 8] & (1 << (DP_ID_COND_CONFIG1 % 8)))
|
||||
/* DP6 联动配置 (raw, 20B, RW) */
|
||||
if (mask[DP_ID_LINKAGE_CONFIG / 8] & (1 << (DP_ID_LINKAGE_CONFIG % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG1 / 8] |= (1 << (DP_ID_COND_CONFIG1 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config1, 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
/* DP7 条件配置2 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG2 / 8] & (1 << (DP_ID_COND_CONFIG2 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG2 / 8] |= (1 << (DP_ID_COND_CONFIG2 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config2, 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
/* DP8 条件配置3 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG3 / 8] & (1 << (DP_ID_COND_CONFIG3 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG3 / 8] |= (1 << (DP_ID_COND_CONFIG3 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config3, 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
/* DP9 条件配置4 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG4 / 8] & (1 << (DP_ID_COND_CONFIG4 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG4 / 8] |= (1 << (DP_ID_COND_CONFIG4 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config4, 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
/* DP10 条件配置5 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG5 / 8] & (1 << (DP_ID_COND_CONFIG5 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG5 / 8] |= (1 << (DP_ID_COND_CONFIG5 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config5, 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
/* DP11 条件配置6 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG6 / 8] & (1 << (DP_ID_COND_CONFIG6 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG6 / 8] |= (1 << (DP_ID_COND_CONFIG6 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config6, 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
/* DP12 条件配置7 (raw, 7B, RW) */
|
||||
if (mask[DP_ID_COND_CONFIG7 / 8] & (1 << (DP_ID_COND_CONFIG7 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_COND_CONFIG7 / 8] |= (1 << (DP_ID_COND_CONFIG7 % 8));
|
||||
memcpy(&out[valIdx], dp->cond_config7, 7);
|
||||
valIdx += 7;
|
||||
bitmap[DP_ID_LINKAGE_CONFIG / 8] |= (1 << (DP_ID_LINKAGE_CONFIG % 8));
|
||||
memcpy(&out[valIdx], dp->linkage_config, 20);
|
||||
valIdx += 20;
|
||||
}
|
||||
/* DP13 定时使能 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_TIME_EN / 8] & (1 << (DP_ID_TIME_EN % 8)))
|
||||
@@ -298,45 +256,52 @@ static uint16_t jbPackDps(const jb_data_point_t *dp,
|
||||
memcpy(&out[valIdx], dp->time_config7, 8);
|
||||
valIdx += 8;
|
||||
}
|
||||
/* DP21 设备故障使能 (uint8, 1B, RW) */
|
||||
/* DP21 运行记录 (raw, 124B, RO) */
|
||||
if (mask[DP_ID_RUN_RECORD / 8] & (1 << (DP_ID_RUN_RECORD % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RUN_RECORD / 8] |= (1 << (DP_ID_RUN_RECORD % 8));
|
||||
memcpy(&out[valIdx], dp->run_record, 124);
|
||||
valIdx += 124;
|
||||
}
|
||||
/* DP23 设备故障使能 (uint8, 1B, RW) */
|
||||
if (mask[DP_ID_FAULT_EN / 8] & (1 << (DP_ID_FAULT_EN % 8)))
|
||||
{
|
||||
bitmap[DP_ID_FAULT_EN / 8] |= (1 << (DP_ID_FAULT_EN % 8));
|
||||
out[valIdx++] = (uint8_t)dp->fault_en;
|
||||
}
|
||||
/* DP22 设备故障 (uint8, 1B, RO) */
|
||||
/* DP24 设备故障 (uint8, 1B, RO) */
|
||||
if (mask[DP_ID_FAULT_CODE / 8] & (1 << (DP_ID_FAULT_CODE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_FAULT_CODE / 8] |= (1 << (DP_ID_FAULT_CODE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->fault_code;
|
||||
}
|
||||
/* DP23 设备告警使能 (uint8, 1B, RW) */
|
||||
/* DP25 设备告警使能 (uint8, 1B, RW) */
|
||||
if (mask[DP_ID_ALARM_EN / 8] & (1 << (DP_ID_ALARM_EN % 8)))
|
||||
{
|
||||
bitmap[DP_ID_ALARM_EN / 8] |= (1 << (DP_ID_ALARM_EN % 8));
|
||||
out[valIdx++] = (uint8_t)dp->alarm_en;
|
||||
}
|
||||
/* DP24 设备告警 (uint8, 1B, RO) */
|
||||
/* DP26 设备告警 (uint8, 1B, RO) */
|
||||
if (mask[DP_ID_ALARM_CODE / 8] & (1 << (DP_ID_ALARM_CODE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_ALARM_CODE / 8] |= (1 << (DP_ID_ALARM_CODE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->alarm_code;
|
||||
}
|
||||
/* DP26 预留DP1 (uint32, 4B, RW) */
|
||||
/* DP28 预留DP1 (uint32, 4B, RW) */
|
||||
if (mask[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
|
||||
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp1);
|
||||
valIdx += 4;
|
||||
}
|
||||
/* DP27 预留DP2 (uint32, 4B, RW) */
|
||||
/* DP29 预留DP2 (uint32, 4B, RW) */
|
||||
if (mask[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
|
||||
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp2);
|
||||
valIdx += 4;
|
||||
}
|
||||
/* DP28 预留DP3 (uint32, 4B, RW) */
|
||||
/* DP30 预留DP3 (uint32, 4B, RW) */
|
||||
if (mask[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
|
||||
@@ -397,107 +362,29 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_FAN_GEAR;
|
||||
}
|
||||
}
|
||||
/* DP5 条件使能 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_COND_EN / 8] & (1 << (DP_ID_COND_EN % 8)))
|
||||
/* DP5 联动删除 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_LINKAGE_DELETE / 8] & (1 << (DP_ID_LINKAGE_DELETE % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->cond_en = (values[valIdx++] != 0);
|
||||
dp->linkage_delete = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_EN;
|
||||
info->dp_ids[info->count++] = DP_ID_LINKAGE_DELETE;
|
||||
}
|
||||
}
|
||||
/* DP6 条件配置1 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG1 / 8] & (1 << (DP_ID_COND_CONFIG1 % 8)))
|
||||
/* DP6 联动配置 (raw, 20B, RW) */
|
||||
if (bitmap[DP_ID_LINKAGE_CONFIG / 8] & (1 << (DP_ID_LINKAGE_CONFIG % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
if (valIdx + 20 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config1, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
memcpy(dp->linkage_config, &values[valIdx], 20);
|
||||
valIdx += 20;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG1;
|
||||
}
|
||||
}
|
||||
/* DP7 条件配置2 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG2 / 8] & (1 << (DP_ID_COND_CONFIG2 % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config2, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG2;
|
||||
}
|
||||
}
|
||||
/* DP8 条件配置3 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG3 / 8] & (1 << (DP_ID_COND_CONFIG3 % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config3, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG3;
|
||||
}
|
||||
}
|
||||
/* DP9 条件配置4 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG4 / 8] & (1 << (DP_ID_COND_CONFIG4 % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config4, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG4;
|
||||
}
|
||||
}
|
||||
/* DP10 条件配置5 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG5 / 8] & (1 << (DP_ID_COND_CONFIG5 % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config5, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG5;
|
||||
}
|
||||
}
|
||||
/* DP11 条件配置6 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG6 / 8] & (1 << (DP_ID_COND_CONFIG6 % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config6, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG6;
|
||||
}
|
||||
}
|
||||
/* DP12 条件配置7 (raw, 7B, RW) */
|
||||
if (bitmap[DP_ID_COND_CONFIG7 / 8] & (1 << (DP_ID_COND_CONFIG7 % 8)))
|
||||
{
|
||||
if (valIdx + 7 <= valLen)
|
||||
{
|
||||
memcpy(dp->cond_config7, &values[valIdx], 7);
|
||||
valIdx += 7;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_COND_CONFIG7;
|
||||
info->dp_ids[info->count++] = DP_ID_LINKAGE_CONFIG;
|
||||
}
|
||||
}
|
||||
/* DP13 定时使能 (bool, 1B, RW) */
|
||||
@@ -603,7 +490,19 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_TIME_CONFIG7;
|
||||
}
|
||||
}
|
||||
/* DP21 设备故障使能 (uint8, 1B, RW) */
|
||||
/* DP22 心跳 (bool, 1B, WO) */
|
||||
if (bitmap[DP_ID_HEARTBEAT / 8] & (1 << (DP_ID_HEARTBEAT % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->heartbeat = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_HEARTBEAT;
|
||||
}
|
||||
}
|
||||
/* DP23 设备故障使能 (uint8, 1B, RW) */
|
||||
if (bitmap[DP_ID_FAULT_EN / 8] & (1 << (DP_ID_FAULT_EN % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
@@ -615,7 +514,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_FAULT_EN;
|
||||
}
|
||||
}
|
||||
/* DP23 设备告警使能 (uint8, 1B, RW) */
|
||||
/* DP25 设备告警使能 (uint8, 1B, RW) */
|
||||
if (bitmap[DP_ID_ALARM_EN / 8] & (1 << (DP_ID_ALARM_EN % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
@@ -627,7 +526,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_ALARM_EN;
|
||||
}
|
||||
}
|
||||
/* DP25 恢复出厂设置 (bool, 1B, WO) */
|
||||
/* DP27 恢复出厂设置 (bool, 1B, WO) */
|
||||
if (bitmap[DP_ID_FACTORY_RESET / 8] & (1 << (DP_ID_FACTORY_RESET % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
@@ -639,7 +538,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_FACTORY_RESET;
|
||||
}
|
||||
}
|
||||
/* DP26 预留DP1 (uint32, 4B, RW) */
|
||||
/* DP28 预留DP1 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
@@ -652,7 +551,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_RESERVA_DP1;
|
||||
}
|
||||
}
|
||||
/* DP27 预留DP2 (uint32, 4B, RW) */
|
||||
/* DP29 预留DP2 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
@@ -665,7 +564,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_RESERVA_DP2;
|
||||
}
|
||||
}
|
||||
/* DP28 预留DP3 (uint32, 4B, RW) */
|
||||
/* DP30 预留DP3 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
@@ -721,52 +620,16 @@ static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
|
||||
mask[DP_ID_CURRENT_HUMI / 8] |= (1 << (DP_ID_CURRENT_HUMI % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP5 条件使能 */
|
||||
if (last->cond_en != cur->cond_en)
|
||||
/* DP5 联动删除 */
|
||||
if (last->linkage_delete != cur->linkage_delete)
|
||||
{
|
||||
mask[DP_ID_COND_EN / 8] |= (1 << (DP_ID_COND_EN % 8));
|
||||
mask[DP_ID_LINKAGE_DELETE / 8] |= (1 << (DP_ID_LINKAGE_DELETE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP6 条件配置1 */
|
||||
if (memcmp(last->cond_config1, cur->cond_config1, 7))
|
||||
/* DP6 联动配置 */
|
||||
if (memcmp(last->linkage_config, cur->linkage_config, 20))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG1 / 8] |= (1 << (DP_ID_COND_CONFIG1 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP7 条件配置2 */
|
||||
if (memcmp(last->cond_config2, cur->cond_config2, 7))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG2 / 8] |= (1 << (DP_ID_COND_CONFIG2 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP8 条件配置3 */
|
||||
if (memcmp(last->cond_config3, cur->cond_config3, 7))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG3 / 8] |= (1 << (DP_ID_COND_CONFIG3 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP9 条件配置4 */
|
||||
if (memcmp(last->cond_config4, cur->cond_config4, 7))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG4 / 8] |= (1 << (DP_ID_COND_CONFIG4 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP10 条件配置5 */
|
||||
if (memcmp(last->cond_config5, cur->cond_config5, 7))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG5 / 8] |= (1 << (DP_ID_COND_CONFIG5 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP11 条件配置6 */
|
||||
if (memcmp(last->cond_config6, cur->cond_config6, 7))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG6 / 8] |= (1 << (DP_ID_COND_CONFIG6 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP12 条件配置7 */
|
||||
if (memcmp(last->cond_config7, cur->cond_config7, 7))
|
||||
{
|
||||
mask[DP_ID_COND_CONFIG7 / 8] |= (1 << (DP_ID_COND_CONFIG7 % 8));
|
||||
mask[DP_ID_LINKAGE_CONFIG / 8] |= (1 << (DP_ID_LINKAGE_CONFIG % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP13 定时使能 */
|
||||
@@ -817,43 +680,45 @@ static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
|
||||
mask[DP_ID_TIME_CONFIG7 / 8] |= (1 << (DP_ID_TIME_CONFIG7 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP21 设备故障使能 */
|
||||
/* DP21 运行记录:每秒都在变,不参与差分上报(避免 2s 防抖推 124B)。
|
||||
* 读请求与 REPORT_PERIOD 全量仍带当前 gDevData.run_record。 */
|
||||
/* DP23 设备故障使能 */
|
||||
if (last->fault_en != cur->fault_en)
|
||||
{
|
||||
mask[DP_ID_FAULT_EN / 8] |= (1 << (DP_ID_FAULT_EN % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP22 设备故障 */
|
||||
/* DP24 设备故障 */
|
||||
if (last->fault_code != cur->fault_code)
|
||||
{
|
||||
mask[DP_ID_FAULT_CODE / 8] |= (1 << (DP_ID_FAULT_CODE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP23 设备告警使能 */
|
||||
/* DP25 设备告警使能 */
|
||||
if (last->alarm_en != cur->alarm_en)
|
||||
{
|
||||
mask[DP_ID_ALARM_EN / 8] |= (1 << (DP_ID_ALARM_EN % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP24 设备告警 */
|
||||
/* DP26 设备告警 */
|
||||
if (last->alarm_code != cur->alarm_code)
|
||||
{
|
||||
mask[DP_ID_ALARM_CODE / 8] |= (1 << (DP_ID_ALARM_CODE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP26 预留DP1 */
|
||||
/* DP28 预留DP1 */
|
||||
if (last->reserva_dp1 != cur->reserva_dp1)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP27 预留DP2 */
|
||||
/* DP29 预留DP2 */
|
||||
if (last->reserva_dp2 != cur->reserva_dp2)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP28 预留DP3 */
|
||||
/* DP30 预留DP3 */
|
||||
if (last->reserva_dp3 != cur->reserva_dp3)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ================================================================
|
||||
* jb_protocol.h — 见宝 IOT 协议层(自动生成)
|
||||
* 设备: 温湿度风扇 | DP 数: 29 | Bitmap: 4B
|
||||
* 协议版本: V1.0 | 生成时间: 2026-08-29 10:31:50
|
||||
* 设备: 温湿度风扇 | DP 数: 25 | Bitmap: 4B
|
||||
* 协议版本: V1.0 | 生成时间: 2026-09-02 16:01:44
|
||||
*
|
||||
* 帧格式: [55 AA] [LEN_H LEN_L] [CMD] [SN] [payload...] [CKSUM]
|
||||
* LEN = CMD(1) + SN(1) + payload + CKSUM(1) = payload + 3
|
||||
@@ -23,10 +23,13 @@
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define PKT_HEAD_0 0x55
|
||||
#define PKT_HEAD_1 0xAA
|
||||
/* ⚠ 生成器给的是 128,必须改回 256:DP21 run_record 124B,加 bitmap(4B) 与
|
||||
* cmd/sn/cksum 开销后 128B 组不出完整帧(规格书第 0 章第 1 条 / 第 19 章)。
|
||||
* 每次从生成器重新拷贝 jb_protocol.h 后都要重新确认本值。 */
|
||||
#define MAX_PACKAGE_LEN 256
|
||||
#define JB_OTA_MAX_PAYLOAD 96 /* OTA 单包 Payload 上限(Byte); 须 <= MAX_PACKAGE_LEN - 11(0x32 帧开销) */
|
||||
|
||||
#define DP_COUNT 29
|
||||
#define DP_COUNT 25
|
||||
#define DP_BITMAP_SIZE 4
|
||||
|
||||
#define SEND_MAX_TIME 2500 /* ACK 重传超时(ms);BLE Indication往返常需数百ms~1s,过短会误重传导致双应答 */
|
||||
@@ -123,14 +126,8 @@ typedef enum
|
||||
DP_ID_FAN_GEAR = 2, /* 风速挡位 (enum, RW) */
|
||||
DP_ID_CURRENT_TEMP = 3, /* 当前温度 (float, RO) */
|
||||
DP_ID_CURRENT_HUMI = 4, /* 当前湿度 (uint8, RO) */
|
||||
DP_ID_COND_EN = 5, /* 条件使能 (bool, RW) */
|
||||
DP_ID_COND_CONFIG1 = 6, /* 条件配置1 (raw, RW) */
|
||||
DP_ID_COND_CONFIG2 = 7, /* 条件配置2 (raw, RW) */
|
||||
DP_ID_COND_CONFIG3 = 8, /* 条件配置3 (raw, RW) */
|
||||
DP_ID_COND_CONFIG4 = 9, /* 条件配置4 (raw, RW) */
|
||||
DP_ID_COND_CONFIG5 = 10, /* 条件配置5 (raw, RW) */
|
||||
DP_ID_COND_CONFIG6 = 11, /* 条件配置6 (raw, RW) */
|
||||
DP_ID_COND_CONFIG7 = 12, /* 条件配置7 (raw, RW) */
|
||||
DP_ID_LINKAGE_DELETE = 5, /* 联动删除 (bool, RW) */
|
||||
DP_ID_LINKAGE_CONFIG = 6, /* 联动配置 (raw, RW) */
|
||||
DP_ID_TIME_EN = 13, /* 定时使能 (bool, RW) */
|
||||
DP_ID_TIME_CONFIG1 = 14, /* 定时配置1 (raw, RW) */
|
||||
DP_ID_TIME_CONFIG2 = 15, /* 定时配置2 (raw, RW) */
|
||||
@@ -139,14 +136,16 @@ typedef enum
|
||||
DP_ID_TIME_CONFIG5 = 18, /* 定时配置5 (raw, RW) */
|
||||
DP_ID_TIME_CONFIG6 = 19, /* 定时配置6 (raw, RW) */
|
||||
DP_ID_TIME_CONFIG7 = 20, /* 定时配置7 (raw, RW) */
|
||||
DP_ID_FAULT_EN = 21, /* 设备故障使能 (uint8, RW) */
|
||||
DP_ID_FAULT_CODE = 22, /* 设备故障 (uint8, RO) */
|
||||
DP_ID_ALARM_EN = 23, /* 设备告警使能 (uint8, RW) */
|
||||
DP_ID_ALARM_CODE = 24, /* 设备告警 (uint8, RO) */
|
||||
DP_ID_FACTORY_RESET = 25, /* 恢复出厂设置 (bool, WO) */
|
||||
DP_ID_RESERVA_DP1 = 26, /* 预留DP1 (uint32, RW) */
|
||||
DP_ID_RESERVA_DP2 = 27, /* 预留DP2 (uint32, RW) */
|
||||
DP_ID_RESERVA_DP3 = 28, /* 预留DP3 (uint32, RW) */
|
||||
DP_ID_RUN_RECORD = 21, /* 运行记录 (raw, RO) */
|
||||
DP_ID_HEARTBEAT = 22, /* 心跳 (bool, WO) */
|
||||
DP_ID_FAULT_EN = 23, /* 设备故障使能 (uint8, RW) */
|
||||
DP_ID_FAULT_CODE = 24, /* 设备故障 (uint8, RO) */
|
||||
DP_ID_ALARM_EN = 25, /* 设备告警使能 (uint8, RW) */
|
||||
DP_ID_ALARM_CODE = 26, /* 设备告警 (uint8, RO) */
|
||||
DP_ID_FACTORY_RESET = 27, /* 恢复出厂设置 (bool, WO) */
|
||||
DP_ID_RESERVA_DP1 = 28, /* 预留DP1 (uint32, RW) */
|
||||
DP_ID_RESERVA_DP2 = 29, /* 预留DP2 (uint32, RW) */
|
||||
DP_ID_RESERVA_DP3 = 30, /* 预留DP3 (uint32, RW) */
|
||||
DP_ID_MAX
|
||||
} dp_id_t;
|
||||
|
||||
@@ -156,14 +155,8 @@ typedef enum
|
||||
#define DP_LEN_FAN_GEAR 1
|
||||
#define DP_LEN_CURRENT_TEMP 2
|
||||
#define DP_LEN_CURRENT_HUMI 1
|
||||
#define DP_LEN_COND_EN 1
|
||||
#define DP_LEN_COND_CONFIG1 7
|
||||
#define DP_LEN_COND_CONFIG2 7
|
||||
#define DP_LEN_COND_CONFIG3 7
|
||||
#define DP_LEN_COND_CONFIG4 7
|
||||
#define DP_LEN_COND_CONFIG5 7
|
||||
#define DP_LEN_COND_CONFIG6 7
|
||||
#define DP_LEN_COND_CONFIG7 7
|
||||
#define DP_LEN_LINKAGE_DELETE 1
|
||||
#define DP_LEN_LINKAGE_CONFIG 20
|
||||
#define DP_LEN_TIME_EN 1
|
||||
#define DP_LEN_TIME_CONFIG1 8
|
||||
#define DP_LEN_TIME_CONFIG2 8
|
||||
@@ -172,6 +165,8 @@ typedef enum
|
||||
#define DP_LEN_TIME_CONFIG5 8
|
||||
#define DP_LEN_TIME_CONFIG6 8
|
||||
#define DP_LEN_TIME_CONFIG7 8
|
||||
#define DP_LEN_RUN_RECORD 124
|
||||
#define DP_LEN_HEARTBEAT 1
|
||||
#define DP_LEN_FAULT_EN 1
|
||||
#define DP_LEN_FAULT_CODE 1
|
||||
#define DP_LEN_ALARM_EN 1
|
||||
@@ -213,14 +208,8 @@ typedef struct
|
||||
uint8_t fan_gear; /* DP2 风速挡位 */
|
||||
float current_temp; /* DP3 当前温度 (float, scale=0.1) */
|
||||
uint8_t current_humi; /* DP4 当前湿度 */
|
||||
uint8_t cond_en; /* DP5 条件使能 */
|
||||
uint8_t cond_config1[7];/* DP6 条件配置1 (7B: en/tempH/tempL/humi/dur/action/cond) */
|
||||
uint8_t cond_config2[7];/* DP7 条件配置2 */
|
||||
uint8_t cond_config3[7];/* DP8 条件配置3 */
|
||||
uint8_t cond_config4[7];/* DP9 条件配置4 */
|
||||
uint8_t cond_config5[7];/* DP10 条件配置5 */
|
||||
uint8_t cond_config6[7];/* DP11 条件配置6 */
|
||||
uint8_t cond_config7[7];/* DP12 条件配置7 */
|
||||
uint8_t linkage_delete;/* DP5 联动删除 */
|
||||
uint8_t linkage_config[20];/* DP6 联动配置 */
|
||||
uint8_t time_en; /* DP13 定时使能 */
|
||||
uint8_t time_config1[8];/* DP14 定时配置1 */
|
||||
uint8_t time_config2[8];/* DP15 定时配置2 */
|
||||
@@ -229,14 +218,16 @@ typedef struct
|
||||
uint8_t time_config5[8];/* DP18 定时配置5 */
|
||||
uint8_t time_config6[8];/* DP19 定时配置6 */
|
||||
uint8_t time_config7[8];/* DP20 定时配置7 */
|
||||
uint8_t fault_en; /* DP21 设备故障使能 */
|
||||
uint8_t fault_code; /* DP22 设备故障 */
|
||||
uint8_t alarm_en; /* DP23 设备告警使能 */
|
||||
uint8_t alarm_code; /* DP24 设备告警 */
|
||||
uint8_t factory_reset;/* DP25 恢复出厂设置 */
|
||||
uint32_t reserva_dp1; /* DP26 预留DP1 */
|
||||
uint32_t reserva_dp2; /* DP27 预留DP2 */
|
||||
uint32_t reserva_dp3; /* DP28 预留DP3 */
|
||||
uint8_t run_record[124];/* DP21 运行记录 */
|
||||
uint8_t heartbeat; /* DP22 心跳 */
|
||||
uint8_t fault_en; /* DP23 设备故障使能 */
|
||||
uint8_t fault_code; /* DP24 设备故障 */
|
||||
uint8_t alarm_en; /* DP25 设备告警使能 */
|
||||
uint8_t alarm_code; /* DP26 设备告警 */
|
||||
uint8_t factory_reset;/* DP27 恢复出厂设置 */
|
||||
uint32_t reserva_dp1; /* DP28 预留DP1 */
|
||||
uint32_t reserva_dp2; /* DP29 预留DP2 */
|
||||
uint32_t reserva_dp3; /* DP30 预留DP3 */
|
||||
} jb_data_point_t;
|
||||
|
||||
/* 控制事件信息 */
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
* @file usr_jb_main.c
|
||||
* @brief CBR3 业务组合根:只做"先后调用谁、谁通知谁",不含具体业务算法
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.31
|
||||
* @version V2.3.0,上电即开机(无 app_boot);5s 配对 / 12s OTA 由 key_manager
|
||||
* 长按分级触发;关机=拔电,已删除全部 app_power 软关机基础设施及
|
||||
* DP0 开关相关死代码(power 恒 1,写死在 app_function_init);
|
||||
* 删除 usr_timer_pick_mode/rearm 动态休眠调度(Type-C 长供电不
|
||||
* 需要低功耗,且 app_disp_scan 常驻 priority=1 已让系统永远无法
|
||||
* 休眠,该机制本来就没有实际效果),业务定时器改为一次性固定注册
|
||||
* @date 2026.09.02
|
||||
* @version V3.0.0,规格书 V3.0.0:条件引擎(app_cond)整模块删除,换
|
||||
* app_linkage(DP5/DP6,联动)+ app_runrec(DP21,31 天运行记录);
|
||||
* 20ms 节拍加 app_runrec_tick,500ms 任务上下文加
|
||||
* app_linkage_run / app_runrec_rotate_if_needed
|
||||
*
|
||||
* @note 协议生成文件 jb_product.c 不要填业务。APP 下行写 gDevData。
|
||||
* usr_timer_loop 由 BLE 回调,勿再注册第二份。
|
||||
@@ -18,6 +16,8 @@
|
||||
* app_fan —— 风扇档位
|
||||
* app_th —— AHT20 采样
|
||||
* app_disp —— 数码管显示
|
||||
* app_linkage —— 联动开停(第 10 章)+ 8.2 闪烁喂数
|
||||
* app_runrec —— 31 天运行秒数(第 13.1 节)
|
||||
* app_function —— 按键动作 + DP 同步 + 配对/OTA 动作落地
|
||||
******************************************************************************/
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
#include "app_th.h"
|
||||
#include "app_disp.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_cond.h"
|
||||
#include "app_linkage.h"
|
||||
#include "app_runrec.h"
|
||||
#include "app_timer_task.h"
|
||||
#include "app_arbiter.h"
|
||||
#include "app_nvm.h"
|
||||
@@ -90,9 +91,10 @@ static void usr_timer_tick(void *priv)
|
||||
user_key_scan(dt); /* 内部完成 5s/12s 长按分级,只置标志 */
|
||||
user_key_handle(); /* 派发短按:切档 */
|
||||
app_th_poll(dt); /* 每2s采集一次温湿度 */
|
||||
app_mode_run(dt); /* 通风/强力状态机(20ms 计时精度;条件/定时在 500ms 任务上下文) */
|
||||
app_mode_run(dt); /* 通风/强力状态机(20ms 计时精度;联动/定时在 500ms 任务上下文) */
|
||||
app_disp_run(dt); /* 刷新显示(告警闪烁) */
|
||||
app_function_poll(); /* 同步 DP */
|
||||
app_runrec_tick(dt); /* 风扇在转只折 pending 秒,不改 31 格、不写 flash */
|
||||
app_nvm_tick(dt); /* 档位/模式/配置变化检测 + 500ms 防抖计时(不写 flash!) */
|
||||
|
||||
/* OTA 灯效期间最高优先级;power_on 恒 1(DP0 预留不当开关,规格书 0 章第 5 条) */
|
||||
@@ -135,11 +137,12 @@ void usr_jb_init(void)
|
||||
usr_timer_add(NULL, app_disp_scan, BOARD_DISP_SCAN_MS, 1);
|
||||
app_th_init();
|
||||
app_mode_init();
|
||||
app_arbiter_init(); /* 模式仲裁(手动>定时>条件)先于条件/定时引擎 */
|
||||
app_cond_init();
|
||||
app_arbiter_init(); /* 模式仲裁(手动>定时>联动)先于联动/定时引擎 */
|
||||
app_linkage_init();
|
||||
app_runrec_init();
|
||||
app_timer_init();
|
||||
app_nvm_init();
|
||||
app_nvm_load();
|
||||
app_nvm_load(); /* 会回填 linkage_config 与 31 天运行记录(13 章) */
|
||||
|
||||
app_function_init();
|
||||
|
||||
@@ -170,8 +173,9 @@ void usr_jb_le_recieve_data(u8 *data, u16 len)
|
||||
* ⚠ 本函数跑在 sys_timer 回调(usr_ble_adv_updata 500ms)= **任务上下文**
|
||||
* (BLE 线程,栈充足),与 20ms 的 usr_timer_tick(usr_timer_add 异步
|
||||
* 接口 = 硬件定时器中断上下文,栈极小)不同。
|
||||
* **所有重量级/自动化引擎必须放这里**:条件/定时引擎(500ms 粒度对
|
||||
* 2s 采样与分钟级时长足够,且单次自毁要写 VM)、VM 落盘、配对/OTA 落地。
|
||||
* **所有重量级/自动化引擎必须放这里**:联动/定时引擎(500ms 粒度对
|
||||
* 2s 采样与分钟级时长足够,且单次自毁要写 VM)、运行记录跨天旋转与
|
||||
* 落盘、VM 落盘、配对/OTA 落地。
|
||||
* 禁止放进 usr_timer_tick——曾因在 20ms 中断里调 syscfg_write 栈溢出软复位。
|
||||
* @return 无
|
||||
*/
|
||||
@@ -179,7 +183,8 @@ void usr_timer_loop(void)
|
||||
{
|
||||
userHandle();
|
||||
jbProtocolHandle(&gDevData);
|
||||
app_cond_run(500); /* 条件引擎(任务上下文;条件/定时仲裁见 app_arbiter) */
|
||||
app_timer_run(500); /* 定时引擎(任务上下文;RTC 门闩 + 窗判断) */
|
||||
app_function_loop(); /* 任务上下文:VM 防抖落盘 + 配对/OTA 动作落地 */
|
||||
app_timer_run(500); /* 定时引擎(RTC 门闩 + 窗判断;窗结束会调联动重判) */
|
||||
app_linkage_run(500); /* 联动引擎(任务上下文;仲裁见 app_arbiter) */
|
||||
app_runrec_rotate_if_needed(); /* 收下 pending、每拍灌 DP21;满 10 分钟/跨天才写 VM */
|
||||
app_function_loop(); /* 任务上下文:VM 防抖落盘 + 配对/OTA 动作落地 */
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
#define BOARD_DISP_ENABLE 1 /**< 1=CT1642 数码管(PG0/PG1 独立) */
|
||||
#define BOARD_TH_ENABLE 1 /**< 1=AHT20 温湿度(PC4/PC5 独立) */
|
||||
#define BOARD_MODE_ENABLE 1 /* 通风/强力状态机;P1 开 */
|
||||
#define BOARD_COND_ENABLE 1 /* 条件引擎;P2 开 */
|
||||
#define BOARD_LINKAGE_ENABLE 1 /* 联动引擎(替换原 7 组条件);P2 开 */
|
||||
#define BOARD_TIMER_ENABLE 1 /* 定时引擎;P2 开 */
|
||||
#define BOARD_RUNREC_ENABLE 1 /* 31 天运行记录;P2 开 */
|
||||
#define BOARD_NVM_ENABLE 1 /* VM 持久化;P1 开 */
|
||||
|
||||
/*============================================================================*/
|
||||
@@ -106,9 +107,9 @@
|
||||
|
||||
/**
|
||||
* 告警闪烁调试宏(P1 台上验证用,正常固件保持 0):
|
||||
* 0 = 关:闪烁由 app_disp_set_temp/humi_alarm 驱动(P2 条件引擎喂)
|
||||
* 1 = 强制温度管闪(模拟温度比较成立)
|
||||
* 2 = 强制湿度管闪(模拟湿度比较成立)
|
||||
* 0 = 关:闪烁由 app_disp_set_temp/humi_alarm 驱动(app_linkage 按 8.2 喂)
|
||||
* 1 = 强制温度管闪(模拟温度超 [min,max])
|
||||
* 2 = 强制湿度管闪(模拟湿度超 [min,max])
|
||||
*/
|
||||
#define BOARD_DISP_ALARM_TEST 0
|
||||
|
||||
@@ -156,4 +157,18 @@
|
||||
#define BOARD_MODE_VENT_OFF_MS (5u * 60u * 1000u) /**< 通风停段 5min */
|
||||
#define BOARD_MODE_POWER_ON_MS (15u * 60u * 1000u) /**< 强力一轮 15min */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 联动(第 10 章:1 条 20B 配置,回差开 1 档 / 关 0 档,不改 mode) */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_LINKAGE_CFG_LEN 20 /**< DP6 linkage_config 长度(Byte) */
|
||||
#define BOARD_LINKAGE_OPEN_GEAR 1 /**< 联动开风扇时写的档位(第 10.3 节) */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 运行记录(第 13.1 节:31 天 × uint32 秒) */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_RUN_RECORD_DAYS 31 /**< 记录天数 */
|
||||
#define BOARD_RUN_RECORD_SAVE_SEC 900 /**< 累计运行 15 分钟写一次 VM */
|
||||
|
||||
#endif /* __BOARD_PIN_H__ */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
lp_signature_set = ABSOLUTE(0x1fd6c);
|
||||
memmem = ABSOLUTE(0x1fd70);
|
||||
memcpy = ABSOLUTE(0x1fd74);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
sdfile_vfs_ops
|
||||
sbc_decoder
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
@echo off
|
||||
Setlocal enabledelayedexpansion
|
||||
@echo ********************************************************************************
|
||||
|
||||
+44
-89
@@ -15,7 +15,7 @@
|
||||
"len": 1,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "设备总开关",
|
||||
"description": "设备总开关,预留",
|
||||
"values": {
|
||||
"0": "关",
|
||||
"1": "开"
|
||||
@@ -80,101 +80,29 @@
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"display_name": "条件使能",
|
||||
"key": "cond_en",
|
||||
"display_name": "联动删除",
|
||||
"key": "linkage_delete",
|
||||
"kind": "normal",
|
||||
"type": "bool",
|
||||
"len": 1,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "一键启动/禁用所有条件配置",
|
||||
"description": "APP没用到此设备,则下发联动删除",
|
||||
"values": {
|
||||
"0": "关",
|
||||
"1": "开"
|
||||
"0": "无操作",
|
||||
"1": "删除联动"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"display_name": "条件配置1",
|
||||
"key": "cond_config1",
|
||||
"display_name": "联动配置",
|
||||
"key": "linkage_config",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"len": 20,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "温度阈值:有符号int16大端,值=℃×10;Byte0=使能;Byte1-2=温度阈值;Byte3=湿度阈值;Byte4=持续分钟(0=立即);Byte5=动作模式(0/1/2);Byte6=Bit7-5温度比较, Bit4-2湿度比较, Bit1-0保留",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"display_name": "条件配置2",
|
||||
"key": "cond_config2",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "同上(7字节)",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"display_name": "条件配置3",
|
||||
"key": "cond_config3",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "同上(7字节)",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"display_name": "条件配置4",
|
||||
"key": "cond_config4",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "同上(7字节)",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"display_name": "条件配置5",
|
||||
"key": "cond_config5",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "同上(7字节)",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"display_name": "条件配置6",
|
||||
"key": "cond_config6",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "同上(7字节)",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"display_name": "条件配置7",
|
||||
"key": "cond_config7",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 7,
|
||||
"scale": 1.0,
|
||||
"dir": "rw",
|
||||
"description": "同上(7字节)",
|
||||
"description": "缸体编号为uint16,温度类型为int8,湿度和版本为uint8;\n后面应该会加上条件和动作,先预留13个Byte",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
@@ -278,6 +206,33 @@
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"display_name": "运行记录",
|
||||
"key": "run_record",
|
||||
"kind": "normal",
|
||||
"type": "raw",
|
||||
"len": 124,
|
||||
"scale": 1.0,
|
||||
"dir": "ro",
|
||||
"description": "31天的运行记录,每4个字节表示当天运行的总时长,单位:秒\nByte0-3:今天风扇运行的总时长\nByte4-7:昨天风扇运行的总时长\n.\n.\n.\nByte120-123:前30天风扇运行的总时长",
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"display_name": "心跳",
|
||||
"key": "heartbeat",
|
||||
"kind": "normal",
|
||||
"type": "bool",
|
||||
"len": 1,
|
||||
"scale": 1.0,
|
||||
"dir": "wo",
|
||||
"description": "APP进入页面之后每5s下发一次心跳,当子设备12秒内没收到心跳\n则停止温湿度上报服务器,防止上报频繁,此条DP预留",
|
||||
"values": {
|
||||
"0": "无操作",
|
||||
"1": "心跳"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"display_name": "设备故障使能",
|
||||
"key": "fault_en",
|
||||
"kind": "normal",
|
||||
@@ -289,7 +244,7 @@
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"id": 24,
|
||||
"display_name": "设备故障",
|
||||
"key": "fault_code",
|
||||
"kind": "fault",
|
||||
@@ -306,7 +261,7 @@
|
||||
"level": 2
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"id": 25,
|
||||
"display_name": "设备告警使能",
|
||||
"key": "alarm_en",
|
||||
"kind": "normal",
|
||||
@@ -318,7 +273,7 @@
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"id": 26,
|
||||
"display_name": "设备告警",
|
||||
"key": "alarm_code",
|
||||
"kind": "alarm",
|
||||
@@ -331,7 +286,7 @@
|
||||
"level": 1
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"id": 27,
|
||||
"display_name": "恢复出厂设置",
|
||||
"key": "factory_reset",
|
||||
"kind": "normal",
|
||||
@@ -345,7 +300,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"id": 28,
|
||||
"display_name": "预留DP1",
|
||||
"key": "reserva_dp1",
|
||||
"kind": "normal",
|
||||
@@ -357,7 +312,7 @@
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"id": 29,
|
||||
"display_name": "预留DP2",
|
||||
"key": "reserva_dp2",
|
||||
"kind": "normal",
|
||||
@@ -369,7 +324,7 @@
|
||||
"values": {}
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"id": 30,
|
||||
"display_name": "预留DP3",
|
||||
"key": "reserva_dp3",
|
||||
"kind": "normal",
|
||||
|
||||
Reference in New Issue
Block a user