using ArduinoBluetoothAPI; using System; using UnityEngine; using System.Collections.Generic; using UnityEngine.UI; using DG.Tweening; /* 蓝牙瞄准模块 */ public class BluetoothAim : MonoBehaviour { readonly string targetDeviceName = "Bbow_20210501"; string targetDeviceService { get { if (CommonConfig.devicePlan == 0 || CommonConfig.devicePlan == 3) { #if UNITY_ANDROID return "0000fff0"; #else return "fff0"; #endif } return "6e400001"; } } string targetDeviceCharacteristicWrite { get { if (CommonConfig.devicePlan == 0 || CommonConfig.devicePlan == 3) { #if UNITY_ANDROID return "0000fff2"; #else return "fff2"; #endif } return "6e400002"; } } string targetDeviceCharacteristicNotify { get { if (CommonConfig.devicePlan == 0 || CommonConfig.devicePlan == 3) { #if UNITY_ANDROID return "0000fff1"; #else return "fff1"; #endif } return "6e400003"; } } BluetoothHelper bluetoothHelper; BluetoothHelperCharacteristic characteristicWrite; BluetoothHelperService bluetoothService; string deviceName = ""; bool canConnect = true; [SerializeField] Text textUI; public BluetoothStatusEnum status = BluetoothStatusEnum.Connect; int dataCount = 0; public bool hasData = false; public long hasDataTime; public static bool scanLock = false; //防止同时扫描冲突 public static BluetoothAim ins; void Start() { ins = this; InitAutoDormancy(); #if UNITY_STANDALONE_WIN || UNITY_EDITOR new GameObject("BleUDP").AddComponent(); #endif } void OnDestroy() { DisconnectBleHelper(); } private bool userDoConnect = false; private bool doConnect = false; public Func action_DoConnectInterceptor; public void DoConnect() { if (action_DoConnectInterceptor != null) { if (action_DoConnectInterceptor.Invoke()) return; } if (status == BluetoothStatusEnum.Connect) { connectCanceled = false; userDoConnect = true; doConnect = true; SetStatus(BluetoothStatusEnum.Connecting); } else if (status == BluetoothStatusEnum.ConnectSuccess) { userDoConnect = false; doConnect = false; OnDisconnect(); #if UNITY_STANDALONE_WIN || UNITY_EDITOR BleUDP.ins.Disconnect(); #else DisconnectBleHelper(); #endif } } //连接取消,退回登录时需要取消连接(扫描) [NonSerialized] public bool connectCanceled = false; void OnDisconnect() { curMac = null; dataCount = 0; hasData = false; canConnect = true; SetStatus(BluetoothStatusEnum.ConnectFail); BowCamera.isTouchMode = true; DestroyWhenDisconenct(); if (AimHandler.ins) AimHandler.ins.SetMsOldDefault(); } float notUserDoConnectTime = 0; void Update() { if (userDoConnect && status == BluetoothStatusEnum.Connect) { DoConnect(); } if (doConnect) Connect(); if (CommonConfig.StandaloneMode) { if (!userDoConnect && status == BluetoothStatusEnum.Connect) { if (notUserDoConnectTime == 0) { DoConnect(); notUserDoConnectTime = 0.1f; } else { notUserDoConnectTime += Time.deltaTime; if (notUserDoConnectTime > 2.5f) { DoConnect(); notUserDoConnectTime = 0.1f; } } } } } void SetStatus(BluetoothStatusEnum statusValue) { status = statusValue; if (status == BluetoothStatusEnum.ConnectFail) { Sequence sequence = DOTween.Sequence(); sequence.AppendInterval(2f); sequence.AppendCallback(delegate () { if (status == BluetoothStatusEnum.ConnectFail) { status = BluetoothStatusEnum.Connect; } }); sequence.SetUpdate(true); SimulateMouseController.ins?.SetBleConnected(false); } else if (status == BluetoothStatusEnum.ConnectSuccess) { SimulateMouseController.ins?.SetBleConnected(true); } } void DisconnectBleHelper() { if (bluetoothHelper != null) bluetoothHelper.Disconnect(); } void Connect() { if (BluetoothShoot.scanLock) { return; } if (!canConnect) { return; } doConnect = false; scanLock = true; canConnect = false; _scanCanRetryCount = 4; SetStatus(BluetoothStatusEnum.Connecting); #if UNITY_STANDALONE_WIN || UNITY_EDITOR ConnectBleByUDP(); #else ConnectBleHelper(); #endif } int _scanCanRetryCount = 4; void ConnectBleHelper() { #if UNITY_ANDROID PopupMgr.ins.ClearAllTip(); if (BluetoothHelperAndroid.IsBluetoothEnabled() == false) { HandleConnectException(TextAutoLanguage2.GetTextByKey("ble-exception1")); return; } if (BluetoothHelperAndroid.RequestBluetoothPermissions(ConnectBleHelper, (permission) => { if (permission.Contains("LOCATION")) { HandleConnectException(TextAutoLanguage2.GetTextByKey("ble-exception2")); } else if (permission.Contains("BLUETOOTH")) { HandleConnectException(TextAutoLanguage2.GetTextByKey("ble-exception3")); } })) return; #endif try { BluetoothHelper.BLE = true; bluetoothHelper = BluetoothHelper.GetNewInstance(); bluetoothHelper.OnConnected += (BluetoothHelper helper) => { Log("连接成功\n" + helper.getDeviceName()); SetStatus(BluetoothStatusEnum.ConnectSuccess); if (connectCanceled) { Debug.Log("ble connectCanceled"); DoConnect(); return; } BowCamera.isTouchMode = false; foreach (BluetoothHelperService service in helper.getGattServices()) { if (service.getName().ToLower().StartsWith(targetDeviceService)) { bluetoothService = service; foreach (BluetoothHelperCharacteristic characteristic in service.getCharacteristics()) { if (characteristic.getName().ToLower().StartsWith(targetDeviceCharacteristicWrite)) { characteristicWrite = characteristic; } else if (characteristic.getName().ToLower().StartsWith(targetDeviceCharacteristicNotify)) { BluetoothHelperCharacteristic ch = new BluetoothHelperCharacteristic(characteristic.getName()); ch.setService(bluetoothService.getName()); bluetoothHelper.Subscribe(ch); } } } } // CallDelay(1, OpenInfrared); // CallDelay(2, OpenReceiveData); // CallDelay(3, RequestBattery); CallDelay(2, () => { if (status != BluetoothStatusEnum.ConnectSuccess) return; InitWhenConenct(); }); }; bluetoothHelper.OnConnectionFailed += (BluetoothHelper helper) => { Log("连接失败\n" + helper.getDeviceName()); OnDisconnect(); }; bluetoothHelper.OnCharacteristicChanged += (helper, value, characteristic) => { if (status != BluetoothStatusEnum.ConnectSuccess) return; // logger.Log(String.Join(",", value)); if (!hasData) { hasData = true; hasDataTime = JCUnityLib.TimeUtils.GetTimestamp(); } dataCount++; if (curMac == null && dataCount < 500) { UploadMacAddress(value); } byte[] bytes = value; // Log(String.Join(",", bytes)); BluetoothClient.UploadData(0, bytes); if (AimHandler.ins) { AimHandler.ins.OnDataReceived(bytes); } }; bluetoothHelper.OnScanEnded += (BluetoothHelper helper, LinkedList nearbyDevices) => { scanLock = false; if (connectCanceled) { userDoConnect = false; canConnect = true; status = BluetoothStatusEnum.Connect; Debug.Log("ble connectCanceled"); return; } foreach (BluetoothDevice device in nearbyDevices) { Log("发现设备 " + device.DeviceName); if (device.DeviceName == targetDeviceName) { deviceName = device.DeviceName; bluetoothHelper.setDeviceName(deviceName); bluetoothHelper.Connect(); Log("匹配设备 " + device.DeviceName); return; } } if (_scanCanRetryCount > 0) { _scanCanRetryCount--; scanLock = true; ConnectBleHelper(); } else { userDoConnect = false; canConnect = true; Log("没有发现设备"); TextAutoLanguage2.GetTextByKey("ble-dev-notfound"); SetStatus(BluetoothStatusEnum.ConnectFail); } }; bluetoothHelper.ScanNearbyDevices(); Log("正在扫描设备"); } catch (Exception e) { Debug.LogError(e); HandleConnectException(TextAutoLanguage2.GetTextByKey("ble-please-open-ble")); } } void HandleConnectException(string errorText) { scanLock = false; canConnect = true; // SetStatus(BluetoothStatusEnum.ConnectFail); status = BluetoothStatusEnum.Connect; userDoConnect = false; PopupMgr.ins.ShowTip(errorText); } void ConnectBleByUDP() { try { BleUDP.ins.OnConnected = () => { Log("连接成功\n" + deviceName); SetStatus(BluetoothStatusEnum.ConnectSuccess); BowCamera.isTouchMode = false; InitWhenConenct(); }; BleUDP.ins.OnConnectionFailed = () => { Log("连接失败\n" + deviceName); OnDisconnect(); }; BleUDP.ins.OnCharacteristicChanged = (byte[] value) => { if (!hasData) { hasDataTime = JCUnityLib.TimeUtils.GetTimestamp(); UploadMacAddress(value); } hasData = true; byte[] bytes = value; // Log(String.Join(",", bytes)); BluetoothClient.UploadData(0, bytes); if (AimHandler.ins) { AimHandler.ins.OnDataReceived(bytes); } }; BleUDP.ins.OnScanEnded = () => { scanLock = false; deviceName = targetDeviceName; BleUDP.ins.Connect(); Log("发现设备\n" + deviceName); }; BleUDP.ins.ScanNearbyDevices(); } catch (Exception e) { Debug.LogError(e.Message); Debug.LogError(e.StackTrace); scanLock = false; canConnect = true; SetStatus(BluetoothStatusEnum.ConnectFail); } } #region 自动进入/退出休眠状态, 这里做程指令发送队列,为了控制连续发送指令的间隔,避免硬件收不到或处理不过来 class CmdToSend { public string[] cmds; public Action onComplete; public Func canDo; public CmdToSend(string[] cmds, Action onComplete, Func canDo) { this.cmds = cmds; this.onComplete = onComplete; this.canDo = canDo; } } Queue cmdWaitingList = new Queue(); bool isSendCmdLocked = false; bool canAutoDormancy = false; bool isStartUp = false; JCUnityLib.CountLock needModularAwake = new JCUnityLib.CountLock(); void CheckAndStartUp() { if (needModularAwake.IsLocked()) { StartUp(); } else { Dormancy(); } } void InitAutoDormancy() { // GlobalEventCenter.ins.onGameSceneLoad += () => { // needModularAwake.Lock(); // CheckAndStartUp(); // }; // GlobalEventCenter.ins.onGameSceneDestroy += () => { // needModularAwake.Unlock(); // CheckAndStartUp(); // }; // GlobalEventCenter.ins.onSimulateMouseAwakeChanged += (waked) => { // if (waked) needModularAwake.Lock(); // else needModularAwake.Unlock();; // CheckAndStartUp(); // }; // GlobalEventCenter.ins.onDeviceCalibrateViewAwakeChanged += (waked) => { // if (waked) needModularAwake.Lock(); // else needModularAwake.Unlock();; // CheckAndStartUp(); // }; //暂时关闭自动休眠,默认是需要模块保持激活 needModularAwake.Lock(); } void InitWhenConenct() { canAutoDormancy = true; List cmds = new List(); cmds.Add("M"); //获取Mac地址 cmds.Add("b"); //确保开启stm32 cmds.Add("b"); //获取初始电量 cmds.Add("1"); //开启发送逻辑 Action onComplete = null; if (needModularAwake.IsLocked()) { cmds.Add("w"); //红外灯开启 cmds.Add("3"); //九轴开启 onComplete = () => { isStartUp = true; }; } else { cmds.Add("s"); //红外灯关闭 cmds.Add("S"); //Stm32关闭 cmds.Add("4"); //九轴关闭 onComplete = () => { isStartUp = false; }; } SendCDM(null, onComplete, cmds.ToArray()); } void DestroyWhenDisconenct() { canAutoDormancy = false; sendCMD_CheckAndDoStop(null); } //启动 void StartUp() { SendCDM(() => { return !isStartUp; }, () => { isStartUp = true; }, "b", "w", "3"); } //休眠 void Dormancy() { SendCDM(() => { return isStartUp; }, () => { isStartUp = false; }, "4", "s", "S"); } void SendCDM(Func canDo, Action onComplete, params string[] cmds) { CmdToSend cmdToSend = new CmdToSend(cmds, onComplete, canDo); if (isSendCmdLocked) { cmdWaitingList.Enqueue(cmdToSend); return; } sendCMD_NotCheck(cmdToSend); } void sendCMD_NotCheck(CmdToSend cmdToSend) { if (cmdToSend.canDo != null && !cmdToSend.canDo.Invoke()) { sendCMD_CheckNext(); return; } isSendCmdLocked = true; Sequence sequence = DOTween.Sequence(); sequence.PrependInterval(0.3f); foreach (var cmd in cmdToSend.cmds) { sequence.AppendCallback(() => { bool stopped = sendCMD_CheckAndDoStop(sequence); if (!stopped) WriteData(cmd); }); sequence.AppendInterval(0.5f); } sequence.AppendCallback(() => { bool stopped = sendCMD_CheckAndDoStop(sequence); if (!stopped) { isSendCmdLocked = false; cmdToSend.onComplete?.Invoke(); sendCMD_CheckNext(); } }); sequence.SetUpdate(true); } void sendCMD_CheckNext() { if (cmdWaitingList.Count <= 0) return; CmdToSend cmdToSend = cmdWaitingList.Dequeue(); sendCMD_NotCheck(cmdToSend); } bool sendCMD_CheckAndDoStop(Sequence sequence) { if (canAutoDormancy) return false; isStartUp = false; isSendCmdLocked = false; cmdWaitingList.Clear(); if (sequence != null) sequence.Kill(); return true; } #endregion public void RequestBattery() { if (!isStartUp) return; if (isSendCmdLocked) return; WriteData("b"); } public void ReplyInfraredShoot() { if (isSendCmdLocked) return; WriteData("I"); } void CallDelay(float delayTime, TweenCallback callback) { Sequence sequence = DOTween.Sequence(); sequence.PrependInterval(delayTime).AppendCallback(callback); sequence.SetUpdate(true); } public void WriteData(string data) { #if UNITY_STANDALONE_WIN || UNITY_EDITOR BleUDP.ins.SendMsg(data); #else if (DebugDeviceCMD.ins) DebugDeviceCMD.ins.ShowCMD(data); BluetoothHelperCharacteristic ch = new BluetoothHelperCharacteristic(characteristicWrite.getName()); ch.setService(bluetoothService.getName()); bluetoothHelper.WriteCharacteristic(ch, data); #endif } void Log(string text) { if (textUI) { textUI.text = text; } Debug.Log(string.Format("[{0}]{1}", typeof(BluetoothAim).Name, text)); } [NonSerialized] public string curMac; void UploadMacAddress(byte[] bytes) { string mac = System.Text.Encoding.ASCII.GetString(bytes); if (mac != null) mac = mac.Trim(); if (CheckIsMacValid(mac)) { curMac = mac; SideTipView.ShowTip("Mac获取成功:" + mac, Color.white); } else { SideTipView.ShowTip("Mac获取失败", Color.yellow); } } bool CheckIsMacValid(string mac) { if (mac == null) return false; if (!mac.StartsWith("{")) return false; if (!mac.EndsWith("}")) return false; if (!mac.Contains(":")) return false; char[] validChars = {'{','}',':','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; foreach (var c in mac.ToCharArray()) { if (Array.IndexOf(validChars, c) == -1) return false; } if (mac.Length != 19) return false; string macNoneFrame = mac.Substring(1, mac.Length - 2); string[] macNoneFrameSplits = macNoneFrame.Split(':'); if (macNoneFrameSplits.Length != 6) return false; foreach (var item in macNoneFrameSplits) { if (item.Length != 2) return false; foreach (var c in item.ToCharArray()) if (Array.IndexOf(validChars, c) < 3) return false; } return true; } }