BleWinHelper.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using UnityEngine;
  6. using System.Runtime.InteropServices;
  7. namespace SmartBowSDK
  8. {
  9. /// <summary>
  10. /// Windows连接BluetoothLE
  11. /// 我的扫描逻辑默认了读写特征都在同一服务下
  12. /// </summary>
  13. public class BleWinHelper : MonoBehaviour
  14. {
  15. private const string LogTag = "BleWinHelper-Log: ";
  16. private static void Log(string text)
  17. {
  18. Debug.Log(LogTag + text);
  19. }
  20. private static void Warn(string text)
  21. {
  22. Debug.LogWarning(LogTag + text);
  23. }
  24. private static void Error(string text)
  25. {
  26. Debug.Log(LogTag + text);
  27. }
  28. private string targetDeviceName = "Bbow_20210501";
  29. private string targetService = "{0000fff0-0000-1000-8000-00805f9b34fb}";
  30. private string targetCharacteristicsNotify = "{0000fff1-0000-1000-8000-00805f9b34fb}";
  31. private string targetCharacteristicsWrite = "{0000fff2-0000-1000-8000-00805f9b34fb}";
  32. private bool isConnectLocking = false;
  33. private bool isScanningDevices = false;
  34. private bool isScanningServices = false;
  35. private bool isScanningCharacteristics = false;
  36. private bool isSubscribed = false;
  37. private bool isSubscribing = false;
  38. private string lastError = null;
  39. private string selectedDeviceId = null;
  40. private Dictionary<string, Dictionary<string, string>> deviceList = new Dictionary<string, Dictionary<string, string>>();
  41. private List<string> serviceList = new List<string>();
  42. private List<string> characteristicsList = new List<string>();
  43. private float _connectedTime = 0;
  44. private float _receiveDataTime = 0;
  45. private float _heartBeatInterval = 0;
  46. private Action OnConnected;
  47. /// <summary>
  48. /// 主动调用Disconnect()不会触发该委托
  49. /// </summary>
  50. private Action OnConnectionFailed;
  51. private Action<byte[]> OnCharacteristicChanged;
  52. public static BleWinHelper RegisterTo(GameObject o)
  53. {
  54. if (_Instance)
  55. {
  56. Error("Register fail, because only one can be registered.");
  57. return null;
  58. }
  59. BleWinHelper comp = o.AddComponent<BleWinHelper>();
  60. BluetoothWindows.Connect = comp.Connect;
  61. BluetoothWindows.Disconnect = comp.Disconnect;
  62. BluetoothWindows.Write = comp.Write;
  63. comp.OnConnected = () => BluetoothWindows.OnConnected?.Invoke();
  64. comp.OnConnectionFailed = () => BluetoothWindows.OnConnectionFailed?.Invoke();
  65. comp.OnCharacteristicChanged = (bytes) => BluetoothWindows.OnCharacteristicChanged?.Invoke(bytes);
  66. return comp;
  67. }
  68. /// <summary>
  69. /// 设置心跳检测
  70. /// 1.每次收到的蓝牙数据都视为心跳
  71. /// 2.帮助触发蓝牙断开监听
  72. /// </summary>
  73. /// <param name="interval">心跳检测间隔</param>
  74. public void SetHeartBeat(float interval)
  75. {
  76. _heartBeatInterval = interval;
  77. }
  78. private static BleWinHelper _Instance;
  79. void Awake()
  80. {
  81. _Instance = this;
  82. }
  83. void OnDestroy()
  84. {
  85. if (_Instance == this) _Instance = null;
  86. BleApi.Quit();
  87. }
  88. void Update()
  89. {
  90. BleApi.ScanStatus status;
  91. if (isScanningDevices)
  92. {
  93. BleApi.DeviceUpdate res = new BleApi.DeviceUpdate();
  94. do
  95. {
  96. status = BleApi.PollDevice(ref res, false);
  97. if (status == BleApi.ScanStatus.AVAILABLE)
  98. {
  99. if (!deviceList.ContainsKey(res.id))
  100. deviceList[res.id] = new Dictionary<string, string>() {
  101. { "name", "" },
  102. { "isConnectable", "False" }
  103. };
  104. if (res.nameUpdated)
  105. deviceList[res.id]["name"] = res.name;
  106. if (res.isConnectableUpdated)
  107. deviceList[res.id]["isConnectable"] = res.isConnectable.ToString();
  108. if (deviceList[res.id]["name"] == targetDeviceName && deviceList[res.id]["isConnectable"] == "True")
  109. {
  110. selectedDeviceId = res.id;
  111. StopDeviceScan();
  112. }
  113. }
  114. else if (status == BleApi.ScanStatus.FINISHED)
  115. {
  116. StartCoroutine(ScanServiceAndCharacteristicsToSubscribe());
  117. }
  118. } while (status == BleApi.ScanStatus.AVAILABLE);
  119. }
  120. if (isScanningServices)
  121. {
  122. BleApi.Service res;
  123. do
  124. {
  125. status = BleApi.PollService(out res, false);
  126. if (status == BleApi.ScanStatus.AVAILABLE)
  127. {
  128. serviceList.Add(res.uuid);
  129. }
  130. else if (status == BleApi.ScanStatus.FINISHED)
  131. {
  132. isScanningServices = false;
  133. }
  134. } while (status == BleApi.ScanStatus.AVAILABLE);
  135. }
  136. if (isScanningCharacteristics)
  137. {
  138. BleApi.Characteristic res;
  139. do
  140. {
  141. status = BleApi.PollCharacteristic(out res, false);
  142. if (status == BleApi.ScanStatus.AVAILABLE)
  143. {
  144. string name = res.userDescription != "no description available" ? res.userDescription : res.uuid;
  145. characteristicsList.Add(name);
  146. }
  147. else if (status == BleApi.ScanStatus.FINISHED)
  148. {
  149. isScanningCharacteristics = false;
  150. }
  151. } while (status == BleApi.ScanStatus.AVAILABLE);
  152. }
  153. if (isSubscribed)
  154. {
  155. BleApi.BLEData res;
  156. while (BleApi.PollData(out res, false))
  157. {
  158. //string text = BitConverter.ToString(res.buf, 0, res.size);
  159. // string text = Encoding.ASCII.GetString(res.buf, 0, res.size);
  160. byte[] bytes = new byte[res.size];
  161. Array.Copy(res.buf, bytes, res.size);
  162. _receiveDataTime = Time.realtimeSinceStartup;
  163. OnCharacteristicChanged?.Invoke(bytes);
  164. }
  165. }
  166. {
  167. BleApi.ErrorMessage res;
  168. BleApi.GetError(out res);
  169. if (lastError != res.msg)
  170. {
  171. Error(res.msg);
  172. lastError = res.msg;
  173. if (lastError.Contains("SendDataAsync") && isSubscribed)
  174. {
  175. HandleConnectFail();
  176. }
  177. }
  178. }
  179. }
  180. void LateUpdate()
  181. {
  182. if (
  183. _heartBeatInterval > 0 &&
  184. isSubscribed &&
  185. Time.realtimeSinceStartup - _connectedTime >= _heartBeatInterval &&
  186. Time.realtimeSinceStartup - _receiveDataTime >= _heartBeatInterval
  187. ) HandleConnectFail();
  188. }
  189. private bool Connect()
  190. {
  191. if (isConnectLocking || isScanningDevices)
  192. {
  193. Warn("Connect Invalid, because is in connect.");
  194. return false;
  195. }
  196. BleApi.StartDeviceScan();
  197. isConnectLocking = true;
  198. isScanningDevices = true;
  199. Log("Start Connect");
  200. return true;
  201. }
  202. private void StopDeviceScan()
  203. {
  204. if (!isScanningDevices) return;
  205. BleApi.StopDeviceScan();
  206. isScanningDevices = false;
  207. }
  208. private IEnumerator ScanServiceAndCharacteristicsToSubscribe()
  209. {
  210. isSubscribing = true;
  211. if (selectedDeviceId == null)
  212. {
  213. HandleConnectFail();
  214. yield break;
  215. }
  216. Log("SelectedDeviceId OK");
  217. BleApi.ScanServices(selectedDeviceId);
  218. isScanningServices = true;
  219. serviceList.Clear();
  220. while (isScanningServices) yield return null;
  221. bool findTargetService = false;
  222. foreach (string service in serviceList)
  223. {
  224. if (service == targetService)
  225. {
  226. findTargetService = true;
  227. Log("FindTargetService OK");
  228. break;
  229. }
  230. }
  231. if (!findTargetService)
  232. {
  233. HandleConnectFail();
  234. yield break;
  235. }
  236. BleApi.ScanCharacteristics(selectedDeviceId, targetService);
  237. isScanningCharacteristics = true;
  238. characteristicsList.Clear();
  239. while (isScanningCharacteristics) yield return null;
  240. bool findTargetCharacteristicsNotify = false;
  241. bool findTargetCharacteristicsWrite = false;
  242. foreach (string characteristics in characteristicsList)
  243. {
  244. if (characteristics == targetCharacteristicsNotify)
  245. {
  246. findTargetCharacteristicsNotify = true;
  247. Log("FindTargetCharacteristicsNotify OK");
  248. }
  249. else if (characteristics == targetCharacteristicsWrite)
  250. {
  251. findTargetCharacteristicsWrite = true;
  252. Log("FindTargetCharacteristicsWrite OK");
  253. }
  254. }
  255. if (!findTargetCharacteristicsNotify || !findTargetCharacteristicsWrite)
  256. {
  257. HandleConnectFail();
  258. yield break;
  259. }
  260. BleApi.SubscribeCharacteristic(selectedDeviceId, targetService, targetCharacteristicsNotify, false);
  261. isSubscribed = true;
  262. isSubscribing = false;
  263. Log("SubscribeCharacteristicNotify OK");
  264. _connectedTime = Time.realtimeSinceStartup;
  265. OnConnected?.Invoke();
  266. }
  267. private void HandleConnectFail()
  268. {
  269. if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
  270. bool isLockBefore = isConnectLocking;
  271. ReinitAfterConnectFail();
  272. if (isLockBefore) OnConnectionFailed?.Invoke();
  273. }
  274. private void ReinitAfterConnectFail()
  275. {
  276. isConnectLocking = false;
  277. isScanningDevices = false;
  278. isScanningServices = false;
  279. isScanningCharacteristics = false;
  280. isSubscribed = false;
  281. isSubscribing = false;
  282. selectedDeviceId = null;
  283. deviceList.Clear();
  284. serviceList.Clear();
  285. characteristicsList.Clear();
  286. }
  287. private bool Write(string text)
  288. {
  289. if (!isSubscribed) return false;
  290. byte[] payload = Encoding.ASCII.GetBytes(text);
  291. BleApi.BLEData data = new BleApi.BLEData();
  292. data.buf = new byte[512];
  293. data.size = (short)payload.Length;
  294. data.deviceId = selectedDeviceId;
  295. data.serviceUuid = targetService;
  296. data.characteristicUuid = targetCharacteristicsWrite;
  297. for (int i = 0; i < payload.Length; i++)
  298. data.buf[i] = payload[i];
  299. // no error code available in non-blocking mode
  300. BleApi.SendData(in data, false);
  301. Log("Write(" + text + ")");
  302. return true;
  303. }
  304. private bool Disconnect()
  305. {
  306. if (!isConnectLocking)
  307. {
  308. Warn("Disconnect Invalid, because not in connect.");
  309. return false;
  310. }
  311. if (isSubscribing)
  312. {
  313. Warn("Disconnect Invalid, because is subscribing.");
  314. return false;
  315. }
  316. if (isScanningDevices) StopDeviceScan();
  317. if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId);
  318. ReinitAfterConnectFail();
  319. Log("Disconnect OK");
  320. return true;
  321. }
  322. }
  323. public class BleApi
  324. {
  325. public enum ScanStatus { PROCESSING, AVAILABLE, FINISHED };
  326. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  327. public struct DeviceUpdate
  328. {
  329. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
  330. public string id;
  331. [MarshalAs(UnmanagedType.I1)]
  332. public bool isConnectable;
  333. [MarshalAs(UnmanagedType.I1)]
  334. public bool isConnectableUpdated;
  335. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
  336. public string name;
  337. [MarshalAs(UnmanagedType.I1)]
  338. public bool nameUpdated;
  339. }
  340. [DllImport("BleWinrtDll.dll", EntryPoint = "StartDeviceScan")]
  341. public static extern void StartDeviceScan();
  342. [DllImport("BleWinrtDll.dll", EntryPoint = "PollDevice")]
  343. public static extern ScanStatus PollDevice(ref DeviceUpdate device, bool block);
  344. [DllImport("BleWinrtDll.dll", EntryPoint = "StopDeviceScan")]
  345. public static extern void StopDeviceScan();
  346. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  347. public struct Service
  348. {
  349. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
  350. public string uuid;
  351. };
  352. [DllImport("BleWinrtDll.dll", EntryPoint = "ScanServices", CharSet = CharSet.Unicode)]
  353. public static extern void ScanServices(string deviceId);
  354. [DllImport("BleWinrtDll.dll", EntryPoint = "PollService")]
  355. public static extern ScanStatus PollService(out Service service, bool block);
  356. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  357. public struct Characteristic
  358. {
  359. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
  360. public string uuid;
  361. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
  362. public string userDescription;
  363. };
  364. [DllImport("BleWinrtDll.dll", EntryPoint = "ScanCharacteristics", CharSet = CharSet.Unicode)]
  365. public static extern void ScanCharacteristics(string deviceId, string serviceId);
  366. [DllImport("BleWinrtDll.dll", EntryPoint = "PollCharacteristic")]
  367. public static extern ScanStatus PollCharacteristic(out Characteristic characteristic, bool block);
  368. [DllImport("BleWinrtDll.dll", EntryPoint = "SubscribeCharacteristic", CharSet = CharSet.Unicode)]
  369. public static extern bool SubscribeCharacteristic(string deviceId, string serviceId, string characteristicId, bool block);
  370. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  371. public struct BLEData
  372. {
  373. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
  374. public byte[] buf;
  375. [MarshalAs(UnmanagedType.I2)]
  376. public short size;
  377. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
  378. public string deviceId;
  379. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
  380. public string serviceUuid;
  381. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
  382. public string characteristicUuid;
  383. };
  384. [DllImport("BleWinrtDll.dll", EntryPoint = "PollData")]
  385. public static extern bool PollData(out BLEData data, bool block);
  386. [DllImport("BleWinrtDll.dll", EntryPoint = "SendData")]
  387. public static extern bool SendData(in BLEData data, bool block);
  388. [DllImport("BleWinrtDll.dll", EntryPoint = "Disconnect", CharSet = CharSet.Unicode)]
  389. public static extern void Disconnect(string deviceId);
  390. [DllImport("BleWinrtDll.dll", EntryPoint = "Quit")]
  391. public static extern void Quit();
  392. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  393. public struct ErrorMessage
  394. {
  395. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
  396. public string msg;
  397. };
  398. [DllImport("BleWinrtDll.dll", EntryPoint = "GetError")]
  399. public static extern void GetError(out ErrorMessage buf);
  400. }
  401. }