using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using ArduinoBluetoothAPI; namespace SmartBowSDK { public class BluetoothAim_SDK : MonoBehaviour { public SmartBowHelper smartBowHelper; public BluetoothDeviceConfig deviceConfig; private BluetoothHelper _bluetoothHelper; private BluetoothHelperService _bluetoothService; private BluetoothHelperCharacteristic _characteristicWrite; private static HashSet _ScanLocker = new HashSet(); private int _receivedDataCount = 0; public string macAddress = null; //设备固件版本 public string deviceVersion = null; public BluetoothStatusEnum bluetoothStatus; private BluetoothWindows _bluetoothWindows; /// /// 6轴 /// private o06DOF.SixAxisFusion sixAxisFusion; /// /// 过滤名称 /// public string filters { get; set; } = ""; /// /// 是否使用mac过滤 /// public bool isConnectName { get; set; } = true;//默认用名字连接 /// /// 传入的mac /// public string connectMacStr { get; set; } = ""; /// /// 当前的传感器类型 /// public SensorAxisType sensorAxisType { get; set; } = SensorAxisType.NineAxisSensor; private void SetStatus(BluetoothStatusEnum newStatus) { BluetoothStatusEnum oldStatus = bluetoothStatus; if (oldStatus == newStatus) return; bluetoothStatus = newStatus; SmartBowLogger.Log(this, $"oldStatus[{oldStatus}]=>newStatus[{newStatus}]"); if (newStatus == BluetoothStatusEnum.None) { _bluetoothHelper = null; _bluetoothService = null; _characteristicWrite = null; _receivedDataCount = 0; macAddress = null; deviceVersion = null; _battery = 0; moduleInited = false; } smartBowHelper.InvokeOnBluetoothStatusChanged(oldStatus, newStatus); } void Awake() { DontDestroyOnLoad(gameObject); deviceConfig = BluetoothDeviceConfig.GetDefault(); } void Update() { LoopHandleCommands(); } private static int _NoneToConnectingStep = 0; public BluetoothWindows CreateWindowBLE() { //如果是window环境,直接实例化一个BluetoothWindows if (BluetoothWindows.IsWindows()) { _bluetoothWindows = new BluetoothWindows(); _bluetoothWindows.OnConnected = OnConnected_windows; _bluetoothWindows.OnConnectionFailed = OnConnectionFailed_windows; _bluetoothWindows.OnCharacteristicChanged = OnCharacteristicChanged_windows; return _bluetoothWindows; } return null; } public void Connect() { if (BluetoothWindows.IsWindows()) { if (bluetoothStatus == BluetoothStatusEnum.None) { _bluetoothWindows.Connect(); SetStatus(BluetoothStatusEnum.Connecting); //初始化一次六轴解析 if (sixAxisFusion == null) { Vector3 accelOffset = Vector3.zero; Vector3 gyroOffset = Vector3.zero; float gForce = 1.0f; float gyroToDegree = 1.0f; sixAxisFusion = new o06DOF.SixAxisFusion(accelOffset, gyroOffset, gForce, gyroToDegree); } } return; } if (_ScanLocker.Count > 0) return; if (bluetoothStatus != BluetoothStatusEnum.None) return; if (_NoneToConnectingStep > 0) return; _NoneToConnectingStep = 1; if (Application.platform == RuntimePlatform.Android) { if (!BluetoothHelperAndroid_SDK.IsBluetoothEnabled()) { HandleConnectError(BluetoothError.BluetoothNotEnabled, "蓝牙开关未打开"); return; } if (BluetoothHelperAndroid_SDK.RequestBluetoothPermissions(() => { _NoneToConnectingStep = 0; Connect(); }, (permission) => { if (permission.Contains("LOCATION")) { HandleConnectError(BluetoothError.LocationPermissionNotGranted, "尚未授予定位权限"); } else if (permission.Contains("BLUETOOTH")) { HandleConnectError(BluetoothError.ScanPermissionNotGranted, "尚未授予扫描附近设备权限"); } })) return; } try { BluetoothHelper.BLE = true; _bluetoothHelper = BluetoothHelper.GetNewInstance(); _bluetoothHelper.OnConnected += OnConnected; _bluetoothHelper.OnConnectionFailed += OnConnectionFailed; SmartBowLogger.Log(this,"创建蓝牙的SensorAxisType类型:"+ sensorAxisType); if (sensorAxisType == SensorAxisType.SixAxisSensor) { //6轴时候,另外的解析操作 _bluetoothHelper.OnCharacteristicChanged += OnCharacteristicChanged6Axis; //初始化一次六轴解析 if (sixAxisFusion == null) { Vector3 accelOffset = Vector3.zero; Vector3 gyroOffset = Vector3.zero; float gForce = 1.0f; float gyroToDegree = 1.0f; sixAxisFusion = new o06DOF.SixAxisFusion(accelOffset, gyroOffset, gForce, gyroToDegree); } } else { //九轴的解析 _bluetoothHelper.OnCharacteristicChanged += OnCharacteristicChanged; } _bluetoothHelper.OnScanEnded += OnScanEnded; _bluetoothHelper.ScanNearbyDevices(); _NoneToConnectingStep = 2; } catch (Exception e) { HandleConnectError(BluetoothError.Unknown, e.ToString()); } if (_NoneToConnectingStep == 2) { _NoneToConnectingStep = 0; _ScanLocker.Add(_bluetoothHelper); SetStatus(BluetoothStatusEnum.Connecting); } } private void HandleConnectError(BluetoothError error, string message) { _NoneToConnectingStep = 0; smartBowHelper.InvokeOnBluetoothError(error, message); } public void Disconnect() { //断开连接时候清除AimDeviceInfo ResetAimDeviceInfoToNull(); if (BluetoothWindows.IsWindows()) { if (_bluetoothWindows.Disconnect()) SetStatus(BluetoothStatusEnum.None); return; } if (_bluetoothHelper != null) _bluetoothHelper.Disconnect(); SetStatus(BluetoothStatusEnum.None); } void OnConnected(BluetoothHelper helper) { if (helper != _bluetoothHelper) return; SetStatus(BluetoothStatusEnum.Connected); foreach (BluetoothHelperService service in helper.getGattServices()) { if (service.getName().ToLower().StartsWith(deviceConfig.service)) { _bluetoothService = service; foreach (BluetoothHelperCharacteristic characteristic in service.getCharacteristics()) { if (characteristic.getName().ToLower().StartsWith(deviceConfig.characteristicWrite)) { _characteristicWrite = characteristic; } else if (characteristic.getName().ToLower().StartsWith(deviceConfig.characteristicNotify)) { BluetoothHelperCharacteristic c = new BluetoothHelperCharacteristic(characteristic.getName()); c.setService(_bluetoothService.getName()); _bluetoothHelper.Subscribe(c); } } } } //指令区分 if (sensorAxisType == SensorAxisType.SixAxisSensor) { //6轴指令 StartCoroutine(InitWhenConnected6Axis()); } else { //九轴 StartCoroutine(InitWhenConnected()); } } void OnConnected_windows() { SetStatus(BluetoothStatusEnum.Connected); if (sensorAxisType == SensorAxisType.SixAxisSensor) { //6轴指令 StartCoroutine(InitWhenConnected6Axis()); } else { //九轴 StartCoroutine(InitWhenConnected()); } } void OnConnectionFailed(BluetoothHelper helper) { if (helper != _bluetoothHelper) return; SetStatus(BluetoothStatusEnum.None); } void OnConnectionFailed_windows() { SetStatus(BluetoothStatusEnum.None); } /// /// 原九轴的传感器解析 /// /// /// /// void OnCharacteristicChanged(BluetoothHelper helper, byte[] value, BluetoothHelperCharacteristic characteristic) { if (helper != _bluetoothHelper) return; if (bluetoothStatus != BluetoothStatusEnum.Connected) return; byte[] bytes = value; if (_receivedDataCount++ < 500) { //旧的逻辑 if (macAddress == null) ParseMacAddress(value); //固件版本号 if (deviceVersion == null) ParseDeviceVersion(value); } smartBowHelper.aimHandler.OnDataReceived(bytes); } /// /// 六轴传感器时候使用的解析 /// /// /// /// void OnCharacteristicChanged6Axis(BluetoothHelper helper, byte[] value, BluetoothHelperCharacteristic characteristic) { if (helper != _bluetoothHelper) return; if (bluetoothStatus != BluetoothStatusEnum.Connected) return; byte[] bytes = value; if (macAddress == null && _receivedDataCount++ < 500) ParseMacAddress(value); //smartBowHelper.aimHandler.OnDataReceived(bytes); //转字符串后就是 Bat if (bytes.Length == 20 && bytes[19] == 0x0a //Bat && bytes[2] == 0x42 && bytes[3] == 0x61 && bytes[4] == 0x74) { //第一步 解析电量? string betty = System.Text.Encoding.ASCII.GetString(bytes); // SmartBowLogger.Log(this, BitConverter.ToString(bytes).Replace("-", "")); //SmartBowLogger.Log(this, "betty:" + betty); // 第二步:解析JSON字符串 Newtonsoft.Json.Linq.JObject json = Newtonsoft.Json.Linq.JObject.Parse(betty); // 第三步:提取"Bat"的值 int batValue = (int)json["Bat"]; SmartBowLogger.Log(this, "Bat Value: " + batValue); // 输出: 100 battery = batValue; } //传感器数据 if (bytes.Length == 20 && bytes[0] == 0x7b && bytes[19] == 0x7d) { o06DOF.SixData sixData = sixAxisFusion.getSixAxisByBytes(bytes); Quaternion quaternion = sixAxisFusion.tranUpdateData(sixData); //输出欧拉角 pitch yaw roll smartBowHelper.InvokeOnSixAxisRotationUpdate(quaternion.eulerAngles); //smartBowHelper.InvokeOnAxisDataUpdateEvent(bytes); } } /// /// 重置6轴identify /// public void ResetSixAxisFusion() { if (sixAxisFusion != null) { sixAxisFusion.ResetToInitialRotation(); } } /// /// pc调用 /// /// /// void OnCharacteristicChanged_windows(string deviceId, byte[] value) { if (sensorAxisType == SensorAxisType.SixAxisSensor) { //6轴指令 OnCharacteristicChanged6Axis(null, value, null); } else { //九轴 OnCharacteristicChanged(null, value, null); } } void OnScanEnded(BluetoothHelper helper, LinkedList nearbyDevices) { _ScanLocker.Remove(helper); if (helper != _bluetoothHelper) return; foreach (BluetoothDevice device in nearbyDevices) { if (isConnectName) { SmartBowLogger.Log(this, $"发现设备{device.DeviceName},is fileters empty:{ string.IsNullOrEmpty(filters)},name:{filters}"); //if (device.DeviceName == deviceConfig.deviceName) //后续匹配名字 可以是多个设备 string _filters = string.IsNullOrEmpty(filters) ? deviceConfig.deviceName : filters; if (_filters.Contains(device.DeviceName)) { _bluetoothHelper.setDeviceName(device.DeviceName); _bluetoothHelper.Connect(); SmartBowLogger.Log(this, $"Name匹配设备{device.DeviceName}"); return; } } else { SmartBowLogger.Log(this, $"发现设备 {device.DeviceAddress},is connectMacStr:{ connectMacStr },DeviceAddress:{device.DeviceAddress}"); //按mac地址匹配 if (connectMacStr.Contains(device.DeviceAddress)) { _bluetoothHelper.setDeviceName(device.DeviceName); _bluetoothHelper.setDeviceAddress(device.DeviceAddress); _bluetoothHelper.Connect(); SmartBowLogger.Log(this, $"Mac匹配设备{device.DeviceAddress}"); return; } } } SetStatus(BluetoothStatusEnum.None); smartBowHelper.InvokeOnBluetoothError(BluetoothError.ScanNotFoundTargetDevice, "扫描结束后未发现目标设备"); } private float _lastWriteDataTime; bool WriteData(string data) { try { if (BluetoothWindows.IsWindows()) { return _bluetoothWindows.Write(data); } BluetoothHelperCharacteristic c = new BluetoothHelperCharacteristic(_characteristicWrite.getName()); c.setService(_bluetoothService.getName()); _bluetoothHelper.WriteCharacteristic(c, data); _lastWriteDataTime = Time.realtimeSinceStartup; return true; } catch (Exception e) { Debug.LogError(e); return false; } } bool WriteByteData(byte[] data) { try { if (BluetoothWindows.IsWindows()) { return _bluetoothWindows.WriteByte(data); } BluetoothHelperCharacteristic c = new BluetoothHelperCharacteristic(_characteristicWrite.getName()); c.setService(_bluetoothService.getName()); _bluetoothHelper.WriteCharacteristic(c, data); _lastWriteDataTime = Time.realtimeSinceStartup; return true; } catch (Exception e) { Debug.LogError(e); return false; } } public bool moduleInited; private string _connectedHandlerID = ""; IEnumerator InitWhenConnected() { string myConnectedHandlerID = Guid.NewGuid().ToString(); _connectedHandlerID = myConnectedHandlerID; yield return new WaitForSecondsRealtime(BluetoothWindows.IsWindows() ? 0.3f : 2.2f); //获取Mac地址 获取设备固件版本号 获取设备信息(设备类型,系统类型) 获取初始电量 开启发送逻辑 Queue cmds = new Queue(new string[] { "M","V","I", "b", "b", "1"}); while (cmds.Count > 0) { if (bluetoothStatus != BluetoothStatusEnum.Connected || myConnectedHandlerID != _connectedHandlerID) yield break; string cmd = cmds.Dequeue(); WriteData(cmd); yield return new WaitForSecondsRealtime(0.3f); } if (bluetoothStatus != BluetoothStatusEnum.Connected || myConnectedHandlerID != _connectedHandlerID) yield break; StartCoroutine(LoopRequestBattery(myConnectedHandlerID)); moduleInited = true; smartBowHelper.InvokeOnBluetoothModuleInited(); } /// /// 初始化6轴指令 /// /// IEnumerator InitWhenConnected6Axis() { string myConnectedHandlerID = Guid.NewGuid().ToString(); _connectedHandlerID = myConnectedHandlerID; yield return new WaitForSecondsRealtime(BluetoothWindows.IsWindows() ? 0.3f : 2.2f); Queue cmds = new Queue(new string[] { "M", "B", "B" }); while (cmds.Count > 0) { if (bluetoothStatus != BluetoothStatusEnum.Connected || myConnectedHandlerID != _connectedHandlerID) yield break; string cmd = cmds.Dequeue(); WriteData(cmd); yield return new WaitForSecondsRealtime(0.3f); } if (bluetoothStatus != BluetoothStatusEnum.Connected || myConnectedHandlerID != _connectedHandlerID) yield break; StartCoroutine(LoopRequestBattery6Axis(myConnectedHandlerID)); moduleInited = true; smartBowHelper.InvokeOnBluetoothModuleInited(); } private int _battery = 0; public int battery { get => _battery; set { _battery = value; //SmartBowLogger.Log(this, "Battery缓存成功"); } } /// /// 九轴原本的连接电池指令 /// /// /// IEnumerator LoopRequestBattery(string myConnectedHandlerID) { while (bluetoothStatus == BluetoothStatusEnum.Connected && myConnectedHandlerID == _connectedHandlerID) { yield return new WaitForSecondsRealtime(10); AddCommandToQueue("b"); } } /// /// 6轴的连接电池指令 /// /// /// IEnumerator LoopRequestBattery6Axis(string myConnectedHandlerID) { while (bluetoothStatus == BluetoothStatusEnum.Connected && myConnectedHandlerID == _connectedHandlerID) { yield return new WaitForSecondsRealtime(10); AddCommandToQueue("B"); } } private List _commands = new List(); void LoopHandleCommands() { if (bluetoothStatus != BluetoothStatusEnum.Connected || !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); WriteData(cmd); } bool AddCommandToQueue(string cmd) { if (bluetoothStatus != BluetoothStatusEnum.Connected || !moduleInited) return false; //如果待插入的指令跟队尾的指令一样,就不要插入了,因为冗余的指令无意义 if (_commands.Count > 0 && _commands[_commands.Count - 1].Equals(cmd)) return true; _commands.Add(cmd); return true; } public void ReplyInfraredShoot() { if (bluetoothStatus != BluetoothStatusEnum.Connected || !moduleInited) return; WriteData("I"); } public void ReplyByte(byte[] value) { if (bluetoothStatus != BluetoothStatusEnum.Connected || !moduleInited) return; WriteByteData(value); } public bool RequestOpen9Axis() { return AddCommandToQueue("3"); } public bool RequestClose9Axis() { return AddCommandToQueue("4"); } public bool RequestOpenInfrared() { return AddCommandToQueue("w"); } public bool RequestCloseInfrared() { return AddCommandToQueue("s"); } void ParseMacAddress(byte[] bytes) { string mac = System.Text.Encoding.ASCII.GetString(bytes); if (mac != null) mac = mac.Trim(); if (CheckIsMacValid(mac)) { #region 根据aimDeviceInfo存在添加一个判断 if (aimDeviceInfo != null) { //需要增加一个判断,判断是否对应的mac设备。不是需要进行重新连接 if (!aimDeviceInfo.bInitMac) { SmartBowLogger.Log(this, "设置设备mac:" + mac); SetAimDeviceMac(mac); } else if (aimDeviceInfo.mac != mac) { SmartBowLogger.LogError(this, "设备不一样,断开连接"); Disconnect(); //抛出一个错误,给用户处理其他业务流程 smartBowHelper.InvokeOnBluetoothError(BluetoothError.MacAddressAndDeviceMismatch, "验证设备MAC和记录的MAC不匹配!"); return; } } #endregion macAddress = mac; SmartBowLogger.Log(this, "MacAddress解析成功"); //获取MacAddress对应的校准记录 string macXXX = macAddress; smartBowHelper.smartBowNetwork.GetCalibrateRecord(macXXX, (record) => { if (macAddress != macXXX) return; smartBowHelper.aimHandler.ResumeCalibrateRecord(record); }); } else SmartBowLogger.LogWarning(this, "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; } #region 校验固件版本信息 bool CheckIsVersionValid(string version) { if (string.IsNullOrEmpty(version)) return false; if (!version.StartsWith("{")) return false; if (!version.EndsWith("}")) return false; // 去掉 { 和 } string versionInner = version.Substring(1, version.Length - 2); // 固件版本格式 Vx.y.z if (!versionInner.StartsWith("V")) return false; string[] parts = versionInner.Substring(1).Split('.'); if (parts.Length != 3) return false; foreach (var part in parts) { if (!int.TryParse(part, out _)) return false; } return true; } void ParseDeviceVersion(byte[] bytes) { string version = System.Text.Encoding.ASCII.GetString(bytes); if (version != null) version = version.Trim(); if (CheckIsVersionValid(version)) { deviceVersion = version; SmartBowLogger.Log(this, "DeviceVersion解析成功:" + version); } else { SmartBowLogger.LogWarning(this, "DeviceVersion解析失败"); } } #endregion #region 记录的设备处理部分 //连接时候判断信息。存在对象则连接需要判断MAC //记录一个设备 AimDeviceInfo aimDeviceInfo = null; //用户初始化一个保存数据的操作的id。 //不设置情况下不给进行相关操作 string keyPrefix = "aim-device-info-"; string userTags = ""; int deviceId = -1; //清空对应id的数据 public void onClearAimDeviceInfoByDeviceId(string _userTags, int _deviceId) { string deviceInfo = PlayerPrefs.GetString(keyPrefix + _userTags + _deviceId, ""); if (deviceInfo != "") { PlayerPrefs.DeleteKey(keyPrefix + _userTags + _deviceId); ResetAimDeviceInfoToNull(); } } public AimDeviceInfo onGetAimDeviceInfos(string _userTags, int _deviceId) { OnGetAimDeviceInfo(_userTags, _deviceId); //SmartBowLogger.Log(this, $"onGetAimDeviceInfos[{JsonUtility.ToJson(aimDeviceInfo)}]"); return aimDeviceInfo; } //根据设置的 void OnGetAimDeviceInfo(string _userTags, int _deviceId) { string deviceInfo = PlayerPrefs.GetString(keyPrefix + _userTags + _deviceId, ""); if (deviceInfo != "") { aimDeviceInfo = JsonUtility.FromJson(deviceInfo);//这里的类是依据最外层{}决定的 } } void OnSaveAimDeviceInfo(string _userTags, int _deviceId) { if (_userTags.Length == 0) { SmartBowLogger.LogError(this, $"存储标识不存在!"); return; } SmartBowLogger.Log(this, $"OnSaveAimDeviceInfo [{JsonUtility.ToJson(aimDeviceInfo)}]"); PlayerPrefs.SetString(keyPrefix + _userTags + _deviceId, JsonUtility.ToJson(aimDeviceInfo)); } //连接蓝牙调用。连接时候创建一个连接信息,用于记录mac 是否要验证 //传入一个id用于区分是哪个设备 //bRecreateDeviceInfo 是否重新创建 public void initAimDeviceInfo(string _userTags, int _deviceId, bool bRecreateDeviceInfo) { userTags = _userTags; deviceId = _deviceId; if (bRecreateDeviceInfo) { //直接覆盖设备信息 onRecreateAimDeviceInfoById(); } else { //获取设备信息。没有创建 onCreateAimDeviceInfoById(); } } //是否存在AimDeviceInfo,存在获取,不存在创建; void onCreateAimDeviceInfoById() { ResetAimDeviceInfoToNull(); OnGetAimDeviceInfo(userTags, deviceId); if (aimDeviceInfo == null) { aimDeviceInfo = new AimDeviceInfo(deviceId); OnSaveAimDeviceInfo(userTags, deviceId); } } //直接覆盖新元素 void onRecreateAimDeviceInfoById() { // 使用Lambda表达式删除特定条件的元素 ResetAimDeviceInfoToNull(); //添加一个新元素 aimDeviceInfo = new AimDeviceInfo(deviceId); //更新信息 OnSaveAimDeviceInfo(userTags, deviceId); } //APP连接需进行MAC地址的比对。 void SetAimDeviceMac(string _mac) { aimDeviceInfo.setInitMac(_mac); OnSaveAimDeviceInfo(userTags, deviceId); } public void ResetAimDeviceInfoToNull() { aimDeviceInfo = null; } #endregion } #region 添加一个记录设备连接的信息,主要用于判断mac地址是否连接操作 [Serializable]//需要在转换为json格式的类的上方添加序列化 public class AimDeviceInfo { public int id; //区分用户设备对应的id public bool bInitMac = false; //是否初始化Mac public string mac; //记录当前 public AimDeviceInfo(int _id) { this.id = _id; } public void setInitMac(string macTemp) { bInitMac = true; mac = macTemp; } public void resetInitMac() { bInitMac = false; mac = ""; } } #endregion }