diff --git a/Assets/Scripts/Bluetooth/BLECommunicationManager.cs b/Assets/Scripts/Bluetooth/BLECommunicationManager.cs index d87ae9a..2d9d4a7 100644 --- a/Assets/Scripts/Bluetooth/BLECommunicationManager.cs +++ b/Assets/Scripts/Bluetooth/BLECommunicationManager.cs @@ -50,7 +50,14 @@ namespace Kill.Bluetooth public event Action OnStatisticsDataReceived; // 收到统计数据 public event Action OnSensorDataReceived; // 收到传感器数据 public event Action OnMosquitoDataReceived; // 收到蚊虫数据通知 + public event Action OnDeviceStateChangeNotification; // 设备状态变化通知(0x01) + public event Action OnErrorStatusNotification; // 错误状态上报通知(0x02) + public event Action OnParameterChangeNotification; // 参数变化通知(0x06) + public event Action OnCapacitorStateNotification; // 蓄能状态通知(0x08) + public event Action OnFillLightConnectionNotification; // 补光灯连接状态通知(0x09) + public event Action OnDeviceNotificationReceived; // 通用通知(命令码 + 设备MAC)用于按 MAC 隔离广播 public event Action OnRawDataReceived; // 收到原始数据(十六进制字符串) + public event Action OnDeviceStateChanged; // 本地设备状态变化(设备状态、字段) public event Action OnRawDataSent; // 发送原始数据(十六进制字符串) public event Action OnCommunicationError; // 通信错误 @@ -89,6 +96,83 @@ namespace Kill.Bluetooth } } + private readonly Dictionary _deviceStates = + new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly object _deviceStateLock = new object(); + private string _lastStateMac; + + /// + /// 获取当前连接设备对应的本地状态对象。 + /// 状态仓库按设备 MAC 隔离,旧设备通知不会覆盖当前设备。 + /// + public BLEDeviceState GetDeviceState(string deviceMac) + { + string normalizedMac = NormalizeDeviceMac(deviceMac); + if (string.IsNullOrEmpty(normalizedMac)) + { + return null; + } + + lock (_deviceStateLock) + { + if (!_deviceStates.TryGetValue(normalizedMac, out BLEDeviceState state)) + { + state = new BLEDeviceState(normalizedMac); + _deviceStates[normalizedMac] = state; + } + + return state; + } + } + + /// + /// 更新当前设备状态,并仅在值发生变化时广播状态事件。 + /// + private bool UpdateDeviceState(string deviceMac, BLEDeviceStateField field, Action update) + { + if (update == null) + { + return false; + } + + BLEDeviceState state = GetDeviceState(deviceMac); + if (state == null) + { + return false; + } + + bool changed; + lock (_deviceStateLock) + { + changed = update(state); + } + + if (changed) + { + try + { + OnDeviceStateChanged?.Invoke(state, field); + } + catch (System.Exception ex) + { + LogError($"OnDeviceStateChanged 处理异常: {ex.Message}"); + } + } + + return changed; + } + + private bool UpdateStateFromResponse(BLEResponse response, BLEDeviceStateField field, Action update) + { + return response != null && response.IsSuccess && + UpdateDeviceState(GetCurrentDeviceMac(), field, update); + } + + private static string NormalizeDeviceMac(string deviceMac) + { + return string.IsNullOrWhiteSpace(deviceMac) ? string.Empty : deviceMac.Trim().ToUpperInvariant(); + } + // 服务和特征UUID (根据实际设备配置) public string ServiceUUID = "0000ffe0-0000-1000-8000-00805f9b34fb"; public string WriteCharacteristicUUID = "0000ffe1-0000-1000-8000-00805f9b34fb"; @@ -190,6 +274,16 @@ namespace Kill.Bluetooth /// private void OnBluetoothConnected() { + string stateMac = GetCurrentDeviceMac(); + if (!string.IsNullOrEmpty(stateMac)) + { + _lastStateMac = stateMac; + UpdateDeviceState( + stateMac, + BLEDeviceStateField.Connection, + state => state.SetConnection(true)); + } + if (AutoInitialize) InitializeCommunication(); } @@ -200,6 +294,19 @@ namespace Kill.Bluetooth private void OnBluetoothDisconnected(string address) { Log($"蓝牙已断开: {address}"); + + // 使用断开参数作为设备状态键;部分平台会在清空地址前触发事件。 + string stateMac = NormalizeDeviceMac(address); + if (string.IsNullOrEmpty(stateMac)) + { + stateMac = string.IsNullOrEmpty(_lastStateMac) ? GetCurrentDeviceMac() : _lastStateMac; + } + _lastStateMac = string.IsNullOrEmpty(stateMac) ? _lastStateMac : stateMac; + UpdateDeviceState( + stateMac, + BLEDeviceStateField.Connection, + state => state.SetConnection(false)); + // 清理状态 IsWaitingResponse = false; _responseTimer = 0; @@ -562,6 +669,15 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var setting = LanguageSetting.FromBytes(response.Data); + if (!response.IsSuccess) + { + LogError($"读取语言设置失败: 状态码={response.Status:X2}"); + } + else + { + UpdateStateFromResponse(response, BLEDeviceStateField.Language, + state => state.SetLanguage(setting)); + } callback?.Invoke(setting); OnLanguageSettingReceived?.Invoke(setting); }); @@ -592,9 +708,15 @@ namespace Kill.Bluetooth { bool success = response.IsSuccess; if (success) + { Log($"语言设置成功: {(isChinese ? "中文" : "英文")}"); + UpdateStateFromResponse(response, BLEDeviceStateField.Language, + state => state.SetLanguage(setting)); + } else + { LogError($"语言设置失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -674,6 +796,15 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var taskList = ScheduleTaskListResponse.FromBytes(response.Status, response.Data); + if (!response.IsSuccess) + { + LogError($"读取定时任务失败: 状态码={response.Status:X2}"); + } + else + { + UpdateStateFromResponse(response, BLEDeviceStateField.ScheduleTasks, + state => state.SetScheduleTasks(taskList.Tasks)); + } callback?.Invoke(taskList); OnScheduleTaskListReceived?.Invoke(taskList); }); @@ -1116,6 +1247,8 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var control = FillLightControl.FromBytes(response.Data); + UpdateStateFromResponse(response, BLEDeviceStateField.FillLight, + state => state.SetFillLight(control)); callback?.Invoke(control); OnFillLightControlReceived?.Invoke(control); }); @@ -1145,9 +1278,15 @@ namespace Kill.Bluetooth { bool success = response.IsSuccess; if (success) + { Log($"补光灯控制设置成功: 开关={(control.Enable ? "开启" : "关闭")}, 类型={control.GetLightTypeString()}, 强度={control.GetIntensityString()}"); + UpdateStateFromResponse(response, BLEDeviceStateField.FillLight, + state => state.SetFillLight(control)); + } else + { LogError($"补光灯控制设置失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -1172,6 +1311,8 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var status = FillLightConnectionStatus.FromBytes(response.Data); + UpdateStateFromResponse(response, BLEDeviceStateField.FillLightConnection, + state => state.SetFillLightConnection(status)); callback?.Invoke(status); OnFillLightConnectionStatusReceived?.Invoke(status); }); @@ -1254,6 +1395,8 @@ namespace Kill.Bluetooth Debug.Log($"[ReadDeviceLockControl] 收到响应: Status={response.Status:X2}, Data={BitConverter.ToString(response.Data)}"); var control = DeviceLockControl.FromBytes(response.Data); Debug.Log($"[ReadDeviceLockControl] 解析结果: IsLocked={control.IsLocked}"); + UpdateStateFromResponse(response, BLEDeviceStateField.LockState, + state => state.SetLockState(control)); callback?.Invoke(control); OnDeviceLockControlReceived?.Invoke(control); }); @@ -1287,9 +1430,15 @@ namespace Kill.Bluetooth Debug.Log($"[WriteDeviceLockControl] 收到响应: Status={response.Status:X2}"); bool success = response.IsSuccess; if (success) + { Log($"设备{(isLocked ? "锁定" : "解锁")}成功"); + UpdateStateFromResponse(response, BLEDeviceStateField.LockState, + state => state.SetLockState(control)); + } else + { LogError($"设备{(isLocked ? "锁定" : "解锁")}失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -1318,6 +1467,8 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var control = AngleControl.FromBytes(response.Data); + UpdateStateFromResponse(response, BLEDeviceStateField.Angle, + state => state.SetAngle(control)); callback?.Invoke(control); OnAngleControlReceived?.Invoke(control); Log($"读取角度控制: {control}"); @@ -1348,9 +1499,15 @@ namespace Kill.Bluetooth { bool success = response.IsSuccess; if (success) + { Log($"角度控制设置成功: {control}"); + UpdateStateFromResponse(response, BLEDeviceStateField.Angle, + state => state.SetAngle(control)); + } else + { LogError($"角度控制设置失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -1375,6 +1532,8 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var control = DistanceControl.FromBytes(response.Data); + UpdateStateFromResponse(response, BLEDeviceStateField.Distance, + state => state.SetDistance(control)); callback?.Invoke(control); OnDistanceControlReceived?.Invoke(control); Log($"读取距离控制: {control}"); @@ -1405,9 +1564,15 @@ namespace Kill.Bluetooth { bool success = response.IsSuccess; if (success) + { Log($"距离控制设置成功: {control}"); + UpdateStateFromResponse(response, BLEDeviceStateField.Distance, + state => state.SetDistance(control)); + } else + { LogError($"距离控制设置失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -1492,6 +1657,8 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var setting = VisualDetectionSetting.FromBytes(response.Data); + UpdateStateFromResponse(response, BLEDeviceStateField.VisualDetection, + state => state.SetVisualDetection(setting)); callback?.Invoke(setting); OnVisualDetectionSettingReceived?.Invoke(setting); Log($"读取视觉检测设置: {setting}"); @@ -1521,9 +1688,15 @@ namespace Kill.Bluetooth { bool success = response.IsSuccess; if (success) + { Log($"视觉检测设置成功: {setting}"); + UpdateStateFromResponse(response, BLEDeviceStateField.VisualDetection, + state => state.SetVisualDetection(setting)); + } else + { LogError($"视觉检测设置失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -1690,6 +1863,15 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var setting = WorkModeSetting.FromBytes(response.Data); + if (!response.IsSuccess) + { + LogError($"读取工作模式失败: 状态码={response.Status:X2}"); + } + else + { + UpdateStateFromResponse(response, BLEDeviceStateField.WorkMode, + state => state.SetWorkMode(setting.Mode)); + } callback?.Invoke(setting); OnWorkModeSettingReceived?.Invoke(setting); Log($"读取工作模式: {setting}"); @@ -1719,9 +1901,15 @@ namespace Kill.Bluetooth { bool success = response.IsSuccess; if (success) + { Log($"工作模式设置成功: {setting}"); + UpdateStateFromResponse(response, BLEDeviceStateField.WorkMode, + state => state.SetWorkMode(setting.Mode)); + } else + { LogError($"工作模式设置失败, 状态码={response.Status:X2}"); + } callback?.Invoke(success); }); } @@ -1746,6 +1934,8 @@ namespace Kill.Bluetooth SendFrame(frame, (response) => { var status = HardwareStatus.FromBytes(response.Data); + UpdateStateFromResponse(response, BLEDeviceStateField.HardwareStatus, + state => state.SetHardwareStatus(status)); callback?.Invoke(status); OnHardwareStatusReceived?.Invoke(status); Log($"查询硬件状态: {status}"); @@ -2420,14 +2610,17 @@ namespace Kill.Bluetooth { bool wasWaitingResponse = IsWaitingResponse; - // 优先处理设备主动通知,避免被等待响应的流程拦截 - if (frame.ReadWrite == BLEConstants.RW_NOTIFY) + // 通知帧优先处理,并且绝对不影响命令队列(IsWaitingResponse / _pendingCallback / _responseTimer) + // 设备主动通知是异步旁路通道,与当前等待响应的命令是两条独立链路 + if (frame.ReadWrite == BLEConstants.RW_NOTIFY || frame.Command == BLEConstants.NOTIFY_COMMAND_CODE) { HandleNotification(frame.Data); + return; } - else if (IsWaitingResponse && frame.Command == LastCommand) + + // 响应帧:优先匹配当前等待的命令。 + if (IsWaitingResponse && frame.Command == LastCommand) { - // 匹配到等待的响应 IsWaitingResponse = false; _responseTimer = 0; @@ -2437,7 +2630,7 @@ namespace Kill.Bluetooth } else if (IsWaitingResponse) { - // 尝试处理非匹配的响应,可能是硬件响应延迟 + // 保留原兼容行为:设备响应延迟时仍允许结束当前等待任务。 IsWaitingResponse = false; _responseTimer = 0; @@ -2445,8 +2638,7 @@ namespace Kill.Bluetooth _pendingCallback?.Invoke(response); _pendingCallback = null; } - - // 如果之前有等待响应,现在响应已处理,继续处理队列 + if (wasWaitingResponse) { ProcessCommandQueue(); @@ -2454,19 +2646,243 @@ namespace Kill.Bluetooth } /// - /// 通用的设备通知分发器,按命令类型路由到对应处理逻辑 + /// 通用的设备通知分发器,按通知类型路由到对应处理逻辑 + /// 通知帧约定:data[0]=通知类型,data[1..]=通知数据 /// private void HandleNotification(byte[] data) { - byte command=data[0]; - switch (command) + if (data == null || data.Length == 0) + { + LogWarning("收到空通知数据"); + return; + } + + byte notifyType = data[0]; + + // 按 MAC 隔离:仅当通知来自当前已连接设备时才向下分发 + string currentMac = GetCurrentDeviceMac(); + if (!string.IsNullOrEmpty(currentMac)) + { + // 触发通用通知事件(携带通知类型 + 当前设备 MAC),便于业务侧按 MAC 二次过滤 + try + { + OnDeviceNotificationReceived?.Invoke(notifyType, currentMac); + } + catch (System.Exception ex) + { + LogError($"OnDeviceNotificationReceived 处理异常: {ex.Message}"); + } + } + + switch (notifyType) { case BLEConstants.NOTIFY_MOSQUITO_DATA: HandleMosquitoDataNotification(data); break; - default: - Log($"未处理的设备通知 0x{command:X2}"); + case BLEConstants.NOTIFY_DEVICE_STATE_CHANGE: + HandleDeviceStateChangeNotification(data); break; + case BLEConstants.NOTIFY_ERROR_STATUS: + HandleErrorStatusNotification(data); + break; + case BLEConstants.NOTIFY_PARAMETER_CHANGE: + HandleParameterChangeNotification(data); + break; + case BLEConstants.NOTIFY_CAPACITOR_STATE: + HandleCapacitorStateNotification(data); + break; + case BLEConstants.NOTIFY_FILL_LIGHT_CONNECTION: + HandleFillLightConnectionNotification(data); + break; + case BLEConstants.NOTIFY_STATISTICS_UPDATE: + case BLEConstants.NOTIFY_SECURITY_ALERT: + Log($"收到通知类型 0x{notifyType:X2}(暂未实现具体业务解析)"); + break; + default: + Log($"未处理的设备通知类型 0x{notifyType:X2}"); + break; + } + } + + /// + /// 获取当前已连接设备的 MAC(用于按 MAC 隔离通知事件) + /// 优先使用 iOS 端从广播中解析出的真实 MAC,否则回退到 BLE 地址 + /// + public string GetCurrentDeviceMac() + { + if (BluetoothManager.Instance == null || !BluetoothManager.Instance.IsConnected) + return null; + string mac = BluetoothManager.Instance.ConnectedDeviceMacAddress; + if (string.IsNullOrEmpty(mac)) + mac = BluetoothManager.Instance.ConnectedDeviceAddress; + return string.IsNullOrEmpty(mac) ? null : mac.ToUpperInvariant(); + } + + private string GetStateMac() + { + string currentMac = GetCurrentDeviceMac(); + return string.IsNullOrEmpty(currentMac) && !string.IsNullOrEmpty(_lastStateMac) + ? _lastStateMac + : currentMac; + } + + private void HandleDeviceStateChangeNotification(byte[] data) + { + var notify = DeviceStateChangeNotification.FromBytes(data); + if (notify.WorkMode > (byte)WorkMode.Eliminate) + { + LogWarning($"设备状态变化通知包含无效工作模式: 0x{notify.WorkMode:X2}"); + return; + } + + Log($"设备状态变化通知: 工作模式=0x{notify.WorkMode:X2}"); + UpdateDeviceState( + GetCurrentDeviceMac(), + BLEDeviceStateField.WorkMode, + state => state.SetWorkMode((WorkMode)notify.WorkMode)); + try + { + OnDeviceStateChangeNotification?.Invoke(notify); + } + catch (System.Exception ex) + { + LogError($"OnDeviceStateChangeNotification 处理异常: {ex.Message}"); + } + } + + private void HandleErrorStatusNotification(byte[] data) + { + var notify = ErrorStatusNotification.FromBytes(data); + if (data != null && data.Length >= 8) + { + UpdateDeviceState( + GetCurrentDeviceMac(), + BLEDeviceStateField.HardwareStatus, + state => state.SetHardwareStatus(notify.Status)); + } + Log($"错误状态上报通知: {notify.Status}"); + try + { + OnErrorStatusNotification?.Invoke(notify); + } + catch (System.Exception ex) + { + LogError($"OnErrorStatusNotification 处理异常: {ex.Message}"); + } + } + + private void HandleParameterChangeNotification(byte[] data) + { + var notify = ParameterChangeNotification.FromBytes(data); + Log($"参数变化通知: 命令=0x{notify.CommandCode:X2}, 数据长度={(notify.Payload?.Length ?? 0)}"); + + // 优先回写本地状态仓库并按字段广播 OnDeviceStateChanged,便于业务侧按字段做局部刷新 + ApplyParameterChangeToDeviceState(notify); + + try + { + OnParameterChangeNotification?.Invoke(notify); + } + catch (System.Exception ex) + { + LogError($"OnParameterChangeNotification 处理异常: {ex.Message}"); + } + } + + /// + /// 把 0x06 参数变化通知解析后回写到 BLEDeviceState 仓库,仅在值变化时通过 OnDeviceStateChanged 按字段广播 + /// + private void ApplyParameterChangeToDeviceState(ParameterChangeNotification notify) + { + string deviceMac = GetCurrentDeviceMac(); + if (string.IsNullOrEmpty(deviceMac) || notify.Payload == null) + { + return; + } + + try + { + switch (notify.CommandCode) + { + case BLEConstants.CMD_LANGUAGE_SETTING: + UpdateDeviceState(deviceMac, BLEDeviceStateField.Language, + state => state.SetLanguage(LanguageSetting.FromBytes(notify.Payload))); + break; + case BLEConstants.CMD_SCHEDULE_TASK: + UpdateDeviceState(deviceMac, BLEDeviceStateField.ScheduleTasks, + state => state.SetScheduleTasks(ScheduleTaskListResponse.FromBytes(0x00, notify.Payload).Tasks)); + break; + case BLEConstants.CMD_FILL_LIGHT_CONTROL: + UpdateDeviceState(deviceMac, BLEDeviceStateField.FillLight, + state => state.SetFillLight(FillLightControl.FromBytes(notify.Payload))); + break; + case BLEConstants.CMD_ANGLE_CONTROL: + UpdateDeviceState(deviceMac, BLEDeviceStateField.Angle, + state => state.SetAngle(AngleControl.FromBytes(notify.Payload))); + break; + case BLEConstants.CMD_DISTANCE_CONTROL: + UpdateDeviceState(deviceMac, BLEDeviceStateField.Distance, + state => state.SetDistance(DistanceControl.FromBytes(notify.Payload))); + break; + case BLEConstants.CMD_VISUAL_DETECTION_SETTING: + UpdateDeviceState(deviceMac, BLEDeviceStateField.VisualDetection, + state => state.SetVisualDetection(VisualDetectionSetting.FromBytes(notify.Payload))); + break; + case BLEConstants.CMD_FILL_LIGHT_CONNECTION_STATUS: + UpdateDeviceState(deviceMac, BLEDeviceStateField.FillLightConnection, + state => state.SetFillLightConnection(FillLightConnectionStatus.FromBytes(notify.Payload))); + break; + default: + // 未映射的字段:不写仓库,避免误覆盖 + break; + } + } + catch (System.Exception ex) + { + LogError($"参数变化通知写仓库异常(命令=0x{notify.CommandCode:X2}): {ex.Message}"); + } + } + + private void HandleCapacitorStateNotification(byte[] data) + { + var notify = CapacitorStateNotification.FromBytes(data); + if (data != null && data.Length >= 2) + { + UpdateDeviceState( + GetCurrentDeviceMac(), + BLEDeviceStateField.CapacitorState, + state => state.SetCapacitorState(notify)); + } + Log($"蓄能状态通知: state=0x{notify.State:X2}"); + try + { + OnCapacitorStateNotification?.Invoke(notify); + } + catch (System.Exception ex) + { + LogError($"OnCapacitorStateNotification 处理异常: {ex.Message}"); + } + } + + private void HandleFillLightConnectionNotification(byte[] data) + { + var notify = FillLightConnectionNotification.FromBytes(data); + if (data != null && data.Length >= 2) + { + var status = new FillLightConnectionStatus { IsConnected = notify.IsConnected }; + UpdateDeviceState( + GetCurrentDeviceMac(), + BLEDeviceStateField.FillLightConnection, + state => state.SetFillLightConnection(status)); + } + Log($"补光灯连接状态通知: IsConnected={notify.IsConnected}"); + try + { + OnFillLightConnectionNotification?.Invoke(notify); + } + catch (System.Exception ex) + { + LogError($"OnFillLightConnectionNotification 处理异常: {ex.Message}"); } } diff --git a/Assets/Scripts/Bluetooth/BLEDeviceState.cs b/Assets/Scripts/Bluetooth/BLEDeviceState.cs new file mode 100644 index 0000000..8a0212e --- /dev/null +++ b/Assets/Scripts/Bluetooth/BLEDeviceState.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using Kill.Bluetooth.Protocol; + +namespace Kill.Bluetooth +{ + /// + /// 设备配置变化字段。 + /// + public enum BLEDeviceStateField : byte + { + None = 0, + Connection, + WorkMode, + LockState, + ScheduleTasks, + Angle, + Distance, + FillLight, + FillLightConnection, + VisualDetection, + MillimeterWave, + Language, + HardwareStatus, + CapacitorState + } + + /// + /// 蓝牙设备运行状态。 + /// 以设备 MAC 为边界保存状态,避免切换设备时旧设备数据覆盖当前设备。 + /// + [Serializable] + public sealed class BLEDeviceState + { + public string DeviceMac { get; private set; } + + public bool IsConnected { get; private set; } + + public WorkMode? WorkMode { get; private set; } + + public DeviceLockControl? LockState { get; private set; } + + public ScheduleTask[] ScheduleTasks { get; private set; } = new ScheduleTask[0]; + + public AngleControl? Angle { get; private set; } + + public DistanceControl? Distance { get; private set; } + + public FillLightControl? FillLight { get; private set; } + + public FillLightConnectionStatus? FillLightConnection { get; private set; } + + public VisualDetectionSetting? VisualDetection { get; private set; } + + public LanguageSetting? Language { get; private set; } + + public HardwareStatus? HardwareStatus { get; private set; } + + public CapacitorStateNotification? CapacitorState { get; private set; } + + public DateTime LastUpdatedAt { get; private set; } + + public BLEDeviceState(string deviceMac) + { + DeviceMac = deviceMac ?? string.Empty; + LastUpdatedAt = DateTime.Now; + } + + internal bool SetConnection(bool connected) + { + if (IsConnected == connected) + { + return false; + } + + IsConnected = connected; + MarkUpdated(); + return true; + } + + internal bool SetWorkMode(WorkMode mode) + { + if (WorkMode.HasValue && WorkMode.Value == mode) + { + return false; + } + + WorkMode = mode; + MarkUpdated(); + return true; + } + + internal bool SetLockState(DeviceLockControl lockState) + { + if (LockState.HasValue && LockState.Value.IsLocked == lockState.IsLocked) + { + return false; + } + + LockState = lockState; + MarkUpdated(); + return true; + } + + internal bool SetScheduleTasks(ScheduleTask[] tasks) + { + ScheduleTask[] newTasks = tasks == null ? new ScheduleTask[0] : (ScheduleTask[])tasks.Clone(); + if (ScheduleTasksEqual(ScheduleTasks, newTasks)) + { + return false; + } + + ScheduleTasks = newTasks; + MarkUpdated(); + return true; + } + + internal bool SetAngle(AngleControl angle) + { + if (Angle.HasValue && Angle.Value.AngleRange == angle.AngleRange) + { + return false; + } + + Angle = angle; + MarkUpdated(); + return true; + } + + internal bool SetDistance(DistanceControl distance) + { + if (Distance.HasValue && + Distance.Value.DetectionDistance == distance.DetectionDistance && + Distance.Value.AimDistance == distance.AimDistance) + { + return false; + } + + Distance = distance; + MarkUpdated(); + return true; + } + + internal bool SetFillLight(FillLightControl fillLight) + { + if (FillLight.HasValue && + FillLight.Value.Enable == fillLight.Enable && + FillLight.Value.LightType == fillLight.LightType && + FillLight.Value.Intensity == fillLight.Intensity) + { + return false; + } + + FillLight = fillLight; + MarkUpdated(); + return true; + } + + internal bool SetFillLightConnection(FillLightConnectionStatus status) + { + if (FillLightConnection.HasValue && + FillLightConnection.Value.IsConnected == status.IsConnected) + { + return false; + } + + FillLightConnection = status; + MarkUpdated(); + return true; + } + + internal bool SetVisualDetection(VisualDetectionSetting setting) + { + if (VisualDetection.HasValue && + VisualDetection.Value.Enable == setting.Enable && + VisualDetection.Value.Sensitivity == setting.Sensitivity) + { + return false; + } + + VisualDetection = setting; + MarkUpdated(); + return true; + } + + internal bool SetLanguage(LanguageSetting language) + { + if (Language.HasValue && Language.Value.Language == language.Language) + { + return false; + } + + Language = language; + MarkUpdated(); + return true; + } + + internal bool SetHardwareStatus(HardwareStatus hardwareStatus) + { + if (HardwareStatus.HasValue && HardwareStatus.Value.Equals(hardwareStatus)) + { + return false; + } + + HardwareStatus = hardwareStatus; + MarkUpdated(); + return true; + } + + internal bool SetCapacitorState(CapacitorStateNotification capacitorState) + { + if (CapacitorState.HasValue && CapacitorState.Value.State == capacitorState.State) + { + return false; + } + + CapacitorState = capacitorState; + MarkUpdated(); + return true; + } + + private void MarkUpdated() + { + LastUpdatedAt = DateTime.Now; + } + + private static bool ScheduleTasksEqual(ScheduleTask[] left, ScheduleTask[] right) + { + if (left == null || right == null || left.Length != right.Length) + { + return false; + } + + for (int i = 0; i < left.Length; i++) + { + if (!left[i].Equals(right[i])) + { + return false; + } + } + + return true; + } + } +} diff --git a/Assets/Scripts/Bluetooth/BLEDeviceState.cs.meta b/Assets/Scripts/Bluetooth/BLEDeviceState.cs.meta new file mode 100644 index 0000000..3d5867c --- /dev/null +++ b/Assets/Scripts/Bluetooth/BLEDeviceState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e69a1efed6be8643ab6d0cf88657497 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Bluetooth/BLE通信方案V2.2.md b/Assets/Scripts/Bluetooth/BLE通信方案V2.2.md new file mode 100644 index 0000000..8a947a0 --- /dev/null +++ b/Assets/Scripts/Bluetooth/BLE通信方案V2.2.md @@ -0,0 +1,2105 @@ +激光灭害虫设备 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/Scripts/Bluetooth/BLE通信方案V2.2.md.meta b/Assets/Scripts/Bluetooth/BLE通信方案V2.2.md.meta new file mode 100644 index 0000000..2ef6096 --- /dev/null +++ b/Assets/Scripts/Bluetooth/BLE通信方案V2.2.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8db7a756f6f76fb4ebb36b63755dd084 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Bluetooth/Protocol/BLEProtocolStructs.cs b/Assets/Scripts/Bluetooth/Protocol/BLEProtocolStructs.cs index 2b5b0d0..622e137 100644 --- a/Assets/Scripts/Bluetooth/Protocol/BLEProtocolStructs.cs +++ b/Assets/Scripts/Bluetooth/Protocol/BLEProtocolStructs.cs @@ -27,7 +27,17 @@ namespace Kill.Bluetooth.Protocol public const byte CMD_UNREGISTER_USER = 0x08; // 注销用户 // 通知类型 - public const byte NOTIFY_MOSQUITO_DATA = 0x0B; // 蚊虫数据通知 + public const byte NOTIFY_DEVICE_STATE_CHANGE = 0x01; // 设备状态变化(工作模式变化) + public const byte NOTIFY_ERROR_STATUS = 0x02; // 错误状态上报(7字节硬件错误状态) + public const byte NOTIFY_STATISTICS_UPDATE = 0x03; // 统计数据更新 + public const byte NOTIFY_SECURITY_ALERT = 0x05; // 安全告警(人体检测等) + public const byte NOTIFY_PARAMETER_CHANGE = 0x06; // 参数变化通知 + public const byte NOTIFY_CAPACITOR_STATE = 0x08; // 蓄能状态通知 + public const byte NOTIFY_FILL_LIGHT_CONNECTION = 0x09; // 补光灯连接状态通知 + public const byte NOTIFY_MOSQUITO_DATA = 0x0B; // 蚊虫数据通知 + + // 通知帧的"命令码"字段(与读写命令区分,统一使用 0xF0) + public const byte NOTIFY_COMMAND_CODE = 0xF0; // 命令码 - 设备设置类 (0x10-0x1F) public const byte CMD_LANGUAGE_SETTING = 0x10; // 语言设置 @@ -2259,5 +2269,118 @@ namespace Kill.Bluetooth.Protocol } #endregion + + #region 通知数据 (0xF0 通知帧) + + /// + /// 设备状态变化通知 + /// 通知类型 0x01:data[0]=0x01,data[1]=工作模式状态码(0=待机 1=扫描 2=消杀) + /// + [Serializable] + public struct DeviceStateChangeNotification + { + public byte WorkMode; // 0x00=待机, 0x01=扫描, 0x02=消杀 + + public static DeviceStateChangeNotification FromBytes(byte[] data) + { + var notify = new DeviceStateChangeNotification(); + if (data != null && data.Length >= 2) + { + notify.WorkMode = data[1]; + } + return notify; + } + } + + /// + /// 错误状态上报通知 + /// 通知类型 0x02:data[0]=0x02,data[1..7]=7字节硬件错误状态(与 0xA1 读响应一致) + /// + [Serializable] + public struct ErrorStatusNotification + { + public HardwareStatus Status; + + public static ErrorStatusNotification FromBytes(byte[] data) + { + var notify = new ErrorStatusNotification(); + if (data != null && data.Length >= 8) + { + byte[] statusBytes = new byte[7]; + Buffer.BlockCopy(data, 1, statusBytes, 0, 7); + notify.Status = HardwareStatus.FromBytes(statusBytes); + } + return notify; + } + } + + /// + /// 参数变化通知 + /// 通知类型 0x06:data[0]=0x06,data[1]=发生变化的命令码,data[2..]=参数数据(与对应读响应一致) + /// 用于通知 APP 设备端参数被修改,无需 APP 主动查询即可同步 + /// + [Serializable] + public struct ParameterChangeNotification + { + public byte CommandCode; // 发生变化的命令码(如 0x10=语言, 0x12=定时任务, 0x22=RGB, 0x26=补光灯 等) + public byte[] Payload; // 参数原始字节(不含通知类型与命令码字节) + + public static ParameterChangeNotification FromBytes(byte[] data) + { + var notify = new ParameterChangeNotification(); + if (data != null && data.Length >= 2) + { + notify.CommandCode = data[1]; + if (data.Length > 2) + { + notify.Payload = new byte[data.Length - 2]; + Buffer.BlockCopy(data, 2, notify.Payload, 0, notify.Payload.Length); + } + } + return notify; + } + } + + /// + /// 补光灯连接状态通知 + /// 通知类型 0x09:data[0]=0x09,data[1]=连接状态(0=断开 1=连接) + /// + [Serializable] + public struct FillLightConnectionNotification + { + public bool IsConnected; + + public static FillLightConnectionNotification FromBytes(byte[] data) + { + var notify = new FillLightConnectionNotification(); + if (data != null && data.Length >= 2) + { + notify.IsConnected = data[1] == 0x01; + } + return notify; + } + } + + /// + /// 蓄能状态通知 + /// 通知类型 0x08:data[0]=0x08,data[1]=状态(0=充电开始 1=充电完成) + /// + [Serializable] + public struct CapacitorStateNotification + { + public byte State; // 0=充电开始, 1=充电完成 + + public static CapacitorStateNotification FromBytes(byte[] data) + { + var notify = new CapacitorStateNotification(); + if (data != null && data.Length >= 2) + { + notify.State = data[1]; + } + return notify; + } + } + + #endregion } diff --git a/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs b/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs index c7e89e6..998b267 100644 --- a/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs +++ b/Assets/Scripts/UI/Pages/HomePage/HomePageCtrl.cs @@ -161,6 +161,9 @@ namespace Kill.UI.Pages BLECommunicationManager.Instance.OnVisualDetectionSettingReceived += OnVisualDetectionReceived; // 订阅蚊虫数据事件 BLECommunicationManager.Instance.OnMosquitoDataReceived += OnMosquitoDataReceived; + + // 订阅本地状态变化事件(按字段局部刷新,保留首次全量拉取) + BLECommunicationManager.Instance.OnDeviceStateChanged += OnDeviceStateChanged; } } @@ -195,6 +198,9 @@ namespace Kill.UI.Pages BLECommunicationManager.Instance.OnVisualDetectionSettingReceived -= OnVisualDetectionReceived; // 取消订阅蚊虫数据事件 BLECommunicationManager.Instance.OnMosquitoDataReceived -= OnMosquitoDataReceived; + + // 取消订阅本地状态变化事件 + BLECommunicationManager.Instance.OnDeviceStateChanged -= OnDeviceStateChanged; } } @@ -482,6 +488,106 @@ namespace Kill.UI.Pages #endregion + #region 本地设备状态变化 (按字段局部刷新) + + /// + /// 按 MAC 隔离:当前设备不匹配(已切换/未连接/选中设备不同)则丢弃通知 + /// + private bool IsNotificationForCurrentDevice() + { + string mac = BLECommunicationManager.Instance?.GetCurrentDeviceMac(); + string selectedMac = selectedDevice?.ble_mac; + if (string.IsNullOrEmpty(mac) || string.IsNullOrEmpty(selectedMac)) return false; + return string.Equals(mac, selectedMac, StringComparison.OrdinalIgnoreCase); + } + + /// + /// 本地设备状态变化回调(按字段局部刷新) + /// 0x01/0x06 通知已由 BLECommunicationManager 解析后回写仓库并按字段广播 + /// 首次全量拉取阶段保留原路径,让读响应触发 UI 更新,避免重复刷新 + /// + private void OnDeviceStateChanged(BLEDeviceState state, BLEDeviceStateField field) + { + if (state == null) return; + if (!IsNotificationForCurrentDevice()) return; + if (isDeviceStateInitializing) return; + + switch (field) + { + case BLEDeviceStateField.WorkMode: + if (state.WorkMode.HasValue) + { + int mode = (int)state.WorkMode.Value; + currentWorkMode = state.WorkMode.Value; + DataManager.Instance.deviceConfig.work_mode = DeviceConfig.ToServerString(state.WorkMode.Value); + pendingUIUpdates.Add(() => deviceCtrl?.InitWrokMode(mode)); + } + break; + case BLEDeviceStateField.Angle: + if (state.Angle.HasValue && !FovSettingPage.IsSettingPageActive()) + { + int fov = (int)state.Angle.Value.ActualAngle; + pendingUIUpdates.Add(() => deviceCtrl?.InitFovText(fov)); + } + break; + case BLEDeviceStateField.Distance: + if (state.Distance.HasValue && !LensSettingPage.IsSettingPageActive()) + { + float detectionDistance = state.Distance.Value.DetectionDistance / 10f; + pendingUIUpdates.Add(() => + deviceCtrl?.InitLensText(detectionDistance, DataManager.Instance.userInfo.unit_system)); + } + break; + case BLEDeviceStateField.FillLight: + if (state.FillLight.HasValue && !LightSettingPage.IsSettingPageActive()) + { + bool enable = state.FillLight.Value.Enable; + pendingUIUpdates.Add(() => deviceCtrl?.InitLightText(enable)); + } + break; + case BLEDeviceStateField.VisualDetection: + if (state.VisualDetection.HasValue && !SafetySettingPage.IsSettingPageActive()) + { + bool enable = state.VisualDetection.Value.Enable; + pendingUIUpdates.Add(() => deviceCtrl?.InitSafeText(enable)); + } + break; + case BLEDeviceStateField.LockState: + if (state.LockState.HasValue) + { + bool isLocked = state.LockState.Value.IsLocked; + pendingUIUpdates.Add(() => deviceCtrl?.InitDeviceControl(!isLocked)); + } + break; + case BLEDeviceStateField.ScheduleTasks: + if (!ScheduleSettingPage.IsSettingPageActive()) + { + var tasks = state.ScheduleTasks; + if (tasks != null && tasks.Length > 0) + { + pendingUIUpdates.Add(() => deviceSchedule?.Init(tasks)); + } + else + { + pendingUIUpdates.Add(() => deviceSchedule?.DisplayNoSchedule()); + } + } + break; + case BLEDeviceStateField.Language: + if (state.Language.HasValue) + { + DataManager.Instance.deviceConfig.language = state.Language.Value.IsChinese ? 1 : 0; + DataManager.Instance.SyncDeviceConfigToServer(); + } + break; + default: + // 其他字段(Connection / LockState / HardwareStatus / CapacitorState / FillLightConnection)暂不在主页直接刷新 + break; + } + } + + #endregion + #region 网络状态事件处理 /// diff --git a/SyntaxCheck/Program.cs b/SyntaxCheck/Program.cs new file mode 100644 index 0000000..3751555 --- /dev/null +++ b/SyntaxCheck/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.dgspec.json b/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.dgspec.json new file mode 100644 index 0000000..bca6b0b --- /dev/null +++ b/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.dgspec.json @@ -0,0 +1,61 @@ +{ + "format": 1, + "restore": { + "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj": {} + }, + "projects": { + "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj", + "projectName": "SyntaxCheck", + "projectPath": "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj", + "packagesPath": "C:\\Users\\YQC\\.nuget\\packages\\", + "outputPath": "D:\\unity_project\\Kill\\SyntaxCheck\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\YQC\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.113/PortableRuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.g.props b/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.g.props new file mode 100644 index 0000000..c744240 --- /dev/null +++ b/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + $(UserProfile)\.nuget\packages\ + C:\Users\YQC\.nuget\packages\ + PackageReference + 6.8.1 + + + + + \ No newline at end of file diff --git a/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.g.targets b/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/SyntaxCheck/obj/SyntaxCheck.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/SyntaxCheck/obj/project.assets.json b/SyntaxCheck/obj/project.assets.json new file mode 100644 index 0000000..bac34e2 --- /dev/null +++ b/SyntaxCheck/obj/project.assets.json @@ -0,0 +1,66 @@ +{ + "version": 3, + "targets": { + "net8.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net8.0": [] + }, + "packageFolders": { + "C:\\Users\\YQC\\.nuget\\packages\\": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj", + "projectName": "SyntaxCheck", + "projectPath": "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj", + "packagesPath": "C:\\Users\\YQC\\.nuget\\packages\\", + "outputPath": "D:\\unity_project\\Kill\\SyntaxCheck\\obj\\", + "projectStyle": "PackageReference", + "configFilePaths": [ + "C:\\Users\\YQC\\AppData\\Roaming\\NuGet\\NuGet.Config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + } + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.113/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/SyntaxCheck/obj/project.nuget.cache b/SyntaxCheck/obj/project.nuget.cache new file mode 100644 index 0000000..0aa469b --- /dev/null +++ b/SyntaxCheck/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "vIdeVNNgNyV9g05vzz3zmuhU/jhHrG+wZsLvVl3eSXOZZKrYzX6bKgJanV1KB2n7qJpxJWpw9zhuwF9gTFQftw==", + "success": true, + "projectFilePath": "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file