using System; using System.Collections; using System.Collections.Generic; using System.Text; using UnityEngine; using System.Runtime.InteropServices; namespace SmartBowSDK { /// /// Windows连接BluetoothLE /// 我的扫描逻辑默认了读写特征都在同一服务下 /// public class BleWinHelper : MonoBehaviour { public string LogTag = "BleWinHelper-Log: "; public bool bDebug = false; private void Log(string text) { if (bDebug)Debug.Log(LogTag + text); } private void Warn(string text) { if (bDebug)Debug.LogWarning(LogTag + text); } private void Error(string text) { if (bDebug)Debug.Log(LogTag + text); } private string targetDeviceName = "Bbow_20210501 | ARTEMIS Pro"; private string targetDeviceNameHOUYIPro = "HOUYI Pro | Bbow_20210501"; private string targetDeviceNameGun = "Pistol | Pistol M9 | Bbow_20210501"; private string targetService = "{0000fff0-0000-1000-8000-00805f9b34fb}"; private string targetCharacteristicsNotify = "{0000fff1-0000-1000-8000-00805f9b34fb}"; private string targetCharacteristicsWrite = "{0000fff2-0000-1000-8000-00805f9b34fb}"; private bool isConnectLocking = false; private bool isScanningDevices = false; private bool isScanningServices = false; private bool isScanningCharacteristics = false; private bool isSubscribed = false; private bool isSubscribing = false; private string lastError = null; [SerializeField] private string selectedDeviceId = null; private Dictionary> deviceList = new Dictionary>(); private List serviceList = new List(); private List characteristicsList = new List(); private float _connectedTime = 0; private float _receiveDataTime = 0; private float _heartBeatInterval = 0; private Action OnConnected; /// /// 主动调用Disconnect()不会触发该委托 /// private Action OnConnectionFailed; private static Action OnCharacteristicChanged; /// /// 注册window对象 /// /// 挂载对象 /// 关联的BluetoothWindows /// 提示的log标签 /// public static BleWinHelper RegisterTo(GameObject o,BluetoothWindows bluetoothWindows,string logTip = "first") { //if (_Instance) //{ // Error("Register fail, because only one can be registered."); // return null; //} GameObject obj = new GameObject("BleWinHelper"+ logTip); obj.transform.SetParent(o.transform); BleWinHelper bleWinHelper = obj.AddComponent(); //日志名字 bleWinHelper.LogTag = "BleWinHelper-" + logTip + ": "; bluetoothWindows.Connect = bleWinHelper.Connect; bluetoothWindows.Disconnect = bleWinHelper.Disconnect; bluetoothWindows.Write = bleWinHelper.Write; bluetoothWindows.WriteByte = bleWinHelper.WriteByte; bleWinHelper.OnConnected = () => bluetoothWindows.OnConnected?.Invoke(); bleWinHelper.OnConnectionFailed = () => bluetoothWindows.OnConnectionFailed?.Invoke(); //多个定义共用一个OnCharacteristicChanged OnCharacteristicChanged += (deviceID,bytes) => { if (deviceID == bleWinHelper.selectedDeviceId) { bluetoothWindows.OnCharacteristicChanged?.Invoke(deviceID, bytes); } }; return bleWinHelper; } /// /// 设置心跳检测 /// 1.每次收到的蓝牙数据都视为心跳 /// 2.帮助触发蓝牙断开监听 /// /// 心跳检测间隔 public void SetHeartBeat(float interval) { _heartBeatInterval = interval; } //private static BleWinHelper _Instance; void Awake() { // _Instance = this; } void OnDestroy() { // if (_Instance == this) _Instance = null; BleApi.Quit(); } void Update() { BleApi.ScanStatus status; if (isScanningDevices) { BleApi.DeviceUpdate res = new BleApi.DeviceUpdate(); do { status = BleApi.PollDevice(ref res, false); if (status == BleApi.ScanStatus.AVAILABLE) { if (!deviceList.ContainsKey(res.id)) deviceList[res.id] = new Dictionary() { { "name", "" }, { "isConnectable", "False" } }; if (res.nameUpdated) deviceList[res.id]["name"] = res.name; if (res.isConnectableUpdated) deviceList[res.id]["isConnectable"] = res.isConnectable.ToString(); //deviceList[res.id]["name"] == targetDeviceName //if (targetDeviceName.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True") //{ // selectedDeviceId = res.id; // StopDeviceScan(); //} if (AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.HOUYIPRO) { //需要判断是否是红外弓箭 if (targetDeviceNameHOUYIPro.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True") { selectedDeviceId = res.id; StopDeviceScan(); } } else if (AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.ARTEMISPRO) { //需要判断是否是ARTEMISPRO弓箭 if (targetDeviceName.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True") { selectedDeviceId = res.id; StopDeviceScan(); } } else if (AimHandler.ins.aimDeviceInfo.type == (int)AimDeviceType.Gun) { //需要判断是否是枪 if (targetDeviceNameGun.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True") { selectedDeviceId = res.id; StopDeviceScan(); } } else { //其余的九轴连接 if (targetDeviceName.Contains(deviceList[res.id]["name"]) && deviceList[res.id]["isConnectable"] == "True") { selectedDeviceId = res.id; StopDeviceScan(); } } } else if (status == BleApi.ScanStatus.FINISHED) { StartCoroutine(ScanServiceAndCharacteristicsToSubscribe()); } } while (status == BleApi.ScanStatus.AVAILABLE); } if (isScanningServices) { BleApi.Service res; do { status = BleApi.PollService(out res, false); if (status == BleApi.ScanStatus.AVAILABLE) { serviceList.Add(res.uuid); } else if (status == BleApi.ScanStatus.FINISHED) { isScanningServices = false; } } while (status == BleApi.ScanStatus.AVAILABLE); } if (isScanningCharacteristics) { BleApi.Characteristic res; do { status = BleApi.PollCharacteristic(out res, false); if (status == BleApi.ScanStatus.AVAILABLE) { Log("res.userDescription:"+ res.userDescription+ ",res.uuid:" + res.uuid); if (res.userDescription == "no description available" || //旧设备 res.userDescription == "SPP Write Channel" || //新设备 res.userDescription == "SPP Read Channel") { characteristicsList.Add(res.uuid); } else { //跟着之前代码 characteristicsList.Add(res.userDescription); } //string name = res.userDescription != "no description available" ? res.userDescription : res.uuid; //characteristicsList.Add(name); } else if (status == BleApi.ScanStatus.FINISHED) { isScanningCharacteristics = false; } } while (status == BleApi.ScanStatus.AVAILABLE); } if (isSubscribed) { BleApi.BLEData res; while (BleApi.PollData(out res, false)) { //string text = BitConverter.ToString(res.buf, 0, res.size); // string text = Encoding.ASCII.GetString(res.buf, 0, res.size); byte[] bytes = new byte[res.size]; Array.Copy(res.buf, bytes, res.size); _receiveDataTime = Time.realtimeSinceStartup; OnCharacteristicChanged?.Invoke(res.deviceId, bytes); } } { BleApi.ErrorMessage res; BleApi.GetError(out res); if (lastError != res.msg) { Error(res.msg); lastError = res.msg; //对应设备才断开 if (lastError.Contains("SendDataAsync") && lastError.Contains(selectedDeviceId) && isSubscribed) { HandleConnectFail(); } } } } void LateUpdate() { if ( _heartBeatInterval > 0 && isSubscribed && Time.realtimeSinceStartup - _connectedTime >= _heartBeatInterval && Time.realtimeSinceStartup - _receiveDataTime >= _heartBeatInterval ) { HandleConnectFail(); } } private bool Connect() { if (isConnectLocking || isScanningDevices) { Warn("Connect Invalid, because is in connect."); return false; } BleApi.StartDeviceScan(); isConnectLocking = true; isScanningDevices = true; Log("Start Connect"); return true; } private void StopDeviceScan() { if (!isScanningDevices) return; BleApi.StopDeviceScan(); isScanningDevices = false; } private IEnumerator ScanServiceAndCharacteristicsToSubscribe() { isSubscribing = true; if (selectedDeviceId == null) { HandleConnectFail(); yield break; } Log("SelectedDeviceId OK"); BleApi.ScanServices(selectedDeviceId); isScanningServices = true; serviceList.Clear(); while (isScanningServices) yield return null; bool findTargetService = false; foreach (string service in serviceList) { if (service == targetService) { findTargetService = true; Log("FindTargetService OK"); break; } } if (!findTargetService) { HandleConnectFail(); yield break; } BleApi.ScanCharacteristics(selectedDeviceId, targetService); isScanningCharacteristics = true; characteristicsList.Clear(); while (isScanningCharacteristics) yield return null; bool findTargetCharacteristicsNotify = false; bool findTargetCharacteristicsWrite = false; foreach (string characteristics in characteristicsList) { if (characteristics == targetCharacteristicsNotify) { findTargetCharacteristicsNotify = true; Log("FindTargetCharacteristicsNotify OK"); } else if (characteristics == targetCharacteristicsWrite) { findTargetCharacteristicsWrite = true; Log("FindTargetCharacteristicsWrite OK"); } } if (!findTargetCharacteristicsNotify || !findTargetCharacteristicsWrite) { HandleConnectFail(); yield break; } BleApi.SubscribeCharacteristic(selectedDeviceId, targetService, targetCharacteristicsNotify, false); isSubscribed = true; isSubscribing = false; Log("SubscribeCharacteristicNotify OK"); _connectedTime = Time.realtimeSinceStartup; OnConnected?.Invoke(); } private void HandleConnectFail() { if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId); bool isLockBefore = isConnectLocking; ReinitAfterConnectFail(); if (isLockBefore) OnConnectionFailed?.Invoke(); } private void ReinitAfterConnectFail() { isConnectLocking = false; isScanningDevices = false; isScanningServices = false; isScanningCharacteristics = false; isSubscribed = false; isSubscribing = false; selectedDeviceId = null; deviceList.Clear(); serviceList.Clear(); characteristicsList.Clear(); } private bool Write(string text) { if (!isSubscribed) return false; byte[] payload = Encoding.ASCII.GetBytes(text); BleApi.BLEData data = new BleApi.BLEData(); data.buf = new byte[512]; data.size = (short)payload.Length; data.deviceId = selectedDeviceId; data.serviceUuid = targetService; data.characteristicUuid = targetCharacteristicsWrite; for (int i = 0; i < payload.Length; i++) data.buf[i] = payload[i]; // no error code available in non-blocking mode BleApi.SendData(in data, false); Log("Write(" + text + ")"); return true; } private bool WriteByte(byte[] payload) { if (!isSubscribed) return false; BleApi.BLEData data = new BleApi.BLEData(); data.buf = new byte[512]; data.size = (short)payload.Length; data.deviceId = selectedDeviceId; data.serviceUuid = targetService; data.characteristicUuid = targetCharacteristicsWrite; for (int i = 0; i < payload.Length; i++) data.buf[i] = payload[i]; // no error code available in non-blocking mode BleApi.SendData(in data, false); Log("Write(byte[])"); return true; } private bool Disconnect() { if (!isConnectLocking) { Warn("Disconnect Invalid, because not in connect."); return false; } if (isSubscribing) { Warn("Disconnect Invalid, because is subscribing."); return false; } if (isScanningDevices) StopDeviceScan(); if (selectedDeviceId != null) BleApi.Disconnect(selectedDeviceId); ReinitAfterConnectFail(); Log("Disconnect OK"); return true; } } }