1、喷淋器 P1/P2功能实现
2、红灯按连接态慢闪
This commit is contained in:
+24
@@ -794,9 +794,28 @@
|
|||||||
<Unit filename="apps/usr_app/app_function.c">
|
<Unit filename="apps/usr_app/app_function.c">
|
||||||
<Option compilerVar="CC" />
|
<Option compilerVar="CC" />
|
||||||
</Unit>
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_arbiter.c">
|
||||||
|
<Option compilerVar="CC" />
|
||||||
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_linkage.c">
|
||||||
|
<Option compilerVar="CC" />
|
||||||
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_runrec.c">
|
||||||
|
<Option compilerVar="CC" />
|
||||||
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_th_scan.c">
|
||||||
|
<Option compilerVar="CC" />
|
||||||
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_timer_task.c">
|
||||||
|
<Option compilerVar="CC" />
|
||||||
|
</Unit>
|
||||||
<Unit filename="apps/usr_app/app_led.c">
|
<Unit filename="apps/usr_app/app_led.c">
|
||||||
<Option compilerVar="CC" />
|
<Option compilerVar="CC" />
|
||||||
</Unit>
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_nvm.c">
|
||||||
|
<Option compilerVar="CC" />
|
||||||
|
</Unit>
|
||||||
|
<Unit filename="apps/usr_app/app_nvm.h" />
|
||||||
<Unit filename="apps/usr_app/app_pair.c">
|
<Unit filename="apps/usr_app/app_pair.c">
|
||||||
<Option compilerVar="CC" />
|
<Option compilerVar="CC" />
|
||||||
</Unit>
|
</Unit>
|
||||||
@@ -820,6 +839,11 @@
|
|||||||
</Unit>
|
</Unit>
|
||||||
<Unit filename="apps/usr_app/app_defer.h" />
|
<Unit filename="apps/usr_app/app_defer.h" />
|
||||||
<Unit filename="apps/usr_app/app_function.h" />
|
<Unit filename="apps/usr_app/app_function.h" />
|
||||||
|
<Unit filename="apps/usr_app/app_arbiter.h" />
|
||||||
|
<Unit filename="apps/usr_app/app_linkage.h" />
|
||||||
|
<Unit filename="apps/usr_app/app_runrec.h" />
|
||||||
|
<Unit filename="apps/usr_app/app_th_scan.h" />
|
||||||
|
<Unit filename="apps/usr_app/app_timer_task.h" />
|
||||||
<Unit filename="apps/usr_app/app_led.h" />
|
<Unit filename="apps/usr_app/app_led.h" />
|
||||||
<Unit filename="apps/usr_app/app_pair.h" />
|
<Unit filename="apps/usr_app/app_pair.h" />
|
||||||
<Unit filename="apps/usr_app/app_pump.h" />
|
<Unit filename="apps/usr_app/app_pump.h" />
|
||||||
|
|||||||
+22
-15
@@ -7,7 +7,7 @@
|
|||||||
| 工程 | `喷淋器_soc_JieLi-JL7016` |
|
| 工程 | `喷淋器_soc_JieLi-JL7016` |
|
||||||
| 产品码 | ProdtCode=`CBR2`,productCode=`PRCBR2` |
|
| 产品码 | ProdtCode=`CBR2`,productCode=`PRCBR2` |
|
||||||
| 协议 | 见宝 SOC 55AA,V1.0.0 |
|
| 协议 | 见宝 SOC 55AA,V1.0.0 |
|
||||||
| 文档版本 | V1.0.0 |
|
| 文档版本 | V1.0.2 |
|
||||||
| 日期 | 2026.09.09 |
|
| 日期 | 2026.09.09 |
|
||||||
| 作者 | cyWu |
|
| 作者 | cyWu |
|
||||||
| 读者 | WorkBuddy / 固件开发 |
|
| 读者 | WorkBuddy / 固件开发 |
|
||||||
@@ -15,6 +15,10 @@
|
|||||||
| 框架源头 | `D:\Jianbao\Project\产品\爬宠\温湿度风扇\温湿度风扇_soc_JieLi-JL7016`(20ms + 500ms、按键分级、扫描时隙) |
|
| 框架源头 | `D:\Jianbao\Project\产品\爬宠\温湿度风扇\温湿度风扇_soc_JieLi-JL7016`(20ms + 500ms、按键分级、扫描时隙) |
|
||||||
| 灯驱参考 | `D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016`(红绿双通道硬件;**事件表不要抄加热垫互斥灯**) |
|
| 灯驱参考 | `D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016`(红绿双通道硬件;**事件表不要抄加热垫互斥灯**) |
|
||||||
|
|
||||||
|
> **V1.0.2(2026-09-09):** 红灯跟 **BLE 连接态**:已连接常亮;已配对未连接(上电重连 / 断连)500ms 慢闪。避免上电把「已绑定」当成「已联网」。
|
||||||
|
>
|
||||||
|
> **V1.0.1(2026-09-09):** 灯脚改为与加热垫相同:**PA8 绿 / PA7 红**(低亮)。红绿通道独立语义不变。
|
||||||
|
>
|
||||||
> **V1.0.0(2026-09-09):** 首版共同理解。5V 常电双脚水泵;两键两灯;红绿通道独立;单击按上次配置跑「时长 × 次数 + 间隔」;APP 写 DP7 立刻开喷;湿度联动回差;7 条计划到点触发一轮(不是时间窗);运行记录计水泵实际转动秒;`MAX_PACKAGE_LEN=512`。
|
> **V1.0.0(2026-09-09):** 首版共同理解。5V 常电双脚水泵;两键两灯;红绿通道独立;单击按上次配置跑「时长 × 次数 + 间隔」;APP 写 DP7 立刻开喷;湿度联动回差;7 条计划到点触发一轮(不是时间窗);运行记录计水泵实际转动秒;`MAX_PACKAGE_LEN=512`。
|
||||||
|
|
||||||
本文是 **已拍板的共同理解**,不是草稿。实现时以本文为准;DP 表以 `dp_definition.json` + 本文第 10.2 / 11.2 / 12.1 / 14.1 / 16 章为准。
|
本文是 **已拍板的共同理解**,不是草稿。实现时以本文为准;DP 表以 `dp_definition.json` + 本文第 10.2 / 11.2 / 12.1 / 14.1 / 16 章为准。
|
||||||
@@ -36,7 +40,7 @@
|
|||||||
11. 编码风格对齐风扇 + 工程师规范:文件小写+下划线,`g_` / `s_` 前缀,枚举 `Pascal_t`,接口 `xxx_init` / `xxx_run` 或 tick / `xxx_get_status`。
|
11. 编码风格对齐风扇 + 工程师规范:文件小写+下划线,`g_` / `s_` 前缀,枚举 `Pascal_t`,接口 `xxx_init` / `xxx_run` 或 tick / `xxx_get_status`。
|
||||||
12. **现有工程是协议骨架。** 没有 `apps/usr_app`、没有 `board_pin.h`。`app_main.c` 里上电强制 `usr_goto_pair=1` **必须删掉**,改为上电进工作态(已配对走重连广播)。
|
12. **现有工程是协议骨架。** 没有 `apps/usr_app`、没有 `board_pin.h`。`app_main.c` 里上电强制 `usr_goto_pair=1` **必须删掉**,改为上电进工作态(已配对走重连广播)。
|
||||||
13. 调试 UART TX = **PC5**。关掉会与 PA8 / PA7 / PB1 / PA0 / PC2 / PA6 冲突的 demo 外设(IR 键、PWM LED、demo I2C 等)。
|
13. 调试 UART TX = **PC5**。关掉会与 PA8 / PA7 / PB1 / PA0 / PC2 / PA6 冲突的 demo 外设(IR 键、PWM LED、demo I2C 等)。
|
||||||
14. **红灯 PA8、绿灯 PA7。** 和加热垫(PA8 绿 / PA7 红)**相反**,禁止照抄加热垫 `board_pin.h` 灯脚。
|
14. **红灯 PA7、绿灯 PA8。** 与加热垫灯脚相同。禁止再把两灯脚写反。
|
||||||
15. **灯是两路独立通道。** 喷淋 / 空闲只改绿灯,不要去关红灯;配网闪红灯时不要去关绿灯。只有 OTA 和配对失败动画临时占用双灯。禁止抄加热垫「开红关绿、互斥」事件表。
|
15. **灯是两路独立通道。** 喷淋 / 空闲只改绿灯,不要去关红灯;配网闪红灯时不要去关绿灯。只有 OTA 和配对失败动画临时占用双灯。禁止抄加热垫「开红关绿、互斥」事件表。
|
||||||
16. **上电绝不自动喷。** 不从 VM 恢复 DP1。泵两脚上电默认低。
|
16. **上电绝不自动喷。** 不从 VM 恢复 DP1。泵两脚上电默认低。
|
||||||
17. **遵守第 0.1 节编码与注释。** 散落 static、把排障过程复制进每个函数、只写不读的计数,一律视为未完成。
|
17. **遵守第 0.1 节编码与注释。** 散落 static、把排障过程复制进每个函数、只写不读的计数,一律视为未完成。
|
||||||
@@ -69,8 +73,8 @@
|
|||||||
|
|
||||||
| 功能 | MCU | 电平 / 说明 |
|
| 功能 | MCU | 电平 / 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 红灯 | **PA8** `IO_PORTA_08` | **低电平亮**(与加热垫灯脚相反) |
|
| 红灯 | **PA7** `IO_PORTA_07` | **低电平亮**(与加热垫相同) |
|
||||||
| 绿灯 | **PA7** `IO_PORTA_07` | **低电平亮**(与加热垫灯脚相反) |
|
| 绿灯 | **PA8** `IO_PORTA_08` | **低电平亮**(与加热垫相同) |
|
||||||
| 喷淋键 | **PB1** `IO_PORTB_01` | 低电平按下 |
|
| 喷淋键 | **PB1** `IO_PORTB_01` | 低电平按下 |
|
||||||
| 配网键 | **PA0** `IO_PORTA_00` | 低电平按下 |
|
| 配网键 | **PA0** `IO_PORTA_00` | 低电平按下 |
|
||||||
| 水泵总供电 | **PC2** `IO_PORTC_02` | **高电平**把 5V 导到泵 VCC |
|
| 水泵总供电 | **PC2** `IO_PORTC_02` | **高电平**把 5V 导到泵 VCC |
|
||||||
@@ -185,7 +189,7 @@ P0 建议先 `LINKAGE/TIMER/TH_SCAN/RUNREC/NVM=0`,台上只验双键、双灯
|
|||||||
- 设备工作态恒开机。DP0 本版忽略。DP1 是 **喷淋作业开关**。
|
- 设备工作态恒开机。DP0 本版忽略。DP1 是 **喷淋作业开关**。
|
||||||
- 无电池、无充电逻辑。
|
- 无电池、无充电逻辑。
|
||||||
- **冷启动水泵:** GPIO 先保持关。VM 恢复配置 / 联动 / 计划 / 运行记录,**不恢复 DP1,不开泵**。湿度没到之前联动不改它(第 13 章)。
|
- **冷启动水泵:** GPIO 先保持关。VM 恢复配置 / 联动 / 计划 / 运行记录,**不恢复 DP1,不开泵**。湿度没到之前联动不改它(第 13 章)。
|
||||||
- 上电灯:绿灯常亮;已配对则红灯常亮,未配对红灯灭。
|
- 上电灯:绿灯常亮;未配对红灯灭;已配对未连接红灯 500ms 慢闪;BLE 连上后红灯常亮。
|
||||||
|
|
||||||
### 5.2 两键分工(运行态)
|
### 5.2 两键分工(运行态)
|
||||||
|
|
||||||
@@ -222,7 +226,7 @@ P0 建议先 `LINKAGE/TIMER/TH_SCAN/RUNREC/NVM=0`,台上只验双键、双灯
|
|||||||
|
|
||||||
## 6. LED(红绿独立通道,低亮)
|
## 6. LED(红绿独立通道,低亮)
|
||||||
|
|
||||||
灯脚:**PA8 红、PA7 绿**。两路独立,固件改其中一个 GPIO 时禁止顺手改另一个,除非命中下面的「双灯例外」。
|
灯脚:**PA7 红、PA8 绿**(与加热垫相同)。两路独立,固件改其中一个 GPIO 时禁止顺手改另一个,除非命中下面的「双灯例外」。
|
||||||
|
|
||||||
### 6.1 绿灯(喷淋通道)
|
### 6.1 绿灯(喷淋通道)
|
||||||
|
|
||||||
@@ -239,9 +243,10 @@ P0 建议先 `LINKAGE/TIMER/TH_SCAN/RUNREC/NVM=0`,台上只验双键、双灯
|
|||||||
| 状态 | 红灯 |
|
| 状态 | 红灯 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 未配对 / 配对失败收尾后 | 灭 |
|
| 未配对 / 配对失败收尾后 | 灭 |
|
||||||
| 已配对(含断连重连、上电已配对) | **常亮**(粘住;空闲 / 喷淋都不要关它) |
|
| 已配对、BLE **未连接**(上电重连 / 断连) | **500ms 慢闪**(空闲 / 喷淋都不要关它) |
|
||||||
| 配网键满 5s 仍按住、以及配对进行中 | 100ms 亮 / 100ms 灭(此时即使已配对也改成闪) |
|
| 已配对、BLE **已连接** | **常亮** |
|
||||||
| 配对成功 | 常亮(接着当「已配对」) |
|
| 配网键满 5s 仍按住、以及配对进行中 | 100ms 亮 / 100ms 灭(此时即使已配对也改成快闪) |
|
||||||
|
| 配对成功 | 若当时已连接则常亮,否则先慢闪,连上再常亮 |
|
||||||
|
|
||||||
配网闪红灯期间,绿灯继续按 6.1 闪或常亮,不要关绿灯。
|
配网闪红灯期间,绿灯继续按 6.1 闪或常亮,不要关绿灯。
|
||||||
|
|
||||||
@@ -252,7 +257,7 @@ P0 建议先 `LINKAGE/TIMER/TH_SCAN/RUNREC/NVM=0`,台上只验双键、双灯
|
|||||||
| OTA | 红绿交替,200ms 半周期,直到升级结束 / 复位 | — |
|
| OTA | 红绿交替,200ms 半周期,直到升级结束 / 复位 | — |
|
||||||
| 配对失败 / 超时 | 红绿交替 3s | 红灯灭;绿灯回到空闲或喷淋 |
|
| 配对失败 / 超时 | 红绿交替 3s | 红灯灭;绿灯回到空闲或喷淋 |
|
||||||
|
|
||||||
优先级高→低:**OTA → 配对失败动画 → 各走各的通道(红=配对态,绿=作业态)**。
|
优先级高→低:**OTA → 配对失败动画 → 各走各的通道(红=连接/配网态,绿=作业态)**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -672,7 +677,7 @@ apps/usr_app/
|
|||||||
**目标:** 双键、双灯、双脚泵可独立验证。
|
**目标:** 双键、双灯、双脚泵可独立验证。
|
||||||
|
|
||||||
- [ ] `board_pin.h` + UART TX=PC5,打印正常
|
- [ ] `board_pin.h` + UART TX=PC5,打印正常
|
||||||
- [ ] 插电即开机(不按键,绿灯常亮,泵两脚低;未配对红灯灭)
|
- [ ] 插电即开机(不按键,绿灯常亮,泵两脚低;未配对红灯灭;已配对未连接红灯 500ms 慢闪)
|
||||||
- [ ] 单击 PB1:泵两脚同时高,按默认 10s × 3 次、间隔 3s 跑完;作业中绿灯 500ms 闪;结束后绿灯常亮、泵关
|
- [ ] 单击 PB1:泵两脚同时高,按默认 10s × 3 次、间隔 3s 跑完;作业中绿灯 500ms 闪;结束后绿灯常亮、泵关
|
||||||
- [ ] 作业中再点 PB1:立刻双脚低、绿灯回常亮
|
- [ ] 作业中再点 PB1:立刻双脚低、绿灯回常亮
|
||||||
- [ ] 示波器 / 逻辑:禁止出现「只高一只脚」
|
- [ ] 示波器 / 逻辑:禁止出现「只高一只脚」
|
||||||
@@ -682,7 +687,7 @@ apps/usr_app/
|
|||||||
- [ ] 满 5s 不松手到 12s → 不进配对,红绿交替进 OTA
|
- [ ] 满 5s 不松手到 12s → 不进配对,红绿交替进 OTA
|
||||||
- [ ] 长按进配对 / OTA 后不复位(动作走任务上下文)
|
- [ ] 长按进配对 / OTA 后不复位(动作走任务上下文)
|
||||||
- [ ] 上电强制 `usr_goto_pair=1` 已删除
|
- [ ] 上电强制 `usr_goto_pair=1` 已删除
|
||||||
- [ ] 灯脚确认:PA8=红、PA7=绿(不要抄加热垫)
|
- [ ] 灯脚确认:PA8=绿、PA7=红(与加热垫相同)
|
||||||
|
|
||||||
**P0 关闭:** `BOARD_LINKAGE/TIMER/TH_SCAN/RUNREC/NVM_ENABLE=0`
|
**P0 关闭:** `BOARD_LINKAGE/TIMER/TH_SCAN/RUNREC/NVM_ENABLE=0`
|
||||||
|
|
||||||
@@ -737,8 +742,8 @@ apps/usr_app/
|
|||||||
#define BOARD_UART_TX_PIN IO_PORTC_05
|
#define BOARD_UART_TX_PIN IO_PORTC_05
|
||||||
#define BOARD_KEY_SPRAY_PIN IO_PORTB_01 /* 喷淋键,只短按 */
|
#define BOARD_KEY_SPRAY_PIN IO_PORTB_01 /* 喷淋键,只短按 */
|
||||||
#define BOARD_KEY_PAIR_PIN IO_PORTA_00 /* 配网键,5s / 12s */
|
#define BOARD_KEY_PAIR_PIN IO_PORTA_00 /* 配网键,5s / 12s */
|
||||||
#define BOARD_LED_RED_PIN IO_PORTA_08 /* 红灯,低亮;与加热垫相反 */
|
#define BOARD_LED_RED_PIN IO_PORTA_07 /* 红灯,低亮;与加热垫相同 */
|
||||||
#define BOARD_LED_GREEN_PIN IO_PORTA_07 /* 绿灯,低亮;与加热垫相反 */
|
#define BOARD_LED_GREEN_PIN IO_PORTA_08 /* 绿灯,低亮;与加热垫相同 */
|
||||||
#define BOARD_PUMP_PWR_PIN IO_PORTC_02 /* 泵 5V 供电,高导通 */
|
#define BOARD_PUMP_PWR_PIN IO_PORTC_02 /* 泵 5V 供电,高导通 */
|
||||||
#define BOARD_PUMP_SW_PIN IO_PORTA_06 /* 泵开关,高导通 */
|
#define BOARD_PUMP_SW_PIN IO_PORTA_06 /* 泵开关,高导通 */
|
||||||
|
|
||||||
@@ -752,6 +757,8 @@ apps/usr_app/
|
|||||||
|
|
||||||
#define BOARD_LED_FAST_ON_MS 100 /* 配网红灯快闪 */
|
#define BOARD_LED_FAST_ON_MS 100 /* 配网红灯快闪 */
|
||||||
#define BOARD_LED_FAST_OFF_MS 100
|
#define BOARD_LED_FAST_OFF_MS 100
|
||||||
|
#define BOARD_LED_RECONN_ON_MS 500 /* 已配对未连接:红灯慢闪 */
|
||||||
|
#define BOARD_LED_RECONN_OFF_MS 500
|
||||||
#define BOARD_LED_SPRAY_ON_MS 500 /* 作业绿灯闪 */
|
#define BOARD_LED_SPRAY_ON_MS 500 /* 作业绿灯闪 */
|
||||||
#define BOARD_LED_SPRAY_OFF_MS 500
|
#define BOARD_LED_SPRAY_OFF_MS 500
|
||||||
#define BOARD_LED_OTA_ON_MS 200
|
#define BOARD_LED_OTA_ON_MS 200
|
||||||
@@ -777,7 +784,7 @@ apps/usr_app/
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 只开 PC2 或只开 PA6 | 禁止。泵接口必须成对 |
|
| 只开 PC2 或只开 PA6 | 禁止。泵接口必须成对 |
|
||||||
| 上电按 VM 自动喷 | 禁止。水会喷出来 |
|
| 上电按 VM 自动喷 | 禁止。水会喷出来 |
|
||||||
| 抄加热垫灯脚(PA8 绿 / PA7 红) | 禁止。本机 PA8 红 / PA7 绿 |
|
| 灯脚写反(PA8 红 / PA7 绿) | 禁止。本机与加热垫相同:PA8 绿 / PA7 红 |
|
||||||
| 抄加热垫互斥灯(开红关绿) | 禁止。红绿独立,空闲不要关红灯 |
|
| 抄加热垫互斥灯(开红关绿) | 禁止。红绿独立,空闲不要关红灯 |
|
||||||
| 抄加热垫时间窗定时 | 禁止。本机是到点触发一轮 |
|
| 抄加热垫时间窗定时 | 禁止。本机是到点触发一轮 |
|
||||||
| 抄加热垫过零 / 继电器 | 禁止 |
|
| 抄加热垫过零 / 继电器 | 禁止 |
|
||||||
|
|||||||
@@ -400,10 +400,16 @@ c_SRC_FILES := \
|
|||||||
apps/usr_jb_proto/usr_jb_main.c \
|
apps/usr_jb_proto/usr_jb_main.c \
|
||||||
apps/usr_app/app_defer.c \
|
apps/usr_app/app_defer.c \
|
||||||
apps/usr_app/app_function.c \
|
apps/usr_app/app_function.c \
|
||||||
|
apps/usr_app/app_arbiter.c \
|
||||||
apps/usr_app/app_led.c \
|
apps/usr_app/app_led.c \
|
||||||
|
apps/usr_app/app_linkage.c \
|
||||||
|
apps/usr_app/app_nvm.c \
|
||||||
|
apps/usr_app/app_runrec.c \
|
||||||
|
apps/usr_app/app_spray.c \
|
||||||
|
apps/usr_app/app_th_scan.c \
|
||||||
|
apps/usr_app/app_timer_task.c \
|
||||||
apps/usr_app/app_pair.c \
|
apps/usr_app/app_pair.c \
|
||||||
apps/usr_app/app_pump.c \
|
apps/usr_app/app_pump.c \
|
||||||
apps/usr_app/app_spray.c \
|
|
||||||
apps/usr_app/key.c \
|
apps/usr_app/key.c \
|
||||||
apps/usr_app/key_manager.c \
|
apps/usr_app/key_manager.c \
|
||||||
apps/usr_app/led.c \
|
apps/usr_app/led.c \
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
/* usr_le_code 库钩子:由静态库导出,供 RCSP BLE 栈回调,非应用层公开 API */
|
/* usr_le_code 库钩子:由静态库导出,供 RCSP BLE 栈回调,非应用层公开 API */
|
||||||
extern void usr_le_data_recieve(u8* data,u16 len);
|
extern void usr_le_data_recieve(u8* data,u16 len);
|
||||||
extern void usr_ble_adv_init();
|
extern void usr_ble_adv_init();
|
||||||
|
/* app_th_scan 钩子:杰理扫描上报为 GAP 0xE2,packet[2] 起 adv_report_t */
|
||||||
|
extern void app_th_scan_report_event(u8 *packet, u16 size);
|
||||||
#if 1
|
#if 1
|
||||||
extern void printf_buf(u8 *buf, u32 len);
|
extern void printf_buf(u8 *buf, u32 len);
|
||||||
#define log_info printf
|
#define log_info printf
|
||||||
@@ -373,6 +375,11 @@ static void cbk_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
|
|||||||
case HCI_EVENT_ENCRYPTION_CHANGE:
|
case HCI_EVENT_ENCRYPTION_CHANGE:
|
||||||
log_info("HCI_EVENT_ENCRYPTION_CHANGE= %d\n", packet[2]);
|
log_info("HCI_EVENT_ENCRYPTION_CHANGE= %d\n", packet[2]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case GAP_EVENT_ADVERTISING_REPORT:
|
||||||
|
/* 与 le_wireless_mic_client 相同:packet[2] 起为 adv_report_t */
|
||||||
|
app_th_scan_report_event(packet, size);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,8 @@ const int config_btctler_le_roles = (LE_ADV | LE_SLAVE);
|
|||||||
const uint64_t config_btctler_le_features = LE_ENCRYPTION;
|
const uint64_t config_btctler_le_features = LE_ENCRYPTION;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
const int config_btctler_le_roles = (LE_ADV | LE_SLAVE);
|
/* 加热垫断连要扫风扇 JBao-* 广播:必须带 LE_SCAN,否则 ble_op_scan_enable2 无效果 */
|
||||||
|
const int config_btctler_le_roles = (LE_ADV | LE_SLAVE | LE_SCAN);
|
||||||
const uint64_t config_btctler_le_features = LE_ENCRYPTION| LE_DATA_PACKET_LENGTH_EXTENSION | LE_2M_PHY;
|
const uint64_t config_btctler_le_features = LE_ENCRYPTION| LE_DATA_PACKET_LENGTH_EXTENSION | LE_2M_PHY;
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_arbiter.c
|
||||||
|
* @brief CBR2 喷淋仲裁(规格书第 13 章):手动 > 计划队列 > 联动
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0, 首次发布
|
||||||
|
*
|
||||||
|
* @note 决策落点(13 章冲突表逐条对应):
|
||||||
|
* 手动开 vs 任何轮 → req_restart/req_start(10.5)+ 作废联动
|
||||||
|
* 手动停 vs 任何轮 → req_stop + 本模块 intervene(作废联动)
|
||||||
|
* 计划到点 vs 在跑 → 计划入队不插队(app_timer_task 排队)
|
||||||
|
* 计划轮 vs 联动 → 联动查 owner==PLAN / 队列让位(app_linkage)
|
||||||
|
* 联动 vs 空闲 → 联动开/停(app_linkage)
|
||||||
|
* 本模块只做两件事:手动作废转调 + 轮结束边沿分发(计划自毁 /
|
||||||
|
* 出队续跑 / 联动补判),避免收口逻辑散落在调用点。
|
||||||
|
* 全部接口只在任务上下文调用。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "system/includes.h"
|
||||||
|
#include "app_arbiter.h"
|
||||||
|
#include "app_spray.h"
|
||||||
|
#include "app_linkage.h"
|
||||||
|
#include "app_timer_task.h"
|
||||||
|
|
||||||
|
#define LOG_TAG_CONST APP
|
||||||
|
#define LOG_TAG "[ARB]"
|
||||||
|
#define LOG_INFO_ENABLE
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化仲裁器
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_init(void)
|
||||||
|
{
|
||||||
|
log_info("arbiter init\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 消费轮结束事件并收口(500ms 任务上下文)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_poll(void)
|
||||||
|
{
|
||||||
|
uint8_t owner;
|
||||||
|
uint8_t slot;
|
||||||
|
|
||||||
|
while (app_spray_pop_done(&owner, &slot)) {
|
||||||
|
app_arbiter_on_round_done(owner, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 手动介入:作废联动轮(RAM 标志,不写 VM;规格书 13 章)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_manual_intervene(void)
|
||||||
|
{
|
||||||
|
app_linkage_void();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 轮结束边沿收口(规格书 12.4 / 13 章)
|
||||||
|
* @param owner 结束轮来源
|
||||||
|
* @param slot 结束轮槽位(owner==PLAN 时有效)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_on_round_done(uint8_t owner, uint8_t slot)
|
||||||
|
{
|
||||||
|
if (owner == (uint8_t)APP_SPRAY_OWNER_PLAN) {
|
||||||
|
/* 单次计划:正常跑完或被手动停都清该条使能(12.3-4 / 10.4) */
|
||||||
|
app_timer_on_plan_done(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计划队列优先于联动:队列非空立刻续跑(不插队当前轮的承诺由
|
||||||
|
* "只在空闲 kick"保证);否则计划/手动/联动轮结束后补判联动 */
|
||||||
|
if (app_timer_has_pending()) {
|
||||||
|
app_timer_kick();
|
||||||
|
} else {
|
||||||
|
app_linkage_reeval();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_arbiter.h
|
||||||
|
* @brief CBR2 喷淋仲裁(规格书第 13 章):手动 > 计划队列 > 联动
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 与加热垫独立 owner 状态机不同:本机 owner 单一事实源在
|
||||||
|
* app_spray 轮上下文(APP_SPRAY_OWNER_*),本模块负责
|
||||||
|
* 「手动作废联动」转调与「轮结束边沿收口」分发:
|
||||||
|
* 计划轮结束 → 单次自毁 + 出队下一条
|
||||||
|
* 队列非空 → 立刻 kick(不插队当前轮,只排队续跑)
|
||||||
|
* 否则 → 联动立刻补判(12.4:计划结束后接着循环)
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __APP_ARBITER_H__
|
||||||
|
#define __APP_ARBITER_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化仲裁器
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 消费轮结束事件并收口(500ms 任务上下文,usr_timer_loop 调用)
|
||||||
|
* @note 位置须在 app_linkage_run / app_timer_run 之前:收口里补判的
|
||||||
|
* 联动 / 出队的计划当拍即生效
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_poll(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 手动介入:作废联动轮(PB1 停 / DP1=0 / DP7 抢权时调用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_manual_intervene(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 轮结束边沿收口(500ms 任务上下文,由 app_function_loop 消费事件调用)
|
||||||
|
* @param owner 结束轮来源(AppSprayOwner_t)
|
||||||
|
* @param slot 结束轮槽位(owner==PLAN 时有效)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_arbiter_on_round_done(uint8_t owner, uint8_t slot);
|
||||||
|
|
||||||
|
#endif /* __APP_ARBITER_H__ */
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* @file app_function.c
|
* @file app_function.c
|
||||||
* @brief 功能总装:喷淋键启停、任务上下文落地配对/OTA(P0 版)
|
* @brief 功能总装:喷淋键启停、出厂重置、任务上下文落地配对/OTA
|
||||||
* @author cyWu <1917507415@qq.com>
|
* @author cyWu <1917507415@qq.com>
|
||||||
* @date 2026.09.09
|
* @date 2026.09.09
|
||||||
* @version V1.0.0
|
* @version V1.1.0
|
||||||
* @history
|
* @history
|
||||||
|
* - V1.1.0, 2026.09.09, cyWu, P1 加 VM 落盘收口(app_nvm_flush)
|
||||||
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布(无 VM/联动/计划;P1+ 再扩展)
|
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布(无 VM/联动/计划;P1+ 再扩展)
|
||||||
*
|
*
|
||||||
* @note ⚠ 硬约束:写 flash(清绑定)、BLE 广播切换(配对/OTA)禁止在
|
* @note ⚠ 硬约束:写 flash(清绑定)、BLE 广播切换(配对/OTA)禁止在
|
||||||
@@ -18,6 +19,11 @@
|
|||||||
#include "app_defer.h"
|
#include "app_defer.h"
|
||||||
#include "app_led.h"
|
#include "app_led.h"
|
||||||
#include "app_spray.h"
|
#include "app_spray.h"
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "app_arbiter.h"
|
||||||
|
#include "app_runrec.h"
|
||||||
|
#include "app_linkage.h"
|
||||||
|
#include "app_timer_task.h"
|
||||||
#include "app_main.h"
|
#include "app_main.h"
|
||||||
#include "usr_le_api.h"
|
#include "usr_le_api.h"
|
||||||
#include "jb_product.h"
|
#include "jb_product.h"
|
||||||
@@ -42,6 +48,8 @@ void app_function_init(void)
|
|||||||
*/
|
*/
|
||||||
void app_function_loop(void)
|
void app_function_loop(void)
|
||||||
{
|
{
|
||||||
|
app_nvm_flush(); /* VM 防抖到点的落盘(任务上下文) */
|
||||||
|
|
||||||
if (app_defer_take(APP_DEFER_OTA)) {
|
if (app_defer_take(APP_DEFER_OTA)) {
|
||||||
app_defer_cancel(APP_DEFER_PAIR);
|
app_defer_cancel(APP_DEFER_PAIR);
|
||||||
usr_app_ota_init();
|
usr_app_ota_init();
|
||||||
@@ -55,6 +63,23 @@ void app_function_loop(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DP24 出厂重置(规格书第 15 章):清业务 VM + RAM 复位
|
||||||
|
* @note 保留 BLE 配对、不断开、不软复位。泵停走请求(下一 tick 生效);
|
||||||
|
* userInit 把 gDevData 全量回默认(DP7=10/3/3、计划全 0、plan_en=0)。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_function_factory_reset(void)
|
||||||
|
{
|
||||||
|
app_spray_req_stop(); /* 泵停,DP1=0(不自动喷) */
|
||||||
|
app_nvm_reset(); /* 清 4 个业务 item(保留配对三元组) */
|
||||||
|
userInit(); /* gDevData 全量回默认 */
|
||||||
|
app_runrec_reset(); /* 31 天记录清零 */
|
||||||
|
app_linkage_init(); /* 作废标志/湿度有效位清掉 */
|
||||||
|
app_timer_init(); /* 计划队列清掉 */
|
||||||
|
log_info("factory reset done, business vm cleared\n");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 同步 DP1 = 喷淋作业态(整轮含间隔等待为 1)
|
* @brief 同步 DP1 = 喷淋作业态(整轮含间隔等待为 1)
|
||||||
* @return 无
|
* @return 无
|
||||||
@@ -75,11 +100,12 @@ void usr_on_key_short(void)
|
|||||||
if (app_led_is_ota()) {
|
if (app_led_is_ota()) {
|
||||||
return; /* OTA 进行中忽略(规格书 5.3) */
|
return; /* OTA 进行中忽略(规格书 5.3) */
|
||||||
}
|
}
|
||||||
|
app_arbiter_manual_intervene(); /* 开/停都是手动抢权,作废联动 */
|
||||||
if (app_spray_is_running()) {
|
if (app_spray_is_running()) {
|
||||||
app_spray_stop();
|
app_spray_req_stop();
|
||||||
log_info("spray key short -> stop\n");
|
log_info("spray key short -> stop\n");
|
||||||
} else {
|
} else {
|
||||||
app_spray_start();
|
app_spray_req_start();
|
||||||
log_info("spray key short -> start\n");
|
log_info("spray key short -> start\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,4 +58,11 @@ void usr_on_key_pair_start(void);
|
|||||||
*/
|
*/
|
||||||
void usr_on_key_ota_start(void);
|
void usr_on_key_ota_start(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DP24 出厂重置(规格书第 15 章):清业务 VM + RAM 复位
|
||||||
|
* @note 保留 BLE 配对、不断开、不软复位。jbEventProcess(任务上下文)调用。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_function_factory_reset(void);
|
||||||
|
|
||||||
#endif /* __APP_FUNCTION_H__ */
|
#endif /* __APP_FUNCTION_H__ */
|
||||||
|
|||||||
+12
-6
@@ -1,10 +1,11 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* @file app_led.c
|
* @file app_led.c
|
||||||
* @brief 灯效:绿=作业态、红=配对态;OTA / 配对失败临时占双灯(规格书 6 章)
|
* @brief 灯效:绿=作业态、红=连接/配网态;OTA / 配对失败临时占双灯(规格书 6 章)
|
||||||
* @author cyWu <1917507415@qq.com>
|
* @author cyWu <1917507415@qq.com>
|
||||||
* @date 2026.09.09
|
* @date 2026.09.09
|
||||||
* @version V1.0.0
|
* @version V1.1.0
|
||||||
* @history
|
* @history
|
||||||
|
* - V1.1.0, 2026.09.09, cyWu, 已连接常亮 / 已配对未连接慢闪,避免上电误判已联网
|
||||||
* - V1.0.0, 2026.09.09, cyWu, P0 独立通道版(不同于加热垫互斥灯)
|
* - V1.0.0, 2026.09.09, cyWu, P0 独立通道版(不同于加热垫互斥灯)
|
||||||
*
|
*
|
||||||
* @note 红绿独立:喷淋/空闲只改绿灯,不要去关红灯;配网闪红灯时不要关绿灯。
|
* @note 红绿独立:喷淋/空闲只改绿灯,不要去关红灯;配网闪红灯时不要关绿灯。
|
||||||
@@ -84,7 +85,8 @@ void app_led_hint_pair(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s_led.pair_hint = 1;
|
s_led.pair_hint = 1;
|
||||||
LED_EventDelete(LED_EVENT_RED_PAIRED); /* 快闪盖过"已配对常亮"(同槽换事件) */
|
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
||||||
|
LED_EventDelete(LED_EVENT_RED_RECONN);
|
||||||
LED_EventAdd(LED_EVENT_RED_PAIRING);
|
LED_EventAdd(LED_EVENT_RED_PAIRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +152,12 @@ void app_led_run(uint16_t dt)
|
|||||||
s_led.pair_hint = 0;
|
s_led.pair_hint = 0;
|
||||||
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
||||||
LED_EventDelete(LED_EVENT_PAIR_FAIL);
|
LED_EventDelete(LED_EVENT_PAIR_FAIL);
|
||||||
log_info("led pair ok -> red solid\n");
|
log_info("led pair ok\n");
|
||||||
} else if (pair_evt == APP_PAIR_EVT_FAIL) {
|
} else if (pair_evt == APP_PAIR_EVT_FAIL) {
|
||||||
s_led.pair_hint = 0;
|
s_led.pair_hint = 0;
|
||||||
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
||||||
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
||||||
|
LED_EventDelete(LED_EVENT_RED_RECONN);
|
||||||
led_restart(LED_EVENT_PAIR_FAIL); /* 交替 3s,播完自删回常态 */
|
led_restart(LED_EVENT_PAIR_FAIL); /* 交替 3s,播完自删回常态 */
|
||||||
log_info("led pair fail 3s\n");
|
log_info("led pair fail 3s\n");
|
||||||
}
|
}
|
||||||
@@ -164,14 +167,17 @@ void app_led_run(uint16_t dt)
|
|||||||
|
|
||||||
/* 常态双通道(高事件占双灯期间不 add,播完自删后下拍自动恢复) */
|
/* 常态双通道(高事件占双灯期间不 add,播完自删后下拍自动恢复) */
|
||||||
if (!high_event) {
|
if (!high_event) {
|
||||||
/* 红灯通道:快闪(提示/配对中) > 常亮(已配对) > 灭(未配对) */
|
/* 红灯:配网快闪 > 已连接常亮 > 已配对未连接慢闪 > 未配对灭 */
|
||||||
if (s_led.pair_hint || app_pair_is_active()) {
|
if (s_led.pair_hint || app_pair_is_active()) {
|
||||||
LED_EventAdd(LED_EVENT_RED_PAIRING);
|
LED_EventAdd(LED_EVENT_RED_PAIRING);
|
||||||
} else if (usr_get_pair_flag()) {
|
} else if (usr_ble_is_connected()) {
|
||||||
LED_EventAdd(LED_EVENT_RED_PAIRED);
|
LED_EventAdd(LED_EVENT_RED_PAIRED);
|
||||||
|
} else if (usr_get_pair_flag()) {
|
||||||
|
LED_EventAdd(LED_EVENT_RED_RECONN);
|
||||||
} else {
|
} else {
|
||||||
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
LED_EventDelete(LED_EVENT_RED_PAIRED);
|
||||||
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
LED_EventDelete(LED_EVENT_RED_PAIRING);
|
||||||
|
LED_EventDelete(LED_EVENT_RED_RECONN);
|
||||||
}
|
}
|
||||||
/* 绿灯通道:作业态(含间隔)闪,否则常亮 */
|
/* 绿灯通道:作业态(含间隔)闪,否则常亮 */
|
||||||
if (app_spray_is_running()) {
|
if (app_spray_is_running()) {
|
||||||
|
|||||||
@@ -0,0 +1,309 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_linkage.c
|
||||||
|
* @brief CBR2 联动引擎(规格书第 11 章 + 第 13 章仲裁):只看湿度回差
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0, 首次发布
|
||||||
|
*
|
||||||
|
* @note 开停只看湿度(11.3,uint8 %;与加热垫温度回差方向相反——过干开):
|
||||||
|
* humi < Hmin → 联动循环喷:空闲则用 DP7 参数开一轮,一轮正常
|
||||||
|
* 结束后(仲裁收口 reeval)再判再开,无额外间隔
|
||||||
|
* humi > Hmax → 联动占着则立刻停轮(req_stop,与谁开的无关)
|
||||||
|
* [Hmin,Hmax](含等于)→ 保持上一态
|
||||||
|
* 湿度源(8 章):BLE 已连接 = 服务器 DP6;断连已配对 = 扫描
|
||||||
|
* JBao-{tank};丢包保持上一态。无任何有效湿度 → 联动不改泵。
|
||||||
|
*
|
||||||
|
* 手动作废(13 章,RAM 不落 VM):PB1 停 / DP1=0 / DP7 抢权触发;
|
||||||
|
* 须先走到「关」条件(H>Hmax)再重新出现 H<Hmin 才允许再开;
|
||||||
|
* 重写 DP9 清手动作废并立刻重判(11.5)。
|
||||||
|
*
|
||||||
|
* ⚠ 判定只在 usr_timer_loop(500ms 任务上下文)与 DP6/9 写路径
|
||||||
|
* 执行,不提供 20ms 中断接口。req_* 只置字节,与 ISR 无竞争。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "system/includes.h"
|
||||||
|
#include "app_linkage.h"
|
||||||
|
#include "app_spray.h"
|
||||||
|
#include "app_timer_task.h"
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "jb_product.h"
|
||||||
|
#include "board_pin.h"
|
||||||
|
|
||||||
|
#define LOG_TAG_CONST APP
|
||||||
|
#define LOG_TAG "[APP_LKG]"
|
||||||
|
#define LOG_INFO_ENABLE
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/** 20B 配置解析结果(规格书 11.2;温度/版本/预留只存不用) */
|
||||||
|
typedef struct {
|
||||||
|
uint16_t tank_id; /**< Byte0-1 大端,0=无效 */
|
||||||
|
uint8_t hmin; /**< Byte4 % */
|
||||||
|
uint8_t hmax; /**< Byte5 % */
|
||||||
|
} LinkageCfg_t;
|
||||||
|
|
||||||
|
/** 联动运行时(RAM;湿度与作废标志均不落 VM) */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t humi; /**< 联动用湿度 % */
|
||||||
|
uint8_t humi_valid; /**< 1=收到过任意来源的有效湿度 */
|
||||||
|
uint8_t voided; /**< 1=被手动作废,须先到「关」条件再武装 */
|
||||||
|
} LinkageCtx_t;
|
||||||
|
|
||||||
|
static LinkageCtx_t s_lk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解析 20B 配置并判定有效性(11.1:缸体≠0 且 Hmin≤Hmax)
|
||||||
|
* @param c 20B 配置
|
||||||
|
* @param out 解析结果
|
||||||
|
* @return 1=有效
|
||||||
|
*/
|
||||||
|
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->hmin = c[4];
|
||||||
|
out->hmax = c[5];
|
||||||
|
if (out->tank_id == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (out->hmin > out->hmax) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 联动让出泵:仅当联动正占着作业时才停(手动/计划占着不动)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void lk_stop(void)
|
||||||
|
{
|
||||||
|
if (app_spray_is_running() && (app_spray_owner() == APP_SPRAY_OWNER_LINKAGE)) {
|
||||||
|
app_spray_req_stop();
|
||||||
|
log_info("linkage stop\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化联动引擎
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_init(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_LINKAGE_ENABLE
|
||||||
|
log_info("linkage disabled\n");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
memset(&s_lk, 0, sizeof(s_lk));
|
||||||
|
log_info("linkage init\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 核心判定(幂等,11.3 + 13 章)
|
||||||
|
* @note 步骤:无效配置 → 联动占着则停;无有效湿度 → 不动;
|
||||||
|
* 走到「关」条件清作废(再武装);作废期间不动作;
|
||||||
|
* 计划占用中或队列待跑时不插嘴;否则按回差循环开/停。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void lk_reeval(void)
|
||||||
|
{
|
||||||
|
LinkageCfg_t cfg;
|
||||||
|
uint8_t valid = lk_parse(gDevData.linkage_config, &cfg);
|
||||||
|
uint8_t dry; /* H < Hmin:过干,开条件 */
|
||||||
|
uint8_t wet; /* H > Hmax:过湿,关条件 */
|
||||||
|
|
||||||
|
/* 11.1:无效配置(缸体 0 / min>max)→ 不自动开;联动占着则停掉 */
|
||||||
|
if (!valid) {
|
||||||
|
lk_stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 11.2:尚无任何有效湿度 → 联动不改泵(保持上电关/上一态) */
|
||||||
|
if (!s_lk.humi_valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dry = (s_lk.humi < cfg.hmin) ? 1u : 0u;
|
||||||
|
wet = (s_lk.humi > cfg.hmax) ? 1u : 0u;
|
||||||
|
|
||||||
|
/* 13 章再武装:先走到「关」条件(H>Hmax)→ 清作废;
|
||||||
|
* 之后重新出现 H<Hmin 才允许再开。等于 Hmin/Hmax 都不算越界 */
|
||||||
|
if (wet) {
|
||||||
|
if (s_lk.voided) {
|
||||||
|
log_info("linkage rearmed (H>Hmax reached)\n");
|
||||||
|
}
|
||||||
|
s_lk.voided = 0;
|
||||||
|
}
|
||||||
|
if (s_lk.voided) {
|
||||||
|
return; /* 作废期间保持上一态,不动作 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 13 章仲裁:计划轮占着(owner==PLAN)或队列待跑时联动不开不关,
|
||||||
|
* 计划收口后由 app_arbiter 调 reeval 补判 */
|
||||||
|
if (app_spray_owner() == APP_SPRAY_OWNER_PLAN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (app_timer_has_pending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dry) {
|
||||||
|
/* 11.3 开:H<Hmin 过干 → 空闲则用 DP7 参数开一轮(循环由收口续) */
|
||||||
|
if (app_spray_owner() == APP_SPRAY_OWNER_NONE) {
|
||||||
|
app_spray_req_start_link();
|
||||||
|
log_info("linkage open (dry, H=%u<%u)\n", s_lk.humi, cfg.hmin);
|
||||||
|
}
|
||||||
|
} else if (wet) {
|
||||||
|
/* 11.3 过湿停:走到 H>Hmax 一律停联动的轮;手动/计划占着不插嘴 */
|
||||||
|
if (app_spray_is_running() && (app_spray_owner() == APP_SPRAY_OWNER_LINKAGE)) {
|
||||||
|
app_spray_req_stop();
|
||||||
|
log_info("linkage stop (wet, H=%u>%u)\n", s_lk.humi, cfg.hmax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 否则 [Hmin,Hmax](含等于):保持上一态(回差) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立刻按第 11.3 节重判一次(DP9 重写 / 计划轮收口后调用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_reeval(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_LINKAGE_ENABLE
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
lk_reeval();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 推进联动引擎(500ms,任务上下文)
|
||||||
|
* @param dt 距上次调用的毫秒数
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_run(uint16_t dt)
|
||||||
|
{
|
||||||
|
#if !BOARD_LINKAGE_ENABLE
|
||||||
|
(void)dt;
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
(void)dt; /* 本模块不计时:每拍完整重判 */
|
||||||
|
lk_reeval();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DP8 联动删除写 1(脉冲):清 RAM+VM 20B;联动占着则停
|
||||||
|
* @note gDevData.linkage_delete 由调用方清回 0(脉冲)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_delete(void)
|
||||||
|
{
|
||||||
|
memset(gDevData.linkage_config, 0, 20);
|
||||||
|
app_nvm_mark_dirty();
|
||||||
|
#if BOARD_LINKAGE_ENABLE
|
||||||
|
s_lk.voided = 0;
|
||||||
|
lk_stop(); /* 11.4:若联动占着作业 → 停 */
|
||||||
|
#endif
|
||||||
|
log_info("linkage config deleted\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief APP 重写 DP9(memcpy 已完成):存 VM + 清手动作废 + 立刻重判
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_set_config(void)
|
||||||
|
{
|
||||||
|
app_nvm_mark_dirty();
|
||||||
|
#if BOARD_LINKAGE_ENABLE
|
||||||
|
s_lk.voided = 0; /* 11.5:清手动作废标志 */
|
||||||
|
lk_reeval(); /* 立刻按新阈值重判(有有效湿度时) */
|
||||||
|
#endif
|
||||||
|
log_info("linkage_config stored\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 手动作废联动轮(PB1 开/停、DP1、DP7 抢权;RAM 不写 VM)
|
||||||
|
* @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 配置是否有效(11.1;扫描门控也用)
|
||||||
|
* @return 1=有效
|
||||||
|
*/
|
||||||
|
uint8_t app_linkage_is_valid(void)
|
||||||
|
{
|
||||||
|
LinkageCfg_t cfg;
|
||||||
|
|
||||||
|
return lk_parse(gDevData.linkage_config, &cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缸体编号(Byte0-1 大端;引擎关掉也可读,供扫描匹配用)
|
||||||
|
* @return 缸体编号,0=未配
|
||||||
|
*/
|
||||||
|
uint16_t app_linkage_get_tank_id(void)
|
||||||
|
{
|
||||||
|
return (uint16_t)(((uint16_t)gDevData.linkage_config[0] << 8) |
|
||||||
|
gDevData.linkage_config[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 联动配置版本(Byte6;扫描匹配用)
|
||||||
|
* @return 配置版本
|
||||||
|
*/
|
||||||
|
uint8_t app_linkage_get_cfg_ver(void)
|
||||||
|
{
|
||||||
|
return gDevData.linkage_config[6];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief BLE 已连接:服务器 DP6 湿度(uint8 %)→ 联动湿度
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_set_connected_humi(uint8_t humi)
|
||||||
|
{
|
||||||
|
if (s_lk.humi_valid && (s_lk.humi == humi)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_lk.humi = humi;
|
||||||
|
s_lk.humi_valid = 1;
|
||||||
|
log_info("linkage humi from dp6: %u%%\n", (unsigned)humi);
|
||||||
|
#if BOARD_LINKAGE_ENABLE
|
||||||
|
lk_reeval(); /* 湿度边沿立刻判,不等下一个 500ms */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 断连扫描:匹配到 JBao-{tank} 广播 → 联动湿度
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_set_scan_humi(uint8_t humi)
|
||||||
|
{
|
||||||
|
if (s_lk.humi_valid && (s_lk.humi == humi)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_lk.humi = humi;
|
||||||
|
s_lk.humi_valid = 1;
|
||||||
|
log_info("linkage humi from scan: %u%%\n", (unsigned)humi);
|
||||||
|
#if BOARD_LINKAGE_ENABLE
|
||||||
|
lk_reeval();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否已有任何有效湿度
|
||||||
|
* @return 1=有
|
||||||
|
*/
|
||||||
|
uint8_t app_linkage_has_humi(void)
|
||||||
|
{
|
||||||
|
return s_lk.humi_valid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_linkage.h
|
||||||
|
* @brief CBR2 联动引擎(规格书第 11 章:1 条 20B DP9 配置,湿度回差开/关)
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 开停只看湿度(11.3,uint8 %):
|
||||||
|
* H < Hmin → 循环喷(一轮正常结束后立刻再开,DP7 参数)
|
||||||
|
* H > Hmax → 立刻停泵、结束作业
|
||||||
|
* [Hmin,Hmax] → 保持上一态(含等于不算越界)
|
||||||
|
* 湿度源(8 章):已连接=DP6(set_connected_humi);断连=扫描
|
||||||
|
* JBao-{tank}(set_scan_humi)。丢广播/无效包 → 当没收到,保持
|
||||||
|
* 上一态(禁止丢包停喷)。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __APP_LINKAGE_H__
|
||||||
|
#define __APP_LINKAGE_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化联动(配置有效性由 is_valid 判;作废标志清 0)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 500ms 任务上下文跑联动:有效配置 + 未被手动作废 + 有有效湿度才判
|
||||||
|
* @param dt 距上次调用的毫秒数(=500)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_run(uint16_t dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立刻按当前湿度重判一次(DP9 重写 / 计划轮收口 / 联动轮结束后调用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_reeval(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DP8 联动删除(脉冲):清 RAM+VM 20B;联动占着则停;delete 清 0
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_delete(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief APP 重写 DP9(memcpy 已完成):存 VM、清手动作废、立刻重判
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_set_config(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 手动作废联动轮(PB1 开/停、DP1、DP7 抢权触发,RAM 不写 VM)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_void(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 配置是否有效(缸体号≠0 且 Hmin≤Hmax)
|
||||||
|
* @return 1=有效
|
||||||
|
*/
|
||||||
|
uint8_t app_linkage_is_valid(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缸体编号(DP9 Byte0-1,uint16 大端)
|
||||||
|
* @return tank_id
|
||||||
|
*/
|
||||||
|
uint16_t app_linkage_get_tank_id(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 配置版本(DP9 Byte6,扫描匹配用)
|
||||||
|
* @return 版本号
|
||||||
|
*/
|
||||||
|
uint8_t app_linkage_get_cfg_ver(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief BLE 已连接:服务器下发 DP6 湿度(uint8 %)→ 联动湿度
|
||||||
|
* @param humi 服务器下发湿度 %
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_set_connected_humi(uint8_t humi);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 断连扫描:匹配到 JBao-{tank} 广播 → 联动湿度(uint8 %)
|
||||||
|
* @param humi 风扇广播湿度 %
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_linkage_set_scan_humi(uint8_t humi);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否已有任何有效湿度(连接 DP6 或扫描匹配到过)
|
||||||
|
* @return 1=有;0=尚无湿度(联动不动作)
|
||||||
|
*/
|
||||||
|
uint8_t app_linkage_has_humi(void);
|
||||||
|
|
||||||
|
#endif /* __APP_LINKAGE_H__ */
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_nvm.c
|
||||||
|
* @brief CBR2 VM 持久化:spray_config 8B / linkage 20B / plan_en / 7 条计划(各15B) /
|
||||||
|
* fault_en / alarm_en / 运行记录(规格书第 14 章)
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0, 首次发布
|
||||||
|
*
|
||||||
|
* @note VM item 分配在 syscfg 用户自定义区 [1~49],本产品从 28 开始
|
||||||
|
* (每台设备 flash 独立,与其它产品 id 无冲突):
|
||||||
|
* CFG_USER_CBR2_CFG =28 状态(magic+ver+spray_config+使能,16B)
|
||||||
|
* CFG_USER_CBR2_PLAN =29 计划 spray_plan1 + time_config2~7(105B)
|
||||||
|
* CFG_USER_CBR2_RUNREC =30 31 天运行记录
|
||||||
|
* CFG_USER_CBR2_LINKAGE =31 联动配置 20B
|
||||||
|
* 拆多个 item:改喷淋参数只擦写 16B 小 item,不重写计划区,减少
|
||||||
|
* flash 磨损;首次上电(magic/ver 不符)保持 userInit 的默认值。
|
||||||
|
*
|
||||||
|
* 打脏模型:本产品 P1 的全部写源都在 jbEventProcess(任务上下文),
|
||||||
|
* 直接 app_nvm_mark_dirty() 即可,tick 不做变化检测、只跑防抖计时;
|
||||||
|
* 真正写入由 app_nvm_flush() 在 usr_timer_loop(任务上下文)执行。
|
||||||
|
*
|
||||||
|
* ⚠ 上下文硬约束:usr_timer_add 回调在硬件定时器中断上下文(栈极
|
||||||
|
* 小),绝对禁止调 syscfg_write(VM 擦写 flash 栈用量大 → 栈溢出
|
||||||
|
* 软复位)。nvm_write_item 的比较缓冲必须 static(放栈上会撑爆中断
|
||||||
|
* 栈)。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "app_defer.h"
|
||||||
|
#include "app_runrec.h"
|
||||||
|
#include "jb_product.h"
|
||||||
|
#include "board_pin.h"
|
||||||
|
#include "system/includes.h"
|
||||||
|
#include "syscfg_id.h"
|
||||||
|
|
||||||
|
#define LOG_TAG_CONST APP
|
||||||
|
#define LOG_TAG "[APP_NVM]"
|
||||||
|
#define LOG_ERROR_ENABLE
|
||||||
|
#define LOG_INFO_ENABLE
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/* ── VM item id(用户区 1~49 内申请)── */
|
||||||
|
#define CFG_USER_CBR2_CFG 28
|
||||||
|
#define CFG_USER_CBR2_PLAN 29
|
||||||
|
#define CFG_USER_CBR2_RUNREC 30
|
||||||
|
#define CFG_USER_CBR2_LINKAGE 31
|
||||||
|
|
||||||
|
#define CBR2_VM_MAGIC 0xCB02
|
||||||
|
#define CBR2_VM_VER 1
|
||||||
|
#define CBR2_VM_DEBOUNCE_MS 500 /* 配置变化防抖(规格书 14 章) */
|
||||||
|
|
||||||
|
#define CBR2_PLAN_SLOTS 7
|
||||||
|
#define CBR2_PLAN_LEN 15
|
||||||
|
|
||||||
|
/** 状态 item(16 字节) */
|
||||||
|
typedef struct {
|
||||||
|
uint16_t magic; /**< CBR2_VM_MAGIC */
|
||||||
|
uint8_t ver; /**< CBR2_VM_VER */
|
||||||
|
uint8_t spray_config[8];/**< DP7(大端:时长 u16 / 次数 u8 / 间隔 u16) */
|
||||||
|
uint8_t plan_en; /**< DP10 计划总开关 */
|
||||||
|
uint8_t fault_en; /**< DP20(原样存) */
|
||||||
|
uint8_t alarm_en; /**< DP22(原样存) */
|
||||||
|
uint8_t rsv[2]; /**< 对齐到 16B */
|
||||||
|
} __packed Cbr2CfgVm_t;
|
||||||
|
|
||||||
|
/** 计划 item(105 字节:plan1 + time_config2~7) */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t plan[CBR2_PLAN_SLOTS][CBR2_PLAN_LEN];
|
||||||
|
} __packed Cbr2PlanVm_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 Cbr2RunrecVm_t;
|
||||||
|
|
||||||
|
#if BOARD_NVM_ENABLE
|
||||||
|
/** NVM 运行时:防抖计时(打脏由写源主动 mark) */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t dirty; /**< 1=防抖计时中 */
|
||||||
|
uint16_t deb_ms; /**< 防抖累计毫秒 */
|
||||||
|
} AppNvmCtx_t;
|
||||||
|
|
||||||
|
static AppNvmCtx_t s_nvm;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 gDevData 收集 7 条计划到 VM 结构(plan1 + time_config2~7)
|
||||||
|
* @param out 输出结构
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void nvm_collect_plan(Cbr2PlanVm_t *out)
|
||||||
|
{
|
||||||
|
const uint8_t *src[CBR2_PLAN_SLOTS] = {
|
||||||
|
gDevData.spray_plan1, gDevData.time_config2, gDevData.time_config3,
|
||||||
|
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||||||
|
gDevData.time_config7
|
||||||
|
};
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < CBR2_PLAN_SLOTS; i++) {
|
||||||
|
memcpy(out->plan[i], src[i], CBR2_PLAN_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写单个 item:先读回比较,内容相同跳过,避免无谓擦写 flash
|
||||||
|
* @param item_id VM item id
|
||||||
|
* @param buf 新数据
|
||||||
|
* @param len 数据长度(≤128,比较缓冲大小)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void nvm_write_item(u16 item_id, void *buf, u16 len)
|
||||||
|
{
|
||||||
|
/* static:比较缓冲不放栈上;本函数只在任务上下文调用,无重入 */
|
||||||
|
static uint8_t old[128];
|
||||||
|
|
||||||
|
if (len > sizeof(old)) {
|
||||||
|
log_error("nvm item %d len %d too big\n", item_id, len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (syscfg_read(item_id, old, len) == len) {
|
||||||
|
if (memcmp(old, buf, len) == 0) {
|
||||||
|
return; /* 没变化,不擦写 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (syscfg_write(item_id, buf, len) > 0) {
|
||||||
|
log_info("nvm item %d saved %dB\n", item_id, len);
|
||||||
|
} else {
|
||||||
|
log_error("nvm item %d write fail\n", item_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写状态 item(spray_config / plan_en / fault_en / alarm_en)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void nvm_write_cfg(void)
|
||||||
|
{
|
||||||
|
Cbr2CfgVm_t st;
|
||||||
|
|
||||||
|
memset(&st, 0, sizeof(st));
|
||||||
|
st.magic = CBR2_VM_MAGIC;
|
||||||
|
st.ver = CBR2_VM_VER;
|
||||||
|
memcpy(st.spray_config, gDevData.spray_config, sizeof(st.spray_config));
|
||||||
|
st.plan_en = gDevData.spray_plan_en ? 1 : 0;
|
||||||
|
st.fault_en = gDevData.fault_en;
|
||||||
|
st.alarm_en = gDevData.alarm_en;
|
||||||
|
nvm_write_item(CFG_USER_CBR2_CFG, &st, sizeof(st));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写计划 item(7 条 × 15B)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void nvm_write_plan(void)
|
||||||
|
{
|
||||||
|
Cbr2PlanVm_t pf;
|
||||||
|
|
||||||
|
nvm_collect_plan(&pf);
|
||||||
|
nvm_write_item(CFG_USER_CBR2_PLAN, &pf, sizeof(pf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写运行记录 item(31 格秒数 + slot0 日期)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void nvm_write_runrec(void)
|
||||||
|
{
|
||||||
|
Cbr2RunrecVm_t rf;
|
||||||
|
|
||||||
|
app_runrec_pack(rf.run_record);
|
||||||
|
rf.slot0_date = app_runrec_get_slot0_date();
|
||||||
|
nvm_write_item(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写联动配置 20B
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void nvm_write_linkage(void)
|
||||||
|
{
|
||||||
|
nvm_write_item(CFG_USER_CBR2_LINKAGE, gDevData.linkage_config, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化:校验 VM 有效性并打日志(数据加载在 app_nvm_load)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_init(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
log_info("nvm disabled\n");
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
Cbr2CfgVm_t st;
|
||||||
|
int ret = syscfg_read(CFG_USER_CBR2_CFG, &st, sizeof(st));
|
||||||
|
|
||||||
|
if ((ret == (int)sizeof(st)) && (st.magic == CBR2_VM_MAGIC) && (st.ver == CBR2_VM_VER)) {
|
||||||
|
log_info("nvm init, vm valid v%d\n", st.ver);
|
||||||
|
} else {
|
||||||
|
log_info("nvm init, vm invalid (ret=%d), use defaults\n", ret);
|
||||||
|
}
|
||||||
|
memset(&s_nvm, 0, sizeof(s_nvm));
|
||||||
|
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即保存全部业务字段
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_save(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
nvm_write_cfg();
|
||||||
|
nvm_write_plan();
|
||||||
|
nvm_write_linkage();
|
||||||
|
s_nvm.dirty = 0;
|
||||||
|
s_nvm.deb_ms = 0;
|
||||||
|
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 VM 加载并回填 gDevData(开机由 usr_jb_init 调用)
|
||||||
|
* @note 只回填配置:DP1/泵保持关,作业不恢复(上电绝不自动喷)。
|
||||||
|
* VM 无效(首次上电)保持 gDevData 现值 = userInit 默认。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_load(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
Cbr2CfgVm_t st;
|
||||||
|
Cbr2PlanVm_t pf;
|
||||||
|
Cbr2RunrecVm_t rf;
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
if ((syscfg_read(CFG_USER_CBR2_CFG, &st, sizeof(st)) != (int)sizeof(st))
|
||||||
|
|| (st.magic != CBR2_VM_MAGIC) || (st.ver != CBR2_VM_VER)) {
|
||||||
|
log_info("nvm load: no valid vm, keep defaults (dp7=10/3/3)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(gDevData.spray_config, st.spray_config, sizeof(st.spray_config));
|
||||||
|
gDevData.spray_plan_en = st.plan_en ? 1 : 0;
|
||||||
|
gDevData.fault_en = st.fault_en;
|
||||||
|
gDevData.alarm_en = st.alarm_en;
|
||||||
|
|
||||||
|
if (syscfg_read(CFG_USER_CBR2_PLAN, &pf, sizeof(pf)) == (int)sizeof(pf)) {
|
||||||
|
uint8_t *dst[CBR2_PLAN_SLOTS] = {
|
||||||
|
gDevData.spray_plan1, gDevData.time_config2, gDevData.time_config3,
|
||||||
|
gDevData.time_config4, gDevData.time_config5, gDevData.time_config6,
|
||||||
|
gDevData.time_config7
|
||||||
|
};
|
||||||
|
for (i = 0; i < CBR2_PLAN_SLOTS; i++) {
|
||||||
|
memcpy(dst[i], pf.plan[i], CBR2_PLAN_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 运行记录回填 RAM(14.1:掉电再上电先把 31 格灌回来) */
|
||||||
|
if (syscfg_read(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf)) == (int)sizeof(rf)) {
|
||||||
|
app_runrec_unpack(rf.run_record);
|
||||||
|
app_runrec_set_slot0_date(rf.slot0_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syscfg_read(CFG_USER_CBR2_LINKAGE, gDevData.linkage_config, 20) != 20) {
|
||||||
|
memset(gDevData.linkage_config, 0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("nvm load v%d: spray cfg %us x%u +%us, plan_en=%d\n",
|
||||||
|
st.ver,
|
||||||
|
(unsigned)(((uint16_t)st.spray_config[0] << 8) | st.spray_config[1]),
|
||||||
|
(unsigned)st.spray_config[2],
|
||||||
|
(unsigned)(((uint16_t)st.spray_config[3] << 8) | st.spray_config[4]),
|
||||||
|
st.plan_en);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 业务节拍:防抖计时,**不执行写入**
|
||||||
|
* @param dt 距上次调用的毫秒数
|
||||||
|
* @note ⚠ 本函数跑在 usr_timer_add 的 20ms 回调里 = 硬件定时器中断上下文
|
||||||
|
* (栈极小)。只允许轻量计时;真正的 syscfg_write 由 app_nvm_flush()
|
||||||
|
* 在 usr_timer_loop(任务上下文)执行。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_tick(uint16_t dt)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
(void)dt;
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (!s_nvm.dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_nvm.deb_ms = (uint16_t)(s_nvm.deb_ms + dt);
|
||||||
|
if (s_nvm.deb_ms >= CBR2_VM_DEBOUNCE_MS) {
|
||||||
|
s_nvm.deb_ms = 0;
|
||||||
|
s_nvm.dirty = 0;
|
||||||
|
app_defer_request(APP_DEFER_NVM_SAVE); /* 只置标志:真正落盘在 app_nvm_flush */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 任务上下文落盘:防抖满后真正写 VM(由 usr_timer_loop 500ms 调用)
|
||||||
|
* @note 必须且只能在非中断上下文调用(VM 擦写 flash 栈用量大)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_flush(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (!app_defer_take(APP_DEFER_NVM_SAVE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
app_nvm_save();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记业务字段有变化,防抖计时归零(重新计满防抖窗口才写)
|
||||||
|
* @note 任务上下文调用(APP 下行改配置:DP7/计划/plan_en/DP20/22)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_mark_dirty(void)
|
||||||
|
{
|
||||||
|
#if BOARD_NVM_ENABLE
|
||||||
|
s_nvm.dirty = 1;
|
||||||
|
s_nvm.deb_ms = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即保存运行记录 item(满 600s / 跨天 / 首次对时,任务上下文)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_save_runrec(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
nvm_write_runrec();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 出厂重置:清全部业务 VM(保留 BLE 配对,规格书第 15 章)
|
||||||
|
* @note 3 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
|
||||||
|
* BLE 配对三元组在 CFG_USER_PAIR_INFO(item 2),不受影响
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_reset(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_NVM_ENABLE
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
Cbr2CfgVm_t st;
|
||||||
|
Cbr2PlanVm_t pf;
|
||||||
|
Cbr2RunrecVm_t rf;
|
||||||
|
uint8_t linkage[20];
|
||||||
|
|
||||||
|
memset(&st, 0, sizeof(st));
|
||||||
|
memset(&pf, 0, sizeof(pf));
|
||||||
|
memset(&rf, 0, sizeof(rf));
|
||||||
|
memset(linkage, 0, sizeof(linkage));
|
||||||
|
syscfg_write(CFG_USER_CBR2_CFG, &st, sizeof(st));
|
||||||
|
syscfg_write(CFG_USER_CBR2_PLAN, &pf, sizeof(pf));
|
||||||
|
syscfg_write(CFG_USER_CBR2_RUNREC, &rf, sizeof(rf));
|
||||||
|
syscfg_write(CFG_USER_CBR2_LINKAGE, linkage, sizeof(linkage));
|
||||||
|
s_nvm.dirty = 0;
|
||||||
|
s_nvm.deb_ms = 0;
|
||||||
|
app_defer_cancel(APP_DEFER_NVM_SAVE);
|
||||||
|
log_info("nvm factory reset, business vm cleared (pairing kept)\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_nvm.h
|
||||||
|
* @brief CBR2 VM 持久化(P1 起):spray_config(8B) / linkage_config(20B) /
|
||||||
|
* spray_plan_en / spray_plan1 + time_config2~7(各 15B) /
|
||||||
|
* fault_en / alarm_en / 运行记录(规格书第 14 章)
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 存:spray_config(含首次默认 10/3/3)、linkage_config 20B、
|
||||||
|
* spray_plan_en、7 条计划、fault_en、alarm_en、run_record。
|
||||||
|
* 不存:DP0/2/3/4/5、DP1 作业开关(上电绝不自动喷)、DP6 当前湿度、
|
||||||
|
* 联动作废标志、linkage_delete(脉冲)、BLE 三元组(出厂重置不清)。
|
||||||
|
*
|
||||||
|
* ⚠ 写 flash 只能在任务上下文(app_nvm_flush / app_nvm_save),
|
||||||
|
* 20ms 中断上下文只允许 app_nvm_tick 防抖计时。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __APP_NVM_H__
|
||||||
|
#define __APP_NVM_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化(校验 VM 有效性,仅日志;数据加载在 app_nvm_load)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 VM 加载业务字段回填 gDevData(开机调用,在 userInit 之后)
|
||||||
|
* @note VM 无效(首次上电)保持 gDevData 现值——userInit 已写入 DP7 默认
|
||||||
|
* 10s×3+3s。只回填配置,DP1/泵保持关,不恢复作业。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_load(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即保存全部业务字段到 VM(任务上下文)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_save(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 任务上下文落盘:防抖满后真正写 VM(由 usr_timer_loop 500ms 调用)
|
||||||
|
* @note 必须且只能在非中断上下文调用(VM 擦写 flash 栈用量大)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_flush(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记业务字段有变化,防抖计时归零(DP7/计划/plan_en/DP20/22 写入时调用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_mark_dirty(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即保存运行记录 item(满 600s / 跨天 / 首次对时调用,任务上下文)
|
||||||
|
* @note 与 app_nvm_save 分开:运行记录落盘节奏与配置变化无关,避免把
|
||||||
|
* 配置区无谓擦写一遍
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_save_runrec(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 出厂重置:清全部业务 VM(含运行记录;保留 BLE 配对,规格书 15 章)
|
||||||
|
* @note 4 个 item 写全 0 → magic 失效,下次开机走首次上电默认值;
|
||||||
|
* RAM 复位由调用方(app_function)完成
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_nvm_reset(void);
|
||||||
|
|
||||||
|
#endif /* __APP_NVM_H__ */
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_runrec.c
|
||||||
|
* @brief CBR2 31 天运行记录(规格书 14.1 节,DP18 RO,124B)
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 什么叫在运行:app_pump_get()==1(PC2 与 PA6 **都已导通**),
|
||||||
|
* 间隔等待/作业态开但泵关不计(14.1)。日期轴:未对时只往 slot[0]
|
||||||
|
* 加、不旋转;对时后按本地日历左移;掉电再上电先由 app_nvm_load 灌 VM。
|
||||||
|
*
|
||||||
|
* ⚠ 上下文分工(勿再踩复位事故):
|
||||||
|
* - app_runrec_tick(20ms 中断):只把泵导通的毫秒折成 pending 秒,
|
||||||
|
* 不碰 s_rr.rec[]、不写 flash。
|
||||||
|
* - app_runrec_rotate_if_needed(500ms 任务):收下 pending 加到
|
||||||
|
* slot[0]、每拍灌 DP18、跨天旋转、满 600s / 跨天 / 首次对时才
|
||||||
|
* 写 VM。拔电没有软关机钩子,接受最多丢不满 10 分钟的秒数。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "system/includes.h" /* local_irq_disable / local_irq_enable */
|
||||||
|
#include "app_runrec.h"
|
||||||
|
#include "app_pump.h"
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "usr_rtc.h"
|
||||||
|
#include "jb_product.h"
|
||||||
|
#include "board_pin.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(如 20260904)
|
||||||
|
* @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); /* DP18 报全 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_pump_get()) {
|
||||||
|
/* 没在喷:不累加,并丢弃不足 1 秒的零头(14.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、灌 DP18、跨天旋转、必要时写 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 每拍灌 DP18:读请求 / 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 打包成 DP18 的 124B 载荷(31 × uint32 大端)
|
||||||
|
* @param out 输出缓冲
|
||||||
|
* @note 模块关闭时仍输出全 0(13.1:关闭时报全 0),
|
||||||
|
* 避免 app_nvm 把未初始化栈内存写进 VM 的 RUNREC item
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_pack(uint8_t *out)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
#if !BOARD_RUNREC_ENABLE
|
||||||
|
memset(out, 0, APP_RUNREC_PACK_LEN);
|
||||||
|
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); /* 开机立刻灌 DP18,不等 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,81 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_runrec.h
|
||||||
|
* @brief CBR2 31 天运行记录(规格书 14.1 节,DP18 RO,124B):泵实际导通计秒
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 什么叫在运行:app_pump_get()==1(PC2 与 PA6 **都已导通**)。
|
||||||
|
* 间隔等待、作业态为开但泵已关都不计。日期轴:未对时只往 slot[0]
|
||||||
|
* 加不旋转;对时后按本地日历左移。落盘:每累计 600s 写一次 VM;
|
||||||
|
* 跨天立刻写。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __APP_RUNREC_H__
|
||||||
|
#define __APP_RUNREC_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "board_pin.h"
|
||||||
|
|
||||||
|
#define APP_RUNREC_PACK_LEN (BOARD_RUN_RECORD_DAYS * 4u) /**< 124B */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化(未对时:只往 slot0 加,不旋转)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 20ms 节拍:继电器实际吸合则累计秒,满 10 分钟打脏(不写 flash)
|
||||||
|
* @param dt 距上次调用的毫秒数
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_tick(uint16_t dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 500ms 任务上下文:收下 pending 秒并灌 DP17;对时后跨天左移旋转
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_rotate_if_needed(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读某天秒数(0=今天)
|
||||||
|
* @param day 0~30
|
||||||
|
* @return 秒
|
||||||
|
*/
|
||||||
|
uint32_t app_runrec_get(uint8_t day);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打包 124B(每格 uint32 大端),灌 gDevData.run_record
|
||||||
|
* @param out 输出缓冲(≥124B)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_pack(uint8_t *out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 VM 解包 124B(uint32 大端)
|
||||||
|
* @param in 输入缓冲
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_unpack(const uint8_t *in);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief slot[0] 对应本地日期(yyyymmdd 打包)
|
||||||
|
* @return 日期
|
||||||
|
*/
|
||||||
|
uint32_t app_runrec_get_slot0_date(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 slot[0] 日期(对时/旋转后写回)
|
||||||
|
* @param ymd yyyymmdd
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_set_slot0_date(uint32_t ymd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清零 31 天记录与日期(出厂重置)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_runrec_reset(void);
|
||||||
|
|
||||||
|
#endif /* __APP_RUNREC_H__ */
|
||||||
+275
-39
@@ -3,13 +3,25 @@
|
|||||||
* @brief 喷淋作业引擎:一轮 = 单次时长 × 次数 + 间隔(规格书第 10 章)
|
* @brief 喷淋作业引擎:一轮 = 单次时长 × 次数 + 间隔(规格书第 10 章)
|
||||||
* @author cyWu <1917507415@qq.com>
|
* @author cyWu <1917507415@qq.com>
|
||||||
* @date 2026.09.09
|
* @date 2026.09.09
|
||||||
* @version V1.0.0
|
* @version V1.3.0
|
||||||
* @history
|
* @history
|
||||||
|
* - V1.3.0, 2026.09.09, cyWu, P2 轮来源 owner(手动/计划/联动)+ 计划参数
|
||||||
|
* 通道 + 轮结束事件(仲裁收口用)
|
||||||
|
* - V1.2.0, 2026.09.09, cyWu, P1 请求队列化:任务上下文只置请求,全部状态
|
||||||
|
* 变更收进 20ms tick 消费(DP 写路径与 ISR 竞争消除)
|
||||||
|
* - V1.1.0, 2026.09.09, cyWu, P1 增加 restart(DP7 写入中止旧轮重开)
|
||||||
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布(默认 10s×3 次+3s,只跑一轮)
|
* - V1.0.0, 2026.09.09, cyWu, P0 首次发布(默认 10s×3 次+3s,只跑一轮)
|
||||||
*
|
*
|
||||||
* @note 本模块是**唯一切泵者**(规格书 7.2):键/DP/联动/计划都只请求
|
* @note 本模块是**唯一切泵者**(规格书 7.2):键/DP/联动/计划都只请求
|
||||||
* "开一轮 / 停"。20ms 节拍倒数,禁止阻塞 delay。间隔等待也算作业态
|
* "开一轮 / 停"。20ms 节拍倒数,禁止阻塞 delay。间隔等待也算作业态
|
||||||
* (DP1=1、绿灯继续闪),但泵已关、不记运行秒(P2 由 app_runrec 负责)。
|
* (DP1=1、绿灯继续闪),但泵已关、不记运行秒(app_runrec 只计
|
||||||
|
* app_pump_get()==1 的秒)。
|
||||||
|
*
|
||||||
|
* @note ⚠ 上下文模型:s_spray 状态机只允许在 20ms tick 上下文访问。
|
||||||
|
* 任务上下文(按键回调同上下文 / BLE 线程写 DP)只准调 req_* 接口
|
||||||
|
* 置单字节请求,幂等判断与执行全部延迟到 app_spray_poll 消费,
|
||||||
|
* 杜绝 ISR 抢占写到一半的状态。轮结束事件由 poll 置位、
|
||||||
|
* app_arbiter 在 500ms 任务上下文消费(单次计划自毁 / 出队 / 补判联动)。
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "system/includes.h"
|
#include "system/includes.h"
|
||||||
@@ -29,12 +41,44 @@ typedef enum {
|
|||||||
SPRAY_PHASE_WAIT /**< 间隔段:泵关,倒数间隔(仅 interval>0 出现) */
|
SPRAY_PHASE_WAIT /**< 间隔段:泵关,倒数间隔(仅 interval>0 出现) */
|
||||||
} SprayPhase_t;
|
} SprayPhase_t;
|
||||||
|
|
||||||
/** 模块状态:全程由 20ms 业务节拍(usr_timer_tick)驱动,无跨上下文竞争 */
|
/** 当前轮来源(规格书 13 章仲裁;手动 > 计划 > 联动) */
|
||||||
|
typedef enum {
|
||||||
|
SPRAY_OWNER_NONE = 0, /**< 空闲 */
|
||||||
|
SPRAY_OWNER_MANUAL, /**< PB1 / DP1 / DP7(手动抢权) */
|
||||||
|
SPRAY_OWNER_PLAN, /**< 计划到点(排队轮到,slot 见 plan_slot) */
|
||||||
|
SPRAY_OWNER_LINKAGE /**< 湿度联动循环 */
|
||||||
|
} SprayOwner_t;
|
||||||
|
|
||||||
|
/** 请求类型(任务上下文置位,20ms tick 消费;后写覆盖先写) */
|
||||||
|
typedef enum {
|
||||||
|
SPRAY_REQ_NONE = 0,
|
||||||
|
SPRAY_REQ_START, /**< 空闲才开一轮(手动,DP7 参数;PB1/DP1) */
|
||||||
|
SPRAY_REQ_STOP, /**< 立刻停(手动停/联动过湿停) */
|
||||||
|
SPRAY_REQ_RESTART, /**< 中止旧轮按最新 DP7 重开(手动,合法 DP7 写入) */
|
||||||
|
SPRAY_REQ_START_LINK,/**< 空闲才开一轮(联动,DP7 参数) */
|
||||||
|
SPRAY_REQ_START_PLAN /**< 空闲才开一轮(计划,req_* 自带参数) */
|
||||||
|
} SprayReq_t;
|
||||||
|
|
||||||
|
/** 模块状态:状态机成员只在 20ms tick 上下文访问;req/参数通道跨上下文 */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t initialized;
|
uint8_t initialized;
|
||||||
uint8_t state; /**< 0=空闲,1=作业中 */
|
uint8_t state; /**< 0=空闲,1=作业中 */
|
||||||
uint8_t phase; /**< 当前段(SPRAY_PHASE_*) */
|
uint8_t phase; /**< 当前段(SPRAY_PHASE_*) */
|
||||||
uint16_t total_count; /**< 本轮总次数(来自 DP7) */
|
volatile uint8_t req; /**< 待执行请求(SprayReq_t),任务上下文写 */
|
||||||
|
volatile uint8_t owner; /**< 当前轮来源(SprayOwner_t) */
|
||||||
|
volatile uint8_t plan_slot;/**< owner==PLAN 时槽位 0~6 */
|
||||||
|
/* 计划请求参数通道(BLE 线程写,20ms 读;32 位核对齐 16 位访问原子) */
|
||||||
|
volatile uint16_t req_dur_s;
|
||||||
|
volatile uint8_t req_cnt;
|
||||||
|
volatile uint16_t req_itv_s;
|
||||||
|
volatile uint8_t req_plan_slot;
|
||||||
|
/* 轮结束队列(ISR 置、500ms 任务弹;2 格防 restart 后短轮在同一拍内盖掉) */
|
||||||
|
volatile uint8_t done_owner[2];
|
||||||
|
volatile uint8_t done_slot[2];
|
||||||
|
volatile uint8_t done_wr;
|
||||||
|
volatile uint8_t done_rd;
|
||||||
|
volatile uint8_t done_cnt;
|
||||||
|
uint16_t total_count; /**< 本轮总次数 */
|
||||||
uint16_t round; /**< 已完成的次数 */
|
uint16_t round; /**< 已完成的次数 */
|
||||||
uint32_t dur_ms; /**< 单次时长 ms */
|
uint32_t dur_ms; /**< 单次时长 ms */
|
||||||
uint32_t wait_ms; /**< 间隔 ms(0=中间不停泵) */
|
uint32_t wait_ms; /**< 间隔 ms(0=中间不停泵) */
|
||||||
@@ -57,7 +101,7 @@ static void spray_parse_cfg(void)
|
|||||||
uint16_t itv = (uint16_t)(((uint16_t)p[3] << 8) | p[4]);
|
uint16_t itv = (uint16_t)(((uint16_t)p[3] << 8) | p[4]);
|
||||||
|
|
||||||
if ((dur == 0) || (cnt == 0)) {
|
if ((dur == 0) || (cnt == 0)) {
|
||||||
/* 从未收到合法 DP7(P0 无 VM,理论上 userInit 已写默认,此处兜底) */
|
/* 从未收到合法 DP7(userInit 已写默认,VM 加载覆盖,此处兜底) */
|
||||||
dur = BOARD_SPRAY_DEFAULT_DUR_S;
|
dur = BOARD_SPRAY_DEFAULT_DUR_S;
|
||||||
cnt = BOARD_SPRAY_DEFAULT_COUNT;
|
cnt = BOARD_SPRAY_DEFAULT_COUNT;
|
||||||
itv = BOARD_SPRAY_DEFAULT_INTERVAL_S;
|
itv = BOARD_SPRAY_DEFAULT_INTERVAL_S;
|
||||||
@@ -71,7 +115,67 @@ static void spray_parse_cfg(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化作业引擎(泵保持关,参数待 start 时解析)
|
* @brief 起一轮(只在 20ms tick 上下文调用)
|
||||||
|
* @param owner 轮来源
|
||||||
|
* @param slot 计划槽位(非计划轮忽略)
|
||||||
|
* @param from_plan 1=用 req_* 计划参数,0=解析 gDevData.spray_config
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void spray_start_round(uint8_t owner, uint8_t slot, uint8_t from_plan)
|
||||||
|
{
|
||||||
|
if (from_plan) {
|
||||||
|
s_spray.dur_ms = (uint32_t)s_spray.req_dur_s * 1000u;
|
||||||
|
s_spray.wait_ms = (uint32_t)s_spray.req_itv_s * 1000u;
|
||||||
|
s_spray.total_count = (s_spray.req_cnt != 0u) ? s_spray.req_cnt : 1u;
|
||||||
|
} else {
|
||||||
|
spray_parse_cfg();
|
||||||
|
}
|
||||||
|
s_spray.owner = owner;
|
||||||
|
s_spray.plan_slot = slot;
|
||||||
|
s_spray.round = 0;
|
||||||
|
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
||||||
|
s_spray.remain_ms = s_spray.dur_ms;
|
||||||
|
s_spray.state = 1;
|
||||||
|
app_pump_set(1); /* 两脚同时高 */
|
||||||
|
log_info("spray start owner=%u slot=%u dur=%u ms x%u wait=%u ms\n",
|
||||||
|
(unsigned)owner, (unsigned)slot,
|
||||||
|
(unsigned)s_spray.dur_ms, (unsigned)s_spray.total_count,
|
||||||
|
(unsigned)s_spray.wait_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立刻停(只在 20ms tick 上下文调用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void spray_stop_now(void)
|
||||||
|
{
|
||||||
|
app_pump_set(0); /* 两脚同时低 */
|
||||||
|
s_spray.state = 0;
|
||||||
|
s_spray.round = 0;
|
||||||
|
s_spray.remain_ms = 0;
|
||||||
|
log_info("spray stop, owner=%u\n", (unsigned)s_spray.owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 发布轮结束事件(stop 消费 / 自然结束收口,20ms 上下文)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void spray_emit_done(void)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
if (s_spray.done_cnt >= 2u) {
|
||||||
|
return; /* 极端堆积:丢掉最新一条,保先到的收口(单次自毁优先) */
|
||||||
|
}
|
||||||
|
i = s_spray.done_wr;
|
||||||
|
s_spray.done_owner[i] = s_spray.owner;
|
||||||
|
s_spray.done_slot[i] = s_spray.plan_slot;
|
||||||
|
s_spray.done_wr = (uint8_t)((i + 1u) & 1u);
|
||||||
|
s_spray.done_cnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化作业引擎(泵保持关,参数待起轮时解析)
|
||||||
* @return APP_SPRAY_OK=成功
|
* @return APP_SPRAY_OK=成功
|
||||||
*/
|
*/
|
||||||
AppSpray_Ret_t app_spray_init(void)
|
AppSpray_Ret_t app_spray_init(void)
|
||||||
@@ -85,6 +189,11 @@ AppSpray_Ret_t app_spray_init(void)
|
|||||||
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
||||||
s_spray.round = 0;
|
s_spray.round = 0;
|
||||||
s_spray.remain_ms = 0;
|
s_spray.remain_ms = 0;
|
||||||
|
s_spray.req = SPRAY_REQ_NONE;
|
||||||
|
s_spray.owner = SPRAY_OWNER_NONE;
|
||||||
|
s_spray.done_wr = 0;
|
||||||
|
s_spray.done_rd = 0;
|
||||||
|
s_spray.done_cnt = 0;
|
||||||
s_spray.initialized = 1;
|
s_spray.initialized = 1;
|
||||||
#else
|
#else
|
||||||
log_info("spray disabled\n");
|
log_info("spray disabled\n");
|
||||||
@@ -93,56 +202,121 @@ AppSpray_Ret_t app_spray_init(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 开一轮(幂等:作业中不重开)
|
* @brief 请求:空闲才开一轮(手动,PB1/DP1,DP7 参数)
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
void app_spray_start(void)
|
void app_spray_req_start(void)
|
||||||
{
|
{
|
||||||
if (!s_spray.initialized) {
|
s_spray.req = SPRAY_REQ_START;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (s_spray.state != 0) {
|
|
||||||
return; /* 已在作业中:保持当前轮(规格书 10.5 幂等) */
|
|
||||||
}
|
|
||||||
|
|
||||||
spray_parse_cfg();
|
|
||||||
s_spray.round = 0;
|
|
||||||
s_spray.phase = SPRAY_PHASE_PUMP_ON;
|
|
||||||
s_spray.remain_ms = s_spray.dur_ms;
|
|
||||||
s_spray.state = 1;
|
|
||||||
app_pump_set(1); /* 两脚同时高 */
|
|
||||||
log_info("spray start dur=%u ms x%u wait=%u ms\n",
|
|
||||||
(unsigned)s_spray.dur_ms, (unsigned)s_spray.total_count,
|
|
||||||
(unsigned)s_spray.wait_ms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 停:立刻两脚拉低、结束作业
|
* @brief 请求:立刻停
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
void app_spray_stop(void)
|
void app_spray_req_stop(void)
|
||||||
{
|
{
|
||||||
if (!s_spray.initialized) {
|
s_spray.req = SPRAY_REQ_STOP;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (s_spray.state == 0) {
|
|
||||||
return; /* 已空闲,保持关(幂等) */
|
|
||||||
}
|
|
||||||
app_pump_set(0); /* 两脚同时低 */
|
|
||||||
s_spray.state = 0;
|
|
||||||
s_spray.round = 0;
|
|
||||||
s_spray.remain_ms = 0;
|
|
||||||
log_info("spray stop\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 作业节拍:段倒数与切泵(20ms 调用)
|
* @brief 请求:中止旧轮按最新 DP7 重开(APP 写合法 DP7,规格书 10.2)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_spray_req_restart(void)
|
||||||
|
{
|
||||||
|
s_spray.req = SPRAY_REQ_RESTART;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求:空闲才开一轮(联动循环,DP7 参数,规格书 11.3)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_spray_req_start_link(void)
|
||||||
|
{
|
||||||
|
s_spray.req = SPRAY_REQ_START_LINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求:空闲才开一轮(计划到点,用该条计划自己的参数,规格书 12.3)
|
||||||
|
* @param dur_s 单次时长秒
|
||||||
|
* @param cnt 次数
|
||||||
|
* @param itv_s 间隔秒
|
||||||
|
* @param slot 槽位 0~6(轮结束收口清单次使能用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_spray_req_start_plan(uint16_t dur_s, uint8_t cnt, uint16_t itv_s, uint8_t slot)
|
||||||
|
{
|
||||||
|
s_spray.req_dur_s = dur_s;
|
||||||
|
s_spray.req_cnt = cnt;
|
||||||
|
s_spray.req_itv_s = itv_s;
|
||||||
|
s_spray.req_plan_slot = slot;
|
||||||
|
s_spray.req = SPRAY_REQ_START_PLAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 消费待执行请求(20ms tick 上下文)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void spray_consume_req(void)
|
||||||
|
{
|
||||||
|
uint8_t req = s_spray.req;
|
||||||
|
|
||||||
|
s_spray.req = SPRAY_REQ_NONE;
|
||||||
|
switch (req) {
|
||||||
|
case SPRAY_REQ_START:
|
||||||
|
if (s_spray.state == 0) {
|
||||||
|
spray_start_round(SPRAY_OWNER_MANUAL, 0, 0); /* 已在喷保持(10.5 幂等) */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SPRAY_REQ_START_LINK:
|
||||||
|
if (s_spray.state == 0) {
|
||||||
|
spray_start_round(SPRAY_OWNER_LINKAGE, 0, 0); /* 计划占用时联动不抢 */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SPRAY_REQ_START_PLAN:
|
||||||
|
if (s_spray.state == 0) {
|
||||||
|
spray_start_round(SPRAY_OWNER_PLAN, s_spray.req_plan_slot, 1);
|
||||||
|
}
|
||||||
|
/* 作业中忽略:kick 只在空闲发出,极端时序丢弃由下个 500ms 补 */
|
||||||
|
break;
|
||||||
|
case SPRAY_REQ_STOP:
|
||||||
|
if (s_spray.state != 0) {
|
||||||
|
spray_stop_now();
|
||||||
|
spray_emit_done();
|
||||||
|
s_spray.owner = SPRAY_OWNER_NONE; /* 回空闲:联动判 owner==NONE 依赖此清零 */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SPRAY_REQ_RESTART:
|
||||||
|
if (s_spray.state != 0) {
|
||||||
|
/* 中止旧轮:先发结束事件让仲裁清单次使能(10.4),再按新参数起轮 */
|
||||||
|
app_pump_set(0);
|
||||||
|
s_spray.state = 0;
|
||||||
|
spray_emit_done();
|
||||||
|
s_spray.owner = SPRAY_OWNER_NONE;
|
||||||
|
log_info("spray restart: abort old round\n");
|
||||||
|
}
|
||||||
|
spray_start_round(SPRAY_OWNER_MANUAL, 0, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 作业节拍:消费请求 + 段倒数与切泵(20ms 调用)
|
||||||
* @param dt 距上次调用的毫秒数
|
* @param dt 距上次调用的毫秒数
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
void app_spray_poll(uint16_t dt)
|
void app_spray_poll(uint16_t dt)
|
||||||
{
|
{
|
||||||
if (!s_spray.initialized || (s_spray.state == 0)) {
|
if (!s_spray.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s_spray.req != SPRAY_REQ_NONE) {
|
||||||
|
spray_consume_req();
|
||||||
|
}
|
||||||
|
if (s_spray.state == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (dt == 0) {
|
if (dt == 0) {
|
||||||
@@ -159,6 +333,8 @@ void app_spray_poll(uint16_t dt)
|
|||||||
s_spray.state = 0;
|
s_spray.state = 0;
|
||||||
s_spray.remain_ms = 0;
|
s_spray.remain_ms = 0;
|
||||||
log_info("spray round done, pump off\n");
|
log_info("spray round done, pump off\n");
|
||||||
|
spray_emit_done();
|
||||||
|
s_spray.owner = SPRAY_OWNER_NONE; /* 回空闲:联动判 owner==NONE 依赖此清零 */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (s_spray.wait_ms > 0) {
|
if (s_spray.wait_ms > 0) {
|
||||||
@@ -198,6 +374,66 @@ uint8_t app_spray_is_running(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询当前轮来源(空闲=NONE)
|
||||||
|
* @return 来源(AppSprayOwner_t)
|
||||||
|
*/
|
||||||
|
uint8_t app_spray_owner(void)
|
||||||
|
{
|
||||||
|
#if BOARD_SPRAY_ENABLE
|
||||||
|
return s_spray.owner;
|
||||||
|
#else
|
||||||
|
return APP_SPRAY_OWNER_NONE;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 当前轮计划槽位(owner==PLAN 时有效)
|
||||||
|
* @return 槽位 0~6
|
||||||
|
*/
|
||||||
|
uint8_t app_spray_plan_slot(void)
|
||||||
|
{
|
||||||
|
#if BOARD_SPRAY_ENABLE
|
||||||
|
return s_spray.plan_slot;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 收下轮结束事件(500ms 任务上下文,由仲裁收口调用)
|
||||||
|
* @param owner 输出:结束轮的来源(SprayOwner_t)
|
||||||
|
* @param slot 输出:结束轮的槽位(owner==PLAN 时有效)
|
||||||
|
* @return 1=有事件(已收下),0=无
|
||||||
|
* @note 2 格队列;多轮堆积时逐次调用直到返回 0
|
||||||
|
*/
|
||||||
|
uint8_t app_spray_pop_done(uint8_t *owner, uint8_t *slot)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
if ((owner == NULL) || (slot == NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#if BOARD_SPRAY_ENABLE
|
||||||
|
local_irq_disable();
|
||||||
|
if (s_spray.done_cnt == 0) {
|
||||||
|
local_irq_enable();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
i = s_spray.done_rd;
|
||||||
|
*owner = s_spray.done_owner[i];
|
||||||
|
*slot = s_spray.done_slot[i];
|
||||||
|
s_spray.done_rd = (uint8_t)((i + 1u) & 1u);
|
||||||
|
s_spray.done_cnt--;
|
||||||
|
local_irq_enable();
|
||||||
|
return 1;
|
||||||
|
#else
|
||||||
|
(void)owner;
|
||||||
|
(void)slot;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 查询模块是否已初始化
|
* @brief 查询模块是否已初始化
|
||||||
* @return APP_SPRAY_OK=已初始化,APP_SPRAY_ERR_NOT_INIT=未初始化
|
* @return APP_SPRAY_OK=已初始化,APP_SPRAY_ERR_NOT_INIT=未初始化
|
||||||
|
|||||||
@@ -3,40 +3,75 @@
|
|||||||
* @brief 喷淋作业引擎:一轮 = 单次时长 × 次数 + 间隔(规格书第 10 章)
|
* @brief 喷淋作业引擎:一轮 = 单次时长 × 次数 + 间隔(规格书第 10 章)
|
||||||
* @author cyWu <1917507415@qq.com>
|
* @author cyWu <1917507415@qq.com>
|
||||||
* @date 2026.09.09
|
* @date 2026.09.09
|
||||||
* @version V1.0.0
|
* @version V1.3.0
|
||||||
******************************************************************************/
|
* @history
|
||||||
|
* - V1.3.0, 2026.09.09, cyWu, P2 轮来源 owner + 计划参数通道 + 轮结束事件
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
#ifndef __APP_SPRAY_H__
|
#ifndef __APP_SPRAY_H__
|
||||||
#define __APP_SPRAY_H__
|
#define __APP_SPRAY_H__
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** 轮来源(规格书 13 章仲裁:手动 > 计划 > 联动) */
|
||||||
|
typedef enum {
|
||||||
|
APP_SPRAY_OWNER_NONE = 0, /**< 空闲 */
|
||||||
|
APP_SPRAY_OWNER_MANUAL, /**< PB1 / DP1 / DP7 */
|
||||||
|
APP_SPRAY_OWNER_PLAN, /**< 计划到点 */
|
||||||
|
APP_SPRAY_OWNER_LINKAGE /**< 湿度联动循环 */
|
||||||
|
} AppSprayOwner_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
APP_SPRAY_OK = 0,
|
APP_SPRAY_OK = 0,
|
||||||
APP_SPRAY_ERR_NOT_INIT
|
APP_SPRAY_ERR_NOT_INIT
|
||||||
} AppSpray_Ret_t;
|
} AppSpray_Ret_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化作业引擎(读 DP7 有效配置,泵保持关)
|
* @brief 初始化作业引擎(泵保持关,参数待起轮时解析)
|
||||||
* @return APP_SPRAY_OK=成功
|
* @return APP_SPRAY_OK=成功
|
||||||
*/
|
*/
|
||||||
AppSpray_Ret_t app_spray_init(void);
|
AppSpray_Ret_t app_spray_init(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 开一轮:用 RAM 里最近有效 DP7 配置(非法回退 10s×3 次+3s)
|
* @brief 请求开一轮:空闲才执行,用 RAM 里最近有效 DP7 配置(手动来源)
|
||||||
* @note 幂等:已在作业中则保持当前轮不重开(规格书 10.5,PB1/DP1 语义)。
|
* @note 请求在 20ms tick 消费;幂等:消费时已在作业中则保持当前轮
|
||||||
|
* (规格书 10.5,PB1/DP1 语义)。
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
void app_spray_start(void);
|
void app_spray_req_start(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 停:立刻两脚拉低、结束作业(间隔等待中也立即停)
|
* @brief 请求停:消费时立刻两脚拉低、结束作业(间隔等待中也立即停)
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
void app_spray_stop(void);
|
void app_spray_req_stop(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 作业节拍:时长/间隔倒数,到点切泵(每 20ms 调用,可中断上下文)
|
* @brief 请求重开一轮:中止当前轮(若有)并按 RAM 里最新 DP7 参数立刻开新轮
|
||||||
|
* @note APP 写合法 DP7 的专用入口(规格书 10.2:中止旧轮再开新轮)。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_spray_req_restart(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求开一轮(联动循环来源,DP7 参数)
|
||||||
|
* @note 规格书 11.3:H<Hmin 时循环开一轮又一轮;计划占用中联动不抢。
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_spray_req_start_link(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 请求开一轮(计划来源,用该条计划自己的参数,规格书 12.3)
|
||||||
|
* @param dur_s 单次时长秒
|
||||||
|
* @param cnt 次数
|
||||||
|
* @param itv_s 间隔秒
|
||||||
|
* @param slot 槽位 0~6(轮结束收口清单次使能用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_spray_req_start_plan(uint16_t dur_s, uint8_t cnt, uint16_t itv_s, uint8_t slot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 作业节拍:先消费请求,再做时长/间隔倒数与切泵(每 20ms 调用)
|
||||||
* @param dt 距上次调用的毫秒数
|
* @param dt 距上次调用的毫秒数
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
@@ -48,6 +83,26 @@ void app_spray_poll(uint16_t dt);
|
|||||||
*/
|
*/
|
||||||
uint8_t app_spray_is_running(void);
|
uint8_t app_spray_is_running(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询当前轮来源(AppSprayOwner_t;空闲=NONE)
|
||||||
|
* @return 来源
|
||||||
|
*/
|
||||||
|
uint8_t app_spray_owner(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 当前轮计划槽位(owner==PLAN 时有效)
|
||||||
|
* @return 槽位 0~6
|
||||||
|
*/
|
||||||
|
uint8_t app_spray_plan_slot(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 收下轮结束事件(500ms 任务上下文,仲裁收口用)
|
||||||
|
* @param owner 输出:结束轮的来源(AppSprayOwner_t)
|
||||||
|
* @param slot 输出:结束轮的槽位(owner==PLAN 时有效)
|
||||||
|
* @return 1=有事件(已收下),0=无
|
||||||
|
*/
|
||||||
|
uint8_t app_spray_pop_done(uint8_t *owner, uint8_t *slot);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 查询模块是否已初始化
|
* @brief 查询模块是否已初始化
|
||||||
* @return APP_SPRAY_OK=已初始化,APP_SPRAY_ERR_NOT_INIT=未初始化
|
* @return APP_SPRAY_OK=已初始化,APP_SPRAY_ERR_NOT_INIT=未初始化
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_th_scan.c
|
||||||
|
* @brief CBR2 断连湿度扫描(规格书第 8 章):500ms 重连广播 ↔ 500ms 扫描
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 门控(8 章表格):已连接 / 未配对 / 配对中(usr_goto_pair) /
|
||||||
|
* OTA 中(usr_goto_updata) / 联动配置无效(缸体 0)→ 不扫描,纯广播。
|
||||||
|
* 进入交替后偶拍重连广播、奇拍扫描(8.2),一拍 = BOARD_ADV_SCAN_SLOT_MS。
|
||||||
|
*
|
||||||
|
* 匹配(8.1):名字 == "JBao-"+DP9 缸体号(uint16 十进制,无前导零)
|
||||||
|
* 且厂商段 0xFFFF 首字节 == DP9 配置版本 → humi=uint8% 喂联动
|
||||||
|
* (温度 int8 不取,DP5 本版忽略)。名字/版本/解析任何一项不符 →
|
||||||
|
* 当没收到,不更新湿度、不停喷(保持上一态)。
|
||||||
|
* 低开销:先滤名字,名字对了才找厂商段。
|
||||||
|
*
|
||||||
|
* ⚠ 上报回调跑在 BLE 栈线程;扫描只允许断连态,占用射频期间广播已
|
||||||
|
* 暂停(usr_le_set_adv_pause),不允许「边广播边扫」。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "system/includes.h"
|
||||||
|
#include "app_config.h"
|
||||||
|
#include "app_main.h" /* usr_var:配对/OTA 流程标志 */
|
||||||
|
#include "app_th_scan.h"
|
||||||
|
#include "app_linkage.h"
|
||||||
|
#include "usr_le_api.h" /* usr_ble_is_connected / usr_get_pair_flag / pause */
|
||||||
|
#include "board_pin.h"
|
||||||
|
#include "btstack/le/ble_api.h" /* ble_op_set_scan_param / ble_op_scan_enable2 */
|
||||||
|
|
||||||
|
#define LOG_TAG_CONST APP
|
||||||
|
#define LOG_TAG "[THSCAN]"
|
||||||
|
#define LOG_INFO_ENABLE
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
/* adv AD type */
|
||||||
|
#define TH_SCAN_AD_NAME_SHORT 0x08u /* Shortened Local Name */
|
||||||
|
#define TH_SCAN_AD_NAME_COMPLETE 0x09u /* Complete Local Name */
|
||||||
|
#define TH_SCAN_AD_MFR 0xFFu /* Manufacturer Specific Data */
|
||||||
|
|
||||||
|
/* 厂商 0xFFFF(小端)后 3B:配置版本 / 温度 int8 / 湿度 uint8%(8.1) */
|
||||||
|
#define TH_SCAN_MFR_PAYLOAD_LEN 3u
|
||||||
|
|
||||||
|
/* 期望名 "JBao-"(5) + uint16 十进制(<=5) + '\0' */
|
||||||
|
#define TH_SCAN_EXPECT_MAX 11u
|
||||||
|
|
||||||
|
/* 扫描参数:passive;一拍连续听 BOARD_ADV_SCAN_SLOT_MS(8.2)。
|
||||||
|
* 不去重:风扇同一地址先发回连再发 JBao-。单位 0.625ms → ms*8/5(500ms→800) */
|
||||||
|
#define TH_SCAN_SCAN_TYPE 0u
|
||||||
|
#define TH_SCAN_INTERVAL_UNIT (uint16_t)((BOARD_ADV_SCAN_SLOT_MS * 8u) / 5u)
|
||||||
|
#define TH_SCAN_WINDOW_UNIT TH_SCAN_INTERVAL_UNIT
|
||||||
|
|
||||||
|
/** 扫描模块运行时(全部状态集中在此) */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t active; /**< 交替运行中(满足门控后置 1) */
|
||||||
|
uint8_t scan_on; /**< 控制器扫描已使能(上报过滤用) */
|
||||||
|
uint32_t tick; /**< 交替拍计数:偶=重连广播,奇=扫描(8.2) */
|
||||||
|
uint16_t tank_id; /**< 期望缸体号(配置变化时重建 expect) */
|
||||||
|
uint8_t cfg_ver; /**< 期望配置版本 */
|
||||||
|
uint8_t expect_len;
|
||||||
|
char expect[TH_SCAN_EXPECT_MAX]; /**< "JBao-"+十进制缸体号 */
|
||||||
|
} ThScanCtx_t;
|
||||||
|
|
||||||
|
static ThScanCtx_t s_scan;
|
||||||
|
|
||||||
|
static void th_scan_disabled_once(void)
|
||||||
|
{
|
||||||
|
static uint8_t s_logged;
|
||||||
|
|
||||||
|
if (!s_logged) {
|
||||||
|
s_logged = 1;
|
||||||
|
log_info("app_th_scan disabled\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 门控:只有断连已配对、非配对/OTA 流程、联动配置有效才扫描
|
||||||
|
* @return 1=进入交替;0=纯广播(找回 APP 优先)
|
||||||
|
*/
|
||||||
|
static uint8_t th_scan_should_run(void)
|
||||||
|
{
|
||||||
|
if (usr_ble_is_connected()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!usr_get_pair_flag()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (usr_var.usr_goto_pair) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (usr_var.usr_goto_updata) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!app_linkage_is_valid()) { /* 缸体 0 / Hmin>Hmax:无联动可跑,不扫 */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 配置变化时重建期望名(tank/版本取自 DP9 联动配置)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void th_scan_sync_cfg(void)
|
||||||
|
{
|
||||||
|
uint16_t tank = app_linkage_get_tank_id();
|
||||||
|
uint8_t ver = app_linkage_get_cfg_ver();
|
||||||
|
char num[6];
|
||||||
|
uint16_t t;
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if (s_scan.tank_id == tank && s_scan.cfg_ver == ver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_scan.tank_id = tank;
|
||||||
|
s_scan.cfg_ver = ver;
|
||||||
|
|
||||||
|
memcpy(s_scan.expect, "JBao-", 5);
|
||||||
|
s_scan.expect_len = 5;
|
||||||
|
t = tank;
|
||||||
|
do { /* 十进制,无前导零 */
|
||||||
|
num[n++] = (char)('0' + (t % 10u));
|
||||||
|
t /= 10u;
|
||||||
|
} while (t);
|
||||||
|
while (n > 0 && s_scan.expect_len < sizeof(s_scan.expect)) {
|
||||||
|
s_scan.expect[s_scan.expect_len++] = num[--n];
|
||||||
|
}
|
||||||
|
s_scan.expect[s_scan.expect_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开扫描(幂等;先由调用方暂停广播,再使能扫描)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void th_scan_start(void)
|
||||||
|
{
|
||||||
|
ble_cmd_ret_e ret;
|
||||||
|
|
||||||
|
if (s_scan.scan_on) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ble_op_set_scan_param(TH_SCAN_SCAN_TYPE, TH_SCAN_INTERVAL_UNIT, TH_SCAN_WINDOW_UNIT);
|
||||||
|
ret = ble_op_scan_enable2(1, 0);
|
||||||
|
if (ret != BLE_CMD_RET_SUCESS) {
|
||||||
|
log_info("scan enable fail %d\n", (int)ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_scan.scan_on = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关扫描(幂等)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void th_scan_stop(void)
|
||||||
|
{
|
||||||
|
if (!s_scan.scan_on) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ble_op_scan_enable2(0, 0);
|
||||||
|
s_scan.scan_on = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 退出交替:停扫、恢复广播,回到纯 adv 策略
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void th_scan_leave(void)
|
||||||
|
{
|
||||||
|
th_scan_stop();
|
||||||
|
usr_le_set_adv_pause(0);
|
||||||
|
s_scan.active = 0;
|
||||||
|
s_scan.tick = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 名字是否等于期望 "JBao-{tank}"
|
||||||
|
* @param data adv 数据
|
||||||
|
* @param dlen adv 数据长度
|
||||||
|
* @return 1=匹配
|
||||||
|
*/
|
||||||
|
static uint8_t th_scan_name_ok(const uint8_t *data, int dlen)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i + 1 < dlen) {
|
||||||
|
uint8_t alen = data[i];
|
||||||
|
|
||||||
|
if (alen == 0 || i + 1 + alen > dlen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((data[i + 1] == TH_SCAN_AD_NAME_COMPLETE ||
|
||||||
|
data[i + 1] == TH_SCAN_AD_NAME_SHORT) &&
|
||||||
|
(uint8_t)(alen - 1u) == s_scan.expect_len &&
|
||||||
|
memcmp(&data[i + 2], s_scan.expect, s_scan.expect_len) == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
i += 1 + alen;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 名字已匹配前提下取厂商段;版本相等才更新联动湿度
|
||||||
|
* @note 版本不等 / 厂商段缺失 → 当没收到(保持上一态)
|
||||||
|
* @param data adv 数据
|
||||||
|
* @param dlen adv 数据长度
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void th_scan_try_feed(const uint8_t *data, int dlen)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i + 1 < dlen) {
|
||||||
|
uint8_t alen = data[i];
|
||||||
|
|
||||||
|
if (alen == 0 || i + 1 + alen > dlen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (data[i + 1] == TH_SCAN_AD_MFR && alen - 1u >= 2u + TH_SCAN_MFR_PAYLOAD_LEN) {
|
||||||
|
const uint8_t *pl = &data[i + 4]; /* 跳过 len/type/2B 厂商号 */
|
||||||
|
|
||||||
|
if (data[i + 2] == 0xFFu && data[i + 3] == 0xFFu &&
|
||||||
|
pl[0] == s_scan.cfg_ver) {
|
||||||
|
app_linkage_set_scan_humi(pl[2]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i += 1 + alen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 一包 adv payload 的匹配入口
|
||||||
|
* @param data AD 结构
|
||||||
|
* @param dlen 长度
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void th_scan_on_adv(const uint8_t *data, int dlen)
|
||||||
|
{
|
||||||
|
if (data == NULL || dlen <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (th_scan_name_ok(data, dlen)) {
|
||||||
|
th_scan_try_feed(data, dlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化扫描模块
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_th_scan_init(void)
|
||||||
|
{
|
||||||
|
#if BOARD_TH_SCAN_ENABLE
|
||||||
|
memset(&s_scan, 0, sizeof(s_scan));
|
||||||
|
log_info("app_th_scan init\n");
|
||||||
|
#else
|
||||||
|
th_scan_disabled_once();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 500ms 任务上下文(usr_timer_loop 内):偶拍广播 / 奇拍扫描
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_th_scan_tick(void)
|
||||||
|
{
|
||||||
|
#if BOARD_TH_SCAN_ENABLE
|
||||||
|
if (!th_scan_should_run()) {
|
||||||
|
th_scan_leave();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
th_scan_sync_cfg();
|
||||||
|
if (!s_scan.active) {
|
||||||
|
s_scan.active = 1;
|
||||||
|
s_scan.tick = 0; /* 8.2:进入先广播一拍,让 APP 优先找回 */
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((s_scan.tick & 1u) == 0u) {
|
||||||
|
th_scan_stop(); /* 偶:重连广播 */
|
||||||
|
usr_le_set_adv_pause(0);
|
||||||
|
} else {
|
||||||
|
usr_le_set_adv_pause(1); /* 奇:关广播、开扫描 ≤500ms */
|
||||||
|
th_scan_start();
|
||||||
|
}
|
||||||
|
s_scan.tick++;
|
||||||
|
#else
|
||||||
|
th_scan_disabled_once();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GAP_EVENT_ADVERTISING_REPORT(栈线程)
|
||||||
|
* @note 杰理布局与 wireless_mic 相同:packet[0]=0xE2 [1]=len [2]=adv_report_t
|
||||||
|
* @param packet HCI 事件包
|
||||||
|
* @param size 事件包长度
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_th_scan_report_event(uint8_t *packet, uint16_t size)
|
||||||
|
{
|
||||||
|
#if BOARD_TH_SCAN_ENABLE
|
||||||
|
adv_report_t *rpt;
|
||||||
|
|
||||||
|
if (!s_scan.active || !s_scan.scan_on || packet == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (size < 12) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rpt = (adv_report_t *)&packet[2];
|
||||||
|
if (rpt->length == 0 || rpt->length > 31) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
th_scan_on_adv(rpt->data, rpt->length);
|
||||||
|
#else
|
||||||
|
(void)packet;
|
||||||
|
(void)size;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_th_scan.h
|
||||||
|
* @brief CBR2 断连湿度扫描(规格书第 8 章):500ms 重连广播 ↔ 500ms 扫描
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
*
|
||||||
|
* @note 只在「断连 + 已配对 + 非配对/OTA 流程 + 联动配置有效(缸体≠0)」
|
||||||
|
* 时进入 500ms 交替:偶拍重连广播 / 奇拍扫描。匹配规则(8.1):
|
||||||
|
* 名字 == "JBao-"+本机 DP9 缸体号(uint16 十进制)+ 配置版本相等
|
||||||
|
* → humi = 厂商段第 3 字节(uint8 %);温度不参与开停(DP5 忽略)。
|
||||||
|
* 名字/版本不对或解析失败 → 当没收到,保持上一湿度与上一喷淋态。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __APP_TH_SCAN_H__
|
||||||
|
#define __APP_TH_SCAN_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化扫描模块
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_th_scan_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 500ms 任务上下文:满足条件时重连广播/扫描各占一拍交替
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_th_scan_tick(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GAP 扫描上报(栈线程)
|
||||||
|
* @note 杰理把扫描结果包成 GAP_EVENT_ADVERTISING_REPORT(0xE2),
|
||||||
|
* packet[2] 起为 adv_report_t。与 wireless_mic 中央示例同一布局。
|
||||||
|
* @param packet HCI 事件包
|
||||||
|
* @param size 事件包长度
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_th_scan_report_event(uint8_t *packet, uint16_t size);
|
||||||
|
|
||||||
|
#endif /* __APP_TH_SCAN_H__ */
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_timer_task.c
|
||||||
|
* @brief CBR2 喷淋计划引擎(规格书第 12 章):到点触发一轮 + 排队 + RTC 门闩
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0, 首次发布
|
||||||
|
*
|
||||||
|
* @note ⚠ 与加热垫时间窗模型完全不同(规格书 12 章明令禁止照抄):
|
||||||
|
* 本机到「开始时:分」用该条自己的时长/次数/间隔(Byte5-9)跑完
|
||||||
|
* 一轮就结束,没有结束时:分、没有窗内保持。
|
||||||
|
*
|
||||||
|
* 字节布局(12.1):Byte0=使能 Byte1=类型(0单次/1每天/2每周)
|
||||||
|
* Byte2=星期位图(Bit0=周一…Bit6=周日) Byte3=时 Byte4=分
|
||||||
|
* Byte5-6=单次时长 u16 大端 Byte7=次数 Byte8-9=间隔 u16 大端。
|
||||||
|
* 非法参数(时长=0 或次数=0):该条本分钟不触发,使能保持。
|
||||||
|
*
|
||||||
|
* 门闩(12.2):RTC 失败或 plan_en=0 → 不入队 + 清队列,不拆当前轮;
|
||||||
|
* 对时成功后 500ms 判定自然覆盖「当前分钟补触发」,过点不补
|
||||||
|
* (h/min 严格相等保证)。
|
||||||
|
*
|
||||||
|
* 排队(12.3):多条同分钟排队(槽位序),当前轮先跑完再跑队列,
|
||||||
|
* 不插队;新触发只入队,真正起轮在作业空闲时(kick / run)。
|
||||||
|
* 去重:上次触发键 = 日期+时:分+槽位,防 500ms 连打。
|
||||||
|
*
|
||||||
|
* 运行上下文:app_timer_run / kick / on_plan_done 只在
|
||||||
|
* usr_timer_loop(500ms 任务上下文)与轮结束收口(同上下文)调用,
|
||||||
|
* 单次自毁写 VM 安全。
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "system/includes.h"
|
||||||
|
#include "app_timer_task.h"
|
||||||
|
#include "app_spray.h"
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "usr_rtc.h"
|
||||||
|
#include "jb_product.h"
|
||||||
|
#include "board_pin.h"
|
||||||
|
|
||||||
|
#define LOG_TAG_CONST APP
|
||||||
|
#define LOG_TAG "[APP_TMR]"
|
||||||
|
#define LOG_INFO_ENABLE
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
#define TIMER_PLAN_SLOTS 7
|
||||||
|
#define TIMER_PLAN_LEN 15
|
||||||
|
|
||||||
|
/** 计划引擎运行时 */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t queue_mask; /**< bit i = 槽 i 待跑(排队) */
|
||||||
|
uint32_t last_key[TIMER_PLAN_SLOTS]; /**< 去重键:日期+时:分(0=未触发过) */
|
||||||
|
uint8_t last_rtc_ok; /**< 对时沿日志用 */
|
||||||
|
} TimerState_t;
|
||||||
|
|
||||||
|
static TimerState_t s_timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 取第 slot 组计划配置(gDevData:spray_plan1 + time_config2~7)
|
||||||
|
* @param slot 0~6
|
||||||
|
* @return 配置指针;越界返回 NULL
|
||||||
|
*/
|
||||||
|
static uint8_t *timer_cfg_ptr(uint8_t slot)
|
||||||
|
{
|
||||||
|
switch (slot) {
|
||||||
|
case 0: return gDevData.spray_plan1;
|
||||||
|
case 1: return gDevData.time_config2;
|
||||||
|
case 2: return gDevData.time_config3;
|
||||||
|
case 3: return gDevData.time_config4;
|
||||||
|
case 4: return gDevData.time_config5;
|
||||||
|
case 5: return gDevData.time_config6;
|
||||||
|
case 6: return gDevData.time_config7;
|
||||||
|
default: return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算某日期是星期几
|
||||||
|
* @param y 年
|
||||||
|
* @param m 月 1~12
|
||||||
|
* @param d 日
|
||||||
|
* @return 0=周一 … 6=周日(12.1 位图 Bit0=周一)
|
||||||
|
*/
|
||||||
|
static uint8_t timer_calc_dow(uint16_t y, uint8_t m, uint8_t d)
|
||||||
|
{
|
||||||
|
uint8_t mm = m;
|
||||||
|
uint16_t yy = y;
|
||||||
|
uint16_t k;
|
||||||
|
int h;
|
||||||
|
|
||||||
|
if (mm < 3) {
|
||||||
|
mm += 12;
|
||||||
|
yy--;
|
||||||
|
}
|
||||||
|
k = yy % 100;
|
||||||
|
/* 蔡勒公式(格里高利历):h=0 周六 … 6 周五 */
|
||||||
|
h = (d + 13 * (mm + 1) / 5 + k + k / 4 + (yy / 100) / 4 + 5 * (yy / 100)) % 7;
|
||||||
|
return (uint8_t)((h + 5) % 7); /* 0=周一 … 6=周日 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 该条计划本分钟是否应触发(类型/星期/时:分判定,参数合法性另查)
|
||||||
|
* @param cfg 15 字节配置(已确认使能)
|
||||||
|
* @param t 本地时间
|
||||||
|
* @return 1=到点
|
||||||
|
*/
|
||||||
|
static uint8_t timer_is_due(const uint8_t *cfg, const struct sys_time *t)
|
||||||
|
{
|
||||||
|
uint8_t type = cfg[1];
|
||||||
|
|
||||||
|
if (type > 2) {
|
||||||
|
return 0; /* 12.1:其它值该条无效,不触发 */
|
||||||
|
}
|
||||||
|
if (((int16_t)t->hour != (int16_t)cfg[3]) || (t->min != cfg[4])) {
|
||||||
|
return 0; /* 严格相等:到点触发一轮,过点不补 */
|
||||||
|
}
|
||||||
|
if (type == 2) {
|
||||||
|
uint8_t dow = timer_calc_dow(t->year, t->month, t->day);
|
||||||
|
|
||||||
|
if (!(cfg[2] & (uint8_t)(1u << dow))) {
|
||||||
|
return 0; /* 每周:位图未置位 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1; /* 类型 0 单次 / 1 每天:时:分相等即到点 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 该条参数是否合法(12.1:时长=0 或次数=0 非法)
|
||||||
|
* @param cfg 15 字节配置
|
||||||
|
* @return 1=合法
|
||||||
|
*/
|
||||||
|
static uint8_t timer_params_ok(const uint8_t *cfg)
|
||||||
|
{
|
||||||
|
uint16_t dur = (uint16_t)(((uint16_t)cfg[5] << 8) | cfg[6]);
|
||||||
|
uint8_t cnt = cfg[7];
|
||||||
|
|
||||||
|
return ((dur != 0u) && (cnt != 0u)) ? 1u : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 当前分钟去重键(日期+时:分;跨 100 年才可能撞,够用)
|
||||||
|
* @param t 本地时间
|
||||||
|
* @return 键值
|
||||||
|
*/
|
||||||
|
static uint32_t timer_minute_key(const struct sys_time *t)
|
||||||
|
{
|
||||||
|
uint32_t date = ((uint32_t)(t->year % 100u) * 384u)
|
||||||
|
+ ((uint32_t)(t->month - 1u) * 32u)
|
||||||
|
+ (uint32_t)(t->day - 1u);
|
||||||
|
|
||||||
|
return (uint32_t)((date << 12) | ((uint32_t)t->hour * 60u + t->min));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 扫描 7 条计划:到点且未触发过 → 入队(去重)
|
||||||
|
* @param t 本地时间(已确认 RTC 有效)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void timer_scan_due(const struct sys_time *t)
|
||||||
|
{
|
||||||
|
uint32_t key = timer_minute_key(t);
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < TIMER_PLAN_SLOTS; i++) {
|
||||||
|
const uint8_t *cfg = timer_cfg_ptr(i);
|
||||||
|
|
||||||
|
if ((cfg == NULL) || (cfg[0] == 0)) {
|
||||||
|
continue; /* 未使能 */
|
||||||
|
}
|
||||||
|
if (!timer_is_due(cfg, t)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s_timer.last_key[i] == key) {
|
||||||
|
continue; /* 12.3-5:本分钟已处理,防 500ms 连打 */
|
||||||
|
}
|
||||||
|
s_timer.last_key[i] = key;
|
||||||
|
if (!timer_params_ok(cfg)) {
|
||||||
|
log_info("timer[%u] invalid params, skipped this minute\n", (unsigned)i);
|
||||||
|
continue; /* 12.1:非法参数本分钟不触发,使能保持 */
|
||||||
|
}
|
||||||
|
s_timer.queue_mask |= (uint8_t)(1u << i);
|
||||||
|
log_info("timer[%u] due at %02u:%02u, queued (mask=0x%02x)\n",
|
||||||
|
(unsigned)i, (unsigned)t->hour, (unsigned)t->min,
|
||||||
|
(unsigned)s_timer.queue_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空闲时出队起轮:取最小槽位的合法条目,用该条 Byte5-9 参数起轮
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
static void timer_try_kick(void)
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
if (app_spray_is_running()) {
|
||||||
|
return; /* 12.3-7/8:当前轮先跑完,不插队 */
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < TIMER_PLAN_SLOTS; i++) {
|
||||||
|
uint8_t *cfg;
|
||||||
|
uint16_t dur;
|
||||||
|
uint16_t itv;
|
||||||
|
|
||||||
|
if (!(s_timer.queue_mask & (uint8_t)(1u << i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cfg = timer_cfg_ptr(i);
|
||||||
|
if ((cfg == NULL) || (cfg[0] == 0) || !timer_params_ok(cfg)) {
|
||||||
|
s_timer.queue_mask &= (uint8_t)~(1u << i); /* 条目失效:跳过 */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dur = (uint16_t)(((uint16_t)cfg[5] << 8) | cfg[6]);
|
||||||
|
itv = (uint16_t)(((uint16_t)cfg[8] << 8) | cfg[9]);
|
||||||
|
/* 入队 bit 留到 owner==PLAN 再清:避免 req 被后续 START/STOP 盖掉后计划丢失 */
|
||||||
|
app_spray_req_start_plan(dur, cfg[7], itv, i);
|
||||||
|
log_info("timer[%u] kick plan %us x%u +%us\n",
|
||||||
|
(unsigned)i, (unsigned)dur, (unsigned)cfg[7], (unsigned)itv);
|
||||||
|
return; /* 一次只起一条,其余等收口 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化计划引擎
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_init(void)
|
||||||
|
{
|
||||||
|
#if !BOARD_TIMER_ENABLE
|
||||||
|
log_info("timer engine disabled\n");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
memset(&s_timer, 0, sizeof(s_timer));
|
||||||
|
log_info("timer init (on-shot trigger mode)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 推进计划引擎(500ms,任务上下文,由 usr_timer_loop 调用)
|
||||||
|
* @param dt 距上次调用的毫秒数
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_run(uint16_t dt)
|
||||||
|
{
|
||||||
|
#if !BOARD_TIMER_ENABLE
|
||||||
|
(void)dt;
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
struct sys_time t;
|
||||||
|
uint8_t rtc_ok = usr_rtc_get_time(&t);
|
||||||
|
|
||||||
|
(void)dt;
|
||||||
|
|
||||||
|
if (rtc_ok && !s_timer.last_rtc_ok) {
|
||||||
|
log_info("timer rtc synced, check due plans\n"); /* 12.2:对时后补当前分钟 */
|
||||||
|
}
|
||||||
|
s_timer.last_rtc_ok = rtc_ok;
|
||||||
|
|
||||||
|
/* 计划已真正开喷:从队列摘掉该槽(req 被覆盖时 bit 仍在,下拍会重试) */
|
||||||
|
if (app_spray_owner() == APP_SPRAY_OWNER_PLAN) {
|
||||||
|
uint8_t slot = app_spray_plan_slot();
|
||||||
|
|
||||||
|
if (slot < TIMER_PLAN_SLOTS) {
|
||||||
|
s_timer.queue_mask &= (uint8_t)~(1u << slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 12.2 门闩:RTC 无效或 plan_en=0 → 不新触发(清队列,不拆当前轮) */
|
||||||
|
if (!rtc_ok || (gDevData.spray_plan_en == 0)) {
|
||||||
|
s_timer.queue_mask = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer_scan_due(&t);
|
||||||
|
timer_try_kick(); /* 空闲且有队列 → 起轮 */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief APP 写 DP10 计划总开关(mark_dirty 由调用方完成)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_set_en(uint8_t en)
|
||||||
|
{
|
||||||
|
#if BOARD_TIMER_ENABLE
|
||||||
|
if (!en) {
|
||||||
|
s_timer.queue_mask = 0; /* 13 章:plan_en=0 立刻停新触发,不拆当前轮 */
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)en;
|
||||||
|
#endif
|
||||||
|
log_info("spray_plan_en=%d\n", en);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 队列是否还有待跑计划
|
||||||
|
* @return 1=非空
|
||||||
|
*/
|
||||||
|
uint8_t app_timer_has_pending(void)
|
||||||
|
{
|
||||||
|
#if BOARD_TIMER_ENABLE
|
||||||
|
return (s_timer.queue_mask != 0) ? 1u : 0u;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空闲时立刻出队起轮(轮结束边沿收口调用)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_kick(void)
|
||||||
|
{
|
||||||
|
#if BOARD_TIMER_ENABLE
|
||||||
|
timer_try_kick();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计划轮结束收口:单次计划清该条使能并落盘(12.3-4 / 10.4)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_on_plan_done(uint8_t slot)
|
||||||
|
{
|
||||||
|
#if BOARD_TIMER_ENABLE
|
||||||
|
uint8_t *cfg = timer_cfg_ptr(slot);
|
||||||
|
|
||||||
|
if ((cfg != NULL) && (cfg[0] != 0) && (cfg[1] == 0)) {
|
||||||
|
cfg[0] = 0; /* 类型 0 单次:跑完/被停都清,避免单次卡死 */
|
||||||
|
app_nvm_mark_dirty();
|
||||||
|
log_info("timer[%u] once plan done, en cleared\n", (unsigned)slot);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)slot;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* @file app_timer_task.h
|
||||||
|
* @brief CBR2 喷淋计划引擎(规格书第 12 章:到点触发一轮 + 排队 + RTC 门闩)
|
||||||
|
* @author cyWu <1917507415@qq.com>
|
||||||
|
* @date 2026.09.09
|
||||||
|
* @version V1.0.0,首次发布
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __APP_TIMER_TASK_H__
|
||||||
|
#define __APP_TIMER_TASK_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化计划引擎(RTC 门闩:usr_rtc_get_time 失败等同 plan_en=0)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 500ms 任务上下文跑计划引擎:门闩判定 + 到点入队 + 空闲起轮
|
||||||
|
* @param dt 距上次调用的毫秒数(=500)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_run(uint16_t dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief APP 写 DP10 计划总开关(mark_dirty 由调用方完成)
|
||||||
|
* @param en 0=关(不再新触发、清队列;不拆当前轮),1=开
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_set_en(uint8_t en);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 队列是否还有待跑计划(联动让位判断用)
|
||||||
|
* @return 1=非空
|
||||||
|
*/
|
||||||
|
uint8_t app_timer_has_pending(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空闲时立刻出队起轮(轮结束边沿收口调用;不空闲则下拍再试)
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_kick(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计划轮结束收口:单次计划清该条使能并落盘(12.3-4 / 10.4)
|
||||||
|
* @param slot 槽位 0~6
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void app_timer_on_plan_done(uint8_t slot);
|
||||||
|
|
||||||
|
#endif /* __APP_TIMER_TASK_H__ */
|
||||||
+4
-3
@@ -47,21 +47,22 @@ typedef enum {
|
|||||||
LED_EVENT_NONE = 0,
|
LED_EVENT_NONE = 0,
|
||||||
LED_EVENT_GRN_IDLE, /**< 绿灯常亮:空闲(DP1=0) */
|
LED_EVENT_GRN_IDLE, /**< 绿灯常亮:空闲(DP1=0) */
|
||||||
LED_EVENT_GRN_SPRAY, /**< 绿灯闪:作业中(含间隔等待,DP1=1) */
|
LED_EVENT_GRN_SPRAY, /**< 绿灯闪:作业中(含间隔等待,DP1=1) */
|
||||||
LED_EVENT_RED_PAIRED, /**< 红灯常亮:已配对(粘住,空闲/喷淋都不关) */
|
LED_EVENT_RED_PAIRED, /**< 红灯常亮:BLE 已连接 */
|
||||||
LED_EVENT_RED_PAIRING, /**< 红灯快闪:配网键满 5s 提示 / 配对进行中 */
|
LED_EVENT_RED_PAIRING, /**< 红灯快闪:配网键满 5s 提示 / 配对进行中 */
|
||||||
LED_EVENT_PAIR_FAIL, /**< 配对失败:红绿交替 3s(临时占双灯,自删) */
|
LED_EVENT_PAIR_FAIL, /**< 配对失败:红绿交替 3s(临时占双灯,自删) */
|
||||||
LED_EVENT_OTA, /**< OTA:红绿交替,直到升级结束/复位(最高) */
|
LED_EVENT_OTA, /**< OTA:红绿交替,直到升级结束/复位(最高) */
|
||||||
|
LED_EVENT_RED_RECONN, /**< 红灯慢闪:已配对未连接(重连中) */
|
||||||
LED_EVENT_MAX
|
LED_EVENT_MAX
|
||||||
} LED_Event_t;
|
} LED_Event_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先级:高者覆盖低者(handler 返回 1 会拦下更低优先级)。
|
* 优先级:高者覆盖低者(handler 返回 1 会拦下更低优先级)。
|
||||||
* 红绿独立通道:绿灯(作业)占 PRIORITY_1、红灯(配对)占 PRIORITY_2,
|
* 红绿独立通道:绿灯(作业)占 PRIORITY_1、红灯(连接/配网)占 PRIORITY_2,
|
||||||
* 红灯 handler 返回 0 放行绿灯;只有 OTA / 配对失败临时占双灯(返回 1)。
|
* 红灯 handler 返回 0 放行绿灯;只有 OTA / 配对失败临时占双灯(返回 1)。
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PRIORITY_1 = 0, /**< 绿灯通道:作业态(空闲常亮 / 作业闪) */
|
PRIORITY_1 = 0, /**< 绿灯通道:作业态(空闲常亮 / 作业闪) */
|
||||||
PRIORITY_2, /**< 红灯通道:配对态(已配常亮 / 配网快闪) */
|
PRIORITY_2, /**< 红灯通道:已连接常亮 / 重连慢闪 / 配网快闪 */
|
||||||
PRIORITY_3, /**< 配对失败动画(红绿交替,临时占双灯) */
|
PRIORITY_3, /**< 配对失败动画(红绿交替,临时占双灯) */
|
||||||
PRIORITY_4, /**< OTA(红绿交替,最高) */
|
PRIORITY_4, /**< OTA(红绿交替,最高) */
|
||||||
PRIORITY_MAX
|
PRIORITY_MAX
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* @file led_manager.c
|
* @file led_manager.c
|
||||||
* @brief CBR2 红绿 LED 事件表:绿=作业态,红=配对态(规格书第 6 章)
|
* @brief CBR2 红绿 LED 事件表:绿=作业态,红=连接/配网态(规格书第 6 章)
|
||||||
* @author cyWu <1917507415@qq.com>
|
* @author cyWu <1917507415@qq.com>
|
||||||
* @date 2026.09.09
|
* @date 2026.09.09
|
||||||
* @version V1.0.0,P0 Bring-up(独立通道,区别于加热垫互斥灯)
|
* @version V1.0.0,P0 Bring-up(独立通道,区别于加热垫互斥灯)
|
||||||
@@ -18,6 +18,7 @@ static uint8_t led_grn_idle_handler(LED_Config_t *led_config);
|
|||||||
static uint8_t led_grn_spray_handler(LED_Config_t *led_config);
|
static uint8_t led_grn_spray_handler(LED_Config_t *led_config);
|
||||||
static uint8_t led_red_paired_handler(LED_Config_t *led_config);
|
static uint8_t led_red_paired_handler(LED_Config_t *led_config);
|
||||||
static uint8_t led_red_pairing_handler(LED_Config_t *led_config);
|
static uint8_t led_red_pairing_handler(LED_Config_t *led_config);
|
||||||
|
static uint8_t led_red_reconn_handler(LED_Config_t *led_config);
|
||||||
static uint8_t led_pair_fail_handler(LED_Config_t *led_config);
|
static uint8_t led_pair_fail_handler(LED_Config_t *led_config);
|
||||||
static uint8_t led_ota_handler(LED_Config_t *led_config);
|
static uint8_t led_ota_handler(LED_Config_t *led_config);
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ static uint8_t led_grn_spray_handler(LED_Config_t *led_config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 红灯常亮(已配对,粘住):只动红灯,放行绿灯通道
|
* @brief 红灯常亮(BLE 已连接):只动红灯,放行绿灯通道
|
||||||
* @param led_config 配置
|
* @param led_config 配置
|
||||||
* @return 0=放行其它优先级
|
* @return 0=放行其它优先级
|
||||||
*/
|
*/
|
||||||
@@ -81,6 +82,17 @@ static uint8_t led_red_pairing_handler(LED_Config_t *led_config)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 红灯慢闪(已配对未连接 / 重连中 500ms):只动红灯,放行绿灯
|
||||||
|
* @param led_config 配置
|
||||||
|
* @return 0=放行其它优先级
|
||||||
|
*/
|
||||||
|
static uint8_t led_red_reconn_handler(LED_Config_t *led_config)
|
||||||
|
{
|
||||||
|
(void)LED_Update(led_config);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 配对失败红绿交替 3s:到点自删,交回双通道常态
|
* @brief 配对失败红绿交替 3s:到点自删,交回双通道常态
|
||||||
* @param led_config 配置
|
* @param led_config 配置
|
||||||
@@ -136,7 +148,7 @@ void user_led_init(void)
|
|||||||
.ledEventHandler = led_grn_spray_handler,
|
.ledEventHandler = led_grn_spray_handler,
|
||||||
},
|
},
|
||||||
[LED_EVENT_RED_PAIRED] = {
|
[LED_EVENT_RED_PAIRED] = {
|
||||||
/* 已配对(含断连重连、上电已配对):红灯常亮粘住,不碰绿灯 */
|
/* BLE 已连接:红灯常亮,不碰绿灯 */
|
||||||
.event = LED_EVENT_RED_PAIRED,
|
.event = LED_EVENT_RED_PAIRED,
|
||||||
.priority = PRIORITY_2,
|
.priority = PRIORITY_2,
|
||||||
.config.ledMask = LED_CHANNEL(LED_CH_RED),
|
.config.ledMask = LED_CHANNEL(LED_CH_RED),
|
||||||
@@ -156,6 +168,18 @@ void user_led_init(void)
|
|||||||
.config.runNum = 0,
|
.config.runNum = 0,
|
||||||
.ledEventHandler = led_red_pairing_handler,
|
.ledEventHandler = led_red_pairing_handler,
|
||||||
},
|
},
|
||||||
|
[LED_EVENT_RED_RECONN] = {
|
||||||
|
/* 已配对未连接(上电重连 / 断连):红灯 500ms 慢闪,不碰绿灯 */
|
||||||
|
.event = LED_EVENT_RED_RECONN,
|
||||||
|
.priority = PRIORITY_2,
|
||||||
|
.config.ledMask = LED_CHANNEL(LED_CH_RED),
|
||||||
|
.config.state = LED_BLINKING,
|
||||||
|
.config.blinkParams.orDer = 0,
|
||||||
|
.config.blinkParams.onTime = BOARD_LED_RECONN_ON_MS,
|
||||||
|
.config.blinkParams.offTime = BOARD_LED_RECONN_OFF_MS,
|
||||||
|
.config.runNum = 0,
|
||||||
|
.ledEventHandler = led_red_reconn_handler,
|
||||||
|
},
|
||||||
[LED_EVENT_PAIR_FAIL] = {
|
[LED_EVENT_PAIR_FAIL] = {
|
||||||
/* 配对失败:红绿交替 3s 后自删(半周期 BOARD_LED_PAIR_FAIL_MS) */
|
/* 配对失败:红绿交替 3s 后自删(半周期 BOARD_LED_PAIR_FAIL_MS) */
|
||||||
.event = LED_EVENT_PAIR_FAIL,
|
.event = LED_EVENT_PAIR_FAIL,
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
|
|
||||||
#include "jb_product.h"
|
#include "jb_product.h"
|
||||||
#include "board_pin.h"
|
#include "board_pin.h"
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "app_spray.h"
|
||||||
|
#include "app_linkage.h"
|
||||||
|
#include "app_timer_task.h"
|
||||||
|
#include "app_arbiter.h"
|
||||||
|
#include "app_function.h"
|
||||||
|
|
||||||
|
#define LOG_TAG_CONST APP
|
||||||
|
#define LOG_TAG "[JB_PROD]"
|
||||||
|
#define LOG_INFO_ENABLE
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════════════════════
|
/* ════════════════════════════════════════════════════════════════
|
||||||
* 全局变量
|
* 全局变量
|
||||||
@@ -72,39 +83,24 @@ void userInit(void)
|
|||||||
* ════════════════════════════════════════════════════════════════ */
|
* ════════════════════════════════════════════════════════════════ */
|
||||||
void userHandle(void)
|
void userHandle(void)
|
||||||
{
|
{
|
||||||
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */
|
/* DP1 由 app_function_poll 同步作业态;DP18 由 app_runrec 灌。
|
||||||
// gDevData.power = read_sensor(); /* DP0 总开关 */
|
* 其余可上报字段在写路径 / VM 加载已就位,这里不再刷传感器。 */
|
||||||
// gDevData.spray_switch = read_sensor(); /* DP1 喷淋开关 */
|
|
||||||
// gDevData.mode = read_sensor(); /* DP2 模式 */
|
|
||||||
// gDevData.once_spray_time = read_sensor(); /* DP3 单次喷淋时长 */
|
|
||||||
// gDevData.continuous_spray_time = read_sensor(); /* DP4 连续喷淋时长 */
|
|
||||||
// memcpy(gDevData.spray_config, sensor_read(), 8); /* DP7 喷淋配置 */
|
|
||||||
// gDevData.linkage_delete = read_sensor(); /* DP8 联动删除 */
|
|
||||||
// memcpy(gDevData.linkage_config, sensor_read(), 20); /* DP9 联动配置 */
|
|
||||||
// gDevData.spray_plan_en = read_sensor(); /* DP10 喷淋计划总开关 */
|
|
||||||
// memcpy(gDevData.spray_plan1, sensor_read(), 15); /* DP11 喷淋计划1 */
|
|
||||||
// memcpy(gDevData.time_config2, sensor_read(), 15); /* DP12 喷淋计划2 */
|
|
||||||
// memcpy(gDevData.time_config3, sensor_read(), 15); /* DP13 喷淋计划3 */
|
|
||||||
// memcpy(gDevData.time_config4, sensor_read(), 15); /* DP14 喷淋计划4 */
|
|
||||||
// memcpy(gDevData.time_config5, sensor_read(), 15); /* DP15 喷淋计划5 */
|
|
||||||
// memcpy(gDevData.time_config6, sensor_read(), 15); /* DP16 喷淋计划6 */
|
|
||||||
// memcpy(gDevData.time_config7, sensor_read(), 15); /* DP17 喷淋计划7 */
|
|
||||||
// memcpy(gDevData.run_record, sensor_read(), 124); /* DP18 运行记录 */
|
|
||||||
// gDevData.fault_en = read_sensor(); /* DP20 设备故障使能 */
|
|
||||||
// gDevData.fault_code = read_sensor(); /* DP21 设备故障 */
|
|
||||||
// gDevData.alarm_en = read_sensor(); /* DP22 设备告警使能 */
|
|
||||||
// gDevData.alarm_code = read_sensor(); /* DP23 设备告警 */
|
|
||||||
// gDevData.reserva_dp1 = read_sensor(); /* DP25 预留DP1 */
|
|
||||||
// gDevData.reserva_dp2 = read_sensor(); /* DP26 预留DP2 */
|
|
||||||
// gDevData.reserva_dp3 = read_sensor(); /* DP27 预留DP3 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════════════════════
|
/* ════════════════════════════════════════════════════════════════
|
||||||
* jbEventProcess — 0x03 APP 控制事件处理(下行 / DP 赋值方向)
|
* jbEventProcess — 0x03 APP 控制事件处理(下行 / DP 赋值方向)
|
||||||
*
|
*
|
||||||
* 协议层自动解包 0x03 帧后调用本函数。
|
* 只做两件事:DP 写入 gDevData(合法时)+ 转调 app_* 模块。
|
||||||
* info->dp_ids[] = 被控制的 DP ID 列表
|
* 业务算法一律不在这里写(规格书硬约束 2)。
|
||||||
* ctrl 已填入下发的值;复制到 gDevData 并执行动作
|
*
|
||||||
|
* P1 行为(规格书第 9 / 16 章):
|
||||||
|
* - 写忽略:DP0 / 2 / 3 / 4 / 5 / 19(不拷贝 gDevData,不动作,不回显)
|
||||||
|
* - DP1:作业开关,转 app_spray(引擎内部幂等:已喷保持,已停保持关)
|
||||||
|
* - DP6:WO 收下(P2 联动用),不上报(jbPackDps 只打 RO/RW)
|
||||||
|
* - DP7:大端校验(时长 0 或次数 0 整包丢弃)→ 存 VM → 立刻重开一轮
|
||||||
|
* - DP8:脉冲删除(清 20B + 清回 0);DP9:收下存 VM(P2 联动引擎)
|
||||||
|
* - DP10~17 / DP20 / DP22:收下 + 存 VM
|
||||||
|
* - DP21 / 23:保持 0(无缺水检测);DP24:出厂重置(第 15 章)
|
||||||
* ════════════════════════════════════════════════════════════════ */
|
* ════════════════════════════════════════════════════════════════ */
|
||||||
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||||
{
|
{
|
||||||
@@ -117,216 +113,150 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
|||||||
{
|
{
|
||||||
switch (info->dp_ids[i])
|
switch (info->dp_ids[i])
|
||||||
{
|
{
|
||||||
|
/* ── 写忽略:DP0 / 2 / 3 / 4 / 5 / 19(规格书第 9 章)── */
|
||||||
/* ── DP0 总开关 (bool) ── */
|
|
||||||
case DP_ID_POWER:
|
case DP_ID_POWER:
|
||||||
if (ctrl->power)
|
case DP_ID_MODE:
|
||||||
{
|
case DP_ID_ONCE_SPRAY_TIME:
|
||||||
gDevData.power = 1;
|
case DP_ID_CONTINUOUS_SPRAY_TIME:
|
||||||
// TODO: 开 — 执行动作
|
case DP_ID_CURRENT_TEMP:
|
||||||
}
|
case DP_ID_HEARTBEAT:
|
||||||
else
|
|
||||||
{
|
|
||||||
gDevData.power = 0;
|
|
||||||
// TODO: 关 — 执行动作
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP1 喷淋开关 (bool) ── */
|
/* ── DP1 喷淋开关 (bool):作业态开关(请求在 20ms tick 消费)── */
|
||||||
case DP_ID_SPRAY_SWITCH:
|
case DP_ID_SPRAY_SWITCH:
|
||||||
if (ctrl->spray_switch)
|
if (ctrl->spray_switch)
|
||||||
{
|
{
|
||||||
gDevData.spray_switch = 1;
|
app_arbiter_manual_intervene(); /* 13 章:DP1=1 也是手动抢权 */
|
||||||
// TODO: 开 — 执行动作
|
app_spray_req_start(); /* 空闲才开;已在喷保持当前轮 */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gDevData.spray_switch = 0;
|
app_spray_req_stop();
|
||||||
// TODO: 关 — 执行动作
|
app_arbiter_manual_intervene(); /* 13 章:手动停作废联动 */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP2 模式 (enum) ── */
|
/* ── DP6 当前湿度 (uint8, WO):已连接=服务器下发 → 喂联动 ── */
|
||||||
case DP_ID_MODE:
|
|
||||||
gDevData.mode = ctrl->mode;
|
|
||||||
switch (ctrl->mode)
|
|
||||||
{
|
|
||||||
case MODE_0: /* 关闭模式 */
|
|
||||||
// TODO: 处理 关闭模式
|
|
||||||
break;
|
|
||||||
case MODE_1: /* 单次喷淋 */
|
|
||||||
// TODO: 处理 单次喷淋
|
|
||||||
break;
|
|
||||||
case MODE_2: /* 连续喷淋 */
|
|
||||||
// TODO: 处理 连续喷淋
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* ── DP3 单次喷淋时长 (uint8) ── */
|
|
||||||
case DP_ID_ONCE_SPRAY_TIME:
|
|
||||||
gDevData.once_spray_time = ctrl->once_spray_time;
|
|
||||||
// TODO: 根据 ctrl->once_spray_time 执行动作
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* ── DP4 连续喷淋时长 (uint8) ── */
|
|
||||||
case DP_ID_CONTINUOUS_SPRAY_TIME:
|
|
||||||
gDevData.continuous_spray_time = ctrl->continuous_spray_time;
|
|
||||||
// TODO: 根据 ctrl->continuous_spray_time 执行动作
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* ── DP5 当前温度 (float, scale=0.1) ── */
|
|
||||||
case DP_ID_CURRENT_TEMP:
|
|
||||||
gDevData.current_temp = ctrl->current_temp;
|
|
||||||
// TODO: 根据 ctrl->current_temp 执行动作 (已是浮点物理量)
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* ── DP6 当前湿度 (uint8) ── */
|
|
||||||
case DP_ID_CURRENT_HUMI:
|
case DP_ID_CURRENT_HUMI:
|
||||||
gDevData.current_humi = ctrl->current_humi;
|
gDevData.current_humi = ctrl->current_humi;
|
||||||
// TODO: 根据 ctrl->current_humi 执行动作
|
app_linkage_set_connected_humi(ctrl->current_humi);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP7 喷淋配置 (raw) ── */
|
/* ── DP7 喷淋配置 (raw 8B):大端,非法整包丢弃(规格书 10.2)── */
|
||||||
case DP_ID_SPRAY_CONFIG:
|
case DP_ID_SPRAY_CONFIG:
|
||||||
memcpy(gDevData.spray_config, ctrl->spray_config, 8);
|
{
|
||||||
// TODO: 根据 gDevData.spray_config 执行动作 (长度 8 字节)
|
uint16_t dur = (uint16_t)(((uint16_t)ctrl->spray_config[0] << 8)
|
||||||
break;
|
| ctrl->spray_config[1]);
|
||||||
|
uint8_t cnt = ctrl->spray_config[2];
|
||||||
|
|
||||||
/* ── DP8 联动删除 (bool) ── */
|
if ((dur == 0) || (cnt == 0))
|
||||||
|
{
|
||||||
|
/* 非法包:不改 RAM / VM,不开喷,保持上一份有效配置 */
|
||||||
|
log_info("dp7 invalid (dur=%u cnt=%u), dropped\n",
|
||||||
|
(unsigned)dur, (unsigned)cnt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(gDevData.spray_config, ctrl->spray_config, 8);
|
||||||
|
app_nvm_mark_dirty(); /* 存 VM(500ms 防抖) */
|
||||||
|
app_arbiter_manual_intervene(); /* 手动抢权:原联动作废(13 章) */
|
||||||
|
app_spray_req_restart(); /* 立刻按新参数开一轮(中止旧轮) */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── DP8 联动删除 (bool):脉冲清 20B 后清回 0(规格书 11.4)── */
|
||||||
case DP_ID_LINKAGE_DELETE:
|
case DP_ID_LINKAGE_DELETE:
|
||||||
if (ctrl->linkage_delete)
|
if (ctrl->linkage_delete)
|
||||||
{
|
{
|
||||||
gDevData.linkage_delete = 1;
|
app_linkage_delete(); /* 清 RAM+VM 20B;联动占着则停 */
|
||||||
// TODO: 开 — 执行动作
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gDevData.linkage_delete = 0;
|
|
||||||
// TODO: 关 — 执行动作
|
|
||||||
}
|
}
|
||||||
|
gDevData.linkage_delete = 0; /* 无动作也走脉冲:清回 0 再上报 */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP9 联动配置 (raw) ── */
|
/* ── DP9 联动配置 (raw 20B):存 VM + 清作废 + 立刻重判(11.5)── */
|
||||||
case DP_ID_LINKAGE_CONFIG:
|
case DP_ID_LINKAGE_CONFIG:
|
||||||
memcpy(gDevData.linkage_config, ctrl->linkage_config, 20);
|
memcpy(gDevData.linkage_config, ctrl->linkage_config, 20);
|
||||||
// TODO: 根据 gDevData.linkage_config 执行动作 (长度 20 字节)
|
app_linkage_set_config();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP10 喷淋计划总开关 (bool) ── */
|
/* ── DP10 喷淋计划总开关 (bool):存 VM;=0 停新触发不拆当前轮 ── */
|
||||||
case DP_ID_SPRAY_PLAN_EN:
|
case DP_ID_SPRAY_PLAN_EN:
|
||||||
if (ctrl->spray_plan_en)
|
gDevData.spray_plan_en = ctrl->spray_plan_en ? 1 : 0;
|
||||||
{
|
app_timer_set_en(gDevData.spray_plan_en);
|
||||||
gDevData.spray_plan_en = 1;
|
app_nvm_mark_dirty();
|
||||||
// TODO: 开 — 执行动作
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gDevData.spray_plan_en = 0;
|
|
||||||
// TODO: 关 — 执行动作
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP11 喷淋计划1 (raw) ── */
|
/* ── DP11~17 计划 (raw 15B):收下存 VM(到点触发在引擎)── */
|
||||||
case DP_ID_SPRAY_PLAN1:
|
case DP_ID_SPRAY_PLAN1:
|
||||||
memcpy(gDevData.spray_plan1, ctrl->spray_plan1, 15);
|
memcpy(gDevData.spray_plan1, ctrl->spray_plan1, 15);
|
||||||
// TODO: 根据 gDevData.spray_plan1 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP12 喷淋计划2 (raw) ── */
|
|
||||||
case DP_ID_TIME_CONFIG2:
|
case DP_ID_TIME_CONFIG2:
|
||||||
memcpy(gDevData.time_config2, ctrl->time_config2, 15);
|
memcpy(gDevData.time_config2, ctrl->time_config2, 15);
|
||||||
// TODO: 根据 gDevData.time_config2 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP13 喷淋计划3 (raw) ── */
|
|
||||||
case DP_ID_TIME_CONFIG3:
|
case DP_ID_TIME_CONFIG3:
|
||||||
memcpy(gDevData.time_config3, ctrl->time_config3, 15);
|
memcpy(gDevData.time_config3, ctrl->time_config3, 15);
|
||||||
// TODO: 根据 gDevData.time_config3 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP14 喷淋计划4 (raw) ── */
|
|
||||||
case DP_ID_TIME_CONFIG4:
|
case DP_ID_TIME_CONFIG4:
|
||||||
memcpy(gDevData.time_config4, ctrl->time_config4, 15);
|
memcpy(gDevData.time_config4, ctrl->time_config4, 15);
|
||||||
// TODO: 根据 gDevData.time_config4 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP15 喷淋计划5 (raw) ── */
|
|
||||||
case DP_ID_TIME_CONFIG5:
|
case DP_ID_TIME_CONFIG5:
|
||||||
memcpy(gDevData.time_config5, ctrl->time_config5, 15);
|
memcpy(gDevData.time_config5, ctrl->time_config5, 15);
|
||||||
// TODO: 根据 gDevData.time_config5 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP16 喷淋计划6 (raw) ── */
|
|
||||||
case DP_ID_TIME_CONFIG6:
|
case DP_ID_TIME_CONFIG6:
|
||||||
memcpy(gDevData.time_config6, ctrl->time_config6, 15);
|
memcpy(gDevData.time_config6, ctrl->time_config6, 15);
|
||||||
// TODO: 根据 gDevData.time_config6 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP17 喷淋计划7 (raw) ── */
|
|
||||||
case DP_ID_TIME_CONFIG7:
|
case DP_ID_TIME_CONFIG7:
|
||||||
memcpy(gDevData.time_config7, ctrl->time_config7, 15);
|
memcpy(gDevData.time_config7, ctrl->time_config7, 15);
|
||||||
// TODO: 根据 gDevData.time_config7 执行动作 (长度 15 字节)
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP19 心跳 (bool) ── */
|
/* ── 保持 0:DP21 / DP23(无缺水检测,规格书第 9 章)── */
|
||||||
case DP_ID_HEARTBEAT:
|
case DP_ID_FAULT_CODE:
|
||||||
if (ctrl->heartbeat)
|
case DP_ID_ALARM_CODE:
|
||||||
{
|
|
||||||
gDevData.heartbeat = 1;
|
|
||||||
// TODO: 开 — 执行动作
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gDevData.heartbeat = 0;
|
|
||||||
// TODO: 关 — 执行动作
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP20 设备故障使能 (uint8) ── */
|
/* ── DP20 / DP22:原样收下存 VM(本版不做位级过滤)── */
|
||||||
case DP_ID_FAULT_EN:
|
case DP_ID_FAULT_EN:
|
||||||
gDevData.fault_en = ctrl->fault_en;
|
gDevData.fault_en = ctrl->fault_en;
|
||||||
// TODO: 根据 ctrl->fault_en 执行动作
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP22 设备告警使能 (uint8) ── */
|
|
||||||
case DP_ID_ALARM_EN:
|
case DP_ID_ALARM_EN:
|
||||||
gDevData.alarm_en = ctrl->alarm_en;
|
gDevData.alarm_en = ctrl->alarm_en;
|
||||||
// TODO: 根据 ctrl->alarm_en 执行动作
|
app_nvm_mark_dirty();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP24 恢复出厂设置 (bool) ── */
|
/* ── DP24 恢复出厂 (bool):清业务 VM 留配对(规格书 15 章)── */
|
||||||
case DP_ID_FACTORY_RESET:
|
case DP_ID_FACTORY_RESET:
|
||||||
if (ctrl->factory_reset)
|
if (ctrl->factory_reset)
|
||||||
{
|
{
|
||||||
gDevData.factory_reset = 1;
|
app_function_factory_reset();
|
||||||
// TODO: 开 — 执行动作
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gDevData.factory_reset = 0;
|
|
||||||
// TODO: 关 — 执行动作
|
|
||||||
}
|
}
|
||||||
|
gDevData.factory_reset = 0; /* WO 脉冲,不清会一直挂在 RAM */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP25 预留DP1 (uint32) ── */
|
/* ── DP25~27 预留 (uint32):存 RAM,无动作 ── */
|
||||||
case DP_ID_RESERVA_DP1:
|
case DP_ID_RESERVA_DP1:
|
||||||
gDevData.reserva_dp1 = ctrl->reserva_dp1;
|
gDevData.reserva_dp1 = ctrl->reserva_dp1;
|
||||||
// TODO: 根据 ctrl->reserva_dp1 执行动作
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP26 预留DP2 (uint32) ── */
|
|
||||||
case DP_ID_RESERVA_DP2:
|
case DP_ID_RESERVA_DP2:
|
||||||
gDevData.reserva_dp2 = ctrl->reserva_dp2;
|
gDevData.reserva_dp2 = ctrl->reserva_dp2;
|
||||||
// TODO: 根据 ctrl->reserva_dp2 执行动作
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* ── DP27 预留DP3 (uint32) ── */
|
|
||||||
case DP_ID_RESERVA_DP3:
|
case DP_ID_RESERVA_DP3:
|
||||||
gDevData.reserva_dp3 = ctrl->reserva_dp3;
|
gDevData.reserva_dp3 = ctrl->reserva_dp3;
|
||||||
// TODO: 根据 ctrl->reserva_dp3 执行动作
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -340,14 +270,11 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
|||||||
* 返回 0=接受升级(协议层应答 JB_OK + MaxDataLen)
|
* 返回 0=接受升级(协议层应答 JB_OK + MaxDataLen)
|
||||||
* 非 0=拒绝,返回值即错误码(JB_OTA_*)
|
* 非 0=拒绝,返回值即错误码(JB_OTA_*)
|
||||||
*
|
*
|
||||||
* ⚠ SOC 侧 OTA 尚未实现:当前仅应答,不执行任何升级动作。 */
|
* ⚠ SOC 侧协议 OTA 本版未实现:禁止假装升级成功。12s 走杰理双备份。 */
|
||||||
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
|
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
|
||||||
{
|
{
|
||||||
(void)fwVersion; (void)fwLength; (void)fwCrc32;
|
(void)fwVersion; (void)fwLength; (void)fwCrc32;
|
||||||
// TODO: 检查 Flash 空间与版本,决定是否接收升级
|
return JB_OTA_STATE_ERR;
|
||||||
// 允许: 返回 JB_OK(协议层应答 JB_OK + MaxDataLen)
|
|
||||||
// 拒绝: 返回对应 JB_OTA_* 错误码(如 JB_OTA_VER_LOW / JB_OTA_SPACE_ERR)
|
|
||||||
return JB_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 0x32 OTA 数据(含分片序号)*/
|
/* 0x32 OTA 数据(含分片序号)*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* @file usr_jb_main.c
|
* @file usr_jb_main.c
|
||||||
* @brief CBR2 业务组合根:只编排调用顺序(P0 双键/双灯/双脚泵)
|
* @brief CBR2 业务组合根:只编排调用顺序(20ms tick + 500ms loop)
|
||||||
* @author cyWu <1917507415@qq.com>
|
* @author cyWu <1917507415@qq.com>
|
||||||
* @date 2026.09.09
|
* @date 2026.09.09
|
||||||
* @version V1.0.0
|
* @version V1.0.0
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
*
|
*
|
||||||
* @note jb_product.c 不写业务。20ms tick 禁止写 VM / 切 BLE;
|
* @note jb_product.c 不写业务。20ms tick 禁止写 VM / 切 BLE;
|
||||||
* 配对、OTA、落盘在 500ms usr_timer_loop(任务上下文)。
|
* 配对、OTA、落盘在 500ms usr_timer_loop(任务上下文)。
|
||||||
* P0 关闭 LINKAGE/TIMER/TH_SCAN/RUNREC/NVM(board_pin.h)。
|
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include "system/includes.h"
|
#include "system/includes.h"
|
||||||
@@ -30,6 +29,12 @@
|
|||||||
#include "app_led.h"
|
#include "app_led.h"
|
||||||
#include "app_pump.h"
|
#include "app_pump.h"
|
||||||
#include "app_spray.h"
|
#include "app_spray.h"
|
||||||
|
#include "app_nvm.h"
|
||||||
|
#include "app_arbiter.h"
|
||||||
|
#include "app_th_scan.h"
|
||||||
|
#include "app_linkage.h"
|
||||||
|
#include "app_timer_task.h"
|
||||||
|
#include "app_runrec.h"
|
||||||
#include "key_manager.h"
|
#include "key_manager.h"
|
||||||
|
|
||||||
#define LOG_TAG_CONST APP
|
#define LOG_TAG_CONST APP
|
||||||
@@ -72,6 +77,8 @@ static void usr_timer_tick(void *priv)
|
|||||||
user_key_handle(); /* 派发按键回调:喷淋启停 / 配对提示 / OTA 标志 */
|
user_key_handle(); /* 派发按键回调:喷淋启停 / 配对提示 / OTA 标志 */
|
||||||
app_spray_poll(dt); /* 作业引擎:时长/间隔倒数,到点切泵(唯一切泵者) */
|
app_spray_poll(dt); /* 作业引擎:时长/间隔倒数,到点切泵(唯一切泵者) */
|
||||||
app_function_poll(); /* 同步 DP1 = 作业态(含间隔等待) */
|
app_function_poll(); /* 同步 DP1 = 作业态(含间隔等待) */
|
||||||
|
app_nvm_tick(dt); /* VM 防抖计时(只计时,写入在任务上下文) */
|
||||||
|
app_runrec_tick(dt); /* 泵实际导通折秒(只累计,写入在任务上下文) */
|
||||||
app_led_run(dt); /* 绿=作业态、红=配对态;内部推进配对会话 */
|
app_led_run(dt); /* 绿=作业态、红=配对态;内部推进配对会话 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +108,16 @@ void usr_jb_init(void)
|
|||||||
jbInit();
|
jbInit();
|
||||||
userInit(); /* DP7 默认 10s×3+3s 已就位 */
|
userInit(); /* DP7 默认 10s×3+3s 已就位 */
|
||||||
|
|
||||||
|
app_nvm_init();
|
||||||
|
app_nvm_load(); /* VM 有效则覆盖 DP7/计划/plan_en;DP1/泵不恢复(不自动喷) */
|
||||||
|
|
||||||
app_pump_init(); /* 泵两脚低(bsp_hw 已配),状态清零 */
|
app_pump_init(); /* 泵两脚低(bsp_hw 已配),状态清零 */
|
||||||
app_spray_init(); /* 作业引擎:空闲不喷 */
|
app_spray_init(); /* 作业引擎:空闲不喷 */
|
||||||
|
app_th_scan_init();
|
||||||
|
app_linkage_init(); /* 联动引擎(湿度回差,P2) */
|
||||||
|
app_timer_init(); /* 计划引擎(到点触发一轮,P2) */
|
||||||
|
app_runrec_init(); /* 31 天运行记录(P2) */
|
||||||
|
app_arbiter_init();
|
||||||
app_function_init();
|
app_function_init();
|
||||||
|
|
||||||
user_key_init(); /* 双键:PB1 喷淋短按 / PA0 配网 5s-12s */
|
user_key_init(); /* 双键:PB1 喷淋短按 / PA0 配网 5s-12s */
|
||||||
@@ -113,9 +128,11 @@ void usr_jb_init(void)
|
|||||||
/* priority=1 常驻注册,永不重挂(5V 常电不需要低功耗) */
|
/* priority=1 常驻注册,永不重挂(5V 常电不需要低功耗) */
|
||||||
usr_timer_add(NULL, usr_timer_tick, USR_TIMER_IDLE_MS, 1);
|
usr_timer_add(NULL, usr_timer_tick, USR_TIMER_IDLE_MS, 1);
|
||||||
|
|
||||||
log_info("sprayer P0 up: led=%d key=%d pump=%d spray=%d\n",
|
log_info("sprayer up: led=%d key=%d pump=%d spray=%d lkg=%d tmr=%d scan=%d rec=%d nvm=%d\n",
|
||||||
BOARD_LED_ENABLE, BOARD_KEY_ENABLE,
|
BOARD_LED_ENABLE, BOARD_KEY_ENABLE,
|
||||||
BOARD_PUMP_ENABLE, BOARD_SPRAY_ENABLE);
|
BOARD_PUMP_ENABLE, BOARD_SPRAY_ENABLE,
|
||||||
|
BOARD_LINKAGE_ENABLE, BOARD_TIMER_ENABLE,
|
||||||
|
BOARD_TH_SCAN_ENABLE, BOARD_RUNREC_ENABLE, BOARD_NVM_ENABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,5 +154,10 @@ void usr_timer_loop(void)
|
|||||||
{
|
{
|
||||||
userHandle();
|
userHandle();
|
||||||
jbProtocolHandle(&gDevData);
|
jbProtocolHandle(&gDevData);
|
||||||
app_function_loop(); /* 配对/OTA 动作落地(app_defer 消费) */
|
app_arbiter_poll(); /* 轮结束收口:单次自毁/出队/联动补判 */
|
||||||
|
app_th_scan_tick(); /* 断连已配对:重连广播/扫描 500ms 交替 */
|
||||||
|
app_linkage_run(500); /* 湿度回差开停 */
|
||||||
|
app_timer_run(500); /* 计划到点入队 + 空闲起轮 */
|
||||||
|
app_runrec_rotate_if_needed(); /* 收 pending 秒/灌 DP18/跨天旋转 */
|
||||||
|
app_function_loop(); /* VM 防抖落盘 + 配对/OTA 落地 */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,39 @@ uint8_t usr_ble_is_connected(void)
|
|||||||
return (usr_get_conn_service() != 0) ? 1u : 0u;
|
return (usr_get_conn_service() != 0) ? 1u : 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 广播门控:扫描分时期间暂停广播发送(见 app_th_scan),仅门控不断连 */
|
||||||
|
static u8 s_adv_paused = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 暂停/恢复广播发送(扫描分时射频仲裁,见 app_th_scan)
|
||||||
|
* @note adv data 仍注册在协议栈,恢复时续发无需重新编码;仅门控发送,
|
||||||
|
* 不动模块使能位,因此已连接时误调不会断开已有链路。
|
||||||
|
* @param pause 1=暂停(扫描期让出射频),0=恢复
|
||||||
|
* @return 无
|
||||||
|
*/
|
||||||
|
void usr_le_set_adv_pause(u8 pause)
|
||||||
|
{
|
||||||
|
extern void bt_ble_adv_enable(u8 enable);
|
||||||
|
|
||||||
|
if (pause == s_adv_paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pause && usr_ble_is_connected()) {
|
||||||
|
return; /* 扫描只允许断连态;已连接保持广播 */
|
||||||
|
}
|
||||||
|
s_adv_paused = pause;
|
||||||
|
bt_ble_adv_enable(pause ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询广播是否处于暂停态
|
||||||
|
* @return 1=暂停中(扫描期),0=正常广播
|
||||||
|
*/
|
||||||
|
u8 usr_le_adv_is_paused(void)
|
||||||
|
{
|
||||||
|
return s_adv_paused;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从 VM 读取配对信息到 usr_auth_data
|
* @brief 从 VM 读取配对信息到 usr_auth_data
|
||||||
* @note 失败则清零,并将 paired_flag 置 0 写回
|
* @note 失败则清零,并将 paired_flag 置 0 写回
|
||||||
@@ -230,6 +263,12 @@ void usr_ble_adv_updata(void)
|
|||||||
const u16 slow_ticks = (u16)((BOARD_BLE_SLOW_ADV_SEC) * (1000u / 500u));
|
const u16 slow_ticks = (u16)((BOARD_BLE_SLOW_ADV_SEC) * (1000u / 500u));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (usr_le_adv_is_paused()) {
|
||||||
|
/* 扫描期(app_th_scan 分时):不刷广播、不切快慢,只跑协议周期 */
|
||||||
|
usr_timer_loop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (usr_var.usr_adv_flag)
|
if (usr_var.usr_adv_flag)
|
||||||
{
|
{
|
||||||
ble_trans_module_enable(0);
|
ble_trans_module_enable(0);
|
||||||
|
|||||||
@@ -43,12 +43,31 @@ void usr_clear_pair_info(void);
|
|||||||
*/
|
*/
|
||||||
uint8_t usr_get_pair_flag(void);
|
uint8_t usr_get_pair_flag(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询 BLE 是否已连接
|
||||||
|
* @return 1=已连接,0=未连接
|
||||||
|
*/
|
||||||
|
uint8_t usr_ble_is_connected(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 启动应用侧 OTA 流程
|
* @brief 启动应用侧 OTA 流程
|
||||||
* @note 置位 usr_goto_updata,并创建 1s 周期定时器驱动断连与 OTA 广播切换
|
* @note 置位 usr_goto_updata,并创建 1s 周期定时器驱动断连与 OTA 广播切换
|
||||||
*/
|
*/
|
||||||
void usr_app_ota_init(void);
|
void usr_app_ota_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 暂停/恢复广播发送(扫描分时射频仲裁,见 app_th_scan)
|
||||||
|
* @note 仅门控发送,不断开连接;恢复后按注册数据续发,无需重新编码
|
||||||
|
* @param pause 1=暂停(扫描期让出射频),0=恢复
|
||||||
|
*/
|
||||||
|
void usr_le_set_adv_pause(uint8_t pause);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询广播是否处于暂停态
|
||||||
|
* @return 1=暂停中(扫描期),0=正常广播
|
||||||
|
*/
|
||||||
|
uint8_t usr_le_adv_is_paused(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+11
-10
@@ -7,8 +7,7 @@
|
|||||||
*
|
*
|
||||||
* @note 调试 UART0_TX=PC5 在 board_jl701n_demo_cfg.h。
|
* @note 调试 UART0_TX=PC5 在 board_jl701n_demo_cfg.h。
|
||||||
* 泵脚成对操作,禁止只开 PC2 或 PA6(PC2 供 5V,PA6 导通泵)。
|
* 泵脚成对操作,禁止只开 PC2 或 PA6(PC2 供 5V,PA6 导通泵)。
|
||||||
* 本机为 5V 常电、无电池;红灯 PA8 / 绿灯 PA7(低亮),
|
* 本机为 5V 常电、无电池;灯脚与加热垫相同:PA8 绿 / PA7 红(低亮)。
|
||||||
* 与加热垫(PA8 绿 / PA7 红)相反,禁止照抄加热垫灯脚。
|
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifndef __BOARD_PIN_H__
|
#ifndef __BOARD_PIN_H__
|
||||||
@@ -21,15 +20,15 @@
|
|||||||
/* 0 时对应模块 init/run 直接 return,GPIO 不配,日志打一次 xxx disabled */
|
/* 0 时对应模块 init/run 直接 return,GPIO 不配,日志打一次 xxx disabled */
|
||||||
/*============================================================================*/
|
/*============================================================================*/
|
||||||
|
|
||||||
#define BOARD_LED_ENABLE 1 /**< 1=红绿状态 LED(PA8 红 / PA7 绿,低亮) */
|
#define BOARD_LED_ENABLE 1 /**< 1=红绿状态 LED(PA7 红 / PA8 绿,低亮) */
|
||||||
#define BOARD_KEY_ENABLE 1 /**< 1=双键(PB1 喷淋 / PA0 配网,低按下) */
|
#define BOARD_KEY_ENABLE 1 /**< 1=双键(PB1 喷淋 / PA0 配网,低按下) */
|
||||||
#define BOARD_PUMP_ENABLE 1 /**< 1=水泵双脚(PC2 供 5V / PA6 导通,高有效) */
|
#define BOARD_PUMP_ENABLE 1 /**< 1=水泵双脚(PC2 供 5V / PA6 导通,高有效) */
|
||||||
#define BOARD_SPRAY_ENABLE 1 /* 喷淋作业引擎;=0 时键/DP 不转泵 */
|
#define BOARD_SPRAY_ENABLE 1 /* 喷淋作业引擎;=0 时键/DP 不转泵 */
|
||||||
#define BOARD_LINKAGE_ENABLE 0 /* 湿度联动(第 11 章);P2 开 */
|
#define BOARD_LINKAGE_ENABLE 1 /* 湿度联动(第 11 章);P2 开 */
|
||||||
#define BOARD_TIMER_ENABLE 0 /* 到点喷淋计划(第 12 章);P2 开 */
|
#define BOARD_TIMER_ENABLE 1 /* 到点喷淋计划(第 12 章);P2 开 */
|
||||||
#define BOARD_TH_SCAN_ENABLE 0 /* 断连扫描风扇广播(第 8 章);P2 开 */
|
#define BOARD_TH_SCAN_ENABLE 1 /* 断连扫描风扇广播(第 8 章);P2 开 */
|
||||||
#define BOARD_RUNREC_ENABLE 0 /* 31 天运行记录(第 14 章);P2 开 */
|
#define BOARD_RUNREC_ENABLE 1 /* 31 天运行记录(第 14 章);P2 开 */
|
||||||
#define BOARD_NVM_ENABLE 0 /* VM 持久化(第 14 章);P1/P2 开 */
|
#define BOARD_NVM_ENABLE 1 /* VM 持久化(第 14 章);P1/P2 开 */
|
||||||
|
|
||||||
/*============================================================================*/
|
/*============================================================================*/
|
||||||
/* GPIO */
|
/* GPIO */
|
||||||
@@ -38,8 +37,8 @@
|
|||||||
#define BOARD_UART_TX_PIN IO_PORTC_05 /**< 调试 UART TX(cfg: TCFG_UART0_TX_PORT) */
|
#define BOARD_UART_TX_PIN IO_PORTC_05 /**< 调试 UART TX(cfg: TCFG_UART0_TX_PORT) */
|
||||||
#define BOARD_KEY_SPRAY_PIN IO_PORTB_01 /**< 喷淋键,低电平按下,只短按 */
|
#define BOARD_KEY_SPRAY_PIN IO_PORTB_01 /**< 喷淋键,低电平按下,只短按 */
|
||||||
#define BOARD_KEY_PAIR_PIN IO_PORTA_00 /**< 配网键,低电平按下,5s / 12s */
|
#define BOARD_KEY_PAIR_PIN IO_PORTA_00 /**< 配网键,低电平按下,5s / 12s */
|
||||||
#define BOARD_LED_RED_PIN IO_PORTA_08 /**< 红灯,低电平亮(与加热垫灯脚相反) */
|
#define BOARD_LED_RED_PIN IO_PORTA_07 /**< 红灯,低电平亮(与加热垫相同) */
|
||||||
#define BOARD_LED_GREEN_PIN IO_PORTA_07 /**< 绿灯,低电平亮(与加热垫灯脚相反) */
|
#define BOARD_LED_GREEN_PIN IO_PORTA_08 /**< 绿灯,低电平亮(与加热垫相同) */
|
||||||
#define BOARD_PUMP_PWR_PIN IO_PORTC_02 /**< 泵总供电:高把 5V 导到泵 VCC */
|
#define BOARD_PUMP_PWR_PIN IO_PORTC_02 /**< 泵总供电:高把 5V 导到泵 VCC */
|
||||||
#define BOARD_PUMP_SW_PIN IO_PORTA_06 /**< 泵开关:高导通泵;须与 PC2 同开同关 */
|
#define BOARD_PUMP_SW_PIN IO_PORTA_06 /**< 泵开关:高导通泵;须与 PC2 同开同关 */
|
||||||
|
|
||||||
@@ -72,6 +71,8 @@
|
|||||||
#define BOARD_LED_SPRAY_OFF_MS 500 /**< 作业绿灯闪:灭 */
|
#define BOARD_LED_SPRAY_OFF_MS 500 /**< 作业绿灯闪:灭 */
|
||||||
#define BOARD_LED_FAST_ON_MS 100 /**< 配网红灯快闪:亮 */
|
#define BOARD_LED_FAST_ON_MS 100 /**< 配网红灯快闪:亮 */
|
||||||
#define BOARD_LED_FAST_OFF_MS 100 /**< 配网红灯快闪:灭 */
|
#define BOARD_LED_FAST_OFF_MS 100 /**< 配网红灯快闪:灭 */
|
||||||
|
#define BOARD_LED_RECONN_ON_MS 500 /**< 已配对未连接/重连:红灯慢闪亮 */
|
||||||
|
#define BOARD_LED_RECONN_OFF_MS 500 /**< 已配对未连接/重连:红灯慢闪灭 */
|
||||||
#define BOARD_LED_OTA_ON_MS 200 /**< OTA 红绿交替半周期:亮 */
|
#define BOARD_LED_OTA_ON_MS 200 /**< OTA 红绿交替半周期:亮 */
|
||||||
#define BOARD_LED_OTA_OFF_MS 200 /**< OTA 红绿交替半周期:灭 */
|
#define BOARD_LED_OTA_OFF_MS 200 /**< OTA 红绿交替半周期:灭 */
|
||||||
#define BOARD_LED_PAIR_FAIL_MS 200 /**< 配对失败红绿交替半周期 */
|
#define BOARD_LED_PAIR_FAIL_MS 200 /**< 配对失败红绿交替半周期 */
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ BspHw_Ret_t bsp_hw_get_status(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 红灯开关(PA8,低电平亮)
|
* @brief 红灯开关(PA7,低电平亮)
|
||||||
* @param on 1=亮,0=灭
|
* @param on 1=亮,0=灭
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +127,7 @@ void bsp_led_red_set(uint8_t on)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绿灯开关(PA7,低电平亮)
|
* @brief 绿灯开关(PA8,低电平亮)
|
||||||
* @param on 1=亮,0=灭
|
* @param on 1=亮,0=灭
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ BspHw_Ret_t bsp_hw_get_status(void);
|
|||||||
/* ═══ 红绿 LED(低电平亮;on 传"亮"语义,内部按 BOARD_LED_ON_LEVEL 取反)═══ */
|
/* ═══ 红绿 LED(低电平亮;on 传"亮"语义,内部按 BOARD_LED_ON_LEVEL 取反)═══ */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 红灯开关(PA8,低亮)
|
* @brief 红灯开关(PA7,低亮)
|
||||||
* @param on 1=亮,0=灭
|
* @param on 1=亮,0=灭
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
void bsp_led_red_set(uint8_t on);
|
void bsp_led_red_set(uint8_t on);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绿灯开关(PA7,低亮)
|
* @brief 绿灯开关(PA8,低亮)
|
||||||
* @param on 1=亮,0=灭
|
* @param on 1=亮,0=灭
|
||||||
* @return 无
|
* @return 无
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user