From 432b80dfa357fdc2a034bbb6d06a24245a54eb93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9C=E8=99=9E=E6=B8=A0=E6=88=90=E2=80=9D?=
<“yuqucheng2006@qq.com”>
Date: Tue, 1 Sep 2026 15:47:57 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A=E5=A4=84UI?=
=?UTF-8?q?=E8=BF=94=E5=9B=9E=E9=80=BB=E8=BE=91=E3=80=81=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E6=89=AB=E7=A0=81=E5=AF=B9=E7=84=A6=E4=B8=8EUI=E5=B8=83?=
=?UTF-8?q?=E5=B1=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 优化HomePage加载逻辑,修复加载弹窗重复显示问题
2. 重构蓝牙分享页、重命名页的返回事件绑定逻辑
3. 优化iOS/Android扫码对焦功能,支持多后置摄像头、软触发对焦
4. 调整扫码预览布局与参数,提升扫码识别率与适配性
5. 删除冗余的Android状态栏管理脚本与无用文件
6. 新增返回回调调试日志
---
Assets/BLE通信方案V2.2.md | 2105 -----------------
Assets/BLE通信方案V2.2.md.meta | 7 -
Assets/Plugins/iOS/CameraFocusHelper.mm | 22 +-
.../connectDevicePage.prefab | 92 +-
.../UI/Pages/ConnectDevice/ScanQRcode.cs | 471 +++-
.../UI/Pages/DeviceInfoPage/RenamePage.cs | 9 +-
.../Pages/DeviceInfoPage/ShareDevicePage.cs | 22 +-
.../Scripts/UI/Pages/HomePage/HomePageCtrl.cs | 4 +-
.../UI/Pages/HomePage/HomePageDevicePage.cs | 4 +-
Assets/Scripts/UI/UIManager.cs | 1 +
.../Scripts/Base/AndroidStatusBarManager.cs | 68 -
11 files changed, 495 insertions(+), 2310 deletions(-)
delete mode 100644 Assets/BLE通信方案V2.2.md
delete mode 100644 Assets/BLE通信方案V2.2.md.meta
delete mode 100644 Kill/Assets/Scripts/Base/AndroidStatusBarManager.cs
diff --git a/Assets/BLE通信方案V2.2.md b/Assets/BLE通信方案V2.2.md
deleted file mode 100644
index 8a947a0..0000000
--- a/Assets/BLE通信方案V2.2.md
+++ /dev/null
@@ -1,2105 +0,0 @@
-激光灭害虫设备 BLE 通信方案
-文档版本信息
-**版本**: v2.2
-**创建日期**: 2025-12-09
-**最后更新**: 2026-02-26
-**状态**: 当前版本
-更改记录
-
-
-1. 方案概述
-1.1 产品背景
-激光灭害虫设备是一款智能家居安全设备,通过激光技术精准消灭蚊虫等害虫。由于激光设备存在安全风险,需要严格的用户注册和认证机制。本方案从产品经理和用户角度出发,设计完整的 APP 与设备交互方案,确保安全性和易用性。
-1.2 通信架构
-
-**通信链路**:
-**APP ****↔**** ****BLE模块**: 通过 BLE 透传模式通信
-**BLE模块**** ****↔**** MCU**: 通过 UART9 串口通信
-**MCU**: STM32H7 主控制器,处理所有业务逻辑
-1.3 通信特点
-**透传模式**: BLE 模块工作在透传模式,数据透明传输
-**双向通信**: 支持 APP 到设备和设备到 APP 的双向数据传输
-**状态监控**: BLE 模块提供 LINK 和 WORK 状态引脚,实时监控连接状态
-**安全认证**: 支持用户注册绑定、密码登录、指纹登录等多种认证方式
-**OTA ****支持**: 支持BLE对MCU的固件更新
-**主动通知**: 设备可主动上报状态变化、错误信息、统计数据等
-
-
-2. BLE 指令分类表
-本表按功能分类展示了所有 BLE 通信指令,便于快速查找和理解指令体系结构。
-2.1 设备注册与认证类 (0x01-0x0F)
-
-2.2 系统控制类 (0x10-0x1F)
-2.3 外设控制类 (0x20-0x3F)
-2.4 害虫消灭控制类 (0x40-0x4F)
-2.5 安全设置类 (0x50-0x5F)
-2.6 高级管理类 (0x60-0x6F)
-2.7 连接管理类 (0x70-0x7F)
-2.8 OTA 升级类 (0x80-0x9F)
-2.9 状态查询类 (0xA0-0xBF)
-
-
-3. 设备功能归纳
-基于设备实际功能模块,从用户角度归纳 APP 可控制的功能。内部控制细节(如振镜电机参数)对用户隐藏,仅暴露用户友好的接口。
-3.1 数据库模块
-3.1.1 设备信息
-**设备序列号**: 唯一标识设备,用于设备识别和绑定(主要标识符)
-**固件信息**: 固件版本号,用于 OTA 升级判断
-**设备**** MAC ****地址**: BLE 模块 MAC 地址(通过 BLE 命令从设备端获取,Android/iOS 无法直接获取)
-**硬件版本**: 硬件版本信息
-**设备名称**: 用户自定义的设备名称(可选)
-**说明**:
-Android 6.0+ 和 iOS 7+ 出于隐私保护,无法直接获取 BLE 设备的真实 MAC 地址
-设备识别主要使用设备序列号
-如需 MAC 地址,需通过 BLE 命令(如查询设备信息命令)从设备端获取
-3.1.2 人员管理
-**用户名**: 用户注册时设置的用户名(最大长度 16 字节)
-**用户密码**: 用户设置的登录密码(加密存储,最大长度 16 字节)
-**指纹**** ID**: 用户指纹模板 ID(设备端存储,关联用户名)
-**绑定关系**: 设备与用户的绑定关系(一个设备可绑定多个用户,最多支持 10 个用户)
-**用户权限**: 管理员/普通用户权限区分(可选)
-3.2 系统控制
-3.2.1 语言设置
-**支持语言****: ****英文、中文**
-**切换方式****: APP ****设置后同步到设备,影响设备**** LCD ****显示和提示音**
-3.2.2 时间管理
-**时间设置****: ****设置设备系统时间(年、月、日、时、分、秒)**
-**日期显示****: ****设备**** LCD ****显示当前日期**
-**时间同步****: APP ****与设备时间同步**
-3.2.3 定时任务
-**功能说明****: ****在指定时间范围内自动运行指定的设备模式,支持按周重复执行。定时关机功能通过设置运行模式为"待机"实现。**
-**任务数量****: ****最多可设置5组定时任务**
-**任务参数****:**
-**任务ID**: 0-4(对应数组索引),标识不同的定时任务
-**开关**控制: 启用/禁用该定时任务(0x00=禁用, 0x01=启用)
-**开始**时间: 开始小时(1字节,0-23) + 开始分钟(1字节,0-59)
-**结束时间**: 结束小时(1字节,0-23) + 结束分钟(1字节,0-59)
-**运行模式**: 待机(0x00)、扫描(0x01)、消杀(0x02)
-**待机模式**: 设备进入待机状态,可用于定时关机功能
-**扫描模式**: 设备进入扫描检测状态
-**消杀模式**: 设备进入消杀工作状态
-**重复设置**: 1字节,按位表示星期几重复执行
-bit0: 周一 (0x01)
-bit1: 周二 (0x02)
-bit2: 周三 (0x04)
-bit3: 周四 (0x08)
-bit4: 周五 (0x10)
-bit5: 周六 (0x20)
-bit6: 周日 (0x40)
-示例:0x3F (0b00111111) 表示周一到周六,0x41 (0b01000001) 表示周一和周日
-**自动执行**:
-在设定的星期几,到达开始时间后,设备自动切换到指定模式
-到达结束时间后,设备自动停止或切换到待机模式
-对于待机模式(定时关机),设备进入待机状态并保持到结束时间
-**使用场景**:
-**定时关机**: 设置运行模式为"待机",在指定时间范围自动进入待机状态(如:每天23:00-06:00自动关机)
-**自动化灭虫任务**: 设置运行模式为"消杀",在指定时间范围自动开始灭虫工作(如:工作日20:00-22:00自动消杀)
-**定时扫描检测**: 设置运行模式为"扫描",在指定时间范围自动开始扫描检测(如:每天08:00-18:00自动扫描)
-**节能管理**: 通过定时任务实现自动化控制,避免长时间工作
-3.3 外设控制
-3.3.1 LCD 显示屏
-**休眠设置**:
-**休眠开关**: 开启/关闭 LCD 休眠功能
-**休眠时间**: 设置 LCD 无操作后进入休眠的时间(1分钟、5分钟、10分钟、30分钟)
-**亮度调节**:
-**自适应开关**: 开启/关闭自适应亮度功能(开启时根据环境光自动调节亮度)
-**亮度值**: 手动设置亮度等级(10-100%,仅在自适应关闭时有效)
-3.3.2 RGB 指示灯
-开关控制: 开启/关闭 RGB 指示灯
-颜色设置: 设置指示灯颜色(RGB 值)
-效果模式: 设置指示灯效果(闪烁、常亮、警示)
-3.3.3 WIFI 功能
-**开关控制**: 开启/关闭 WIFI 功能(如设备支持)
-开关值:0x00=关闭,0x01=开启
-**WIFI ****配置**: WIFI SSID 和密码设置
-SSID:最多32字节的ASCII字符串,以\0结尾
-密码:最多32字节的ASCII字符串,以\0结尾
-**数据格式**:
-读操作:固定返回65字节(开关1字节 + SSID 32字节 + 密码32字节)
-写操作:可变长度,至少1字节(开关),可选包含SSID和密码
-3.3.4 BLE 功能
-**蓝牙名称**: 设置 BLE 广播名称
-**说明**: BLE 功能始终开启,不支持通过 APP 关闭(关闭后无法通信)
-3.3.5 多媒体功能
-**视频录制**:
-**开关控**制: 开启/关闭视频录制功能(0x00=关闭,0x01=开启)
-录制时长: 设置单次录制时长(秒),支持3秒、5秒、10秒
-**音效设置**:
-**开关控制**: 开启/关闭音效(0x00=关闭,0x01=开启)
-音效类型: 选择不同的提示音效(5种类型)
-0x00 = Hello音效
-0x01 = Goodbye音效
-0x02 = Test音效
-0x03 = BIU音效
-0x04 = Main音效
-音量**控制**: 设置音量大小(0-15,对应芯片E0-EF共16级)
-**数据格式**:
-读操作:固定返回5字节(视频录制开关1字节 + 录制时长1字节 + 音效开关1字节 + 音效类型1字节 + 音量1字节)
-写操作:可变长度,至少1字节(视频录制开关),可选包含其他字段
-3.3.6 补光灯
-**功能说明**: 补光灯是外部设备,用于夜间补光,提高检测效果
-**开关控制**: 开启/关闭补光灯
-**补光类型**:
-**红外光**: 用于夜间补光,不影响人眼
-白光: 普通白光补光
-**光照强度设置**: **设置**补光灯光照强度(低、中、高)
-**连接状态监控**:
-设备实时检测补光灯连接状态
-补光灯连接/断开时主动上报状态
-APP 可查询补光灯连接状态
-3.3.7 可见光激光器
-**功能说明**: 可见光激光器用于提供可见激光指示,辅助瞄准和定位
-**开关控制**: 开启/关闭可见光激光器
-**使用场景**:
-激光瞄准辅助
-设备调试和校准
-用户可视化激光位置
-3.4 害虫消灭控制
-3.4.1 角度控制
-**扫描角度**: 设置激光扫描角度范围
-**数据**格式: 2字节,小端序(低字节在前,高字节在后)
-单位: 0.1度
-范围: 1-900(对应0.1度-90度)
-读操作: 返回当前角度范围设置(2字节)
-写操作: 设置角度范围(2字节),设备验证范围后更新数据库
-**数据库字段**: db_cfg.pest_ctrl.angle_range(uint16_t,单位:0.1度)
-3.4.2 距离控制
-**检测距离**: 设置害虫检测距离范围(米)
-**瞄准距离**: 设置激光瞄准距离(米)
-3.5 安全模块
-3.5.1 毫米波雷达
-**开关控制**: 开启/关闭毫米波雷达检测(0x00=关闭,0x01=开启)
-**灵敏度设置**: 设置人体检测灵敏度
-0x00 = 低灵敏度
-0x01 = 中灵敏度
-0x02 = 高灵敏度
-**安全距离**: 设置安全保护距离
-**数据格式**: 2字节,小端序(低字节在前,高字节在后)
-单位: 0.1米
-范围: 1-100(对应0.1米-10米)
-**数据格式**:
-读操作:返回4字节固定长度数据(开关1字节 + 灵敏度1字节 + 安全距离2字节)
-写操作:接收4字节数据(开关1字节 + 灵敏度1字节 + 安全距离2字节)
-**数据库字段**: db_cfg.safety.mmw_radar.enable、sensitivity、safe_distance
-3.5.2 视觉检测
-**开关控制**: 开启/关闭视觉检测功能(0x00=关闭,0x01=开启)
-**灵敏度设置**: 设置视觉检测灵敏度
-0x00 = 低灵敏度
-0x01 = 中灵敏度
-0x02 = 高灵敏度
-**数据格式**:
-读操作:返回2字节固定长度数据(开关1字节 + 灵敏度1字节)
-写操作:接收2字节数据(开关1字节 + 灵敏度1字节)
-**数据库字段**: db_cfg.safety.vision.enable、sensitivity
-**说明**: 摄像头可视范围固定,无需软件设置检测范围
-3.5.3 加速度传感器(振动检测)
-**功能说明**: 3轴加速度传感器用于检测设备振动状态,确保激光灭虫设备稳定放置
-**开关控制**: 开启/关闭加速度传感器检测(0x00=关闭,0x01=开启)
-**灵敏度设置**: 设置振动检测灵敏度
-0x00 = 低灵敏度
-0x01 = 中灵敏度
-0x02 = 高灵敏度
-**振动阈值**: 设置振动检测阈值
-**范围**: 1-255
-说明: 超过阈值时触发告警或停止工作
-**数据格式(命令0x52**** - ****加速度传感器设置)**:
-读操作:返回3字节固定长度数据(开关1字节 + 灵敏度1字节 + 振动阈值1字节)
-写操作:接收3字节数据(开关1字节 + 灵敏度1字节 + 振动阈值1字节)
-**数据格式(命令0x53**** - ****查询加速度传感器数据)**:
-读操作:返回6字节固定长度数据(X轴加速度2字节 + Y轴加速度2字节 + Z轴加速度2字节)
-每个轴数据:2字节,小端序,有符号整数(int16_t),单位:mg
-数据范围:-32768到32767(对应-32768mg到32767mg)
-**数据库字段**: db_cfg.safety.accel.enable、sensitivity、threshold
-**实时数据来源**: meas_ctrl.accel.x_mg、y_mg、z_mg(float类型,单位:mg)
-**应用场景**:
-设备放置稳定性检测
-设备被移动或碰撞检测
-设备异常振动告警
-确保激光精准瞄准(设备不稳定时禁止或暂停激光发射)
-实时数据监控(查询X、Y、Z轴加速度值,可通过命令0xA5"查询设备传感器数据"获取)
-3.5.4 温度监控
-**功能说明**: 监控设备当前温度,防止设备过热损坏
-**温度检测**: 实时检测设备温度(单位:摄氏度)
-**温度阈值**: 设置温度告警阈值和停止阈值
-**超温保护**: 温度超过阈值时自动停止设备工作并上报状态
-**温度数据查询**: 通过命令0xA5"查询设备传感器数据"获取当前温度值
-**应用场景**:
-设备温度实时监控
-超温保护(防止设备过热)
-温度异常告警
-确保设备安全运行
-**安全机制**:
-温度超过停止阈值时,设备立即停止所有工作
-设备主动上报超温状态给 APP(通过通知类型0x02错误状态上报中的温度错误字段)
-温度恢复正常后,需要用户确认才能重新启动
-3.6 设备状态监控
-3.6.1 工作模式
-**待机模式**: 设备已启动但未开始工作
-**扫描模式**: 设备正在扫描检测害虫
-**消杀模式**: 设备正在执行消杀任务
-3.6.2 硬件状态
-**硬件错误状态查询**: 通过命令0xA1查询各硬件模块的错误状态
-温度错误: 0x00=无错误, 0x01=温度过高, 0x02=温度过低
-电机错误: 0x00=无错误, 0x01=温度错误, 0x02=反馈错误
-激光器错误: 0x00=无错误, 0x01=温度过高, 0x02=电压过低, 0x03=电压过高
-视觉错误: 0x00=无错误, 0x01=通信错误
-加速度错误: 0x00=无错误, 0x01=通信错误
-毫米波雷达错误: 0x00=无错误, 0x01=通信错误
-激光雷达错误: 0x00=无错误, 0x01=通信错误
-**数据来源**: meas_ctrl.error(测量控制模块的错误状态)
-**通信状态**: BLE 连接状态(由 APP 端 BLE SDK 检测,设备端通过硬件 LINK 引脚检测)
-3.6.3 统计数据
-**统计数据查询**: 通过命令0xA3查询设备工作统计数据和历史日志索引
-统计数据: 灭虫数量、总工作时间、今日工作时间、扫描次数、激光发射次数、蚊虫数据数量
-datalog日志记录数量。
-数据来源: db_cfg.statistics(统计数据)和db_cfg.datalog(日志索引)
-传感器异常: 传感器故障或异常
-温度异常: 设备温度超过阈值
-系统错误: 系统级错误
-3.6.4 传感器数据查询
-**传感器数据查询**: 通过命令0xA5一次性查询设备温度、3D传感器数据和电容电压
-**温度数据**: 2字节,单位:0.1°C(例如250表示25.0°C)
-**3D传**感器数据: 6字节,X/Y/Z轴加速度各2字节,单位:mg,有符号整数(范围:-32768到32767)
-电容电压: 2字节,单位:0.01V(例如1530表示15.30V)
-数据来源:
-温度:meas_ctrl.temp(测量控制模块的温度值)
-3D传感器:meas_ctrl.accel.x_mg、y_mg、z_mg(测量控制模块的加速度数据)
-电容电压:capture_get_laser_gun_dev()->current_voltage(激光器设备的当前电容电压)
-**应用场景**:
-设备状态实时监控
-传感器数据采集
-设备健康检查
-调试和诊断
-3.7 高级管理
-3.7.1 恢复出厂设置
-**功能说明****: ****清除所有用户设置,恢复设备到出厂状态**
-**安全要求****: ****需要管理员权限或特殊认证**
-3.8 固件管理
-3.8.1 固件更新(OTA)
-**版本查询****: ****查询OTA版本信息(命令0x80,返回固件版本、硬件版本、OTA版本)**
-**升级检查****: ****检查是否有新版本固件**
-**OTA ****升级****: ****通过**** BLE ****进行固件升级**
-
-
-4. BLE 控制场景分类
-基于用户功能模块,将 BLE 控制场景分为以下八大类:
-4.1 设备注册与认证类
-4.1.1 设备首次注册绑定
-**场景描述**: 设备首次开机,用户在 APP 完成注册和安全考试后,将用户信息绑定到设备
-**典型操作**:
-APP 检测设备未注册状态(通过命令 0x01 查询)
-用户在 APP 完成安全考试(APP 端实现,考试内容关于激光安全使用)
-用户填写用户名和密码(APP 端验证)
-APP 将设备序列号与用户信息绑定关系存储到云端数据库
-APP 发送设备注册确认到设备(命令 0x02,仅发送用户名,不发送密码)
-设备存储用户名信息(设备端不存储密码)
-设备返回绑定成功确认
-**使用场景**:
-新设备首次使用
-设备恢复出厂设置后重新注册
-**安全要求**:
-设备与用户绑定关系存储在云端(APP数据库)
-设备端仅存储用户名,不存储密码
-一个设备最多绑定 10 个用户
-首次注册后设备进入已注册状态,后续操作需要登录
-**通信流程**:
-
-4.1.2 用户登录认证
-**场景描述**: 用户通过 APP 登录设备,验证用户权限
-**认证方式**:
-**密码登录**: APP 在云端验证用户名和密码,验证通过后发送用户名和解锁状态到设备(命令 0x03),设备接收后解锁
-**指纹登录**: 设备本地验证指纹(指纹验证由设备端硬件模块完成,不通过BLE命令)
-**典型操作**:
-**密码登录流程**:
-用户在 APP 输入用户名和密码
-APP 在云端验证设备序列号与用户的绑定关系
-APP 在云端验证用户名和密码
-验证通过后,APP 发送用户名和解锁状态到设备(命令 0x03)
-设备接收解锁状态,记录登录时间
-设备返回解锁成功确认
-**指纹登录使能设置流程**:
-用户必须先通过密码登录解锁设备
-APP 发送用户名和使能状态到设备(命令 0x04)
-设备验证用户名是否存在
-设备设置该用户的指纹登录使能状态(finger_id_valid字段)
-设备保存设置并返回结果
-认证成功后,APP 获得设备控制权限,可执行后续操作
-**使用场景**:
-每次 APP 连接设备时(已注册设备必须登录)
-设备长时间未操作后重新认证(建议超时时间 30 分钟)
-**安全要求**:
-密码验证在云端完成,设备端不验证密码
-设备与用户绑定关系存储在云端,APP通过云端验证绑定关系
-连续 5 次登录失败后,APP 端锁定 5 分钟
-登录成功后,设备记录登录时间,用于超时判断
-**通信流程**:
-**密码登录流程**:
-
-**指纹登录使能设置流程**:
-
-4.1.3 指纹录制管理
-**场景描述**: 用户在设备上录制指纹,用于后续指纹登录
-**典型操作**:
-用户必须先通过APP登录(首次录制需密码登录,因为尚未有指纹)
-APP 发起指纹录制请求(命令 0x05,指定用户名)
-设备检查用户是否已解锁(设备端记录登录状态)
-设备验证用户名是否存在
-设备确定page_id:
-如果用户已有finger_id,使用它重新注册(覆盖旧指纹)
-如果用户没有finger_id,分配新的page_id(查找数据库中已使用的最大page_id + 1)
-设备检查指纹库是否已满
-设备启动指纹注册状态机(异步处理)
-设备引导用户按压指纹(循环采集9次)
-设备验证指纹质量,合并特征,存储模板
-注册成功后,设备自动更新数据库:
-设置 finger_id = page_id
-设置 finger_id_valid = 1
-保存到数据库
-设备返回录制结果(成功/失败)
-**使用场景**:
-用户首次设置指纹登录
-重新录制指纹(覆盖旧指纹)
-**安全要求**:
-必须已通过APP登录(设备已解锁)才能录制指纹
-不能有注册正在进行中(同一时间只能有一个注册流程)
-指纹数据仅存储在设备端,不传输到 APP
-指纹模板与用户名关联,存储到指纹库的指定page_id位置
-注册完成后自动更新数据库,无需额外命令
-**通信流程**:
-
-4.1.4 用户权限查询
-**场景描述**: 查询设备绑定的用户列表和权限信息
-**典型操作**:
-APP 从云端查询设备绑定的用户列表(绑定关系在云端)
-APP 查询设备端存储的用户名列表(命令 0x07)
-设备返回用户名、指纹ID、指纹ID有效标志等信息(设备端仅存储用户名和指纹信息)
-**使用场景**:
-多用户管理
-权限验证
-**说明**: 设备与用户的完整绑定关系存储在云端,设备端仅存储用户名列表用于指纹验证
-**通信流程**:
-
-4.1.6 设备分享流程
-**场景描述**: 设备所有者(管理员)将设备分享给其他用户,被分享者通过BLE连接设备并完成注册绑定
-**典型操作**:
-**步骤1:所有者发起分享**:
-设备所有者(管理员)在APP中选择"分享设备"功能
-APP向云端服务器发送分享请求(设备序列号 + 被分享者手机号/账号)
-云端验证所有者权限,生成分享邀请(可选:生成分享邀请码)
-云端向被分享者发送分享通知(短信/推送消息)
-**步骤2:被分享者接受分享**:
-被分享者收到分享通知,打开APP查看分享邀请
-APP从云端查询分享邀请状态和设备信息
-被分享者确认接受分享
-**步骤3:被分享者连接设备并**注册:
-被分享者需要**物理靠近设备**(BLE连接需要近距离)
-被分享者APP扫描并发现设备(通过设备序列号识别)
-被分享者APP发起BLE连接请求
-BLE连接建立后,APP查询设备注册状态(命令 0x01)
-设备返回已注册状态
-被分享者在APP上完成安全考试(APP端实现)
-被分享者填写用户名和密码(APP端验证格式)
-APP向云端验证分享邀请有效性
-云端将设备序列号与新用户绑定(存储到云端数据库)
-APP发送设备注册确认到设备(命令 0x02,仅发送用户名)
-设备检查用户数量(最多支持10个用户)
-设备存储用户名信息(设备端不存储密码)
-设备返回注册成功确认
-APP向云端确认分享完成
-云端通知所有者分享成功
-**使用场景**:
-家庭成员共享设备
-多用户协作使用设备
-**安全要求**:
-被分享者必须物理靠近设备才能完成BLE配对(安全要求)
-被分享者必须完成安全考试才能注册
-设备最多支持10个用户,超过限制时注册失败
-设备与用户绑定关系存储在云端,设备端仅存储用户名
-**重要说明**:
-设备分享必须通过BLE本地连接完成,不支持远程分享
-被分享者需要在现场才能完成BLE配对和注册
-分享流程与首次注册流程相同,都需要通过BLE连接设备
-**通信流程**:
-
-4.2 系统控制类
-4.2.1 语言设置
-**场景描述**: 设置设备显示语言
-**典型操作**:
-APP 发送语言设置命令(英文/中文)
-设备保存语言设置
-设备 LCD 显示更新为对应语言
-**使用场景**: 用户个性化设置
-**通信流程**:
-
-4.2.2 时间管理
-**场景描述**: 设置和同步设备时间
-**典型操作**:
-APP 发送时间设置命令(年、月、日、时、分、秒)
-设备保存时间设置
-APP 查询设备当前时间
-**使用场景**: 时间同步、定时功能设置
-**通信流程**:
-
-4.2.3 定时任务设置
-**场景描述**: 设置定时任务,在指定时间范围内自动运行指定的设备模式,支持按周重复执行。定时关机功能通过设置运行模式为"待机"实现。
-**典型操作**:
-APP 添加/修改定时任务(任务ID、开关、开始时间、结束时间、模式、重复设置)
-APP 查询所有定时任务列表
-APP 删除定时任务(设置开关为关闭)
-设备在指定时间范围内自动执行定时任务
-**任务参数**:
-**任务ID**: 0-4(对应数组索引),最多支持5组定时任务
-**开关**: 启用/禁用该定时任务(0x00=禁用, 0x01=启用)
-**开始时间**: 开始小时(1字节,0-23) + 开始分钟(1字节,0-59)
-**结束时间**: 结束小时(1字节,0-23) + 结束分钟(1字节,0-59)
-**运行模式**: 待机(0x00)、扫描(0x01)、消杀(0x02)
-**待机**模式**(0x00)**: 设备进入待机状态,可用于定时关机功能
-扫描模式(0x01): 设备进入扫描检测状态
-消杀模式(0x02): 设备进入消杀工作状态
-**重复设置**: 1字节,按位表示星期几重复执行
-bit0: 周一 (0x01)
-bit1: 周二 (0x02)
-bit2: 周三 (0x04)
-bit3: 周四 (0x08)
-bit4: 周五 (0x10)
-bit5: 周六 (0x20)
-bit6: 周日 (0x40)
-示例:0x3F (0b00111111) 表示周一到周六,0x41 (0b01000001) 表示周一和周日
-**使用场景**:
-**定时关机**: 设置运行模式为"待机",在指定时间范围自动进入待机状态(如:每天23:00-06:00自动关机)
-自动化灭虫任务: 设置运行模式为"消杀",在指定时间范围自动开始灭虫工作(如:工作日20:00-22:00自动消杀)
-定时扫描检测: 设置运行模式为"扫描",在指定时间范围自动开始扫描检测(如:每天08:00-18:00自动扫描)
-节能管理: 通过定时任务实现自动化控制,避免长时间工作
-**通信流程**:
-
-4.3 外设控制类
-4.3.1 LCD 控制
-**场景描述**: 设置 LCD 显示屏休眠功能和亮度
-**典型操作**:
-APP 设置 LCD 休眠开关和休眠时间(命令 0x20)
-APP 设置亮度自适应开关和亮度值(命令 0x21)
-APP 设置亮度值(10-100%,仅在自适应关闭时有效)
-**使用场景**: 节能管理、显示优化
-**休眠设置说明**:
-**休眠**开关: 0x00=关闭休眠功能,0x01=开启休眠功能
-休眠时间: 0x01=1分钟,0x05=5分钟,0x0A=10分钟,0x1E=30分钟
-当休眠功能开启时,LCD 在无操作达到设定时间后自动进入休眠状态
-休眠状态下 LCD 关闭显示以节省电量,用户操作时自动唤醒
-**通信流程**:
-
-4.3.2 RGB 指示灯控制
-**场景描述**: 控制 RGB 指示灯的颜色和效果
-**典型操作**:
-APP 控制指示灯开关
-APP 设置指示灯颜色(RGB 值)
-APP 设置指示灯效果(闪烁、常亮、警示)
-**使用场景**: 状态指示、个性化设置
-**通信流程**:
-
-4.3.3 WIFI 控制
-**场景描述**: 控制 WIFI 功能开关和配置
-**典型操作**:
-APP 查询当前 WIFI 配置(读操作)
-APP 控制 WIFI 开关(写操作,仅发送开关字节)
-APP 配置 WIFI SSID 和密码(写操作,发送开关+SSID+密码)
-**使用场景**: 网络连接管理
-**数据格式说明**:
-**读操作**: 设备返回65字节固定长度数据
-字节0:开关状态(0x00=关闭,0x01=开启)
-字节1-32:SSID字符串(32字节,ASCII编码)
-字节33-64:密码字符串(32字节,ASCII编码)
-**写操作**: APP发送可变长度数据
-字节0:开关值(0x00=关闭,0x01=开启,必填)
-字节1-N:SSID字符串(以\0结尾,最多32字节,可选)
-字节N+1-M:密码字符串(以\0结尾,最多32字节,可选)
-说明:如果只更新开关,只需发送1字节;如果更新SSID,发送开关+SSID;如果同时更新SSID和密码,发送完整数据
-**通信流程**:
-
-4.3.4 BLE 控制
-**场景描述**: 设置 BLE 广播名称
-**典型操作**:
-APP 设置 BLE 广播名称
-**使用场景**: 蓝牙管理、设备识别
-**说明**:
-BLE 功能始终开启,不支持通过 APP 关闭(关闭后无法与 APP 通信)
-Android/iOS 无法直接获取 BLE MAC 地址,设备识别主要使用设备序列号
-**通信流程**:
-
-4.3.5 多媒体控制
-**场景描述**: 控制视频录制和音效设置
-**典型操作**:
-APP 查询当前多媒体配置(读操作)
-APP 控制视频录制开关(写操作,仅发送开关字节)
-APP 设置录制时长(写操作,发送开关+时长)
-APP 控制音效开关(写操作,发送视频录制相关字段+音效开关)
-APP 选择音效类型和音量(写操作,发送完整数据)
-**使用场景**: 功能配置
-**数据格式说明**:
-**读操作**: 设备返回5字节固定长度数据
-字节0:视频录制开关(0x00=关闭,0x01=开启)
-字节1:录制时长(1字节,0x03=3秒, 0x05=5秒, 0x0A=10秒)
-字节2:音效开关(0x00=关闭,0x01=开启)
-字节3:音效类型(0x00=Hello, 0x01=Goodbye, 0x02=Test, 0x03=BIU, 0x04=Main)
-字节4:音量(0-15,对应芯片E0-EF共16级)
-**写操作**: APP发送可变长度数据
-字节0:视频录制开关(0x00=关闭,0x01=开启,必填)
-字节1:录制时长(1字节,可选,0x03=3秒, 0x05=5秒, 0x0A=10秒)
-字节2:音效开关(0x00=关闭,0x01=开启,可选)
-字节3:音效类型(0x00-0x04,可选)
-字节4:音量(0-15,可选)
-说明:如果只更新视频录制开关,只需发送1字节;如果更新录制时长,发送开关+时长(2字节);如果同时更新音效,发送完整数据(5字节)
-**通信流程**:
-
-4.3.6 补光灯控制
-**场景描述**: 控制补光灯开关、类型和光照强度,监控补光灯连接状态
-**典型操作**:
-APP 控制补光灯开关
-APP 选择补光类型(红外光/白光)
-APP 设置补光光照强度(低/中/高)
-APP 查询补光灯连接状态
-设备检测到补光灯连接/断开时主动上报状态
-**使用场景**:
-夜间补光、检测优化
-补光灯设备管理
-连接状态监控
-**连接状态**:
-补光灯连接时,设备主动上报连接状态(通知类型 0x09)
-补光灯断开时,设备主动上报断开状态
-APP 可随时查询补光灯连接状态
-**通信流程**:
-
-4.3.7 可见光激光器控制
-**场景描述**: 控制可见光激光器开关
-**典型操作**:
-APP 控制可见光激光器开关
-APP 查询可见光激光器状态
-**使用场景**:
-激光瞄准辅助
-设备调试和校准
-用户可视化激光位置
-**通信流程**:
-
-4.4 害虫消灭控制类
-4.4.1 角度控制
-**场景描述**: 设置激光扫描角度范围
-**典型操作**:
-APP 查询当前角度范围设置(读操作)
-APP 设置扫描角度范围(写操作,发送2字节角度值)
-**使用场景**: 扫描范围调整
-**数据格式说明**:
-**读操作**: 设备返回2字节固定长度数据
-字节0-1:角度范围(2字节,小端序,单位:0.1度,范围:1-900)
-**写操作**: APP发送2字节数据
-字节0-1:角度范围(2字节,小端序,单位:0.1度,范围:1-900)
-说明:设备会验证角度范围是否在有效范围内(1-900),超出范围将返回参数错误
-**通信流程**:
-
-4.4.2 距离控制
-**场景描述**: 设置害虫检测和瞄准距离
-**典型操作**:
-APP 设置检测距离范围(米)
-APP 设置瞄准距离(米)
-**使用场景**: 检测范围优化
-**通信流程**:
-
-4.5 安全设置类
-4.5.1 毫米波雷达设置
-**场景描述**: 配置毫米波雷达检测参数
-**典型操作**:
-APP 查询当前毫米波雷达设置(读操作)
-APP 控制毫米波雷达开关(写操作)
-APP 设置检测灵敏度(低/中/高)
-APP 设置安全保护距离
-**使用场景**: 人体检测、安全保护
-**数据格式说明**:
-**读操作**: 设备返回4字节固定长度数据
-字节0:开关(0x00=关闭,0x01=开启)
-字节1:灵敏度(0x00=低,0x01=中,0x02=高)
-字节2-3:安全距离(2字节,小端序,单位:0.1米,范围:1-100)
-**写操作**: APP发送4字节数据
-字节0:开关(0x00=关闭,0x01=开启)
-字节1:灵敏度(0x00=低,0x01=中,0x02=高)
-字节2-3:安全距离(2字节,小端序,单位:0.1米,范围:1-100)
-说明:设备会验证所有参数的有效性,超出范围将返回参数错误
-**通信流程**:
-
-4.5.2 视觉检测设置
-**场景描述**: 配置视觉检测参数
-**典型操作**:
-APP 查询当前视觉检测设置(读操作)
-APP 控制视觉检测开关(写操作)
-APP 设置检测灵敏度(低/中/高)
-**使用场景**: 安全检测、目标识别
-**说明**: 摄像头可视范围固定,无需软件设置检测范围
-**数据格式说明**:
-**读操作**: 设备返回2字节固定长度数据
-字节0:开关(0x00=关闭,0x01=开启)
-字节1:灵敏度(0x00=低,0x01=中,0x02=高)
-**写操作**: APP发送2字节数据
-字节0:开关(0x00=关闭,0x01=开启)
-字节1:灵敏度(0x00=低,0x01=中,0x02=高)
-说明:设备会验证所有参数的有效性,超出范围将返回参数错误
-**通信流程**:
-
-4.5.3 加速度传感器设置
-**场景描述**: 配置3轴加速度传感器参数,用于检测设备振动状态
-**典型操作**:
-APP 查询当前加速度传感器设置(命令0x52,读操作)
-APP 控制加速度传感器开关(命令0x52,写操作)
-APP 设置振动检测灵敏度(低/中/高)
-APP 设置振动检测阈值
-APP 查询加速度传感器实时数据(命令0x53,读操作)
-**使用场景**:
-设备稳定性检测(确保设备稳定放置)
-设备移动检测(检测设备是否被移动)
-异常振动告警(检测设备异常振动)
-安全保护(设备不稳定时禁止或暂停激光发射)
-实时数据监控(查询X、Y、Z轴加速度值)
-**安全要求**:
-检测到设备不稳定时,设备自动停止激光发射
-设备稳定后,需要用户确认才能重新启动
-振动数据实时监控,超过阈值立即响应
-**查询加速度传感器数据(命令0x53)**:
-**数据格式说明**:
-**读操作**: 设备返回6字节固定长度数据
-字节0-1:X轴加速度(2字节,小端序,有符号整数int16_t,单位:mg)
-字节2-3:Y轴加速度(2字节,小端序,有符号整数int16_t,单位:mg)
-字节4-5:Z轴加速度(2字节,小端序,有符号整数int16_t,单位:mg)
-数据范围:-32768到32767(对应-32768mg到32767mg)
-说明:数据格式为小端序(低字节在前,高字节在后),有符号整数
-**数据格式说明(命令0x52**** - ****加速度传感器设置)**:
-**读操作**: 设备返回3字节固定长度数据
-字节0:开关(0x00=关闭,0x01=开启)
-字节1:灵敏度(0x00=低,0x01=中,0x02=高)
-字节2:振动阈值(1-255)
-**写操作**: APP发送3字节数据
-字节0:开关(0x00=关闭,0x01=开启)
-字节1:灵敏度(0x00=低,0x01=中,0x02=高)
-字节2:振动阈值(1-255)
-说明:设备会验证所有参数的有效性,超出范围将返回参数错误
-**数据格式说明(命令0x53**** - ****查询加速度传感器数据)**:
-**读操作**: 设备返回6字节固定长度数据
-字节0-1:X轴加速度(2字节,小端序,有符号整数int16_t,单位:mg)
-字节2-3:Y轴加速度(2字节,小端序,有符号整数int16_t,单位:mg)
-字节4-5:Z轴加速度(2字节,小端序,有符号整数int16_t,单位:mg)
-数据范围:-32768到32767(对应-32768mg到32767mg)
-说明:数据格式为小端序(低字节在前,高字节在后),有符号整数,数据来源于meas_ctrl.accel
-**通信流程(命令0x52**** - ****加速度传感器设置)**:
-
-**通信流程(命令0x53**** - ****查询加速度传感器数据)**:
-
-**实时监控流程**:
-
-4.5.4 温度监控设置
-**场景描述**: 配置温度监控参数,监控设备温度状态
-**典型操作**:
-APP 设置温度告警阈值
-APP 设置温度停止阈值
-APP 查询温度数据(通过命令0xA5"查询设备传感器数据"获取温度值)
-**使用场景**:
-设备温度实时监控
-超温保护设置
-温度异常告警
-设备安全运行保障
-**安全要求**:
-温度超过停止阈值时,设备自动停止工作
-温度超过告警阈值时,设备主动上报告警(通过通知类型 0x02 错误状态上报中的温度错误字段)
-温度恢复正常后,需要用户确认才能重新启动
-温度数据实时监控,超过阈值立即响应
-**通信流程**:
-
-4.6 高级管理类
-4.6.1 恢复出厂设置
-**场景描述**: 清除所有用户设置,恢复设备到出厂状态
-**典型操作**:
-APP 发送恢复出厂设置命令(需要管理员权限)
-设备清除所有用户数据、绑定关系、参数设置
-设备返回恢复完成确认
-**使用场景**:
-设备故障恢复
-设备转售前清理
-**通信流程**:
-
-4.6.2 设备信息查询
-**场景描述**: 查询设备基本信息
-**典型操作**:
-APP 查询设备序列号
-APP 查询OTA版本信息(命令0x80)
-APP 查询硬件版本
-APP 查询设备 MAC 地址
-**使用场景**: 设备识别、版本管理
-**说明**:
-Android 6.0+ 和 iOS 7+ 无法直接获取 BLE 设备的真实 MAC 地址
-设备识别主要使用设备序列号
-MAC 地址需通过查询设备信息命令(0xA2)从设备端获取
-**通信流程**:
-
-4.7 OTA 升级类
-4.7.1 OTA版本信息查询
-**场景描述**: 查询设备当前固件版本、硬件版本和OTA版本信息
-**典型操作**:
-APP 查询OTA版本信息(命令0x80),返回12字节版本信息(固件版本4 + 硬件版本4 + OTA版本4)
-设备返回版本信息(固件版本、硬件版本、OTA版本)
-或者 APP 查询设备信息(命令0xA2),从返回数据中提取版本信息
-**使用场景**: 版本检查、升级判断、OTA升级前版本验证
-**说明**:
-命令0x80专门用于OTA升级场景,返回12字节版本信息(固件版本4 + 硬件版本4 + OTA版本4)
-命令0xA2返回完整的设备信息(76字节),包含版本信息和其他设备信息
-数据来源:db_cfg.device_info(与命令0xA2的版本信息字段一致)
-**通信流程**:
-
-4.7.2 固件升级
-**场景描述**: 通过 BLE 进行设备固件升级
-**操作流程**:
-APP 查询OTA版本信息(命令0x80),检查是否需要升级
-APP 发送升级开始命令(命令0x81),发送版本号、固件大小和CRC16校验和
-设备擦除1MB SPI Flash区域(地址0x100000),写入文件头(版本号、固件大小、CRC16占位为0),保存期望的CRC16
-APP 分包传输固件数据(命令0x82),设备写入Flash并实时计算CRC16
-当所有数据接收完成时,设备比较计算的CRC16与期望的CRC16,如果匹配则设置force_boot=1(有效),否则设置force_boot=0(无效)
-APP 发送结束传输命令(命令0x83),发送总大小
-设备验证接收数据大小,保存CRC16到文件头,更新数据库OTA版本和force_boot标志,返回成功
-**使用场景**: 功能更新、Bug 修复
-**Flash存储格式**:
-存储位置:SPI Flash地址0x100000,大小1MB
-文件头(10字节):版本号(4) + 固件大小(4) + CRC16(2)
-文件体:固件数据(从地址0x10000A开始)
-数据格式:所有多字节数据采用小端序
-**说明**: 升级进度由 APP 端和设备端各自计算,无需查询命令。APP 端根据已发送数据包和总数据量计算进度,设备端根据已接收数据包和总数据量计算进度。
-**通信流程**:
-
-4.8 设备状态监控类
-4.8.1 工作模式查询
-**场景描述**: 查询设备当前工作模式
-**典型操作**:
-APP 查询设备工作模式(命令 0xA0)
-设备返回模式信息(待机/扫描/消杀)
-**模式说明**:
-**待机模式**** (0x00)**: 设备已启动但未开始工作
-**扫描模式**** (0x01)**: 设备正在扫描检测害虫
-**消杀模式**** (0x02)**: 设备正在执行消杀任务
-**数据格式**:
-**读操作**: 返回1字节
-字节0:工作模式值(0x00=待机, 0x01=扫描, 0x02=消杀)
-**写操作**: 不支持
-**数据来源**: 从数据库 db_cfg.system.work_mode 读取当前工作模式设置
-**使用场景**: 设备状态监控、工作流程判断
-**通信流程**:
-
-4.8.2 硬件状态查询
-**场景描述**: 查询设备各硬件模块的错误状态
-**典型操作**:
-APP 查询硬件状态(命令 0xA1,读操作)
-设备返回7字节硬件错误状态
-**数据格式**:
-字节0 - 温度错误: 0x00=无错误, 0x01=温度过高, 0x02=温度过低
-字节1 - 电机错误: 0x00=无错误, 0x01=温度错误, 0x02=反馈错误
-字节2 - 激光器错误: 0x00=无错误, 0x01=温度过高, 0x02=电压过低, 0x03=电压过高
-字节3 - 视觉错误: 0x00=无错误, 0x01=通信错误
-字节4 - 加速度错误: 0x00=无错误, 0x01=通信错误
-字节5 - 毫米波雷达错误: 0x00=无错误, 0x01=通信错误
-字节6 - 激光雷达错误: 0x00=无错误, 0x01=通信错误
-**数据来源**: meas_ctrl.error(测量控制模块的错误状态)
-**使用场景**: 故障诊断、设备维护、实时监控
-**通信流程**:
-
-4.8.3 统计数据查询
-**场景描述**: 查询设备工作统计数据和历史日志索引信息
-**典型操作**:
-APP 查询统计数据(命令 0xA3,读操作)
-设备返回统计数据(灭虫数量、工作时间、扫描次数等)和datalog日志索引信息
-**统计数据说明**:
-灭虫数量: 累计灭虫总数(uint32_t)
-总工作时间: 设备累计工作时间(uint32_t,单位:秒)
-今日工作时间: 今日累计工作时间(uint32_t,单位:秒)
-扫描次数: 累计扫描次数(uint32_t)
-激光发射次数: 累计激光发射次数(uint32_t)
-蚊虫数据数量: 扫描到、杀了的蚊子总数(uint32_t)
-本地数据记录数量;
-**数据来源**: db_cfg.statistics(统计数据)和db_cfg.datalog(日志索引)
-**使用场景**: 数据统计、历史数据查询、设备性能分析
-**通信流程**:
-
-4.8.4 蚊虫数据查询
-**场景描述**: 从SPI Flash读取指定索引范围的蚊虫数据
-**典型操作**:
-APP 设置索引范围(命令 0xA4,写操作)
-设备返回成功响应,然后通过通知机制发送每条数据
-APP 接收通知数据包,解析并显示蚊虫数据
-**索引范围设置**:
-**指定范围**: start_index(4字节) + end_index(4字节)
-**读取全部**: start_index=0xFFFFFFFF,自动设置为0到kill_count-1
-**数据格式**:
-通知数据包: 索引(4字节) + 蚊虫数据(18字节)
-蚊虫数据: 角度(*1000度,int32_t,支持负数,例如90000表示90.000度,-22500表示-22.500度) + 距离(毫米) + 大小(毫米) + 时间(年月日时分秒) + 保留字段
-**发送间隔**: 每条数据间隔10ms
-**数据来源**: SPI Flash(从SPI_FLASH_MOSQUITO_DATA_ADDR开始存储)
-**使用场景**: 历史数据查询、数据分析、数据导出
-**通信流程**:
-
-4.8.5 查询设备传感器数据
-**场景描述**: 一次性查询设备温度、3D传感器数据和电容电压
-**典型操作**:
-APP 查询设备传感器数据(命令 0xA5)
-设备返回10字节传感器数据(温度、X/Y/Z轴加速度、电容电压)
-APP 解析并显示传感器数据
-**使用场景**:
-设备状态实时监控
-传感器数据采集
-设备健康检查
-调试和诊断
-**通信流程**:
-
-4.8.6 状态变化通知
-**场景描述**: 设备状态变化时主动通知 APP
-**典型操作**:
-设备检测到状态变化(工作状态、硬件状态等)
-设备主动发送状态通知(通知类型 0x01)
-APP 接收并显示状态变化
-**使用场景**: 实时状态监控、异常告警
-4.9 连接管理类
-4.9.1 断开连接
-**场景描述**: 主动断开 BLE 连接
-**典型操作**:
-APP 发送断开连接命令(命令 0x70)
-设备断开 BLE 连接
-APP 端 BLE SDK 检测到连接断开,更新设备状态
-**使用场景**: 用户主动断开、设备切换、多用户切换
-**通信流程**:
-**设备连接流程**:
-
-4.9.2 多用户连接管理
-**场景描述**: 多个设备使用者同时在设备边上时,BLE连接管理策略
-**BLE连接限制**:
-点对点连接: BLE是点对点连接协议,同一时间设备只能连接一个手机
-连接独占: 当一个手机已连接设备时,其他手机无法连接
-连接冲突: 多个手机同时尝试连接时,设备只能接受一个连接请求
-**连接策略**:
-**推荐方案:先连接者优先**** + ****主动断开机制**
-**先连接者优先**:
-第一个成功连接设备的手机获得连接权限
-其他手机连接请求被拒绝
-已连接的用户可以正常使用设备(登录、控制、查询等)
-**主动断开机制**:
-当前连接用户可以通过命令 0x70 主动断开连接
-APP提供"断开连接"按钮,方便用户主动断开
-断开后,设备释放连接资源,其他用户可以连接
-**连接状态提示**:
-尝试连接但设备已被占用时,APP显示"设备已被其他用户连接,请等待或联系当前用户断开连接"
-当前连接用户APP显示"设备已连接"状态
-其他用户APP显示"设备已被占用"状态
-**连接冲突处理流程**:
-
-**可选优化策略**:
-**超时自动断开**:
-连接空闲超过一定时间(如30分钟)自动断开
-释放连接资源,方便其他用户连接
-需要用户重新连接和登录
-**管理员优先连接**:
-管理员用户连接时,可请求当前用户断开(需用户确认)
-管理员连接请求优先级更高
-需要用户确认机制,避免强制断开
-**信号强度优先**:
-多个连接请求时,选择信号强度(RSSI值)最强的手机
-适用于多个用户同时在场的情况
-需要BLE模块支持多连接请求处理
-**实现要点**:
-**设备端**:
-通过LINK引脚检测连接状态(高电平=已连接)
-连接建立时,记录连接状态
-连接断开时,释放连接资源
-拒绝新的连接请求(如果已有连接)
-**APP端**:
-通过BLE SDK回调检测连接状态
-连接失败时,显示友好的错误提示
-提供"断开连接"按钮,方便用户主动断开
-显示连接状态(已连接/未连接/连接中)
-**使用场景**:
-多个家庭成员同时在场,需要切换用户控制设备
-设备管理员需要临时接管设备控制权
-用户离开设备后,其他用户可以连接使用
-**注意事项**:
-BLE连接是独占的,同一时间只能有一个手机连接
-切换用户需要当前用户主动断开连接
-远程用户无法通过WiFi抢占BLE连接(BLE和WiFi是独立的通信通道)
-
-5. OTA 升级功能
-5.1 OTA 功能概述
-OTA(Over-The-Air)升级功能允许通过 BLE 无线方式更新设备固件,无需物理连接,大大提升了设备维护和功能更新的便利性。用户可以通过 APP 方便地升级设备固件,获得新功能和 Bug 修复。
-5.2 OTA 升级架构
-
-5.3 OTA 升级流程
-5.3.1 升级前准备
-**版本检查**: APP 使用命令0x80查询设备OTA版本信息(固件版本、硬件版本、OTA版本)
-**兼容性验证**: 验证升级包与设备硬件兼容
-**存储空间检查**: 确认 Flash 有足够空间
-**用户确认**: 用户确认是否进行升级
-**进入升级模式**: 设备进入升级模式,停止正常工作
-5.3.2 固件传输
-**开始升级(命令0x81)**:
-APP发送版本号(4字节) + 固件大小(4字节) + CRC16校验和(2字节)
-设备擦除1MB SPI Flash区域(地址0x100000,256个扇区,每个4KB)
-设备写入文件头:版本号(4) + 固件大小(4) + CRC16占位(2) = 10字节
-文件头CRC16字段初始化为0,等待后续验证通过后更新
-设备保存期望的CRC16到状态跟踪结构,用于后续实时校验
-**分包传输(命令0x82)**:
-将固件包分成多个数据包(建议 20-244 字节/包,适配 BLE MTU)
-每个数据包格式:数据包序号(2字节) + 数据(N字节)
-设备将数据顺序写入Flash(从地址0x10000A开始)
-设备实时计算并累积CRC16(CRC16-CCITT算法,多项式0x1021,初始值0xFFFF)
-当所有数据接收完成时(received_size >= firmware_size),设备比较计算的CRC16与期望的CRC16
-CRC16匹配:设置force_boot=1(固件有效),标记在database的device_info.force_boot中
-CRC16不匹配:设置force_boot=0(固件无效),标记在database的device_info.force_boot中
-设备返回成功响应,上位机继续发送下一帧
-设备跟踪已接收数据大小和当前写入地址
-**结束传输(命令0x83)**:
-APP发送总大小(4字节)
-设备验证接收数据大小是否匹配
-设备保存CRC16到文件头(偏移8字节处)
-设备更新数据库OTA版本(db_cfg.device_info.ota_version = 固件版本号)
-设备更新数据库force_boot标志(db_cfg.device_info.force_boot,已在0x82中设置)
-设备保存数据库
-设备清除OTA升级状态
-设备返回成功响应
-注意:CRC16校验已在0x82命令中完成,这里只验证大小并保存CRC16到文件头
-**进度反馈**: 设备实时跟踪接收进度,APP端根据已发送数据包和总数据量计算进度
-5.3.3 固件存储格式
-**Flash存储格式**:
-存储位置:SPI Flash地址0x100000,大小1MB
-文件头(10字节,地址0x100000-0x100009):
-字节0-3:版本号(uint32_t,4字节小端序)
-字节4-7:固件大小(uint32_t,4字节小端序,单位:字节)
-字节8-9:CRC16(uint16_t,2字节小端序)
-文件体(从地址0x10000A开始):固件数据(Bin格式)
-5.3.4 升级后处理
-**设置启动标志**: 在 Bootloader 中设置新固件启动标志
-**系统复位**: 复位系统进入 Bootloader
-**固件启动**: Bootloader 加载并启动新固件
-**启动验证**: 验证新固件是否正常启动
-**升级确认**: APP 重新连接设备,确认升级成功
-5.4 OTA 安全机制
-**固件签名**: 升级包包含数字签名,防止恶意固件
-**版本验证**: 只允许升级到更高版本或指定版本
-**回滚机制**: 升级失败时自动回滚到原固件
-**双重备份**: 保留原固件备份,确保升级安全
-**权限验证**: 升级操作需要用户登录认证
-5.5 OTA 升级命令
-(详细命令格式将在通信协议文档中定义)
-OTA_VERSION_QUERY (0x80): 查询OTA版本信息(返回固件版本、硬件版本、OTA版本)
-OTA_START (0x81): 开始升级(接收版本号4 + 大小4 + CRC16 2,擦除Flash,写入文件头,保存期望的CRC16)
-OTA_DATA (0x82): 传输固件数据包(接收序号2 + 数据N,写入Flash,实时计算CRC16,当所有数据接收完成时比较CRC16并设置force_boot标志)
-OTA_END (0x83): 结束传输(接收总大小4,验证接收数据大小,保存CRC16到文件头,更新数据库OTA版本和force_boot标志)
-OTA_VERIFY (0x84): 校验固件(已废弃,功能已合并到0x82,在数据接收时实时校验)
-OTA_CANCEL (0x85): 取消升级(清除OTA状态)
-**说明**:
-升级进度由 APP 端和设备端各自计算,无需查询命令。
-**Flash存储格式**:
-存储位置:SPI Flash地址0x100000(1MB起始地址),大小1MB
-文件头(10字节):版本号(4) + 固件大小(4) + CRC16(2)
-文件体:固件数据(从地址0x10000A开始)
-CRC16算法:CRC16-CCITT(多项式0x1021,初始值0xFFFF)
-
-
-6. 通信协议框架
-6.1 协议结构
-优化后的协议结构如下:
-**协议优势**:
-**统一结构**: 所有命令采用统一的数据包结构,便于解析和处理
-**操作分离**: 读/写操作明确分离,简化命令设计
-**长度标识**: 明确的数据长度字段,支持可变长度数据
-**完整性校验**: 16位 CRC 校验,确保数据传输完整性
-**扩展性强**: 命令码空间充足,便于后续功能扩展
-6.2 命令分类
-采用分组命令码分配策略,便于管理和扩展:
-6.2.1 设备注册与认证类 (0x01-0x0F)
-6.2.2 系统控制类 (0x10-0x1F)
-6.2.3 外设控制类 (0x20-0x3F)
-6.2.4 害虫消灭控制类 (0x40-0x4F)
-6.2.5 安全设置类 (0x50-0x5F)
-6.2.6 高级管理类 (0x60-0x6F)
-6.2.7 连接管理类 (0x70-0x7F)
-
-6.2.8 OTA 升级类 (0x80-0x9F)
-OTA 命令采用固定长度和可变长度结合:
-**说明**:
-升级进度由 APP 端和设备端各自计算,无需查询命令。APP 端根据已发送数据包和总数据量计算进度,设备端根据已接收数据包和总数据量计算进度。
-**CRC16校验流程**:
-在0x81命令时,上位机发送期望的CRC16校验值
-在0x82命令传输数据时,设备实时计算并累积CRC16
-当所有数据接收完成时,设备比较计算的CRC16与期望的CRC16
-如果CRC16匹配,设置force_boot=1(固件有效);否则设置force_boot=0(固件无效)
-force_boot标志保存在database的device_info.force_boot变量中,用于Bootloader判断是否启动新固件
-**Flash存储格式**:
-存储位置:SPI Flash地址0x100000(1MB起始地址),大小1MB
-文件头(10字节,地址0x100000-0x100009):
-字节0-3:版本号(uint32_t,4字节小端序)
-字节4-7:固件大小(uint32_t,4字节小端序,单位:字节)
-字节8-9:CRC16(uint16_t,2字节小端序)
-文件体(从地址0x10000A开始):固件数据(Bin格式)
-数据格式:所有多字节数据采用小端序(低字节在前)
-CRC16算法:CRC16-CCITT(多项式0x1021,初始值0xFFFF)
-
-
-6.2.9 状态查询类 (0xA0-0xBF)
-6.2.10 保留/扩展 (0xC0-0xFF)
-**0xC0-0xDF**: 保留用于未来功能扩展
-**0xE0-0xEF**: 设备特定命令
-**0xF0-0xFF**: 调试和测试命令
-6.3 响应格式
-6.3.1 读操作响应
-**成功响应**:
-**错误响应**:
-6.3.2 写操作响应
-**成功响应**:
-**错误响应**:
-6.3.3 设备主动通知
-**状态通知**:
-**通知类型**:
-0x01: 设备状态变化(工作模式变化等)
-工作模式变化:待机→扫描、扫描→消杀、消杀→待机等
-状态数据:状态码(1字节) + 状态描述(可选)
-0x02: 错误状态上报(系统错误、硬件故障等)
-数据格式:7字节 = 温度错误(1) + 电机错误(1) + 激光器错误(1) + 视觉错误(1) + 加速度错误(1) + 毫米波雷达错误(1) + 激光雷达错误(1)
-温度错误:0x00=无错误, 0x01=温度过高, 0x02=温度过低
-电机错误:0x00=无错误, 0x01=温度错误, 0x02=反馈错误
-激光器错误:0x00=无错误, 0x01=温度过高, 0x02=电压过低, 0x03=电压过高
-视觉错误:0x00=无错误, 0x01=通信错误
-加速度错误:0x00=无错误, 0x01=通信错误
-毫米波雷达错误:0x00=无错误, 0x01=通信错误
-激光雷达错误:0x00=无错误, 0x01=通信错误
-数据来源:meas_ctrl.error(测量控制模块的错误状态)
-0x03: 统计数据更新(灭虫数量、工作时间等)
-数据格式:44字节 = 灭虫数量(4) + 蚊虫数据数量(4) + 扫描次数(4) + 激光发射次数(4) + 总工作时间(4) + 今日工作时间(4) + 工作时间(4) + 角度(4) + 距离(2) + 大小(2) + 时间(6) + 保留(2)
-统计数据:灭虫数量、蚊虫数据数量、扫描次数、激光发射次数、总工作时间、今日工作时间、工作时间(本次工作会话时间)
-本次虫子数据:角度(*1000度,int32_t,支持负数,例如90000表示90.000度,-22500表示-22.500度)、距离(毫米)、大小(毫米)、时间(year+month+day+hour+minute+second)
-触发条件:每次成功写入蚊虫数据到Flash后发送
-0x04: 保留(原连接状态变化通知已删除)
-0x05: 安全告警(人体检测、安全保护触发等)
-0x06: 参数变化通知(参数被修改时主动通知)
-0x07: 保留(原设备振动告警功能已合并到0x02错误状态上报中的加速度错误字段)
-0x08: 蓄能状态通知(电容充电开始/完成)
-充电开始:设备开始充电
-充电完成:设备准备就绪,可发射激光
-0x09: 补光灯连接状态通知(补光灯连接/断开)
-连接:补光灯设备已连接
-断开:补光灯设备已断开
-0x0A: 保留(原温度告警通知功能已合并到0x02错误状态上报中的温度错误字段)
-0x0B: 蚊虫数据通知(查询蚊虫数据时发送)
-数据格式:索引(4字节小端序) + 蚊虫数据(18字节)
-蚊虫数据结构:角度(4字节,*1000度,int32_t,支持负数,例如90000表示90.000度,-22500表示-22.500度) + 距离(2字节,毫米) + 大小(2字节,毫米) + 时间(6字节:year+month+day+hour+minute+second) + 保留(4字节)
-发送间隔:每条数据间隔10ms
-触发条件:命令0xA4写操作成功后,设备自动发送指定索引范围的所有数据
-6.3.4 错误码定义
-错误码采用统一格式:
-6.4 安全机制
-采用多层安全机制:
-**用户认证**:
-密码验证在云端完成,设备端不验证密码
-设备与用户绑定关系存储在云端,APP通过云端验证绑定关系
-除注册和登录命令外,其他命令需要设备已解锁(设备端记录登录状态)
-**权限验证**: 设备端验证设备解锁状态,拒绝未解锁操作
-**数据完整性**: CRC16 校验确保数据包完整性
-**数据加密**: APP与云端之间的密码传输需要加密(使用 HTTPS 或 AES-128)
-**命令超时**: 设置命令超时时间(建议 5 秒),防止长时间等待
-**重传机制**: 校验失败时支持数据包重传(最多 3 次)
-6.5 协议实现建议
-协议设计遵循以下原则:
-**统一包结构**: 所有命令采用统一的数据包结构,简化解析逻辑
-**读/写分离**: 明确区分读操作和写操作,提高协议清晰度
-**分组管理**: 命令码按功能分组,便于管理和扩展
-**固定长度优先**: 优先使用固定长度数据包,提高处理效率
-**可变长度支持**: 对于大数据传输(如 OTA),支持可变长度数据包
-**校验机制**: 使用 CRC16 校验,确保数据传输可靠性
-
-
-7. 技术实现规范
-7.1 通信协议实现规范
-**详细通信协议**: 定义完整的命令格式、数据格式、错误码等
-**协议结构规范**:
-所有命令必须严格遵循 6.1 节定义的协议结构:
-**帧头**: 固定为 0xAA 0x55(2字节),标识 BLE 通信数据包
-**命令码**: 1字节,范围 0x01-0xFF,标识具体操作类型(参考 6.2 节命令分类)
-**读/写标识**: 1字节,0x00=读操作,0x01=写操作,0x02=通知
-**长度**: 1字节,数据字段长度(0-255 字节)
-**数据**: N字节,命令参数或响应数据(可变长度)
-**校验**: 2字节,CRC16 校验(LSB + MSB),校验范围:帧头到数据字段的所有字节
-**数据字段规范**:
-**字节序**: 采用小端序(Little-Endian),与 STM32H7 处理器一致
-多字节数据(如 16 位、32 位整数)的低字节在前,高字节在后
-示例:0x1234 存储为 0x34 0x12,0x12345678 存储为 0x78 0x56 0x34 0x12
-**编码**方式:
-**ASCII ****字符串**: 用于用户名、设备名称等文本数据,使用 ASCII 编码,不足长度用 0x00 填充
-**二进制数据**: 用于传感器数据、状态值等,直接使用二进制表示
-**枚举值**: 用于开关状态、模式选择等,使用单字节枚举值(如 0x00=关,0x01=开)
-**数据对齐**: 单字节数据无需对齐,多字节数据按自然边界对齐
-**错误码规范**:
-错误码定义参考 6.3.4 节,采用统一格式
-错误码在响应数据的第一字节返回(状态码字段)
-错误码分类:
-0x00: 成功
-0x01: 参数错误(参数无效或超出范围)
-0x02: 权限错误(用户未登录或权限不足)
-0x03: 设备忙(设备正在执行其他操作)
-0x04: 命令不支持(命令码不存在或未实现)
-0x05: 数据校验失败(CRC 校验失败)
-0x06: 超时(操作超时)
-0x07: 设备错误(设备内部错误)
-0x08-0xFF: 保留用于扩展错误码
-**协议版本管理**:
-协议版本号建议在设备信息查询命令(0xA2)中返回
-版本号格式:主版本号(1字节) + 次版本号(1字节) + 修订号(1字节)
-兼容性处理:新版本协议应向后兼容,新增命令使用新的命令码,不修改现有命令格式
-**命令格式示例**:
-读操作示例(查询设备注册状态,命令 0x01):
-写操作示例(设备注册绑定,命令 0x02):
-读操作响应示例(查询设备注册状态,命令 0x01,成功响应):
-写操作响应示例(设备注册绑定,命令 0x02,成功响应):
-错误响应示例(命令 0x02,参数错误):
-**数据加密**: APP 与 BLE 通信不需要应用层加密
-**数据完整性保护**:
-通过 CRC16 校验确保数据包完整性(参考 6.1 节协议结构)
-CRC16 校验范围:帧头到数据字段的所有字节
-校验失败时,设备返回错误码 0x05(数据校验失败),参考 6.3.4 节错误码定义
-校验失败时支持数据包重传(最多 3 次),参考 6.4 节安全机制
-**敏感数据处理**:
-**用户密码**: 在云端验证,不在 BLE 层传输,设备端不存储密码
-**设备序列号**: 通过查询命令(0xA2)获取,用于设备识别
-**指纹数据**: 仅存储在设备端,不通过 BLE 传输,设备本地验证
-**其他敏感数据**: 根据实际需求,可在设备端加密存储,但不在 BLE 通信层传输
-**安全机制总结**:
-BLE 链路层已提供加密保护
-应用层通过 CRC16 校验保证数据完整性
-敏感数据不在 BLE 层传输,降低安全风险
-与 6.4 节安全机制保持一致:数据完整性通过 CRC16 保证,无需应用层加密
-**连接状态检测**: BLE 连接状态检测机制
-**APP ****端实现细节**:
-**BLE SDK ****回调**: 通过 BLE SDK 提供的连接状态回调函数实时检测连接状态
-连接建立:onConnectionStateChange(connected)
-连接断开:onConnectionStateChange(disconnected)
-连接失败:onConnectionStateChange(failed)
-**状态机设计**:
-状态:未连接 → 连接中 → 已连接 → 断开中 → 未连接
-状态转换触发:BLE SDK 回调事件
-状态持久化:APP 端记录最后连接状态,用于 UI 显示
-**参考**: 1.3 节通信特点中说明 BLE 模块提供 LINK 和 WORK 状态引脚
-**设备端实现细节**:
-**LINK ****引脚检测**: 通过硬件 LINK 引脚检测连接状态
-高电平 = 已连接
-低电平 = 未连接
-**中断处理**: 使用 GPIO 中断检测 LINK 引脚状态变化
-上升沿中断:连接建立
-下降沿中断:连接断开
-**状态上报**: 连接状态由APP端BLE SDK直接检测,无需设备端主动通知
-注意:仅在连接存在时才能发送通知,连接断开后无法通信
-**内部逻辑处理**:
-连接断开时,设备自动停止工作(如停止激光发射、停止灭虫任务等)
-连接断开时,清理临时数据,保存必要状态
-连接建立时,恢复设备状态,准备接收命令
-**连接断开后的处理流程**:
-**APP ****端**:
-检测到连接断开后,更新 UI 显示(显示"设备已断开")
-清理命令队列,取消未完成的命令请求
-保存设备状态缓存,用于重连后状态同步
-**设备端**:
-立即停止所有工作(激光发射、灭虫任务等)
-保存当前工作状态和参数设置
-清理临时数据,准备下次连接
-**状态恢复**: 重连后,APP 可查询设备状态(命令 0xA0),同步设备状态
-**重连机制**:
-**自动重连策略**: APP 端实现自动重连功能
-检测到连接断开后,等待一定时间(建议 1-3 秒)
-自动发起重连请求
-重连失败后,增加等待时间(指数退避策略)
-最多重试 3-5 次,超过次数后提示用户手动重连
-**重连间隔**:
-首次重连:1 秒
-第二次重连:2 秒
-第三次重连:4 秒
-后续重连:8 秒(最大间隔)
-**用户手动重连**: 提供手动重连按钮,用户可主动触发重连
-**说明**:
-连接状态通过硬件 LINK 引脚检测,无需应用层命令查询
-如果 BLE 连接断开,APP 和设备之间无法通信,因此无需应用层查询命令
-与 4.9 节连接管理类保持一致:连接管理通过 BLE SDK 和硬件引脚实现
-**多设备管理**(可选功能): 支持一个 APP 管理多个设备
-**设备列表数据结构**:
-**设备信息**(参考 3.1.1 节设备信息):
-设备序列号:唯一标识设备(主要标识符,16字节,ASCII编码)
-设备名称:用户自定义的设备名称(可选,用于显示)
-固件版本:设备固件版本号(用于版本管理)
-硬件版本:设备硬件版本号
-设备 MAC 地址:通过查询设备信息命令(0x61)获取(Android/iOS 无法直接获取)
-**连接状态**:
-连接状态:已连接/未连接/连接中
-最后连接时间:记录最后成功连接的时间戳
-连接质量:信号强度(RSSI值)
-**设备配置缓存**:
-最后使用的参数设置(角度、距离等)
-设备工作模式(待机/扫描/消杀)
-设备硬件状态和错误状态(如有)
-**设备切换流程**:
-**断开当前设备**:
-APP 发送断开连接命令(命令 0x70,参考 6.2.7 节连接管理类)
-设备收到命令后断开 BLE 连接
-APP 端 BLE SDK 检测到连接断开,更新设备状态
-**连接新设备**:
-APP 扫描并发现目标设备(通过设备序列号或设备名称识别)
-APP 发起 BLE 连接请求
-连接建立后,APP 查询设备注册状态(命令 0x01)
-如设备已注册,APP 提示用户登录(密码或指纹)
-登录成功后,APP 同步设备状态(查询工作模式、参数设置等)
-**状态同步**:
-切换设备后,APP 查询设备当前状态(命令 0xA0、0xA1等)
-APP 更新设备配置缓存
-APP 显示设备当前状态和参数设置
-**批量操作设计**:
-**批量查询**: APP 端实现批量查询功能
-遍历设备列表,逐个设备执行查询命令
-使用操作队列管理批量操作,避免并发冲突
-显示批量操作进度(已完成/总数)
-操作超时处理:单个设备操作超时后,继续下一个设备
-**批量设置**: APP 端实现批量设置功能
-遍历设备列表,逐个设备执行设置命令
-使用操作队列管理批量操作
-显示批量操作进度和结果(成功/失败)
-部分失败处理:记录失败设备,操作完成后提示用户
-**操作队列**:
-使用队列管理批量操作,确保操作顺序
-支持取消批量操作(清空队列)
-操作失败时,记录错误信息,继续执行后续操作
-**设备标识方案**:
-**主要标识符**: 设备序列号(16字节,ASCII编码)
-通过查询设备信息命令(0xA2)获取,从返回数据中提取序列号
-设备序列号唯一标识设备,用于设备识别和绑定
-**辅助标识符**: 设备 MAC 地址(可选)
-通过查询设备信息命令(0xA2)获取
-注意:Android 6.0+ 和 iOS 7+ 无法直接获取 BLE MAC 地址(参考 3.1.1 节说明)
-设备识别主要使用设备序列号
-用户**标识**: 设备名称(用户自定义)
-用于 APP 界面显示,方便用户识别设备
-设备名称可重复,不作为唯一标识
-**设备分组和分类**:
-**按房间分组**: 用户可将设备按房间分组(客厅、卧室、厨房等)
-APP 端实现分组管理功能
-分组信息仅存储在 APP 端,不传输到设备
-**按**类型**分类**: 根据设备功能分类(灭虫设备、其他设备等)
-APP 端实现分类管理功能
-用于批量操作时的设备筛选
-自定义**标签**: 用户可为设备添加自定义标签
-APP 端实现标签管理功能
-标签信息仅存储在 APP 端
-**说明**:
-多设备管理为可选功能,APP 可根据实际需求实现
-所有操作使用 6.1 节的通信帧结构(帧头 0xAA 0x55、命令码、读/写、长度、数据、CRC16)
-仅涉及 APP 端设备管理,不涉及云端
-设备切换时,确保当前设备操作完成后再切换
-批量操作时,注意 BLE 连接限制(同一时间只能连接一个设备)
-**状态同步**: 设备状态变化时主动通知 APP
-**状态变化检测**:
-**状态机**设计(参考 3.6 节设备状态监控):
-工作模式:待机 → 扫描 → 消杀 → 待机
-硬件状态:正常 → 异常 → 正常(包含错误状态)
-**变化**触发**条件**:
-工作模式变化:用户操作、设备自动切换
-硬件状态变化:硬件故障检测、硬件恢复检测、错误发生、错误恢复
-参数变化:参数被修改(通过 APP 或设备本地操作)
-**状态变化检测机制**:
-设备端实时监测各状态值
-状态值变化时,立即触发状态变化事件
-状态变化事件触发主动通知(参考 6.3.3 节设备主动通知)
-**通知优先级**:
-紧急状态(最高优先级):
-错误状态上报(通知类型 0x02):硬件异常、系统错误等(包含温度错误、加速度错误/振动、电机错误、激光器错误、视觉错误、毫米波雷达错误、激光雷达错误)
-安全告警(通知类型 0x05):人体检测、安全保护触发
-处理:立即显示告警,中断当前操作(如需要)
-**普通**状态(中等优先级):
-设备状态变化(通知类型 0x01):工作模式变化等
-参数变化通知(通知类型 0x06):参数被修改
-处理:更新 UI 显示,记录状态变化
-**统计**信息(低优先级):
-统计数据更新(通知类型 0x03):灭虫数量、工作时间等,包含本次虫子的详细信息(角度、距离、大小、时间)
-蓄能状态通知(通知类型 0x08):电容充电开始/完成
-补光灯连接状态通知(通知类型 0x09):补光灯连接/断开
-蚊虫数据通知(通知类型 0x0B):查询蚊虫数据时发送,每条数据间隔10ms
-处理:后台更新统计数据,不中断用户操作
-**通知可靠性**:
-**确认**机制:
-APP 收到通知后,可发送确认响应(可选)
-设备端记录通知发送状态,用于重传判断
-**重传**策略:
-通知发送失败时(如连接断开),设备端记录未确认通知
-连接恢复后,设备端重新发送未确认的通知
-重传次数限制:最多重传 3 次
-**通知去重**:
-设备端记录最后发送的通知内容
-相同内容的通知不重复发送(避免重复通知)
-状态值变化时才发送通知(避免重复通知)
-**状态缓存和同步**:
-**APP ****端状态缓存**:
-APP 端缓存设备状态(工作模式、硬件状态、参数设置等)
-状态缓存用于 UI 显示,减少查询命令
-状态缓存更新:收到通知时更新,查询命令返回时更新
-状态缓存持久化:APP 退出时保存,启动时恢复
-**设备端**状态**持久化**:
-设备端持久化关键状态(参数设置、工作模式等)
-状态持久化到 Flash,设备重启后恢复
-状态变化时,立即更新持久化存储
-**状态同步机制**:
-连接建立后,APP 查询设备当前状态(命令 0xA0、0xA1等)
-APP 比较缓存状态和设备状态,如有差异则更新缓存
-设备端状态变化时,主动发送通知,APP 更新缓存
-**状态冲突处理**:
-**APP ****和设备**状态**不一致**:
-场景:APP 缓存状态与设备实际状态不一致
-处理:连接建立后,APP 查询设备当前状态,同步状态缓存
-**并发操作**冲突:
-场景:APP 发送设置命令,同时设备状态发生变化
-处理:设备端优先处理设置命令,状态变化通知在命令处理完成后发送
-**状态回滚**:
-场景:设备状态变化后,用户需要恢复到之前状态
-处理:APP 发送恢复命令,设备执行状态恢复,发送状态变化通知
-**通知格式**:
-所有通知使用 6.1 节的通信帧结构:
-帧头:0xAA 0x55
-通知码:与命令码区分(建议使用 0xF0-0xFF 范围)
-读/写标识:0x02(表示通知)
-长度:通知数据长度
-数据:通知类型(1字节) + 通知数据(参考 6.3.3 节设备主动通知)
-校验:CRC16 校验
-与 6.3.3 节设备主动通知保持一致,参考通知类型 0x01-0x0B(注意:0x07和0x0A已合并到0x02)
-**通知格式示例**:
-设备状态变化通知示例(通知类型 0x01,工作状态变化):
-错误状态上报通知示例(通知类型 0x02,振镜电机异常):
-**说明**:
-状态同步机制确保 APP 和设备状态一致
-通知优先级确保重要状态及时通知用户
-状态缓存减少查询命令,提高通信效率
-所有通知使用 6.1 节的通信帧结构(读/写标识为 0x02 表示通知)
-7.2 协议文档规划
-后续将添加以下详细协议文档,所有文档必须遵循 6.1 节的通信帧结构(帧头 0xAA 0x55、命令码、读/写标识、长度、数据、CRC16 校验)。
-7.2.1 BLE 通信协议详细规范.md
-**文档目的**: 定义完整的命令格式、数据格式、字节序和编码规范
-**目录结构**:
-**协议概述**
-协议版本信息
-协议结构说明(参考 6.1 节)
-通信流程概述
-**命令格式定义**(基于 6.2 节命令分类)
-设备注册与认证类 (0x01-0x0F)
-每个命令的详细格式
-请求格式(帧头、命令码、读/写、长度、数据、校验)
-响应格式(成功/失败)
-数据字段详细说明
-系统控制类 (0x10-0x1F)
-外设控制类 (0x20-0x3F)
-害虫消灭控制类 (0x40-0x4F)
-安全设置类 (0x50-0x5F)
-高级管理类 (0x60-0x6F)
-连接管理类 (0x70-0x7F)
-OTA 升级类 (0x80-0x9F)
-状态查询类 (0xA0-0xBF)
-**数据格式定义**
-字节序规范(小端序,与 STM32H7 一致)
-编码方式(ASCII 字符串、二进制数据、枚举值)
-数据结构定义
-用户信息结构
-设备状态结构
-参数配置结构
-传感器数据结构
-**响应格式定义**(基于 6.3 节响应格式)
-读操作响应(成功/失败)
-写操作响应(成功/失败)
-设备主动通知格式
-**错误码定义**(基于 6.3.4 节错误码定义)
-错误码列表
-错误原因说明
-错误处理建议
-**编写优先级**: 高(基础协议文档)
-7.2.2 OTA 升级协议详细规范.md
-**文档目的**: 定义 OTA 升级的详细协议和流程
-**目录结构**:
-**升级概述**
-OTA 功能说明(参考 5.1 节)
-升级架构(参考 5.2 节)
-**升级流程详细说明**(基于 5.3 节)
-升级前准备
-固件传输流程
-固件烧录流程
-升级后处理流程
-**数据包格式**(基于 6.2.8 节 OTA 升级类)
-查询OTA版本信息(命令 0x80,返回12字节:固件版本4 + 硬件版本4 + OTA版本4)
-开始升级(命令 0x81,接收版本号4 + 大小4,擦除Flash,写入文件头)
-传输固件数据(命令 0x82,接收序号2 + 数据N,写入Flash)
-结束传输(命令 0x83,接收总大小4,验证接收数据大小)
-结束传输(命令 0x83,接收总大小4,验证接收数据大小,保存CRC16到文件头,更新数据库OTA版本和force_boot标志)
-取消升级(命令 0x85,清除OTA状态)
-所有命令使用 6.1 节的通信帧结构
-**错误处理机制**
-升级过程中的错误处理
-错误恢复机制
-回滚机制
-**安全机制**(参考 5.4 节)
-固件签名验证
-版本验证
-权限验证
-**编写优先级**: 中(重要功能文档)
-7.2.3 错误码定义.md
-**文档目的**: 详细定义所有错误码及其处理方式
-**目录结构**:
-**错误码概述**
-错误码格式说明(参考 6.3.4 节)
-错误码分类
-**标准错误码列表**(基于 6.3.4 节)
-0x00: 成功
-0x01: 参数错误
-0x02: 权限错误
-0x03: 设备忙
-0x04: 命令不支持
-0x05: 数据校验失败
-0x06: 超时
-0x07: 设备错误
-每个错误码包含:
-错误码值
-错误含义
-错误原因说明
-错误处理建议
-典型场景示例
-**扩展错误码**(0x08-0xFF)
-保留用于未来扩展
-扩展错误码定义规范
-**错误处理流程**
-APP 端错误处理流程
-设备端错误处理流程
-错误恢复机制
-**编写优先级**: 高(基础协议文档)
-7.2.4 数据格式定义.md
-**文档目的**: 定义所有数据结构的详细格式
-**目录结构**:
-**数据格式概述**
-字节序规范(小端序)
-编码方式(ASCII、二进制、枚举)
-数据对齐规则
-**基础数据类型**
-单字节数据类型
-多字节数据类型(16位、32位)
-字符串类型
-枚举类型
-**数据结构定义**
-**用户信息结构**
-用户名(16字节,ASCII)
-指纹ID(1字节,uint8_t类型,0-255)
-指纹ID有效标志(1字节,0=无效,1=有效)
-设备状态结构
-工作模式(待机/扫描/消杀)
-硬件状态(正常/异常,包含错误状态)
-错误状态(错误码、错误描述,属于硬件状态)
-**参数配置结构**
-角度控制参数(2字节)
-距离控制参数(4字节)
-安全设置参数(开关、灵敏度、阈值等)
-**命令数据格式**(按命令分类)
-设备注册与认证类数据格式
-系统控制类数据格式
-外设控制类数据格式
-害虫消灭控制类数据格式
-安全设置类数据格式
-状态查询类数据格式
-**通知数据格式**(参考 6.3.3 节)
-各通知类型的数据格式
-通知数据示例
-**编写优先级**: 高(基础协议文档)
-7.2.5 文档关联关系
-**BLE ****通信协议详细**规范**.md** 是核心文档,其他文档作为补充
-错误码定义.md 和 数据格式定义.md 被 BLE 通信协议详细规范.md 引用
-**OTA ****升级协议详细规范.md** 是独立的功能文档,但遵循相同的协议结构
-7.2.6 文档维护和更新机制
-版本管理: 每个文档包含版本号,与主方案文档版本号关联
-兼容性: 新版本协议应向后兼容,文档中明确标注兼容性说明
-文档审核: 协议文档编写完成后,需与主方案文档(6.1-6.3 节)进行一致性检查
-**说明**:
-所有协议文档必须遵循 6.1 节的通信帧结构
-仅涉及 APP 与设备 BLE 通信协议,不涉及云端协议
-所有命令、响应、通知格式必须与 6.2 节和 6.3 节保持一致
-
-| 版本 | 日期 | 更改内容 | 更改人 |
-|---|---|---|---|
-| V2.2 | 20260226 | 主要更新内容:
-补充补光灯控制指令;
-添加0X56环境变化检测以及试射测试指令;
-补充0XA1硬件状态说明
-部分0X80查询版本信息说明
-优化0XA3、0XA4指令协议 | |
-| v2.1 | 2025-12-25 | 主要更新内容:
-- 数据格式优化:角度字段精度提升,年份格式优化,定时任务格式改进
-- OTA升级流程完善:版本信息扩展,CRC校验优化,Flash存储优化
-- 通知机制优化:错误状态扩展为7字节,统计数据扩展为44字节,删除重复通知
-- 命令功能完善:合并重复命令,新增传感器数据查询命令,完善用户认证和指纹功能
-- 外设控制完善:LCD、RGB、WIFI等外设控制功能优化
-- 安全设置完善:毫米波雷达、视觉检测、加速度传感器等安全功能完善
-- 文档结构优化:修正工作状态定义,新增设备分享和多用户管理章节 | - |
-| V1.1 | 2025-11-11 | 修正工作状态定义:
-- 将"查询设备工作状态"改为"查询设备工作模式"
-- 工作模式仅保留三种:待机、扫描、消杀(移除运行、蓄能、停止、错误)
-- 将错误状态归类到硬件状态中
-- 更新所有相关章节和命令说明
-- 新增设备分享流程章节(4.1.5)
-- 新增多用户连接管理章节(4.9.2) | - |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x01 | 查询设备注册状态 | 读 | 返回设备是否已注册(1字节) | 设备首次连接时查询,判断是否需要注册 |
-| 0x02 | 设备注册绑定 | 写 | 用户名(16) | 设备首次注册时使用,APP将绑定关系存储到云端,仅发送用户名到设备 |
-| 0x03 | 用户登录(密码) | 写 | 用户名(16) + 解锁状态(1) | 已注册设备登录时使用(密码方式),APP云端验证通过后发送解锁状态 |
-| 0x04 | 指纹登录使能设置 | 写 | 用户名(16) + 使能状态(1) | 设置指定用户的指纹登录使能状态(0=禁用, 1=使能),需要设备已解锁 |
-| 0x05 | 指纹录制请求 | 写 | 用户名(16) | 启动指纹注册流程,需要设备已解锁
-功能:验证用户名,分配或使用已有的page_id,启动指纹注册状态机
-注册完成后自动更新数据库(finger_id和finger_id_valid字段) |
-| 0x06 | 保留 | - | - | 扩展用 |
-| 0x07 | 查询用户列表 | 读 | 返回用户列表 | 管理员查询设备绑定的所有用户信息 |
-| 0x08 | 注销用户 | 写 | 用户名(16) | 删除设备绑定的用户,需要设备已解锁
-功能:验证用户名,删除指纹模板(如果存在),删除用户信息
-如果删除后没有用户了,自动重置设备注册状态 |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x10 | 语言设置 | 读/写 | 0x00=英文, 0x01=中文 | 用户个性化设置,更改设备显示语言 |
-| 0x11 | 时间设置 | 读/写 | 年(1) + 月(1) + 日(1) + 时(1) + 分(1) + 秒(1)
-年:1字节,相对于2000年的偏移(0-255对应2000-2255年) | 设备时间同步,用于定时功能 |
-| 0x12 | 定时任务设置 | 读/写 | 写:任务ID(1) + 开关(1) + 开始小时(1) + 开始分钟(1) + 结束小时(1) + 结束分钟(1) + 模式(1) + 重复(1)
-读:返回所有定时任务列表(最多5组,每组7字节)
-时间范围:0-23小时,0-59分钟
-重复:bit0周一,bit1周二,bit2周三,bit3周四,bit4周五,bit5周六,bit6周日 | 自动化控制,在指定时间范围内运行指定模式,支持按周重复,最多设置5组定时任务 |
-| 0x13-0x1F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x20 | LCD 休眠设置 | 读/写 | 字节0:休眠开关(0x00=关, 0x01=开)
-字节1:休眠时间(0x01=1分钟, 0x05=5分钟, 0x0A=10分钟, 0x1E=30分钟) | 节能管理,设置LCD显示屏休眠开关和休眠时间 |
-| 0x21 | LCD 亮度设置 | 读/写 | 字节0:自适应开关(0x00=关闭自适应, 0x01=开启自适应)
-字节1:亮度值(10-100,仅在自适应关闭时有效) | 显示优化,设置LCD亮度自适应开关和亮度值 |
-| 0x22 | RGB 指示灯控制 | 读/写 | 开关(1) + R(1) + G(1) + B(1) + 效果(1) | 状态指示,个性化设置指示灯颜色和效果
-效果:0x00=闪烁, 0x01=常亮, 0x02=警示 |
-| 0x23 | WIFI 控制 | 读/写 | 读:65字节
-写:可变长度 | 网络配置,控制WIFI开关和连接信息
-读操作:返回65字节(开关1字节 + SSID 32字节 + 密码32字节)
-写操作:字节0为开关(0x00=关, 0x01=开),字节1-N为SSID字符串(以\0结尾,最多32字节,可选),字节N+1-M为密码字符串(以\0结尾,最多32字节,可选) |
-| 0x24 | BLE 控制 | 读/写 | 名称 | 蓝牙配置,设置BLE广播名称(BLE功能始终开启,不支持关闭) |
-| 0x25 | 多媒体控制 | 读/写 | 读:5字节
-写:可变长度 | 功能配置,控制视频录制和音效设置
-读操作:返回5字节(视频录制开关1字节 + 录制时长1字节 + 音效开关1字节 + 音效类型1字节 + 音量1字节)
-写操作:字节0为视频录制开关(0x00=关, 0x01=开),字节1为录制时长(1字节,0x03=3秒, 0x05=5秒, 0x0A=10秒),字节2为音效开关(0x00=关, 0x01=开),字节3为音效类型(0x00=Hello, 0x01=Goodbye, 0x02=Test, 0x03=BIU, 0x04=Main),字节4为音量(0-15,对应芯片E0-EF共16级) |
-| 0x26 | 补光灯控制 | 读/写 | 开关(1) + 类型(1) + 光照强度(1) | 夜间补光,控制补光灯开关、类型和光照强度 |
-| 0x27 | 查询补光灯连接状态 | 读 | 返回连接状态(0=未连接, 1=已连接) | 设备管理,查询补光灯外部设备连接状态 |
-| 0x28 | 可见光激光器控制 | 读/写 | 0x00=关, 0x01=开 | 激光控制,控制可见光激光器开关 |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x40 | 角度控制 | 读/写 | 角度范围(2字节) | 扫描范围调整,设置激光扫描角度范围
-读操作:返回2字节(角度范围,小端序,单位:0.1度)
-写操作:接收2字节(角度范围,小端序,单位:0.1度)
-角度范围:1-900(对应0.1度-90度)(uint8_t)(angle_range & 0xFF); (uint8_t)((angle_range >> 8) & 0xFF); |
-| 0x41 | 距离控制 | 读/写 | 检测距离(2) + 瞄准距离(2) | 检测范围优化,设置害虫检测和瞄准距离 |
-| 0x42-0x4F | 保留 | - | - | 扩展用
-注:0x42(害虫类型)、0x43(启动/停止)、0x44(查询消灭状态)已删除 |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x50 | 毫米波雷达设置 | 读/写 | 开关(1) + 灵敏度(1) + 距离(2) | 人体检测,配置毫米波雷达检测参数和安全距离
-读操作:返回4字节(开关1字节 + 灵敏度1字节 + 安全距离2字节)
-
-写操作:接收4字节(开关1字节 + 灵敏度1字节 + 安全距离2字节)
-开关:0x00=关闭, 0x01=开启
-灵敏度:0x00=低, 0x01=中, 0x02=高
-安全距离:2字节,小端序,单位0.1米,范围1-100(0.1米-10米) |
-| 0x51 | 视觉检测设置 | 读/写 | 开关(1) + 灵敏度(1) | 安全检测,配置视觉检测开关和灵敏度
-读操作:返回2字节(开关1字节 + 灵敏度1字节)
-写操作:接收2字节(开关1字节 + 灵敏度1字节)
-开关:0x00=关闭, 0x01=开启
-灵敏度:0x00=低, 0x01=中, 0x02=高 |
-| 0x52 | 加速度传感器设置 | 读/写 | 开关(1) + 灵敏度(1) + 阈值(1) | 稳定性检测,配置振动检测参数,确保设备稳定放置
-读操作:返回3字节(开关1字节 + 灵敏度1字节 + 振动阈值1字节)
-写操作:接收3字节(开关1字节 + 灵敏度1字节 + 振动阈值1字节)
-开关:0x00=关闭, 0x01=开启
-灵敏度:0x00=低, 0x01=中, 0x02=高
-振动阈值:1-255 |
-| 0x53 | 查询加速度传感器数据 | 读 | 返回X(2) + Y(2) + Z(2)轴加速度值 | 数据监控,查询3轴加速度传感器实时数据
-读操作:返回6字节(X轴加速度2字节 + Y轴加速度2字节 + Z轴加速度2字节)
-数据格式:每个轴2字节,小端序,有符号整数(int16_t)
-单位:mg(毫重力加速度)
-数据范围:-32768到32767(对应-32768mg到32767mg) |
-| 0x54 | 保留 | - | - | 扩展用
-注:0x54(查询振动状态)、0x56(查询当前温度)、0x57(查询温度状态)的功能已合并到0xA5"查询设备传感器数据"中 |
-| 0x55 | 温度监控设置 | 读/写 | 告警阈值(1) + 停止阈值(1) + 开关(1) | 超温保护,设置温度告警和停止阈值 |
-| 0X56 | 环境变化检测和试射测试 | 读/写 | 1 | 读操作:返回1字节环境变化状态(0-没有变化,1-发生变化)
-写操作:触发激光进行3次试射测试,每次间隔1秒,每次持续10ms.写操作:0X01进行激光测试
-说明,仅用于触发测试 |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x60 | 恢复出厂设置 | 写 | 需要管理员权限 | 设备管理,恢复设备到出厂默认状态(需管理员权限) |
-| 0x61-0x63 | 保留 | - | - | 已删除,功能已合并到0xA2命令中(参考2.9节) |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x70 | 断开连接 | 写 | 主动断开连接 | 连接管理,APP主动断开BLE连接 |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0x80 | 查询OTA版本信息 | 读 | 返回12字节版本信息(固件版本4 + 硬件版本4 + OTA版本4) | OTA升级,查询设备当前固件版本、硬件版本和OTA版本信息,用于升级前版本检查 |
-| 0x81 | 开始升级 | 写 | 版本号(4) + 大小(4) + CRC16(2) | OTA升级,启动固件升级流程
-功能:擦除1MB SPI Flash区域(地址0x100000),写入文件头(版本号、固件大小、CRC16占位为0),保存期望的CRC16
-数据格式:版本号(4字节小端序) + 固件大小(4字节小端序) + CRC16校验和(2字节小端序)
-Flash操作:擦除256个扇区(每个4KB),写入文件头10字节
-CRC16:从0x81命令接收,用于后续实时校验 |
-| 0x82 | 传输固件数据 | 写 | 数据包序号(2) + 数据(N) | OTA升级,分包传输固件数据包
-功能:将数据写入Flash,实时计算并累积CRC16,当所有数据接收完成时比较CRC16并设置force_boot标志
-数据格式:数据包序号(2字节小端序) + 数据(N字节)
-写入位置:从文件体起始地址(0x100000 + 10)开始顺序写入
-CRC16校验:实时累积计算CRC16,当所有数据接收完成时,如果CRC16匹配则设置force_boot=1(有效),否则设置force_boot=0(无效) |
-| 0x83 | 结束传输 | 写 | 总大小(4) | OTA升级,固件数据传输完成,确认总大小
-功能:验证接收数据大小是否匹配,保存CRC16到文件头并更新数据库
-数据格式:总大小(4字节小端序)
-验证:比较接收的总大小与已接收数据大小是否一致
-注意:CRC16校验已在0x82命令中完成,这里只验证大小并保存CRC16到文件头 |
-| 0x84 | 保留 | - | - | 已废弃,CRC校验功能已合并到0x82(传输固件数据)命令中,在数据接收时实时校验 |
-| 0x85 | 取消升级 | 写 | 取消标志(1) | OTA升级,取消正在进行的升级操作
-功能:清除OTA升级状态
-数据格式:取消标志(1字节,值任意) |
-
-
-| 命令码 | 指令名称 | 读/写 | 说明 | 备注 |
-|---|---|---|---|---|
-| 0xA0 | 工作模式设置 | 读/写 | 读:返回1字节工作模式(0x00=待机, 0x01=扫描, 0x02=消杀)
-写:接收1字节工作模式(0x00=待机, 0x01=扫描, 0x02=消杀) | 状态监控和控制,查询或设置设备当前工作模式 |
-| 0xA1 | 查询硬件状态 | 读 | 返回7字节硬件错误状态
-数据格式:温度错误(1) + 电机错误(1) + 激光器错误(1) + 视觉错误(1) + 加速度错误(1) + 毫米波雷达错误(1) + 激光雷达错误(1)
-数据来源:meas_ctrl.error | 硬件监控,查询各硬件模块的错误状态 |
-| 0xA2 | 查询设备信息 | 读/写 | 读:返回76字节设备信息
-写:接收56字节设备信息
-读数据格式:设备序列号(16) + 设备型号(16) + 设备ID(16) + BLE MAC地址(6) + WiFi MAC地址(6) + 固件版本(4) + 硬件版本(4) + OTA版本(4)
-写数据格式:设备序列号(16) + 设备型号(16) + 设备ID(16) + 固件版本(4) + 硬件版本(4)
-数据来源/存储:db_cfg.device_info | 设备信息查询和设置,查询或设置设备基本信息(序列号、型号、ID、版本信息等) |
-| 0xA3 | 查询统计数据 | 读 | 返回32字节(统计数据24字节 + datalog日志信息154字节)
-统计数据:灭虫数量(4) + 总工作时间(4) + 今日工作时间(4) + 扫描次数(4) + 激光发射次数(4) + 蚊虫数据数量(4)+本地记录(4) | 数据统计,查询设备工作统计数据和历史日志索引 |
-| 0xA4 | 查询蚊虫数据 | 读/写 | 写:接收8字节(start_index 4字节 + end_index 4字节,小端序)
-如果start_index=0xFFFFFFFF,表示读取全部数据
-读:返回当前设置的索引范围(start_index 4字节 + end_index 4字节)
-数据发送:通过通知机制(通知类型0x0B)发送每条蚊虫数据(22字节:索引4字节 + 数据18字节),每条数据间隔10ms
-数据来源:SPI Flash | 数据查询,从SPI Flash读取指定索引范围的蚊虫数据 |
-| 0xA5 | 查询设备传感器数据 | 读 | 返回10字节传感器数据
-数据格式:温度(2字节,单位:0.1°C) + X轴加速度(2字节,单位:mg) + Y轴加速度(2字节,单位:mg) + Z轴加速度(2字节,单位:mg) + 电容电压(2字节,单位:0.01V)
-数据来源:meas_ctrl.temp、meas_ctrl.accel、capture_get_laser_gun_dev()->current_voltage | 传感器数据查询,一次性获取设备温度、3D传感器数据和电容电压 |
-| 0xA6-0xBF | 保留 | - | - | 扩展用 |
-
-
-| 帧头 | 命令 | 读/写 | 长度 | 数据 | 校验 |
-|---|---|---|---|---|---|
-| 2字节 | 1字节 | 1字节 | 1字节 | N字节 | 2字节 |
-| 固定标识 0xAA 0x55,标识 BLE 通信数据包 | 命令码(0x01-0xFF),标识具体操作类型 | 操作类型标识:0x00=读操作,0x01=写操作,0x02=通知 | 数据字段长度(0-255 字节) | 命令参数或响应数据(可变长度) | CRC16 校验(LSB + MSB),校验范围:帧头到数据字段的所有字节 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x01 | 查询设备注册状态 | 读 | 1 | 返回设备是否已注册 |
-| 0x02 | 设备注册绑定 | 写 | 16 | 用户名(16),绑定关系存储在云端 |
-| 0x03 | 用户登录(密码) | 写 | 17 | 用户名(16) + 解锁状态(1),APP云端验证后发送 |
-| 0x04 | 指纹登录使能设置 | 写 | 17 | 用户名(16) + 使能状态(1),使能状态:0x00=禁用, 0x01=使能 |
-| 0x05 | 指纹录制请求 | 写 | 16 | 用户名(16)
-功能:启动指纹注册流程
-- 验证用户名是否存在
-- 确定page_id:如果用户已有finger_id,使用它重新注册;否则分配新的page_id
-- 检查指纹库是否已满
-- 启动指纹注册状态机(异步处理)
-- 注册成功后自动更新数据库(finger_id和finger_id_valid字段)
-权限要求:需要设备已解锁,且不能有注册正在进行中
-错误码:用户不存在(0x03)、权限拒绝(0x02)、设备错误(0x07)
-说明:注册状态可通过查询用户列表(0x07)查看finger_id_valid字段 |
-| 0x06 | 保留 | - | - | 扩展用 |
-| 0x07 | 查询用户列表 | 读 | 可变 | 返回用户列表
-数据格式:用户数量(1字节) + 每个用户:用户名(16字节) + 指纹ID(1字节) + 指纹ID有效标志(1字节)
-说明:指纹ID为uint8_t类型(0-255),指纹ID有效标志:0=无效,1=有效 |
-| 0x08 | 注销用户 | 写 | 16 | 用户名(16)
-功能:删除设备绑定的用户
-- 验证用户名是否存在
-- 如果用户有有效的指纹ID(finger_id_valid == 1),删除指纹库中对应的模板
-- 删除用户信息(用户名、指纹ID、登录时间等)
-- 检查是否还有用户存在,如果没有用户了,重置设备注册状态(registered = 0)
-- 保存数据库
-权限要求:需要设备已解锁
-错误码:用户不存在(0x03)、权限拒绝(0x02)、设备错误(0x07) |
-| 0x09-0x0F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x10 | 语言设置 | 读/写 | 1 | 0x00=英文, 0x01=中文 |
-| 0x11 | 时间设置 | 读/写 | 6 | 年(1) + 月(1) + 日(1) + 时(1) + 分(1) + 秒(1)
-年:1字节,相对于2000年的偏移(0-255对应2000-2255年) |
-| 0x12 | 定时任务设置 | 读/写 | 写:8
-读:可变(最多35) | 写:任务ID(1) + 开关(1) + 开始小时(1) + 开始分钟(1) + 结束小时(1) + 结束分钟(1) + 模式(1) + 重复(1)
-任务ID:0-4(对应数组索引)
-开关:0x00=禁用, 0x01=启用
-开始小时:0-23
-开始分钟:0-59
-结束小时:0-23
-结束分钟:0-59
-模式:待机(0x00,用于定时关机)、扫描(0x01)、消杀(0x02)
-重复:1字节,按位表示星期几重复执行
-- bit0: 周一 (0x01)
-- bit1: 周二 (0x02)
-- bit2: 周三 (0x04)
-- bit3: 周四 (0x08)
-- bit4: 周五 (0x10)
-- bit5: 周六 (0x20)
-- bit6: 周日 (0x40)
-读:返回所有定时任务列表(最多5组,每组7字节=开关+开始小时+开始分钟+结束小时+结束分钟+模式+重复) |
-| 0x13-0x1F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x20 | LCD 休眠设置 | 读/写 | 2 | 字节0:休眠开关(0x00=关, 0x01=开)
-字节1:休眠时间(0x01=1分钟, 0x05=5分钟, 0x0A=10分钟, 0x1E=30分钟) |
-| 0x21 | LCD 亮度设置 | 读/写 | 2 | 字节0:自适应开关(0x00=关闭自适应, 0x01=开启自适应)
-字节1:亮度值(10-100,仅在自适应关闭时有效) |
-| 0x22 | RGB 指示灯控制 | 读/写 | 5 | 开关(1) + R(1) + G(1) + B(1) + 效果(1)
-效果:0x00=闪烁, 0x01=常亮, 0x02=警示 |
-| 0x23 | WIFI 控制 | 读/写 | 读:65
-写:可变 | 读操作:返回65字节(开关1字节 + SSID 32字节 + 密码32字节)
-写操作:字节0为开关(0x00=关, 0x01=开),字节1-N为SSID字符串(以\0结尾,最多32字节,可选),字节N+1-M为密码字符串(以\0结尾,最多32字节,可选)
-说明:写操作至少需要1字节(开关),SSID和密码为可选字段 |
-| 0x24 | BLE 控制 | 读/写 | 可变 | 名称,BLE功能始终开启,不支持关闭 |
-| 0x25 | 多媒体控制 | 读/写 | 读:5
-写:可变 | 读操作:返回5字节(视频录制开关1字节 + 录制时长1字节 + 音效开关1字节 + 音效类型1字节 + 音量1字节)
-写操作:字节0为视频录制开关(0x00=关, 0x01=开),字节1为录制时长(1字节,0x03=3秒, 0x05=5秒, 0x0A=10秒),字节2为音效开关(0x00=关, 0x01=开),字节3为音效类型(0x00=Hello, 0x01=Goodbye, 0x02=Test, 0x03=BIU, 0x04=Main),字节4为音量(0-15)
-说明:写操作至少需要1字节(视频录制开关),其他字段为可选 |
-| 0x26 | 补光灯控制 | 读/写 | 3 | 开关(1) + 类型(1) + 光照强度(1)
-类型:0-红外光;1-白光;
-光照强度:0-低、1-中、2-高 |
-| 0x27 | 查询补光灯连接状态 | 读 | 1 | 返回连接状态(0=未连接, 1=已连接) |
-| 0x28 | 可见光激光器控制 | 读/写 | 1 | 0x00=关, 0x01=开 |
-| 0x29-0x3F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x40 | 角度控制 | 读/写 | 2 | 读操作:返回2字节(角度范围,小端序,单位:0.1度)
-写操作:接收2字节(角度范围,小端序,单位:0.1度)
-角度范围:1-900(对应0.1度-90度)
-说明:数据格式为小端序(低字节在前,高字节在后) |
-| 0x41 | 距离控制 | 读/写 | 4 | 检测距离(2) + 瞄准距离(2) |
-| 0x42-0x4F | 保留 | - | - | 扩展用
-注:0x42(害虫类型)、0x43(启动/停止)、0x44(查询消灭状态)已删除 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x50 | 毫米波雷达设置 | 读/写 | 4 | 读操作:返回4字节(开关1字节 + 灵敏度1字节 + 安全距离2字节)
-写操作:接收4字节(开关1字节 + 灵敏度1字节 + 安全距离2字节)
-开关:0x00=关闭, 0x01=开启
-灵敏度:0x00=低, 0x01=中, 0x02=高
-安全距离:2字节,小端序,单位0.1米,范围1-100(0.1米-10米)
-说明:数据格式为小端序(低字节在前,高字节在后) |
-| 0x51 | 视觉检测设置 | 读/写 | 2 | 读操作:返回2字节(开关1字节 + 灵敏度1字节)
-写操作:接收2字节(开关1字节 + 灵敏度1字节)
-开关:0x00=关闭, 0x01=开启
-灵敏度:0x00=低, 0x01=中, 0x02=高 |
-| 0x52 | 加速度传感器设置 | 读/写 | 3 | 读操作:返回3字节(开关1字节 + 灵敏度1字节 + 振动阈值1字节)
-写操作:接收3字节(开关1字节 + 灵敏度1字节 + 振动阈值1字节)
-开关:0x00=关闭, 0x01=开启
-灵敏度:0x00=低, 0x01=中, 0x02=高
-振动阈值:1-255 |
-| 0x53 | 查询加速度传感器数据 | 读 | 6 | 读操作:返回6字节(X轴加速度2字节 + Y轴加速度2字节 + Z轴加速度2字节)
-数据格式:每个轴2字节,小端序,有符号整数(int16_t)
-单位:mg(毫重力加速度)
-数据范围:-32768到32767(对应-32768mg到32767mg)
-说明:数据格式为小端序(低字节在前,高字节在后),有符号整数 |
-| 0x54 | 保留 | - | - | 扩展用
-注:0x54(查询振动状态)、0x56(查询当前温度)、0x57(查询温度状态)的功能已合并到0xA5"查询设备传感器数据"中 |
-| 0x55 | 温度监控设置 | 读/写 | 3 | 告警阈值(1) + 停止阈值(1) + 开关(1) |
-| 0X56 | 环境变化检测和试射测试 | 读/写 | 1 | 读操作:返回1字节环境变化状态(0-没有变化,1-发生变化)
-写操作:触发激光进行3次试射测试,每次间隔1秒,每次持续10ms.写操作:0X01进行激光测试
-说明,仅用于触发测试 |
-| 0x57-0x5F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x60 | 恢复出厂设置 | 写 | 1 | 需要管理员权限 |
-| 0x61-0x63 | 保留 | - | - | 已删除,功能已合并到0xA2命令中 |
-| 0x64-0x6F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x70 | 断开连接 | 写 | 1 | 主动断开连接 |
-| 0x71-0x7F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0x80 | 查询OTA版本信息 | 读 | 12 | 读操作:返回12字节版本信息
-数据格式(12字节,小端序):
-字节0-3:固件版本(uint32_t,4字节小端序)
-字节4-7:硬件版本(uint32_t,4字节小端序)
-字节8-11:OTA版本(uint32_t,4字节小端序)
-数据来源:db_cfg.device_info
-uint32_t:99999999表示版本号:99.99.99;1表示版本号00.00.00.01;
-写操作:不支持
-错误码:命令不支持(0x04) |
-| 0x81 | 开始升级 | 写 | 10 | 写操作:接收10字节(版本号4 + 大小4 + CRC16 2)
-数据格式(10字节,小端序):
-字节0-3:版本号(uint32_t,4字节小端序)
-字节4-7:固件大小(uint32_t,4字节小端序,单位:字节)
-字节8-9:CRC16校验和(uint16_t,2字节小端序)
-
-功能说明:
-1. 验证参数:检查固件大小是否有效(0 < 大小 <= 1MB-10字节)
-2. 检查状态:如果已有升级在进行,返回设备错误
-3. 擦除Flash:擦除1MB SPI Flash区域(地址0x100000,256个扇区,每个4KB)
-4. 写入文件头:版本号(4) + 固件大小(4) + CRC16(2,占位为0) = 10字节
-5. 初始化状态:设置OTA升级状态跟踪,保存期望的CRC16到状态结构
-6. 初始化force_boot:设置db_cfg.device_info.force_boot=0(初始为无效)
-
-错误码:参数无效(0x01)、设备错误(0x07) |
-| 0x82 | 传输固件数据 | 写 | 可变 | 写操作:接收可变长度(数据包序号2 + 数据N)
-数据格式(可变长度,小端序):
-字节0-1:数据包序号(uint16_t,2字节小端序)
-字节2-N:固件数据(N字节)
-
-功能说明:
-1. 检查状态:如果升级未开始,返回设备错误
-2. 验证数据:检查数据大小是否超出固件大小,检查写入地址是否超出范围
-3. 写入Flash:将数据写入Flash(从文件体起始地址0x10000A开始顺序写入)
-4. 实时计算CRC16:对接收的数据实时计算并累积CRC16(CRC16-CCITT算法,多项式0x1021,初始值0xFFFF)
-5. 更新状态:更新已接收数据大小和当前写入地址
-6. CRC16校验:当所有数据接收完成时(received_size >= firmware_size),比较计算的CRC16与期望的CRC16
- - 如果CRC16匹配:设置db_cfg.device_info.force_boot=1(固件有效)
- - 如果CRC16不匹配:设置db_cfg.device_info.force_boot=0(固件无效)
-7. 返回状态:返回成功响应,上位机继续发送下一帧数据
-
-错误码:参数无效(0x01)、设备错误(0x07) |
-| 0x83 | 结束传输 | 写 | 4 | 写操作:接收4字节(总大小4)
-数据格式(4字节,小端序):
-字节0-3:总大小(uint32_t,4字节小端序,单位:字节)
-
-功能说明:
-1. 检查状态:如果升级未开始,返回设备错误
-2. 验证大小:比较接收的总大小与已接收数据大小是否一致
-3. 保存CRC16:保存计算的CRC16到文件头(偏移8字节处)
-4. 更新数据库:更新数据库OTA版本(db_cfg.device_info.ota_version)和force_boot标志(db_cfg.device_info.force_boot)
-5. 保存数据库:保存数据库到非易失性存储
-6. 清除状态:清除OTA升级状态跟踪
-7. 返回响应:返回成功响应
-
-注意:CRC16校验已在0x82命令中完成,这里只验证大小并保存CRC16到文件头
-
-错误码:参数无效(0x01)、设备错误(0x07) |
-| 0x84 | 保留 | - | - | 已废弃:此命令已废弃,CRC校验功能已合并到0x82(传输固件数据)命令中,在数据接收时实时校验 |
-| 0x85 | 取消升级 | 写 | 1 | 写操作:接收1字节(取消标志)
-数据格式(1字节):
-字节0:取消标志(uint8_t,值任意)
-
-功能说明:
-1. 清除状态:清除OTA升级状态跟踪
-2. 返回成功:即使没有升级在进行,也返回成功
-
-错误码:参数无效(0x01) |
-| 0x86-0x9F | 保留 | - | - | 扩展用 |
-
-
-| 命令码 | 命令名称 | 读/写 | 数据长度 | 说明 |
-|---|---|---|---|---|
-| 0xA0 | 工作模式设置 | 读/写 | 1 | 读:返回1字节工作模式(0x00=待机, 0x01=扫描, 0x02=消杀)
-写:接收1字节工作模式(0x00=待机, 0x01=扫描, 0x02=消杀)
-数据来源/存储:db_cfg.system.work_mode |
-| 0xA1 | 查询硬件状态 | 读 | 7 | 返回7字节硬件错误状态
-字节0:温度错误(0x00=无错误, 0x01=温度过高, 0x02=温度过低)
-字节1:电机错误(0x00=无错误, 0x01=温度错误, 0x02=反馈错误)
-字节2:激光器错误(0x00=无错误, 0x01=温度过高, 0x02=电压过低, 0x03=电压过高)
-字节3:视觉错误(0x00=无错误, 0x01=通信错误)
-字节4:加速度错误(0x00=无错误, 0x01=通信错误,0X02=设备不稳定)
-字节5:毫米波雷达错误(0x00=无错误, 0x01=通信错误)
-字节6:激光雷达错误(0x00=无错误, 0x01=通信错误)
-数据来源:meas_ctrl.error(测量控制模块的错误状态) |
-| 0xA2 | 查询设备信息 | 读/写 | 读:76
-写:56 | 读操作:返回76字节设备信息
-数据格式(76字节,小端序):
-字节0-15:设备序列号(16字节,ASCII编码)
-字节16-31:设备型号(16字节,ASCII编码)
-字节32-47:设备ID(16字节,ASCII编码)
-字节48-53:BLE MAC地址(6字节)
-字节54-59:WiFi MAC地址(6字节)
-字节60-63:固件版本(uint32_t,4字节小端序)
-字节64-67:硬件版本(uint32_t,4字节小端序)
-字节68-71:OTA版本(uint32_t,4字节小端序)
-数据来源:db_cfg.device_info
-
-写操作:接收56字节设备信息
-数据格式(56字节,小端序):
-字节0-15:设备序列号(16字节,ASCII编码)
-字节16-31:设备型号(16字节,ASCII编码)
-字节32-47:设备ID(16字节,ASCII编码)
-字节48-51:固件版本(uint32_t,4字节小端序)
-字节52-55:硬件版本(uint32_t,4字节小端序)
-数据存储:db_cfg.device_info |
-| 0xA3 | 查询统计数据 | 读 | 32 | 返回32字节(统计数据24字节 + datalog日志信息4字节)
-统计数据部分(24字节,小端序):
-字节0-3:灭虫数量(uint32_t)
-字节4-7:总工作时间(uint32_t,单位:秒)
-字节8-11:今日工作时间(uint32_t,单位:秒)
-字节12-15:扫描次数(uint32_t)
-字节16-19:激光发射次数(uint32_t)
-字节20-23:蚊虫数据数量(uint32_t,扫描到、杀了的蚊子总数)
-
-datalog日志信息部分(4字节):
-字节24-27:设备本地数据条数。 |
-| 0xA4 | 查询蚊虫数据 | 读/写 | 写:4
-读:4
-通知:22 | 写操作:接收4字节(读取的数量,小端序)
-- 写操作成功后,设备通过通知机制发送数据,先发送成功响应,然后发送每条数据
-读操作:返回4字节(设备本地数据条数,小端序)
-数据发送(通知机制):
-- 通知类型:0x0B(蚊虫数据通知)
-- 每条通知数据:22字节 = 索引(4字节小端序) + 蚊虫数据(18字节)
-- 蚊虫数据结构:角度(4字节,*1000度,int32_t,支持负数,例如90000表示90.000度,-22500表示-22.500度) + 距离(2字节,毫米) + 大小(2字节,毫米) + 时间(6字节:year+month+day+hour+minute+second) + 保留(4字节)
-- 每条数据间隔:10ms
-数据来源:SPI Flash(从SPI_FLASH_MOSQUITO_DATA_ADDR开始存储,每条数据16字节,其中角度字段为int32_t) |
-| 0xA5 | 查询设备传感器数据 | 读 | 10 | 读操作:返回10字节传感器数据
-数据格式(10字节,小端序):
-字节0-1:温度(uint16_t,单位:0.1°C,例如250表示25.0°C)
-字节2-3:X轴加速度(int16_t,单位:mg,有符号整数,范围:-32768到32767)
-字节4-5:Y轴加速度(int16_t,单位:mg,有符号整数,范围:-32768到32767)
-字节6-7:Z轴加速度(int16_t,单位:mg,有符号整数,范围:-32768到32767)
-字节8-9:电容电压(uint16_t,单位:0.01V,例如1530表示15.30V)
-数据来源:
-- 温度:meas_ctrl.temp(测量控制模块的温度值,单位:°C)
-- 3D传感器数据:meas_ctrl.accel.x_mg、y_mg、z_mg(测量控制模块的加速度数据,单位:mg)
-- 电容电压:capture_get_laser_gun_dev()->current_voltage(激光器设备的当前电容电压,单位:V)
-说明:
-- 如果无法获取电容电压(设备未初始化或读取失败),电容电压字段返回0
-- 数据格式为小端序(低字节在前,高字节在后)
-- 写操作不支持,返回命令不支持错误 |
-| 0xA6-0xBF | 保留 | - | - | 扩展用 |
-
-
-| 帧头 | 命令 | 读/写 | 长度 | 数据 | 校验 |
-|---|---|---|---|---|---|
-| 2字节 | 1字节 | 1字节(0x00) | 1字节 | N字节 | 2字节 |
-| 固定标识 0xAA 0x55 | 原命令码 | 读操作标识 | 数据长度 | 状态码(1字节, 0x00=成功) + 响应数据 | CRC16校验 |
-
-
-| 帧头 | 命令 | 读/写 | 长度 | 数据 | 校验 |
-|---|---|---|---|---|---|
-| 2字节 | 1字节 | 1字节(0x00) | 1字节 | N字节 | 2字节 |
-| 固定标识 0xAA 0x55 | 原命令码 | 读操作标识 | 数据长度 | 状态码(1字节, 非0=错误码) + 错误描述(可选) | CRC16校验 |
-
-
-| 帧头 | 命令 | 读/写 | 长度 | 数据 | 校验 |
-|---|---|---|---|---|---|
-| 2字节 | 1字节 | 1字节(0x01) | 1字节 | N字节 | 2字节 |
-| 固定标识 0xAA 0x55 | 原命令码 | 写操作标识 | 数据长度 | 状态码(1字节, 0x00=成功) + 确认信息(可选) | CRC16校验 |
-
-
-| 帧头 | 命令 | 读/写 | 长度 | 数据 | 校验 |
-|---|---|---|---|---|---|
-| 2字节 | 1字节 | 1字节(0x01) | 1字节 | N字节 | 2字节 |
-| 固定标识 0xAA 0x55 | 原命令码 | 写操作标识 | 数据长度 | 状态码(1字节, 非0=错误码) + 错误描述(可选) | CRC16校验 |
-
-
-| 帧头 | 通知码 | 读/写 | 长度 | 数据 | 校验 |
-|---|---|---|---|---|---|
-| 2字节 | 1字节 | 1字节(0x02) | 1字节 | N字节 | 2字节 |
-| 固定标识 0xAA 0x55 | 通知码(与命令码区分) | 通知标识 | 数据长度 | 通知类型(1字节) + 通知数据 | CRC16校验 |
-
-
-| 错误码 | 含义 | 说明 |
-|---|---|---|
-| 0x00 | 成功 | 操作成功 |
-| 0x01 | 参数错误 | 参数无效或超出范围 |
-| 0x02 | 权限错误 | 用户未登录或权限不足 |
-| 0x03 | 设备忙 | 设备正在执行其他操作 |
-| 0x04 | 命令不支持 | 命令码不存在或未实现 |
-| 0x05 | 数据校验失败 | CRC 校验失败 |
-| 0x06 | 超时 | 操作超时 |
-| 0x07 | 设备错误 | 设备内部错误 |
-| 0x08-0xFF | 保留 | 扩展错误码 |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 命令 | 1 | 0x01 |
-| 3 | 读/写 | 1 | 0x00(读操作) |
-| 4 | 长度 | 1 | 0x00(无数据) |
-| 5-6 | 校验 | 2 | CRC16(字节0-4) |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 命令 | 1 | 0x02 |
-| 3 | 读/写 | 1 | 0x01(写操作) |
-| 4 | 长度 | 1 | 0x10(16字节) |
-| 5-20 | 数据 | 16 | 用户名(ASCII编码,不足用0x00填充) |
-| 21-22 | 校验 | 2 | CRC16(字节0-20) |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 命令 | 1 | 0x01(原命令码) |
-| 3 | 读/写 | 1 | 0x00(读操作) |
-| 4 | 长度 | 1 | 0x02(2字节数据) |
-| 5 | 状态码 | 1 | 0x00(成功) |
-| 6 | 响应数据 | 1 | 0x01(已注册) |
-| 7-8 | 校验 | 2 | CRC16(字节0-6) |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 命令 | 1 | 0x02(原命令码) |
-| 3 | 读/写 | 1 | 0x01(写操作) |
-| 4 | 长度 | 1 | 0x01(1字节数据) |
-| 5 | 状态码 | 1 | 0x00(成功) |
-| 6-7 | 校验 | 2 | CRC16(字节0-5) |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 命令 | 1 | 0x02(原命令码) |
-| 3 | 读/写 | 1 | 0x01(写操作) |
-| 4 | 长度 | 1 | 0x01(1字节数据) |
-| 5 | 状态码 | 1 | 0x01(参数错误) |
-| 6-7 | 校验 | 2 | CRC16(字节0-5) |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 通知码 | 1 | 0xF0(示例通知码) |
-| 3 | 读/写 | 1 | 0x02(通知) |
-| 4 | 长度 | 1 | 0x02(2字节数据) |
-| 5 | 通知类型 | 1 | 0x01(设备状态变化) |
-| 6 | 状态码 | 1 | 0x01(扫描模式) |
-| 7-8 | 校验 | 2 | CRC16(字节0-6) |
-
-
-| 字节序号 | 字段名 | 字节数 | 值/说明 |
-|---|---|---|---|
-| 0-1 | 帧头 | 2 | 0xAA 0x55 |
-| 2 | 通知码 | 1 | 0xF0(示例通知码) |
-| 3 | 读/写 | 1 | 0x02(通知) |
-| 4 | 长度 | 1 | 0x02(2字节数据) |
-| 5 | 通知类型 | 1 | 0x02(错误状态上报) |
-| 6 | 错误码 | 1 | 0x07(设备错误) |
-| 7-8 | 校验 | 2 | CRC16(字节0-6) |
diff --git a/Assets/BLE通信方案V2.2.md.meta b/Assets/BLE通信方案V2.2.md.meta
deleted file mode 100644
index becaf7b..0000000
--- a/Assets/BLE通信方案V2.2.md.meta
+++ /dev/null
@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: dd01dd9ee3681b4479d61a61f861ca73
-TextScriptImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Plugins/iOS/CameraFocusHelper.mm b/Assets/Plugins/iOS/CameraFocusHelper.mm
index 54b9a2a..269946c 100644
--- a/Assets/Plugins/iOS/CameraFocusHelper.mm
+++ b/Assets/Plugins/iOS/CameraFocusHelper.mm
@@ -4,16 +4,21 @@
/// 获取当前正在使用的后置摄像头设备
static AVCaptureDevice *GetActiveRearCamera(void)
{
- // 遍历所有正在运行的 AVCaptureSession,找到后置摄像头的输入
+ // 遍历所有 AVCaptureDevice,找到后置摄像头
+ // 注意:Unity WebCamTexture 内部的 AVCaptureSession 不会暴露给原生代码,
+ // 但 AVCaptureDevice 本身可以独立 lockForConfiguration 设置对焦模式,
+ // 不需要成为当前 session 的输入设备。
if (@available(iOS 10.0, *)) {
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
- discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera]
+ discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera,
+ AVCaptureDeviceTypeBuiltInTelephotoCamera,
+ AVCaptureDeviceTypeBuiltInDualCamera,
+ AVCaptureDeviceTypeBuiltInDualWideCamera,
+ AVCaptureDeviceTypeBuiltInTripleCamera]
mediaType:AVMediaTypeVideo
position:AVCaptureDevicePositionBack];
for (AVCaptureDevice *d in session.devices) {
- if (d.deviceType == AVCaptureDeviceTypeBuiltInWideAngleCamera) {
- return d;
- }
+ return d; // 拿到任意后置摄像头即可
}
}
return [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
@@ -68,4 +73,11 @@ extern "C" {
}
}
+ /// 周期性重新触发对焦(与 Android softTrigger 思路一致)
+ /// 通过"点对焦 → 1.5s 后恢复连续对焦"的方式让镜头做一次完整对焦循环,无黑屏
+ void _TriggerContinuousRefocus(void)
+ {
+ _TriggerAutoFocus(0.5f, 0.5f);
+ }
+
}
diff --git a/Assets/Res/ui/connectdevicepage/connectDevicePage.prefab b/Assets/Res/ui/connectdevicepage/connectDevicePage.prefab
index 7279eaa..4d4ce38 100644
--- a/Assets/Res/ui/connectdevicepage/connectDevicePage.prefab
+++ b/Assets/Res/ui/connectdevicepage/connectDevicePage.prefab
@@ -1190,10 +1190,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 1027440964492730408}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 1}
- m_AnchorMax: {x: 0, y: 1}
- m_AnchoredPosition: {x: 345, y: -20.5}
- m_SizeDelta: {x: 640, y: 1}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3091012864071486640
CanvasRenderer:
@@ -1272,7 +1272,7 @@ RectTransform:
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
- m_SizeDelta: {x: 0, y: 61}
+ m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &3494079613403270933
CanvasRenderer:
@@ -2219,6 +2219,7 @@ GameObject:
- component: {fileID: 3037912135858668214}
- component: {fileID: 7322899035469920849}
- component: {fileID: 595539671003003557}
+ - component: {fileID: 6470133651671008070}
m_Layer: 5
m_Name: Image
m_TagString: Untagged
@@ -2294,14 +2295,65 @@ MonoBehaviour:
m_EditorClassIdentifier:
cameraPreview: {fileID: 7322899035469920849}
scanType: 0
- enableUpscale: 1
- upscaleFactor: 2
- scanResolutionWidth: 640
useAsyncScan: 1
- centerCropRatio: 0.5
- enableAutoFocus: 1
- focusInterval: 1
+ centerCropRatio: 0.4
previewUpdateInterval: 0.03
+--- !u!114 &6470133651671008070
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2945323903071304015}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_WrapAround: 0
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 0
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Selected
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 7322899035469920849}
+ m_OnClick:
+ m_PersistentCalls:
+ m_Calls:
+ - m_Target: {fileID: 595539671003003557}
+ m_TargetAssemblyTypeName: Kill.UI.Pages.ScanQRcode, Assembly-CSharp
+ m_MethodName: TriggerRefocus
+ m_Mode: 1
+ m_Arguments:
+ m_ObjectArgument: {fileID: 0}
+ m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
+ m_IntArgument: 0
+ m_FloatArgument: 0
+ m_StringArgument:
+ m_BoolArgument: 0
+ m_CallState: 2
--- !u!1 &2952991085794770192
GameObject:
m_ObjectHideFlags: 0
@@ -4697,10 +4749,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5409940142953436106}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 1}
- m_AnchorMax: {x: 0, y: 1}
- m_AnchoredPosition: {x: 79, y: -24}
- m_SizeDelta: {x: 48, y: 48}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3550239897604221497
CanvasRenderer:
@@ -6700,10 +6752,10 @@ RectTransform:
m_Children: []
m_Father: {fileID: 5409940142953436106}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
- m_AnchorMin: {x: 0, y: 1}
- m_AnchorMax: {x: 0, y: 1}
- m_AnchoredPosition: {x: 262, y: -24}
- m_SizeDelta: {x: 262, y: 42}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &9174153800022959653
CanvasRenderer:
@@ -8222,7 +8274,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
- m_IsActive: 0
+ m_IsActive: 1
--- !u!224 &6952404845597796920
RectTransform:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/UI/Pages/ConnectDevice/ScanQRcode.cs b/Assets/Scripts/UI/Pages/ConnectDevice/ScanQRcode.cs
index c410225..4aae2fe 100644
--- a/Assets/Scripts/UI/Pages/ConnectDevice/ScanQRcode.cs
+++ b/Assets/Scripts/UI/Pages/ConnectDevice/ScanQRcode.cs
@@ -22,31 +22,17 @@ namespace Kill.UI.Pages
[Header("UI组件")]
public RawImage cameraPreview; // 摄像头预览
[Header("扫描设置")]
- float scanInterval = 0.1f; // 扫描间隔(秒)
+ float scanInterval = 0.05f; // 扫描间隔(秒),密集扫描提升远距离识别率
public ScanType scanType = ScanType.ConnectDevice; // 扫描类型
-
- [Header("小二维码优化")]
- [Tooltip("启用图像放大(推荐用于小二维码)")]
- public bool enableUpscale = true;
- [Tooltip("放大倍数")]
- public float upscaleFactor = 2f;
-
+
[Header("性能优化")]
- [Tooltip("降低扫描分辨率(推荐值:640或480)")]
- public int scanResolutionWidth = 1080;
[Tooltip("扫描处理线程数")]
- public bool useAsyncScan = true;
+ bool useAsyncScan = true;
[Header("中心裁切放大")]
- [Tooltip("截取画面中心区域的比例(0-1),值越小放越大,推荐0.55~0.7")]
+ [Tooltip("截取画面中心区域的比例(0-1),值越小放越大。\n推荐:近距离 0.55~0.65;远距离扫码 0.8~0.9")]
[Range(0.3f, 1.0f)]
- public float centerCropRatio = 0.65f;
-
- [Header("自动对焦")]
- [Tooltip("启用自动对焦")]
- public bool enableAutoFocus = true;
- [Tooltip("对焦间隔(秒)")]
- public float focusInterval = 1f;
+ float centerCropRatio = 1f;
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
@@ -54,6 +40,16 @@ namespace Kill.UI.Pages
[DllImport("__Internal")]
private static extern void _TriggerAutoFocus(float normalizedX, float normalizedY);
+
+ [DllImport("__Internal")]
+ private static extern void _TriggerContinuousRefocus();
+#endif
+
+#if UNITY_ANDROID && !UNITY_EDITOR
+ // 通过反射拿到的 WebCamTexture 内部 Camera 实例(Unity 2022 LTS 一般拿不到,详见 EnableAndroidContinuousAutoFocus)
+ private AndroidJavaObject androidCameraInstance;
+ // 标记是否成功接管对焦控制
+ private bool androidFocusControlAvailable = false;
#endif
// 摄像头相关
@@ -75,19 +71,38 @@ namespace Kill.UI.Pages
StopScan();
}
- void Update()
+ // 手动重新对焦防抖时间:0.6s 内的重复点击只触发一次,避免过于频繁触发对焦
+ private float lastManualRefocusTime = -10f;
+ private const float ManualRefocusCooldown = 0.6f;
+
+ ///
+ /// 用户手动点击预览时调用:触发一次软重新对焦(无黑屏,体验好)。
+ /// 已接管 Camera 的设备走点对焦;未接管(Unity 2022 LTS 默认情况)走 autoFocusPoint 重新设置。
+ ///
+ public void TriggerRefocus()
{
+ if (!isScanning) return;
+ if (Time.unscaledTime - lastManualRefocusTime < ManualRefocusCooldown) return;
+
+ lastManualRefocusTime = Time.unscaledTime;
+
#if UNITY_IOS && !UNITY_EDITOR
- if (isScanning && Input.touchCount > 0)
+ // iOS:调用原生插件触发一次点对焦(1.5s 后原生自动恢复连续对焦,无黑屏)
+ _TriggerContinuousRefocus();
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ if (androidFocusControlAvailable && androidCameraInstance != null)
{
- Touch touch = Input.GetTouch(0);
- if (touch.phase == TouchPhase.Began)
- {
- float normX = touch.position.x / Screen.width;
- float normY = touch.position.y / Screen.height;
- _TriggerAutoFocus(normX, normY);
- }
+ // 已接管:走正常的点对焦
+ TriggerAndroidAutoFocus(0.5f, 0.5f);
}
+ else
+ {
+ // 未接管:走软触发(无黑屏)
+ Debug.Log("[ScanQRcode] 用户点击预览,触发软重新对焦");
+ SoftTriggerAndroidFocus();
+ }
+#else
+ // 编辑器/其他平台:无操作
#endif
}
@@ -121,6 +136,16 @@ namespace Kill.UI.Pages
Destroy(previewTexture);
previewTexture = null;
}
+
+#if UNITY_ANDROID && !UNITY_EDITOR
+ // 反射拿到的 Camera 实例由 WebCamTexture 内部管理,仅清空引用即可
+ if (androidCameraInstance != null)
+ {
+ androidCameraInstance.Dispose();
+ androidCameraInstance = null;
+ }
+ androidFocusControlAvailable = false;
+#endif
}
///
@@ -182,8 +207,9 @@ namespace Kill.UI.Pages
}
}
RectTransform cameraPreviewRect=cameraPreview.GetComponent();
- // 使用 1080p 分辨率
- webCamTexture = new WebCamTexture(deviceName, 1920, 1080, 30);
+ // 尝试更高分辨率(4K);WebCamTexture 在不支持时会自动回退到设备最大值
+ // 注意:高分辨率下解码 CPU 压力大,需要配合性能较好的设备
+ webCamTexture = new WebCamTexture(deviceName, 3840, 2160, 30);
// 开始播放
webCamTexture.Play();
@@ -191,11 +217,30 @@ namespace Kill.UI.Pages
// 等待摄像头启动
yield return new WaitUntil(() => webCamTexture.width > 100);
+ // 输出实际分辨率,方便确认设备用的最大值
+ Debug.Log($"[ScanQRcode] 摄像头实际分辨率: {webCamTexture.width}x{webCamTexture.height}");
+
+ // 等 0.3s 再触发对焦,避免系统未准备好
+ yield return new WaitForSeconds(0.3f);
+
#if UNITY_IOS && !UNITY_EDITOR
// iOS: 通过原生插件开启连续自动对焦(autoFocusPoint 在 iOS 无效)
_EnableContinuousAutoFocus();
- // 定期维护对焦状态,防止被系统重置
+ // 周期维护对焦:每 3s 触发一次点对焦(无黑屏),让镜头持续对焦
StartCoroutine(IOsFocusMaintainCoroutine());
+#elif UNITY_ANDROID && !UNITY_EDITOR
+ // Android:WebCamTexture 默认不会自动对焦,需主动通过 Camera API 触发
+ EnableAndroidContinuousAutoFocus();
+ // 仅当我们拿到 Camera 实例时才启动对焦维护协程(Unity 2022 LTS Camera2 后端一般拿不到)
+ if (androidFocusControlAvailable)
+ {
+ StartCoroutine(AndroidFocusMaintainCoroutine());
+ }
+ else
+ {
+ // 兜底:未接管到 Camera 时,周期性触发重新对焦(部分只对焦一次的设备)
+ StartCoroutine(AndroidFallbackFocusMaintainCoroutine());
+ }
#endif
webCamTexture.autoFocusPoint = new Vector2(0.5f, 0.5f);
// 调整预览画面比例
@@ -208,25 +253,59 @@ namespace Kill.UI.Pages
}
+ // 预览纹理长边基准(按 RawImage 实际宽高比生成,避免变形)
+ // 保持 UI RectTransform 比例,内部纹理按比例放大到接近 1080 长边
+ // 但不超过相机实际分辨率的 1.5 倍,避免低端设备过度放大导致画质损失
+ private const int PreviewTextureLongSide = 1080;
+ private const float MaxUpscaleRatio = 1.5f;
+
///
- /// 调整预览画面比例 - 从相机画面中心截取最大适合预览画面比例的画面
- /// 手机竖着拿,摄像头默认横向输出,需要旋转并截取中间区域
- /// 预览框是横向的(1080x680),显示旋转后的竖向画面
+ /// 调整预览画面比例 - 从相机画面中心截取并旋转,生成与 UI 框同比例的高清纹理。
+ /// 手机竖着拿,摄像头默认横向输出,需要旋转 90 度。
+ /// 预览纹理宽高比跟随 RawImage RectTransform 宽高比,长边固定 1080,
+ /// 既保证远距离扫码识别率,又避免画面变形。
+ /// 对低端设备(720p 摄像头)会自适应降低纹理尺寸,避免过度放大导致画质损失。
///
private void AdjustPreviewAspect()
{
if (webCamTexture == null || cameraPreview == null) return;
-
- // 获取相机分辨率(摄像头默认横向,如 1920x1080)
- int cameraWidth = webCamTexture.width; // 1920
- int cameraHeight = webCamTexture.height; // 1080
-
- // 获取预览区域尺寸(横向预览框,比如 1080x680)
+
+ // 获取相机分辨率(摄像头默认横向,如 3840x2160 / 1920x1080 / 1280x720)
+ int cameraWidth = webCamTexture.width;
+ int cameraHeight = webCamTexture.height;
+
+ // 获取预览区域尺寸(用于按比例生成纹理,避免变形)
RectTransform previewRect = cameraPreview.GetComponent();
- int previewWidth = (int)previewRect.rect.width; // 1080
- int previewHeight = (int)previewRect.rect.height; // 680
-
- // 创建预览纹理(与预览框相同尺寸)
+ float rectW = Mathf.Max(1, previewRect.rect.width);
+ float rectH = Mathf.Max(1, previewRect.rect.height);
+
+ // 相机短边(旋转后对应预览宽边),用于限制过度放大
+ int cameraShortSide = Mathf.Min(cameraWidth, cameraHeight);
+
+ // 按 RectTransform 比例计算纹理尺寸(长边固定 1080)
+ int previewWidth, previewHeight;
+ if (rectW >= rectH)
+ {
+ previewWidth = PreviewTextureLongSide;
+ previewHeight = Mathf.Max(1, Mathf.RoundToInt(PreviewTextureLongSide * (rectH / rectW)));
+ }
+ else
+ {
+ previewHeight = PreviewTextureLongSide;
+ previewWidth = Mathf.Max(1, Mathf.RoundToInt(PreviewTextureLongSide * (rectW / rectH)));
+ }
+
+ // 自适应:如果纹理长边超过相机短边的 1.5 倍,按相机分辨率等比缩小(保护低端设备画质)
+ int previewLongSide = Mathf.Max(previewWidth, previewHeight);
+ int maxAllowedLong = Mathf.RoundToInt(cameraShortSide * MaxUpscaleRatio);
+ if (previewLongSide > maxAllowedLong)
+ {
+ float scale = (float)maxAllowedLong / previewLongSide;
+ previewWidth = Mathf.Max(1, Mathf.RoundToInt(previewWidth * scale));
+ previewHeight = Mathf.Max(1, Mathf.RoundToInt(previewHeight * scale));
+ }
+
+ // 创建预览纹理(按 UI 比例)
if (previewTexture == null || previewTexture.width != previewWidth || previewTexture.height != previewHeight)
{
if (previewTexture != null)
@@ -235,32 +314,265 @@ namespace Kill.UI.Pages
}
previewTexture = new Texture2D(previewWidth, previewHeight, TextureFormat.RGB24, false);
}
-
+
// 从相机画面中裁切并旋转
- // 手机竖着拿,相机横向输出,需要顺时针旋转90度
- // 从相机画面中间截取适合预览比例的竖向区域
CutAndRotateCameraFrame(cameraWidth, cameraHeight, previewWidth, previewHeight);
-
+
// 设置预览纹理
cameraPreview.texture = previewTexture;
cameraPreview.uvRect = new Rect(0, 0, 1, 1);
-
+
// 重置旋转(因为已经在像素层面旋转了)
previewRect.localEulerAngles = Vector3.zero;
-
+
Debug.Log($"[ScanQRcode] 预览调整 - 相机: {cameraWidth}x{cameraHeight}, 预览: {previewWidth}x{previewHeight}");
}
#if UNITY_IOS && !UNITY_EDITOR
///
- /// iOS 对焦维护协程 — 定期重新设置连续自动对焦,防止被系统重置
+ /// iOS 对焦维护协程 — 每 3s 触发一次点对焦(无黑屏),让镜头持续重新对焦
+ /// 与 Android softTrigger 策略保持一致
///
private IEnumerator IOsFocusMaintainCoroutine()
{
+ // 首次等待 3s 让初始对焦完成
+ yield return new WaitForSeconds(3f);
+
while (isScanning)
{
+ // 触发一次点对焦(无黑屏),1.5s 后原生会自动恢复连续对焦
+ _TriggerContinuousRefocus();
yield return new WaitForSeconds(3f);
- _EnableContinuousAutoFocus();
+ }
+ }
+#endif
+
+#if UNITY_ANDROID && !UNITY_EDITOR
+ ///
+ /// 启用 Android 自动对焦。
+ /// Unity 2022 LTS 在 Android 上使用 Camera2 API 内部创建 CameraDevice,
+ /// 无法直接反射拿到 Camera 对象,本方法仅尝试拿到旧 Camera API 的实例。
+ /// 失败时仅打印日志,不阻塞扫码流程(依赖系统默认对焦)。
+ ///
+ private void EnableAndroidContinuousAutoFocus()
+ {
+ androidCameraInstance = GetAndroidCameraFromWebCamTexture(webCamTexture);
+ if (androidCameraInstance != null)
+ {
+ try
+ {
+ SetCameraFocusMode(androidCameraInstance, "FOCUS_MODE_CONTINUOUS_PICTURE");
+ androidFocusControlAvailable = true;
+ Debug.Log("[ScanQRcode] Android 自动对焦已接管(FOCUS_MODE_CONTINUOUS_PICTURE)");
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[ScanQRcode] 设置 Android 自动对焦模式失败: {e.Message}");
+ androidFocusControlAvailable = false;
+ }
+ }
+ else
+ {
+ androidFocusControlAvailable = false;
+ // 尝试让 WebCamTexture 自身使用自动对焦(部分 ROM 支持)
+ try { webCamTexture.autoFocusPoint = new Vector2(0.5f, 0.5f); } catch { }
+ }
+ }
+
+ ///
+ /// 触发 Android 单次自动对焦(指定点)
+ ///
+ private void TriggerAndroidAutoFocus(float normX, float normY)
+ {
+ if (!androidFocusControlAvailable || androidCameraInstance == null)
+ {
+ // 没接管到对焦,触摸对焦无效(保持安静,不打日志避免刷屏)
+ return;
+ }
+
+ try
+ {
+ // 屏幕坐标 -> camera 坐标系(Camera API 的 focusAreas 使用 -1000~1000 坐标系)
+ int focusX = Mathf.RoundToInt((normX - 0.5f) * 2000f);
+ int focusY = Mathf.RoundToInt((normY - 0.5f) * 2000f);
+
+ using (var rectClass = new AndroidJavaClass("android.graphics.Rect"))
+ using (var area = rectClass.CallStatic("new", focusX - 100, focusY - 100, focusX + 100, focusY + 100))
+ using (var areasList = new AndroidJavaObject("java.util.ArrayList"))
+ {
+ areasList.Call("add", area);
+ androidCameraInstance.Call("setFocusAreas", areasList);
+ androidCameraInstance.Call("setMeteringAreas", areasList);
+ }
+
+ // 先切到 AUTO 模式才能执行单次对焦,对焦完再切回 CONTINUOUS
+ SetCameraFocusMode(androidCameraInstance, "FOCUS_MODE_AUTO");
+
+ // AndroidJavaProxy 不是 IDisposable,不能 using;也不需要 Dispose,GC 即可回收
+ var callback = new AutoFocusCallbackProxy(this);
+ androidCameraInstance.Call("autoFocus", callback);
+
+ // 1.5s 后切回连续模式(对焦回调可能未触发,需要兜底)
+ StartCoroutine(RestoreContinuousFocusAfter(1.5f));
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[ScanQRcode] Android 触摸对焦失败: {e.Message}");
+ }
+ }
+
+ private IEnumerator RestoreContinuousFocusAfter(float seconds)
+ {
+ yield return new WaitForSeconds(seconds);
+ if (androidFocusControlAvailable && androidCameraInstance != null)
+ {
+ SetCameraFocusMode(androidCameraInstance, "FOCUS_MODE_CONTINUOUS_PICTURE");
+ }
+ }
+
+ ///
+ /// 周期性重新设置连续对焦,防止 Android 镜头因失焦后停止自动对焦
+ ///
+ private IEnumerator AndroidFocusMaintainCoroutine()
+ {
+ while (isScanning)
+ {
+ yield return new WaitForSeconds(2.5f);
+ if (androidFocusControlAvailable && androidCameraInstance != null)
+ {
+ try
+ {
+ SetCameraFocusMode(androidCameraInstance, "FOCUS_MODE_CONTINUOUS_PICTURE");
+ }
+ catch { /* 实例可能已被释放,下次循环重试 */ }
+ }
+ }
+ }
+
+ ///
+ /// 兜底对焦策略:未接管到 Camera 实例时(Unity 2022 LTS 默认情况),
+ /// 每 3s 通过 softTrigger(autoFocusPoint 重新设置,几乎无感)让镜头重新对焦。
+ /// 用户也可通过调用 TriggerRefocus() 主动触发。
+ ///
+ private IEnumerator AndroidFallbackFocusMaintainCoroutine()
+ {
+ // 首次等待 3s 让初始对焦完成
+ yield return new WaitForSeconds(3f);
+
+ while (isScanning && webCamTexture != null)
+ {
+ // 如果在运行中已经接管到对焦控制(理论上不会变),停止兜底
+ if (androidFocusControlAvailable) break;
+
+ // 软触发:重新设置 autoFocusPoint(无黑屏)
+ SoftTriggerAndroidFocus();
+
+ yield return new WaitForSeconds(3f);
+ }
+ }
+
+ ///
+ /// 软触发对焦:通过重新设置 autoFocusPoint 让手机做一次快速对焦,无黑屏。
+ /// 部分手机响应后会重新对焦,部分手机忽略此属性(需要走硬重启兜底)。
+ ///
+ private void SoftTriggerAndroidFocus()
+ {
+ if (webCamTexture == null || !webCamTexture.isPlaying) return;
+ try
+ {
+ // 在 (0.5, 0.5) 和 (0.5, 0.51) 之间切换,强制触发 setter
+ // 相同值部分 ROM 不响应,需要做点变化
+ webCamTexture.autoFocusPoint = new Vector2(0.5f, 0.51f);
+ webCamTexture.autoFocusPoint = new Vector2(0.5f, 0.5f);
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[ScanQRcode] softTrigger 失败: {e.Message}");
+ }
+ }
+
+ ///
+ /// 通过停止+重新播放 WebCamTexture 强制镜头重新对焦。
+ /// 注意:会有约 200~500ms 的预览黑屏闪烁,权衡使用。
+ ///
+ private void RestartWebCamTextureForFocus()
+ {
+ try
+ {
+ if (webCamTexture == null) return;
+ webCamTexture.Stop();
+ webCamTexture.Play();
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[ScanQRcode] 重启 WebCamTexture 失败: {e.Message}");
+ }
+ }
+
+ ///
+ /// 通过反射从 WebCamTexture 拿到内部的 android.hardware.Camera 实例。
+ /// 注意:Unity 2022 LTS 在 Android 上使用 Camera2 API,此反射在 2022 LTS 上几乎拿不到值。
+ ///
+ private AndroidJavaObject GetAndroidCameraFromWebCamTexture(WebCamTexture tex)
+ {
+ try
+ {
+ string[] candidates = { "m_Camera", "m_NativeCamera", "camera", "mCamera" };
+ foreach (var name in candidates)
+ {
+ var f = typeof(WebCamTexture).GetField(name,
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ if (f != null)
+ {
+ var v = f.GetValue(tex);
+ if (v is AndroidJavaObject ajo) return ajo;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogWarning($"[ScanQRcode] 反射获取 Camera 失败: {e.Message}");
+ }
+ return null;
+ }
+
+ ///
+ /// 设置 Camera 对焦模式(Camera API 官方推荐通过 setParameters 设置)
+ ///
+ private void SetCameraFocusMode(AndroidJavaObject camera, string modeName)
+ {
+ if (camera == null) return;
+ using (var parameters = camera.Call("getParameters"))
+ {
+ parameters.Call("setFocusMode", modeName);
+ camera.Call("setParameters", parameters);
+ }
+ // 部分旧设备直接调用 setFocusMode(String) 也生效,兼容一下
+ camera.Call("setFocusMode", modeName);
+ }
+
+ ///
+ /// AutoFocusCallback 的 C# 实现(AndroidJavaProxy)
+ ///
+ private class AutoFocusCallbackProxy : AndroidJavaProxy
+ {
+ private readonly ScanQRcode _owner;
+ public AutoFocusCallbackProxy(ScanQRcode owner) : base("android.hardware.Camera$AutoFocusCallback")
+ {
+ _owner = owner;
+ }
+
+ // Java 签名: void onAutoFocus(boolean success, Camera camera)
+ public void onAutoFocus(bool success, AndroidJavaObject camera)
+ {
+ try
+ {
+ if (_owner != null && _owner.androidFocusControlAvailable && _owner.androidCameraInstance != null)
+ {
+ _owner.SetCameraFocusMode(_owner.androidCameraInstance, "FOCUS_MODE_CONTINUOUS_PICTURE");
+ }
+ }
+ catch { }
}
}
#endif
@@ -270,62 +582,57 @@ namespace Kill.UI.Pages
///
/// 从相机画面裁切并旋转,生成预览画面
/// 支持中心裁切放大:只截取画面中心区域,放大到预览尺寸
+ /// 性能优化:按行复用 float 增量,避免重复乘法
///
private void CutAndRotateCameraFrame(int camW, int camH, int targetW, int targetH)
{
- // 相机横向 1920x1080,需要顺时针旋转90度变成 1080x1920
- // 预览框横向 1080x680
- // 从旋转后的 1080x1920 中截取中间区域并放大
-
// 计算中心裁切区域大小
- // centerCropRatio=1.0 时等于原始裁切(取 targetH 列 / 所有行),无缩放
- // centerCropRatio<1.0 时取更小区域放大,值越小放大倍数越大
int sourceCropH = Mathf.Max(1, Mathf.RoundToInt(targetH * centerCropRatio));
int sourceCropW = Mathf.Max(1, Mathf.RoundToInt(targetW * centerCropRatio));
int cropY = (camW - sourceCropH) / 2;
int cropX = (camH - sourceCropW) / 2;
-
+
// 两个方向缩放一致,保证不变形
float scale = centerCropRatio;
-
+
// 获取相机像素数据
Color32[] cameraPixels = webCamTexture.GetPixels32();
Color32[] targetPixels = new Color32[targetW * targetH];
-
+
// 顺时针旋转90度 + 中心裁切 + 缩放
- // 目标像素 (x, y) 对应源坐标:
- // srcX = cropY + (targetH-1-y) * scale (裁切区域内纵向缩放)
- // srcY = cropX + x * scale (裁切区域内横向缩放)
for (int y = 0; y < targetH; y++)
{
+ float srcXBase = cropY + (targetH - 1 - y) * scale; // 整行的 srcX
+ float srcYBase = cropX; // 起始 srcY
+
+ int rowStart = y * targetW;
for (int x = 0; x < targetW; x++)
{
- float srcX = cropY + (targetH - 1 - y) * scale;
- float srcY = cropX + x * scale;
-
- // 双线性插值采样
+ float srcX = srcXBase;
+ float srcY = srcYBase + x * scale;
+
int sx = (int)srcX;
int sy = (int)srcY;
float fx = srcX - sx;
float fy = srcY - sy;
int sx1 = Mathf.Min(sx + 1, camW - 1);
int sy1 = Mathf.Min(sy + 1, camH - 1);
-
+
int idx00 = sy * camW + sx;
int idx10 = sy * camW + sx1;
int idx01 = sy1 * camW + sx;
int idx11 = sy1 * camW + sx1;
-
+
Color32 c00 = cameraPixels[idx00];
Color32 c10 = cameraPixels[idx10];
Color32 c01 = cameraPixels[idx01];
Color32 c11 = cameraPixels[idx11];
-
+
float r = (1-fx)*(1-fy)*c00.r + fx*(1-fy)*c10.r + (1-fx)*fy*c01.r + fx*fy*c11.r;
float g = (1-fx)*(1-fy)*c00.g + fx*(1-fy)*c10.g + (1-fx)*fy*c01.g + fx*fy*c11.g;
float b = (1-fx)*(1-fy)*c00.b + fx*(1-fy)*c10.b + (1-fx)*fy*c01.b + fx*fy*c11.b;
-
- targetPixels[y * targetW + x] = new Color32(
+
+ targetPixels[rowStart + x] = new Color32(
(byte)Mathf.Clamp(r, 0, 255),
(byte)Mathf.Clamp(g, 0, 255),
(byte)Mathf.Clamp(b, 0, 255),
@@ -333,7 +640,7 @@ namespace Kill.UI.Pages
);
}
}
-
+
// 设置像素到预览纹理
previewTexture.SetPixels32(targetPixels);
previewTexture.Apply();
@@ -484,13 +791,7 @@ namespace Kill.UI.Pages
Texture2D snapshot = new Texture2D(webCamTexture.width, webCamTexture.height);
snapshot.SetPixels(webCamTexture.GetPixels());
snapshot.Apply();
-
- // // 如果启用放大,对图像进行放大处理
- // if (enableUpscale && upscaleFactor > 1f)
- // {
- // snapshot = UpscaleTexture(snapshot, upscaleFactor);
- // }
-
+
return snapshot;
}
catch
@@ -525,17 +826,17 @@ namespace Kill.UI.Pages
// 使用 RenderTexture 进行高质量缩放
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight, 0, RenderTextureFormat.RGB565);
RenderTexture.active = rt;
-
+
Graphics.Blit(source, rt);
result.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
result.Apply();
-
+
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
-
+
// 销毁原始纹理
Destroy(source);
-
+
return result;
}
diff --git a/Assets/Scripts/UI/Pages/DeviceInfoPage/RenamePage.cs b/Assets/Scripts/UI/Pages/DeviceInfoPage/RenamePage.cs
index 1e1960d..a623ac8 100644
--- a/Assets/Scripts/UI/Pages/DeviceInfoPage/RenamePage.cs
+++ b/Assets/Scripts/UI/Pages/DeviceInfoPage/RenamePage.cs
@@ -24,6 +24,7 @@ namespace Kill.UI.Pages
private Action onConfirmCallback;
private int type = 0;//0设备重命名 1用户名重命名
private string deviceMac;// 待重命名设备的蓝牙MAC(为空时取当前选中设备)
+ private Action closeMenuPageCallback;
///
/// 初始化页面
///
@@ -33,6 +34,7 @@ namespace Kill.UI.Pages
/// 设备蓝牙MAC(重命名非选中设备时传入)
public void Init(string name, Action onConfirm, int type = 0, string deviceMac = null)
{
+ closeMenuPageCallback=UIManager.Instance.GetBackAction();
currentName = name;
onConfirmCallback = onConfirm;
this.type = type;
@@ -172,7 +174,12 @@ namespace Kill.UI.Pages
///
private void Back()
{
- UIManager.Instance.ClearBackAction();
+ if (closeMenuPageCallback != null)
+ {
+ UIManager.Instance.RegisterBackAction(closeMenuPageCallback);
+ }
+ else
+ UIManager.Instance.ClearBackAction();
Destroy(gameObject);
}
diff --git a/Assets/Scripts/UI/Pages/DeviceInfoPage/ShareDevicePage.cs b/Assets/Scripts/UI/Pages/DeviceInfoPage/ShareDevicePage.cs
index a07c003..da2d338 100644
--- a/Assets/Scripts/UI/Pages/DeviceInfoPage/ShareDevicePage.cs
+++ b/Assets/Scripts/UI/Pages/DeviceInfoPage/ShareDevicePage.cs
@@ -24,13 +24,14 @@ namespace Kill.UI.Pages
///
/// 设备蓝牙MAC(分享非选中设备时传入)
/// 关闭页面回调
- public void Init(string deviceMac = null, Action onClose = null)
+ public void Init(string deviceMac = null)
{
this.deviceMac = deviceMac;
- this.onCloseAction = onClose;
}
+ private Action closeCallback;
void Start()
{
+ closeCallback=UIManager.Instance.GetBackAction();
UIManager.Instance.RegisterBackAction(Back);
backButton.onClick.RemoveAllListeners();
backButton.onClick.AddListener(Back);
@@ -53,23 +54,12 @@ namespace Kill.UI.Pages
}
public void Back()
{
- if (onCloseAction != null)
+ if (closeCallback != null)
{
- // 恢复上层页面的返回事件
- UIManager.Instance.RegisterBackAction(onCloseAction);
+ UIManager.Instance.RegisterBackAction(closeCallback);
}
else
- {
- var infoPage = GetComponentInParent();
- if (infoPage != null)
- {
- UIManager.Instance.RegisterBackAction(infoPage.Back);
- }
- else
- {
- UIManager.Instance.ClearBackAction();
- }
- }
+ UIManager.Instance.ClearBackAction();
Destroy(gameObject);
}
public async Task Share()
diff --git a/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs b/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs
index 831efcb..c7e89e6 100644
--- a/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs
+++ b/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs
@@ -75,12 +75,14 @@ namespace Kill.UI.Pages
async Task Start()
{
+ LoadingUI.Show();
if(string.IsNullOrEmpty(DataManager.Instance.userInfo.country_code))
{
var page= Instantiate(selectCountryPage,transform);
page.gameObject.SetActive(true);
page.Init(null,false);
page.onConfirm=(code)=>{UIManager.Instance.OpenMainPage(UIManager.PageName.homePage);};
+ LoadingUI.Hide();
return;
}
// 订阅蓝牙事件
@@ -870,7 +872,7 @@ namespace Kill.UI.Pages
public async Task Init()
{
- LoadingUI.Show();
+
if (DataManager.Instance.userInfo.device_count == 0 || (OwnedDevices.Count == 0 && SharedDevices.Count == 0))
{
noDevicePage.SetActive(true);
diff --git a/Assets/Scripts/UI/Pages/HomePage/HomePageDevicePage.cs b/Assets/Scripts/UI/Pages/HomePage/HomePageDevicePage.cs
index 998d048..d966e57 100644
--- a/Assets/Scripts/UI/Pages/HomePage/HomePageDevicePage.cs
+++ b/Assets/Scripts/UI/Pages/HomePage/HomePageDevicePage.cs
@@ -196,7 +196,6 @@ namespace Kill.UI.Pages
item.menuButtons[3].onClick.AddListener(() => ToggleTop(item, true));
item.menuButtons[4].onClick.AddListener(() => ToggleTop(item, false));
}
-
///
/// 打开设备分享页面
///
@@ -208,7 +207,7 @@ namespace Kill.UI.Pages
currentShareDevicePage = Instantiate(shareDevicePagePrefab, transform).GetComponent();
currentShareDevicePage.gameObject.SetActive(true);
// 关闭分享页时恢复设备列表页的返回事件
- currentShareDevicePage.Init(item.deviceInfo.ble_mac, () => UIManager.Instance.RegisterBackAction(ClosePage));
+ currentShareDevicePage.Init(item.deviceInfo.ble_mac);
}
///
@@ -305,6 +304,7 @@ namespace Kill.UI.Pages
if(lastSelectedDevice==selectedDevice)
{
gameObject.SetActive(false);
+ UIManager.Instance.ClearBackAction();
}
else
{
diff --git a/Assets/Scripts/UI/UIManager.cs b/Assets/Scripts/UI/UIManager.cs
index 6a9b7ca..c035121 100644
--- a/Assets/Scripts/UI/UIManager.cs
+++ b/Assets/Scripts/UI/UIManager.cs
@@ -115,6 +115,7 @@ namespace Kill.UI
if (backAction != null)
{
Debug.Log("执行注册返回");
+ Debug.Log(backAction.Method.Name);
var action = backAction;
action.Invoke();
return;
diff --git a/Kill/Assets/Scripts/Base/AndroidStatusBarManager.cs b/Kill/Assets/Scripts/Base/AndroidStatusBarManager.cs
deleted file mode 100644
index e7f44be..0000000
--- a/Kill/Assets/Scripts/Base/AndroidStatusBarManager.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-public void ShowTransparentSystemUI()
-{
-#if UNITY_ANDROID && !UNITY_EDITOR
- try
- {
- Debug.Log("尝试显示透明Android系统UI");
-
- using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
- using (var activity = unityPlayer.GetStatic("currentActivity"))
- {
- // 检查 activity 是否为 null
- if (activity == null)
- {
- Debug.LogError("当前活动对象为空,无法显示导航栏");
- // 添加延迟重试机制
- Invoke("ShowTransparentSystemUI", 0.5f);
- return;
- }
-
- // 使用 runOnUiThread 确保在主线程执行
- activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
- {
- using (var window = activity.Call("getWindow"))
- {
- // 清除全屏标志
- window.Call("clearFlags", 0x00000400); // WindowManager.LayoutParams.FLAG_FULLSCREEN
-
- // 添加半透明状态栏和导航栏标志 (适用于API 19及以上版本)
- if (IsAndroidApiLevelAtLeast(19))
- {
- window.Call("addFlags", 0x04000000); // WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
- window.Call("addFlags", 0x08000000); // WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
- }
-
- // 对于API 21及以上版本,设置状态栏和导航栏颜色为透明
- if (IsAndroidApiLevelAtLeast(21))
- {
- window.Call("setStatusBarColor", 0x00000000); // 透明
- window.Call("setNavigationBarColor", 0x00000000); // 透明
- }
-
- // 设置系统UI可见性
- using (var decorView = window.Call("getDecorView"))
- {
- var systemUiVisibility = decorView.Call("getSystemUiVisibility");
- systemUiVisibility &= ~(0x00000004 | 0x00000002); // 清除SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN
-
- // 对于API 23及以上版本,设置浅色状态栏图标(深色背景下可见)
- if (IsAndroidApiLevelAtLeast(23))
- {
- systemUiVisibility |= 0x00002000; // SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- }
-
- decorView.Call("setSystemUiVisibility", systemUiVisibility);
- }
- }
- }));
- }
-
- Screen.fullScreen = false;
- Debug.Log("已完成显示透明Android系统UI的尝试");
- }
- catch (System.Exception e)
- {
- Debug.LogError("显示透明系统UI时发生错误: " + e.Message);
- }
-#endif
-}
\ No newline at end of file