|
@@ -1,6 +1,7 @@
|
|
|
-using System;
|
|
|
|
|
|
|
+using System;
|
|
|
using System.Collections;
|
|
using System.Collections;
|
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
|
|
|
+using System.Linq;
|
|
|
using System.Text;
|
|
using System.Text;
|
|
|
using UnityEngine;
|
|
using UnityEngine;
|
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices;
|
|
@@ -11,22 +12,83 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Windows连接BluetoothLE
|
|
/// Windows连接BluetoothLE
|
|
|
/// 我的扫描逻辑默认了读写特征都在同一服务下
|
|
/// 我的扫描逻辑默认了读写特征都在同一服务下
|
|
|
|
|
+ /// 扫描策略(对齐文档:广播含 16-bit 0xFFF0 + 名称含 “WF”;现网兼容旧名):
|
|
|
|
|
+ /// - 粗筛 0xFFF0:依赖 DLL <see cref="BleApi.BleScanMode"/> 的 AdvertisementWatcher/Combined;纯 DeviceInformationOnly 无法在扫描期按广播 FFF0 过滤。
|
|
|
|
|
+ /// 连接后仍由 targetService(FFF0)GATT 校验。
|
|
|
|
|
+ /// - 精筛名称:默认在设备名中查找子串 <see cref="bleFilterNameKey"/>(默认 “WF”),**默认不区分大小写**;勾选 <see cref="bleFilterNameKeyCaseSensitive"/> 后仅字面匹配(如仅 “WF” 不匹配 “wf”)。
|
|
|
|
|
+ /// 关键字可在 Inspector 改为如 “WF_” 以收紧前缀。另可选各 Aim 类型白名单全名(忽略大小写,兼容青凤鸾旧名)。
|
|
|
|
|
+ /// 关闭 <see cref="allowLegacyDeviceNameWhitelist"/> 则仅走关键字子串(无白名单全名)。
|
|
|
|
|
+ /// - 按 MAC 连接时以 MAC 为准,FFF0 仍后验。
|
|
|
|
|
+ /// - 若 <see cref="AimDeviceInfo"/> 已保存绑定 MAC(bInitMac),默认仅连接该 MAC,避免枪类白名单含 Bbow 时误连弓箭。
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public class BleWinHelper : MonoBehaviour
|
|
public class BleWinHelper : MonoBehaviour
|
|
|
{
|
|
{
|
|
|
|
|
+ /// <summary>与文档 #define TARGET_SERVICE_UUID 0xFFF0 一致(Windows 扫描期不可见,仅文档与后验服务 UUID 对应)。</summary>
|
|
|
|
|
+ public const ushort BleAdvertisedServiceUuid16 = 0xFFF0;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>文档默认 FILTER_NAME_KEY;<see cref="bleFilterNameKey"/> 留空时不做关键字子串匹配,仅白名单全名(若允许)。</summary>
|
|
|
|
|
+ public const string BleFilterNameKeyDefault = "WF";
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("名称精筛子串,默认 WF;可改为 WF_ 等以收紧匹配。留空则仅能通过下方白名单全名命中(若允许)。")]
|
|
|
|
|
+ public string bleFilterNameKey = BleFilterNameKeyDefault;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("关=关键字不区分大小写(wf/WF/Wf 均可);开=严格按字面(如仅 WF 命中,wf 不命中)。")]
|
|
|
|
|
+ public bool bleFilterNameKeyCaseSensitive = false;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>扫描阶段最长时长(秒),到时停止枚举并由排序后的目标列表开始匹配。</summary>
|
|
|
|
|
+ public const float DeviceScanPhaseDurationSeconds = 10f;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>目标设备列表最多候选数,满则停止扫描。</summary>
|
|
|
|
|
+ public const int MaxScanTargetCandidates = 10;
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>排序时该旧设备名固定排在列表末尾(与管道串中的名称一致)。</summary>
|
|
|
|
|
+ public const string LegacyDeviceNameSortLast = "Bbow_20210501";
|
|
|
|
|
+
|
|
|
|
|
+ [Header("BLE 扫描(文档:0xFFF0 广播 + 名称关键字;Windows 见类注释)")]
|
|
|
|
|
+ [Tooltip("开=名称含关键字(见 bleFilterNameKey)或白名单全名命中(忽略大小写,旧设备)。关=仅关键字子串(仍受大小写开关影响)。")]
|
|
|
|
|
+ public bool allowLegacyDeviceNameWhitelist = true;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("仅当 logBleVerbosePollStream 开启时生效:开=verbose 流里打印周边/未过精筛;关=verbose 流里只打印精筛候选。")]
|
|
|
|
|
+ public bool logAllBleAdvertisements = false;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("开=每次 Poll 按旧逻辑刷屏(ShouldEmitBleListStyleLog);关=不打印该类逐条日志(推荐)。")]
|
|
|
|
|
+ public bool logBleVerbosePollStream = false;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("开=每新增一台进「目标候选列表」打一行;扫描 FINISHED 再打一次完整列表。关=不打。")]
|
|
|
|
|
+ public bool logBleScanListEvents = true;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("须非 DeviceInformationOnly 才能在扫描期按广播 0xFFF0 过滤无关设备。扫描时长由本脚本 10s / 满 10 台控制。")]
|
|
|
|
|
+ public BleApi.BleScanMode bleDllScanMode = BleApi.BleScanMode.Combined;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("AimDeviceInfo 已保存绑定 MAC 时,仅匹配该 MAC,避免枪类白名单含 Bbow 时误连弓箭后再断开。")]
|
|
|
|
|
+ public bool restrictConnectToSavedMacWhenBonded = true;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("当存档存在绑定 MAC(bInitMac)时:扫描中一旦命中该 MAC 且可连接,立即选中并结束扫描,不经名称筛选与长等待;扫描总时长上限见 fastReconnectScanMaxSeconds。")]
|
|
|
|
|
+ public bool enableFastReconnectBySavedMac = true;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("仅当 enableFastReconnectBySavedMac 且存档有 MAC 时生效:扫描最长秒数(找不到则结束);一般 2~5。")]
|
|
|
|
|
+ public float fastReconnectScanMaxSeconds = 3f;
|
|
|
|
|
+
|
|
|
|
|
+ [Header("BLE 诊断(PollData)")]
|
|
|
|
|
+ [Tooltip("开=true:写入 SmartBowSDK.BleWinrtDllSharedInbox,打印每帧从原生队列排空的通知包数量(需工程引用含 BleWinrtDllSharedInbox 的 SmartBowSDK.dll)。")]
|
|
|
|
|
+ public bool logBleDllInboxVerbose = false;
|
|
|
|
|
+
|
|
|
|
|
+ [Tooltip("须与 logBleDllInboxVerbose 同时开启;每包一行 deviceId/len(极易刷屏)。")]
|
|
|
|
|
+ public bool logBleDllInboxEachPacket = false;
|
|
|
|
|
+
|
|
|
public string LogTag = "BleWinHelper-Log: ";
|
|
public string LogTag = "BleWinHelper-Log: ";
|
|
|
- public bool bDebug = false;
|
|
|
|
|
- private void Log(string text)
|
|
|
|
|
|
|
+ public bool bDebug = true;
|
|
|
|
|
+ private void Log(string text)
|
|
|
{
|
|
{
|
|
|
- if (bDebug)Debug.Log(LogTag + text);
|
|
|
|
|
|
|
+ if (bDebug) Debug.Log(LogTag + text);
|
|
|
}
|
|
}
|
|
|
private void Warn(string text)
|
|
private void Warn(string text)
|
|
|
{
|
|
{
|
|
|
- if (bDebug)Debug.LogWarning(LogTag + text);
|
|
|
|
|
|
|
+ if (bDebug) Debug.LogWarning(LogTag + text);
|
|
|
}
|
|
}
|
|
|
- private void Error(string text)
|
|
|
|
|
|
|
+ private void Error(string text)
|
|
|
{
|
|
{
|
|
|
- if (bDebug)Debug.Log(LogTag + text);
|
|
|
|
|
|
|
+ if (bDebug) Debug.Log(LogTag + text);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private string targetDeviceNameAxis = "Bbow_20210501 | ARTEMIS | HOUYI | HOUYI Pro | ARTEMIS Pro";
|
|
private string targetDeviceNameAxis = "Bbow_20210501 | ARTEMIS | HOUYI | HOUYI Pro | ARTEMIS Pro";
|
|
@@ -35,6 +97,7 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
private string targetDeviceNameGun = "Pistol | Pistol M9 | Bbow_20210501";
|
|
private string targetDeviceNameGun = "Pistol | Pistol M9 | Bbow_20210501";
|
|
|
private string targetDeviceNameGun_M17 = "Pistol M17";
|
|
private string targetDeviceNameGun_M17 = "Pistol M17";
|
|
|
private string targetDeviceNameGun_M416 = "Rifle M416";
|
|
private string targetDeviceNameGun_M416 = "Rifle M416";
|
|
|
|
|
+ /// <summary>对应文档 16-bit 0xFFF0 的 128-bit GATT Service(连接后粗筛实质校验)。</summary>
|
|
|
private string targetService = "{0000fff0-0000-1000-8000-00805f9b34fb}";
|
|
private string targetService = "{0000fff0-0000-1000-8000-00805f9b34fb}";
|
|
|
private string targetCharacteristicsNotify = "{0000fff1-0000-1000-8000-00805f9b34fb}";
|
|
private string targetCharacteristicsNotify = "{0000fff1-0000-1000-8000-00805f9b34fb}";
|
|
|
private string targetCharacteristicsWrite = "{0000fff2-0000-1000-8000-00805f9b34fb}";
|
|
private string targetCharacteristicsWrite = "{0000fff2-0000-1000-8000-00805f9b34fb}";
|
|
@@ -54,17 +117,34 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
private List<string> serviceList = new List<string>();
|
|
private List<string> serviceList = new List<string>();
|
|
|
private List<string> characteristicsList = new List<string>();
|
|
private List<string> characteristicsList = new List<string>();
|
|
|
|
|
|
|
|
|
|
+ private struct ScanCandidateEntry
|
|
|
|
|
+ {
|
|
|
|
|
+ public string DeviceId;
|
|
|
|
|
+ public string Name;
|
|
|
|
|
+ public int RssiDbm;
|
|
|
|
|
+ public bool RssiValid;
|
|
|
|
|
+ public bool IsConnectable;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private readonly Dictionary<string, ScanCandidateEntry> _scanCandidatesById = new Dictionary<string, ScanCandidateEntry>();
|
|
|
|
|
+ private float _deviceScanPhaseStartTime;
|
|
|
|
|
+ private float _scanPhaseTimeoutSeconds = DeviceScanPhaseDurationSeconds;
|
|
|
|
|
+ private bool _scanPhaseCompletionScheduled;
|
|
|
|
|
+
|
|
|
private float _connectedTime = 0;
|
|
private float _connectedTime = 0;
|
|
|
private float _receiveDataTime = 0;
|
|
private float _receiveDataTime = 0;
|
|
|
private float _heartBeatInterval = 0;
|
|
private float _heartBeatInterval = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+ // [合并-DLL Win11] 扫描结束回调(是否已选中设备)。DLL 版会接到 bluetoothWindows.OnScanEnded。
|
|
|
|
|
+ // 【冲突/对接】若你工程里的 BluetoothWindows 有 OnScanEnded,请在 RegisterTo 里取消下面注释的那一行赋值。
|
|
|
|
|
+ private Action<bool> OnScanEnded;
|
|
|
|
|
+
|
|
|
private Action OnConnected;
|
|
private Action OnConnected;
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// 主动调用Disconnect()不会触发该委托
|
|
/// 主动调用Disconnect()不会触发该委托
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
private Action OnConnectionFailed;
|
|
private Action OnConnectionFailed;
|
|
|
- private static Action<string, byte[]> OnCharacteristicChanged;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// 注册window对象
|
|
/// 注册window对象
|
|
@@ -73,17 +153,17 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
/// <param name="bluetoothWindows">关联的BluetoothWindows</param>
|
|
/// <param name="bluetoothWindows">关联的BluetoothWindows</param>
|
|
|
/// <param name="logTip">提示的log标签</param>
|
|
/// <param name="logTip">提示的log标签</param>
|
|
|
/// <returns></returns>
|
|
/// <returns></returns>
|
|
|
- public static BleWinHelper RegisterTo(GameObject o,BluetoothWindows bluetoothWindows,string logTip = "first")
|
|
|
|
|
|
|
+ public static BleWinHelper RegisterTo(GameObject o, BluetoothWindows bluetoothWindows, string logTip = "first")
|
|
|
{
|
|
{
|
|
|
//if (_Instance)
|
|
//if (_Instance)
|
|
|
//{
|
|
//{
|
|
|
// Error("Register fail, because only one can be registered.");
|
|
// Error("Register fail, because only one can be registered.");
|
|
|
// return null;
|
|
// return null;
|
|
|
//}
|
|
//}
|
|
|
-
|
|
|
|
|
- GameObject obj = new GameObject("BleWinHelper"+ logTip);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ GameObject obj = new GameObject("BleWinHelper" + logTip);
|
|
|
obj.transform.SetParent(o.transform);
|
|
obj.transform.SetParent(o.transform);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
BleWinHelper bleWinHelper = obj.AddComponent<BleWinHelper>();
|
|
BleWinHelper bleWinHelper = obj.AddComponent<BleWinHelper>();
|
|
|
//日志名字
|
|
//日志名字
|
|
|
bleWinHelper.LogTag = "BleWinHelper-" + logTip + ": ";
|
|
bleWinHelper.LogTag = "BleWinHelper-" + logTip + ": ";
|
|
@@ -93,12 +173,13 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
bluetoothWindows.WriteByte = bleWinHelper.WriteByte;
|
|
bluetoothWindows.WriteByte = bleWinHelper.WriteByte;
|
|
|
bleWinHelper.OnConnected = () => bluetoothWindows.OnConnected?.Invoke();
|
|
bleWinHelper.OnConnected = () => bluetoothWindows.OnConnected?.Invoke();
|
|
|
bleWinHelper.OnConnectionFailed = () => bluetoothWindows.OnConnectionFailed?.Invoke();
|
|
bleWinHelper.OnConnectionFailed = () => bluetoothWindows.OnConnectionFailed?.Invoke();
|
|
|
- //多个定义共用一个OnCharacteristicChanged
|
|
|
|
|
- OnCharacteristicChanged += (deviceID,bytes) => {
|
|
|
|
|
- if (deviceID == bleWinHelper.selectedDeviceId) {
|
|
|
|
|
-
|
|
|
|
|
|
|
+ bleWinHelper.OnScanEnded = (bool bSelectedDeviceId) => bluetoothWindows.OnScanEnded?.Invoke(bSelectedDeviceId);
|
|
|
|
|
+ BleWinrtDllSharedInbox.PacketReceived += (deviceID, bytes) =>
|
|
|
|
|
+ {
|
|
|
|
|
+ if (deviceID == bleWinHelper.selectedDeviceId && bleWinHelper.isSubscribed)
|
|
|
|
|
+ bleWinHelper._receiveDataTime = Time.realtimeSinceStartup;
|
|
|
|
|
+ if (deviceID == bleWinHelper.selectedDeviceId)
|
|
|
bluetoothWindows.OnCharacteristicChanged?.Invoke(deviceID, bytes);
|
|
bluetoothWindows.OnCharacteristicChanged?.Invoke(deviceID, bytes);
|
|
|
- }
|
|
|
|
|
};
|
|
};
|
|
|
//bleWinHelper.bDebug = true;
|
|
//bleWinHelper.bDebug = true;
|
|
|
return bleWinHelper;
|
|
return bleWinHelper;
|
|
@@ -119,109 +200,97 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
|
|
|
|
|
void Awake()
|
|
void Awake()
|
|
|
{
|
|
{
|
|
|
- // _Instance = this;
|
|
|
|
|
|
|
+ // _Instance = this;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
void OnDestroy()
|
|
|
{
|
|
{
|
|
|
- // if (_Instance == this) _Instance = null;
|
|
|
|
|
|
|
+ // if (_Instance == this) _Instance = null;
|
|
|
BleApi.Quit();
|
|
BleApi.Quit();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void Update()
|
|
void Update()
|
|
|
{
|
|
{
|
|
|
|
|
+ BleWinrtDllSharedInbox.VerboseLogPollDispatch = logBleDllInboxVerbose;
|
|
|
|
|
+ BleWinrtDllSharedInbox.LogEachPacket = logBleDllInboxEachPacket;
|
|
|
|
|
+ BleWinrtDllSharedInbox.PumpOncePerUnityFrame();
|
|
|
|
|
+
|
|
|
BleApi.ScanStatus status;
|
|
BleApi.ScanStatus status;
|
|
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
|
|
+ // 设备扫描阶段(DLL:StartDeviceScan → PollDevice 循环 → Stop / FINISHED)
|
|
|
|
|
+ // 数据流概览:
|
|
|
|
|
+ // ① DLL 侧(见 bleDllScanMode)可先做广播 0xFFF0 粗筛;② 本脚本做名称关键字 + 白名单精筛;
|
|
|
|
|
+ // ③ 绑定 MAC 策略;④ 可选「存档 MAC 快速命中」;⑤ 其余设备进入排序候选表,扫描结束后再 TrySelect。
|
|
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
if (isScanningDevices)
|
|
if (isScanningDevices)
|
|
|
{
|
|
{
|
|
|
|
|
+ // 每次 Update 尽可能排空 DLL 队列:非阻塞 PollDevice,直到无 AVAILABLE 为止。
|
|
|
BleApi.DeviceUpdate res = new BleApi.DeviceUpdate();
|
|
BleApi.DeviceUpdate res = new BleApi.DeviceUpdate();
|
|
|
do
|
|
do
|
|
|
{
|
|
{
|
|
|
|
|
+ // block=false:无数据立即返回,避免卡死主线程。
|
|
|
status = BleApi.PollDevice(ref res, false);
|
|
status = BleApi.PollDevice(ref res, false);
|
|
|
if (status == BleApi.ScanStatus.AVAILABLE)
|
|
if (status == BleApi.ScanStatus.AVAILABLE)
|
|
|
{
|
|
{
|
|
|
- if (!deviceList.ContainsKey(res.id))
|
|
|
|
|
- deviceList[res.id] = new Dictionary<string, string>() {
|
|
|
|
|
- { "name", "" },
|
|
|
|
|
- { "isConnectable", "False" }
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ // 已在前面某次 Poll 中选定了设备(快速重连或 FINISHED 前逻辑):跳过后续 AVAILABLE,等 FINISHED。
|
|
|
|
|
+ if (selectedDeviceId != null)
|
|
|
|
|
+ continue;
|
|
|
|
|
+
|
|
|
|
|
+ // --- 合并 WinRT 推送的增量字段到 deviceList(按 deviceId 唯一键)---
|
|
|
|
|
+ EnsureDeviceListEntry(res.id);
|
|
|
if (res.nameUpdated)
|
|
if (res.nameUpdated)
|
|
|
deviceList[res.id]["name"] = res.name;
|
|
deviceList[res.id]["name"] = res.name;
|
|
|
if (res.isConnectableUpdated)
|
|
if (res.isConnectableUpdated)
|
|
|
deviceList[res.id]["isConnectable"] = res.isConnectable.ToString();
|
|
deviceList[res.id]["isConnectable"] = res.isConnectable.ToString();
|
|
|
|
|
+ if (res.rssiUpdated)
|
|
|
|
|
+ {
|
|
|
|
|
+ deviceList[res.id]["rssi"] = res.rssiDbm.ToString();
|
|
|
|
|
+ deviceList[res.id]["rssiValid"] = "True";
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 获取当前设备信息
|
|
|
|
|
string deviceName = deviceList[res.id]["name"];
|
|
string deviceName = deviceList[res.id]["name"];
|
|
|
|
|
+ // 当前帧 isConnectable 或与历史缓存「或」:避免只收到旧缓存时误判不可连。
|
|
|
bool isConnectable = res.isConnectable || deviceList[res.id]["isConnectable"] == "True";
|
|
bool isConnectable = res.isConnectable || deviceList[res.id]["isConnectable"] == "True";
|
|
|
|
|
|
|
|
- var type = (AimDeviceType)AimHandler.ins.aimDeviceInfo.type;
|
|
|
|
|
- //Debug.Log("type:" + type);
|
|
|
|
|
- if (type == AimDeviceType.HOUYIPRO)
|
|
|
|
|
- {
|
|
|
|
|
- TrySelectDevice(targetDeviceNameHOUYIPro, deviceName, isConnectable, "HOUYIPro", res.id);
|
|
|
|
|
- }
|
|
|
|
|
- else if (type == AimDeviceType.ARTEMISPRO)
|
|
|
|
|
- {
|
|
|
|
|
- TrySelectDevice(targetDeviceName, deviceName, isConnectable, "ARTEMISPRO", res.id);
|
|
|
|
|
- }
|
|
|
|
|
- else if (type == AimDeviceType.Gun)
|
|
|
|
|
- {
|
|
|
|
|
- //Debug.Log(targetDeviceNameGun + "-----:" + deviceName);
|
|
|
|
|
- TrySelectDevice(targetDeviceNameGun, deviceName, isConnectable, "Pistol", res.id);
|
|
|
|
|
- }
|
|
|
|
|
- else if (type == AimDeviceType.PistolM17)
|
|
|
|
|
|
|
+ // --- 分支 A:有存档 MAC 且开启快速重连时,优先只按 MAC 命中(跳过名称精筛与候选表)---
|
|
|
|
|
+ if (TryFastSelectBySavedBondMac(res.id, isConnectable))
|
|
|
|
|
+ continue;
|
|
|
|
|
+
|
|
|
|
|
+ // --- 分支 B:名称精筛(关键字 + 可选旧名白名单)+ 绑定 MAC 一致性 ---
|
|
|
|
|
+ // PickFineFilterPipeForCurrentType:按 Aim 类型取 Inspector 管道串,供白名单全名匹配。
|
|
|
|
|
+ string finePipe = PickFineFilterPipeForCurrentType((AimDeviceType)AimHandler.ins.aimDeviceInfo.type);
|
|
|
|
|
+ bool qualifies = DeviceNamePassesFineFilter(deviceName, finePipe)
|
|
|
|
|
+ && WinRtDeviceIdMatchesSavedBondMac(res.id);
|
|
|
|
|
+ // 可选:逐条 Poll 详细日志(易刷屏);仅当 logBleVerbosePollStream 开启时走 ShouldEmitBleListStyleLog。
|
|
|
|
|
+ if (logBleVerbosePollStream && ShouldEmitBleListStyleLog(logAllBleAdvertisements, qualifies, res, deviceName))
|
|
|
|
|
+ LogBleVerbosePollStreamLine(qualifies, deviceName, res.id, res);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 分支 C:通过精筛且有名 → 写入「目标候选」字典(最多 MaxScanTargetCandidates),满则请求 DLL 停扫 ---
|
|
|
|
|
+ if (qualifies && !string.IsNullOrWhiteSpace(deviceName))
|
|
|
{
|
|
{
|
|
|
- TrySelectDevice(targetDeviceNameGun_M17, deviceName, isConnectable, "PistolM17", res.id);
|
|
|
|
|
|
|
+ // MaybeUpsertScanCandidate 返回 true 表示本轮是「首次」加入该 deviceId,用于只打一次「新进」日志。
|
|
|
|
|
+ if (MaybeUpsertScanCandidate(res.id, deviceName.Trim(), isConnectable, res) && logBleScanListEvents)
|
|
|
|
|
+ LogBleScanNewCandidateLine(deviceName, res.id, res);
|
|
|
}
|
|
}
|
|
|
- else if (type == AimDeviceType.RifleM416)
|
|
|
|
|
- {
|
|
|
|
|
- TrySelectDevice(targetDeviceNameGun_M416, deviceName, isConnectable, "RifleM416", res.id);
|
|
|
|
|
- }
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- //其余的九轴连接
|
|
|
|
|
- TrySelectDevice(targetDeviceNameAxis, deviceName, isConnectable, deviceName, res.id);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- //if (AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.HOUYIPRO)
|
|
|
|
|
- //{ //需要判断是否是红外弓箭
|
|
|
|
|
- // if (targetDeviceNameHOUYIPro.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True")
|
|
|
|
|
- // {
|
|
|
|
|
- // selectedDeviceId = res.id;
|
|
|
|
|
- // StopDeviceScan();
|
|
|
|
|
- // }
|
|
|
|
|
- //}
|
|
|
|
|
- //else if (AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.ARTEMISPRO)
|
|
|
|
|
- //{ //需要判断是否是ARTEMISPRO弓箭
|
|
|
|
|
- // if (targetDeviceName.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True")
|
|
|
|
|
- // {
|
|
|
|
|
- // selectedDeviceId = res.id;
|
|
|
|
|
- // StopDeviceScan();
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- //}
|
|
|
|
|
- //else if (AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.Gun|| AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.PistolM17|| AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.RifleM416)
|
|
|
|
|
- //{
|
|
|
|
|
- // //需要判断是否是枪
|
|
|
|
|
- // if (targetDeviceNameGun.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True")
|
|
|
|
|
- // {
|
|
|
|
|
- // selectedDeviceId = res.id;
|
|
|
|
|
- // StopDeviceScan();
|
|
|
|
|
- // }
|
|
|
|
|
- //}
|
|
|
|
|
- //else
|
|
|
|
|
- //{ //其余的九轴连接
|
|
|
|
|
- // if (targetDeviceName.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True")
|
|
|
|
|
- // {
|
|
|
|
|
- // selectedDeviceId = res.id;
|
|
|
|
|
- // StopDeviceScan();
|
|
|
|
|
- // }
|
|
|
|
|
- //}
|
|
|
|
|
}
|
|
}
|
|
|
else if (status == BleApi.ScanStatus.FINISHED)
|
|
else if (status == BleApi.ScanStatus.FINISHED)
|
|
|
{
|
|
{
|
|
|
- StartCoroutine(ScanServiceAndCharacteristicsToSubscribe());
|
|
|
|
|
|
|
+ // DLL 宣布扫描结束:关标志位,再统一做「候选排序 + TrySelect + GATT 协程」。
|
|
|
|
|
+ isScanningDevices = false;
|
|
|
|
|
+ CompleteScanPhaseAndSubscribe();
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
} while (status == BleApi.ScanStatus.AVAILABLE);
|
|
} while (status == BleApi.ScanStatus.AVAILABLE);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 主动停扫:候选已满或超过本阶段超时(有存档 MAC 时超时可能缩短为 fastReconnectScanMaxSeconds)---
|
|
|
|
|
+ // 仅 native Stop;isScanningDevices 仍为 true,直到随后某帧 Poll 到 FINISHED 再收尾。
|
|
|
|
|
+ if (isScanningDevices && selectedDeviceId == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ bool full = _scanCandidatesById.Count >= MaxScanTargetCandidates;
|
|
|
|
|
+ bool timeout = Time.realtimeSinceStartup - _deviceScanPhaseStartTime >= _scanPhaseTimeoutSeconds;
|
|
|
|
|
+ if (full || timeout)
|
|
|
|
|
+ BleApi.StopDeviceScan();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if (isScanningServices)
|
|
if (isScanningServices)
|
|
|
{
|
|
{
|
|
@@ -247,20 +316,8 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
status = BleApi.PollCharacteristic(out res, false);
|
|
status = BleApi.PollCharacteristic(out res, false);
|
|
|
if (status == BleApi.ScanStatus.AVAILABLE)
|
|
if (status == BleApi.ScanStatus.AVAILABLE)
|
|
|
{
|
|
{
|
|
|
- Log("res.userDescription:"+ res.userDescription+ ",res.uuid:" + res.uuid);
|
|
|
|
|
- if (res.userDescription == "no description available" || //旧设备
|
|
|
|
|
- res.userDescription == "SPP Write Channel" || //新设备
|
|
|
|
|
- res.userDescription == "SPP Read Channel")
|
|
|
|
|
- {
|
|
|
|
|
-
|
|
|
|
|
- characteristicsList.Add(res.uuid);
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- //跟着之前代码
|
|
|
|
|
- characteristicsList.Add(res.userDescription);
|
|
|
|
|
- }
|
|
|
|
|
- //string name = res.userDescription != "no description available" ? res.userDescription : res.uuid;
|
|
|
|
|
- //characteristicsList.Add(name);
|
|
|
|
|
|
|
+ Log("res.userDescription:" + res.userDescription + ",res.uuid:" + res.uuid);
|
|
|
|
|
+ characteristicsList.Add(res.uuid);
|
|
|
}
|
|
}
|
|
|
else if (status == BleApi.ScanStatus.FINISHED)
|
|
else if (status == BleApi.ScanStatus.FINISHED)
|
|
|
{
|
|
{
|
|
@@ -268,20 +325,6 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
}
|
|
}
|
|
|
} while (status == BleApi.ScanStatus.AVAILABLE);
|
|
} while (status == BleApi.ScanStatus.AVAILABLE);
|
|
|
}
|
|
}
|
|
|
- if (isSubscribed)
|
|
|
|
|
- {
|
|
|
|
|
- BleApi.BLEData res;
|
|
|
|
|
- while (BleApi.PollData(out res, false))
|
|
|
|
|
- {
|
|
|
|
|
- //string text = BitConverter.ToString(res.buf, 0, res.size);
|
|
|
|
|
- // string text = Encoding.ASCII.GetString(res.buf, 0, res.size);
|
|
|
|
|
- byte[] bytes = new byte[res.size];
|
|
|
|
|
- Array.Copy(res.buf, bytes, res.size);
|
|
|
|
|
- _receiveDataTime = Time.realtimeSinceStartup;
|
|
|
|
|
- OnCharacteristicChanged?.Invoke(res.deviceId, bytes);
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
{
|
|
{
|
|
|
BleApi.ErrorMessage res;
|
|
BleApi.ErrorMessage res;
|
|
|
BleApi.GetError(out res);
|
|
BleApi.GetError(out res);
|
|
@@ -297,23 +340,322 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// WinRT 设备 Id 末尾一般为真实 BLE MAC,例如 ...-d3:5c:89:57:68:de
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ static string ExtractMacFromDeviceId(string deviceId)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(deviceId))
|
|
|
|
|
+ return null;
|
|
|
|
|
+ int lastDash = deviceId.LastIndexOf('-');
|
|
|
|
|
+ if (lastDash >= 0 && lastDash < deviceId.Length - 1)
|
|
|
|
|
+ return deviceId.Substring(lastDash + 1);
|
|
|
|
|
+ return deviceId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>无广播名时不打空引号,减少无效日志。</summary>
|
|
|
|
|
+ static string FormatScanDeviceNameForLog(string name) =>
|
|
|
|
|
+ string.IsNullOrWhiteSpace(name) ? "无广播名" : name.Trim();
|
|
|
|
|
+
|
|
|
|
|
+ string BleListMetaDescription()
|
|
|
|
|
+ {
|
|
|
|
|
+ string keyDisp = string.IsNullOrEmpty(bleFilterNameKey) ? "(无,仅白名单)" : bleFilterNameKey;
|
|
|
|
|
+ string caseDisp = bleFilterNameKeyCaseSensitive ? "敏感" : "不敏感";
|
|
|
|
|
+ return allowLegacyDeviceNameWhitelist
|
|
|
|
|
+ ? $"关键字「{keyDisp}」({caseDisp})或旧名"
|
|
|
|
|
+ : $"仅关键字「{keyDisp}」({caseDisp})";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static string FormatRssiForLog(BleApi.DeviceUpdate res) =>
|
|
|
|
|
+ res.rssiUpdated ? $"{res.rssiDbm} dBm" : "—";
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 逐条 Poll 的详细日志(需 <see cref="logBleVerbosePollStream"/>)。与 <see cref="LogBleScanNewCandidateLine"/> 不同:后者仅在首次进入候选表时打印。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ void LogBleVerbosePollStreamLine(bool qualifies, string deviceName, string deviceId, BleApi.DeviceUpdate res)
|
|
|
|
|
+ {
|
|
|
|
|
+ string macLog = ExtractMacFromDeviceId(deviceId) ?? deviceId;
|
|
|
|
|
+ string rssiLog = FormatRssiForLog(res);
|
|
|
|
|
+ string nameDisp = FormatScanDeviceNameForLog(deviceName);
|
|
|
|
|
+ string keyDisp = string.IsNullOrEmpty(bleFilterNameKey) ? "(无,仅白名单)" : bleFilterNameKey;
|
|
|
|
|
+ string caseDisp = bleFilterNameKeyCaseSensitive ? "敏感" : "不敏感";
|
|
|
|
|
+ if (logAllBleAdvertisements)
|
|
|
|
|
+ Log($"BLE | 粗:广播0x{BleAdvertisedServiceUuid16:X4}(DLL模式) | 精:{(qualifies ? "候选" : "否")}{(allowLegacyDeviceNameWhitelist ? "+白名单" : $"/仅关键字「{keyDisp}」({caseDisp})")} | 名:{nameDisp} | MAC:{macLog} | RSSI:{rssiLog}");
|
|
|
|
|
+ else
|
|
|
|
|
+ Log($"BLE列表 | 0x{BleAdvertisedServiceUuid16:X4} | {BleListMetaDescription()} | 名:{nameDisp} | MAC:{macLog} | RSSI:{rssiLog}");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>某 deviceId 首次进入 <see cref="_scanCandidatesById"/> 时打印一行,便于对照「名称 + MAC + RSSI」。</summary>
|
|
|
|
|
+ void LogBleScanNewCandidateLine(string deviceName, string deviceId, BleApi.DeviceUpdate res)
|
|
|
|
|
+ {
|
|
|
|
|
+ string macLog = ExtractMacFromDeviceId(deviceId) ?? deviceId;
|
|
|
|
|
+ string rssiLog = FormatRssiForLog(res);
|
|
|
|
|
+ string nameDisp = FormatScanDeviceNameForLog(deviceName);
|
|
|
|
|
+ Log($"BLE列表·新进 | 0x{BleAdvertisedServiceUuid16:X4} | {BleListMetaDescription()} | 名:{nameDisp} | MAC:{macLog} | RSSI:{rssiLog}");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>在 <see cref="CompleteScanPhaseAndSubscribe"/> 内、排序后打印完整候选列表(与日志中的顺序一致)。</summary>
|
|
|
|
|
+ void LogBleScanFinalSummary()
|
|
|
|
|
+ {
|
|
|
|
|
+ var list = _scanCandidatesById.Values.ToList();
|
|
|
|
|
+ list.Sort(CompareScanCandidates);
|
|
|
|
|
+ Log($"BLE列表·结束 | 共 {list.Count} 台({LegacyDeviceNameSortLast} 置尾 / 同名 RSSI 序)");
|
|
|
|
|
+ for (int i = 0; i < list.Count; i++)
|
|
|
|
|
+ {
|
|
|
|
|
+ var c = list[i];
|
|
|
|
|
+ string mac = ExtractMacFromDeviceId(c.DeviceId) ?? c.DeviceId;
|
|
|
|
|
+ string rssi = c.RssiValid ? $"{c.RssiDbm} dBm" : "—";
|
|
|
|
|
+ Log($" [{i + 1}] 名:{FormatScanDeviceNameForLog(c.Name)} | MAC:{mac} | RSSI:{rssi} | 可连:{c.IsConnectable}");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>仅名称/可连有变化时必打;仅有 RSSI 且仍无广播名则不打,避免刷屏。</summary>
|
|
|
|
|
+ static bool ShouldLogScanLine(BleApi.DeviceUpdate res, string deviceNameCached)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!(res.nameUpdated || res.isConnectableUpdated || res.rssiUpdated))
|
|
|
|
|
+ return false;
|
|
|
|
|
+ if (res.rssiUpdated && !res.nameUpdated && !res.isConnectableUpdated && string.IsNullOrWhiteSpace(deviceNameCached))
|
|
|
|
|
+ return false;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 规范式列表:默认仅当「通过精筛」且名称或可连有更新时打印(不刷无广播名周边设备、不刷纯 RSSI)。
|
|
|
|
|
+ /// logAll=true 时退回详细模式(仍遵守 ShouldLogScanLine 对空名的 RSSI 抑制)。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ static bool ShouldEmitBleListStyleLog(bool logAll, bool passesFineFilter, BleApi.DeviceUpdate res, string deviceNameCached)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (logAll)
|
|
|
|
|
+ return ShouldLogScanLine(res, deviceNameCached);
|
|
|
|
|
+ if (!passesFineFilter)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ return res.nameUpdated || res.isConnectableUpdated;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>精筛:名称含 <see cref="bleFilterNameKey"/>(大小写由 <see cref="bleFilterNameKeyCaseSensitive"/>);若 <see cref="allowLegacyDeviceNameWhitelist"/> 则另允许管道串中全名(忽略大小写,旧设备)。</summary>
|
|
|
|
|
+ bool DeviceNamePassesFineFilter(string deviceName, string pipeSeparatedLegacyNames)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrWhiteSpace(deviceName))
|
|
|
|
|
+ return false;
|
|
|
|
|
+ string trimmed = deviceName.Trim();
|
|
|
|
|
+ var keyCmp = bleFilterNameKeyCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
|
|
|
|
+ if (!string.IsNullOrEmpty(bleFilterNameKey) && trimmed.IndexOf(bleFilterNameKey, keyCmp) >= 0)
|
|
|
|
|
+ return true;
|
|
|
|
|
+ if (!allowLegacyDeviceNameWhitelist)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ if (string.IsNullOrWhiteSpace(pipeSeparatedLegacyNames))
|
|
|
|
|
+ return false;
|
|
|
|
|
+ foreach (var part in pipeSeparatedLegacyNames.Split('|'))
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.Equals(part.Trim(), trimmed, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ string PickFineFilterPipeForCurrentType(AimDeviceType type)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (type == AimDeviceType.HOUYIPRO) return targetDeviceNameHOUYIPro;
|
|
|
|
|
+ if (type == AimDeviceType.ARTEMISPRO) return targetDeviceName;
|
|
|
|
|
+ if (type == AimDeviceType.Gun) return targetDeviceNameGun;
|
|
|
|
|
+ if (type == AimDeviceType.PistolM17) return targetDeviceNameGun_M17;
|
|
|
|
|
+ if (type == AimDeviceType.RifleM416) return targetDeviceNameGun_M416;
|
|
|
|
|
+ return targetDeviceNameAxis;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static string NormalizeMacHexDigits(string mac)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(mac)) return "";
|
|
|
|
|
+ return string.Concat(mac.Where(Uri.IsHexDigit)).ToUpperInvariant();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool TryGetSavedBondMacFromAim(out string mac)
|
|
|
|
|
+ {
|
|
|
|
|
+ mac = null;
|
|
|
|
|
+ if (AimHandler.ins == null || AimHandler.ins.aimDeviceInfo == null) return false;
|
|
|
|
|
+ var info = AimHandler.ins.aimDeviceInfo;
|
|
|
|
|
+ if (!info.bInitMac || string.IsNullOrWhiteSpace(info.mac)) return false;
|
|
|
|
|
+ mac = info.mac;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>无绑定 MAC 记录时不限制;有记录则 WinRT 设备尾段 MAC 须与存档一致(双向 Contains 兼容格式差异)。</summary>
|
|
|
|
|
+ bool WinRtDeviceIdMatchesSavedBondMac(string deviceId)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!restrictConnectToSavedMacWhenBonded) return true;
|
|
|
|
|
+ if (!TryGetSavedBondMacFromAim(out string saved)) return true;
|
|
|
|
|
+ string a = NormalizeMacHexDigits(ExtractMacFromDeviceId(deviceId));
|
|
|
|
|
+ string b = NormalizeMacHexDigits(saved);
|
|
|
|
|
+ if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return true;
|
|
|
|
|
+ return a.Contains(b) || b.Contains(a);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>存档 MAC 与 WinRT deviceId 尾段是否视为同一设备(双向 Contains);任一侧无法解析为十六进制则返回 false。</summary>
|
|
|
|
|
+ static bool SavedMacMatchesWinRtDeviceId(string savedMac, string deviceId)
|
|
|
|
|
+ {
|
|
|
|
|
+ string a = NormalizeMacHexDigits(ExtractMacFromDeviceId(deviceId));
|
|
|
|
|
+ string b = NormalizeMacHexDigits(savedMac);
|
|
|
|
|
+ if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false;
|
|
|
|
|
+ return a.Contains(b) || b.Contains(a);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 有绑定 MAC 且 <see cref="enableFastReconnectBySavedMac"/>:扫描中一旦 WinRT deviceId 尾段与存档 MAC 匹配且可连接,
|
|
|
|
|
+ /// 立即设置 <see cref="selectedDeviceId"/> 并 <see cref="BleApi.StopDeviceScan"/>(不经 <see cref="DeviceNamePassesFineFilter"/> / 候选表)。
|
|
|
|
|
+ /// 随后仍等待 <see cref="BleApi.ScanStatus.FINISHED"/>,由 <see cref="CompleteScanPhaseAndSubscribe"/> 进入 GATT。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ bool TryFastSelectBySavedBondMac(string deviceId, bool isConnectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!enableFastReconnectBySavedMac || !isConnectable)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ if (!TryGetSavedBondMacFromAim(out string saved))
|
|
|
|
|
+ return false;
|
|
|
|
|
+ if (!SavedMacMatchesWinRtDeviceId(saved, deviceId))
|
|
|
|
|
+ return false;
|
|
|
|
|
+
|
|
|
|
|
+ selectedDeviceId = deviceId;
|
|
|
|
|
+ BleApi.StopDeviceScan();
|
|
|
|
|
+ if (logBleScanListEvents || bDebug)
|
|
|
|
|
+ Log($"BLE·快速重连 | 按存档 MAC 命中({NormalizeMacHexDigits(saved)}),跳过名称筛选;等待 FINISHED 后建链。");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ float ResolveScanPhaseTimeoutSeconds()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (enableFastReconnectBySavedMac && TryGetSavedBondMacFromAim(out _))
|
|
|
|
|
+ return Mathf.Min(DeviceScanPhaseDurationSeconds, Mathf.Max(0.5f, fastReconnectScanMaxSeconds));
|
|
|
|
|
+ return DeviceScanPhaseDurationSeconds;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private bool TrySelectDevice(string filterNames, string deviceName, bool isConnectable, string typeName, string deviceId)
|
|
private bool TrySelectDevice(string filterNames, string deviceName, bool isConnectable, string typeName, string deviceId)
|
|
|
{
|
|
{
|
|
|
- //Log($"匹配设备 [{isConnectable}] :{string.IsNullOrWhiteSpace(filterNames)}");
|
|
|
|
|
- if (!isConnectable || string.IsNullOrWhiteSpace(filterNames)) return false;
|
|
|
|
|
|
|
+ if (!isConnectable || string.IsNullOrWhiteSpace(deviceName)) return false;
|
|
|
|
|
+ if (!DeviceNamePassesFineFilter(deviceName, filterNames)) return false;
|
|
|
|
|
+ if (!WinRtDeviceIdMatchesSavedBondMac(deviceId)) return false;
|
|
|
|
|
+
|
|
|
|
|
+ selectedDeviceId = deviceId;
|
|
|
|
|
+ StopDeviceScan();
|
|
|
|
|
+ Log($"匹配设备 [{typeName}] {FormatScanDeviceNameForLog(deviceName)} | MAC:{ExtractMacFromDeviceId(deviceId) ?? deviceId}");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void EnsureDeviceListEntry(string id)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!deviceList.ContainsKey(id))
|
|
|
|
|
+ {
|
|
|
|
|
+ deviceList[id] = new Dictionary<string, string> {
|
|
|
|
|
+ { "name", "" },
|
|
|
|
|
+ { "isConnectable", "False" },
|
|
|
|
|
+ { "rssi", "" },
|
|
|
|
|
+ { "rssiValid", "False" }
|
|
|
|
|
+ };
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var d = deviceList[id];
|
|
|
|
|
+ if (!d.ContainsKey("rssi")) d["rssi"] = "";
|
|
|
|
|
+ if (!d.ContainsKey("rssiValid")) d["rssiValid"] = "False";
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- string[] filterArray = filterNames.Split('|'); // 支持多个名字
|
|
|
|
|
- foreach (var f in filterArray)
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 将通过精筛的设备写入 <see cref="_scanCandidatesById"/>(并镜像核心字段到 <see cref="deviceList"/> 的维护过程在 Poll 上半段)。
|
|
|
|
|
+ /// 同一 deviceId 重复出现:更新名称 / 可连 / RSSI,不增加计数。
|
|
|
|
|
+ /// 新建条目若使总数达到 <see cref="MaxScanTargetCandidates"/>:调用 <see cref="BleApi.StopDeviceScan"/> 促使 DLL 结束扫描。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <returns>是否为本轮扫描中<strong>首次</strong>加入该 deviceId(用于只打一次 <see cref="LogBleScanNewCandidateLine"/>)。</returns>
|
|
|
|
|
+ bool MaybeUpsertScanCandidate(string deviceId, string nameTrimmed, bool isConnectable, BleApi.DeviceUpdate res)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_scanCandidatesById.TryGetValue(deviceId, out var existing))
|
|
|
{
|
|
{
|
|
|
- if (string.Equals(f.Trim(), deviceName.Trim(), StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
+ existing.Name = nameTrimmed;
|
|
|
|
|
+ existing.IsConnectable = isConnectable || existing.IsConnectable;
|
|
|
|
|
+ if (res.rssiUpdated)
|
|
|
{
|
|
{
|
|
|
- selectedDeviceId = deviceId;
|
|
|
|
|
- StopDeviceScan();
|
|
|
|
|
- Log($"匹配设备 [{typeName}] :{deviceName}");
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ existing.RssiDbm = res.rssiDbm;
|
|
|
|
|
+ existing.RssiValid = true;
|
|
|
}
|
|
}
|
|
|
|
|
+ _scanCandidatesById[deviceId] = existing;
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
- return false;
|
|
|
|
|
|
|
+ if (_scanCandidatesById.Count >= MaxScanTargetCandidates)
|
|
|
|
|
+ return false;
|
|
|
|
|
+ _scanCandidatesById[deviceId] = new ScanCandidateEntry
|
|
|
|
|
+ {
|
|
|
|
|
+ DeviceId = deviceId,
|
|
|
|
|
+ Name = nameTrimmed,
|
|
|
|
|
+ IsConnectable = isConnectable,
|
|
|
|
|
+ RssiDbm = res.rssiDbm,
|
|
|
|
|
+ RssiValid = res.rssiUpdated
|
|
|
|
|
+ };
|
|
|
|
|
+ if (_scanCandidatesById.Count >= MaxScanTargetCandidates)
|
|
|
|
|
+ BleApi.StopDeviceScan();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>排序:名称 Ordinal;<see cref="LegacyDeviceNameSortLast"/> 置尾;同名按 RSSI 强到弱(dBm 越大越前)。</summary>
|
|
|
|
|
+ static int CompareScanCandidates(ScanCandidateEntry a, ScanCandidateEntry b)
|
|
|
|
|
+ {
|
|
|
|
|
+ bool aBbow = string.Equals(a.Name?.Trim(), LegacyDeviceNameSortLast, StringComparison.Ordinal);
|
|
|
|
|
+ bool bBbow = string.Equals(b.Name?.Trim(), LegacyDeviceNameSortLast, StringComparison.Ordinal);
|
|
|
|
|
+ if (aBbow != bBbow)
|
|
|
|
|
+ return aBbow ? 1 : -1;
|
|
|
|
|
+
|
|
|
|
|
+ int nameCmp = string.Compare(a.Name, b.Name, StringComparison.Ordinal);
|
|
|
|
|
+ if (nameCmp != 0)
|
|
|
|
|
+ return nameCmp;
|
|
|
|
|
+
|
|
|
|
|
+ int ra = a.RssiValid ? a.RssiDbm : int.MinValue;
|
|
|
|
|
+ int rb = b.RssiValid ? b.RssiDbm : int.MinValue;
|
|
|
|
|
+ return rb.CompareTo(ra);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ static string TypeNameForLog(AimDeviceType type)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (type == AimDeviceType.HOUYIPRO) return "HOUYIPro";
|
|
|
|
|
+ if (type == AimDeviceType.ARTEMISPRO) return "ARTEMISPRO";
|
|
|
|
|
+ if (type == AimDeviceType.Gun) return "Pistol";
|
|
|
|
|
+ if (type == AimDeviceType.PistolM17) return "PistolM17";
|
|
|
|
|
+ if (type == AimDeviceType.RifleM416) return "RifleM416";
|
|
|
|
|
+ return "Axis";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 扫描已结束且未走快速重连选中:将 <see cref="_scanCandidatesById"/> 按 <see cref="CompareScanCandidates"/> 排序后,
|
|
|
|
|
+ /// 从前往后调用 <see cref="TrySelectDevice"/>(结合当前 Aim 类型白名单管道),直到第一个可连且仍过精筛的设备。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ void TryPickDeviceFromSortedScanCandidates()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (AimHandler.ins == null || AimHandler.ins.aimDeviceInfo == null)
|
|
|
|
|
+ return;
|
|
|
|
|
+ var type = (AimDeviceType)AimHandler.ins.aimDeviceInfo.type;
|
|
|
|
|
+ string pipe = PickFineFilterPipeForCurrentType(type);
|
|
|
|
|
+ string label = TypeNameForLog(type);
|
|
|
|
|
+ var list = _scanCandidatesById.Values.ToList();
|
|
|
|
|
+ list.Sort(CompareScanCandidates);
|
|
|
|
|
+ if (list.Count > 0)
|
|
|
|
|
+ Log($"扫描结束:从候选列表按序尝试连接 [{label}]({LegacyDeviceNameSortLast} 置尾 / 同名 RSSI)。");
|
|
|
|
|
+ foreach (var c in list)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (TrySelectDevice(pipe, c.Name, c.IsConnectable, label, c.DeviceId))
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// DLL 返回 <see cref="BleApi.ScanStatus.FINISHED"/> 时调用(且仅处理一次,见 <see cref="_scanPhaseCompletionScheduled"/>)。
|
|
|
|
|
+ /// 顺序:可选打印候选总表 → 若尚无 <see cref="selectedDeviceId"/> 则从排序列表挑选 → 启动 <see cref="ScanServiceAndCharacteristicsToSubscribe"/>(GATT 发现与订阅)→ 回调 <see cref="OnScanEnded"/>。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ void CompleteScanPhaseAndSubscribe()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (_scanPhaseCompletionScheduled)
|
|
|
|
|
+ return;
|
|
|
|
|
+ _scanPhaseCompletionScheduled = true;
|
|
|
|
|
+ if (logBleScanListEvents)
|
|
|
|
|
+ LogBleScanFinalSummary();
|
|
|
|
|
+ if (selectedDeviceId == null)
|
|
|
|
|
+ TryPickDeviceFromSortedScanCandidates();
|
|
|
|
|
+ StartCoroutine(ScanServiceAndCharacteristicsToSubscribe());
|
|
|
|
|
+ Log(" ScanStatus FINISHED !");
|
|
|
|
|
+ OnScanEnded?.Invoke(selectedDeviceId != null);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void LateUpdate()
|
|
void LateUpdate()
|
|
@@ -327,9 +669,13 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
{
|
|
{
|
|
|
HandleConnectFail();
|
|
HandleConnectFail();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 入口:重置连接相关状态 → <see cref="BleApi.SetBleScanMode"/> / <see cref="BleApi.StartDeviceScan"/> → 记录扫描阶段起点与超时秒数。
|
|
|
|
|
+ /// 后续逻辑均在 <see cref="Update"/> 的 Poll 循环与 <see cref="CompleteScanPhaseAndSubscribe"/> 中完成。
|
|
|
|
|
+ /// </summary>
|
|
|
private bool Connect()
|
|
private bool Connect()
|
|
|
{
|
|
{
|
|
|
if (isConnectLocking || isScanningDevices)
|
|
if (isConnectLocking || isScanningDevices)
|
|
@@ -337,10 +683,23 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
Warn("Connect Invalid, because is in connect.");
|
|
Warn("Connect Invalid, because is in connect.");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ // [合并-DLL Win11] 开始连接前重置状态,避免脏数据
|
|
|
|
|
+ ReinitAfterConnectFail();
|
|
|
|
|
+
|
|
|
|
|
+ if (bleDllScanMode == BleApi.BleScanMode.DeviceInformationOnly)
|
|
|
|
|
+ Warn("BleDllScanMode=DeviceInformationOnly:扫描期无法按广播 0xFFF0 过滤耳机/手环等,建议 Combined 或 AdvertisementWatcherFff0。");
|
|
|
|
|
+ BleApi.SetBleScanMode(bleDllScanMode);
|
|
|
BleApi.StartDeviceScan();
|
|
BleApi.StartDeviceScan();
|
|
|
isConnectLocking = true;
|
|
isConnectLocking = true;
|
|
|
isScanningDevices = true;
|
|
isScanningDevices = true;
|
|
|
- Log("Start Connect");
|
|
|
|
|
|
|
+ _deviceScanPhaseStartTime = Time.realtimeSinceStartup;
|
|
|
|
|
+ _scanPhaseTimeoutSeconds = ResolveScanPhaseTimeoutSeconds();
|
|
|
|
|
+ string keyLog = string.IsNullOrEmpty(bleFilterNameKey) ? $"(无子串,仅白名单)" : bleFilterNameKey;
|
|
|
|
|
+ string caseLog = bleFilterNameKeyCaseSensitive ? "区分大小写" : "不区分大小写";
|
|
|
|
|
+ if (enableFastReconnectBySavedMac && TryGetSavedBondMacFromAim(out string savedMac))
|
|
|
|
|
+ Log($"开始扫描:检测到存档 MAC({NormalizeMacHexDigits(savedMac)}),优先按 MAC 快速匹配(≤{_scanPhaseTimeoutSeconds:0.#}s),命中即连;否则再走名称列表。");
|
|
|
|
|
+ else
|
|
|
|
|
+ Log($"开始扫描:广播 0x{BleAdvertisedServiceUuid16:X4}(DLL 模式)+ 名称子串「{keyLog}」({caseLog})/ 旧名白名单;最长 {_scanPhaseTimeoutSeconds:0.#}s,最多 {MaxScanTargetCandidates} 台候选,结束后按序匹配。");
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -349,6 +708,7 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
if (!isScanningDevices) return;
|
|
if (!isScanningDevices) return;
|
|
|
BleApi.StopDeviceScan();
|
|
BleApi.StopDeviceScan();
|
|
|
isScanningDevices = false;
|
|
isScanningDevices = false;
|
|
|
|
|
+ Log($"Stop DeviceScan!");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private IEnumerator ScanServiceAndCharacteristicsToSubscribe()
|
|
private IEnumerator ScanServiceAndCharacteristicsToSubscribe()
|
|
@@ -358,6 +718,7 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
if (selectedDeviceId == null)
|
|
if (selectedDeviceId == null)
|
|
|
{
|
|
{
|
|
|
HandleConnectFail();
|
|
HandleConnectFail();
|
|
|
|
|
+ // [合并-DLL 冲突/未接入] DLL 版: smartBowHelper.InvokeOnBluetoothError(BluetoothError.Unknown, "选择设备 DeviceId 不存在!"); 本脚本无 SmartBowHelper 引用,请按需在上层处理
|
|
|
yield break;
|
|
yield break;
|
|
|
}
|
|
}
|
|
|
Log("SelectedDeviceId OK");
|
|
Log("SelectedDeviceId OK");
|
|
@@ -383,6 +744,7 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
if (!findTargetService)
|
|
if (!findTargetService)
|
|
|
{
|
|
{
|
|
|
HandleConnectFail();
|
|
HandleConnectFail();
|
|
|
|
|
+ // [合并-DLL 冲突/未接入] DLL 版: smartBowHelper.InvokeOnBluetoothError(BluetoothError.Unknown, "目标设备服务 service 不存在!");
|
|
|
yield break;
|
|
yield break;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -412,12 +774,16 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
if (!findTargetCharacteristicsNotify || !findTargetCharacteristicsWrite)
|
|
if (!findTargetCharacteristicsNotify || !findTargetCharacteristicsWrite)
|
|
|
{
|
|
{
|
|
|
HandleConnectFail();
|
|
HandleConnectFail();
|
|
|
|
|
+ // [合并-DLL 冲突/未接入] DLL 版: smartBowHelper.InvokeOnBluetoothError(BluetoothError.Unknown, "目标设备特征值 Characteristics 不存在!");
|
|
|
yield break;
|
|
yield break;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
BleApi.SubscribeCharacteristic(selectedDeviceId, targetService, targetCharacteristicsNotify, false);
|
|
BleApi.SubscribeCharacteristic(selectedDeviceId, targetService, targetCharacteristicsNotify, false);
|
|
|
isSubscribed = true;
|
|
isSubscribed = true;
|
|
|
isSubscribing = false;
|
|
isSubscribing = false;
|
|
|
|
|
+
|
|
|
|
|
+ // [合并-DLL Win11] 订阅成功后启动 DLL 推送线程(与 HandleConnectFail/Disconnect 中 Stop 成对)
|
|
|
|
|
+ BleApi.StartBlePushThread();
|
|
|
Log("SubscribeCharacteristicNotify OK");
|
|
Log("SubscribeCharacteristicNotify OK");
|
|
|
|
|
|
|
|
_connectedTime = Time.realtimeSinceStartup;
|
|
_connectedTime = Time.realtimeSinceStartup;
|
|
@@ -426,6 +792,9 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
|
|
|
|
|
private void HandleConnectFail()
|
|
private void HandleConnectFail()
|
|
|
{
|
|
{
|
|
|
|
|
+ // [合并-DLL Win11] 先停推送线程再断开
|
|
|
|
|
+ BleApi.StopBlePushThread();
|
|
|
|
|
+
|
|
|
if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
|
|
if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
|
|
|
bool isLockBefore = isConnectLocking;
|
|
bool isLockBefore = isConnectLocking;
|
|
|
ReinitAfterConnectFail();
|
|
ReinitAfterConnectFail();
|
|
@@ -441,6 +810,8 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
isSubscribed = false;
|
|
isSubscribed = false;
|
|
|
isSubscribing = false;
|
|
isSubscribing = false;
|
|
|
selectedDeviceId = null;
|
|
selectedDeviceId = null;
|
|
|
|
|
+ _scanPhaseCompletionScheduled = false;
|
|
|
|
|
+ _scanCandidatesById.Clear();
|
|
|
deviceList.Clear();
|
|
deviceList.Clear();
|
|
|
serviceList.Clear();
|
|
serviceList.Clear();
|
|
|
characteristicsList.Clear();
|
|
characteristicsList.Clear();
|
|
@@ -460,7 +831,7 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
data.buf[i] = payload[i];
|
|
data.buf[i] = payload[i];
|
|
|
// no error code available in non-blocking mode
|
|
// no error code available in non-blocking mode
|
|
|
BleApi.SendData(in data, false);
|
|
BleApi.SendData(in data, false);
|
|
|
- Log("Write(" + text + ")");
|
|
|
|
|
|
|
+ //Log("Write(" + text + ")");
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -498,6 +869,9 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
Warn("Disconnect Invalid, because is subscribing.");
|
|
Warn("Disconnect Invalid, because is subscribing.");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
+ // [合并-DLL Win11]
|
|
|
|
|
+ BleApi.StopBlePushThread();
|
|
|
|
|
+
|
|
|
if (isScanningDevices) StopDeviceScan();
|
|
if (isScanningDevices) StopDeviceScan();
|
|
|
if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
|
|
if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
|
|
|
ReinitAfterConnectFail();
|
|
ReinitAfterConnectFail();
|
|
@@ -505,5 +879,5 @@ namespace SmartBowSDK_BleWinHelper
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
}
|
|
}
|