|
@@ -0,0 +1,538 @@
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Collections;
|
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
+using System.Text;
|
|
|
|
|
+using UnityEngine;
|
|
|
|
|
+using System.Runtime.InteropServices;
|
|
|
|
|
+using System.Linq;
|
|
|
|
|
+
|
|
|
|
|
+namespace SmartBowSDK
|
|
|
|
|
+{
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Windows连接BluetoothLE
|
|
|
|
|
+ /// 我的扫描逻辑默认了读写特征都在同一服务下
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public class BleWinHelper : MonoBehaviour
|
|
|
|
|
+ {
|
|
|
|
|
+ public string LogTag = "[BleWinHelper]";
|
|
|
|
|
+ private void Log(string text)
|
|
|
|
|
+ {
|
|
|
|
|
+ //Debug.Log(LogTag + text);
|
|
|
|
|
+ SmartBowLogger.Log(this, LogTag + text);
|
|
|
|
|
+ }
|
|
|
|
|
+ private void Warn(string text)
|
|
|
|
|
+ {
|
|
|
|
|
+ //Debug.LogWarning(LogTag + text);
|
|
|
|
|
+ SmartBowLogger.Log(this, LogTag + text);
|
|
|
|
|
+ }
|
|
|
|
|
+ private void Error(string text)
|
|
|
|
|
+ {
|
|
|
|
|
+ //Debug.Log(LogTag + text);
|
|
|
|
|
+ SmartBowLogger.Log(this, LogTag + text);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //private string targetDeviceName = "Bbow_20210501 | ARTEMIS Pro | HOUYI Pro | Pistol | Pistol M9 | BGBox_202012";
|
|
|
|
|
+ //private string targetDeviceNameHOUYIPro = "HOUYI Pro";
|
|
|
|
|
+ //private string targetDeviceNameGun = "Pistol | Pistol M9 | Bbow_20210501";
|
|
|
|
|
+ private string targetService = "{0000fff0-0000-1000-8000-00805f9b34fb}";
|
|
|
|
|
+ private string targetCharacteristicsNotify = "{0000fff1-0000-1000-8000-00805f9b34fb}";
|
|
|
|
|
+ private string targetCharacteristicsWrite = "{0000fff2-0000-1000-8000-00805f9b34fb}";
|
|
|
|
|
+
|
|
|
|
|
+ private bool isConnectLocking = false;
|
|
|
|
|
+ private bool isScanningDevices = false;
|
|
|
|
|
+ private bool isScanningServices = false;
|
|
|
|
|
+ private bool isScanningCharacteristics = false;
|
|
|
|
|
+ private bool isSubscribed = false;
|
|
|
|
|
+ private bool isSubscribing = false;
|
|
|
|
|
+
|
|
|
|
|
+ private string lastError = null;
|
|
|
|
|
+ [SerializeField]
|
|
|
|
|
+ private string selectedDeviceId = null;
|
|
|
|
|
+
|
|
|
|
|
+ private Dictionary<string, Dictionary<string, string>> deviceList = new Dictionary<string, Dictionary<string, string>>();
|
|
|
|
|
+ private List<string> serviceList = new List<string>();
|
|
|
|
|
+ private List<string> characteristicsList = new List<string>();
|
|
|
|
|
+
|
|
|
|
|
+ private float _connectedTime = 0;
|
|
|
|
|
+ private float _receiveDataTime = 0;
|
|
|
|
|
+ private float _heartBeatInterval = 0;
|
|
|
|
|
+
|
|
|
|
|
+ private Action<bool> OnScanEnded;
|
|
|
|
|
+ private Action OnConnected;
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 主动调用Disconnect()不会触发该委托
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private Action OnConnectionFailed;
|
|
|
|
|
+ private static Action<string, byte[]> OnCharacteristicChanged;
|
|
|
|
|
+
|
|
|
|
|
+ public SmartBowHelper smartBowHelper;
|
|
|
|
|
+ public BluetoothAim_SDK bluetoothAim;
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 注册window对象
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="o">挂载对象</param>
|
|
|
|
|
+ /// <param name="bluetoothWindows">关联的BluetoothWindows</param>
|
|
|
|
|
+ /// <param name="logTip">提示的log标签</param>
|
|
|
|
|
+ /// <returns></returns>
|
|
|
|
|
+ public static BleWinHelper RegisterTo(SmartBowHelper _smartBowHelper, BluetoothWindows bluetoothWindows,string logTip = "first")
|
|
|
|
|
+ {
|
|
|
|
|
+ //if (_Instance)
|
|
|
|
|
+ //{
|
|
|
|
|
+ // Error("Register fail, because only one can be registered.");
|
|
|
|
|
+ // return null;
|
|
|
|
|
+ //}
|
|
|
|
|
+ string bleWinName = "BleWinHelper" + logTip;
|
|
|
|
|
+ GameObject obj = new GameObject(bleWinName);
|
|
|
|
|
+ obj.transform.SetParent(_smartBowHelper.transform);
|
|
|
|
|
+
|
|
|
|
|
+ BleWinHelper bleWinHelper = obj.AddComponent<BleWinHelper>();
|
|
|
|
|
+ bleWinHelper.smartBowHelper = _smartBowHelper;
|
|
|
|
|
+ bleWinHelper.bluetoothAim = _smartBowHelper.bluetoothAim;
|
|
|
|
|
+ //日志名字
|
|
|
|
|
+ bleWinHelper.LogTag = logTip + ": ";
|
|
|
|
|
+
|
|
|
|
|
+ bluetoothWindows.Connect = bleWinHelper.Connect;
|
|
|
|
|
+ bluetoothWindows.Disconnect = bleWinHelper.Disconnect;
|
|
|
|
|
+ bluetoothWindows.Write = bleWinHelper.Write;
|
|
|
|
|
+ bluetoothWindows.WriteByte = bleWinHelper.WriteByte;
|
|
|
|
|
+ // windos 通知 bluetoothAim
|
|
|
|
|
+ bleWinHelper.OnScanEnded = (bool bSelectedDeviceId) => bluetoothWindows.OnScanEnded?.Invoke(bSelectedDeviceId);
|
|
|
|
|
+ bleWinHelper.OnConnected = () => bluetoothWindows.OnConnected?.Invoke();
|
|
|
|
|
+ bleWinHelper.OnConnectionFailed = () => bluetoothWindows.OnConnectionFailed?.Invoke();
|
|
|
|
|
+ //多个定义共用一个OnCharacteristicChanged
|
|
|
|
|
+ OnCharacteristicChanged += (deviceID,bytes) => {
|
|
|
|
|
+ if (deviceID == bleWinHelper.selectedDeviceId) {
|
|
|
|
|
+
|
|
|
|
|
+ bluetoothWindows.OnCharacteristicChanged?.Invoke(deviceID, bytes);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return bleWinHelper;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 设置心跳检测
|
|
|
|
|
+ /// 1.每次收到的蓝牙数据都视为心跳
|
|
|
|
|
+ /// 2.帮助触发蓝牙断开监听
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="interval">心跳检测间隔</param>
|
|
|
|
|
+ public void SetHeartBeat(float interval)
|
|
|
|
|
+ {
|
|
|
|
|
+ _heartBeatInterval = interval;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //private static BleWinHelper _Instance;
|
|
|
|
|
+
|
|
|
|
|
+ void Awake()
|
|
|
|
|
+ {
|
|
|
|
|
+ // _Instance = this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void OnDestroy()
|
|
|
|
|
+ {
|
|
|
|
|
+ // if (_Instance == this) _Instance = null;
|
|
|
|
|
+ BleApi.Quit();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void Update()
|
|
|
|
|
+ {
|
|
|
|
|
+ BleApi.ScanStatus status;
|
|
|
|
|
+ if (isScanningDevices)
|
|
|
|
|
+ {
|
|
|
|
|
+ BleApi.DeviceUpdate res = new BleApi.DeviceUpdate();
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ status = BleApi.PollDevice(ref res, false);
|
|
|
|
|
+ if (status == BleApi.ScanStatus.AVAILABLE)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 如果存在 selectedDeviceId,不要重复
|
|
|
|
|
+ if (selectedDeviceId != null) {
|
|
|
|
|
+ //Log($"当前的 selectedDeviceId: {selectedDeviceId}");
|
|
|
|
|
+ continue; // 跳过后续 AVAILABLE 的处理,等待 FINISHED
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!deviceList.ContainsKey(res.id))
|
|
|
|
|
+ {
|
|
|
|
|
+ deviceList[res.id] = new Dictionary<string, string>() {
|
|
|
|
|
+ { "name", "" },
|
|
|
|
|
+ { "isConnectable", "False" }
|
|
|
|
|
+ };
|
|
|
|
|
+ //这里参考手机蓝牙模式
|
|
|
|
|
+ if (smartBowHelper.GetIsConnectName())
|
|
|
|
|
+ {
|
|
|
|
|
+ //后续匹配名字 可以是多个设备
|
|
|
|
|
+ Log($"发现设备{res.name},is fileters empty:{ string.IsNullOrEmpty(smartBowHelper.GetFilters())},name:{smartBowHelper.GetFilters()}");
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // 统一格式化:去掉非十六进制字符,转大写
|
|
|
|
|
+ string FormatMac(string mac) =>
|
|
|
|
|
+ string.Concat(mac.Where(c => Uri.IsHexDigit(c))).ToUpper();
|
|
|
|
|
+
|
|
|
|
|
+ string searchMac = FormatMac(ExtractMacFromDeviceId(res.id));
|
|
|
|
|
+ string connectMac = FormatMac(smartBowHelper.GetConnectMacStr());
|
|
|
|
|
+
|
|
|
|
|
+ Log($"发现设备 {searchMac},is connectMacStr:{connectMac },DeviceAddress:{searchMac}");
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (res.nameUpdated)
|
|
|
|
|
+ deviceList[res.id]["name"] = res.name;
|
|
|
|
|
+ if (res.isConnectableUpdated)
|
|
|
|
|
+ deviceList[res.id]["isConnectable"] = res.isConnectable.ToString();
|
|
|
|
|
+
|
|
|
|
|
+ //这里参考手机蓝牙模式
|
|
|
|
|
+ if (smartBowHelper.GetIsConnectName())
|
|
|
|
|
+ {
|
|
|
|
|
+ //后续匹配名字 可以是多个设备
|
|
|
|
|
+ //Log( $"发现设备{res.name},is fileters empty:{ string.IsNullOrEmpty(smartBowHelper.GetFilters())},name:{smartBowHelper.GetFilters()}");
|
|
|
|
|
+ string _filters = string.IsNullOrEmpty(smartBowHelper.GetFilters())
|
|
|
|
|
+ ? bluetoothAim.deviceConfig.deviceName
|
|
|
|
|
+ : smartBowHelper.GetFilters();
|
|
|
|
|
+
|
|
|
|
|
+ string[] filterArray = _filters.Split('|'); // 支持多个名字
|
|
|
|
|
+ foreach (var f in filterArray)
|
|
|
|
|
+ {
|
|
|
|
|
+ string trimmedFilter = f.Trim();
|
|
|
|
|
+ if (string.Equals(trimmedFilter, deviceList[res.id]["name"].Trim(), StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
+ && res.isConnectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ selectedDeviceId = res.id;
|
|
|
|
|
+ StopDeviceScan();
|
|
|
|
|
+ Log($"Name匹配设备 {trimmedFilter}");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ // 统一格式化:去掉非十六进制字符,转大写
|
|
|
|
|
+ string FormatMac(string mac) =>
|
|
|
|
|
+ string.Concat(mac.Where(c => Uri.IsHexDigit(c))).ToUpper();
|
|
|
|
|
+
|
|
|
|
|
+ string searchMac = FormatMac(ExtractMacFromDeviceId(res.id));
|
|
|
|
|
+ string connectMac = FormatMac(smartBowHelper.GetConnectMacStr());
|
|
|
|
|
+
|
|
|
|
|
+ //Log($"发现设备 {searchMac},is connectMacStr:{connectMac },DeviceAddress:{searchMac}");
|
|
|
|
|
+ //按mac地址匹配
|
|
|
|
|
+ if (!string.IsNullOrEmpty(connectMac) && connectMac.Contains(searchMac) && res.isConnectable)
|
|
|
|
|
+ {
|
|
|
|
|
+ selectedDeviceId = res.id;
|
|
|
|
|
+ StopDeviceScan();
|
|
|
|
|
+ Log($"MAC匹配设备 {searchMac}");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (status == BleApi.ScanStatus.FINISHED)
|
|
|
|
|
+ {
|
|
|
|
|
+ StartCoroutine(ScanServiceAndCharacteristicsToSubscribe());
|
|
|
|
|
+
|
|
|
|
|
+ Log($" ScanStatus FINISHED !");
|
|
|
|
|
+ //停止扫描后通知?
|
|
|
|
|
+ OnScanEnded?.Invoke(selectedDeviceId != null);
|
|
|
|
|
+ }
|
|
|
|
|
+ } while (status == BleApi.ScanStatus.AVAILABLE);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isScanningServices)
|
|
|
|
|
+ {
|
|
|
|
|
+ BleApi.Service res;
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ status = BleApi.PollService(out res, false);
|
|
|
|
|
+ if (status == BleApi.ScanStatus.AVAILABLE)
|
|
|
|
|
+ {
|
|
|
|
|
+ serviceList.Add(res.uuid);
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (status == BleApi.ScanStatus.FINISHED)
|
|
|
|
|
+ {
|
|
|
|
|
+ isScanningServices = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } while (status == BleApi.ScanStatus.AVAILABLE);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isScanningCharacteristics)
|
|
|
|
|
+ {
|
|
|
|
|
+ BleApi.Characteristic res;
|
|
|
|
|
+ do
|
|
|
|
|
+ {
|
|
|
|
|
+ status = BleApi.PollCharacteristic(out res, false);
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (status == BleApi.ScanStatus.FINISHED)
|
|
|
|
|
+ {
|
|
|
|
|
+ isScanningCharacteristics = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } 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.GetError(out res);
|
|
|
|
|
+ if (lastError != res.msg)
|
|
|
|
|
+ {
|
|
|
|
|
+ Error(res.msg);
|
|
|
|
|
+ lastError = res.msg;
|
|
|
|
|
+ //对应设备才断开
|
|
|
|
|
+ if (lastError.Contains("SendDataAsync") && lastError.Contains(selectedDeviceId) && isSubscribed)
|
|
|
|
|
+ {
|
|
|
|
|
+ HandleConnectFail();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void LateUpdate()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (
|
|
|
|
|
+ _heartBeatInterval > 0 &&
|
|
|
|
|
+ isSubscribed &&
|
|
|
|
|
+ Time.realtimeSinceStartup - _connectedTime >= _heartBeatInterval &&
|
|
|
|
|
+ Time.realtimeSinceStartup - _receiveDataTime >= _heartBeatInterval
|
|
|
|
|
+ )
|
|
|
|
|
+ {
|
|
|
|
|
+ HandleConnectFail();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool Connect()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (isConnectLocking || isScanningDevices)
|
|
|
|
|
+ {
|
|
|
|
|
+ Warn("Connect Invalid, because is in connect.");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ //开始连接时候,重置一下参数
|
|
|
|
|
+ ReinitAfterConnectFail();
|
|
|
|
|
+
|
|
|
|
|
+ BleApi.StartDeviceScan();
|
|
|
|
|
+ isConnectLocking = true;
|
|
|
|
|
+ isScanningDevices = true;
|
|
|
|
|
+ Log("Start Connect!");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void StopDeviceScan()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!isScanningDevices) return;
|
|
|
|
|
+ BleApi.StopDeviceScan();
|
|
|
|
|
+ isScanningDevices = false;
|
|
|
|
|
+ Log($"Stop DeviceScan!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private IEnumerator ScanServiceAndCharacteristicsToSubscribe()
|
|
|
|
|
+ {
|
|
|
|
|
+ isSubscribing = true;
|
|
|
|
|
+
|
|
|
|
|
+ if (selectedDeviceId == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ HandleConnectFail();
|
|
|
|
|
+ smartBowHelper.InvokeOnBluetoothError(BluetoothError.Unknown, "选择设备 DeviceId 不存在!");
|
|
|
|
|
+ yield break;
|
|
|
|
|
+ }
|
|
|
|
|
+ Log("SelectedDeviceId OK");
|
|
|
|
|
+
|
|
|
|
|
+ BleApi.ScanServices(selectedDeviceId);
|
|
|
|
|
+ isScanningServices = true;
|
|
|
|
|
+ serviceList.Clear();
|
|
|
|
|
+
|
|
|
|
|
+ while (isScanningServices) yield return null;
|
|
|
|
|
+
|
|
|
|
|
+ bool findTargetService = false;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (string service in serviceList)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (service == targetService)
|
|
|
|
|
+ {
|
|
|
|
|
+ findTargetService = true;
|
|
|
|
|
+ Log("FindTargetService OK");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!findTargetService)
|
|
|
|
|
+ {
|
|
|
|
|
+ HandleConnectFail();
|
|
|
|
|
+ //一般都不会出现这个问题,直接使用Unknown
|
|
|
|
|
+ smartBowHelper.InvokeOnBluetoothError(BluetoothError.Unknown, "目标设备服务 service 不存在!");
|
|
|
|
|
+ yield break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BleApi.ScanCharacteristics(selectedDeviceId, targetService);
|
|
|
|
|
+ isScanningCharacteristics = true;
|
|
|
|
|
+ characteristicsList.Clear();
|
|
|
|
|
+
|
|
|
|
|
+ while (isScanningCharacteristics) yield return null;
|
|
|
|
|
+
|
|
|
|
|
+ bool findTargetCharacteristicsNotify = false;
|
|
|
|
|
+ bool findTargetCharacteristicsWrite = false;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (string characteristics in characteristicsList)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (characteristics == targetCharacteristicsNotify)
|
|
|
|
|
+ {
|
|
|
|
|
+ findTargetCharacteristicsNotify = true;
|
|
|
|
|
+ Log("FindTargetCharacteristicsNotify OK");
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (characteristics == targetCharacteristicsWrite)
|
|
|
|
|
+ {
|
|
|
|
|
+ findTargetCharacteristicsWrite = true;
|
|
|
|
|
+ Log("FindTargetCharacteristicsWrite OK");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!findTargetCharacteristicsNotify || !findTargetCharacteristicsWrite)
|
|
|
|
|
+ {
|
|
|
|
|
+ HandleConnectFail();
|
|
|
|
|
+ //一般都不会出现这个问题,直接使用Unknown
|
|
|
|
|
+ smartBowHelper.InvokeOnBluetoothError(BluetoothError.Unknown, "目标设备特征值 Characteristics 不存在!");
|
|
|
|
|
+ yield break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BleApi.SubscribeCharacteristic(selectedDeviceId, targetService, targetCharacteristicsNotify, false);
|
|
|
|
|
+ isSubscribed = true;
|
|
|
|
|
+ isSubscribing = false;
|
|
|
|
|
+ Log("SubscribeCharacteristicNotify OK");
|
|
|
|
|
+
|
|
|
|
|
+ _connectedTime = Time.realtimeSinceStartup;
|
|
|
|
|
+ OnConnected?.Invoke();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 连接错误
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ private void HandleConnectFail()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
|
|
|
|
|
+ bool isLockBefore = isConnectLocking;
|
|
|
|
|
+ ReinitAfterConnectFail();
|
|
|
|
|
+ if (isLockBefore) OnConnectionFailed?.Invoke();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void ReinitAfterConnectFail()
|
|
|
|
|
+ {
|
|
|
|
|
+ isConnectLocking = false;
|
|
|
|
|
+ isScanningDevices = false;
|
|
|
|
|
+ isScanningServices = false;
|
|
|
|
|
+ isScanningCharacteristics = false;
|
|
|
|
|
+ isSubscribed = false;
|
|
|
|
|
+ isSubscribing = false;
|
|
|
|
|
+ selectedDeviceId = null;
|
|
|
|
|
+ deviceList.Clear();
|
|
|
|
|
+ serviceList.Clear();
|
|
|
|
|
+ characteristicsList.Clear();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool Write(string text)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!isSubscribed) return false;
|
|
|
|
|
+ byte[] payload = Encoding.ASCII.GetBytes(text);
|
|
|
|
|
+ BleApi.BLEData data = new BleApi.BLEData();
|
|
|
|
|
+ data.buf = new byte[512];
|
|
|
|
|
+ data.size = (short)payload.Length;
|
|
|
|
|
+ data.deviceId = selectedDeviceId;
|
|
|
|
|
+ data.serviceUuid = targetService;
|
|
|
|
|
+ data.characteristicUuid = targetCharacteristicsWrite;
|
|
|
|
|
+ for (int i = 0; i < payload.Length; i++)
|
|
|
|
|
+ data.buf[i] = payload[i];
|
|
|
|
|
+ // no error code available in non-blocking mode
|
|
|
|
|
+ BleApi.SendData(in data, false);
|
|
|
|
|
+ Log("Write(" + text + ")");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ private bool WriteByte(byte[] payload)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!isSubscribed) return false;
|
|
|
|
|
+
|
|
|
|
|
+ BleApi.BLEData data = new BleApi.BLEData();
|
|
|
|
|
+ data.buf = new byte[512];
|
|
|
|
|
+ data.size = (short)payload.Length;
|
|
|
|
|
+ data.deviceId = selectedDeviceId;
|
|
|
|
|
+ data.serviceUuid = targetService;
|
|
|
|
|
+ data.characteristicUuid = targetCharacteristicsWrite;
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < payload.Length; i++)
|
|
|
|
|
+ data.buf[i] = payload[i];
|
|
|
|
|
+
|
|
|
|
|
+ // no error code available in non-blocking mode
|
|
|
|
|
+ BleApi.SendData(in data, false);
|
|
|
|
|
+
|
|
|
|
|
+ Log("Write(byte[])");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool Disconnect()
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!isConnectLocking)
|
|
|
|
|
+ {
|
|
|
|
|
+ Warn("Disconnect Invalid, because not in connect.");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isSubscribing)
|
|
|
|
|
+ {
|
|
|
|
|
+ Warn("Disconnect Invalid, because is subscribing.");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isScanningDevices) StopDeviceScan();
|
|
|
|
|
+ if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
|
|
|
|
|
+ ReinitAfterConnectFail();
|
|
|
|
|
+ Log("Disconnect OK");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 其实是 WinRT BLE API 的设备 ID 格式,它包含两部分:
|
|
|
|
|
+ /// 前面:BluetoothLE#BluetoothLE00:e0:4c:2a:12:97 → 设备的内部标识(适配器 + 随机 ID)。
|
|
|
|
|
+ /// 后面:d3:5c:89:57:68:de → 真实的 MAC 地址。
|
|
|
|
|
+ /// 微软官方文档也说明了: WinRT 不直接给 BluetoothAddress,而是拼接在 Id 的末尾。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ /// <param name="deviceId">参考 BluetoothLE#BluetoothLE00:e0:4c:2a:12:97-d3:5c:89:57:68:de</param>
|
|
|
|
|
+ /// <returns></returns>
|
|
|
|
|
+ string ExtractMacFromDeviceId(string deviceId)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (string.IsNullOrEmpty(deviceId))
|
|
|
|
|
+ return null;
|
|
|
|
|
+
|
|
|
|
|
+ // WinRT 格式一般是:BluetoothLE#BluetoothLEXX:XX:XX:XX:XX:XX-YY:YY:YY:YY:YY:YY
|
|
|
|
|
+ int lastDash = deviceId.LastIndexOf('-');
|
|
|
|
|
+ if (lastDash >= 0 && lastDash < deviceId.Length - 1)
|
|
|
|
|
+ {
|
|
|
|
|
+ return deviceId.Substring(lastDash + 1); // 提取最后一段 MAC
|
|
|
|
|
+ }
|
|
|
|
|
+ return deviceId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|