From 5875d2a339cd75337c705338947958095191a870 Mon Sep 17 00:00:00 2001 From: Wuchuyuan <1917507415@qq.com> Date: Thu, 10 Sep 2026 11:43:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=B8=8B=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9A=20A=E7=94=A8=E6=88=B7=E7=BB=91=E5=AE=9AIPC?= =?UTF-8?q?=E5=92=8C=E9=97=A8=E5=A4=B9=E5=BC=B9=E5=8A=9B=E7=BB=B3=EF=BC=8C?= =?UTF-8?q?=E5=88=86=E4=BA=AB=E7=BB=99B=E7=94=A8=E6=88=B7=EF=BC=9B=20A?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=85=88=E8=BF=9B=E5=85=A5=E9=97=A8=E5=A4=B9?= =?UTF-8?q?=E5=BC=B9=E5=8A=9B=E7=BB=B3=E9=A1=B5=E9=9D=A2=EF=BC=8C=E5=B7=B2?= =?UTF-8?q?=E5=94=A4=E9=86=92=E7=8E=A9=E5=85=B7=EF=BC=8CB=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=86=8D=E8=BF=9B=E5=85=A5=E9=A1=B5=E9=9D=A2=20A?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=80=80=E5=87=BA=E9=A1=B5=E9=9D=A2=E4=B8=8B?= =?UTF-8?q?=E5=8F=91=E4=BC=91=E7=9C=A0=E6=8C=87=E4=BB=A4=EF=BC=8CB?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=93=8D=E4=BD=9C=E7=8E=A9=E5=85=B7=EF=BC=8C?= =?UTF-8?q?=E6=9C=AA=E6=8F=90=E7=A4=BA=E6=93=8D=E4=BD=9C=E6=88=90=E5=8A=9F?= =?UTF-8?q?/=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/usr_app/app_function.c | 49 +++++++++-- apps/usr_app/app_function.h | 14 ++- apps/usr_jb_proto/Protocol/jb_product.c | 19 +++++ apps/usr_jb_proto/usr_jb_main.c | 9 ++ docs/门夹弹力绳_需求文档.md | 109 +++++++++++++++++------- 5 files changed, 155 insertions(+), 45 deletions(-) diff --git a/apps/usr_app/app_function.c b/apps/usr_app/app_function.c index 71e07db..99075a5 100644 --- a/apps/usr_app/app_function.c +++ b/apps/usr_app/app_function.c @@ -2,8 +2,8 @@ * @file app_function.c * @brief 设备功能开关 + 自动玩法 + 手动点动 + 按键动作实现 * @author cyWu <1917507415@qq.com> - * @date 2026.08.26 - * @version V1.1.0 + * @date 2026.09.10 + * @version V1.2.0 * @history * - V1.0.0, 2026.08.25, cyWu, 首次发布,实现 DP0 开关、自动玩法、手动点动与按键动作 * - V1.1.0, 2026.08.26, cyWu, 切模式闪灯改到 function_play_mode_apply 统一 @@ -11,6 +11,8 @@ * 启动也补上闪灯;DP2 停转(cmd=3)改边沿触发, * 避免残留的 03 一直把刚启动的自动模式打断; * 自动模式新增单次最长运行时长,到点回待机 + * - V1.2.0, 2026.09.10, cyWu, 软关后 APP 下发其它控制指令也可唤醒, + * 唤醒后本拍继续执行该指令(玩法/点动) ******************************************************************************/ #include "app_function.h" @@ -39,11 +41,13 @@ typedef struct { uint16_t hand_ms; volatile uint8_t hand_write_pending; /**< DP2 刚写入(03 停转要靠事件) */ volatile uint8_t hand_write_cmd; + volatile uint8_t app_cmd_wake; /**< 软关后收到其它 APP 控制 DP */ } AppFunctionCtx_t; static AppFunctionCtx_t s_func; static void function_hand_stop(void); +static void function_app_power_on(const char *reason); static void function_play_mode_apply(uint8_t mode); static void function_play_mode_poll(void); static void function_play_mode_timeout(uint16_t dt); @@ -70,18 +74,45 @@ void app_function_dp_hand_write(uint8_t cmd) s_func.hand_write_pending = 1; } +/** + * @brief APP 下发了非关机控制 DP,软关时下一拍唤醒 + * @return 无 + */ +void app_function_on_app_cmd(void) +{ + s_func.app_cmd_wake = 1; +} + +/** + * @brief APP 侧开机:功能开、同步 DP0、挂开机绿灯 + * @param reason 日志原因(英文) + * @return 无 + */ +static void function_app_power_on(const char *reason) +{ + s_func.app_power = 1; + gDevData.power = 1; + app_led_hint_power_on(); + log_info("%s\n", reason); +} + /** * @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动 * @return 无 */ void app_function_poll(void) { - /* DP0:APP 下发开关。充电中 / 正在关机时不跟,避免和 USB 软关抢状态 */ + uint8_t cmd_wake = s_func.app_cmd_wake; + + s_func.app_cmd_wake = 0; + + /* DP0:APP 下发开关。充电中 / 正在关机时不跟,避免和 USB 软关抢状态。 + * 已软关时,后续其它控制 DP 也当开机,本拍接着跑玩法/点动。 */ if (!app_power_usb_online() && !app_power_poweroff_pending()) { - if (gDevData.power && !s_func.app_power) { - s_func.app_power = 1; - app_led_hint_power_on(); - log_info("APP power on\n"); + if (!s_func.app_power && cmd_wake) { + function_app_power_on("APP cmd wake from soft off"); + } else if (gDevData.power && !s_func.app_power) { + function_app_power_on("APP power on"); } else if (!gDevData.power && s_func.app_power) { app_function_soft_off(); } @@ -133,7 +164,7 @@ void app_function_stop_all(void) } /** - * @brief 软关:电机停、DP0=0,等 APP/短按再开。不断 BLE + * @brief 软关:电机停、DP0=0,等 APP 开机/其它指令/短按再开。不断 BLE * @return 无 */ void app_function_soft_off(void) @@ -145,7 +176,7 @@ void app_function_soft_off(void) gDevData.play_mode = 0; app_mode_stop(); function_hand_stop(); - log_info("soft off, wait APP power on\n"); + log_info("soft off, wait APP cmd or key\n"); } /** diff --git a/apps/usr_app/app_function.h b/apps/usr_app/app_function.h index cf86469..6064940 100644 --- a/apps/usr_app/app_function.h +++ b/apps/usr_app/app_function.h @@ -2,10 +2,11 @@ * @file app_function.h * @brief 设备功能开关(DP0)+ 自动玩法(DP1)+ 手动点动(DP2)+ 按键动作 * @author cyWu <1917507415@qq.com> - * @date 2026.08.25 - * @version V1.0.0 + * @date 2026.09.10 + * @version V1.2.0 * @history * - V1.0.0, 2026.08.25, cyWu, 首次发布,实现 DP0 开关、自动玩法、手动点动与按键动作 + * - V1.2.0, 2026.09.10, cyWu, 软关后 APP 下发其它控制 DP 也可唤醒功能 * * @note 本模块决定“电机现在该不该转、转哪种”,会读 app_power/app_pair 的 * 状态作为阻塞条件(充电中/深睡中/配对中都不能转),也会触发 @@ -30,6 +31,13 @@ void app_function_init(void); */ void app_function_dp_hand_write(uint8_t cmd); +/** + * @brief APP 下发了非关机控制 DP(玩法/点动等),软关时下一拍唤醒功能 + * @note 与关机 DP 同一帧到达时不要调用,避免关完立刻被同包其它 DP 拉开 + * @return 无 + */ +void app_function_on_app_cmd(void); + /** * @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动,每个业务 tick 调用一次 * @return 无 @@ -50,7 +58,7 @@ void app_function_tick_1ms(uint16_t dt); void app_function_stop_all(void); /** - * @brief 软关:电机停、DP0=0,等 APP/短按再开(USB 拔出 / APP 关时用) + * @brief 软关:电机停、DP0=0,等 APP 开机/其它指令/短按再开(USB 拔出 / APP 关时用) * @return 无 */ void app_function_soft_off(void); diff --git a/apps/usr_jb_proto/Protocol/jb_product.c b/apps/usr_jb_proto/Protocol/jb_product.c index 1cc9739..4dc3ae2 100644 --- a/apps/usr_jb_proto/Protocol/jb_product.c +++ b/apps/usr_jb_proto/Protocol/jb_product.c @@ -69,6 +69,9 @@ void userHandle(void) * ════════════════════════════════════════════════════════════════ */ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) { + uint8_t other_cmd = 0; + uint8_t got_power_off = 0; + if (!info || !ctrl) { return -1; @@ -89,6 +92,7 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) else { gDevData.power = 0; + got_power_off = 1; // TODO: 关 — 执行动作 } break; @@ -96,6 +100,7 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) /* ── DP1 自动模式 (enum) ── */ case DP_ID_PLAY_MODE: gDevData.play_mode = ctrl->play_mode; + other_cmd = 1; switch (ctrl->play_mode) { case PLAY_MODE_0: /* 停止复位 */ @@ -127,6 +132,7 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) /* ── DP2 手动模式 (raw) ── */ case DP_ID_HAND_MODE: memcpy(gDevData.hand_mode, ctrl->hand_mode, 2); + other_cmd = 1; /* 薄适配:通知业务层;03=停,00=空闲 */ { extern void usr_dp_hand_write(uint8_t cmd); @@ -136,6 +142,7 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) /* ── DP3 绳索校准 (bool) ── */ case DP_ID_CALIBRATION: + other_cmd = 1; if (ctrl->calibration) { gDevData.calibration = 1; @@ -151,30 +158,35 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) /* ── DP4 绳索圈数 (uint8) ── */ case DP_ID_NUMBERTURNS: gDevData.numberturns = ctrl->numberturns; + other_cmd = 1; // TODO: 根据 ctrl->numberturns 执行动作 break; /* ── DP8 勿扰模式开关 (raw) ── */ case DP_ID_NO_DISTURB_ENABLE: memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable, 13); + other_cmd = 1; // TODO: 根据 gDevData.no_disturb_enable 执行动作 (长度 13 字节) break; /* ── DP9 预留DP1 (uint32) ── */ case DP_ID_RESERVA_DP1: gDevData.reserva_dp1 = ctrl->reserva_dp1; + other_cmd = 1; // TODO: 根据 ctrl->reserva_dp1 执行动作 break; /* ── DP10 预留DP2 (uint32) ── */ case DP_ID_RESERVA_DP2: gDevData.reserva_dp2 = ctrl->reserva_dp2; + other_cmd = 1; // TODO: 根据 ctrl->reserva_dp2 执行动作 break; /* ── DP11 预留DP3 (uint32) ── */ case DP_ID_RESERVA_DP3: gDevData.reserva_dp3 = ctrl->reserva_dp3; + other_cmd = 1; // TODO: 根据 ctrl->reserva_dp3 执行动作 break; @@ -182,6 +194,13 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl) break; } } + + /* 软关后其它控制 DP 可唤醒;与关机 DP 同帧则关机优先,避免关完立刻被拉开 */ + if (other_cmd && !got_power_off) + { + extern void usr_dp_app_cmd(void); + usr_dp_app_cmd(); + } return 0; } diff --git a/apps/usr_jb_proto/usr_jb_main.c b/apps/usr_jb_proto/usr_jb_main.c index b15c459..49104f9 100644 --- a/apps/usr_jb_proto/usr_jb_main.c +++ b/apps/usr_jb_proto/usr_jb_main.c @@ -306,6 +306,15 @@ void usr_dp_hand_write(uint8_t cmd) app_function_dp_hand_write(cmd); } +/** + * @brief APP 下发了非关机控制 DP(jb_product 薄适配) + * @return 无 + */ +void usr_dp_app_cmd(void) +{ + app_function_on_app_cmd(); +} + /** * @brief BLE 收到见宝载荷,送入协议环形缓冲 * @param data 载荷指针 diff --git a/docs/门夹弹力绳_需求文档.md b/docs/门夹弹力绳_需求文档.md index 6b8a4e0..59ace4e 100644 --- a/docs/门夹弹力绳_需求文档.md +++ b/docs/门夹弹力绳_需求文档.md @@ -1,11 +1,12 @@ -# 门夹弹力绳 需求文档(V1.1) +# 门夹弹力绳 需求文档(V1.3) | 项目 | 内容 | | --- | --- | | 产品名称 | 门夹弹力绳(智能逗宠玩具) | | 型号 | PICBC2 / CBC2 | | 主控 | 杰理 AC7016C | -| 日期 | 2026-08-25 | +| 日期 | 2026-09-10 | +| 修订 | V1.3:App 软关后,再下发其它控制指令也可退出关机 | --- @@ -13,26 +14,39 @@ 门夹弹力绳是一款智能逗宠玩具。设备夹在门板上,内部电机带动弹力绳收放,让绳端玩具按不同"玩法"动起来逗宠物玩。支持手机 App 通过远程控制,也支持按键本地操作。 +硬件:红绿双指示灯(低电平亮)+ 单按键(低电平按下)。 + --- ## 2. 核心功能 ### 2.1 开关机 -| 操作 | 行为 | -| --- | --- | -| 深睡状态下按 3 秒 | 开机 | -| 深睡状态下按 5 秒 | 开机并进入配对模式 | -| 开机状态下按 3 秒 | 关机(深睡省电) | -| 插着充电线时上电 | 自动开机,不用按键 | -| 刚上电没按按键 | 保持深睡,不误开机 | +| 操作 | 行为 | 灯效 | +| --- | --- | --- | +| 刚上电没按按键、也没插充电线 | 保持深睡,不误开机 | 灭 | +| 插着充电线时上电 | 自动开机,跳过按键判定 | 进正常灯效 | +| 深睡状态下按住 **3 秒** 后松手 | 开机 | 满 3 秒时绿灯常亮 | +| 深睡状态下按住 **5 秒** 后松手 | 开机并进入配对(清配对信息) | 满 5 秒切成绿灯快闪 | +| 深睡状态下按住 **12 秒** | 开机并进入 OTA(不等松手) | 随后红绿交替闪 | +| 深睡状态下电量过低(约 ≤5%)且未充电 | 拒绝开机,红闪 5 下后继续深睡 | 红 200ms 亮/灭 ×5 | +| 开机状态下按住 **3 秒** | 关机(深睡省电) | 立刻灭灯 | -- 深睡状态下,按按键(3 秒/5 秒)或插充电线均可唤醒开机。 +补充说明: + +- 深睡唤醒:按按键(3 秒开机 / 5 秒配对 / 12 秒 OTA)或插充电线均可开机。 +- 满 5 秒只是「武装配对 + 快闪提示」,真正提交配对在 **松手** 那一刻。如果继续按到 12 秒,改道进 OTA,不再进配对。 +- 开机判定时如果手还按着,会锁住按键,松开后才认短按/长按,避免刚开机立刻关机。 ### 2.2 开关(App 控制) - App 可开/关设备功能;关闭后电机停转,蓝牙不断开,可随时再开。 +- 功能关闭后,以下操作都会退出软关并恢复功能: + - App 下发开机 DP + - App 再下发其它控制指令(自动玩法、手动点动等),并按该指令继续执行 + - 短按按键:只唤醒功能,**不切玩法** - 拔掉充电线会自动进入低功耗状态(电机停转、LED 关闭、蓝牙保持连接)。 +- 充电中、正在深睡关机:其它指令不会把功能拉开。 ### 2.3 自动玩法(6 种,App 或按键选择) @@ -48,46 +62,74 @@ - 玩法会循环播放,直到切换或停止。 - 运行中可随时切换到其他玩法或停止。 +- 单次最长运行 **10 分钟**,到点自动停转回待机(保护性超时,不闪灯,避免误以为被按到)。 +- 充电中、配对中、正在深睡关机:不允许跑自动玩法,强制回到停转。 +- 功能关闭时若 App 下发玩法/点动:先退出软关,再按该指令执行。 ### 2.4 手动控制(App 控制) - App 可手动点动:控制电机**收绳**或**放绳**,松手/点停止后电机停止。 - 手动控制进行中,自动玩法暂停。 +- 从自动/空闲切到手动时,绿灯闪两下提示。 +- 手动点动进行中,短按按键无效,避免打断收放绳。 +- 选中自动玩法会先停手动,再进入自动模式。 ### 2.5 按键控制 -| 操作 | 行为 | -| --- | --- | -| 短按(<1 秒) | 切换玩法:停 → 玩法1 → 玩法2 → … → 玩法6 → 停,循环 | -| 长按 3 秒 | 关机 | -| 充电中 / 配对中 | 按键无效 | +开机后只有一颗按键,短按/长按含义如下: + +| 操作 | 条件 | 行为 | +| --- | --- | --- | +| 短按(松开且未到 3 秒) | 功能开、空闲 | 切换玩法:停 → 玩法1 → … → 玩法6 → 停,循环;绿灯闪两下 | +| 短按 | 功能已关(App 软关 / 拔充电线后) | 只唤醒功能,不切玩法;挂待机绿灯 | +| 短按 | 手动点动进行中 | 无效 | +| 长按 3 秒 | 功能开 | 关机进深睡 | +| 短按 / 长按 | 充电中 / 配对中 / 正在关机 | 无效 | ### 2.6 电量与充电 -- 电量按电压换算:满电 4.2V = 100%,3.2V = 0%,电量自动上报 App 显示。 -- **低电量**(电池电压低于 **3.7V**):App 上报警告,指示灯红灯慢闪;持续 1 分钟未充电则自动关机保护电池。 -- **电量极低**(低于 3.2V):直接自动关机。 -- **插入充电线**:电机立即停止、进入充电状态,只有红灯常亮,表示充电中,充满转绿灯常亮;充电期间不执行玩法,按键无效。 -- **充电中拔掉充电线**:设备自动进入低功耗状态(电机停转、LED关闭、蓝牙保持连接),需按短按按键或 App 重新开启后才能使用。 -- 电池:锂电池,USB 5V 充电,充满自动转灯。 +- 电量按电压换算:满电约 4.12V = 100%,3.2V = 0%,电量自动上报 App 显示。充电内阻压降下,充满基准按 4.2V 计。 +- **低电量**(电池电压低于 **3.7V**):App 上报警告,指示灯红灯慢闪。只提示、不关机;电压回升超过 3.75V 后退出低电。 +- **空电关机**(显示电量 ≤ **5%**):立刻停电机,红灯闪 5 下后自动关机保护电池。充电中不判定。 +- **开机电量过低**(未充电且裸电压约 ≤ 3.25V,对应约 5%):拒绝开机,红闪 5 下后继续深睡。 +- 开机后约 3 秒内不因低电/空电关机,避免上电瞬间误判。 +- **插入充电线**:电机立即停止、进入充电状态,红灯常亮表示充电中,充满转绿灯常亮;充电期间不执行玩法,按键无效。功能关掉时充电灯仍继续亮。 +- **充电中拔掉充电线**:设备自动进入低功耗状态(电机停转、LED 关闭、蓝牙保持连接),需短按按键或 App 重新开启后才能使用。 +- 电池:锂电池,USB 5V 充电,充满自动转灯;硬件判定充满后电量强制收尾到 100%。 ### 2.7 指示灯 -| 状态 | 灯效 | -| --- | --- | -| 正常待机 | 绿灯常亮 | -| 充电中 | 红灯常亮 | -| 充满 | 绿灯常亮 | -| 低电量 | 红灯慢闪 | -| 配对中 | 绿灯快闪 | -| 配对成功 | 绿灯常亮 3 秒 | -| 配对失败 | 红绿交替闪 3 秒 | -| 按键切玩法 | 绿灯闪两下 | +红绿双灯,通道独立,可以同时亮。充电灯和功能开关无关。 + +| 状态 | 灯效 | 节奏 | +| --- | --- | --- | +| 正常待机(功能开、没充电) | 绿灯常亮 | 一直亮 | +| 充电中 | 红灯常亮 | 一直亮 | +| 充满 | 绿灯常亮 | 一直亮 | +| 低电量(功能开、没接电) | 红灯慢闪 | 亮 200ms / 周期 2s | +| 配对中 | 绿灯快闪 | 100ms 亮 / 100ms 灭 | +| 配对成功 | 绿灯常亮 3 秒 | 一次性 | +| 配对失败 | 红绿交替 3 秒 | 200ms 半周期 | +| 切玩法 / 切手动 | 绿灯闪两下 | 150ms 灭 + 150ms 亮 ×2 | +| OTA | 红绿交替,独占全部灯 | 200ms 半周期,一直闪到升级结束/复位 | +| 运行中空电关机 | 关绿灯,红灯闪 5 下再掉电 | 200ms 亮/灭 ×5 | +| 开机电量过低被拒 | 红灯闪 5 下后继续深睡 | 同上 | + +优先级(高的盖住低的): + +1. OTA / 空电关机红闪 / 切模式绿闪 +2. 配对快闪、配对成功、配对失败 +3. 充电红 / 充满绿 / 低电红闪 +4. 待机绿灯(最低) + +OTA 和空电关机会清掉其它灯,只跑自己那一种。功能关掉时,待机/配对/切模式/低电灯会清掉,**充电灯保留**。 ### 2.8 配对 -- 首次使用:深睡状态长按 5 秒开机并进入配对,App 扫码连接即可。 -- 配对窗口 120 秒:期间连接成功绿灯 3 秒;超时未连接保持开机,可重新配对。 +- 首次使用:深睡状态长按 5 秒后松手,开机并进入配对,App 扫码连接即可。 +- 按满 5 秒绿灯先切到配对快闪;若继续按到 12 秒则改道 OTA,不再提交配对。 +- 配对窗口 120 秒:期间连接成功绿灯常亮 3 秒;超时未连接红绿交替 3 秒,之后保持开机,可重新配对。 +- 配对期间不跑玩法、按键无效。 - App 可解绑设备,解绑后设备清除配对信息,重新配对即可再次使用。 ### 2.9 时间同步 @@ -97,6 +139,7 @@ ### 2.10 固件升级(OTA) - 支持 App 远程升级固件,升级协议已打通。 +- 本地入口:深睡状态按住 12 秒,开机并进入 OTA 广播,红绿交替闪。 - 当前版本:固件 V1.0.0、硬件 V1.0.0。 - 实际升级流程(写入芯片、校验、版本判断)待开发。