暂时提交

This commit is contained in:
“虞渠成” 2026-09-03 13:33:58 +08:00
parent 2dd4013f6f
commit 8e879b009a
13 changed files with 3180 additions and 13 deletions

View File

@ -50,7 +50,14 @@ namespace Kill.Bluetooth
public event Action<StatisticsData> OnStatisticsDataReceived; // 收到统计数据 public event Action<StatisticsData> OnStatisticsDataReceived; // 收到统计数据
public event Action<SensorData> OnSensorDataReceived; // 收到传感器数据 public event Action<SensorData> OnSensorDataReceived; // 收到传感器数据
public event Action<MosquitoData> OnMosquitoDataReceived; // 收到蚊虫数据通知 public event Action<MosquitoData> OnMosquitoDataReceived; // 收到蚊虫数据通知
public event Action<DeviceStateChangeNotification> OnDeviceStateChangeNotification; // 设备状态变化通知0x01
public event Action<ErrorStatusNotification> OnErrorStatusNotification; // 错误状态上报通知0x02
public event Action<ParameterChangeNotification> OnParameterChangeNotification; // 参数变化通知0x06
public event Action<CapacitorStateNotification> OnCapacitorStateNotification; // 蓄能状态通知0x08
public event Action<FillLightConnectionNotification> OnFillLightConnectionNotification; // 补光灯连接状态通知0x09
public event Action<byte, string> OnDeviceNotificationReceived; // 通用通知(命令码 + 设备MAC用于按 MAC 隔离广播
public event Action<string> OnRawDataReceived; // 收到原始数据(十六进制字符串) public event Action<string> OnRawDataReceived; // 收到原始数据(十六进制字符串)
public event Action<BLEDeviceState, BLEDeviceStateField> OnDeviceStateChanged; // 本地设备状态变化(设备状态、字段)
public event Action<string> OnRawDataSent; // 发送原始数据(十六进制字符串) public event Action<string> OnRawDataSent; // 发送原始数据(十六进制字符串)
public event Action<string> OnCommunicationError; // 通信错误 public event Action<string> OnCommunicationError; // 通信错误
@ -89,6 +96,83 @@ namespace Kill.Bluetooth
} }
} }
private readonly Dictionary<string, BLEDeviceState> _deviceStates =
new Dictionary<string, BLEDeviceState>(StringComparer.OrdinalIgnoreCase);
private readonly object _deviceStateLock = new object();
private string _lastStateMac;
/// <summary>
/// 获取当前连接设备对应的本地状态对象。
/// 状态仓库按设备 MAC 隔离,旧设备通知不会覆盖当前设备。
/// </summary>
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;
}
}
/// <summary>
/// 更新当前设备状态,并仅在值发生变化时广播状态事件。
/// </summary>
private bool UpdateDeviceState(string deviceMac, BLEDeviceStateField field, Action<BLEDeviceState> 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<BLEDeviceState> 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 (根据实际设备配置) // 服务和特征UUID (根据实际设备配置)
public string ServiceUUID = "0000ffe0-0000-1000-8000-00805f9b34fb"; public string ServiceUUID = "0000ffe0-0000-1000-8000-00805f9b34fb";
public string WriteCharacteristicUUID = "0000ffe1-0000-1000-8000-00805f9b34fb"; public string WriteCharacteristicUUID = "0000ffe1-0000-1000-8000-00805f9b34fb";
@ -190,6 +274,16 @@ namespace Kill.Bluetooth
/// </summary> /// </summary>
private void OnBluetoothConnected() private void OnBluetoothConnected()
{ {
string stateMac = GetCurrentDeviceMac();
if (!string.IsNullOrEmpty(stateMac))
{
_lastStateMac = stateMac;
UpdateDeviceState(
stateMac,
BLEDeviceStateField.Connection,
state => state.SetConnection(true));
}
if (AutoInitialize) if (AutoInitialize)
InitializeCommunication(); InitializeCommunication();
} }
@ -200,6 +294,19 @@ namespace Kill.Bluetooth
private void OnBluetoothDisconnected(string address) private void OnBluetoothDisconnected(string address)
{ {
Log($"蓝牙已断开: {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; IsWaitingResponse = false;
_responseTimer = 0; _responseTimer = 0;
@ -562,6 +669,15 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var setting = LanguageSetting.FromBytes(response.Data); 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); callback?.Invoke(setting);
OnLanguageSettingReceived?.Invoke(setting); OnLanguageSettingReceived?.Invoke(setting);
}); });
@ -592,9 +708,15 @@ namespace Kill.Bluetooth
{ {
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"语言设置成功: {(isChinese ? "" : "")}"); Log($"语言设置成功: {(isChinese ? "" : "")}");
UpdateStateFromResponse(response, BLEDeviceStateField.Language,
state => state.SetLanguage(setting));
}
else else
{
LogError($"语言设置失败, 状态码={response.Status:X2}"); LogError($"语言设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -674,6 +796,15 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var taskList = ScheduleTaskListResponse.FromBytes(response.Status, response.Data); 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); callback?.Invoke(taskList);
OnScheduleTaskListReceived?.Invoke(taskList); OnScheduleTaskListReceived?.Invoke(taskList);
}); });
@ -1116,6 +1247,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var control = FillLightControl.FromBytes(response.Data); var control = FillLightControl.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.FillLight,
state => state.SetFillLight(control));
callback?.Invoke(control); callback?.Invoke(control);
OnFillLightControlReceived?.Invoke(control); OnFillLightControlReceived?.Invoke(control);
}); });
@ -1145,9 +1278,15 @@ namespace Kill.Bluetooth
{ {
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"补光灯控制设置成功: 开关={(control.Enable ? "" : "")}, 类型={control.GetLightTypeString()}, 强度={control.GetIntensityString()}"); Log($"补光灯控制设置成功: 开关={(control.Enable ? "" : "")}, 类型={control.GetLightTypeString()}, 强度={control.GetIntensityString()}");
UpdateStateFromResponse(response, BLEDeviceStateField.FillLight,
state => state.SetFillLight(control));
}
else else
{
LogError($"补光灯控制设置失败, 状态码={response.Status:X2}"); LogError($"补光灯控制设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -1172,6 +1311,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var status = FillLightConnectionStatus.FromBytes(response.Data); var status = FillLightConnectionStatus.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.FillLightConnection,
state => state.SetFillLightConnection(status));
callback?.Invoke(status); callback?.Invoke(status);
OnFillLightConnectionStatusReceived?.Invoke(status); OnFillLightConnectionStatusReceived?.Invoke(status);
}); });
@ -1254,6 +1395,8 @@ namespace Kill.Bluetooth
Debug.Log($"[ReadDeviceLockControl] 收到响应: Status={response.Status:X2}, Data={BitConverter.ToString(response.Data)}"); Debug.Log($"[ReadDeviceLockControl] 收到响应: Status={response.Status:X2}, Data={BitConverter.ToString(response.Data)}");
var control = DeviceLockControl.FromBytes(response.Data); var control = DeviceLockControl.FromBytes(response.Data);
Debug.Log($"[ReadDeviceLockControl] 解析结果: IsLocked={control.IsLocked}"); Debug.Log($"[ReadDeviceLockControl] 解析结果: IsLocked={control.IsLocked}");
UpdateStateFromResponse(response, BLEDeviceStateField.LockState,
state => state.SetLockState(control));
callback?.Invoke(control); callback?.Invoke(control);
OnDeviceLockControlReceived?.Invoke(control); OnDeviceLockControlReceived?.Invoke(control);
}); });
@ -1287,9 +1430,15 @@ namespace Kill.Bluetooth
Debug.Log($"[WriteDeviceLockControl] 收到响应: Status={response.Status:X2}"); Debug.Log($"[WriteDeviceLockControl] 收到响应: Status={response.Status:X2}");
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"设备{(isLocked ? "" : "")}成功"); Log($"设备{(isLocked ? "" : "")}成功");
UpdateStateFromResponse(response, BLEDeviceStateField.LockState,
state => state.SetLockState(control));
}
else else
{
LogError($"设备{(isLocked ? "" : "")}失败, 状态码={response.Status:X2}"); LogError($"设备{(isLocked ? "" : "")}失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -1318,6 +1467,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var control = AngleControl.FromBytes(response.Data); var control = AngleControl.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.Angle,
state => state.SetAngle(control));
callback?.Invoke(control); callback?.Invoke(control);
OnAngleControlReceived?.Invoke(control); OnAngleControlReceived?.Invoke(control);
Log($"读取角度控制: {control}"); Log($"读取角度控制: {control}");
@ -1348,9 +1499,15 @@ namespace Kill.Bluetooth
{ {
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"角度控制设置成功: {control}"); Log($"角度控制设置成功: {control}");
UpdateStateFromResponse(response, BLEDeviceStateField.Angle,
state => state.SetAngle(control));
}
else else
{
LogError($"角度控制设置失败, 状态码={response.Status:X2}"); LogError($"角度控制设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -1375,6 +1532,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var control = DistanceControl.FromBytes(response.Data); var control = DistanceControl.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.Distance,
state => state.SetDistance(control));
callback?.Invoke(control); callback?.Invoke(control);
OnDistanceControlReceived?.Invoke(control); OnDistanceControlReceived?.Invoke(control);
Log($"读取距离控制: {control}"); Log($"读取距离控制: {control}");
@ -1405,9 +1564,15 @@ namespace Kill.Bluetooth
{ {
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"距离控制设置成功: {control}"); Log($"距离控制设置成功: {control}");
UpdateStateFromResponse(response, BLEDeviceStateField.Distance,
state => state.SetDistance(control));
}
else else
{
LogError($"距离控制设置失败, 状态码={response.Status:X2}"); LogError($"距离控制设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -1492,6 +1657,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var setting = VisualDetectionSetting.FromBytes(response.Data); var setting = VisualDetectionSetting.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.VisualDetection,
state => state.SetVisualDetection(setting));
callback?.Invoke(setting); callback?.Invoke(setting);
OnVisualDetectionSettingReceived?.Invoke(setting); OnVisualDetectionSettingReceived?.Invoke(setting);
Log($"读取视觉检测设置: {setting}"); Log($"读取视觉检测设置: {setting}");
@ -1521,9 +1688,15 @@ namespace Kill.Bluetooth
{ {
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"视觉检测设置成功: {setting}"); Log($"视觉检测设置成功: {setting}");
UpdateStateFromResponse(response, BLEDeviceStateField.VisualDetection,
state => state.SetVisualDetection(setting));
}
else else
{
LogError($"视觉检测设置失败, 状态码={response.Status:X2}"); LogError($"视觉检测设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -1690,6 +1863,15 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var setting = WorkModeSetting.FromBytes(response.Data); 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); callback?.Invoke(setting);
OnWorkModeSettingReceived?.Invoke(setting); OnWorkModeSettingReceived?.Invoke(setting);
Log($"读取工作模式: {setting}"); Log($"读取工作模式: {setting}");
@ -1719,9 +1901,15 @@ namespace Kill.Bluetooth
{ {
bool success = response.IsSuccess; bool success = response.IsSuccess;
if (success) if (success)
{
Log($"工作模式设置成功: {setting}"); Log($"工作模式设置成功: {setting}");
UpdateStateFromResponse(response, BLEDeviceStateField.WorkMode,
state => state.SetWorkMode(setting.Mode));
}
else else
{
LogError($"工作模式设置失败, 状态码={response.Status:X2}"); LogError($"工作模式设置失败, 状态码={response.Status:X2}");
}
callback?.Invoke(success); callback?.Invoke(success);
}); });
} }
@ -1746,6 +1934,8 @@ namespace Kill.Bluetooth
SendFrame(frame, (response) => SendFrame(frame, (response) =>
{ {
var status = HardwareStatus.FromBytes(response.Data); var status = HardwareStatus.FromBytes(response.Data);
UpdateStateFromResponse(response, BLEDeviceStateField.HardwareStatus,
state => state.SetHardwareStatus(status));
callback?.Invoke(status); callback?.Invoke(status);
OnHardwareStatusReceived?.Invoke(status); OnHardwareStatusReceived?.Invoke(status);
Log($"查询硬件状态: {status}"); Log($"查询硬件状态: {status}");
@ -2420,14 +2610,17 @@ namespace Kill.Bluetooth
{ {
bool wasWaitingResponse = IsWaitingResponse; bool wasWaitingResponse = IsWaitingResponse;
// 优先处理设备主动通知,避免被等待响应的流程拦截 // 通知帧优先处理并且绝对不影响命令队列IsWaitingResponse / _pendingCallback / _responseTimer
if (frame.ReadWrite == BLEConstants.RW_NOTIFY) // 设备主动通知是异步旁路通道,与当前等待响应的命令是两条独立链路
if (frame.ReadWrite == BLEConstants.RW_NOTIFY || frame.Command == BLEConstants.NOTIFY_COMMAND_CODE)
{ {
HandleNotification(frame.Data); HandleNotification(frame.Data);
return;
} }
else if (IsWaitingResponse && frame.Command == LastCommand)
// 响应帧:优先匹配当前等待的命令。
if (IsWaitingResponse && frame.Command == LastCommand)
{ {
// 匹配到等待的响应
IsWaitingResponse = false; IsWaitingResponse = false;
_responseTimer = 0; _responseTimer = 0;
@ -2437,7 +2630,7 @@ namespace Kill.Bluetooth
} }
else if (IsWaitingResponse) else if (IsWaitingResponse)
{ {
// 尝试处理非匹配的响应,可能是硬件响应延迟 // 保留原兼容行为:设备响应延迟时仍允许结束当前等待任务。
IsWaitingResponse = false; IsWaitingResponse = false;
_responseTimer = 0; _responseTimer = 0;
@ -2445,8 +2638,7 @@ namespace Kill.Bluetooth
_pendingCallback?.Invoke(response); _pendingCallback?.Invoke(response);
_pendingCallback = null; _pendingCallback = null;
} }
// 如果之前有等待响应,现在响应已处理,继续处理队列
if (wasWaitingResponse) if (wasWaitingResponse)
{ {
ProcessCommandQueue(); ProcessCommandQueue();
@ -2454,19 +2646,243 @@ namespace Kill.Bluetooth
} }
/// <summary> /// <summary>
/// 通用的设备通知分发器,按命令类型路由到对应处理逻辑 /// 通用的设备通知分发器,按通知类型路由到对应处理逻辑
/// 通知帧约定data[0]=通知类型data[1..]=通知数据
/// </summary> /// </summary>
private void HandleNotification(byte[] data) private void HandleNotification(byte[] data)
{ {
byte command=data[0]; if (data == null || data.Length == 0)
switch (command) {
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: case BLEConstants.NOTIFY_MOSQUITO_DATA:
HandleMosquitoDataNotification(data); HandleMosquitoDataNotification(data);
break; break;
default: case BLEConstants.NOTIFY_DEVICE_STATE_CHANGE:
Log($"未处理的设备通知 0x{command:X2}"); HandleDeviceStateChangeNotification(data);
break; 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;
}
}
/// <summary>
/// 获取当前已连接设备的 MAC用于按 MAC 隔离通知事件)
/// 优先使用 iOS 端从广播中解析出的真实 MAC否则回退到 BLE 地址
/// </summary>
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}");
}
}
/// <summary>
/// 把 0x06 参数变化通知解析后回写到 BLEDeviceState 仓库,仅在值变化时通过 OnDeviceStateChanged 按字段广播
/// </summary>
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}");
} }
} }

