Ver código fonte

添加兼容cmd流程,新的sdk连接方式

slambb 2 semanas atrás
pai
commit
8de705241f

+ 253 - 7
SmartBowSDK/BluetoothAim_SDK.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using UnityEngine;
 using ArduinoBluetoothAPI;
 using System.Linq;
+using SmartBowSDK.CMD;
 
 namespace SmartBowSDK
 {
@@ -53,7 +54,6 @@ namespace SmartBowSDK
         public SensorAxisType sensorAxisType { get; set; } = SensorAxisType.NineAxisSensor;
 
 
-
         private void SetStatus(BluetoothStatusEnum newStatus)
         {
             BluetoothStatusEnum oldStatus = bluetoothStatus;
@@ -78,7 +78,6 @@ namespace SmartBowSDK
         {
             DontDestroyOnLoad(gameObject);
             deviceConfig = BluetoothDeviceConfig.GetDefault();
-
         }
 
         void Update()
@@ -104,6 +103,19 @@ namespace SmartBowSDK
 
             return null;
         }
+        /// <summary>
+        /// 根据当前的 isUseCompanionDeviceManager 需求来连接
+        /// </summary>
+        public void ConnectFormType() {
+            if (isUseCompanionDeviceManager)
+            {
+                ConnectCMD();
+            }
+            else {
+                Connect();
+            }
+
+        }
         public void Connect()
         {
             if (BluetoothWindows.IsWindows())
@@ -206,6 +218,12 @@ namespace SmartBowSDK
             //断开连接时候清除AimDeviceInfo
             ResetAimDeviceInfoToNull();
 
+            if (isUseCompanionDeviceManager) {
+                cmdManager.Disconnect();
+                SetStatus(BluetoothStatusEnum.None);
+                return;
+            }
+
             if (BluetoothWindows.IsWindows())
             {
                 if (_bluetoothWindows.Disconnect())
@@ -289,7 +307,9 @@ namespace SmartBowSDK
             if (helper != _bluetoothHelper) return;
             if (bluetoothStatus != BluetoothStatusEnum.Connected) return;
             byte[] bytes = value;
-            if (_receivedDataCount++ < 500) { //旧的逻辑
+            //旧的是500,
+            int limit = isUseCompanionDeviceManager ? 50 : 500;
+            if (_receivedDataCount++ < limit) { //旧的逻辑
                 if (macAddress == null) ParseMacAddress(value);
                 //固件版本号
                 if (deviceVersion == null) ParseDeviceVersion(value);
@@ -438,12 +458,233 @@ namespace SmartBowSDK
             }
         }
 
+        #region 使用CompanionDeviceManager API 管理连接
+
+        public bool isUseCompanionDeviceManager { get; set; } = false;//默认用原本蓝牙扫描连接
+
+        private CMDManager cmdManager;
+        private bool isCMDScanning = false;
+        private string CmdName = "";
+        private string CmdMac = "";
+        private int cmdScanTimeout = 10;
+        /// <summary>
+        /// 设置超时时间
+        /// </summary>
+        /// <param name="timeout"></param>
+        //public void SetCompanionDeviceManagerScanTimeout(int timeout) {
+        //    cmdScanTimeout = timeout;
+        //}
+        /// <summary>
+        /// 初始化 CompanionDeviceManager 连接
+        /// </summary>
+        public void InitCMDManager()
+        {
+            if (cmdManager != null) return;
+
+            cmdManager = new CMDManager();
+            cmdManager.SetUUIDs(deviceConfig.service,deviceConfig.characteristicWrite,deviceConfig.characteristicNotify);
+            cmdManager.OnCMDDeviceFound += OnCMDScanFound;
+            cmdManager.OnCMDScanFailed += OnCMDScanFailed;
+
+            cmdManager.OnCMDBLEConnected += OnCMDBLEConnected;
+            cmdManager.OnCMDBLEDisconnected += OnCMDBLEDisconnected;
+            cmdManager.OnCMDBLEReady += OnCMDBLEReady;
+            cmdManager.OnCMDBLENotify += OnCMDBLENotify;
+
+            cmdManager.OnPermissionDenied += OnCMDPermissionDenied;
+            cmdManager.OnPermissionDontAsk += OnCMDPermissionDontAsk;
+
+
+            SmartBowLogger.Log(this, "CMDManager 已初始化。");
+        }
+        /// <summary>
+        /// 清空 CompanionDeviceManager 信息
+        /// </summary>
+        public void ClearCMDManager() {
+            if (cmdManager != null)
+            {
+                cmdManager.Cleanup();   // 调用 CMDManager 内部彻底清理方法
+
+                cmdManager.OnCMDDeviceFound -= OnCMDScanFound;
+                cmdManager.OnCMDScanFailed -= OnCMDScanFailed;
+
+                cmdManager.OnCMDBLEConnected -= OnCMDBLEConnected;
+                cmdManager.OnCMDBLEDisconnected -= OnCMDBLEDisconnected;
+                cmdManager.OnCMDBLEReady -= OnCMDBLEReady;
+                cmdManager.OnCMDBLENotify -= OnCMDBLENotify;
+
+                cmdManager.OnPermissionDenied -= OnCMDPermissionDenied;
+                cmdManager.OnPermissionDontAsk -= OnCMDPermissionDontAsk;
+                cmdManager = null;
+
+                SmartBowLogger.Log(this, "CMDManager 已清除。");
+            }
+        }
+
+        /// <summary>
+        /// 使用CompanionDeviceManager连接,仅是 Android 平台
+        /// </summary>
+        public void ConnectCMD()
+        {
+            if (!isUseCompanionDeviceManager)
+            {
+                SmartBowLogger.Log(this, $"使用CMD需要设置isUseCompanionDeviceManager");
+                return;
+            }
+            if (isCMDScanning) return;
+            if (bluetoothStatus != BluetoothStatusEnum.None) return;
+
+            if (Application.platform == RuntimePlatform.Android)
+            {
+                if (!BluetoothHelperAndroid_SDK.IsBluetoothEnabled())
+                {
+                    HandleConnectError(BluetoothError.BluetoothNotEnabled, "蓝牙开关未打开");
+                    return;
+                }
+            }
+            try
+            {
+                //_bluetoothHelper.ScanNearbyDevices();
+                //".*"
+                if (isConnectName)
+                {
+                    string _filters = string.IsNullOrEmpty(filters) ? deviceConfig.deviceNameRegex : filters;
+                    string regex = "(" + _filters + ")";
+                    //扫描时候设置一个时间
+                    //cmdManager.scanTimeout = cmdScanTimeout;
+                    cmdManager.StartScan(regex);
+                }
+                else {
+                 
+                    cmdManager.ConnectMac(FormatMacWithColon(connectMacStr));
+                }
+                isCMDScanning = true;
+                SetStatus(BluetoothStatusEnum.Connecting);
+            }
+            catch (Exception e)
+            {
+                CMDHandleConnectError(CMDScanState.UNKNOWN_ERROR, e.ToString());
+            }
+        }
+        /**
+         * mac.Where(c => Uri.IsHexDigit(c))遍历 mac 字符串中的每一个字符 c。
+         * Uri.IsHexDigit(c) 会判断这个字符是否是 16 进制数字(0-9、A-F、a-f)。
+         * 所以这一步会 过滤掉所有非 16 进制的字符,比如 -、:、空格等。
+         * string.Concat(...)
+         * 将过滤后的字符重新拼接成一个新的字符串。
+         * .ToUpper()
+         * 将整个字符串转成大写。
+         */
+        string FormatMacWithColon(string mac)
+        {
+            var hex = string.Concat(mac.Where(c => Uri.IsHexDigit(c))).ToUpper();
+            return string.Join(":", Enumerable.Range(0, hex.Length / 2)
+                                             .Select(i => hex.Substring(i * 2, 2)));
+        }
+
+
+        private void OnCMDScanFound(string name,string mac)
+        {
+            //MAC = D3:5C:89:57:68:DE
+            SmartBowLogger.Log(this, $"[SmartBow] CMD 扫描到设备 MAC = " + mac);
+            // 监听蓝牙准备就绪
+            CmdMac = mac;
+            CmdName = name;
+            //扫描之后也是这里直接连接
+            StartCoroutine(DelayConnect(FormatMacWithColon(CmdMac)));
+        }
+        private IEnumerator DelayConnect(string mac)
+        {
+            yield return new WaitForSeconds(0.3f);
+            cmdManager.ConnectMac(FormatMacWithColon(mac));
+        }
+        private void OnCMDScanFailed(CMDScanState state, string reason) {
+            SmartBowLogger.Log(this, $"[SmartBow] CMD OnCMDScanFailed:"+ reason);
+            SetStatus(BluetoothStatusEnum.None);
+            isCMDScanning = false;
+            StartCoroutine(SafeUICallback(state, reason));
+        }
+
+        IEnumerator SafeUICallback(CMDScanState state, string reason)
+        {
+            yield return new WaitForEndOfFrame();
+            yield return null;  // 再等一帧
+            smartBowHelper.InvokeOnCMDState(state, reason);
+        }
+
+        private void OnCMDBLEDisconnected() {
+            SmartBowLogger.Log(this, $"[SmartBow] CMD OnCMDBLEDisconnected.");
+            Disconnect();
+            isCMDScanning = false;
+        }
+        /// <summary>
+        /// 连接上准备好
+        /// </summary>
+        private void OnCMDBLEConnected()
+        {
+            SmartBowLogger.Log(this, $"[SmartBow] CMD OnCMDBLEConnected.");
+        }
+        /// <summary>
+        /// 获取到特征值时候触发
+        /// </summary>
+        private void OnCMDBLEReady() {
+            SmartBowLogger.Log(this, $"[SmartBow] CMD OnCMDBLEReady.");
+            SetStatus(BluetoothStatusEnum.Connected);
+            isCMDScanning = false;
+            //指令区分
+            if (sensorAxisType == SensorAxisType.SixAxisSensor)
+            {
+                //6轴指令
+                StartCoroutine(InitWhenConnected6Axis());
+            }
+            else
+            {
+                //九轴
+                StartCoroutine(InitWhenConnected());
+            }
+        }
+
+        private void OnCMDBLENotify(byte[] value) {
+
+            if (sensorAxisType == SensorAxisType.SixAxisSensor)
+            {
+                // 6轴指令
+                OnCharacteristicChanged6Axis(null, value, null);
+            }
+            else
+            {
+                // 九轴
+                OnCharacteristicChanged(null, value, null);
+            }
+        }
+
+        public void OnCMDPermissionDenied(string perm) {
+            SmartBowLogger.LogError(this,$"[App] 用户拒绝权限:{perm}");
+
+            CMDHandleConnectError(CMDScanState.CMDDenied, $"拒绝权限:{perm}");
+        }
+        public void OnCMDPermissionDontAsk(string perm)
+        {
+            SmartBowLogger.LogError(this, $"[App] 用户永久拒绝权限:{perm}");
+            CMDHandleConnectError(CMDScanState.CMDDontAsk, $"永久拒绝权限:{perm}");
+        }
+        private void CMDHandleConnectError(CMDScanState state, string message)
+        {
+            smartBowHelper.InvokeOnCMDState(state, message);
+        }
+        #endregion
+
 
         private float _lastWriteDataTime;
         bool WriteData(string data)
         {
             try
             {
+                if (isUseCompanionDeviceManager)
+                {
+                    return cmdManager.WriteString(data);
+                }
+
                 if (BluetoothWindows.IsWindows())
                 {
                     return _bluetoothWindows.Write(data);
@@ -464,6 +705,11 @@ namespace SmartBowSDK
         {
             try
             {
+                if (isUseCompanionDeviceManager)
+                {
+                    return cmdManager.WriteBytes(data);
+                }
+
                 if (BluetoothWindows.IsWindows())
                 {
                     return _bluetoothWindows.WriteByte(data);
@@ -717,10 +963,10 @@ namespace SmartBowSDK
                 deviceVersion = version;
                 SmartBowLogger.Log(this, "DeviceVersion解析成功:" + version);
             }
-            else
-            {
-                SmartBowLogger.LogWarning(this, "DeviceVersion解析失败");
-            }
+            //else
+            //{
+            //    SmartBowLogger.LogWarning(this, "DeviceVersion解析失败");
+            //}
         }
 
         #endregion

+ 12 - 6
SmartBowSDK/BluetoothDeviceConfig.cs

@@ -5,15 +5,12 @@ namespace SmartBowSDK
     public class BluetoothDeviceConfig
     {
         public string deviceName;
+        public string deviceNameRegex;
         public string service;
         public string characteristicWrite;
         public string characteristicNotify;
 
-        public static BluetoothDeviceConfig GetDefault()
-        {
-            BluetoothDeviceConfig config = new BluetoothDeviceConfig();
-            //两个设备匹配
-            config.deviceName  = string.Join(" | ", new[] {
+        string[] deviceList = new[] {
                 "Bbow_20210501",
                 "HOUYI Pro",
                 "Pistol",
@@ -23,7 +20,16 @@ namespace SmartBowSDK
                 "ARTEMIS Pro",//游戏弓
                 "APOLLO", //KIT
                 "WDF-Baseball"//棒球
-            });
+            };
+
+        public static BluetoothDeviceConfig GetDefault()
+        {
+            BluetoothDeviceConfig config = new BluetoothDeviceConfig();
+            //两个设备匹配
+            config.deviceName  = string.Join(" | ", config.deviceList);
+            //不留空格.用于正则
+            config.deviceNameRegex = string.Join("|", config.deviceList);
+
             if (Application.platform == RuntimePlatform.Android)
             {
                 config.service = "0000fff0";

+ 1 - 1
SmartBowSDK/BluetoothError.cs

@@ -26,6 +26,6 @@
         /// <summary>
         /// mac地址和设备不匹配
         /// </summary>
-        MacAddressAndDeviceMismatch,
+        MacAddressAndDeviceMismatch
     }
 }

+ 44 - 0
SmartBowSDK/CMDBleCallbackProxy.cs

@@ -0,0 +1,44 @@
+using System;
+using UnityEngine;
+
+namespace SmartBowSDK.CMD
+{
+    public class CMDBleCallbackProxy : AndroidJavaProxy
+    {
+        public Action OnBLEConnectedEvent;
+        public Action OnBLEDisconnectedEvent;
+        public Action OnBLEReadyEvent;
+        public Action<byte[]> OnBLENotifyEvent;
+
+        public CMDBleCallbackProxy()
+            : base("com.ble.mycdmmanager.CMDBleCallback") { }
+
+        // Java -> Unity: 蓝牙连接成功
+        void onBLEConnected()
+        {
+           // Debug.Log("[CMDBleCallbackProxy] onBLEConnected");
+            OnBLEConnectedEvent?.Invoke();
+        }
+
+        // Java -> Unity: 蓝牙断开
+        void onBLEDisconnected()
+        {
+           // Debug.Log("[CMDBleCallbackProxy] onBLEDisconnected");
+            OnBLEDisconnectedEvent?.Invoke();
+        }
+
+        // Java -> Unity: 蓝牙服务可用
+        void onBLEReady()
+        {
+           // Debug.Log("[CMDBleCallbackProxy] onBLEReady");
+            OnBLEReadyEvent?.Invoke();
+        }
+
+        // Java -> Unity: 接收到 Notify 数据
+        void onBLENotify(byte[] data)
+        {
+            //Debug.Log("[CMDBleCallbackProxy] onBLENotify: " + BitConverter.ToString(data));
+            OnBLENotifyEvent?.Invoke(data);
+        }
+    }
+}

+ 523 - 0
SmartBowSDK/CMDManager.cs

@@ -0,0 +1,523 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using UnityEngine;
+using UnityEngine.Android;
+
+namespace SmartBowSDK.CMD
+{
+    public class CMDManager
+    {
+        // ---- GC 防止回调被销毁 ----
+        private CMDScannerCallbackProxy scannerProxy;
+        private CMDBleCallbackProxy bleCallbackProxy;
+
+        private string pendingScanPattern = null;
+
+        public event Action<string, string> OnCMDDeviceFound;
+        public event Action<CMDScanState,string> OnCMDScanFailed;
+
+        public event Action OnCMDBLEConnected;
+        public event Action OnCMDBLEDisconnected;
+        public event Action OnCMDBLEReady;
+        public event Action<byte[]> OnCMDBLENotify;
+
+        public event Action<string> OnPermissionDenied;     // 某权限被拒绝
+        public event Action<string> OnPermissionDontAsk;   // 被永久拒绝
+
+        //public int scanTimeout = 10;    // 传超时 ,默认10秒
+        // Android 12+
+        private readonly string[] BLE_PERMISSIONS_31 =
+        {
+            "android.permission.BLUETOOTH_SCAN",
+            "android.permission.BLUETOOTH_CONNECT"
+        };
+
+        // Android 6~11
+        private readonly string[] BLE_PERMISSIONS =
+        {
+            Permission.FineLocation
+        };
+
+
+        // ----------- 统一复用对象 -----------
+        private AndroidJavaObject unityActivity;     // currentActivity
+        private AndroidJavaClass bleManagerClass;    // CMDBleManager.class
+        private AndroidJavaObject bleManagerInst;    // CMDBleManager.getInstance()
+        private AndroidJavaClass transparentProxyActivityClass;    // CMDBleManager.class
+
+
+        // ========= 队列 & 后台解析线程 =========
+        private readonly ConcurrentQueue<byte[]> notifyQueue = new ConcurrentQueue<byte[]>();
+        private readonly AutoResetEvent notifyEvent = new AutoResetEvent(false);
+        private Thread notifyWorker;
+        private volatile bool notifyWorkerRunning = false;
+
+        // 主线程派发节流:最多每 dispatchIntervalMs 毫秒派发一次到主线程
+        private readonly int dispatchIntervalMs = 0; // 50 Hz 向主线程派发(可调)
+        private long lastDispatchTime = 0;
+
+        // -------- Constructor --------
+        public CMDManager()
+        {
+            // 初始化统一复用对象 --------
+            unityActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer")
+                            .GetStatic<AndroidJavaObject>("currentActivity");
+
+            //bleManagerClass = new AndroidJavaClass("com.ble.mycdmmanager.CMDBleManager");
+            //bleManagerInst = bleManagerClass.CallStatic<AndroidJavaObject>("getInstance");
+            bleManagerInst = new AndroidJavaObject(
+                "com.ble.mycdmmanager.CMDBleManager",
+                unityActivity  // 传 context
+            );
+
+            transparentProxyActivityClass = new AndroidJavaClass("com.ble.mycdmmanager.CMDTransparentProxyActivity");
+
+
+            // BLE callback
+            bleCallbackProxy = new CMDBleCallbackProxy();
+
+            bleCallbackProxy.OnBLEConnectedEvent += () =>
+                OnCMDBLEConnected?.Invoke();
+
+            bleCallbackProxy.OnBLEDisconnectedEvent += () =>
+                OnCMDBLEDisconnected?.Invoke();
+
+            bleCallbackProxy.OnBLEReadyEvent += () =>
+                OnCMDBLEReady?.Invoke();
+
+            bleCallbackProxy.OnBLENotifyEvent += (mergedDataObj) =>
+            {
+                //SmartBowLogger.LogError(this, "[OnBLENotifyEvent]:"+ BitConverter.ToString(data));
+                //long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 
+                //SmartBowLogger.Log(this,$"[CMDBleManager→Unity] NotifyArrived | t={now} | size={data.Length}");
+                //OnCMDBLENotify?.Invoke(data); // 实时调用
+                // 快速入队(非阻塞)
+                //EnqueueNotify(data);
+
+               // --- 1. 使用 Buffer.BlockCopy 安全地将 JNI 数组转换为 C# byte[] ---
+                if (!(mergedDataObj is Array sourceArray))
+                {
+                    Debug.LogError($"[CMDManager] JNI 回调期望一个数组,但接收到: {mergedDataObj?.GetType()}");
+                    return;
+                }
+
+                int totalLen = sourceArray.Length;
+                byte[] mergedData = new byte[totalLen];
+                // BlockCopy 可以在 byte[] 和 sbyte[] 之间进行安全的原始内存复制
+                System.Buffer.BlockCopy(sourceArray, 0, mergedData, 0, totalLen);
+
+                // --- 2. 拆分多条数据 ---
+                int index = 0;
+                while (index + 2 <= mergedData.Length)
+                {
+                    // 确保 Java 和 C# 的字节序一致(您的代码使用了小端序 for BitConverter.ToUInt16)
+                    ushort len = BitConverter.ToUInt16(mergedData, index);
+                    index += 2;
+
+                    if (index + len > mergedData.Length)
+                    {
+                        Debug.LogWarning("[CMDManager] 数据长度超出合并数组范围");
+                        break;
+                    }
+
+                    byte[] singleData = new byte[len];
+                    Array.Copy(mergedData, index, singleData, 0, len);
+                    index += len;
+
+                    // --- 3. 快速入队(非阻塞) ---
+                    EnqueueNotify(singleData); // 使用您的高性能 C# 队列和后台线程
+                }
+            };
+
+            // 启动主线程 dispatcher(如果未存在)
+            MainThreadDispatcher.EnsureCreated();
+
+            // 启动后台线程
+            StartNotifyWorker();
+
+            //bleManagerClass.CallStatic("setGlobalCMDBleCallback", bleCallbackProxy);
+            bleManagerInst.Call("setCmdBleCallback", bleCallbackProxy);
+
+
+        }
+        /// <summary>
+        /// 设置服务特征值
+        /// </summary>
+        /// <param name="service"></param>
+        /// <param name="write"></param>
+        /// <param name="notify"></param>
+        public void SetUUIDs(string service, string write, string notify) {
+
+            bleManagerInst.Call("setUUIDs",  service,  write, notify );
+
+        }
+
+        // -------- 权限处理 --------
+
+        private string[] GetRequiredPermissions() =>
+            GetAndroidSDKInt() >= 31 ? BLE_PERMISSIONS_31 : BLE_PERMISSIONS;
+
+        private bool CheckAllBLEPermissions()
+        {
+            foreach (var p in GetRequiredPermissions())
+            {
+                if (!Permission.HasUserAuthorizedPermission(p))
+                    return false;
+            }
+            return true;
+        }
+
+        private int GetAndroidSDKInt()
+        {
+            using (var v = new AndroidJavaClass("android.os.Build$VERSION"))
+                return v.GetStatic<int>("SDK_INT");
+        }
+
+        private void RequestAllPermissions(Action onAllGranted)
+        {
+            var perms = GetRequiredPermissions();
+            RequestPermissionRecursive(perms, 0, onAllGranted);
+        }
+
+        private void RequestPermissionRecursive(string[] perms, int index, Action onAllGranted)
+        {
+            if (index >= perms.Length)
+            {
+                onAllGranted?.Invoke();
+                return;
+            }
+
+            string permission = perms[index];
+
+            PermissionManager.Request(
+                permission,
+                onGranted: () =>
+                {
+                    // 继续请求下一项权限
+                    RequestPermissionRecursive(perms, index + 1, onAllGranted);
+                },
+                onDenied: () =>
+                {
+                    Debug.LogError($"[CMDManager] 权限被拒绝:{permission}");
+
+                    // ---- 调用事件 ----
+                    OnPermissionDenied?.Invoke(permission);
+                },
+                onDontAsk: () =>
+                {
+                    Debug.LogError($"[CMDManager] 权限永久拒绝:{permission}");
+
+                    // ---- 调用事件 ----
+                    OnPermissionDontAsk?.Invoke(permission);
+                }
+            );
+        }
+        private void EnsureBLEPermissionThen(Action doWork)
+        {
+            if (CheckAllBLEPermissions())
+            {
+                doWork?.Invoke();
+            }
+            else
+            {
+                RequestAllPermissions(() =>
+                {
+                    doWork?.Invoke();
+                });
+            }
+        }
+
+        //     ---- 扫描入口 ----
+
+        public void StartScan(string pattern)
+        {
+            pendingScanPattern = pattern;
+
+            EnsureBLEPermissionThen(() =>
+            {
+                LaunchTransparentProxyScan(pendingScanPattern);
+            });
+        }
+
+
+        // ---- 启动透明 Activity ----
+
+        private void LaunchTransparentProxyScan(string pattern)
+        {
+            // Scanner Callback
+            scannerProxy = new CMDScannerCallbackProxy();
+            scannerProxy.OnDeviceFoundEvent = (name, mac) =>
+            {
+                //Debug.Log("[CMDManager] DeviceFound: " + name + " - " + mac);
+                OnCMDDeviceFound?.Invoke(name, mac);
+            };
+
+            scannerProxy.OnScanFailedEvent = (state,reason) =>
+            {
+                //Debug.LogError("[CMDManager] ScanFailed: " + reason);
+                OnCMDScanFailed?.Invoke(state,reason);
+            };
+            // ---- 设置静态回调 ----
+            transparentProxyActivityClass.SetStatic("globalCMDScannerCallback", scannerProxy);
+            // ---- 启动 Activity ----
+            AndroidJavaObject intent = new AndroidJavaObject(
+                "android.content.Intent",
+                unityActivity,
+                new AndroidJavaClass("com.ble.mycdmmanager.CMDTransparentProxyActivity")
+            );
+            //扫描设备名字字符串
+            intent.Call<AndroidJavaObject>("putExtra", "pattern", pattern);
+   
+            //intent.Call<AndroidJavaObject>("putExtra", "timeout_sec", scanTimeout);
+
+            //intent.Call<AndroidJavaObject>("addFlags", 0x10000000); // FLAG_ACTIVITY_NEW_TASK
+
+            unityActivity.Call("startActivity", intent);
+        }
+        /// <summary>
+        /// 通过 MAC 地址连接设备
+        /// </summary>
+        public bool ConnectMac(string mac)
+        {
+            if (string.IsNullOrEmpty(mac)) return false;
+
+            bool result = false;
+
+            EnsureBLEPermissionThen(() =>
+            {
+                result = bleManagerInst.Call<bool>("connectMac", mac);
+            });
+
+            return result;
+        }
+
+        /**
+         * 断开cmd蓝牙连接
+         */
+        public void Disconnect()
+        {
+            try
+            {
+                bleManagerInst.Call("disconnect");
+                //清空队列
+                ClearNotifyQueue();
+                SmartBowLogger.Log(this, "[CMDManager] Disconnect called (OK)");
+            }
+            catch (Exception e)
+            {
+                SmartBowLogger.LogError(this, "[CMDManager] Disconnect error: " + e);
+            }
+        }
+        /**
+         * 清空cmd manager
+         */
+        public void Cleanup()
+        {
+            try
+            {
+                SmartBowLogger.Log(this, "[CMDManager] <Disconnect> begin");
+
+                // 1) 停 Worker
+                StopNotifyWorker();
+
+                // 2) 清空队列
+                ClearNotifyQueue();
+
+                // 3) 注销静态回调,避免旧 CMD 的 notify 残留
+                //try
+                //{
+                //    // 移除 Scanner 回调
+                //    transparentProxyActivityClass.SetStatic<AndroidJavaObject>("globalCMDScannerCallback", null);
+                //    // 移除 BLE 回调
+                //    bleManagerClass.CallStatic("setGlobalCMDBleCallback", null);
+
+                //    SmartBowLogger.Log(this, "[CMDManager] static callbacks cleared");
+                //}
+                //catch (Exception ex)
+                //{
+                //    SmartBowLogger.LogError(this, "[CMDManager] clear static callback error:" + ex);
+                //}
+
+                // 4) 最后再断开 Android BLE
+                bleManagerInst.Call("disconnect");
+
+                SmartBowLogger.Log(this, "[CMDManager] Cleanup done");
+            }
+            catch (Exception e)
+            {
+                SmartBowLogger.LogError(this, "[CMDManager] Cleanup error: " + e);
+            }
+        }
+
+
+
+        public bool SendCommand(string text)
+        {
+            if (string.IsNullOrWhiteSpace(text))
+                return false;
+
+            bool success;
+
+            // 判断是否 HEX(01 02 03)
+            if (System.Text.RegularExpressions.Regex.IsMatch(text, @"^([0-9A-Fa-f]{2}\s*)+$"))
+            {
+                byte[] bytes = ParseHexString(text);
+                success = WriteBytes(bytes);
+            }
+            else
+            {
+                success = WriteString(text);
+            }
+
+            if (!success)
+            {
+                SmartBowLogger.LogError(this,"[CMDManager] Send failed, BLE not ready");
+                return false;
+            }
+
+            SmartBowLogger.Log(this,"[CMDManager] Sent: " + text);
+            return true;
+        }
+
+        public bool WriteString(string str)
+        {
+            try
+            {
+                return bleManagerInst.Call<bool>("writeString", str);
+            }
+            catch (System.Exception e)
+            {
+                SmartBowLogger.LogError(this,"[CMDManager] writeString error: " + e);
+                return false;
+            }
+
+        }
+
+        public bool WriteBytes(byte[] bytes)
+        {
+            try
+            {
+                return bleManagerInst.Call<bool>("writeBytes", bytes);
+            }
+            catch (System.Exception e)
+            {
+                SmartBowLogger.LogError(this,"[CMDManager] writeBytes error: " + e);
+                return false;
+            }
+        }
+
+        private byte[] ParseHexString(string hex)
+        {
+            try
+            {
+                string[] parts = hex.Trim().Split(' ');
+                byte[] bytes = new byte[parts.Length];
+
+                for (int i = 0; i < parts.Length; i++)
+                {
+                    bytes[i] = System.Convert.ToByte(parts[i], 16);
+                }
+
+                return bytes;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+
+        // -------- 队列入队(由 CMDBleCallbackProxy 调用) --------
+        internal void EnqueueNotify(byte[] data)
+        {
+            if (data == null) return;
+            notifyQueue.Enqueue(data);
+            notifyEvent.Set(); // 唤醒后台线程
+        }
+
+        private void StartNotifyWorker()
+        {
+            if (notifyWorkerRunning) return;
+            notifyWorkerRunning = true;
+            notifyWorker = new Thread(NotifyWorkerLoop)
+            {
+                IsBackground = true,
+                Name = "CMDManager-NotifyWorker",
+                Priority = System.Threading.ThreadPriority.AboveNormal // 提升优先级
+            };
+            notifyWorker.Start();
+        }
+
+        private void StopNotifyWorker()
+        {
+            notifyWorkerRunning = false;
+            notifyEvent.Set();
+            try { notifyWorker?.Join(500); } catch { }
+            notifyWorker = null;
+        }
+        private void ClearNotifyQueue()
+        {
+            while (notifyQueue.TryDequeue(out _)) ;
+            SmartBowLogger.Log(this, "[CMDManager] NotifyQueue cleared");
+        }
+        private void NotifyWorkerLoop()
+        {
+            // 后台线程不断消费队列,尽快完成解析工作
+            while (notifyWorkerRunning)
+            {
+                try
+                {
+                    // 等待直到有数据
+                    if (!notifyQueue.TryDequeue(out var data))
+                    {
+                        // 等待唤醒或超时
+                        notifyEvent.WaitOne(50);
+                        continue;
+                    }
+
+                    // 处理单条数据(在后台线程)
+                    ProcessNotifyBackground(data);
+
+                    // 批量消费:尽可能多地一次性取完队列,以减少唤醒次数
+                    while (notifyQueue.TryDequeue(out data))
+                    {
+                        ProcessNotifyBackground(data);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Debug.LogError("[CMDManager] NotifyWorkerLoop error: " + ex);
+                }
+            }
+        }
+
+        private void ProcessNotifyBackground(byte[] bytes)
+        {
+            // 直接传引用,不做限频派发
+            MainThreadDispatcher.Enqueue(() =>
+            {
+                OnCMDBLENotify?.Invoke(bytes);
+            });
+            // 后台处理完毕后,只做限频派发
+            //DispatchToMainThreadWithThrottle(bytes);
+        }
+
+        // 将数据派发到主线程,但节流:每 dispatchIntervalMs 才发一次
+        private void DispatchToMainThreadWithThrottle(byte[] processed)
+        {
+            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            if (now - lastDispatchTime >= dispatchIntervalMs)
+            {
+                lastDispatchTime = now;
+                // 直接传引用,不做 Array.Copy
+                MainThreadDispatcher.Enqueue(() =>
+                {
+                    OnCMDBLENotify?.Invoke(processed);
+                });
+            }
+        }
+
+    }
+}

+ 36 - 0
SmartBowSDK/CMDScanState.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SmartBowSDK.CMD
+{
+    //| Android 回调 | Unity 得到的 CMDScanState | error 内容                   |
+    //| ----------   | ----------------------    | ---------------------------- |
+    //| 超时         | TIMEOUT                   | "Scan Timeout"               |
+    //| 用户取消     | USER_CANCEL               | "User canceled selection"    |
+    //| 双击退出     | DOUBLE_TAP_EXIT           | "User double tapped to exit" |
+    //| 设备 null    | DEVICE_NULL               | "Device is NULL"             |
+    //| 异常         | UNKNOWN_ERROR             | e.getMessage()               |
+
+    public enum CMDScanState
+    {
+        SUCCESS = 0,
+        TIMEOUT = 1,
+        USER_CANCEL = 2,
+        DEVICE_NULL = 3,
+        DATA_NULL = 4,
+        UNKNOWN_ERROR = 5,
+        DOUBLE_TAP_EXIT = 6,
+
+        /// <summary>
+        /// CompanionDeviceManager 拒绝权限
+        /// </summary>
+        CMDDenied,
+
+        /// <summary>
+        /// CompanionDeviceManager 永久拒绝
+        /// </summary>
+        CMDDontAsk
+    }
+
+}

+ 27 - 0
SmartBowSDK/CMDScannerCallbackProxy.cs

@@ -0,0 +1,27 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+namespace SmartBowSDK.CMD
+{
+    public class CMDScannerCallbackProxy : AndroidJavaProxy
+    {
+        public System.Action<string,string> OnDeviceFoundEvent;
+        public System.Action<CMDScanState,string> OnScanFailedEvent;
+
+        public CMDScannerCallbackProxy()
+            : base("com.ble.mycdmmanager.CMDScannerCallback") { }
+
+        void onDeviceFound(string name,string address)
+        {
+            OnDeviceFoundEvent?.Invoke(name,address);
+        }
+
+        void onScanFailed(AndroidJavaObject state, string error)
+        {
+            int code = state.Get<int>("code");
+            CMDScanState scanState = (CMDScanState)code;
+            OnScanFailedEvent?.Invoke(scanState,error);
+        }
+    }
+
+}

+ 49 - 0
SmartBowSDK/MainThreadDispatcher.cs

@@ -0,0 +1,49 @@
+// MainThreadDispatcher.cs
+// 非常轻量的主线程派发器(单例 MonoBehaviour)
+// CMDManager 会自动 EnsureCreated()
+
+using System;
+using System.Collections.Concurrent;
+using UnityEngine;
+
+public class MainThreadDispatcher : MonoBehaviour
+{
+    private static MainThreadDispatcher _instance;
+    private static readonly ConcurrentQueue<Action> queue = new ConcurrentQueue<Action>();
+
+    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
+    private static void InitOnLoad()
+    {
+        // 清理旧实例引用(域重载时)
+        _instance = null;
+    }
+
+    public static void EnsureCreated()
+    {
+        if (_instance != null) return;
+        var go = new GameObject("MainThreadDispatcher");
+        DontDestroyOnLoad(go);
+        _instance = go.AddComponent<MainThreadDispatcher>();
+    }
+
+    public static void Enqueue(Action action)
+    {
+        if (action == null) return;
+        queue.Enqueue(action);
+    }
+
+    private void Update()
+    {
+        while (queue.TryDequeue(out var action))
+        {
+            try
+            {
+                action();
+            }
+            catch (Exception e)
+            {
+                Debug.LogError("[MainThreadDispatcher] action error: " + e);
+            }
+        }
+    }
+}

+ 48 - 0
SmartBowSDK/PermissionManager.cs

@@ -0,0 +1,48 @@
+using System;
+using UnityEngine;
+using UnityEngine.Android;
+namespace SmartBowSDK
+{
+    public static class PermissionManager
+    {
+        public static void Request(string permission, Action onGranted, Action onDenied = null, Action onDontAsk = null)
+        {
+            // ÒÑÓÐȨÏÞ
+            if (Permission.HasUserAuthorizedPermission(permission))
+            {
+                Debug.Log($"[Permission] Already Granted: {permission}");
+                onGranted?.Invoke();
+                return;
+            }
+
+            Debug.Log($"[Permission] Request: {permission}");
+
+            var callbacks = new PermissionCallbacks();
+
+            callbacks.PermissionGranted += p =>
+            {
+                Debug.Log($"[Permission] Granted: {p}");
+                onGranted?.Invoke();
+            };
+
+            callbacks.PermissionDenied += p =>
+            {
+                Debug.Log($"[Permission] Denied: {p}");
+                onDenied?.Invoke();
+            };
+
+            callbacks.PermissionDeniedAndDontAskAgain += p =>
+            {
+                Debug.Log($"[Permission] DontAskAgain: {p}");
+                onDontAsk?.Invoke();
+            };
+
+            Permission.RequestUserPermission(permission, callbacks);
+        }
+
+        public static bool Has(string permission)
+        {
+            return Permission.HasUserAuthorizedPermission(permission);
+        }
+    }
+}

+ 61 - 2
SmartBowSDK/SmartBowHelper.cs

@@ -1,4 +1,5 @@
 using System;
+using SmartBowSDK.CMD;
 using UnityEngine;
 
 namespace SmartBowSDK
@@ -332,8 +333,45 @@ namespace SmartBowSDK
 
             SmartBowLogger.Log(this, "SetSensorAxisType:" + bluetoothAim.sensorAxisType);
         }
+        /// <summary>
+        /// 设置cmd超时扫描时间
+        /// </summary>
+        /// <param name="timeout"></param>
+        //public void SetCompanionDeviceManagerScanTimeout(int timeout = 10) {
+        //    bluetoothAim.SetCompanionDeviceManagerScanTimeout(timeout);
+        //}
+
+        /// <summary>
+        /// 初始化 使用CompanionDeviceManager api 管理连接
+        /// 切换是否使用 CompanionDeviceManager 模式。
+        /// </summary>
+        /// <param name="isCMD">是否使用CompanionDeviceManager连接</param>
+        public bool UseCompanionDeviceManager(bool isUseCompanionDeviceManager = true)
+        {
+            if (bluetoothAim.bluetoothStatus != BluetoothStatusEnum.None)
+            {
+                SmartBowLogger.LogError(this,
+                    "[SetUseCompanionDeviceManager] 只能在断开状态下调用!");
+                return bluetoothAim.isUseCompanionDeviceManager;
+            }
+
+            bluetoothAim.isUseCompanionDeviceManager = isUseCompanionDeviceManager;
 
+            if (isUseCompanionDeviceManager)
+                bluetoothAim.InitCMDManager();
+            else
+                bluetoothAim.ClearCMDManager();
+
+            return isUseCompanionDeviceManager;
+        }
 
+        /// <summary>
+        /// 获取 CompanionDeviceManager api 管理 状态
+        /// </summary>
+        public bool GetUseCompanionDeviceManagerStatus()
+        {
+            return bluetoothAim.isUseCompanionDeviceManager;
+        }
         /// <summary>
         /// 连接蓝牙模块
         /// 新增连接操作,需要用户标签和设备id
@@ -356,8 +394,9 @@ namespace SmartBowSDK
             //判断mac需要创建一个连接信息
             bluetoothAim.initAimDeviceInfo(userTags,deviceId,bRecreateDeviceInfo);
             //进行蓝牙连接
-            bluetoothAim.Connect();
+            bluetoothAim.ConnectFormType();
         }
+
         /// <summary>
         /// 清空建立的蓝牙判断数据
         /// userTags: 存储的信息标签,按实际用户区分
@@ -437,7 +476,10 @@ namespace SmartBowSDK
         /// 监听蓝牙错误
         /// </summary>
         public event BluetoothErrorEvent OnBluetoothError;
-
+        /// <summary>
+        /// 监听CMD状态
+        /// </summary>
+        public event CMDStateEvent OnCMDState;
         /// <summary>
         /// 获取Mac地址
         /// Tip:需要模块初始化完成才能获取
@@ -637,6 +679,23 @@ namespace SmartBowSDK
                 Debug.LogError(e);
             }
         }
+        /// <summary>
+        /// CMD状态回调
+        /// </summary>
+        /// <param name="state"></param>
+        /// <param name="message"></param>
+        public delegate void CMDStateEvent(CMDScanState state, string message);
+        internal void InvokeOnCMDState(CMDScanState state, string message)
+        {
+            try
+            {
+                OnCMDState?.Invoke(state, message);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
         internal void InvokeOnFunctionKeyPress()
         {
             try