fix(ble&homepage): 修复蓝牙参数变化通知与主页状态同步问题
1. 调整BLE参数变化通知的数据长度校验,支持无载荷的通知场景 2. 当参数变化通知无载荷时,主动触发对应配置项的查询 3. 新增蓝牙状态补拉排队机制,解决串行查询期间请求被吞的问题 4. 优化主页配置轮询逻辑,使用独立快照避免BLE回写污染导致的循环查询 5. 新增定时任务变化检测与精准拉取逻辑
This commit is contained in:
parent
5478355435
commit
1710194d2f
@ -2803,7 +2803,8 @@ namespace Kill.Bluetooth
|
||||
|
||||
private void HandleParameterChangeNotification(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length < 3)
|
||||
// 最少2字节:通知类型 + 命令码(载荷允许为空,如固件上报 06-A0 不带新值)
|
||||
if (data == null || data.Length < 2)
|
||||
{
|
||||
LogWarning("参数变化通知数据长度不足");
|
||||
return;
|
||||
@ -2812,6 +2813,48 @@ namespace Kill.Bluetooth
|
||||
var notify = ParameterChangeNotification.FromBytes(data);
|
||||
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,便于业务侧按字段做局部刷新
|
||||
ApplyParameterChangeToDeviceState(notify);
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ using Kill.Bluetooth;
|
||||
using Kill.Bluetooth.Protocol;
|
||||
using Kill.Core;
|
||||
using Unity.VisualScripting;
|
||||
|
||||
using Kill.Managers.ResponseModels;
|
||||
namespace Kill.UI.Pages
|
||||
{
|
||||
public class HomePageCtrl : MonoBehaviour
|
||||
@ -66,6 +66,9 @@ namespace Kill.UI.Pages
|
||||
// 串行查询相关
|
||||
private Queue<Action> statusQueryQueue = new Queue<Action>(); // 状态查询队列
|
||||
private bool isQueryingStatus = false; // 是否正在查询状态
|
||||
private HashSet<string> _pendingBluetoothPullFields; // 串行查询期间排队的蓝牙补拉:变化的配置字段
|
||||
private bool _pendingBluetoothPullTasks; // 串行查询期间排队的蓝牙补拉:定时任务是否变化
|
||||
private DeviceConfig _lastCloudConfigSnapshot; // 轮询专用云端配置快照(与 DataManager.deviceConfig 解耦,避免BLE回写污染导致变化误判)
|
||||
|
||||
// 工作模式设置后查询相关
|
||||
private bool isWaitingForWorkModeQuery = false; // 是否正在等待工作模式查询响应
|
||||
@ -352,6 +355,15 @@ namespace Kill.UI.Pages
|
||||
StopCoroutine(deviceStatusQueryCoroutine);
|
||||
deviceStatusQueryCoroutine = null;
|
||||
}
|
||||
|
||||
// 串行查询协程在此被终止(正常完成/超时),必须复位查询标志并处理排队中的蓝牙状态补拉,
|
||||
// 否则 isQueryingStatus 会一直卡在 true,后续蓝牙状态拉取请求全部被吞
|
||||
if (isQueryingStatus)
|
||||
{
|
||||
isQueryingStatus = false;
|
||||
TryFlushPendingBluetoothPull();
|
||||
}
|
||||
|
||||
bluetoothDeviceInfoCoroutine=StartCoroutine(CheckDeviceError());
|
||||
Debug.Log("[HomePageCtrl] 设备状态初始化完成");
|
||||
}
|
||||
@ -380,6 +392,8 @@ namespace Kill.UI.Pages
|
||||
DataManager.Instance.hasBluetooth = false;
|
||||
isBluetoothUserLoggedIn = false;
|
||||
isQueryingStatus = false;
|
||||
_pendingBluetoothPullFields = null;
|
||||
_pendingBluetoothPullTasks = false;
|
||||
statusQueryQueue?.Clear();
|
||||
if (deviceStatusQueryCoroutine != null)
|
||||
{
|
||||
@ -474,10 +488,103 @@ namespace Kill.UI.Pages
|
||||
|
||||
isQueryingStatus = false;
|
||||
Debug.Log("[HomePageCtrl] 串行查询设备状态完成");
|
||||
|
||||
// 串行查询期间触发的蓝牙状态拉取请求,结束后按变化字段补拉
|
||||
TryFlushPendingBluetoothPull();
|
||||
|
||||
if(!hasWifi)
|
||||
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>
|
||||
@ -2098,20 +2205,59 @@ namespace Kill.UI.Pages
|
||||
});
|
||||
}
|
||||
|
||||
// 在线状态处理后拉取一次设备配置,与本地快照比对,有变化则刷新主页UI
|
||||
DeviceConfig configBefore = DataManager.Instance.deviceConfig?.Clone();
|
||||
// 在线状态处理后拉取一次设备配置,与云端快照比对,有变化则刷新主页UI
|
||||
// 快照用独立的 _lastCloudConfigSnapshot,不能用 DataManager.deviceConfig:
|
||||
// BLE 拉取结果会回写 deviceConfig(如工作模式/锁定状态),若快照被污染,
|
||||
// 会造成"云端值 vs BLE回写值"每轮都判为变化 → 无限循环触发蓝牙拉取
|
||||
DeviceConfig configBefore = _lastCloudConfigSnapshot;
|
||||
bool configChanged = false;
|
||||
await DataManager.Instance.GetDeviceConfig(selectedDevice.ble_mac);
|
||||
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)
|
||||
{
|
||||
configChanged = true;
|
||||
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(() =>
|
||||
{
|
||||
// await 期间蓝牙可能已连接,避免用 WiFi 数据覆盖 BLE 数据
|
||||
if (hasBluetooth) return;
|
||||
InitDeviceUIFromConfig();
|
||||
if (hasBluetooth)
|
||||
{
|
||||
// 蓝牙已连接:按变化字段通过蓝牙精准拉取(结果经 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()
|
||||
{
|
||||
await DataManager.Instance.GetScheduleTasks(selectedDevice.ble_mac);
|
||||
ApplyScheduleTasksToHomeUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把 DataManager.scheduleTaskDatas 转换并刷新主页定时任务UI
|
||||
/// </summary>
|
||||
private void ApplyScheduleTasksToHomeUI()
|
||||
{
|
||||
var tasks = new List<ScheduleTask>();
|
||||
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>
|
||||
/// 通过WiFi发送BLE指令到设备(使用共通错误处理)
|
||||
/// </summary>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user