View File

@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using Kill.Bluetooth.Protocol;
namespace Kill.Bluetooth
{
/// <summary>
/// 设备配置变化字段。
/// </summary>
public enum BLEDeviceStateField : byte
{
None = 0,
Connection,
WorkMode,
LockState,
ScheduleTasks,
Angle,
Distance,
FillLight,
FillLightConnection,
VisualDetection,
MillimeterWave,
Language,
HardwareStatus,
CapacitorState
}
/// <summary>
/// 蓝牙设备运行状态。
/// 以设备 MAC 为边界保存状态,避免切换设备时旧设备数据覆盖当前设备。
/// </summary>
[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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e69a1efed6be8643ab6d0cf88657497
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8db7a756f6f76fb4ebb36b63755dd084
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -27,7 +27,17 @@ namespace Kill.Bluetooth.Protocol
public const byte CMD_UNREGISTER_USER = 0x08; // 注销用户 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) // 命令码 - 设备设置类 (0x10-0x1F)
public const byte CMD_LANGUAGE_SETTING = 0x10; // 语言设置 public const byte CMD_LANGUAGE_SETTING = 0x10; // 语言设置
@ -2259,5 +2269,118 @@ namespace Kill.Bluetooth.Protocol
} }
#endregion #endregion
#region (0xF0 )
/// <summary>
/// 设备状态变化通知
/// 通知类型 0x01data[0]=0x01data[1]=工作模式状态码0=待机 1=扫描 2=消杀)
/// </summary>
[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;
}
}
/// <summary>
/// 错误状态上报通知
/// 通知类型 0x02data[0]=0x02data[1..7]=7字节硬件错误状态与 0xA1 读响应一致)
/// </summary>
[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;
}
}
/// <summary>
/// 参数变化通知
/// 通知类型 0x06data[0]=0x06data[1]=发生变化的命令码data[2..]=参数数据(与对应读响应一致)
/// 用于通知 APP 设备端参数被修改,无需 APP 主动查询即可同步
/// </summary>
[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;
}
}
/// <summary>
/// 补光灯连接状态通知
/// 通知类型 0x09data[0]=0x09data[1]=连接状态0=断开 1=连接)
/// </summary>
[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;
}
}
/// <summary>
/// 蓄能状态通知
/// 通知类型 0x08data[0]=0x08data[1]=状态0=充电开始 1=充电完成)
/// </summary>
[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
} }

