BluetoothAim.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. using ArduinoBluetoothAPI;
  2. using System;
  3. using UnityEngine;
  4. using System.Collections.Generic;
  5. using UnityEngine.UI;
  6. using DG.Tweening;
  7. /* 蓝牙瞄准模块 */
  8. public class BluetoothAim : MonoBehaviour
  9. {
  10. SBLogger logger = new SBLogger(typeof(BluetoothAim).Name);
  11. readonly string targetDeviceName = "Bbow_20210501";
  12. string targetDeviceService {
  13. get {
  14. if (CommonConfig.devicePlan == 0 || CommonConfig.devicePlan == 3) {
  15. #if UNITY_ANDROID
  16. return "0000fff0";
  17. #else
  18. return "fff0";
  19. #endif
  20. }
  21. return "6e400001";
  22. }
  23. }
  24. string targetDeviceCharacteristicWrite {
  25. get {
  26. if (CommonConfig.devicePlan == 0 || CommonConfig.devicePlan == 3) {
  27. #if UNITY_ANDROID
  28. return "0000fff2";
  29. #else
  30. return "fff2";
  31. #endif
  32. }
  33. return "6e400002";
  34. }
  35. }
  36. string targetDeviceCharacteristicNotify {
  37. get {
  38. if (CommonConfig.devicePlan == 0 || CommonConfig.devicePlan == 3) {
  39. #if UNITY_ANDROID
  40. return "0000fff1";
  41. #else
  42. return "fff1";
  43. #endif
  44. }
  45. return "6e400003";
  46. }
  47. }
  48. BluetoothHelper bluetoothHelper;
  49. BluetoothHelperCharacteristic characteristicWrite;
  50. BluetoothHelperService bluetoothService;
  51. string deviceName = "";
  52. bool canConnect = true;
  53. [SerializeField] Text textUI;
  54. public BluetoothStatusEnum status = BluetoothStatusEnum.Connect;
  55. public bool hasData = false;
  56. public long hasDataTime;
  57. public static bool scanLock = false; //防止同时扫描冲突
  58. public static BluetoothAim ins;
  59. void Start()
  60. {
  61. ins = this;
  62. InitAutoDormancy();
  63. #if UNITY_STANDALONE_WIN || UNITY_EDITOR
  64. new GameObject("BleUDP").AddComponent<BleUDP>();
  65. #endif
  66. }
  67. void OnDestroy()
  68. {
  69. DisconnectBleHelper();
  70. }
  71. private bool userDoConnect = false;
  72. private bool doConnect = false;
  73. public Func<bool> action_DoConnectInterceptor;
  74. public void DoConnect()
  75. {
  76. if (action_DoConnectInterceptor != null) {
  77. if (action_DoConnectInterceptor.Invoke()) return;
  78. }
  79. if (status == BluetoothStatusEnum.Connect)
  80. {
  81. userDoConnect = true;
  82. doConnect = true;
  83. SetStatus(BluetoothStatusEnum.Connecting);
  84. }
  85. else if (status == BluetoothStatusEnum.ConnectSuccess)
  86. {
  87. userDoConnect = false;
  88. doConnect = false;
  89. OnDisconnect();
  90. #if UNITY_STANDALONE_WIN || UNITY_EDITOR
  91. BleUDP.ins.Disconnect();
  92. #else
  93. DisconnectBleHelper();
  94. #endif
  95. }
  96. }
  97. void OnDisconnect()
  98. {
  99. hasData = false;
  100. canConnect = true;
  101. SetStatus(BluetoothStatusEnum.ConnectFail);
  102. BowCamera.isTouchMode = true;
  103. DestroyWhenDisconenct();
  104. if (AimHandler.ins) AimHandler.ins.SetMsOldDefault();
  105. }
  106. void Update()
  107. {
  108. if (userDoConnect && status == BluetoothStatusEnum.Connect)
  109. {
  110. DoConnect();
  111. }
  112. if (doConnect) Connect();
  113. }
  114. void SetStatus(BluetoothStatusEnum statusValue)
  115. {
  116. status = statusValue;
  117. if (status == BluetoothStatusEnum.ConnectFail)
  118. {
  119. Sequence sequence = DOTween.Sequence();
  120. sequence.AppendInterval(2f);
  121. sequence.AppendCallback(delegate ()
  122. {
  123. if (status == BluetoothStatusEnum.ConnectFail)
  124. {
  125. status = BluetoothStatusEnum.Connect;
  126. }
  127. });
  128. sequence.SetUpdate(true);
  129. DeviceReconnectView.Show();
  130. SimulateMouseController.ins?.SetBleConnected(false);
  131. } else if (status == BluetoothStatusEnum.ConnectSuccess) {
  132. SimulateMouseController.ins?.SetBleConnected(true);
  133. }
  134. }
  135. void DisconnectBleHelper()
  136. {
  137. if (bluetoothHelper != null) bluetoothHelper.Disconnect();
  138. }
  139. void Connect()
  140. {
  141. if (BluetoothShoot.scanLock)
  142. {
  143. return;
  144. }
  145. if (!canConnect)
  146. {
  147. return;
  148. }
  149. doConnect = false;
  150. scanLock = true;
  151. canConnect = false;
  152. SetStatus(BluetoothStatusEnum.Connecting);
  153. #if UNITY_STANDALONE_WIN || UNITY_EDITOR
  154. ConnectBleByUDP();
  155. #else
  156. ConnectBleHelper();
  157. #endif
  158. }
  159. void ConnectBleHelper()
  160. {
  161. try
  162. {
  163. #if UNITY_ANDROID
  164. using (var helper = new AndroidJavaClass("com.example.smartbowlib.BluetoothHelper"))
  165. {
  166. using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
  167. {
  168. using (var context = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
  169. {
  170. bool ok = helper.CallStatic<bool>("requestPermissions", context);
  171. if (!ok) throw new Exception("安卓12的蓝牙权限-尚未授权");
  172. }
  173. }
  174. }
  175. #endif
  176. BluetoothHelper.BLE = true;
  177. bluetoothHelper = BluetoothHelper.GetNewInstance();
  178. bluetoothHelper.OnConnected += (BluetoothHelper helper) =>
  179. {
  180. Log("连接成功\n" + helper.getDeviceName());
  181. SetStatus(BluetoothStatusEnum.ConnectSuccess);
  182. BowCamera.isTouchMode = false;
  183. logger.Log("连接成功");
  184. foreach (BluetoothHelperService service in helper.getGattServices())
  185. {
  186. logger.Log("待检测的服务名:" + service.getName());
  187. if (service.getName().ToLower().StartsWith(targetDeviceService))
  188. {
  189. bluetoothService = service;
  190. logger.Warn("匹配的服务名:" + service.getName());
  191. foreach (BluetoothHelperCharacteristic characteristic in service.getCharacteristics())
  192. {
  193. logger.Log("待检测的特征:" + characteristic.getName());
  194. if (characteristic.getName().ToLower().StartsWith(targetDeviceCharacteristicWrite))
  195. {
  196. characteristicWrite = characteristic;
  197. logger.Warn("匹配的写入特征:" + characteristic.getName());
  198. }
  199. else if (characteristic.getName().ToLower().StartsWith(targetDeviceCharacteristicNotify))
  200. {
  201. logger.Warn("匹配的订阅特征(准备):" + characteristic.getName());
  202. BluetoothHelperCharacteristic ch = new BluetoothHelperCharacteristic(characteristic.getName());
  203. ch.setService(bluetoothService.getName());
  204. bluetoothHelper.Subscribe(ch);
  205. logger.Warn("匹配的订阅特征(完成):" + characteristic.getName());
  206. }
  207. }
  208. }
  209. }
  210. logger.Log("服务匹配结束");
  211. // CallDelay(1, OpenInfrared);
  212. // CallDelay(2, OpenReceiveData);
  213. // CallDelay(3, RequestBattery);
  214. CallDelay(2, () =>
  215. {
  216. if (status != BluetoothStatusEnum.ConnectSuccess) return;
  217. logger.Log("初始化自动发送CMD");
  218. InitWhenConenct();
  219. });
  220. };
  221. bluetoothHelper.OnConnectionFailed += (BluetoothHelper helper) =>
  222. {
  223. Log("连接失败\n" + helper.getDeviceName());
  224. OnDisconnect();
  225. };
  226. bluetoothHelper.OnCharacteristicChanged += (helper, value, characteristic) =>
  227. {
  228. // logger.Log(String.Join(",", value));
  229. if (!hasData) {
  230. hasDataTime = JCUnityLib.TimeUtils.GetTimestamp();
  231. UploadMacAddress(value);
  232. }
  233. hasData = true;
  234. byte[] bytes = value;
  235. // Log(String.Join(",", bytes));
  236. BluetoothClient.UploadData(0, bytes);
  237. if (AimHandler.ins)
  238. {
  239. AimHandler.ins.OnDataReceived(bytes);
  240. }
  241. };
  242. int scanCount = 0;
  243. bluetoothHelper.OnScanEnded += (BluetoothHelper helper, LinkedList<BluetoothDevice> nearbyDevices) =>
  244. {
  245. scanLock = false;
  246. foreach (BluetoothDevice device in nearbyDevices)
  247. {
  248. if (device.DeviceName == targetDeviceName)
  249. {
  250. deviceName = device.DeviceName;
  251. bluetoothHelper.setDeviceName(deviceName);
  252. bluetoothHelper.Connect();
  253. Log("发现设备\n" + device.DeviceName);
  254. return;
  255. }
  256. }
  257. if (scanCount < 3)
  258. { //如果没扫描到,则重新扫描,达到延迟提示失败的效果
  259. scanCount++;
  260. scanLock = true;
  261. bluetoothHelper.ScanNearbyDevices();
  262. }
  263. else
  264. {
  265. canConnect = true;
  266. Log("没有发现设备");
  267. SetStatus(BluetoothStatusEnum.ConnectFail);
  268. }
  269. };
  270. bluetoothHelper.ScanNearbyDevices();
  271. Log("正在扫描设备");
  272. }
  273. catch (Exception e)
  274. {
  275. Debug.LogError(e.Message);
  276. Debug.LogError(e.StackTrace);
  277. scanLock = false;
  278. canConnect = true;
  279. // SetStatus(BluetoothStatusEnum.ConnectFail);
  280. status = BluetoothStatusEnum.Connect;
  281. userDoConnect = false;
  282. PopupMgr.ins.ShowTip(TextAutoLanguage2.GetTextByKey("ble-please-open-ble"));
  283. }
  284. }
  285. void ConnectBleByUDP()
  286. {
  287. try
  288. {
  289. BleUDP.ins.OnConnected = () =>
  290. {
  291. Log("连接成功\n" + deviceName);
  292. SetStatus(BluetoothStatusEnum.ConnectSuccess);
  293. BowCamera.isTouchMode = false;
  294. InitWhenConenct();
  295. };
  296. BleUDP.ins.OnConnectionFailed = () =>
  297. {
  298. Log("连接失败\n" + deviceName);
  299. OnDisconnect();
  300. };
  301. BleUDP.ins.OnCharacteristicChanged = (byte[] value) =>
  302. {
  303. if (!hasData) {
  304. hasDataTime = JCUnityLib.TimeUtils.GetTimestamp();
  305. UploadMacAddress(value);
  306. }
  307. hasData = true;
  308. byte[] bytes = value;
  309. // Log(String.Join(",", bytes));
  310. BluetoothClient.UploadData(0, bytes);
  311. if (AimHandler.ins)
  312. {
  313. AimHandler.ins.OnDataReceived(bytes);
  314. }
  315. };
  316. BleUDP.ins.OnScanEnded = () =>
  317. {
  318. scanLock = false;
  319. deviceName = targetDeviceName;
  320. BleUDP.ins.Connect();
  321. Log("发现设备\n" + deviceName);
  322. };
  323. BleUDP.ins.ScanNearbyDevices();
  324. }
  325. catch (Exception e)
  326. {
  327. Debug.LogError(e.Message);
  328. Debug.LogError(e.StackTrace);
  329. scanLock = false;
  330. canConnect = true;
  331. SetStatus(BluetoothStatusEnum.ConnectFail);
  332. }
  333. }
  334. #region 自动进入/退出休眠状态, 这里做程指令发送队列,为了控制连续发送指令的间隔,避免硬件收不到或处理不过来
  335. class CmdToSend
  336. {
  337. public string[] cmds;
  338. public Action onComplete;
  339. public Func<bool> canDo;
  340. public CmdToSend(string[] cmds, Action onComplete, Func<bool> canDo)
  341. {
  342. this.cmds = cmds;
  343. this.onComplete = onComplete;
  344. this.canDo = canDo;
  345. }
  346. }
  347. Queue<CmdToSend> cmdWaitingList = new Queue<CmdToSend>();
  348. bool isSendCmdLocked = false;
  349. bool canAutoDormancy = false;
  350. bool isStartUp = false;
  351. JCUnityLib.CountLock needModularAwake = new JCUnityLib.CountLock();
  352. void CheckAndStartUp()
  353. {
  354. if (needModularAwake.IsLocked())
  355. {
  356. StartUp();
  357. }
  358. else
  359. {
  360. Dormancy();
  361. }
  362. }
  363. void InitAutoDormancy()
  364. {
  365. // GlobalEventCenter.ins.onGameSceneLoad += () => {
  366. // needModularAwake.Lock();
  367. // CheckAndStartUp();
  368. // };
  369. // GlobalEventCenter.ins.onGameSceneDestroy += () => {
  370. // needModularAwake.Unlock();
  371. // CheckAndStartUp();
  372. // };
  373. // GlobalEventCenter.ins.onSimulateMouseAwakeChanged += (waked) => {
  374. // if (waked) needModularAwake.Lock();
  375. // else needModularAwake.Unlock();;
  376. // CheckAndStartUp();
  377. // };
  378. // GlobalEventCenter.ins.onDeviceCalibrateViewAwakeChanged += (waked) => {
  379. // if (waked) needModularAwake.Lock();
  380. // else needModularAwake.Unlock();;
  381. // CheckAndStartUp();
  382. // };
  383. //暂时关闭自动休眠,默认是需要模块保持激活
  384. needModularAwake.Lock();
  385. }
  386. void InitWhenConenct()
  387. {
  388. canAutoDormancy = true;
  389. List<string> cmds = new List<string>();
  390. cmds.Add("M"); //获取Mac地址
  391. cmds.Add("b"); //确保开启stm32
  392. cmds.Add("b"); //获取初始电量
  393. cmds.Add("1"); //开启发送逻辑
  394. Action onComplete = null;
  395. if (needModularAwake.IsLocked())
  396. {
  397. cmds.Add("w"); //红外灯开启
  398. cmds.Add("3"); //九轴开启
  399. onComplete = () =>
  400. {
  401. isStartUp = true;
  402. };
  403. }
  404. else
  405. {
  406. cmds.Add("s"); //红外灯关闭
  407. cmds.Add("S"); //Stm32关闭
  408. cmds.Add("4"); //九轴关闭
  409. onComplete = () =>
  410. {
  411. isStartUp = false;
  412. };
  413. }
  414. SendCDM(null, onComplete, cmds.ToArray());
  415. }
  416. void DestroyWhenDisconenct()
  417. {
  418. canAutoDormancy = false;
  419. sendCMD_CheckAndDoStop(null);
  420. }
  421. //启动
  422. void StartUp()
  423. {
  424. SendCDM(() =>
  425. {
  426. return !isStartUp;
  427. }, () =>
  428. {
  429. isStartUp = true;
  430. }, "b", "w", "3");
  431. }
  432. //休眠
  433. void Dormancy()
  434. {
  435. SendCDM(() =>
  436. {
  437. return isStartUp;
  438. }, () =>
  439. {
  440. isStartUp = false;
  441. }, "4", "s", "S");
  442. }
  443. void SendCDM(Func<bool> canDo, Action onComplete, params string[] cmds)
  444. {
  445. CmdToSend cmdToSend = new CmdToSend(cmds, onComplete, canDo);
  446. if (isSendCmdLocked)
  447. {
  448. cmdWaitingList.Enqueue(cmdToSend);
  449. return;
  450. }
  451. sendCMD_NotCheck(cmdToSend);
  452. }
  453. void sendCMD_NotCheck(CmdToSend cmdToSend)
  454. {
  455. if (cmdToSend.canDo != null && !cmdToSend.canDo.Invoke())
  456. {
  457. sendCMD_CheckNext();
  458. return;
  459. }
  460. isSendCmdLocked = true;
  461. Sequence sequence = DOTween.Sequence();
  462. sequence.PrependInterval(0.3f);
  463. foreach (var cmd in cmdToSend.cmds)
  464. {
  465. sequence.AppendCallback(() =>
  466. {
  467. bool stopped = sendCMD_CheckAndDoStop(sequence);
  468. if (!stopped) WriteData(cmd);
  469. });
  470. sequence.AppendInterval(0.5f);
  471. }
  472. sequence.AppendCallback(() =>
  473. {
  474. bool stopped = sendCMD_CheckAndDoStop(sequence);
  475. if (!stopped)
  476. {
  477. isSendCmdLocked = false;
  478. cmdToSend.onComplete?.Invoke();
  479. sendCMD_CheckNext();
  480. }
  481. });
  482. sequence.SetUpdate(true);
  483. }
  484. void sendCMD_CheckNext()
  485. {
  486. if (cmdWaitingList.Count <= 0) return;
  487. CmdToSend cmdToSend = cmdWaitingList.Dequeue();
  488. sendCMD_NotCheck(cmdToSend);
  489. }
  490. bool sendCMD_CheckAndDoStop(Sequence sequence)
  491. {
  492. if (canAutoDormancy) return false;
  493. isStartUp = false;
  494. isSendCmdLocked = false;
  495. cmdWaitingList.Clear();
  496. if (sequence != null) sequence.Kill();
  497. return true;
  498. }
  499. #endregion
  500. public void RequestBattery()
  501. {
  502. if (!isStartUp) return;
  503. if (isSendCmdLocked) return;
  504. WriteData("b");
  505. }
  506. public void ReplyInfraredShoot()
  507. {
  508. if (isSendCmdLocked) return;
  509. WriteData("I");
  510. }
  511. void CallDelay(float delayTime, TweenCallback callback)
  512. {
  513. Sequence sequence = DOTween.Sequence();
  514. sequence.PrependInterval(delayTime).AppendCallback(callback);
  515. sequence.SetUpdate(true);
  516. }
  517. public void WriteData(string data)
  518. {
  519. logger.Log("发送CMD(准备):" + data);
  520. #if UNITY_STANDALONE_WIN || UNITY_EDITOR
  521. BleUDP.ins.SendMsg(data);
  522. #else
  523. if (DebugDeviceCMD.ins) DebugDeviceCMD.ins.ShowCMD(data);
  524. BluetoothHelperCharacteristic ch = new BluetoothHelperCharacteristic(characteristicWrite.getName());
  525. ch.setService(bluetoothService.getName());
  526. bluetoothHelper.WriteCharacteristic(ch, data);
  527. #endif
  528. logger.Log("发送CMD(完成):" + data);
  529. }
  530. void Log(string text)
  531. {
  532. if (textUI)
  533. {
  534. textUI.text = text;
  535. }
  536. }
  537. void UploadMacAddress(byte[] bytes) {
  538. string mac = System.Text.Encoding.ASCII.GetString(bytes);
  539. if (mac != null) mac = mac.Trim();
  540. if (CheckIsMacValid(mac)) {
  541. LoginMgr.myUserInfo.mac = mac;
  542. UserComp.Instance.saveMac();
  543. }
  544. }
  545. bool CheckIsMacValid(string mac) {
  546. if (mac == null) return false;
  547. if (!mac.StartsWith("{")) return false;
  548. if (!mac.EndsWith("}")) return false;
  549. if (!mac.Contains(":")) return false;
  550. char[] validChars = {'{','}',':','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
  551. foreach (var c in mac.ToCharArray()) {
  552. if (Array.IndexOf(validChars, c) == -1) return false;
  553. }
  554. return true;
  555. }
  556. }