using System.Collections.Generic; using UnityEngine; using ArduinoBluetoothAPI; using System; using System.Text; using UnityEngine.UI; using DG.Tweening; using System.Collections; namespace InfraredManager { public class ConnetDevicesSingle : MonoBehaviour { #region #endregion #region 连接弓箭蓝牙部分 private BluetoothHelper bluetoothHelperBow; public BluetoothHelper mBluetoothHelperBow { get { return bluetoothHelperBow; } } private BluetoothHelperCharacteristic characteristicWriteBow; private BluetoothHelperService bluetoothServiceBow; private string bowTargetDeviceName = "Bbow_20210501"; private string bowDeviceName = ""; string bowDeviceService = "0000fff0"; string bowDeviceCharacteristicWrite = "0000fff2"; string bowDeviceCharacteristicNotify = "0000fff1"; private bool isBowScanning; public bool mIsBowScanning { get { return isBowScanning; } } private bool isBowConnecting; public bool mIsBowConnecting { get { return isBowConnecting; } } private string data; private int _receivedDataCount = 0; public string macAddress = null; private string inputStr = ""; #endregion private LinkedList devices; #region log输出 public Action OnLogAction; void logOutBow(string text) { Debug.Log("logOutBow:" + text); OnLogAction?.Invoke(text); } #endregion public static ConnetDevicesSingle ins; //默认关闭准心 public bool bCrosshair = false; #region 红外Demo相关监听操作 public Action posAction; public Action infraredShootAction; public Action crosshairAction; #endregion void Awake() { //初始化 ins = this; Debug.Log("ConnetDevicesSingle!"); OnUpdateState(); } //获取一次初始值 public void OnInitConfig() { } public void OnUpdateState() { } public void ButtonShoot() { posAction?.Invoke(new Vector3(1 * Screen.width, 1 * Screen.height, 0)); //调用游戏中的射箭接口 if (ArmBow.ins) { ArmBow.ins.ADS_fire(true); } else { OnGameShoot?.Invoke(10); } } public void onShowCrosshair() { //显示游戏光标 PlayerPrefs.SetInt("CrossHairImageActive", 1); bCrosshair = true; crosshairAction?.Invoke(true); } public void onHideCrosshair() { //隐藏游戏光标 PlayerPrefs.SetInt("CrossHairImageActive", 0); bCrosshair = false; crosshairAction?.Invoke(false); } // Start is called before the first frame update void Start() { } public void ConnectBLE() { logOutBow("连接蓝牙 BluetoothHelperAndroid"); if (BluetoothHelperAndroid.IsBluetoothEnabled() == false) { Debug.Log("蓝牙未打开!"); logOutBow("蓝牙未打开!"); return; } if (BluetoothHelperAndroid.RequestBluetoothPermissions(ConnectBLE, (permission) => { if (permission.Contains("LOCATION")) { logOutBow("LOCATION 权限未开!"); } else if (permission.Contains("BLUETOOTH")) { logOutBow("蓝牙权限未开!"); } })) return; data = ""; try { BluetoothHelper.BLE = true; bluetoothHelperBow = BluetoothHelper.GetNewInstance(); bluetoothHelperBow.OnConnected += OnConnected; bluetoothHelperBow.OnConnectionFailed += OnConnectionFailed; bluetoothHelperBow.OnScanEnded += OnScanEnded2; bluetoothHelperBow.OnCharacteristicChanged += (helper, value, characteristic) => { //if (helper != bluetoothHelperBow) return; byte[] bytes = value; //九轴数据反馈 //Debug.Log("Hex:"+ ToHexStrFromByte(bytes) +" ,长度:"+ bytes.Length); if (inputStr == "M") ParseMacAddress(value); if (bytes.Length != 27 && bytes.Length != 39) { if (bytes.Length == 2) { if (bytes[0] == 0x66 && bytes[1] == 0x31) { //短按功能键 logOutBow("短按功能键"); } else if (bytes[0] == 0x66 && bytes[1] == 0x32) { //长按功能键 logOutBow("长按功能键"); } else if (bytes[1] == 10) { //电量 logOutBow("电量:"+ bytes[0]); } } else if (bytes[0] == 0x5b) { // Debug.Log("射箭:" + bytes.ToString()); //logOutBow("射箭"); //红外线检测到射箭 OnInfraredDataReceived(bytes); } } if (inputStr != "") { Debug.Log("清除指令:"+ inputStr); inputStr = ""; } }; } catch (Exception e) { Debug.LogError(e); } StartCoroutine(delayStartBle()); } //Bow扫描结束 void OnScanEnded2(BluetoothHelper helper, LinkedList nearbyDevices) { Debug.Log("2 ended " + nearbyDevices.Count); logOutBow("2 ended " + nearbyDevices.Count); if (nearbyDevices.Count == 0) { isBowScanning = helper.ScanNearbyDevices(); return; } isBowScanning = false; foreach (BluetoothDevice device in nearbyDevices) { Debug.Log(device.DeviceName); if (device.DeviceName == bowTargetDeviceName) { logOutBow("匹配设备 " + device.DeviceName); bowDeviceName = bowTargetDeviceName; helper.setDeviceName(bowDeviceName); helper.Connect(); isBowConnecting = true; } } } void OnConnected(BluetoothHelper helper) { if (helper.getId() == bluetoothHelperBow.getId()) { Debug.Log("2 连接成功:" + helper.getDeviceName()); logOutBow("弓箭 连接成功:" + helper.getDeviceName()); isBowConnecting = false; foreach (BluetoothHelperService service in helper.getGattServices()) { if (service.getName().ToLower().StartsWith(bowDeviceService)) { bluetoothServiceBow = service; foreach (BluetoothHelperCharacteristic characteristic in service.getCharacteristics()) { if (characteristic.getName().ToLower().StartsWith(bowDeviceCharacteristicWrite)) { characteristicWriteBow = characteristic; } else if (characteristic.getName().ToLower().StartsWith(bowDeviceCharacteristicNotify)) { BluetoothHelperCharacteristic ch = new BluetoothHelperCharacteristic(characteristic.getName()); ch.setService(bluetoothServiceBow.getName()); bluetoothHelperBow.Subscribe(ch); } } } } StartCoroutine(InitWhenConnected()); } } void OnConnectionFailed(BluetoothHelper helper) { Debug.Log("Connection lost"); if (helper.getId() == bluetoothHelperBow.getId()) { isBowConnecting = false; _receivedDataCount = 0; macAddress = null; moduleInited = false; logOutBow("弓箭断开连接"); } } IEnumerator delayStartBle() { yield return new WaitForSeconds(1.0f); OnBowScanNearbyDevices(); } //Connect bow ble public void OnBowScanNearbyDevices() { if (bluetoothHelperBow == null) { return; } if (!bluetoothHelperBow.isConnected() && !isBowScanning && !isBowConnecting) { //未连接,未扫描,不是连接中 if (BluetoothHelperAndroid.IsBluetoothEnabled() == false) { Debug.Log("exception1"); logOutBow("蓝牙未打开"); return; } if (BluetoothHelperAndroid.RequestBluetoothPermissions(() => { //开始扫描。调用最后一个bluetoothHelperBow扫描即可 isBowScanning = bluetoothHelperBow.ScanNearbyDevices(); logOutBow("开始扫描连接!"); }, (permission) => { if (permission.Contains("LOCATION")) { Debug.LogError("Exception:LOCATION"); logOutBow("Exception:LOCATION"); } else if (permission.Contains("BLUETOOTH")) { Debug.LogError("Exception: BLUETOOTH"); logOutBow("Exception:BLUETOOTH"); } })) return; isBowScanning = bluetoothHelperBow.ScanNearbyDevices(); logOutBow("开始扫描连接!"); } else if (!bluetoothHelperBow.isConnected() && isBowScanning) { //扫描中 logOutBow("扫描中..."); } else if (bluetoothHelperBow.isConnected()) { logOutBow("断开Bow连接"); //已连接,断开连接 bluetoothHelperBow.Disconnect(); } } private void Update() { LoopHandleCommands(); } void OnDestroy() { if (bluetoothHelperBow != null) { bluetoothHelperBow.Disconnect(); OnDisconnect(); } } /// /// 字节数组转16进制字符串:空格分隔 /// /// /// public string ToHexStrFromByte(byte[] byteDatas) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < byteDatas.Length; i++) { builder.Append(string.Format("{0:X2} ", byteDatas[i])); } return builder.ToString().Trim(); } /// /// 字符串转16进制 /// /// 要转格式的字符串 public byte[] Convert16(string strText) { strText = strText.Replace(" ", ""); strText = strText.Replace(" ", ""); Debug.Log(strText); byte[] bText = new byte[strText.Length / 2]; for (int i = 0; i < strText.Length / 2; i++) { bText[i] = Convert.ToByte(Convert.ToInt32(strText.Substring(i * 2, 2), 16)); } return bText; } string getBytesToIndex(byte[] bytes, int index) { StringBuilder builderCrosshair = new StringBuilder(); builderCrosshair.Append(string.Format("{0:X2}", bytes[index])); return builderCrosshair.ToString().Trim(); } float onVectorValue(byte[] bytes, int startIndex) { //整数部分 string intPartHex = string.Format("{0:X2}", bytes[startIndex]).ToString().Trim(); ; int intPartNumber = Convert.ToInt32(intPartHex, 16); StringBuilder builder = new StringBuilder(); for (int i = 0; i < 2; i++) { builder.Append(string.Format("{0:X2}", bytes[i + startIndex + 1])); } string dataHex = builder.ToString().Trim(); int decimalPartNumber = Convert.ToInt32(dataHex, 16); return intPartNumber + (float)decimalPartNumber / 32768; } #region 自动进入/退出休眠状态, 这里做程指令发送队列,为了控制连续发送指令的间隔,避免硬件收不到或处理不过来 public bool moduleInited; private string _connectedHandlerID = ""; IEnumerator InitWhenConnected() { string myConnectedHandlerID = Guid.NewGuid().ToString(); _connectedHandlerID = myConnectedHandlerID; yield return new WaitForSecondsRealtime(2.2f); Queue cmds = new Queue(new string[] { "M", "b", "b", "1"}); while (cmds.Count > 0) { if ( myConnectedHandlerID != _connectedHandlerID) yield break; string cmd = cmds.Dequeue(); onWriteDataToBow(cmd); yield return new WaitForSecondsRealtime(0.3f); } if ( myConnectedHandlerID != _connectedHandlerID) yield break; //StartCoroutine(LoopRequestBattery(myConnectedHandlerID)); moduleInited = true; //smartBowHelper.InvokeOnBluetoothModuleInited(); StartCoroutine(RequestShoot(myConnectedHandlerID)); } IEnumerator RequestShoot(string myConnectedHandlerID) { yield return new WaitForSeconds(1.0f); if (myConnectedHandlerID == _connectedHandlerID) { AddCommandToQueue("w"); StartCoroutine(RequestAxis(myConnectedHandlerID)); } } IEnumerator RequestAxis(string myConnectedHandlerID) { yield return new WaitForSeconds(1.0f); if (myConnectedHandlerID == _connectedHandlerID) { AddCommandToQueue("3"); } } private List _commands = new List(); void LoopHandleCommands() { if (bluetoothHelperBow == null) return; if (!bluetoothHelperBow.isConnected() || !moduleInited) { if (_commands.Count > 0) _commands.Clear(); return; } if (Time.realtimeSinceStartup - _lastWriteDataTime < 0.2f) return; if (_commands.Count == 0) return; string cmd = _commands[0]; _commands.RemoveAt(0); onWriteDataToBow(cmd); } bool AddCommandToQueue(string cmd) { if (bluetoothHelperBow == null) return false; if (!bluetoothHelperBow.isConnected() || !moduleInited) return false; //如果待插入的指令跟队尾的指令一样,就不要插入了,因为冗余的指令无意义 if (_commands.Count > 0 && _commands[_commands.Count - 1].Equals(cmd)) return true; _commands.Add(cmd); return true; } public void RequestMac() { macAddress = null; AddCommandToQueue("M"); } public void RequestBattery1() { AddCommandToQueue("b"); } public void Request1() { AddCommandToQueue("1"); } public void RequestOpen9Axis() { AddCommandToQueue("3"); } public void RequestClose9Axis() { AddCommandToQueue("4"); } public void RequestOpenInfrared() { AddCommandToQueue("w"); } public void RequestCloseInfrared() { AddCommandToQueue("s"); } #endregion #region 弓箭部分代码操作 public Action OnGameShoot; //[NonSerialized] public byte byteTime1; //[NonSerialized] public byte byteTime2; /**通过红外线数据进行射击 */ /// /// 游戏里箭的速度 /// public float gameArrowSpeed = 0; /// /// 上一次的射击数据ID /// private int _lastShootID = -1; /// /// 当收到红外数据时 /// /// public void OnInfraredDataReceived(byte[] bytes) { //序号 int id = bytes[1]; if (id == _lastShootID) return; //因为硬件为了避免丢包,会连续发几条相同射击通知过来 _lastShootID = id; //时区1耗时 float time1 = bytes[2] * 0.1f; //时区2耗时 float time2 = bytes[3] * 0.1f; //时区耗时总和 float totalTime = time1 + time2; //校验和 int sumCheck = (bytes[0] + bytes[1] + bytes[2] + bytes[3]) & 0xff; //校验和比较结果 bool sumCheckRes = sumCheck == bytes[4]; //实体箭速度 float solidArrowSpeed = 0.05f / (totalTime / 1000f); //通过动能定理求箭的速度(实体箭质量*实体箭速度^2=游戏中箭的质量*游戏中箭的速度^2) gameArrowSpeed = Mathf.Sqrt(solidArrowSpeed * solidArrowSpeed * CommonConfig.arrowWeight / UserSettings.ins.actualArrowWeight); //打印 string logInfo = $"序号{id},时区1:{time1}毫秒,时区2:{time2}毫秒,校验:{sumCheckRes},实体箭速度:{solidArrowSpeed}m/s,游戏箭速度:{gameArrowSpeed}m/s"; logOutBow( logInfo); //收到正确的射箭数据,就回复硬件,否则n毫秒后硬件会认为丢包而进行重传 if (sumCheckRes) ReplyInfraredShoot(); //通知九轴算法,因为射箭抖动会使算法计算的姿态角产生误差 //打开准心的时候,直接调用射击接口 if (ArmBow.ins) { ArmBow.ins.ADS_fire(true, gameArrowSpeed); } else if (DuckHunter.SmartBowController.Instance) { DuckHunter.SmartBowController.Instance.OnShooting(gameArrowSpeed); } //smartBowHelper.InvokeOnShooting(gameArrowSpeed); } //断开弓箭连接 void OnDisconnect() { //DestroyWhenDisconenct(); } public void RequestBattery() { onWriteDataToBow("b"); } //回复弓箭红外 public void ReplyInfraredShoot() { onWriteDataToBow("I"); } private float _lastWriteDataTime; public void onWriteDataToBow(string data) { BluetoothHelperCharacteristic ch = new BluetoothHelperCharacteristic(characteristicWriteBow.getName()); ch.setService(bluetoothServiceBow.getName()); bluetoothHelperBow.WriteCharacteristic(ch, data); _lastWriteDataTime = Time.realtimeSinceStartup; //logOutBow("onWriteDataToBow:"+data); inputStr = data; } void ParseMacAddress(byte[] bytes) { //logOutBow("ParseMacAddress!"); string mac = System.Text.Encoding.ASCII.GetString(bytes); if (mac != null) mac = mac.Trim(); if (CheckIsMacValid(mac)) { macAddress = mac; logOutBow("MacAddress解析成功:"+ macAddress); //获取MacAddress对应的校准记录 } else logOutBow("MacAddress解析失败"); } 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; } #endregion } }