View File

@ -161,6 +161,9 @@ namespace Kill.UI.Pages
BLECommunicationManager.Instance.OnVisualDetectionSettingReceived += OnVisualDetectionReceived; BLECommunicationManager.Instance.OnVisualDetectionSettingReceived += OnVisualDetectionReceived;
// 订阅蚊虫数据事件 // 订阅蚊虫数据事件
BLECommunicationManager.Instance.OnMosquitoDataReceived += OnMosquitoDataReceived; BLECommunicationManager.Instance.OnMosquitoDataReceived += OnMosquitoDataReceived;
// 订阅本地状态变化事件(按字段局部刷新,保留首次全量拉取)
BLECommunicationManager.Instance.OnDeviceStateChanged += OnDeviceStateChanged;
} }
} }
@ -195,6 +198,9 @@ namespace Kill.UI.Pages
BLECommunicationManager.Instance.OnVisualDetectionSettingReceived -= OnVisualDetectionReceived; BLECommunicationManager.Instance.OnVisualDetectionSettingReceived -= OnVisualDetectionReceived;
// 取消订阅蚊虫数据事件 // 取消订阅蚊虫数据事件
BLECommunicationManager.Instance.OnMosquitoDataReceived -= OnMosquitoDataReceived; BLECommunicationManager.Instance.OnMosquitoDataReceived -= OnMosquitoDataReceived;
// 取消订阅本地状态变化事件
BLECommunicationManager.Instance.OnDeviceStateChanged -= OnDeviceStateChanged;
} }
} }
@ -482,6 +488,106 @@ namespace Kill.UI.Pages
#endregion #endregion
#region ()
/// <summary>
/// 按 MAC 隔离:当前设备不匹配(已切换/未连接/选中设备不同)则丢弃通知
/// </summary>
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);
}
/// <summary>
/// 本地设备状态变化回调(按字段局部刷新)
/// 0x01/0x06 通知已由 BLECommunicationManager 解析后回写仓库并按字段广播
/// 首次全量拉取阶段保留原路径,让读响应触发 UI 更新,避免重复刷新
/// </summary>
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 #region
/// <summary> /// <summary>

2
SyntaxCheck/Program.cs Normal file
View File

@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

View File

@ -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"
}
}
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\YQC\.nuget\packages\</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.8.1</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\YQC\.nuget\packages\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />

View File

@ -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"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"version": 2,
"dgSpecHash": "vIdeVNNgNyV9g05vzz3zmuhU/jhHrG+wZsLvVl3eSXOZZKrYzX6bKgJanV1KB2n7qJpxJWpw9zhuwF9gTFQftw==",
"success": true,
"projectFilePath": "D:\\unity_project\\Kill\\SyntaxCheck\\SyntaxCheck.csproj",
"expectedPackageFiles": [],
"logs": []
}