feat(bluetooth,ui): 添加毫米波雷达状态支持并优化设备状态处理

1. 新增MillimeterWaveSetting设备状态字段与更新方法
2. 修复HomePageCtrl中设备配置空引用风险
3. 重构BLE通讯状态更新逻辑,统一MAC地址获取
4. 补充各类BLE通知的数据长度校验
5. 新增设备锁状态参数变更通知处理
6. 优化日志描述与协程管理逻辑
This commit is contained in:
“虞渠成” 2026-09-04 10:59:43 +08:00
parent 8e879b009a
commit 320ece473f
3 changed files with 133 additions and 24 deletions

View File

@ -128,7 +128,7 @@ namespace Kill.Bluetooth
/// <summary>
/// 更新当前设备状态,并仅在值发生变化时广播状态事件。
/// </summary>
private bool UpdateDeviceState(string deviceMac, BLEDeviceStateField field, Action<BLEDeviceState> update)
private bool UpdateDeviceState(string deviceMac, BLEDeviceStateField field, Func<BLEDeviceState, bool> update)
{
if (update == null)
{
@ -162,10 +162,9 @@ namespace Kill.Bluetooth
return changed;
}
private bool UpdateStateFromResponse(BLEResponse response, BLEDeviceStateField field, Action<BLEDeviceState> update)
private bool UpdateStateFromResponse(BLEResponse response, BLEDeviceStateField field, Func<BLEDeviceState, bool> update)
{
return response != null && response.IsSuccess &&
UpdateDeviceState(GetCurrentDeviceMac(), field, update);
return response.IsSuccess && UpdateDeviceState(GetStateMac(), field, update);
}
private static string NormalizeDeviceMac(string deviceMac)
@ -295,11 +294,13 @@ namespace Kill.Bluetooth
{
Log($"蓝牙已断开: {address}");
// 使用断开参数作为设备状态键;部分平台会在清空地址前触发事件。
string stateMac = NormalizeDeviceMac(address);
// 断开参数通常是 BLE 地址,优先使用已记录的真实 MAC 作为状态键。
string stateMac = string.IsNullOrEmpty(_lastStateMac)
? NormalizeDeviceMac(address)
: _lastStateMac;
if (string.IsNullOrEmpty(stateMac))
{
stateMac = string.IsNullOrEmpty(_lastStateMac) ? GetCurrentDeviceMac() : _lastStateMac;
stateMac = GetCurrentDeviceMac();
}
_lastStateMac = string.IsNullOrEmpty(stateMac) ? _lastStateMac : stateMac;
UpdateDeviceState(
@ -881,6 +882,15 @@ namespace Kill.Bluetooth
Log($"定时任务设置成功: {tasks[0]}");
else
Log($"定时任务批量设置成功,共 {tasks.Length} 条任务");
ScheduleTask[] normalizedTasks = new ScheduleTask[tasks.Length];
for (int i = 0; i < tasks.Length; i++)
{
normalizedTasks[i] = tasks[i];
normalizedTasks[i].TaskId = (byte)i;
}
UpdateStateFromResponse(response, BLEDeviceStateField.ScheduleTasks,
state => state.SetScheduleTasks(normalizedTasks));
}
else
{
@ -1601,6 +1611,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) =>
{
var setting = MillimeterWaveSetting.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.MillimeterWave,
state => state.SetMillimeterWave(setting));
callback?.Invoke(setting);
OnMillimeterWaveSettingReceived?.Invoke(setting);
Log($"读取毫米波雷达设置: {setting}");
@ -1630,9 +1642,15 @@ namespace Kill.Bluetooth
{
bool success = response.IsSuccess;
if (success)
Log($"毫米波雷达设置成功: {setting}");
{
Log($"毫米波设置成功: {setting}");
UpdateStateFromResponse(response, BLEDeviceStateField.MillimeterWave,
state => state.SetMillimeterWave(setting));
}
else
LogError($"毫米波雷达设置失败, 状态码={response.Status:X2}");
{
LogError($"毫米波设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success);
});
}
@ -2612,7 +2630,7 @@ namespace Kill.Bluetooth
// 通知帧优先处理并且绝对不影响命令队列IsWaitingResponse / _pendingCallback / _responseTimer
// 设备主动通知是异步旁路通道,与当前等待响应的命令是两条独立链路
if (frame.ReadWrite == BLEConstants.RW_NOTIFY || frame.Command == BLEConstants.NOTIFY_COMMAND_CODE)
if (frame.ReadWrite == BLEConstants.RW_NOTIFY && frame.Command == BLEConstants.NOTIFY_COMMAND_CODE)
{
HandleNotification(frame.Data);
return;
@ -2728,6 +2746,12 @@ namespace Kill.Bluetooth
private void HandleDeviceStateChangeNotification(byte[] data)
{
if (data == null || data.Length < 2)
{
LogWarning("设备状态变化通知数据长度不足");
return;
}
var notify = DeviceStateChangeNotification.FromBytes(data);
if (notify.WorkMode > (byte)WorkMode.Eliminate)
{
@ -2737,7 +2761,7 @@ namespace Kill.Bluetooth
Log($"设备状态变化通知: 工作模式=0x{notify.WorkMode:X2}");
UpdateDeviceState(
GetCurrentDeviceMac(),
GetStateMac(),
BLEDeviceStateField.WorkMode,
state => state.SetWorkMode((WorkMode)notify.WorkMode));
try
@ -2752,11 +2776,17 @@ namespace Kill.Bluetooth
private void HandleErrorStatusNotification(byte[] data)
{
if (data == null || data.Length < 8)
{
LogWarning("错误状态通知数据长度不足");
return;
}
var notify = ErrorStatusNotification.FromBytes(data);
if (data != null && data.Length >= 8)
{
UpdateDeviceState(
GetCurrentDeviceMac(),
GetStateMac(),
BLEDeviceStateField.HardwareStatus,
state => state.SetHardwareStatus(notify.Status));
}
@ -2773,6 +2803,12 @@ namespace Kill.Bluetooth
private void HandleParameterChangeNotification(byte[] data)
{
if (data == null || data.Length < 3)
{
LogWarning("参数变化通知数据长度不足");
return;
}
var notify = ParameterChangeNotification.FromBytes(data);
Log($"参数变化通知: 命令=0x{notify.CommandCode:X2}, 数据长度={(notify.Payload?.Length ?? 0)}");
@ -2794,7 +2830,7 @@ namespace Kill.Bluetooth
/// </summary>
private void ApplyParameterChangeToDeviceState(ParameterChangeNotification notify)
{
string deviceMac = GetCurrentDeviceMac();
string deviceMac = GetStateMac();
if (string.IsNullOrEmpty(deviceMac) || notify.Payload == null)
{
return;
@ -2805,35 +2841,52 @@ namespace Kill.Bluetooth
switch (notify.CommandCode)
{
case BLEConstants.CMD_LANGUAGE_SETTING:
if (notify.Payload.Length < 1) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.Language,
state => state.SetLanguage(LanguageSetting.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_SCHEDULE_TASK:
if (notify.Payload.Length < 7) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.ScheduleTasks,
state => state.SetScheduleTasks(ScheduleTaskListResponse.FromBytes(0x00, notify.Payload).Tasks));
break;
case BLEConstants.CMD_DEVICE_LOCK:
if (notify.Payload.Length < 1) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.LockState,
state => state.SetLockState(DeviceLockControl.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_FILL_LIGHT_CONTROL:
if (notify.Payload.Length < 3) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.FillLight,
state => state.SetFillLight(FillLightControl.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_ANGLE_CONTROL:
if (notify.Payload.Length < 2) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.Angle,
state => state.SetAngle(AngleControl.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_DISTANCE_CONTROL:
if (notify.Payload.Length < 4) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.Distance,
state => state.SetDistance(DistanceControl.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_VISUAL_DETECTION_SETTING:
if (notify.Payload.Length < 2) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.VisualDetection,
state => state.SetVisualDetection(VisualDetectionSetting.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_MILLIMETER_WAVE_SETTING:
if (notify.Payload.Length < 4) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.MillimeterWave,
state => state.SetMillimeterWave(MillimeterWaveSetting.FromBytes(notify.Payload)));
break;
case BLEConstants.CMD_FILL_LIGHT_CONNECTION_STATUS:
if (notify.Payload.Length < 1) return;
UpdateDeviceState(deviceMac, BLEDeviceStateField.FillLightConnection,
state => state.SetFillLightConnection(FillLightConnectionStatus.FromBytes(notify.Payload)));
break;
default:
// 未映射的字段:不写仓库,避免误覆盖
// 暂不写入本地状态,避免异常参数覆盖正确值
break;
}
}
@ -2845,11 +2898,17 @@ namespace Kill.Bluetooth
private void HandleCapacitorStateNotification(byte[] data)
{
if (data == null || data.Length < 2)
{
LogWarning("蓄能状态通知数据长度不足");
return;
}
var notify = CapacitorStateNotification.FromBytes(data);
if (data != null && data.Length >= 2)
{
UpdateDeviceState(
GetCurrentDeviceMac(),
GetStateMac(),
BLEDeviceStateField.CapacitorState,
state => state.SetCapacitorState(notify));
}
@ -2866,12 +2925,18 @@ namespace Kill.Bluetooth
private void HandleFillLightConnectionNotification(byte[] data)
{
if (data == null || data.Length < 2)
{
LogWarning("补光灯连接状态通知数据长度不足");
return;
}
var notify = FillLightConnectionNotification.FromBytes(data);
if (data != null && data.Length >= 2)
{
var status = new FillLightConnectionStatus { IsConnected = notify.IsConnected };
UpdateDeviceState(
GetCurrentDeviceMac(),
GetStateMac(),
BLEDeviceStateField.FillLightConnection,
state => state.SetFillLightConnection(status));
}

View File

@ -52,6 +52,8 @@ namespace Kill.Bluetooth
public VisualDetectionSetting? VisualDetection { get; private set; }
public MillimeterWaveSetting? MillimeterWave { get; private set; }
public LanguageSetting? Language { get; private set; }
public HardwareStatus? HardwareStatus { get; private set; }
@ -183,6 +185,21 @@ namespace Kill.Bluetooth
return true;
}
internal bool SetMillimeterWave(MillimeterWaveSetting setting)
{
if (MillimeterWave.HasValue &&
MillimeterWave.Value.Enable == setting.Enable &&
MillimeterWave.Value.Sensitivity == setting.Sensitivity &&
MillimeterWave.Value.ActualSafeDistance == setting.ActualSafeDistance)
{
return false;
}
MillimeterWave = setting;
MarkUpdated();
return true;
}
internal bool SetLanguage(LanguageSetting language)
{
if (Language.HasValue && Language.Value.Language == language.Language)

View File

@ -60,6 +60,7 @@ namespace Kill.UI.Pages
private int TOTAL_STATUS_COUNT = 8; // 总共需要查询的状态数量
private float statusQueryTimeout = 15f; // 状态查询超时时间(秒)
private Coroutine statusQueryTimeoutCoroutine; // 超时协程
private Coroutine deviceStatusQueryCoroutine; // 首次状态全量查询协程
// 串行查询相关
private Queue<Action> statusQueryQueue = new Queue<Action>(); // 状态查询队列
@ -286,6 +287,12 @@ namespace Kill.UI.Pages
/// </summary>
private void StartDeviceStateInitialization()
{
if (deviceStatusQueryCoroutine != null)
{
StopCoroutine(deviceStatusQueryCoroutine);
deviceStatusQueryCoroutine = null;
}
isDeviceStateInitializing = true;
pendingStatusCount = TOTAL_STATUS_COUNT;
@ -334,6 +341,11 @@ namespace Kill.UI.Pages
StopCoroutine(statusQueryTimeoutCoroutine);
statusQueryTimeoutCoroutine = null;
}
if (deviceStatusQueryCoroutine != null)
{
StopCoroutine(deviceStatusQueryCoroutine);
deviceStatusQueryCoroutine = null;
}
bluetoothDeviceInfoCoroutine=StartCoroutine(CheckDeviceError());
Debug.Log("[HomePageCtrl] 设备状态初始化完成");
}
@ -363,6 +375,11 @@ namespace Kill.UI.Pages
isBluetoothUserLoggedIn = false;
isQueryingStatus = false;
statusQueryQueue?.Clear();
if (deviceStatusQueryCoroutine != null)
{
StopCoroutine(deviceStatusQueryCoroutine);
deviceStatusQueryCoroutine = null;
}
UIManager.Instance?.ClearBackAction();
// 刷新首页设备连接状态UI延迟到主循环执行与连接成功时保持一致
pendingUIUpdates.Add(() =>
@ -398,7 +415,7 @@ namespace Kill.UI.Pages
StartDeviceStateInitialization();
// 串行查询设备状态
StartCoroutine(QueryAllDeviceStatusSequential());
deviceStatusQueryCoroutine = StartCoroutine(QueryAllDeviceStatusSequential());
}
/// <summary>
@ -493,9 +510,13 @@ namespace Kill.UI.Pages
/// <summary>
/// 按 MAC 隔离:当前设备不匹配(已切换/未连接/选中设备不同)则丢弃通知
/// </summary>
private bool IsNotificationForCurrentDevice()
private bool IsNotificationForCurrentDevice(BLEDeviceState state = null)
{
string mac = BLECommunicationManager.Instance?.GetCurrentDeviceMac();
string mac = state?.DeviceMac;
if (string.IsNullOrEmpty(mac))
{
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);
@ -509,7 +530,7 @@ namespace Kill.UI.Pages
private void OnDeviceStateChanged(BLEDeviceState state, BLEDeviceStateField field)
{
if (state == null) return;
if (!IsNotificationForCurrentDevice()) return;
if (!IsNotificationForCurrentDevice(state)) return;
if (isDeviceStateInitializing) return;
switch (field)
@ -519,7 +540,10 @@ namespace Kill.UI.Pages
{
int mode = (int)state.WorkMode.Value;
currentWorkMode = state.WorkMode.Value;
DataManager.Instance.deviceConfig.work_mode = DeviceConfig.ToServerString(state.WorkMode.Value);
if (DataManager.Instance.deviceConfig != null)
{
DataManager.Instance.deviceConfig.work_mode = DeviceConfig.ToServerString(state.WorkMode.Value);
}
pendingUIUpdates.Add(() => deviceCtrl?.InitWrokMode(mode));
}
break;
@ -576,12 +600,15 @@ namespace Kill.UI.Pages
case BLEDeviceStateField.Language:
if (state.Language.HasValue)
{
DataManager.Instance.deviceConfig.language = state.Language.Value.IsChinese ? 1 : 0;
DataManager.Instance.SyncDeviceConfigToServer();
if (DataManager.Instance.deviceConfig != null)
{
DataManager.Instance.deviceConfig.language = state.Language.Value.IsChinese ? 1 : 0;
DataManager.Instance.SyncDeviceConfigToServer();
}
}
break;
default:
// 其他字段Connection / LockState / HardwareStatus / CapacitorState / FillLightConnection暂不在主页直接刷新
// 其他字段Connection / HardwareStatus / CapacitorState / FillLightConnection暂不在主页直接刷新
break;
}
}