删除条件配置功能,修改成联动配置DP,并且增加31天运行记录,修改程序需求

This commit is contained in:
2026-09-03 10:21:11 +08:00
parent 5377186cfb
commit d56d8204e1
31 changed files with 1609 additions and 1430 deletions
+195 -122
View File
@@ -6,13 +6,16 @@
| 芯片 | JL7016 / AC7016CBR28 |
| 工程 | `温湿度风扇_soc_JieLi-JL7016` |
| 产品码 | ProdtCode=`CBR3`productCode=`PRCBR3` |
| 协议 | 见宝 SOC 55AAV1.0.0 |
| 文档版本 | V2.2.0 |
| 日期 | 2026.08.31 |
| 协议 | 见宝 SOC 55AAV1.0.02026-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** 后:风扇 0mode 回到 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. 条件引擎(P27 组)
## 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,只要有效配置就判闪):
- 缸体编号 ≠ 0Byte01uint16 大端)
- `Tmin ≤ Tmax``Hmin ≤ Hmax`
- `BOARD_LINKAGE_ENABLE=1`
否则视为无联动:不自动开 1 档;若原先是联动占着风扇则关掉。
缸体编号只存、不参与开停判断(本机一路 AHT20)。配置版本(Byte6)只存不解释。Byte7–19 预留:整包 20B 原样落盘,控制逻辑忽略。
### 10.2 20 字节布局(DP6 `linkage_config`
| 偏移 | 字段 | 编码 |
|---|---|---|
| Byte0 | 使能 | 0=关,非 0=开 |
| Byte12 | 温度阈值 | **有符号 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 保留 |
| Byte01 | 缸体编号 | uint16 **大端** |
| Byte2 | 最低温度 `Tmin` | 有符号 int8,单位 ℃ |
| Byte3 | 最高温度 `Tmax` | 有符号 int8,单位 |
| Byte4 | 最低湿度 `Hmin` | uint8,单位 % |
| Byte5 | 最高湿度 `Hmax` | uint8,单位 % |
| Byte6 | 配置版本 | uint8,只存不用 |
| Byte719 | 预留 | 只存不用 |
比较编码(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 | &lt; |
| 010 | &lt;= |
| 011 | == |
| 100 | &gt;= |
| 101 | &gt; |
| 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,直到结束时刻。
- 结束时刻:风扇 0mode=0。
- 结束时若条件比较仍成立:**不要** 立刻让条件接手,必须重新满时长(第 12 章)。
- 结束时刻:风扇 0mode=0。然后 **立刻按第 10.3 节判一次联动**(不是手动作废):仍超最高 → 开 1 档。
---
@@ -437,77 +460,113 @@ usr_rtc_get_time() 失败 → 全部定时不执行(等同 time_en=0)
优先级 **高 → 低**
1. **手动**:短按、APP 写 `fan_gear`、APP 写 `mode`
2. **定时窗**RTC 有效且 `time_en=1` 且当前落在窗内)
3. **条件**`cond_en=1` 且已触发保持中
1. **手动**:短按、APP 写 `fan_gear`、APP 写 `mode`
2. **定时窗**RTC 有效且 `time_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=0gear=0条件/定时全 0`cond_en=time_en=0`
首次上电(VM 无效):mode=0gear=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`RO124B
31 天 × 4 字节,每格 uint32 **大端**,单位秒:
| 偏移 | 含义 |
|---|---|
| Byte0–3 | 今天风扇运行总秒数 |
| Byte47 | 昨天 |
| … | … |
| Byte120123 | 前 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=0gear=0,风扇停,条件/定时停止。
- RAM 中 mode=0gear=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_condDP5/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 节为准。