fix(ble&homepage): 修复蓝牙参数变化通知与主页状态同步问题

1. 调整BLE参数变化通知的数据长度校验,支持无载荷的通知场景
2. 当参数变化通知无载荷时,主动触发对应配置项的查询
3. 新增蓝牙状态补拉排队机制,解决串行查询期间请求被吞的问题
4. 优化主页配置轮询逻辑,使用独立快照避免BLE回写污染导致的循环查询
5. 新增定时任务变化检测与精准拉取逻辑
This commit is contained in:
“虞渠成” 2026-09-11 13:42:23 +08:00
parent 5478355435
commit 1710194d2f
2 changed files with 227 additions and 7 deletions

View File

@ -2803,7 +2803,8 @@ namespace Kill.Bluetooth
private void HandleParameterChangeNotification(byte[] data) private void HandleParameterChangeNotification(byte[] data)
{ {
if (data == null || data.Length < 3) // 最少2字节通知类型 + 命令码(载荷允许为空,如固件上报 06-A0 不带新值)
if (data == null || data.Length < 2)
{ {
LogWarning("参数变化通知数据长度不足"); LogWarning("参数变化通知数据长度不足");
return; return;
@ -2812,6 +2813,48 @@ namespace Kill.Bluetooth
var notify = ParameterChangeNotification.FromBytes(data); var notify = ParameterChangeNotification.FromBytes(data);
Log($"参数变化通知: 命令=0x{notify.CommandCode:X2}, 数据长度={(notify.Payload?.Length ?? 0)}"); Log($"参数变化通知: 命令=0x{notify.CommandCode:X2}, 数据长度={(notify.Payload?.Length ?? 0)}");
if (notify.Payload == null || notify.Payload.Length == 0)
{
// 硬件约定:参数变化通知不携带新值,收到后按命令码主动查询一次真实值
switch (notify.CommandCode)
{
case BLEConstants.CMD_WORK_MODE_SETTING:
ReadWorkMode();
break;
case BLEConstants.CMD_LANGUAGE_SETTING:
ReadLanguageSetting();
break;
case BLEConstants.CMD_SCHEDULE_TASK:
ReadScheduleTasks();
break;
case BLEConstants.CMD_DEVICE_LOCK:
ReadDeviceLockControl();
break;
case BLEConstants.CMD_FILL_LIGHT_CONTROL:
ReadFillLightControl();
break;
case BLEConstants.CMD_FILL_LIGHT_CONNECTION_STATUS:
ReadFillLightConnectionStatus();
break;
case BLEConstants.CMD_ANGLE_CONTROL:
ReadAngleControl();
break;
case BLEConstants.CMD_DISTANCE_CONTROL:
ReadDistanceControl();
break;
case BLEConstants.CMD_VISUAL_DETECTION_SETTING:
ReadVisualDetectionSetting();
break;
case BLEConstants.CMD_MILLIMETER_WAVE_SETTING:
ReadMillimeterWaveSetting();
break;
default:
LogWarning($"参数变化通知无对应读接口: 命令=0x{notify.CommandCode:X2}");
break;
}
return;
}
// 优先回写本地状态仓库并按字段广播 OnDeviceStateChanged便于业务侧按字段做局部刷新 // 优先回写本地状态仓库并按字段广播 OnDeviceStateChanged便于业务侧按字段做局部刷新
ApplyParameterChangeToDeviceState(notify); ApplyParameterChangeToDeviceState(notify);

View File

@ -12,7 +12,7 @@ using Kill.Bluetooth;
using Kill.Bluetooth.Protocol; using Kill.Bluetooth.Protocol;
using Kill.Core; using Kill.Core;
using Unity.VisualScripting; using Unity.VisualScripting;
using Kill.Managers.ResponseModels;
namespace Kill.UI.Pages namespace Kill.UI.Pages
{ {
public class HomePageCtrl : MonoBehaviour public class HomePageCtrl : MonoBehaviour
@ -66,6 +66,9 @@ namespace Kill.UI.Pages
// 串行查询相关 // 串行查询相关
private Queue<Action> statusQueryQueue = new Queue<Action>(); // 状态查询队列 private Queue<Action> statusQueryQueue = new Queue<Action>(); // 状态查询队列
private bool isQueryingStatus = false; // 是否正在查询状态 private bool isQueryingStatus = false; // 是否正在查询状态
private HashSet<string> _pendingBluetoothPullFields; // 串行查询期间排队的蓝牙补拉:变化的配置字段
private bool _pendingBluetoothPullTasks; // 串行查询期间排队的蓝牙补拉:定时任务是否变化
private DeviceConfig _lastCloudConfigSnapshot; // 轮询专用云端配置快照(与 DataManager.deviceConfig 解耦避免BLE回写污染导致变化误判
// 工作模式设置后查询相关 // 工作模式设置后查询相关
private bool isWaitingForWorkModeQuery = false; // 是否正在等待工作模式查询响应 private bool isWaitingForWorkModeQuery = false; // 是否正在等待工作模式查询响应
@ -352,6 +355,15 @@ namespace Kill.UI.Pages
StopCoroutine(deviceStatusQueryCoroutine); StopCoroutine(deviceStatusQueryCoroutine);
deviceStatusQueryCoroutine = null; deviceStatusQueryCoroutine = null;
} }
// 串行查询协程在此被终止(正常完成/超时),必须复位查询标志并处理排队中的蓝牙状态补拉,
// 否则 isQueryingStatus 会一直卡在 true后续蓝牙状态拉取请求全部被吞
if (isQueryingStatus)
{
isQueryingStatus = false;
TryFlushPendingBluetoothPull();
}
bluetoothDeviceInfoCoroutine=StartCoroutine(CheckDeviceError()); bluetoothDeviceInfoCoroutine=StartCoroutine(CheckDeviceError());
Debug.Log("[HomePageCtrl] 设备状态初始化完成"); Debug.Log("[HomePageCtrl] 设备状态初始化完成");
} }
@ -380,6 +392,8 @@ namespace Kill.UI.Pages
DataManager.Instance.hasBluetooth = false; DataManager.Instance.hasBluetooth = false;
isBluetoothUserLoggedIn = false; isBluetoothUserLoggedIn = false;
isQueryingStatus = false; isQueryingStatus = false;
_pendingBluetoothPullFields = null;
_pendingBluetoothPullTasks = false;
statusQueryQueue?.Clear(); statusQueryQueue?.Clear();
if (deviceStatusQueryCoroutine != null) if (deviceStatusQueryCoroutine != null)
{ {
@ -474,10 +488,103 @@ namespace Kill.UI.Pages
isQueryingStatus = false; isQueryingStatus = false;
Debug.Log("[HomePageCtrl] 串行查询设备状态完成"); Debug.Log("[HomePageCtrl] 串行查询设备状态完成");
// 串行查询期间触发的蓝牙状态拉取请求,结束后按变化字段补拉
TryFlushPendingBluetoothPull();
if(!hasWifi) if(!hasWifi)
OnGetAllMosquitoDataClick(); OnGetAllMosquitoDataClick();
} }
/// <summary>
/// 通过蓝牙按变化字段精准拉取最新设备状态(命令队列串行发送,结果经 OnDeviceStateChanged 按字段刷新UI
/// </summary>
/// <param name="changedFields">变化的 DeviceConfig 字段名集合;为空表示无配置字段变化</param>
/// <param name="scheduleTasksChanged">定时任务是否变化</param>
private void PullLatestStatusViaBluetooth(HashSet<string> changedFields = null, bool scheduleTasksChanged = false)
{
var ble = BLECommunicationManager.Instance;
if (ble == null) return;
// 解析变化字段对应的蓝牙查询命令去重其余字段wifi/rgb/lcd/video/sound等主页不展示且无蓝牙对应查询跳过
var commands = new HashSet<string>();
if (changedFields != null)
{
foreach (var field in changedFields)
{
switch (field)
{
case "work_mode": commands.Add("work_mode"); break;
case "language": commands.Add("language"); break;
case "is_locked": commands.Add("lock"); break;
case "fov_angle": commands.Add("angle"); break;
case "detection_distance":
case "aim_distance": commands.Add("distance"); break;
case "fill_light_enable":
case "fill_light_type":
case "fill_light_intensity": commands.Add("fill_light"); break;
case "visual_detect_enable":
case "visual_sensitivity": commands.Add("visual"); break;
case "radar_enable":
case "radar_sensitivity":
case "radar_safe_distance": commands.Add("radar"); break;
}
}
}
if (scheduleTasksChanged) commands.Add("schedule");
if (commands.Count == 0)
{
Debug.LogWarning("[HomePageCtrl] 蓝牙状态拉取无需要查询的变化字段,跳过");
return;
}
Debug.Log($"[HomePageCtrl] 蓝牙已连接,按变化字段拉取设备状态: {string.Join(",", commands)}");
if (commands.Contains("work_mode")) ble.ReadWorkMode();
if (commands.Contains("schedule")) ble.ReadScheduleTasks();
if (commands.Contains("language")) ble.ReadLanguageSetting();
if (commands.Contains("lock")) ble.ReadDeviceLockControl();
if (commands.Contains("angle")) ble.ReadAngleControl();
if (commands.Contains("distance")) ble.ReadDistanceControl();
if (commands.Contains("fill_light")) ble.ReadFillLightControl();
if (commands.Contains("visual")) ble.ReadVisualDetectionSetting();
if (commands.Contains("radar")) ble.ReadMillimeterWaveSetting();
}
/// <summary>
/// 串行查询进行中,把本次蓝牙拉取请求排队(合并变化字段),查询结束后补拉
/// </summary>
private void QueueBluetoothPull(HashSet<string> changedFields, bool scheduleTasksChanged)
{
if (changedFields != null && changedFields.Count > 0)
{
if (_pendingBluetoothPullFields == null)
_pendingBluetoothPullFields = new HashSet<string>();
foreach (var f in changedFields)
_pendingBluetoothPullFields.Add(f);
}
if (scheduleTasksChanged) _pendingBluetoothPullTasks = true;
Debug.Log("[HomePageCtrl] 串行查询进行中,蓝牙状态拉取排队待补拉");
}
/// <summary>
/// 处理排队的蓝牙状态补拉(按变化字段精准查询),并清空排队记录
/// </summary>
private void TryFlushPendingBluetoothPull()
{
bool hasPending = (_pendingBluetoothPullFields != null && _pendingBluetoothPullFields.Count > 0) || _pendingBluetoothPullTasks;
if (!hasPending) return;
var fields = _pendingBluetoothPullFields;
bool tasksChanged = _pendingBluetoothPullTasks;
_pendingBluetoothPullFields = null;
_pendingBluetoothPullTasks = false;
if (hasBluetooth)
PullLatestStatusViaBluetooth(fields, tasksChanged);
}
/// <summary> /// <summary>
/// 查询所有设备状态(旧方法,已废弃) /// 查询所有设备状态(旧方法,已废弃)
/// </summary> /// </summary>
@ -2098,20 +2205,59 @@ namespace Kill.UI.Pages
}); });
} }
// 在线状态处理后拉取一次设备配置与本地快照比对有变化则刷新主页UI // 在线状态处理后拉取一次设备配置与云端快照比对有变化则刷新主页UI
DeviceConfig configBefore = DataManager.Instance.deviceConfig?.Clone(); // 快照用独立的 _lastCloudConfigSnapshot不能用 DataManager.deviceConfig
// BLE 拉取结果会回写 deviceConfig如工作模式/锁定状态),若快照被污染,
// 会造成"云端值 vs BLE回写值"每轮都判为变化 → 无限循环触发蓝牙拉取
DeviceConfig configBefore = _lastCloudConfigSnapshot;
bool configChanged = false;
await DataManager.Instance.GetDeviceConfig(selectedDevice.ble_mac); await DataManager.Instance.GetDeviceConfig(selectedDevice.ble_mac);
if (stopPolling) break; if (stopPolling) break;
var changedFields = DataManager.Instance.deviceConfig?.GetChangedFields(configBefore); // 首轮(无快照)只建立基线不触发,避免初始化时全字段误判变化
var changedFields = configBefore != null
? DataManager.Instance.deviceConfig?.GetChangedFields(configBefore)
: null;
if (changedFields != null && changedFields.Count > 0) if (changedFields != null && changedFields.Count > 0)
{ {
configChanged = true;
Debug.Log($"[HomePageCtrl] 检测到设备配置变化: {string.Join(",", changedFields.Keys)}刷新主页UI"); Debug.Log($"[HomePageCtrl] 检测到设备配置变化: {string.Join(",", changedFields.Keys)}刷新主页UI");
}
// 无论是否变化都更新云端快照Clone 隔离,防止后续 BLE 回写污染)
_lastCloudConfigSnapshot = DataManager.Instance.deviceConfig?.Clone();
// 拉取定时任务与本地快照比对
var tasksBefore = DataManager.Instance.scheduleTaskDatas != null
? new List<ScheduleTaskData>(DataManager.Instance.scheduleTaskDatas)
: new List<ScheduleTaskData>();
await DataManager.Instance.GetScheduleTasks(selectedDevice.ble_mac);
if (stopPolling) break;
bool tasksChanged = !IsSameScheduleTasks(tasksBefore, DataManager.Instance.scheduleTaskDatas);
if (tasksChanged)
{
Debug.Log("[HomePageCtrl] 检测到定时任务变化刷新主页定时任务UI");
}
if (configChanged || tasksChanged)
{
var changedKeys = configChanged ? new HashSet<string>(changedFields.Keys) : null;
pendingUIUpdates.Add(() => pendingUIUpdates.Add(() =>
{ {
// await 期间蓝牙可能已连接,避免用 WiFi 数据覆盖 BLE 数据 // await 期间蓝牙可能已连接,避免用 WiFi 数据覆盖 BLE 数据
if (hasBluetooth) return; if (hasBluetooth)
InitDeviceUIFromConfig(); {
// 蓝牙已连接:按变化字段通过蓝牙精准拉取(结果经 OnDeviceStateChanged 按字段刷新UI
if (isQueryingStatus)
{
// 串行全量查询进行中,结束后按变化字段补拉,避免本次触发被吞
QueueBluetoothPull(changedKeys, tasksChanged);
return;
}
PullLatestStatusViaBluetooth(changedKeys, tasksChanged);
return;
}
if (configChanged) InitDeviceUIFromConfig();
if (tasksChanged) ApplyScheduleTasksToHomeUI();
}); });
} }
} }
@ -2195,6 +2341,14 @@ namespace Kill.UI.Pages
private async Task RefreshHomeScheduleFromWifi() private async Task RefreshHomeScheduleFromWifi()
{ {
await DataManager.Instance.GetScheduleTasks(selectedDevice.ble_mac); await DataManager.Instance.GetScheduleTasks(selectedDevice.ble_mac);
ApplyScheduleTasksToHomeUI();
}
/// <summary>
/// 把 DataManager.scheduleTaskDatas 转换并刷新主页定时任务UI
/// </summary>
private void ApplyScheduleTasksToHomeUI()
{
var tasks = new List<ScheduleTask>(); var tasks = new List<ScheduleTask>();
foreach (var d in DataManager.Instance.scheduleTaskDatas) foreach (var d in DataManager.Instance.scheduleTaskDatas)
{ {
@ -2223,6 +2377,29 @@ namespace Kill.UI.Pages
}); });
} }
/// <summary>
/// 比较两次定时任务列表是否一致(逐项比较关键字段)
/// </summary>
private static bool IsSameScheduleTasks(List<ScheduleTaskData> before, List<ScheduleTaskData> after)
{
int beforeCount = before?.Count ?? 0;
int afterCount = after?.Count ?? 0;
if (beforeCount != afterCount) return false;
for (int i = 0; i < beforeCount; i++)
{
var a = before[i];
var b = after[i];
if (a.task_id != b.task_id || a.enabled != b.enabled ||
a.start_hour != b.start_hour || a.start_minute != b.start_minute ||
a.end_hour != b.end_hour || a.end_minute != b.end_minute ||
a.mode != b.mode || a.repeat_mask != b.repeat_mask)
{
return false;
}
}
return true;
}
/// <summary> /// <summary>
/// 通过WiFi发送BLE指令到设备使用共通错误处理 /// 通过WiFi发送BLE指令到设备使用共通错误处理
/// </summary> /// </summary>