# 喷淋器固件需求规格书 | 项 | 内容 | |---|---| | 产品 | 喷淋器(爬宠) | | 芯片 | JL7016 / AC7016C(BR28) | | 工程 | `喷淋器_soc_JieLi-JL7016` | | 产品码 | ProdtCode=`CBR2`,productCode=`PRCBR2` | | 协议 | 见宝 SOC 55AA,V1.0.0 | | 文档版本 | V1.0.0 | | 日期 | 2026.09.09 | | 作者 | cyWu | | 读者 | WorkBuddy / 固件开发 | | 参考工程 | `D:\Jianbao\Project\产品\爬宠\电子开关-加热垫\电子开关-加热垫_soc_JieLi-JL7016`(框架 / 仲裁 / 联动 / 运行记录 / 配对 OTA / NVM) | | 框架源头 | `D:\Jianbao\Project\产品\爬宠\温湿度风扇\温湿度风扇_soc_JieLi-JL7016`(20ms + 500ms、按键分级、扫描时隙) | | 灯驱参考 | `D:\Jianbao\Project\产品\宠物\门夹弹力绳\门夹弹力绳_soc_JieLi-JL7016`(红绿双通道硬件;**事件表不要抄加热垫互斥灯**) | > **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 章为准。 --- ## 0. WorkBuddy 开干前必读(硬约束) 1. **程序框架必须和加热垫 / 风扇一致。** 组合根 `usr_jb_main.c`:20ms `usr_timer_tick`(硬件定时器中断)+ 500ms `usr_timer_loop`(BLE 任务上下文)。配对 / OTA / 写 VM 只准在任务上下文落地(`app_defer` / `app_function_loop`)。禁止在 20ms ISR 里 `syscfg_write`、切 BLE 广播、跑联动 / 计划引擎。 2. **不要把业务算法写进 `jb_product.c`。** 只把 DP 写入 `gDevData` 并转调 `app_*`。 3. **不要抄门夹的电池 / 低电 / 深睡开机判定。** 本机 **5V 常电、无电池**。上电即开机;拔电即关,无软关机。 4. **不要抄加热垫的过零 / 继电器,也不要抄风扇的 PWM / 数码管 / AHT20。** 执行器是 **PC2 + PA6 双脚水泵**;湿度来自扫描或 DP6,不是本机传感器。 5. **`MAX_PACKAGE_LEN` 必须为 512。** 生成值是 128,装不下 DP18 `run_record`(124B)。改完禁止再生成回 128。OTA 单包 `JB_OTA_MAX_PAYLOAD` 可保持 96。 6. **DP6 `current_humi` 为 WO。** 已连接时服务器下发;设备不上报;断连扫描到的湿度不填 DP6。 7. **DP5 `current_temp` 本版忽略。** 联动只看湿度。 8. **本版写忽略、不实现动作:** DP0 总开关、DP2 模式、DP3 单次时长、DP4 连续时长、DP5 当前温度、DP19 心跳。DP21 / DP23 保持 0(无缺水检测)。DP20 / DP22 原样存、不做位过滤。 9. 每个模块必须能用 `board_pin.h` 的 `BOARD_xxx_ENABLE` **单独关掉** 仍能编过、能配网上报。 10. 日志全部 **英文**。中文只出现在注释和本文。 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` **必须删掉**,改为上电进工作态(已配对走重连广播)。 13. 调试 UART TX = **PC5**。关掉会与 PA8 / PA7 / PB1 / PA0 / PC2 / PA6 冲突的 demo 外设(IR 键、PWM LED、demo I2C 等)。 14. **红灯 PA8、绿灯 PA7。** 和加热垫(PA8 绿 / PA7 红)**相反**,禁止照抄加热垫 `board_pin.h` 灯脚。 15. **灯是两路独立通道。** 喷淋 / 空闲只改绿灯,不要去关红灯;配网闪红灯时不要去关绿灯。只有 OTA 和配对失败动画临时占用双灯。禁止抄加热垫「开红关绿、互斥」事件表。 16. **上电绝不自动喷。** 不从 VM 恢复 DP1。泵两脚上电默认低。 17. **遵守第 0.1 节编码与注释。** 散落 static、把排障过程复制进每个函数、只写不读的计数,一律视为未完成。 --- ## 0.1 编码与注释(禁止再犯) 新产品模块(`apps/usr_app/*`、`apps/usr_periph/*`、`usr_jb_main.c`)必须遵守。从风扇 / 门夹 / 加热垫 **原样拷贝** 的 `key.c` / `led.c` 框架不要借机大改;只改引脚、双键实例和灯效事件表。 1. **一个模块一个上下文。** 两个及以上的模块状态变量必须收进 `XxxCtx_t`,文件内只留 `static XxxCtx_t s_xxx`。ISR 与主循环共享的成员在结构体里标 `volatile`。单个标志可以单独 `static`(如 `app_defer` 的位图)。 2. **禁止一排裸 static。** 反例:`s_edge_cb`、`s_edge_cnt`、`s_last_level`、`s_initialized` 并列。正例:`static PumpCtx_t s_pump`。 3. **不要只写不读的诊断变量。** 需要看泵是否在转,用已有 getter + 一条日志。 4. **文件头三件事:做什么、一两行硬约束、history。** 同一条约束只写一次。禁止把排障日记复制到每个 `@note`。 5. **函数注释短。** `@brief` 一行;`@param` / `@return` 按需;`@note` 只写本函数独特约束。不要复述代码。 6. **注释解释为什么,不写排障日记。** 「两脚必须同时高,泵才会转」是为什么;「我改了三次 GPIO 才好」不要进源码。 7. **P0 路径不堆死字段。** 没有 getter、没有日志、P2 也没用上的计数不要先占着。 --- ## 1. 产品一句话 5V 常供电的爬宠喷淋器:双 GPIO 同时导通才开泵。本地单击喷淋键,按上次 APP 配置跑完「单次时长 × 次数 + 间隔」一轮;APP 可写配置立刻开喷,也可写开关停喷。连着时用服务器下发的湿度做联动;断连时扫描风扇 `JBao-{缸体号}` 广播取湿度。过干则循环喷、过湿则停、中间维持上一态。7 条喷淋计划到开始时:分各跑一轮(不是加热垫那种时间窗)。水泵实际转动的秒数记 31 天运行记录。 --- ## 2. 硬件资源 ### 2.1 引脚(以用户原理图说明 + 本文为准) | 功能 | MCU | 电平 / 说明 | |---|---|---| | 红灯 | **PA8** `IO_PORTA_08` | **低电平亮**(与加热垫灯脚相反) | | 绿灯 | **PA7** `IO_PORTA_07` | **低电平亮**(与加热垫灯脚相反) | | 喷淋键 | **PB1** `IO_PORTB_01` | 低电平按下 | | 配网键 | **PA0** `IO_PORTA_00` | 低电平按下 | | 水泵总供电 | **PC2** `IO_PORTC_02` | **高电平**把 5V 导到泵 VCC | | 水泵开关 | **PA6** `IO_PORTA_06` | **高电平**导通泵;必须与 PC2 同时开,泵才工作 | | 调试 UART TX | **PC5** `IO_PORTC_05` | 只发打印 | | 供电 | 5V 常电 → 板内 3.3V | 无电池、无充电管理 | 板级文件仍指向 `CONFIG_BOARD_JL701N_DEMO`。产品 GPIO **全部集中到** `apps/usr_periph/board_pin.h`,并改 `board_jl701n_demo_cfg.h`: ```c #define TCFG_UART0_TX_PORT IO_PORTC_05 ``` 关掉与产品脚冲突的 demo 外设(IR 键、PWM LED、demo I2C / RDEC 等)。产品灯走 `app_led`,保持 `TCFG_PWMLED_ENABLE=DISABLE`。 ### 2.2 水泵安全 - GPIO 上电默认:**PC2=0 且 PA6=0**。 - 喷水时段:两脚同时置高。 - 间隔等待、空闲、停止、故障收口:两脚同时拉低。 - **禁止只开其中一只脚。** 写泵接口必须成对操作。 - 无过零、无继电器模块。切 GPIO 可在 20ms 节拍里直接改(5V 小泵,不是市电触点)。 - 上电绝不按 VM 自动开泵。 --- ## 3. 软件架构(对齐加热垫,删过零/继电器,加双键/双脚泵/喷淋作业) ``` apps/earphone/app_main.c └─ usr_rtc_init() └─ usr_jb_init() ← 唯一组合根 bsp_hw_init() app_led_init() ← 灯效通道表最先装载 jbInit() + userInit() (上电即开机:无按键等待) app_pump_init() app_spray_init() app_arbiter_init() app_linkage_init() / app_timer_init() / app_runrec_init() app_th_scan_init() app_nvm_init() + app_nvm_load() app_function_init() user_key_init() + lock_if_held(两颗键各自锁短按) usr_timer_add(usr_timer_tick, 20ms, priority=1) ← 常驻,永不重挂 ``` **20ms 业务节拍**(`usr_timer_tick`,硬件定时器中断上下文,禁止再开 1ms ISR): 1. `jbTimerIrq()` × dt 2. `user_key_scan` + `user_key_handle`(两颗键) 3. `app_spray_poll`(作业节拍:时长 / 间隔倒数;到点切泵) 4. `app_function_poll`(同步 DP1 = 作业态,不是某一只脚的瞬时电平) 5. `app_runrec_tick`(两脚都已导通则累加秒;满 10 分钟只打脏,不写 flash) 6. `app_nvm_tick`(变化检测 + 500ms 防抖计时,不写 flash) 7. `app_led_run` **priority 固定 1、只注册一次,不动态重挂**:5V 常电,不需要低功耗。 `usr_timer_loop()`(BLE 500ms 任务上下文):`userHandle()`、`jbProtocolHandle()`、 `app_th_scan_tick()`(断连时 500ms 重连广播 / 500ms 扫描交替)、 `app_linkage_run()` / `app_timer_run()`、`app_runrec_rotate_if_needed()`、 `app_function_loop()`(VM 落盘 + 配对 / OTA 落地)。 可直接复用加热垫 / 风扇源码(拷贝后改引脚 / 事件表,不要改框架语义): | 复用 | 路径 | 改什么 | |---|---|---| | 按键 FSM | `key.c` / `key.h` / `key_manager.c` | **两实例**:喷淋键只短按;配网键 5s / 12s | | 配对会话 | `app_pair.c` | 120s 超时;红灯通道跟配对态 | | 延后执行 | `app_defer.c` | 原样 | | NVM / 仲裁 / 运行记录 | `app_nvm` / `app_arbiter` / `app_runrec` | owner 从继电器改为喷淋作业;记录改计泵实际导通秒 | | 联动 | `app_linkage` | 开停条件改湿度回差(第 11.3);湿度源改扫描 / DP6 | | 定时 | `app_timer_task` | **改成到点触发一轮**,不要抄加热垫时间窗 | | RTC | `usr_rtc.c` | 原样 | | 扫描 | `app_th_scan` | 取广播湿度,不取温度 | | 协议骨架 | `usr_jb_proto` | 已生成;`MAX_PACKAGE_LEN` 改 512,DP6=WO | LED 底层拷门夹双通道(`led.c` 打开 `ALTERNATING_BLINK_ENABLE`),事件表按第 6 章 **独立通道**,不要用加热垫互斥表。 **不要拷贝:** `app_battery.c`、`app_boot.c`、`app_power.c`、`app_fan.c`、`app_disp.c`、`app_th.c`、`app_mode.c`、`app_relay.c`、`app_zc.c`、`bsp_zc.c`、充电 GPIO、CT1642、AHT20、过零。 --- ## 4. 模块开关(独立使能 / 屏蔽调试) 全部放 `apps/usr_periph/board_pin.h`。`0` 时:对应 `init/run` 直接 return,GPIO 不配,日志打一次 `xxx disabled`。 ```c #define BOARD_LED_ENABLE 1 #define BOARD_KEY_ENABLE 1 #define BOARD_PUMP_ENABLE 1 #define BOARD_SPRAY_ENABLE 1 /* 喷淋作业引擎;=0 时键/DP 不转泵 */ #define BOARD_LINKAGE_ENABLE 1 #define BOARD_TIMER_ENABLE 1 /* P0/P1 可先 0 */ #define BOARD_TH_SCAN_ENABLE 1 /* 断连扫描;P0/P1 可 0 */ #define BOARD_RUNREC_ENABLE 1 #define BOARD_NVM_ENABLE 1 /* P0 可 0 */ ``` P0 建议先 `LINKAGE/TIMER/TH_SCAN/RUNREC/NVM=0`,台上只验双键、双灯、双脚泵。 --- ## 5. 电源与按键 ### 5.1 上电 / 断电 - **上电直接进入工作态**,无需按键。流程:`bsp_hw_init` → `app_led_init` → `jbInit/userInit` → 各 `app_*_init` → `app_nvm_load` → `user_key_init` + `user_key_lock_if_held` → 20ms 节拍。 - **关机 = 拔电。** 无软关机、无深睡开机判定。 - 上电若某键仍按着:只锁**该键短按**(防一上电就开喷),不锁配网键的 5s / 12s。 - 设备工作态恒开机。DP0 本版忽略。DP1 是 **喷淋作业开关**。 - 无电池、无充电逻辑。 - **冷启动水泵:** GPIO 先保持关。VM 恢复配置 / 联动 / 计划 / 运行记录,**不恢复 DP1,不开泵**。湿度没到之前联动不改它(第 13 章)。 - 上电灯:绿灯常亮;已配对则红灯常亮,未配对红灯灭。 ### 5.2 两键分工(运行态) 加热垫是一颗键兼短按 / 配网 / OTA。本机必须拆开,禁止把 5s / 12s 做在喷淋键上。 #### PB1 喷淋键 | 事件 | 行为 | |---|---| | 单击(松开且按下时长 < 5000 ms) | 空闲 → 用上次有效配置开一轮(第 10 章);作业中 → 立刻停(第 10.4) | | 按住 ≥ 5s | **无动作**(不配网、不进 OTA) | #### PA0 配网键 | 按住时长 | 行为 | |---|---| | < 5000 ms 松开 | 短按:无动作 | | 满 5000 ms(仍按住) | **只**切红灯 100ms 快闪,**不清绑定** | | 满 5s 后、12s 前松开 | 提交配对:`usr_goto_pair_mode()` + `app_pair_start()` 120s | | 满 12000 ms(到点即触发) | 进 OTA,不等松手;同时撤销本次配对 | 配对刻意不在 5s 到点提交 —— 用户可能继续按到 12s 进 OTA。 ⚠ 20ms ISR 只准置标志 + 切灯效。清绑定与 `usr_app_ota_init` 延后到 `app_function_loop()`。 ### 5.3 会话中的按键 | 场景 | PB1 | PA0 | |---|---|---| | 配对进行中 | 仍可启停喷淋 | 忽略再次 5s / 12s | | OTA 进行中 | 忽略 | 忽略 | --- ## 6. LED(红绿独立通道,低亮) 灯脚:**PA8 红、PA7 绿**。两路独立,固件改其中一个 GPIO 时禁止顺手改另一个,除非命中下面的「双灯例外」。 ### 6.1 绿灯(喷淋通道) | 状态 | 绿灯 | |---|---| | 空闲(DP1=关) | 常亮 | | 喷淋作业中(含间隔等待,DP1=开) | 500ms 亮 / 500ms 灭 | | 上电 | 常亮(泵关) | 绿灯跟 **作业态**(DP1),不是 PA6 / PC2 的瞬时电平。间隔等待也算作业中,绿灯继续闪。 ### 6.2 红灯(配网通道) | 状态 | 红灯 | |---|---| | 未配对 / 配对失败收尾后 | 灭 | | 已配对(含断连重连、上电已配对) | **常亮**(粘住;空闲 / 喷淋都不要关它) | | 配网键满 5s 仍按住、以及配对进行中 | 100ms 亮 / 100ms 灭(此时即使已配对也改成闪) | | 配对成功 | 常亮(接着当「已配对」) | 配网闪红灯期间,绿灯继续按 6.1 闪或常亮,不要关绿灯。 ### 6.3 双灯例外(临时占用两路,结束后各回自己的通道) | 事件 | 图案 | 结束后 | |---|---|---| | OTA | 红绿交替,200ms 半周期,直到升级结束 / 复位 | — | | 配对失败 / 超时 | 红绿交替 3s | 红灯灭;绿灯回到空闲或喷淋 | 优先级高→低:**OTA → 配对失败动画 → 各走各的通道(红=配对态,绿=作业态)**。 --- ## 7. 水泵(双脚同时切) ### 7.1 接口 - `app_pump_init` / `app_pump_set(uint8_t on)` / `app_pump_get()`。 - `on=1`:PC2=1 且 PA6=1。 - `on=0`:PC2=0 且 PA6=0。 - 禁止导出「只改一只脚」的接口。 - `BOARD_PUMP_ENABLE=0`:`set` 直接 return,GPIO 不配。 ### 7.2 谁可以切泵 只有 `app_spray` 作业引擎调用 `app_pump_set`。键 / DP / 联动 / 计划都只请求「开一轮 / 停」,不直接改 GPIO。 --- ## 8. 湿度来源(无本机传感器) 内部统一用 `humi`(uint8,单位 %)。联动比较见 11.3。 | 场景 | 湿度从哪来 | 扫描 | DP6 | |---|---|---|---| | BLE 已连接 | 服务器写 DP6(uint8 %) | **停** | WO 收下,不存 VM,不上报 | | BLE 断连且已配对 | 扫描风扇自定义广播 | **500ms 扫描 / 500ms 重连广播** 交替 | 扫描值 **不写** DP6 | | 未配对 / 配对中 / OTA | 不扫描 | 重连或绑定 / OTA 广播 | — | DP5 当前温度:本版 **写忽略**,不参与开停。 ### 8.1 风扇侧广播格式(只读约定,喷淋器不发这种包) 风扇 `app_th_adv.c`: - Complete Local Name:`JBao-{tank_id}`(十进制,无前导零),例如缸体 1 → `JBao-1` - Manufacturer `0xFFFF` 后 3 字节:`[配置版本 uint8][温度 int8 ℃][湿度 uint8 %]` 喷淋器匹配规则: 1. 名字等于 `JBao-` + 本机 DP9 缸体编号(uint16 十进制)。 2. 配置版本等于 DP9 Byte6。 3. 两者都满足才更新 `humi = 湿度uint8`。 4. 名字不对、版本不对、解析失败:**当没收到**,保持上一湿度和上一喷淋态(第 11.3)。 缸体编号为 0:视为无有效联动,不扫描匹配。 ### 8.2 500ms 时隙 断连已配对: ``` tick 偶数:见宝重连广播(encode4reconn) tick 奇数:关广播、开扫描 ≤500ms,回调里滤 JBao- 前缀 已连接 / 配对 / OTA:退出该交替,回到风扇同款 adv 策略 ``` 实现时若协议栈不能真正双角色并发,**必须时分**,不允许「边广播边扫」把时序写死成阻塞。 湿度丢失(长时间无匹配包):**保持上一态**,不因丢包停喷。 --- ## 9. 本版不用的 DP | DP | 本版 | |---|---| | 0 power 总开关 | 写忽略 | | 2 mode 模式 | 写忽略(单次 / 连续那套预留语义作废,喷淋参数只走 DP7 / 计划自带字段) | | 3 once_spray_time | 写忽略 | | 4 continuous_spray_time | 写忽略 | | 5 current_temp | 写忽略;不上报 | | 19 heartbeat | 写忽略 | | 21 fault_code | 保持 0 | | 23 alarm_code | 保持 0(缺水告警本版无传感器) | 无 `app_mode`。DP20 / DP22 有写入则原样存 VM。 --- ## 10. 喷淋作业引擎 设备同一时刻最多跑 **一轮** 作业。一轮 = 按「单次时长、次数、间隔」把泵开停若干次,然后结束。 ### 10.1 有效配置 来源优先级(只决定 **参数**,不决定谁有权开): 1. 计划触发:用 **该条计划自己的** 时长 / 次数 / 间隔(第 12.1)。 2. 其它开喷(PB1、DP1=1、写 DP7、联动):用 RAM / VM 里的 DP7。 3. DP7 无效或从未收过:默认 **时长 10s、次数 3、间隔 3s**。 首次上电 VM 无效:把默认写入 RAM,并作为 DP7 上报,当作已有有效配置。 ### 10.2 DP7 `spray_config`(8 字节) | 偏移 | 字段 | 编码 | |---|---|---| | Byte0–1 | 单次喷淋时长 | uint16 **大端**,单位秒,1~65535 | | Byte2 | 喷淋次数 | uint8,1~255 | | Byte3–4 | 喷淋间隔 | uint16 **大端**,单位秒,0~65535 | | Byte5–7 | 预留 | 只存不用 | 默认上电值(十六进制):`00 0A 03 00 03 00 00 00`。 非法包:**时长=0 或 次数=0**。整包丢弃:不改 RAM / VM,不开喷,保持上一份有效配置。 **间隔=0:** 喷完一次马上喷下一次,中间 **不停泵**(两脚保持高,直到整轮次数用完)。 APP 写 DP7(合法包):`memcpy` 8 字节 → 存 VM → **立刻按新参数开一轮**。若正在喷:中止旧轮再开新轮。记入手动抢权(第 13 章),DP1 报开。 ### 10.3 一轮怎么跑 ``` count 次循环: 泵开 等待 duration 秒 若还不是最后一次 且 interval > 0: 泵关 等待 interval 秒 泵关 作业结束,DP1 报关 ``` - 整轮期间(含间隔等待)DP1 = 1,绿灯按作业态闪。 - 轮次自然结束:泵关,DP1 = 0。 - 20ms 节拍里倒数;不要用阻塞 `delay`。 ### 10.4 停止 任一来源请求停: - 立刻两脚拉低,结束作业,DP1 报关。 - 来源是 PB1 单击(作业中)或 APP 写 DP1=0:手动作废联动(第 13 章)。 - 若当前轮是 **类型 0 单次计划** 占用的:该条 Byte0 清 0 并写 VM(被手动中途停也要清,避免单次卡死)。 ### 10.5 其它开喷入口 | 入口 | 参数 | 是否手动抢权 | |---|---|---| | PB1 单击(空闲) | 上次有效 DP7 | 是 | | APP 写 DP1=1 | 上次有效 DP7 | 是 | | APP 写 DP7 | 新 DP7 | 是 | | 计划到点 | 该条 Byte5–9 | 否(计划排队,见 12.3) | | 联动请求开 | 上次有效 DP7 | 否 | APP 写 DP1=1 时若已在作业中:保持当前轮,不重开(幂等)。写 DP1=0 时若已空闲:保持关。 --- ## 11. 联动引擎 设备只有 **1 条** 20 字节联动配置(DP9)。布局与加热垫 DP8 相同,本版只用湿度字段。 ### 11.1 有效配置 同时满足才跑联动开停: - 缸体编号 ≠ 0(Byte0–1,uint16 大端) - `Hmin ≤ Hmax` - `BOARD_LINKAGE_ENABLE=1` 否则视为无联动:不自动开;若原先是联动占着作业则停泵。 温度 min / max、配置版本:整包 20B 原样落盘。温度 **不参与开停**。版本只用于扫描匹配(8.1)。Byte7–19 预留,逻辑忽略。 无单独「联动总开关」。停联动:DP8 写 1、或 DP9 缸体写成 0 / min>max、或手动作废。 ### 11.2 20 字节布局(DP9 `linkage_config`) | 偏移 | 字段 | 编码 | |---|---|---| | Byte0–1 | 缸体编号 | uint16 **大端** | | Byte2 | 最低温度 `Tmin` | 有符号 int8 ℃,只存不用 | | Byte3 | 最高温度 `Tmax` | 有符号 int8 ℃,只存不用 | | Byte4 | 最低湿度 `Hmin` | uint8 % | | Byte5 | 最高湿度 `Hmax` | uint8 % | | Byte6 | 配置版本 | uint8,扫描匹配用 | | Byte7–19 | 预留 | 只存不用 | 比较用 uint8 湿度:`H < Hmin`、`H > Hmax`。**等于不算越界**,走「中间维持上一态」。 已连接:`humi = gDevData.current_humi`。断连扫描:广播第 3 字节。尚无任何有效湿度:联动不改泵(保持上电关 / 上一态)。 ### 11.3 开停(只看湿度) 在有效配置、未被手动作废、仲裁允许联动、且已有有效湿度时,每个 500ms 任务 tick 判一次: | 条件 | 动作 | |---|---| | `H < Hmin` | 请求进入喷淋:用 DP7 循环开一轮又一轮(一轮正常结束后立刻再开,无额外间隔) | | `H > Hmax` | 立刻停泵、结束作业 | | `Hmin ≤ H ≤ Hmax`(含等于) | **保持上一态**(上一态在喷就继续循环,上一态停就保持停) | 过干开、过湿关。无有效湿度 / 丢包:不改。 联动只在 **空闲且未被作废** 时开下一轮。泵被手动 / 计划占着时联动不抢(第 13 章)。 ### 11.4 联动删除(DP8) | 写入 | 行为 | |---|---| | 0 | 无操作 | | 1 | 清 RAM + VM 里 20 字节;若当前是联动占着作业 → 停;然后把 `linkage_delete` **清回 0** 再上报(脉冲) | ### 11.5 APP 重写 DP9 `memcpy` 20 字节 → 存 VM → **清手动作废标志** → 立刻按新阈值重判(有有效湿度时)。缸体编号写成 0:按无效配置,联动占着则停。 --- ## 12. 喷淋计划引擎(7 组,到点触发一轮) **不要抄加热垫时间窗。** 加热垫是开始~结束保持开;本机是「到开始时:分,用该条自己的时长 / 次数 / 间隔跑完一轮就结束」。没有结束时:分。 ### 12.1 15 字节(DP11~17) | 偏移 | 字段 | |---|---| | Byte0 | 使能 0/1 | | Byte1 | 定时类型:0=单次,1=每天,2=每周。其它值该条无效,不触发 | | Byte2 | 星期位图 Bit0=周一 … Bit6=周日(仅类型 2) | | Byte3 | 开始小时 0~23 | | Byte4 | 开始分钟 0~59 | | Byte5–6 | 单次时长 uint16 **大端** 秒 | | Byte7 | 次数 uint8 | | Byte8–9 | 间隔 uint16 **大端** 秒 | | Byte10–14 | 预留,只存不用 | 本版 **没有** 加热垫那种「Byte1 模式忽略」。Byte1 就是类型。 非法参数(时长=0 或 次数=0):该条本分钟不触发,使能保持原值。 ### 12.2 总开关与 RTC 门闩 `DP10 spray_plan_en`:所有计划的总开关。 ``` usr_rtc_get_time() 失败 → 全部计划不新触发(等同 plan_en=0 的「不触发」) 仅 BLE 已连接但从未对时 → 同上 plan_en=0 → 不新触发 ``` `plan_en=0` 或 RTC 无效:**不中断** 已经在跑的一轮(手动 / APP / 联动仍可停)。 无电池,拔电后 RTC 丢失。开机后必须等 APP / 网关 `usr_rtc_set_from_unix` 成功。 对时成功瞬间:若 **当前时:分刚好等于** 某条有效计划的开始时:分,补触发一次;**过了这一分钟不再补**(避免下午计划在晚上对时被补喷)。 ### 12.3 触发与排队 每个 500ms 任务 tick: 1. RTC 无效或 `plan_en=0`:不入队。 2. 类型 1:每天该时:分触发一次。 3. 类型 2:仅位图置位的星期,该时:分触发一次。 4. 类型 0:该时:分触发一次;该轮 **正常跑完或被手动停** 后把 Byte0 清 0 并写 VM。 5. 去重:用「上次触发的本地日期 + 时 + 分 + 槽位」防 500ms 任务在同一分钟内连打。 6. 多条同一分钟:**排队**,做完一条再做下一条,不重叠喷。 7. **计划到点时若泵已经在跑**(手动 / 上一计划 / 联动):当前轮先跑完,再跑队列,**不插队中止**。 8. 新触发只入队,真正 `app_spray_start` 发生在作业空闲且轮到该条时。 ### 12.4 一轮结束之后 - 计划轮正常结束:出队;立刻按第 11.3 补判联动(湿度仍 `< Hmin` 且未作废 → 联动接着循环)。 - 被手动停:作废联动,不再立刻补开;若是单次计划,仍清该条使能。 --- ## 13. 仲裁(唯一决策表) 优先级 **高 → 低**: 1. **手动**:PB1、APP 写 DP1、APP 写 DP7 2. **计划队列**(RTC 有效且 `plan_en=1` 且已到点入队 / 正在跑计划轮) 3. **联动**(有效配置且本 tick 满足开 / 关条件) | 冲突 | 胜者 | 副作用 | |---|---|---| | 手动开(含写 DP7)vs 正在跑的任何轮 | 手动 | 中止当前轮,按新来源参数重开;若原是联动占着 → **本轮联动作废**(RAM,不写 VM) | | 手动停 vs 正在跑的任何轮 | 手动 | 立刻停;联动作废;单次计划清使能 | | 计划到点 vs 正在跑 | 当前轮先跑完 | 计划入队,不插队 | | 计划轮 vs 联动 | 计划 | 计划占着时联动不开下一轮;计划结束收口后立刻按 11.3 重判 | | 联动 vs 空闲 | 联动 | 开则开一轮(循环),关则停 | 作废标志不存 VM。 **手动作废后的再武装:** 必须先走到第 11.3 的「关」条件(`H > Hmax`),再重新出现 `H < Hmin`,才允许联动再开。作废期间丢广播仍保持上一态。 **冷启动:** 作废标志是 RAM,上电为空。泵保持关。有有效湿度且无计划占用时,允许联动按 11.3 立刻改(过干则开喷)。 `plan_en` 写成 0:立刻停新触发(不拆当前轮)。无联动总开关;无效配置 / DP8 删除即停联动。 --- ## 14. 持久化(VM) 存(冷启动保留): - `spray_config`(8B,含首次默认 10 / 3 / 3) - `linkage_config`(20B) - `spray_plan_en`、`spray_plan1` + `time_config2~7`(各 15B) - `fault_en`、`alarm_en`(若有写入) - `run_record`:31 × uint32 秒 + slot[0] 对应的本地日期(对时后才有意义) 不存: - DP0 / DP2 / DP3 / DP4 / DP5、DP1 作业开关、DP6 当前湿度、联动作废标志、泵 GPIO - `linkage_delete`(脉冲) - BLE 三元组(`CFG_USER_PAIR_INFO`,出厂重置 **不清**) 上电:加载上述字段,泵保持关,DP1=0,再让联动按第 13 章冷启动规则判(无湿度则不动)。 首次上电(VM 无效):DP7=默认 10s / 3 次 / 3s,联动 20B 全 0,计划全 0,`spray_plan_en=0`,31 天运行记录全 0,DP1=0。 写 VM 时机: - APP 改联动 / 计划 / 喷淋配置、单次计划自毁:500ms 防抖 - 运行记录:累计泵转满 **10 分钟** 写一次;**跨天旋转时立刻写** ### 14.1 运行记录(DP18 `run_record`,RO,124B) 31 天 × 4 字节,每格 uint32 **大端**,单位秒: | 偏移 | 含义 | |---|---| | Byte0–3 | 今天喷淋器运行总秒数 | | Byte4–7 | 昨天 | | … | … | | Byte120–123 | 前 30 天 | **什么叫在运行:** `app_pump_get()==1`(PC2 与 PA6 **都已导通**)。间隔等待、只开了一只脚、作业态为开但泵已关,**都不计**。手动 / 计划 / 联动只要泵真正在转都算。 **日期轴:** 与加热垫 / 风扇相同 —— 未对时只往 slot[0] 加、不旋转;对时后按本地日历左移;掉电再上电先灌 VM。 **落盘:** 每累计运行满 600 秒写一次 VM。跨天立刻写。拔电接受最多丢不满 10 分钟的秒数。 `BOARD_RUNREC_ENABLE=0`:不累加、DP18 报全 0。 --- ## 15. 出厂重置(DP24 写 1) - **清全部业务 VM**(第 14 章列表,含 DP7 默认恢复、联动 20B、7 条计划、31 天运行记录)。 - **保留 BLE 配对,不断开、不软复位。** - RAM 中泵停、DP1=0,联动 / 计划停止,作废标志清掉;DP7 回到默认 10 / 3 / 3。 - 换机:运行中 **按住配网键 5s 后松手** 清配对进绑定广播(见 5.2)。 --- ## 16. BLE / DP 行为摘要 身份:`CBR2` / `PRCBR2`。帧格式见 `jb_protocol.h`(`MAX_PACKAGE_LEN=512`)。 | DP | 处理 | |---|---| | 0 power | 本版写忽略 | | 1 spray_switch | 写 1 开一轮(已在喷则保持);写 0 立刻停。上报 **作业态**(整轮含间隔为 1) | | 2–4 | 本版写忽略 | | 5 current_temp | 本版写忽略;不上报 | | 6 current_humi | **WO**。已连接时服务器下发 → 更新联动用湿度。不上报。断连扫描不填 | | 7 spray_config | 8 字节;合法则存 VM + 立刻开一轮(第 10.2) | | 8 linkage_delete | 第 11.4 节,脉冲 | | 9 linkage_config | 20 字节 memcpy + 存 VM + 第 11.5 节立刻重判 | | 10 spray_plan_en | 见第 12 章 | | 11–17 计划 | 15 字节 + 存 VM | | 18 run_record | RO,由 `app_runrec` 填 124B,禁止 APP 写 | | 19 heartbeat | 本版忽略 | | 20 fault_en | 原样存,P0 不做位级过滤 | | 21 fault_code | 保持 0 | | 22 alarm_en | 原样存 | | 23 alarm_code | 保持 0 | | 24 factory_reset | 第 15 章 | | 25–27 reserva | 存 RAM,可不实现动作 | OTA:运行态配网键按住 12s 走 `usr_app_ota_init`(任务上下文,红绿交替)。协议 OTA(`jbOnOtaNotify`)**本版 stub**,禁止假装升级成功后擦用户区;加热垫同款直接 `JB_OTA_STATE_ERR` 也可。 配对超时 120s,失败保持开机,灯回「红灭 + 绿按作业 / 空闲」。 断连已配对:500ms 见宝重连广播 ↔ 500ms 扫描(第 8.2)。绑定广播仍用 `CBR2` / `PRCBR2`,不要改成 `JBao-` 名(那是风扇温湿度窗)。 --- ## 17. 建议文件清单(新建,加热垫 / 风扇已有的拷贝改) ``` apps/usr_periph/ board_pin.h # 引脚、时序、BOARD_xxx_ENABLE bsp_hw.c/.h # LED 双通道 / KEY×2 / 泵双脚 GPIO usr_rtc.c/.h # 已有则保留 apps/usr_app/ key.c/.h, key_manager.c/.h # 拷风扇;两实例:喷淋短按 + 配网 5s/12s led.c/.h, led_manager.c/.h # 拷门夹双通道 + 红绿交替;事件表按第 6 章 app_led.c/.h # 绿=作业,红=配对;OTA / 失败占双灯 app_defer.c/.h # 拷加热垫 / 风扇 app_pair.c/.h # 拷加热垫,红灯通道跟配对态 app_pump.c/.h # 双脚成对切,禁止单脚接口 app_spray.c/.h # 第 10 章作业引擎(唯一切泵者) app_th_scan.c/.h # 500ms 交替扫描 JBao-{tank},取湿度 app_linkage.c/.h # 第 11 章湿度回差 app_runrec.c/.h # 泵实际导通计秒 app_timer_task.c/.h # 第 12 章到点触发 + 排队 app_nvm.c/.h app_function.c/.h app_arbiter.c/.h ``` `usr_jb_main.c` 只编排,不写算法。禁止引入 `app_fan` / `app_disp` / `app_th` / `app_mode` / `app_relay` / `app_zc`。 --- ## 18. 分阶段交付与验收 ### P0 — 台上 Bring-up(不依赖 APP) **目标:** 双键、双灯、双脚泵可独立验证。 - [ ] `board_pin.h` + UART TX=PC5,打印正常 - [ ] 插电即开机(不按键,绿灯常亮,泵两脚低;未配对红灯灭) - [ ] 单击 PB1:泵两脚同时高,按默认 10s × 3 次、间隔 3s 跑完;作业中绿灯 500ms 闪;结束后绿灯常亮、泵关 - [ ] 作业中再点 PB1:立刻双脚低、绿灯回常亮 - [ ] 示波器 / 逻辑:禁止出现「只高一只脚」 - [ ] 间隔 3s 期间两脚都低,但绿灯仍闪(作业态未结束) - [ ] PA0 满 5s → 红灯 100ms 闪,绿灯不被关掉(仍常亮或仍闪) - [ ] 5s 后松手 → 清绑定进配对(红灯继续闪) - [ ] 满 5s 不松手到 12s → 不进配对,红绿交替进 OTA - [ ] 长按进配对 / OTA 后不复位(动作走任务上下文) - [ ] 上电强制 `usr_goto_pair=1` 已删除 - [ ] 灯脚确认:PA8=红、PA7=绿(不要抄加热垫) **P0 关闭:** `BOARD_LINKAGE/TIMER/TH_SCAN/RUNREC/NVM_ENABLE=0` ### P1 — 最小产品闭环 - [ ] 5s 配对,APP 能写 DP1 开关喷淋;DP1 报作业态(间隔期间仍为 1) - [ ] APP 写合法 DP7:立刻按新参数开喷;PB1 再点能停 - [ ] 非法 DP7(时长 0 或次数 0):不改配置、不开喷 - [ ] 短按同步上报 DP1 - [ ] VM:改 DP7 后断电,重新上电 **不自动喷**,但单击按上次配置喷 - [ ] 首次上电 DP7 上报默认 `00 0A 03 00 03 00 00 00` - [ ] DP0 / 2 / 3 / 4 / 5 / 19 写忽略;设备不关、泵不跟这些 DP - [ ] DP6 WO:APP / 服务器能写下湿度;设备全量上报 **不含** DP6 / DP5 - [ ] 计划 7 组 + `spray_plan_en` 能存;`BOARD_TIMER_ENABLE=0` 时不执行 - [ ] `MAX_PACKAGE_LEN=512`,APP 能读完整 124B 运行记录(本阶段记录可仍为 0) ### P2 — 自动化 - [ ] 断连:500ms 重连广播 / 500ms 扫描;已连接停扫描 - [ ] 匹配 `JBao-{缸体号}` 且版本相同才更新湿度;温度不参与开停 - [ ] 联动:`HHmax` 停,中间保持;无匹配包保持上一态 - [ ] 缸体号 0 / min>max 不跑联动 - [ ] 手动作废后须先 `H>Hmax` 再 `HHmax`,再回到 `HHmax` → 停。 11. APP 写合法 DP7 → 中止当前轮并按新参数重开,联动作废。 12. 间隔等待期间 DP1 仍为 1,绿灯仍闪,运行记录 **不加** 间隔秒。 --- ## 19. `board_pin.h` 时序宏(实现时抄这份数) ```c #define BOARD_UART_TX_PIN IO_PORTC_05 #define BOARD_KEY_SPRAY_PIN IO_PORTB_01 /* 喷淋键,只短按 */ #define BOARD_KEY_PAIR_PIN IO_PORTA_00 /* 配网键,5s / 12s */ #define BOARD_LED_RED_PIN IO_PORTA_08 /* 红灯,低亮;与加热垫相反 */ #define BOARD_LED_GREEN_PIN IO_PORTA_07 /* 绿灯,低亮;与加热垫相反 */ #define BOARD_PUMP_PWR_PIN IO_PORTC_02 /* 泵 5V 供电,高导通 */ #define BOARD_PUMP_SW_PIN IO_PORTA_06 /* 泵开关,高导通 */ #define BOARD_LED_ON_LEVEL 0 #define BOARD_PUMP_ON_LEVEL 1 #define BOARD_KEY_ACTIVE_LEVEL 0 #define BOARD_KEY_PAIR_MS 5000 #define BOARD_KEY_OTA_MS 12000 #define BOARD_PAIR_TIMEOUT_MS (120u * 1000u) #define BOARD_LED_FAST_ON_MS 100 /* 配网红灯快闪 */ #define BOARD_LED_FAST_OFF_MS 100 #define BOARD_LED_SPRAY_ON_MS 500 /* 作业绿灯闪 */ #define BOARD_LED_SPRAY_OFF_MS 500 #define BOARD_LED_OTA_ON_MS 200 #define BOARD_LED_OTA_OFF_MS 200 #define BOARD_LED_PAIR_FAIL_MS 200 /* 红绿交替半周期 */ #define BOARD_LED_PAIR_FAIL_HOLD_MS 3000 #define BOARD_ADV_SCAN_SLOT_MS 500 /* 重连广播 / 扫描各占一拍 */ #define BOARD_SPRAY_DEFAULT_DUR_S 10 #define BOARD_SPRAY_DEFAULT_COUNT 3 #define BOARD_SPRAY_DEFAULT_INTERVAL_S 3 #define BOARD_RUN_RECORD_DAYS 31 #define BOARD_RUN_RECORD_SAVE_SEC 600 ``` --- ## 20. 风险与不要擅自发挥的点 | 风险 | 要求 | |---|---| | 只开 PC2 或只开 PA6 | 禁止。泵接口必须成对 | | 上电按 VM 自动喷 | 禁止。水会喷出来 | | 抄加热垫灯脚(PA8 绿 / PA7 红) | 禁止。本机 PA8 红 / PA7 绿 | | 抄加热垫互斥灯(开红关绿) | 禁止。红绿独立,空闲不要关红灯 | | 抄加热垫时间窗定时 | 禁止。本机是到点触发一轮 | | 抄加热垫过零 / 继电器 | 禁止 | | 湿度当温度、或温度参与开停 | 本版禁止 | | 丢广播就停喷 | 禁止(已拍板保持上一态) | | 计划过点后对时补喷 | 禁止;只补「当前这一分钟」 | | 计划到点插队中止手动轮 | 禁止;排队 | | 生成协议再导回 LEN=128 | 禁止;必须 512 | | `app_main` 上电清配对进绑定 | 必须删 | | 20ms ISR 写 VM / 切 BLE | 禁止(风扇已踩过栈溢出) | | 一排裸 static / 排障史注释 | 禁止。≥2 个状态收进一个 `s_xxx`。详见第 0.1 节 | | 协议 OTA stub 返回 OK 当已升级 | 注释标明未升级,或直接 `JB_OTA_STATE_ERR` | | 扫描时改 GATT 名 | 不要改 0x2A00;只解析对方 adv | | 把 DP1 理解成「某一只脚的电平」 | DP1 是作业态,间隔期间仍为开 | | 把 DP3 / DP4 当喷淋参数 | 禁止;参数只走 DP7 和计划自带字段 | | 喷淋键上做 5s / 12s | 禁止;配网只在 PA0 | --- ## 21. 给 WorkBuddy 的推荐开工顺序 1. 改 `MAX_PACKAGE_LEN=512`。`jb_product.c` 只保留 DP 赋值,业务迁 `app_*`。删 `app_main` 上电强制配对。 2. `board_pin.h` + `bsp_hw`(双灯双键双脚泵);UART TX=PC5。 3. 从加热垫拷 `usr_jb_main` 框架、defer、pair、nvm 骨架;key 改两实例;LED 拷门夹双通道,事件表按第 6 章重写。 4. `app_pump` + `app_spray`:P0 验收单击默认 10/3/3、中途停、灯独立。 5. P1:DP1 / DP7、VM 不恢复开喷、忽略 DP、计划只存。 6. P2:`app_th_scan`、`app_linkage`、到点计划 + 排队、`app_runrec`、仲裁用例。 每一步都保持对应 `BOARD_xxx_ENABLE` 可关。提交前对照第 0.1 节:一个模块一个 `s_xxx`,注释写为什么,不要排障日记。 --- **文档结束。** 行为争议以本文第 5~14 章为准;字节布局以第 10.2 / 11.2 / 12.1 / 14.1 节为准。