Browse Source

sdk版本备份

slambb 1 năm trước cách đây
commit
d31955429e

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.vs/
+/bin/
+/obj/

+ 84 - 0
SmartBowSDK.csproj

@@ -0,0 +1,84 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<TargetFramework>netstandard2.0</TargetFramework>
+		<Version>1.0.1</Version>
+		<AssemblyVersion>1.0.1.0</AssemblyVersion>
+		<FileVersion>1.0.1.0</FileVersion>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<Reference Include="UnityEngine">
+			<HintPath>D:\DeveloperSoftware\2021.3.22f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.dll</HintPath>
+		</Reference>
+		<Reference Include="UnityEngine.JSONSerializeModule">
+			<HintPath>D:\DeveloperSoftware\2021.3.22f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll</HintPath>
+		</Reference>
+		<Reference Include="UnityEngine.CoreModule">
+			<HintPath>D:\DeveloperSoftware\2021.3.22f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll</HintPath>
+		</Reference>
+		<Reference Include="UnityEngine.AndroidJNIModule">
+			<HintPath>D:\DeveloperSoftware\2021.3.22f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll</HintPath>
+		</Reference>
+		<Reference Include="UnityEngine.UnityWebRequestModule">
+			<HintPath>D:\DeveloperSoftware\2021.3.22f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll</HintPath>
+		</Reference>
+		<Reference Include="UnityEngine.UnityWebRequestTextureModule">
+			<HintPath>D:\DeveloperSoftware\2021.3.22f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll</HintPath>
+		</Reference>
+		<Reference Include="Fractions">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/Fractions.dll</HintPath>
+		</Reference>
+		<Reference Include="BouncyCastle.Crypto">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/BouncyCastle.Crypto.dll</HintPath>
+		</Reference>
+		<Reference Include="MathNet.Numerics">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/MathNet.Numerics.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Memory">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Memory.dll</HintPath>
+		</Reference>
+		<Reference Include="o0MagnetometerCalibrater">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/o0MagnetometerCalibrater.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Text.Encoding.CodePages">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Text.Encoding.CodePages.dll</HintPath>
+		</Reference>
+		<Reference Include="o0NetLib">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/o0NetLib.dll</HintPath>
+		</Reference>
+		<Reference Include="SixLabors.ImageSharp">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/SixLabors.ImageSharp.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Buffers">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Buffers.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Reflection.Emit.Lightweight">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Reflection.Emit.Lightweight.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Runtime.CompilerServices.Unsafe">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Reflection.Emit.ILGeneration">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Reflection.Emit.ILGeneration.dll</HintPath>
+		</Reference>
+		<Reference Include="o0NetIMU">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/o0NetIMU.dll</HintPath>
+		</Reference>
+		<Reference Include="zlib.net">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/zlib.net.dll</HintPath>
+		</Reference>
+		<Reference Include="System.Numerics.Vectors">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/System.Numerics.Vectors.dll</HintPath>
+		</Reference>
+		<Reference Include="Mono.Data.Sqlite">
+			<HintPath>../Assets/SmartBowSDK/o0Lib/Mono.Data.Sqlite.dll</HintPath>
+		</Reference>
+		<Reference Include="ArduinoBluetoothAPI">
+			<HintPath>../Assets/Plugins/ArduinoBluetoothAPI.dll</HintPath>
+		</Reference>
+		<Reference Include="Newtonsoft.Json">
+			<HintPath>../Assets/Packages/JsonDotNet/Assemblies/Standalone/Newtonsoft.Json.dll</HintPath>
+		</Reference>
+	</ItemGroup>
+</Project>

+ 25 - 0
SmartBowSDK.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31829.152
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartBowSDK", "SmartBowSDK.csproj", "{23FEE110-2DD3-42D3-AD20-406F2556DBE1}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Release|Any CPU = Release|Any CPU
+		Debug|Any CPU = Debug|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{23FEE110-2DD3-42D3-AD20-406F2556DBE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{23FEE110-2DD3-42D3-AD20-406F2556DBE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{23FEE110-2DD3-42D3-AD20-406F2556DBE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{23FEE110-2DD3-42D3-AD20-406F2556DBE1}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {8595B828-6974-4E63-A258-0B29D4BD9C11}
+	EndGlobalSection
+EndGlobal

+ 22 - 0
SmartBowSDK/AOTAdapter_SDK.cs

@@ -0,0 +1,22 @@
+using UnityEngine;
+using o0.Num;
+using o0.Geometry;
+
+namespace SmartBowSDK
+{
+    /// <summary>
+    /// AOT适配器
+    /// </summary>
+    public class AOTAdapter_SDK : MonoBehaviour
+    {
+        static AOTAdapter_SDK()
+        {
+            //o0.Num泛型类的显式创建
+            Num<Vector<double>>.Plus = (a, b) => a + b;
+            Num<Vector<double>>.Minus = (a, b) => a - b;
+            Num<Vector<double>>.Multiply = (a, b) => a * b;
+            Num<Vector<double>>.Divide = (a, b) => a / b;
+            Num<Vector<double>>.Rate = (a, b) => a * b;
+        }
+    }
+}

+ 121 - 0
SmartBowSDK/AimHandler_SDK.cs

@@ -0,0 +1,121 @@
+using System;
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace SmartBowSDK
+{
+    /* 瞄准处理器 */
+    public class AimHandler_SDK : MonoBehaviour
+    {
+        public SmartBowHelper smartBowHelper;
+
+        private Quaternion _gameRotation = Quaternion.identity;
+        public Quaternion gameRotation
+        {
+            get => _gameRotation;
+            set
+            {
+                _gameRotation = value;
+                //去掉z轴影响
+                Vector3 normalVector = _gameRotation * Vector3.forward;
+                Quaternion normalQuat = Quaternion.LookRotation(normalVector);
+                //通知外部监听函数
+                smartBowHelper.InvokeOnRotationUpdate(normalQuat);
+            }
+        }
+
+        private AxisBaseHandler_SDK m_axisHandler;
+
+        void Start()
+        {
+            m_axisHandler = new Axis9Handler_SDK(this);
+            m_axisHandler.Init();
+        }
+
+        public void ReinitAxisHandler()
+        {
+            m_axisHandler.Init();
+        }
+
+        Quaternion newRotation = Quaternion.identity;
+        public void SetNewRotation(o0.Geometry.Quaternion o0Quat)
+        {
+            Quaternion quat = o0.Bow.Extension.ToUnityQuaternion(o0Quat);
+            if (float.IsNaN(quat.x) || float.IsNaN(quat.y) || float.IsNaN(quat.z) || float.IsNaN(quat.w))
+            {
+                Debug.LogError($"九轴Rotation存在Nan值,double:{o0Quat.ToString()},float:{quat.ToString()}");
+                return;
+            }
+            if (float.IsInfinity(quat.x) || float.IsInfinity(quat.y) || float.IsInfinity(quat.z) || float.IsInfinity(quat.w))
+            {
+                Debug.LogError($"九轴Rotation存在Infinity值,double:{o0Quat.ToString()},float:{quat.ToString()}");
+                return;
+            }
+            newRotation = quat;
+        }
+
+        public void Update()
+        {
+            gameRotation = Quaternion.Lerp(gameRotation, newRotation, Time.deltaTime * 7);
+        }
+
+        public void OnDataReceived(byte[] bytes)
+        {
+            if (bytes.Length != 27 && bytes.Length != 39)
+            {
+                if (bytes.Length == 2)
+                {
+                    if (bytes[0] == 0x66 && bytes[1] == 0x31)
+                    {
+                        //短按功能键
+                        smartBowHelper.InvokeOnFunctionKeyPress();
+                    }
+                    else if (bytes[0] == 0x66 && bytes[1] == 0x32)
+                    {
+                        //长按功能键
+                        smartBowHelper.InvokeOnFunctionKeyLongPress();
+                    }
+                    else if (bytes[1] == 10)
+                    {
+                        //电量
+                        smartBowHelper.bluetoothAim.battery = bytes[0];
+                    }
+                }
+                else if (bytes[0] == 0x5b)
+                {
+                    //红外线检测到射箭
+                    smartBowHelper.shootChecker.OnInfraredDataReceived(bytes);
+                }
+                else if (bytes[0] == 0x5a)
+                {
+                    //手枪应答
+                    smartBowHelper.shootChecker.ReplyGun(bytes);
+                }
+                else if (bytes[0] == 0x5C)
+                {
+                    smartBowHelper.shootChecker.UpdateTheMagazine(bytes);
+
+                }
+                return;
+            }
+            m_axisHandler.Update(bytes);
+        }
+
+        public void DoIdentity()
+        {
+            try { m_axisHandler.DoIdentity(); } catch (Exception e) { Debug.LogWarning("DoIdentity:" + e.Message); }
+            gameRotation = newRotation;
+        }
+
+        public void NotifyAxisOnShot() { m_axisHandler.NotifyAxisOnShot(); }
+        public void StartGyrCalibration() { m_axisHandler.StartGyrCalibration(); }
+        public void StopGyrCalibration() { m_axisHandler.StopGyrCalibration(); }
+        public bool IsGyrCalibrating() { return m_axisHandler.IsGyrCalibrating(); }
+        public float GetGyrProgress() { return m_axisHandler.GetGyrProgress(); }
+        public bool IsGyrCompleted() { return m_axisHandler.IsGyrCompleted(); }
+        public void StartMagCalibration() { m_axisHandler.StartMagCalibration(); }
+        public bool IsMagCompleted() { return m_axisHandler.IsMagCompleted(); }
+        public void ResumeCalibrateRecord(string record) { m_axisHandler.ResumeCalibrateRecord(record); }
+
+    }
+}

+ 81 - 0
SmartBowSDK/AttitudeJson_SDK.cs

@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using Newtonsoft.Json.Linq;
+using o0.IMU;
+using o0.Geometry;
+using o0;
+
+namespace SmartBowSDK
+{
+    public class AttitudeJson_SDK : CustomJson_SDK
+    {
+        public AttitudeJson_SDK() : base()
+        {
+            InitJsonClass<_9AxisPreProcessor>(() => new _9AxisPreProcessor(),
+                "AccByteIndex",
+                "GyrByteIndex",
+                "MagByteIndex",
+                "ByteToAcc",
+                "ByteToGyr",
+                "ByteToMag",
+                "GyrCalibrater",
+                "MagCalibrater",
+                "ByteReverseMS"
+            );
+            InitJsonClass<MagnetometerAutoCalibrater>(() => new MagnetometerAutoCalibrater(),
+                "CountPerLength",
+                "EllipsoidFitting",
+                "FitCountLeft",
+                "FitThreshold",
+                "LastTimestamp",
+                "Lock",
+                "MaxCount",
+                "NewBlock",
+                "NewBlockAccumulation",
+                "Variance",
+                "VectorByBlock"
+            );
+            InitJsonClass<ByteToShorts>(() => new ByteToShorts(3),
+                "ByteToShort"
+            );
+            InitJsonClass<ByteToShort>(() => new ByteToShort(null),
+                "MinCount",
+                "Reverse",
+                "Short"
+            );
+            InitJsonClass<MeanMaintainer<Vector<double>>>(() => new MeanMaintainer<Vector<double>>(),
+                "Count",
+                "Mean"
+            );
+            InitJsonClass<ByteToShortByVariance>(() => new ByteToShortByVariance(),
+                "Count",
+                "Reverse",
+                "Short",
+                "ThresholdRate"
+            );
+            InitJsonClass<EllipsoidFitting>(() => new EllipsoidFitting(default, null, default),
+                "Center",
+                "CorrectMatrixArray",
+                "Radius"
+            );
+            InitJsonClass<Dictionary<Vector<int>, Vector<double>>>(
+                o => JToken.FromObject(o),
+                jt =>
+                {
+                    var o = new Dictionary<Vector<int>, Vector<double>>();
+                    foreach (JProperty item in jt)
+                        o[ParseByExporter<Vector<int>>(JArray.Parse(item.Name))]
+                            = ParseByExporter<Vector<double>>(item.Value);
+                    return o;
+                }
+            );
+            InitJsonClass<Vector<int>>(
+                o => JToken.FromObject(o),
+                jt => new Vector<int>(jt.ToObject<int[]>())
+            );
+            InitJsonClass<Vector<double>>(
+                o => JToken.FromObject(o),
+                jt => new Vector<double>(jt.ToObject<double[]>())
+            );
+        }
+    }
+}

+ 234 - 0
SmartBowSDK/Axis9Handler_SDK.cs

@@ -0,0 +1,234 @@
+using System;
+using UnityEngine;
+using Newtonsoft.Json;
+using System.Reflection;
+
+namespace SmartBowSDK
+{
+    public class Axis9Handler_SDK : AxisBaseHandler_SDK
+    {
+        private o0.Bow.o09AxisAfterXiaMenFromDll_SDK _9Axis;
+        private AttitudeJson_SDK attitudeJson = new AttitudeJson_SDK();
+
+        public o0.Geometry.Vector<int> GyrByteIndex = new o0.Geometry.Vector<int>(-3, -2, -1);
+        public o0.Geometry.Vector<int> AccByteIndex = new o0.Geometry.Vector<int>(-3, -2, -1);
+        public o0.Geometry.Vector<int> MagByteIndex = new o0.Geometry.Vector<int>(3, 2, -1);/**///9轴 usb向上 pcb向右 电池向左
+
+        public Axis9Handler_SDK(AimHandler_SDK aimHandler) : base(aimHandler) { }
+
+        public override void Init()
+        {
+            _9Axis = new o0.Bow.o09AxisAfterXiaMenFromDll_SDK(GyrByteIndex, AccByteIndex, MagByteIndex);
+            LoadIdentity();
+        }
+
+        public override void Update(byte[] bytes)
+        {
+            UpdateCheckGyrCompleted();
+
+            o0.Geometry.Quaternion Qua = o0.Geometry.Quaternion.Identity;
+            try
+            {
+                lock (_9Axis)
+                    Qua = _9Axis.Update(new byte[] { bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18] },
+                        new byte[] { bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12] },
+                        new byte[] { bytes[19], bytes[20], bytes[21], bytes[22], bytes[23], bytes[24] },
+                        bytes[1], bytes[2], bytes[3], bytes[4]);/**///9轴
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+            m_aimHandler.SetNewRotation(Qua);
+
+            UpdateCheckMagCompleted();
+        }
+
+        private void UpdateCheckGyrCompleted()
+        {
+            if (_9Axis.Attitude.GyrCalibrate && GetGyrProgress() >= 1)
+            {
+                StopGyrCalibration();
+                SaveCalibrateRecord();
+            }
+        }
+
+        private bool _magCompleted;
+        private void UpdateCheckMagCompleted()
+        {
+            bool magCompleted = IsMagCompleted();
+            if (magCompleted && !_magCompleted)
+                SaveCalibrateRecord();
+            _magCompleted = magCompleted;
+        }
+        private void CorrectMagCompleted()
+        {
+            _magCompleted = IsMagCompleted();
+        }
+
+        public override void DoIdentity()
+        {
+            if (_9Axis.States.Count == 0) return; //没数据时触发视角归位会产生NAN,从而导致算法崩坏
+            SetIdentityAndSave();
+            m_aimHandler.SetNewRotation(_9Axis.getLastState().Qua);
+        }
+
+        public override void NotifyAxisOnShot()
+        {
+            _9Axis.OnShot(100, 100, 100000);
+        }
+
+        public override void StartGyrCalibration()
+        {
+            _9Axis.Attitude.GyrCalibrate = true;
+        }
+
+        public override void StopGyrCalibration()
+        {
+            _9Axis.Attitude.GyrCalibrate = false;
+        }
+
+        public override bool IsGyrCalibrating()
+        {
+            return _9Axis.Attitude.GyrCalibrate;
+        }
+
+        public override float GetGyrProgress()
+        {
+            return _9Axis.Attitude.GyrCalibrater.Count / 2000f;
+        }
+
+        public override bool IsGyrCompleted()
+        {
+            return _9Axis.Attitude.GyrCalibrater.Count >= 2000;
+        }
+
+        public override void StartMagCalibration()
+        {
+            _9Axis.Attitude.MagCalibrater = new o0.IMU.MagnetometerAutoCalibrater(0.001d);
+        }
+
+        public override bool IsMagCompleted()
+        {
+            return _9Axis.Attitude.MagCalibrater.Complete;
+        }
+
+        public override void SaveCalibrateRecord()
+        {
+            try
+            {
+                SmartBowLogger.Log(this, "开始序列化九轴数据");
+                string record = attitudeJson.Stringify(_9Axis.Attitude);
+                if (!string.IsNullOrEmpty(record))
+                {
+                    SmartBowLogger.Log(this, $"九轴数据序列化完成(长度{record.Length})\nGyrMeanLen:{_9Axis.Attitude.GyrCalibrater.Mean.Length}\nMagVariance:{_9Axis.Attitude.MagCalibrater.Variance}");
+                    if (!IsGyrCompleted())
+                    {
+                        SmartBowLogger.LogWarning(this, "陀螺仪未校准,因校准数据不完整无法上传!");
+                        return;
+                    }
+                    if (!IsMagCompleted())
+                    {
+                        SmartBowLogger.LogWarning(this, "地磁计未校准,因校准数据不完整无法上传!");
+                        return;
+                    }
+                    string macAddress = m_aimHandler.smartBowHelper.bluetoothAim.macAddress;
+                    if (macAddress == null)
+                    {
+                        Debug.Log("MacAddress为null,无法上传校准记录!");
+                        return;
+                    }
+                    m_aimHandler.smartBowHelper.smartBowNetwork.SaveCalibrateRecord(macAddress, record);
+                }
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+
+        public override void ResumeCalibrateRecord(string record)
+        {
+            try
+            {
+                SmartBowLogger.Log(this, $"成功加载服务端的九轴数据(长度{record.Length})");
+                _9Axis.Attitude = attitudeJson.Parse<o0.IMU._9AxisPreProcessor>(record);
+                SmartBowLogger.Log(this, $"九轴数据恢复成功\nGyrMeanLen:{_9Axis.Attitude.GyrCalibrater.Mean.Length}\nMagVariance:{_9Axis.Attitude.MagCalibrater.Variance}");
+                if (!IsAxisRight())
+                {
+                    _9Axis.Attitude = new o0.IMU._9AxisPreProcessor(GyrByteIndex, AccByteIndex, MagByteIndex);
+                    SmartBowLogger.LogWarning(this, "跟保存的轴向不相同,重置校准记录!");
+                }
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+            CorrectMagCompleted();
+        }
+
+        //判断轴向是否正确
+        private bool IsAxisRight()
+        {
+            object o = _9Axis.Attitude;
+            Type t = o.GetType();
+            var _gyr = t.GetField("GyrByteIndex", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(o);
+            var _acc = t.GetField("AccByteIndex", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(o);
+            var _mag = t.GetField("MagByteIndex", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(o);
+            bool gyrEqual = GyrByteIndex.Equals(_gyr);
+            bool accEqual = AccByteIndex.Equals(_acc);
+            bool magEqual = MagByteIndex.Equals(_mag);
+            return gyrEqual && accEqual && magEqual;
+        }
+
+        private void LoadIdentity()
+        {
+            try
+            {
+                string accStr = PlayerPrefs.GetString("AccIdentity0", "");
+                if (accStr.Length > 0)
+                {
+                    double[] arr = JsonConvert.DeserializeObject<double[]>(accStr);
+                    var v = new o0.Geometry.Vector<double>(arr);
+                    if (!double.IsNaN(v.x) && !double.IsInfinity(v.x)
+                        && !double.IsNaN(v.y) && !double.IsInfinity(v.y)
+                        && !double.IsNaN(v.z) && !double.IsInfinity(v.z)
+                    )
+                    {
+                        _9Axis.AccIdentity = v;
+                    }
+                }
+                string magStr = PlayerPrefs.GetString("MagIdentity0", "");
+                if (magStr.Length > 0)
+                {
+                    double[] arr = JsonConvert.DeserializeObject<double[]>(magStr);
+                    var v = new o0.Geometry.Vector<double>(arr);
+                    if (!double.IsNaN(v.x) && !double.IsInfinity(v.x)
+                        && !double.IsNaN(v.y) && !double.IsInfinity(v.y)
+                        && !double.IsNaN(v.z) && !double.IsInfinity(v.z)
+                    )
+                    {
+                        _9Axis.MagIdentity = v;
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+
+        private void SetIdentityAndSave()
+        {
+            _9Axis.SetIdentity();
+            var a = _9Axis.AccIdentity;
+            var m = _9Axis.MagIdentity;
+            PlayerPrefs.SetString("AccIdentity0", JsonConvert.SerializeObject(new double[]{
+                a.x, a.y, a.z
+            }));
+            PlayerPrefs.SetString("MagIdentity0", JsonConvert.SerializeObject(new double[]{
+                m.x, m.y, m.z
+            }));
+        }
+    }
+}

+ 24 - 0
SmartBowSDK/AxisBaseHandler_SDK.cs

@@ -0,0 +1,24 @@
+namespace SmartBowSDK
+{
+    public abstract class AxisBaseHandler_SDK
+    {
+        protected AimHandler_SDK m_aimHandler;
+        public AxisBaseHandler_SDK(AimHandler_SDK aimHandler)
+        {
+            m_aimHandler = aimHandler;
+        }
+        public abstract void Init();
+        public abstract void Update(byte[] bytes);
+        public abstract void DoIdentity();
+        public abstract void NotifyAxisOnShot();
+        public abstract void StartGyrCalibration();
+        public abstract void StopGyrCalibration();
+        public abstract bool IsGyrCalibrating();
+        public abstract float GetGyrProgress();
+        public abstract bool IsGyrCompleted();
+        public abstract void StartMagCalibration();
+        public abstract bool IsMagCompleted();
+        public abstract void SaveCalibrateRecord();
+        public abstract void ResumeCalibrateRecord(string record);
+    }
+}

+ 744 - 0
SmartBowSDK/BluetoothAim_SDK.cs

@@ -0,0 +1,744 @@
+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<object> _ScanLocker = new HashSet<object>();
+
+        private int _receivedDataCount = 0;
+        public string macAddress = null;
+
+        public BluetoothStatusEnum bluetoothStatus;
+
+        private BluetoothWindows _bluetoothWindows;
+
+        /// <summary>
+        /// 6轴
+        /// </summary>
+        private o06DOF.SixAxisFusion sixAxisFusion;
+
+        /// <summary>
+        /// 过滤名称
+        /// </summary>
+        public string filters { get; set; } = "";
+
+
+        /// <summary>
+        /// 当前的传感器类型
+        /// </summary>
+        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;
+                _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);
+        }
+        /// <summary>
+        /// 原九轴的传感器解析
+        /// </summary>
+        /// <param name="helper"></param>
+        /// <param name="value"></param>
+        /// <param name="characteristic"></param>
+        void OnCharacteristicChanged(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);
+        }
+        /// <summary>
+        /// 六轴传感器时候使用的解析
+        /// </summary>
+        /// <param name="helper"></param>
+        /// <param name="value"></param>
+        /// <param name="characteristic"></param>
+        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);
+            }
+        }
+        /// <summary>
+        /// 重置6轴identify
+        /// </summary>
+        public void ResetSixAxisFusion() {
+            if (sixAxisFusion != null) {
+                sixAxisFusion.ResetToInitialRotation();
+            }
+        }
+
+        /// <summary>
+        /// pc调用
+        /// </summary>
+        /// <param name="deviceId"></param>
+        /// <param name="value"></param>
+        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<BluetoothDevice> nearbyDevices)
+        {
+            _ScanLocker.Remove(helper);
+            if (helper != _bluetoothHelper) return;
+            foreach (BluetoothDevice device in nearbyDevices)
+            {
+                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, $"匹配设备{device.DeviceName}");
+                    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);
+            Queue<string> cmds = new Queue<string>(new string[] { "M", "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();
+
+        }
+
+        /// <summary>
+        /// 初始化6轴指令
+        /// </summary>
+        /// <returns></returns>
+        IEnumerator InitWhenConnected6Axis()
+        {
+            string myConnectedHandlerID = Guid.NewGuid().ToString();
+            _connectedHandlerID = myConnectedHandlerID;
+            yield return new WaitForSecondsRealtime(BluetoothWindows.IsWindows() ? 0.3f : 2.2f);
+            Queue<string> cmds = new Queue<string>(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缓存成功");
+            }
+        }
+        /// <summary>
+        /// 九轴原本的连接电池指令
+        /// </summary>
+        /// <param name="myConnectedHandlerID"></param>
+        /// <returns></returns>
+        IEnumerator LoopRequestBattery(string myConnectedHandlerID)
+        {
+            while (bluetoothStatus == BluetoothStatusEnum.Connected && myConnectedHandlerID == _connectedHandlerID)
+            {
+                yield return new WaitForSecondsRealtime(10);
+                AddCommandToQueue("b");
+            }
+        }
+        /// <summary>
+        /// 6轴的连接电池指令
+        /// </summary>
+        /// <param name="myConnectedHandlerID"></param>
+        /// <returns></returns>
+        IEnumerator LoopRequestBattery6Axis(string myConnectedHandlerID)
+        {
+            while (bluetoothStatus == BluetoothStatusEnum.Connected && myConnectedHandlerID == _connectedHandlerID)
+            {
+                yield return new WaitForSecondsRealtime(10);
+                AddCommandToQueue("B");
+            }
+        }
+
+        private List<string> _commands = new List<string>();
+        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 记录的设备处理部分
+        //连接时候判断信息。存在对象则连接需要判断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<AimDeviceInfo>(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
+}

+ 223 - 0
SmartBowSDK/BluetoothDecryptor.cs

@@ -0,0 +1,223 @@
+using System;
+using UnityEngine;
+
+public class BluetoothDecryptor
+{
+    // 授权标志
+    private static bool _authorizationFlg = false;
+    // 密钥长度
+    private const int KEY_LENGTH = 12;
+    // 对称密钥
+    private static readonly byte[] c_abySecretKey = System.Text.Encoding.ASCII.GetBytes("qingfengluan");
+    private static readonly byte[] _decryptExpected = new byte[4];
+    private static readonly byte[] _encryptExpected = new byte[4];
+
+    /***********************************************************
+      *@函 数 名	:	AUTHOR_ImprovedEncrypt
+      *@功能说明	:	加密函数
+      *@形    参	:	pbyData - 待加密字符串
+      *@				dwDataLen - 加密数据长度
+      *@返 回 值	:	void
+      *@作    者	:	Shaoxin Wu
+      *@日    期	:	2024-06-24
+    ***********************************************************/
+    private static void AUTHOR_ImprovedEncrypt(byte[] data, int offset, int length)
+    {
+        for (int i = offset; i < offset + length; i++)
+        {
+            for (int j = 0; j < KEY_LENGTH; j++)
+            {
+                data[i] ^= c_abySecretKey[j]; // 多轮异或
+                data[i] = (byte)((data[i] << 1) | (data[i] >> 7)); // 左循环移位
+            }
+        }
+    }
+    /***********************************************************
+    *@函 数 名 : AUTHOR_ImprovedDecrypt
+    *@功能说明 : 解密函数
+    *@形 参 : pbyData - 待解密字符串
+    *@ dwDataLen - 解密数据长度
+    *@返 回 值 : pbyData - 解密后的数据
+    *@作 者 : Shaoxin Wu
+    *@日 期 : 2024-06-24
+    ***********************************************************/
+    public static byte[] AUTHOR_ImprovedDecrypt(byte[] pbyData, uint dwDataLen)
+    {
+        for (uint i = 0; i < dwDataLen; i++)
+        {
+            for (int j = KEY_LENGTH - 1; j >= 0; j--)
+            {
+                pbyData[i] = (byte)((pbyData[i] >> 1) | (pbyData[i] << 7)); // 右循环移位
+                pbyData[i] ^= c_abySecretKey[j]; // 多轮异或
+            }
+        }
+        return pbyData;
+    }
+
+    /***********************************************************
+    *@函 数 名 : CheckSum
+    *@功能说明 : 累加和校验计算
+    *@形 参 : pbyData - 数据地址
+    *@ byDataLen - 校验数据长度
+    *@返 回 值 : 校验和结果
+    *@作 者 : Shaoxin Wu
+    *@日 期 : 2024-06-24
+    ***********************************************************/
+    public static byte CheckSum(byte[] pbyData, byte byDataLen)
+    {
+        byte byRet = 0;
+        for (byte i = 0; i < byDataLen; i++)
+        {
+            byRet += pbyData[i];
+        }
+        return byRet;
+    }
+
+    /// <summary>
+    /// 最前面插入一个字节
+    /// </summary>
+    /// <param name="originalArray"></param>
+    /// <param name="newByte"></param>
+    /// <returns></returns>
+    public static byte[] InsertByteAtBeginning(byte[] originalArray, byte newByte)
+    {
+        byte[] newArray = new byte[originalArray.Length + 1];
+        newArray[0] = newByte;
+        Array.Copy(originalArray, 0, newArray, 1, originalArray.Length);
+        return newArray;
+    }
+
+    public static byte[] GetResponse(byte[] bytes) {
+        //byte[] dataToDecrypt = { 0x5A, 0x76, 0xD1, 0x02, 0xEB, 0x8E, 0x5D };
+        //需要解密的4个数据
+        byte[] encryptedData = new byte[] { bytes[1], bytes[2], bytes[3], bytes[4] };
+        uint dataLength = (uint)encryptedData.Length;
+        byte checksumStart = CheckSum(new byte[] { bytes[0] , bytes[1], bytes[2], bytes[3], bytes[4] }, 5);
+        // 打印开始校验和
+        //Debug.Log("checksumStart: " + checksumStart + ",对应16进制=" + checksumStart.ToString("X"));
+
+        // 打印原始加密数据
+        Debug.Log("Original Data: " + System.BitConverter.ToString(bytes));
+        //Debug.Log("Original Encrypted Data: " + System.BitConverter.ToString(encryptedData));
+        // 解密数据
+        byte[] decryptedData = AUTHOR_ImprovedDecrypt(encryptedData, dataLength);
+        // 打印解密后的数据
+        //Debug.Log("Decrypted Data: " + System.BitConverter.ToString(decryptedData));
+        byte[] newArray = InsertByteAtBeginning(decryptedData, bytes[0]);
+        //Debug.Log("前插 {0x5A} 后:  " + System.BitConverter.ToString(newArray));
+        // 计算累加和校验
+        byte checksum = CheckSum(newArray, (byte)newArray.Length);
+       // Debug.Log("checksum:" + checksum + ", 对应16进制:"+ checksum.ToString("X"));
+        //byte checksum = Decryptor.CheckSum(decryptedData, (byte)dataLength);
+        //// 打印校验和
+        //Debug.Log("Checksum: " + checksum+",16=" + checksum.ToString("X"));
+        // 组装应答数据
+        byte[] responseData = new byte[7];
+        responseData[0] = 0x5A; // 起始码
+        System.Array.Copy(decryptedData, 0, responseData, 1, decryptedData.Length); // 解密数据
+        responseData[5] = checksum; // 累加和校验
+        responseData[6] = 0x5D; // 结束码
+        // 将应答数据转换为字符串
+        Debug.Log("Original Response Data: " + System.BitConverter.ToString(responseData));
+       // string responseString = System.BitConverter.ToString(responseData).Replace("-", "");
+        //Debug.Log("Response Data: " + responseString);
+        return responseData;
+    }
+
+
+    /***********************************************************
+   *@函 数 名	:	AUTHOR_GenerateRandomData
+   *@功能说明	:	生成随机数据函数
+   *@形    参	:	dwSeed - 种子值
+   *@				pbyData - 存放随机数据的数组
+   *@				dwDataLen - 随机数据长度
+   *@返 回 值	:	void
+   *@作    者	:	Shaoxin Wu
+   *@日    期	:	2024-06-24
+   ***********************************************************/
+    private static void AUTHOR_GenerateRandomData(uint dwSeed, byte[] data, int offset, int length)
+    {
+        for (int i = 0; i < length; i++)
+        {
+            data[offset + i] = (byte)(dwSeed & 0xFF); // 简单地取低8位作为随机数据
+            dwSeed >>= 1; // 移位
+        }
+    }
+
+    /***********************************************************
+    *@函 数 名	:	AUTHOR_IsDecrypt
+    *@功能说明	:	获取授权状态
+    *@形    参	:	void
+    *@返 回 值	:	void
+    *@作    者	:	Shaoxin Wu
+    *@日    期	:	2024-07-28
+    ***********************************************************/
+    public static bool AUTHOR_IsDecrypt()
+    {
+        return _authorizationFlg;
+    }
+
+    /***********************************************************
+    *@函 数 名	:	AUTHOR_Decrypt
+    *@功能说明	:	解析授权响应数据
+    *@形    参	:	void
+    *@返 回 值	:	void
+    *@作    者	:	Shaoxin Wu
+    *@日    期	:	2024-07-28
+    ***********************************************************/
+    public static void AUTHOR_Decrypt(byte[] pbyData)
+    {
+        byte byCheckSum = CheckSum(pbyData, 5);
+        if (byCheckSum == pbyData[5])
+        {
+            Array.Copy(pbyData, 1, _encryptExpected, 0, 4);
+            if (BitConverter.ToUInt32(pbyData, 1) == BitConverter.ToUInt32(_decryptExpected, 0))
+            {
+                _authorizationFlg = true;
+            }
+        }
+    }
+
+    /***********************************************************
+    *@函 数 名	:	AUTHOR_Encryption
+    *@功能说明	:	取消授权
+    *@形    参	:	void
+    *@返 回 值	:	void
+    *@作    者	:	Shaoxin Wu
+    *@日    期	:	2024-07-28
+    ***********************************************************/
+    public static void AUTHOR_Encryption()
+    {
+        _authorizationFlg = false;
+    }
+
+    /***********************************************************
+    *@函 数 名	:	AUTHOR_SendReq
+    *@功能说明	:	输入系统Tick,生成随机值并加密
+    *@形    参	:	void
+    *@返 回 值	:	void
+    *@作    者	:	Shaoxin Wu
+    *@日    期	:	2024-07-28
+    ***********************************************************/
+    public static byte[] AUTHOR_SendReq(uint dwTick)
+    {
+        byte[] abySendData = new byte[7];
+
+        abySendData[0] = 0x5A;
+        // 根据种子生成随机值
+        AUTHOR_GenerateRandomData(dwTick, abySendData, 1, 4);
+        Array.Copy(abySendData, 1, _decryptExpected, 0, 4);
+        // 加密数据
+        AUTHOR_ImprovedEncrypt(abySendData, 1, 4);
+        abySendData[5] = CheckSum(abySendData, 5);
+        abySendData[6] = 0x5D;
+
+        return abySendData;
+    }
+    //// 模拟发送数据
+    //private static void SPP_DataNotify(byte[] data)
+    //{
+    //    // 实际发送数据的实现
+    //    Console.WriteLine(BitConverter.ToString(data));
+    //}
+}

+ 32 - 0
SmartBowSDK/BluetoothDeviceConfig.cs

@@ -0,0 +1,32 @@
+using UnityEngine;
+
+namespace SmartBowSDK
+{
+    public class BluetoothDeviceConfig
+    {
+        public string deviceName;
+        public string service;
+        public string characteristicWrite;
+        public string characteristicNotify;
+
+        public static BluetoothDeviceConfig GetDefault()
+        {
+            BluetoothDeviceConfig config = new BluetoothDeviceConfig();
+            //两个设备匹配
+            config.deviceName = "Bbow_20210501 | HOUYI Pro | Pistol | Pistol M9";
+            if (Application.platform == RuntimePlatform.Android)
+            {
+                config.service = "0000fff0";
+                config.characteristicWrite = "0000fff2";
+                config.characteristicNotify = "0000fff1";
+            }
+            else if (Application.platform == RuntimePlatform.IPhonePlayer)
+            {
+                config.service = "fff0";
+                config.characteristicWrite = "fff2";
+                config.characteristicNotify = "fff1";
+            }
+            return config;
+        }
+    }
+}

+ 31 - 0
SmartBowSDK/BluetoothError.cs

@@ -0,0 +1,31 @@
+namespace SmartBowSDK
+{
+    public enum BluetoothError
+    {
+        /// <summary>
+        /// 未知错误
+        /// </summary>
+        Unknown,
+        /// <summary>
+        /// 蓝牙开关未打开(仅支持安卓)
+        /// </summary>
+        BluetoothNotEnabled,
+        /// <summary>
+        /// 尚未授予定位权限(仅支持安卓)
+        /// </summary>
+        LocationPermissionNotGranted,
+        /// <summary>
+        /// 尚未授予扫描附近设备权限(仅支持安卓)
+        /// </summary>
+        ScanPermissionNotGranted,
+        /// <summary>
+        /// 扫描结束后未发现目标设备
+        /// </summary>
+        ScanNotFoundTargetDevice,
+
+        /// <summary>
+        /// mac地址和设备不匹配
+        /// </summary>
+        MacAddressAndDeviceMismatch,
+    }
+}

+ 103 - 0
SmartBowSDK/BluetoothHelperAndroid_SDK.cs

@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Events;
+using UnityEngine.Android;
+
+namespace SmartBowSDK
+{
+    public class BluetoothHelperAndroid_SDK
+    {
+        public static bool IsBluetoothEnabled()
+        {
+            using (var classBluetoothAdapter = new AndroidJavaClass("android.bluetooth.BluetoothAdapter"))
+            using (var bluetoothAdapter = classBluetoothAdapter.CallStatic<AndroidJavaObject>("getDefaultAdapter"))
+            {
+                if (bluetoothAdapter == null)
+                {
+                    Debug.Log("当前设备不支持蓝牙功能");
+                    return false;
+                }
+                bool isEnabled = bluetoothAdapter.Call<bool>("isEnabled");
+                return isEnabled;
+            }
+        }
+
+        public static bool RequestBluetoothPermissions(UnityAction onAllGranted, UnityAction<string> onDenied)
+        {
+            using (var buildVersion = new AndroidJavaClass("android.os.Build$VERSION"))
+            {
+                int sdkInt = buildVersion.GetStatic<int>("SDK_INT");
+                List<string> permissionListA = new List<string>();
+                List<string> permissionListB = new List<string>();
+                if (sdkInt >= 23)
+                {
+                    permissionListA.Add(Permission.CoarseLocation);
+                    permissionListA.Add(Permission.FineLocation);
+                    if (sdkInt < 31)
+                    {
+                        permissionListB.Add("android.permission.BLUETOOTH");
+                        permissionListB.Add("android.permission.BLUETOOTH_ADMIN");
+                    }
+                    else
+                    {
+                        permissionListB.Add("android.permission.BLUETOOTH_SCAN");
+                        permissionListB.Add("android.permission.BLUETOOTH_ADVERTISE");
+                        permissionListB.Add("android.permission.BLUETOOTH_CONNECT");
+                    }
+                }
+                if (IsPermissionsGranted(permissionListA)
+                    && IsPermissionsGranted(permissionListB)) return false;
+                RequestUserPermissions(permissionListA.ToArray(), () =>
+                {
+                    RequestUserPermissions(permissionListB.ToArray(), onAllGranted, onDenied);
+                }, onDenied);
+                return true;
+            }
+        }
+
+        private static bool IsPermissionsGranted(List<string> permissions)
+        {
+            foreach (var permission in permissions)
+                if (!Permission.HasUserAuthorizedPermission(permission)) return false;
+            return true;
+        }
+
+        private static void RequestUserPermissions(string[] permissions, UnityAction onAllGranted, UnityAction<string> onDenied)
+        {
+            bool hasExecuteOnDenied = false;
+            List<string> permissionsNeedRequest = new List<string>();
+            foreach (var permission in permissions)
+                if (!Permission.HasUserAuthorizedPermission(permission)) permissionsNeedRequest.Add(permission);
+            if (permissionsNeedRequest.Count > 0)
+            {
+                var requestCallback = new PermissionCallbacks();
+                requestCallback.PermissionGranted += (permission) =>
+                {
+                    Debug.Log("用户同意" + permission);
+                    permissionsNeedRequest.Remove(permission);
+                    if (permissionsNeedRequest.Count == 0) onAllGranted?.Invoke();
+                };
+                requestCallback.PermissionDenied += (permission) =>
+                {
+                    Debug.LogWarning("用户拒绝" + permission);
+                    if (!hasExecuteOnDenied)
+                    {
+                        hasExecuteOnDenied = true;
+                        onDenied?.Invoke(permission);
+                    }
+                };
+                requestCallback.PermissionDeniedAndDontAskAgain += (permission) =>
+                {
+                    Debug.LogWarning("用户拒绝且要求不再询问" + permission);
+                    if (!hasExecuteOnDenied)
+                    {
+                        hasExecuteOnDenied = true;
+                        onDenied?.Invoke(permission);
+                    }
+                };
+                Permission.RequestUserPermissions(permissionsNeedRequest.ToArray(), requestCallback);
+            }
+            else onAllGranted?.Invoke();
+        }
+    }
+}

+ 62 - 0
SmartBowSDK/BluetoothStatusEnum.cs

@@ -0,0 +1,62 @@
+namespace SmartBowSDK
+{
+    public enum BluetoothStatusEnum
+    {
+        /// <summary>
+        /// 未连接
+        /// </summary>
+        None,
+        /// <summary>
+        /// 连接中
+        /// </summary>
+        Connecting,
+        /// <summary>
+        /// 已连接
+        /// </summary>
+        Connected,
+    }
+
+    public enum BluetoothDeviceStatus
+    {
+        /// <summary>
+        /// 无操作
+        /// </summary>
+        None,
+        /// <summary>
+        /// 弹夹分离
+        /// </summary>
+        MagazineSeparation,
+        /// <summary>
+        /// 弹夹上膛
+        /// </summary>
+        MagazineLoading
+
+    }
+
+
+    public enum BluetoothDeviceType
+    {
+        NONE = -1,
+        HOUYIPro = 0,
+        ARTEMISPro = 1,
+        Pistol1 = 2,
+    }
+
+    /// <summary>
+    /// 添加一个传感器轴类型。默认的是使用的是九轴
+    /// 因为要连接其他设备。所以添加一个传感器类型来区分数据解析
+    /// </summary>
+    public enum SensorAxisType 
+    {
+        /// <summary>
+        /// 九轴传感器
+        /// </summary>
+        NineAxisSensor = 0,
+
+        /// <summary>
+        /// 六轴传感器
+        /// </summary>
+        SixAxisSensor = 1
+    
+    }
+}

+ 20 - 0
SmartBowSDK/BluetoothWindows.cs

@@ -0,0 +1,20 @@
+using System;
+using UnityEngine;
+
+namespace SmartBowSDK
+{
+    public class BluetoothWindows
+    {
+        public  Func<bool> Connect;
+        public  Func<bool> Disconnect;
+        public  Func<string, bool> Write;
+        public  Func<byte[], bool> WriteByte;
+        public  Action OnConnected;
+        public  Action OnConnectionFailed;
+        public  Action<string,byte[]> OnCharacteristicChanged;
+        public static bool IsWindows()
+        {
+            return Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor;
+        }
+    }
+}

+ 7 - 0
SmartBowSDK/CalibrateDataStorageMode.cs

@@ -0,0 +1,7 @@
+namespace SmartBowSDK {
+    public enum CalibrateDataStorageMode
+    {
+        Local,
+        Remote,
+    }
+}

+ 186 - 0
SmartBowSDK/CustomJson_SDK.cs

@@ -0,0 +1,186 @@
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace SmartBowSDK
+{
+    public class CustomJson_SDK
+    {
+        public Dictionary<Type, Func<object, JToken>> importerDict = new Dictionary<Type, Func<object, JToken>>();
+        public Dictionary<Type, Func<JToken, object>> exporterDict = new Dictionary<Type, Func<JToken, object>>();
+
+        public CustomJson_SDK()
+        {
+            //add importer
+            importerDict.Add(typeof(short), (o) => JValue.FromObject(o));
+            importerDict.Add(typeof(int), (o) => JValue.FromObject(o));
+            importerDict.Add(typeof(long), (o) => JValue.FromObject(o));
+            importerDict.Add(typeof(float), (o) => JValue.FromObject(o));
+            importerDict.Add(typeof(double), (o) => JValue.FromObject(o));
+            importerDict.Add(typeof(bool), (o) => JValue.FromObject(o));
+            importerDict.Add(typeof(double[][]), (o) => JArray.FromObject(o));
+            importerDict.Add(typeof(Nullable<bool>), (o) => JValue.FromObject(o));
+            //add exporter
+            exporterDict.Add(typeof(short), (jt) => jt.ToObject<short>());
+            exporterDict.Add(typeof(int), (jt) => jt.ToObject<int>());
+            exporterDict.Add(typeof(long), (jt) => jt.ToObject<long>());
+            exporterDict.Add(typeof(float), (jt) => jt.ToObject<float>());
+            exporterDict.Add(typeof(double), (jt) => jt.ToObject<double>());
+            exporterDict.Add(typeof(bool), (jt) => jt.ToObject<bool>());
+            exporterDict.Add(typeof(double[][]), (jt) => ((JArray)jt).ToObject<double[][]>());
+            exporterDict.Add(typeof(Nullable<bool>), (jt) => jt.ToObject<Nullable<bool>>());
+        }
+
+        public void InitJsonClass<T>(Func<object, JToken> importer, Func<JToken, object> exporter)
+        {
+            Type type = typeof(T);
+            importerDict.Add(type, importer);
+            exporterDict.Add(type, exporter);
+        }
+
+        public void InitJsonClass<T>(Func<object> ctor, params string[] props)
+        {
+            Type type = typeof(T);
+            exporterDict.Add(type, (jt) =>
+            {
+                var o = ctor.Invoke();
+                FetchFieldValues(o, jt, props);
+                return o;
+            });
+            importerDict.Add(type, (o) =>
+            {
+                JToken jt = new JObject();
+                FetchJsonProperties(jt, o, props);
+                return jt;
+            });
+        }
+
+        public T Parse<T>(string text)
+        {
+            var jt = JToken.Parse(text);
+            return (T)ParseByExporter(jt, typeof(T));
+        }
+
+        public string Stringify(object o)
+        {
+            return JsonConvert.SerializeObject(ToJTokenByImporter(o));
+        }
+
+        public void FetchJsonProperties(JToken target, object fromObject, params string[] propertyNames)
+        {
+            Type fromObjectType = fromObject.GetType();
+            foreach (var propertyName in propertyNames)
+            {
+                FieldInfo targetField = fromObjectType.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+                if (targetField == null)
+                {
+                    PropertyInfo propertyInfo = fromObjectType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+                    if (propertyInfo == null)
+                    {
+                        throw new Exception($"获取PropertyInfo[{propertyName}]失败");
+                    }
+                    else
+                    {
+                        JToken propertyValue = ToJTokenByImporter(propertyInfo.GetValue(fromObject));
+                        target[propertyName] = propertyValue;
+                    }
+                }
+                else
+                {
+                    JToken propertyValue = ToJTokenByImporter(targetField.GetValue(fromObject));
+                    target[propertyName] = propertyValue;
+                }
+            }
+        }
+
+        public JToken ToJTokenByImporter(object o)
+        {
+            if (o == null) return null;
+            Type type = o.GetType();
+            Func<object, JToken> importer = null;
+            importerDict.TryGetValue(type, out importer);
+            if (importer == null)
+            {
+                if (IsArray(type) && type.GetArrayRank() == 1)
+                { //是一维数组才会触发
+                    Type elemType = type.GetElementType();
+                    importerDict.TryGetValue(elemType, out importer);
+                    if (importer == null) throw new Exception($"缺少Type<{elemType.Name}>的Importer");
+                    JArray ja = new JArray();
+                    Array arr = (Array)o;
+                    for (int i = 0; i < arr.Length; i++) ja.Add(importer.Invoke(arr.GetValue(i)));
+                    return ja;
+                }
+                else
+                {
+                    throw new Exception($"缺少Type<{type.Name}>的Importer");
+                }
+            }
+            return importer.Invoke(o);
+        }
+
+        public T ParseByExporter<T>(JToken jt)
+        {
+            return (T)ParseByExporter(jt, typeof(T));
+        }
+
+        public object ParseByExporter(JToken jt, Type type)
+        {
+            if (jt.Type == JTokenType.Null) return null;
+            Func<JToken, object> exporter = null;
+            exporterDict.TryGetValue(type, out exporter);
+            if (exporter == null)
+            {
+                if (IsArray(type) && type.GetArrayRank() == 1)
+                { //是一维数组才会触发
+                    Type elemType = type.GetElementType();
+                    exporterDict.TryGetValue(elemType, out exporter);
+                    if (exporter == null) throw new Exception($"缺少Type<{elemType.Name}>的Exporter");
+                    var ja = (JArray)jt;
+                    var arr = Array.CreateInstance(elemType, ja.Count);
+                    for (int i = 0; i < arr.Length; i++) arr.SetValue(exporter.Invoke(ja[i]), i);
+                    return arr;
+                }
+                else
+                {
+                    throw new Exception($"缺少Type<{type.Name}>的Exporter");
+                }
+            }
+            return exporter.Invoke(jt);
+        }
+
+        public void FetchFieldValues(object target, JToken fromJson, params string[] fieldNames)
+        {
+            Type targetType = target.GetType();
+            foreach (var fieldName in fieldNames)
+            {
+                FieldInfo targetField = targetType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+                if (targetField == null)
+                {
+                    PropertyInfo propertyInfo = targetType.GetProperty(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+                    if (propertyInfo == null)
+                    {
+                        throw new Exception($"获取PropertyInfo[{fieldName}]失败");
+                    }
+                    else
+                    {
+                        object fieldValue = ParseByExporter(fromJson[fieldName], propertyInfo.PropertyType);
+                        propertyInfo.SetValue(target, fieldValue);
+                    }
+                }
+                else
+                {
+                    object fieldValue = ParseByExporter(fromJson[fieldName], targetField.FieldType);
+                    targetField.SetValue(target, fieldValue);
+                }
+            }
+        }
+
+        public bool IsArray(Type type)
+        {
+            return type.BaseType == typeof(Array);
+        }
+    }
+}

+ 89 - 0
SmartBowSDK/ShootChecker_SDK.cs

@@ -0,0 +1,89 @@
+using UnityEngine;
+
+namespace SmartBowSDK
+{
+    /// <summary>
+    /// 射箭检测器
+    /// </summary>
+    public class ShootChecker_SDK : MonoBehaviour
+    {
+        public SmartBowHelper smartBowHelper;
+        /// <summary>
+        /// 游戏里箭的速度
+        /// </summary>
+        public float gameArrowSpeed = 0;
+        /// <summary>
+        /// 游戏里箭的重量
+        /// </summary>
+        public float gameArrowWeight = 20;
+        /// <summary>
+        /// 现实里箭的重量
+        /// </summary>
+        public float solidArrowWeight = 75;
+        /// <summary>
+        /// 上一次的射击数据ID
+        /// </summary>
+        private int _lastShootID = -1;
+
+        /// <summary>
+        /// 当收到红外数据时
+        /// </summary>
+        /// <param name="bytes"></param>
+        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 * solidArrowWeight / gameArrowWeight);
+            //打印
+            string logInfo = $"序号{id},时区1:{time1}毫秒,时区2:{time2}毫秒,校验:{sumCheckRes},实体箭速度:{solidArrowSpeed}m/s,游戏箭速度:{gameArrowSpeed}m/s";
+            SmartBowLogger.Log(this, logInfo);
+            //收到正确的射箭数据,就回复硬件,否则n毫秒后硬件会认为丢包而进行重传
+            if (sumCheckRes) smartBowHelper.bluetoothAim.ReplyInfraredShoot();
+            //通知九轴算法,因为射箭抖动会使算法计算的姿态角产生误差
+            smartBowHelper.aimHandler.NotifyAxisOnShot();
+            smartBowHelper.InvokeOnShooting(gameArrowSpeed);
+        }
+
+        /// <summary>
+        /// 回去枪指令
+        /// </summary>
+        /// <param name="bytes"></param>
+        public void ReplyGun(byte[] bytes)
+        {
+            byte[] response = BluetoothDecryptor.GetResponse(bytes);
+            smartBowHelper.bluetoothAim.ReplyByte(response);
+        }
+
+        /// <summary>
+        /// 枪的弹夹状态
+        /// 0x00 弹夹分离,0x01 上弹夹
+        /// </summary>
+        /// <param name="bytes"></param>
+        public void UpdateTheMagazine(byte[] bytes)
+        {
+            Debug.Log("切换弹夹后:" + System.BitConverter.ToString(bytes));
+            if (bytes[1] == 0x00 )
+            {
+                smartBowHelper.InvokeOnBleDevice(BluetoothDeviceType.Pistol1, BluetoothDeviceStatus.MagazineSeparation);
+            }
+            else if(bytes[1] == 0x01) {
+                smartBowHelper.InvokeOnBleDevice(BluetoothDeviceType.Pistol1, BluetoothDeviceStatus.MagazineLoading);
+            }
+        }
+    }
+}

+ 566 - 0
SmartBowSDK/SmartBowHelper.cs

@@ -0,0 +1,566 @@
+using System;
+using UnityEngine;
+
+namespace SmartBowSDK
+{
+    public class SmartBowHelper : MonoBehaviour
+    {
+        private static SmartBowHelper _Instance;
+        internal SmartBowNetwork smartBowNetwork;
+        internal BluetoothAim_SDK bluetoothAim;
+        internal AimHandler_SDK aimHandler;
+        internal ShootChecker_SDK shootChecker;
+        public CalibrateDataStorageMode calibrateDataStorageMode = CalibrateDataStorageMode.Remote;
+
+        void Awake()
+        {
+            DontDestroyOnLoad(gameObject);
+            gameObject.AddComponent<AOTAdapter_SDK>();
+            smartBowNetwork = gameObject.AddComponent<SmartBowNetwork>();
+            bluetoothAim = gameObject.AddComponent<BluetoothAim_SDK>();
+            aimHandler = gameObject.AddComponent<AimHandler_SDK>();
+            shootChecker = gameObject.AddComponent<ShootChecker_SDK>();
+            smartBowNetwork.smartBowHelper = this;
+            bluetoothAim.smartBowHelper = this;
+            aimHandler.smartBowHelper = this;
+            shootChecker.smartBowHelper = this;
+        }
+
+        /// <summary>
+        /// 获取唯一实例
+        /// </summary>
+        /// <returns></returns>
+        public static SmartBowHelper GetInstance()
+        {
+            if (!_Instance) _Instance = new GameObject(typeof(SmartBowHelper).Name).AddComponent<SmartBowHelper>();
+            return _Instance;
+        }
+
+        /// <summary>
+        /// 创建新的实例
+        /// </summary>
+        /// <returns></returns>
+        public static SmartBowHelper NewInstance()
+        {
+            return new GameObject(typeof(SmartBowHelper).Name).AddComponent<SmartBowHelper>();
+        }
+
+        /// <summary>
+        /// 获取一个window 蓝牙用例
+        /// </summary>
+        /// <returns></returns>
+        public BluetoothWindows CreateBluetoothWindows()
+        {
+
+            return bluetoothAim.CreateWindowBLE();
+        }
+
+        #region 登录
+        /// <summary>
+        /// 用户登录
+        /// </summary>
+        /// <param name="gameID">游戏ID</param>
+        /// <param name="channelID">渠道ID</param>
+        /// <param name="username">账号</param>
+        /// <param name="password">密码</param>
+        /// <param name="callback">回调函数</param>
+        public void Login(string gameID, string channelID, string username, string password, Action<LoginResult> callback)
+        {
+            smartBowNetwork.Login(gameID, channelID, username, password, callback);
+        }
+
+        /// <summary>
+        /// 用户登出
+        /// </summary>
+        /// <param name="callback">回调函数</param>
+        public void Logout(Action<bool> callback)
+        {
+            smartBowNetwork.Logout(callback);
+        }
+
+        /// <summary>
+        /// 获取用户基本信息
+        /// </summary>
+        /// <param name="callback">回调函数</param>
+        public void GetUserInfo(Action<GetUserInfoResult> callback)
+        {
+            smartBowNetwork.GetUserInfo(callback);
+        }
+        #endregion
+
+        #region 蓝牙:蓝牙部分新增修改,添加当前判断设备mac
+        ///// <summary>
+        ///// 连接蓝牙模块
+        ///// </summary>
+        //public void Connect()
+        //{
+        //    //之前的默认不用判断mac
+        //    //当前连接不需要需要检测设备,设置为null
+        //    bluetoothAim.ResetAimDeviceInfoToNull();
+
+        //    bluetoothAim.Connect();
+        //}
+
+        /// <summary>
+        /// 设置蓝牙过滤名称
+        /// </summary>
+        /// <param name="name"></param>
+        public void SetFilters(string name) { 
+           if (!string.IsNullOrWhiteSpace(name))
+            {
+                bluetoothAim.filters = name;
+            }
+        }
+
+        /// <summary>
+        /// 设置传感器类型,用来区分解析
+        /// </summary>
+        /// <param name="sensorAxisType"></param>
+        public void SetSensorAxisType(SensorAxisType sensorAxisType) 
+        {
+            bluetoothAim.sensorAxisType = sensorAxisType;
+
+            SmartBowLogger.Log(this, "SetSensorAxisType:" + bluetoothAim.sensorAxisType);
+        }
+
+
+        /// <summary>
+        /// 连接蓝牙模块
+        /// 新增连接操作,需要用户标签和设备id
+        /// userTags: 存储的信息标签,按实际用户区分
+        /// deviceId:对应设备id,同一个用户下的不同设备
+        /// bRecreateDeviceInfo: 重新创建设备信息。false 的情况下默认限制mac
+        /// </summary>
+        public void Connect(string userTags,int deviceId, bool bRecreateDeviceInfo = false)
+        {
+            if (userTags == null || userTags.Length == 0)
+            {
+                SmartBowLogger.LogError(this, $"清除信息错误,userTags不能为空!");
+                return;
+            }
+            if (deviceId <= 0)
+            {
+                SmartBowLogger.LogError(this, $"清除信息错误,deviceId 不能小于等于 0!");
+                return;
+            }
+            //判断mac需要创建一个连接信息
+            bluetoothAim.initAimDeviceInfo(userTags,deviceId,bRecreateDeviceInfo);
+            //进行蓝牙连接
+            bluetoothAim.Connect();
+        }
+        /// <summary>
+        /// 清空建立的蓝牙判断数据
+        /// userTags: 存储的信息标签,按实际用户区分
+        /// deviceId:对应设备id,同一个用户下的不同设备
+        /// </summary>
+        public void ClearDeviceInfo(string userTags,int deviceId)
+        {
+            if (userTags == null || userTags.Length == 0)
+            {
+                SmartBowLogger.LogError(this, $"清除信息错误,userTags不能为空!");
+                return;
+            }
+            if (deviceId <= 0)
+            {
+                SmartBowLogger.LogError(this, $"清除信息错误,deviceId 不能小于等于 0!");
+                return ;
+            }
+            bluetoothAim.onClearAimDeviceInfoByDeviceId(userTags, deviceId);
+        }
+        /// <summary>
+        /// 获取存储的数据
+        /// userTags: 存储的信息标签,按实际用户区分
+        /// deviceId:对应设备id,同一个用户下的不同设备
+        /// </summary>
+        public AimDeviceInfo GetDeviceInfo(string userTags, int deviceId) {
+            if (userTags == null || userTags.Length == 0)
+            {
+                SmartBowLogger.LogError(this, $"清除信息错误,userTags不能为空!");
+                return null;
+            }
+            if (deviceId <= 0)
+            {
+                SmartBowLogger.LogError(this, $"清除信息错误,deviceId 不能小于等于 0!");
+                return null;
+            }
+            return bluetoothAim.onGetAimDeviceInfos(userTags, deviceId);
+        }
+
+        /// <summary>
+        /// 断开蓝牙模块
+        /// </summary>
+        public void Disconnect()
+        {
+            bluetoothAim.Disconnect();
+        }
+
+        /// <summary>
+        /// 获取蓝牙状态
+        /// </summary>
+        /// <returns>蓝牙状态</returns>
+        public BluetoothStatusEnum GetBluetoothStatus()
+        {
+            return bluetoothAim.bluetoothStatus;
+        }
+
+        /// <summary>
+        /// 监听蓝牙状态变化
+        /// </summary>
+        public event BluetoothStatusChangedEvent OnBluetoothStatusChanged;
+
+        /// <summary>
+        /// 判断蓝牙模块是否初始化完成
+        /// </summary>
+        /// <returns>蓝牙模块是否初始化完成</returns>
+        public bool IsBluetoothModuleInited()
+        {
+            return bluetoothAim.moduleInited;
+        }
+
+        /// <summary>
+        /// 监听蓝牙模块初始化完成
+        /// Tip:连接蓝牙模块成功后会有几秒的初始化过程,初始化完成后才能进行传感器相关的函数调用。
+        /// </summary>
+        public Action OnBluetoothModuleInited;
+
+        /// <summary>
+        /// 监听蓝牙错误
+        /// </summary>
+        public event BluetoothErrorEvent OnBluetoothError;
+
+        /// <summary>
+        /// 获取Mac地址
+        /// Tip:需要模块初始化完成才能获取
+        /// </summary>
+        /// <returns>如果返回null则是获取失败</returns>
+        public string GetMacAddress()
+        {
+            return bluetoothAim.macAddress;
+        }
+
+        /// <summary>
+        /// 获取电量
+        /// Tip:需要模块初始化完成才能获取
+        /// </summary>
+        /// <returns>电量值(范围0~100),值为0时也代表获取失败</returns>
+        public int GetBattery()
+        {
+            return bluetoothAim.battery;
+        }
+
+        /// <summary>
+        /// 设置目标设备名称(用于搜索和连接)
+        /// </summary>
+        /// <param name="deviceName">设备名称</param>
+        public void SetTargetDeviceName(string deviceName)
+        {
+            bluetoothAim.deviceConfig.deviceName = deviceName;
+        }
+        #endregion
+
+        #region 传感器
+        /// <summary>
+        /// 开启九轴传感
+        /// </summary>
+        /// <returns>请求是否发送成功</returns>
+        public bool StartRotationSensor()
+        {
+            return bluetoothAim.RequestOpen9Axis();
+        }
+
+        /// <summary>
+        /// 停止九轴传感
+        /// </summary>
+        /// <returns>请求是否发送成功</returns>
+        public bool StopRotationSensor()
+        {
+            return bluetoothAim.RequestClose9Axis();
+        }
+
+        /// <summary>
+        /// 监听姿态角更新
+        /// </summary>
+        public event RotationUpdateEvent OnRotationUpdate;
+
+        /// <summary>
+        /// 开启射箭传感
+        /// </summary>
+        /// <returns>请求是否发送成功</returns>
+        public bool StartShootingSensor()
+        {
+            return bluetoothAim.RequestOpenInfrared();
+        }
+
+        /// <summary>
+        /// 停止射箭传感
+        /// </summary>
+        /// <returns>请求是否发送成功</returns>
+        public bool StopShootingSensor()
+        {
+            return bluetoothAim.RequestCloseInfrared();
+        }
+
+        /// <summary>
+        /// 监听射箭
+        /// </summary>
+        public ShootingEvent OnShooting;
+
+        /// <summary>
+        /// 开始陀螺仪校准
+        /// </summary>
+        public void StartGyrCalibration()
+        {
+            aimHandler.StartGyrCalibration();
+        }
+
+        /// <summary>
+        /// 停止陀螺仪校准
+        /// </summary>
+        public void StopGyrCalibration()
+        {
+            aimHandler.StopGyrCalibration();
+        }
+
+        /// <summary>
+        /// 判断陀螺仪是否正在校准
+        /// </summary>
+        /// <returns>陀螺仪是否正在校准</returns>
+        public bool IsGyrCalibrating()
+        {
+            return aimHandler.IsGyrCalibrating();
+        }
+
+        /// <summary>
+        /// 获取陀螺仪校准进度
+        /// </summary>
+        /// <returns>陀螺仪校准进度</returns>
+        public float GetGyrProgress()
+        {
+            return aimHandler.GetGyrProgress();
+        }
+
+        /// <summary>
+        /// 判断陀螺仪是否校准完成
+        /// </summary>
+        /// <returns>陀螺仪是否校准完成</returns>
+        public bool IsGyrCompleted()
+        {
+            return aimHandler.IsGyrCompleted();
+        }
+
+        /// <summary>
+        /// 开始地磁计校准
+        /// </summary>
+        public void StartMagCalibration()
+        {
+            aimHandler.StartMagCalibration();
+        }
+
+        /// <summary>
+        /// 判断地磁计是否校准完成
+        /// </summary>
+        /// <returns>地磁计是否校准完成</returns>
+        public bool IsMagCompleted()
+        {
+            return aimHandler.IsMagCompleted();
+        }
+
+        /// <summary>
+        /// 视角归位
+        /// </summary>
+        public void ResetAim()
+        {
+            aimHandler.DoIdentity();
+        }
+
+
+        /// <summary>
+        /// 监听模块的功能键短按
+        /// </summary>
+        public Action OnFunctionKeyPress;
+
+        /// <summary>
+        /// 监听模块的功能键长按
+        /// </summary>
+        public Action OnFunctionKeyLongPress;
+        #endregion
+
+        /// <summary>
+        /// 蓝牙状态变化事件
+        /// </summary>
+        /// <param name="oldStatus">旧状态</param>
+        /// <param name="newStatus">新状态</param>
+        public delegate void BluetoothStatusChangedEvent(BluetoothStatusEnum oldStatus, BluetoothStatusEnum newStatus);
+        internal void InvokeOnBluetoothStatusChanged(BluetoothStatusEnum oldStatus, BluetoothStatusEnum newStatus)
+        {
+            try
+            {
+                OnBluetoothStatusChanged?.Invoke(oldStatus, newStatus);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+        /// <summary>
+        /// 蓝牙错误事件
+        /// </summary>
+        /// <param name="error">错误类型</param>
+        /// <param name="message">消息描述</param>
+        public delegate void BluetoothErrorEvent(BluetoothError error, string message);
+        internal void InvokeOnBluetoothError(BluetoothError error, string message)
+        {
+            try
+            {
+                OnBluetoothError?.Invoke(error, message);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+        internal void InvokeOnFunctionKeyPress()
+        {
+            try
+            {
+                OnFunctionKeyPress?.Invoke();
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+        internal void InvokeOnFunctionKeyLongPress()
+        {
+            try
+            {
+                OnFunctionKeyLongPress?.Invoke();
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+        /// <summary>
+        /// 姿态角更新事件
+        /// </summary>
+        /// <param name="rotation">姿态角</param>
+        public delegate void RotationUpdateEvent(Quaternion rotation);
+        internal void InvokeOnRotationUpdate(Quaternion rotation)
+        {
+            if (bluetoothAim.bluetoothStatus != BluetoothStatusEnum.Connected) return;
+            try
+            {
+                OnRotationUpdate?.Invoke(rotation);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+        /// <summary>
+        /// 射箭事件
+        /// </summary>
+        /// <param name="speed">速度(m/s)</param>
+        public delegate void ShootingEvent(float speed);
+        internal void InvokeOnShooting(float speed)
+        {
+            try
+            {
+                OnShooting?.Invoke(speed);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+        internal void InvokeOnBluetoothModuleInited()
+        {
+            try
+            {
+                OnBluetoothModuleInited?.Invoke();
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+
+        /// <summary>
+        /// 监听枪状态
+        /// </summary>
+        public BleDeviceEvent OnBleDeviceState;
+
+        /// <summary>
+        /// 硬件对应的事件
+        /// </summary>
+        public delegate void BleDeviceEvent(BluetoothDeviceType bleDeviceType, BluetoothDeviceStatus gunStatusEnum);
+        internal void InvokeOnBleDevice(BluetoothDeviceType bleDeviceType, BluetoothDeviceStatus gunStatusEnum)
+        {
+            try
+            {
+                OnBleDeviceState?.Invoke(bleDeviceType,gunStatusEnum);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+
+        #region 6轴部分接口
+        /// <summary>
+        /// 6轴数据重置初始位置数据
+        /// </summary>
+        public void ResetSixAxisIdentity()
+        {
+            bluetoothAim.ResetSixAxisFusion();
+        }
+        /// <summary>
+        /// 监听姿态角更新
+        /// </summary>
+        public event SixAxisRotationUpdateEvent OnSixAxisRotationUpdate;
+
+        /// <summary>
+        /// 姿态角更新事件
+        /// </summary>
+        /// <param name="rotation">姿态角</param>
+        public delegate void SixAxisRotationUpdateEvent(Vector3 eulerAngles);
+        internal void InvokeOnSixAxisRotationUpdate(Vector3 eulerAngles)
+        {
+            if (bluetoothAim.bluetoothStatus != BluetoothStatusEnum.Connected) return;
+            try
+            {
+                OnSixAxisRotationUpdate?.Invoke(eulerAngles);
+            }
+            catch (Exception e)
+            {
+                Debug.LogError(e);
+            }
+        }
+
+        ///// <summary>
+        ///// 芯片数据更新事件
+        ///// </summary>
+        ///// <param name="rotation">姿态角</param>
+        //public delegate void AxisDataUpdateEvent(byte[] value);
+        //internal void InvokeOnAxisDataUpdateEvent(byte[] value)
+        //{
+        //    if (bluetoothAim.bluetoothStatus != BluetoothStatusEnum.Connected) return;
+        //    try
+        //    {
+        //        OnAxisDataUpdate?.Invoke(value);
+        //    }
+        //    catch (Exception e)
+        //    {
+        //        Debug.LogError(e);
+        //    }
+        //}
+        ///// <summary>
+        ///// 芯片数据更新
+        ///// </summary>
+        //public event AxisDataUpdateEvent OnAxisDataUpdate;
+        #endregion
+
+
+    }
+}

+ 26 - 0
SmartBowSDK/SmartBowLogger.cs

@@ -0,0 +1,26 @@
+using UnityEngine;
+
+namespace SmartBowSDK
+{
+    public class SmartBowLogger
+    {
+        public static bool isDebug = false;
+        public static void Log(object context, string message)
+        {
+            if (!isDebug) return;
+            Debug.Log(string.Format("[{0}]{1}", context.GetType().Name, message));
+        }
+
+        public static void LogWarning(object context, string message)
+        {
+            if (!isDebug) return;
+            Debug.LogWarning(string.Format("[{0}]{1}", context.GetType().Name, message));
+        }
+
+        public static void LogError(object context, string message)
+        {
+            if (!isDebug) return;
+            Debug.LogError(string.Format("[{0}]{1}", context.GetType().Name, message));
+        }
+    }
+}

+ 181 - 0
SmartBowSDK/SmartBowNetwork.cs

@@ -0,0 +1,181 @@
+using System;
+using System.Collections;
+using UnityEngine;
+using UnityEngine.Networking;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace SmartBowSDK
+{
+    public class SmartBowNetwork : MonoBehaviour
+    {
+        public SmartBowHelper smartBowHelper;
+
+        private const string serverURL = "https://xmjssvr.cn/SmartBowBusinessServer";
+        //private const string serverURL = "http://127.0.0.1:11332/SmartBowBusinessServer";
+
+        private string loginToken;
+        public void Login(string gameID, string channelID, string username, string password, Action<LoginResult> callback)
+        {
+            WWWForm form = new WWWForm();
+            form.AddField("gameID", gameID);
+            form.AddField("channelID", channelID);
+            form.AddField("username", username);
+            form.AddField("password", password);
+            StartCoroutine(Post(serverURL + "/SmartBowSDK/login", form, (res) =>
+            {
+                bool loginSuccess = res.code == 0;
+                if (loginSuccess) loginToken = res.data as string;
+                LoginResult loginResult = new LoginResult();
+                loginResult.success = loginSuccess;
+                loginResult.message = res.msg;
+                callback?.Invoke(loginResult);
+            }));
+        }
+
+        public void Logout(Action<bool> callback)
+        {
+            loginToken = null;
+            callback?.Invoke(true);
+        }
+
+        public void GetUserInfo(Action<GetUserInfoResult> callback)
+        {
+            GetUserInfoResult getUserInfoResult = new GetUserInfoResult();
+            if (loginToken == null)
+            {
+                getUserInfoResult.message = "尚未登录";
+                callback?.Invoke(getUserInfoResult);
+                return;
+            }
+            WWWForm form = new WWWForm();
+            form.AddField("token", loginToken);
+            StartCoroutine(Post(serverURL + "/SmartBowSDK/getUserInfo", form, (res) =>
+            {
+                if (res.code == 0)
+                {
+                    UserInfo userInfo = (res.data as JObject).ToObject<UserInfo>();
+                    getUserInfoResult.success = true;
+                    getUserInfoResult.userInfo = userInfo;
+                }
+                getUserInfoResult.message = res.msg;
+                callback?.Invoke(getUserInfoResult);
+            }));
+        }
+
+        private string GetCalibrateDataStorageLocalKey(string mac)
+        {
+            string key = "CalibrateDataStorage";
+            string version = "1.0.0";
+            return key + "_" + mac + "_" + version;
+        }
+
+        internal void SaveCalibrateRecord(string mac, string record)
+        {
+            if (smartBowHelper.calibrateDataStorageMode == CalibrateDataStorageMode.Local)
+            {
+                PlayerPrefs.SetString(GetCalibrateDataStorageLocalKey(mac), record == null ? "" : record);
+            }
+            else if (smartBowHelper.calibrateDataStorageMode == CalibrateDataStorageMode.Remote)
+            {
+                WWWForm form = new WWWForm();
+                form.AddField("mac", mac);
+                form.AddField("record", record);
+                StartCoroutine(Post(serverURL + "/SmartBowSDK/saveCalibrateRecord", form, null));
+            }
+        }
+
+        internal void GetCalibrateRecord(string mac, Action<string> callback)
+        {
+            if (smartBowHelper.calibrateDataStorageMode == CalibrateDataStorageMode.Local)
+            {
+                callback?.Invoke(PlayerPrefs.GetString(GetCalibrateDataStorageLocalKey(mac), ""));
+            }
+            else if (smartBowHelper.calibrateDataStorageMode == CalibrateDataStorageMode.Remote)
+            {
+                WWWForm form = new WWWForm();
+                form.AddField("mac", mac);
+                StartCoroutine(Post(serverURL + "/SmartBowSDK/getCalibrateRecord", form, (res) =>
+                {
+                    if (res.code == 0) callback?.Invoke(res.data as string);
+                }));
+            }
+        }
+
+        public static IEnumerator Post(string url, WWWForm form, Action<RequestResult> callback)
+        {
+            using (UnityWebRequest request = UnityWebRequest.Post(url, form))
+            {
+                yield return request.SendWebRequest();
+                if (request.result == UnityWebRequest.Result.Success)
+                    callback?.Invoke(JsonConvert.DeserializeObject<RequestResult>(request.downloadHandler.text));
+                else
+                    callback?.Invoke(new RequestResult());
+            }
+        }
+
+        public static IEnumerator LoadSprite(string url, Action<Sprite> callback)
+        {
+            using (UnityWebRequest uwr = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET))
+            {
+                uwr.downloadHandler = new DownloadHandlerTexture();
+                yield return uwr.SendWebRequest();
+                if (uwr.result != UnityWebRequest.Result.Success) yield break;
+                Texture2D texture = DownloadHandlerTexture.GetContent(uwr);
+                Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
+                callback?.Invoke(sprite);
+            }
+        }
+    }
+
+    public class RequestResult
+    {
+        public int code = -9999;
+        public object data;
+        public string msg = "网络出错";
+    }
+
+    public class LoginResult
+    {
+        /// <summary>
+        /// 是否登录成功
+        /// </summary>
+        public bool success;
+        /// <summary>
+        /// 消息描述
+        /// </summary>
+        public string message;
+    }
+
+    public class GetUserInfoResult
+    {
+        /// <summary>
+        /// 是否获取成功
+        /// </summary>
+        public bool success;
+        /// <summary>
+        /// 消息描述
+        /// </summary>
+        public string message;
+        /// <summary>
+        /// 用户信息
+        /// </summary>
+        public UserInfo userInfo;
+    }
+
+    public class UserInfo
+    {
+        /// <summary>
+        /// 昵称
+        /// </summary>
+        public string nickname;
+        /// <summary>
+        /// 头像地址
+        /// </summary>
+        public string avatarUrl;
+        /// <summary>
+        /// 性别(1表示男,2表示女)
+        /// </summary>
+        public int gender;
+    }
+}

+ 183 - 0
SmartBowSDK/o06DOF.cs

@@ -0,0 +1,183 @@
+using ArduinoBluetoothAPI;
+using System;
+using UnityEngine;
+
+
+public class o06DOF
+{
+
+    /////////////////////g       degree/ms         
+    //void Update(Vector3 Acc, Vector3 Gyr, Vector3 Mag, long TimeGap)
+    //{
+    //    if (TimeGap <= 0)
+    //        return;
+    //}
+
+    public class SixAxisFusion
+    {
+        Vector3 acceleratorOffset;
+        Vector3 gyroscopeOffset;
+        float gForce;
+        float gyroscopeToDegree;
+        //float acceleratorRateAsOffset;
+
+        float overallGVectorCredibility = 0;
+        Vector3 previousAccelerator;
+        public Vector3 acceleratorGlobal;
+        public Quaternion quaternion = new Quaternion(0, 0, 0, 1);
+        public Vector3 accelerationNG//with No G force
+        {
+            get
+            {
+                var newQuat = new Quaternion(quaternion.x, quaternion.y, quaternion.z, -quaternion.w);
+                return previousAccelerator - newQuat * new Vector3(0, -gForce, 0);
+            }
+        }
+        public Vector3 speed = Vector3.zero;
+        public Vector3 locus = Vector3.zero;
+        public Vector3 rotation = Vector3.zero;
+        public SixAxisFusion(Vector3 acceleratorOffset, Vector3 gyroscopeOffset, float gForce, float gyroscopeToDegree)
+        {
+            this.acceleratorOffset = acceleratorOffset;// 反向校正
+            this.gyroscopeOffset = gyroscopeOffset;// 反向校正
+            this.gForce = gForce;
+            this.gyroscopeToDegree = gyroscopeToDegree;
+            //this.acceleratorRateAsOffset = acceleratorRateAsOffset;
+
+            previousAccelerator = new Vector3(0, -gForce, 0);
+            acceleratorGlobal = new Vector3(0, -gForce, 0);
+        }
+        Quaternion update(Vector3 accelerator, Vector3 gyroscope)
+        {
+            accelerator += acceleratorOffset;
+            gyroscope += gyroscopeOffset;
+            gyroscope *= gyroscopeToDegree;
+
+            var newQua = new Quaternion();
+            newQua.eulerAngles = gyroscope;
+            // 根据陀螺仪计算新的四元数旋转
+            //var newQua = Quaternion.Euler(gyroscope); // 使用 Euler 角度来生成四元数
+            quaternion = quaternion * newQua;//一定要这样乘
+
+            float gForceCredibility = 1 / (1 + Mathf.Pow((Mathf.Abs(Mathf.Sqrt(accelerator.sqrMagnitude) - gForce) / gForce * 16), 1.0f));
+            float gDegreeCredibility = 1 / (1 + Mathf.Pow(Vector3.Angle(accelerator, newQua * previousAccelerator), 1.0f));
+            overallGVectorCredibility = overallGVectorCredibility * 0.3f + gForceCredibility * gDegreeCredibility * 0.7f;
+            //Debug.Log(Mathf.Pow(overallGVectorCredibility, 0.1f));
+
+            float rotationUnCredibility = Mathf.Pow(1 - 1 / (1 + gyroscope.magnitude), 5);
+            //Debug.Log(rotationUnCredibility);
+
+            acceleratorGlobal = quaternion * accelerator;
+            Vector3 downVector = new Vector3(0, -1, 0);
+            float angle = Vector3.Angle(acceleratorGlobal, downVector);
+            //Vector3 gVector = Vector3.RotateTowards(acceleratorGlobal, downVector, angle / 180 * Mathf.PI * Mathf.Pow(overallGVectorCredibility, 1f) * rotationUnCredibility, 1);//偏移量,从陀螺仪数据往加速计数据的偏移量
+            Vector3 gVector = Vector3.RotateTowards(acceleratorGlobal, downVector, angle / 180 * Mathf.PI * Mathf.Pow(overallGVectorCredibility, 1f) * 0.1f, 1);//偏移量,从陀螺仪数据往加速计数据的偏移量
+                                                                                                                                                                //Vector3 gVector = Vector3.RotateTowards(acceleratorGlobal, downVector, angle / 180 * Mathf.PI * 1, 1);//偏移量,从陀螺仪数据往加速计数据的偏移量
+
+            newQua = new Quaternion();
+            newQua.SetFromToRotation(quaternion * accelerator, gVector);
+            // 使用 SetFromToRotation 进行四元数校正
+           // newQua = Quaternion.FromToRotation(quaternion * accelerator, gVector);
+            quaternion = newQua * quaternion;//一定要反过来乘
+
+            previousAccelerator = accelerator;
+            rotation = gyroscope;
+            speed = speed * 0.95f + accelerationNG;
+            locus = locus * 0.95f + speed;
+            return quaternion;
+        }
+
+        public void ResetToInitialRotation()
+        {
+            quaternion = Quaternion.identity;
+            speed = Vector3.zero;
+            locus = Vector3.zero;
+            rotation = Vector3.zero;
+
+            previousAccelerator = new Vector3(0, -gForce, 0);
+            acceleratorGlobal = new Vector3(0, -gForce, 0);
+
+            Debug.Log("重置到初始状态");
+        }
+        /// <summary>
+        /// 转换成四元数
+        /// </summary>
+        /// <param name="accelerator"></param>
+        /// <param name="gyroscope"></param>
+        /// <returns></returns>
+        public Quaternion tranUpdateData(SixData sixData) {
+            return update(sixData.Acc, sixData.Gryo);
+        }
+
+
+        protected double LastMS;
+        /// <summary>
+        /// 解析处理一个6轴数据
+        /// </summary>
+        /// <param name="bytes"></param>
+        /// <returns></returns>
+        public SixData getSixAxisByBytes(byte[] bytes)
+        {
+            // 获取时间信息
+            int min = bytes[16];
+            int sec = bytes[17];
+            float millis = SNum(bytes[1], bytes[2]);
+            var CurrentMS = (((double)min) * 60 + sec) * 1000 + millis;
+            if (LastMS == default)
+            {
+                LastMS = CurrentMS;
+            }
+            // 计算时间差,以秒为单位
+            var GapMS = CurrentMS - LastMS;
+            LastMS = CurrentMS;
+            double deltaTime = GapMS / 1000.0;  // 转换为秒
+            string handle = bytes[3].ToString();
+            float ax = (SNum(bytes[4], bytes[5]) / 32768 * 16);
+            float ay = (SNum(bytes[6], bytes[7]) / 32768 * 16);
+            float az = (SNum(bytes[8], bytes[9]) / 32768 * 16);
+            float gx = (SNum(bytes[10], bytes[11]) / 32768 * 2000); // 度/秒
+            float gy = (SNum(bytes[12], bytes[13]) / 32768 * 2000); // 度/秒
+            float gz = (SNum(bytes[14], bytes[15]) / 32768 * 2000); // 度/秒
+            string[] parseDataAcc = { ax.ToString("#0.000"), ay.ToString("#0.000"), az.ToString("#0.000") };
+            string[] parseDataGro = { gx.ToString("#0.000"), gy.ToString("#0.000"), gz.ToString("#0.000") };
+            string[] parseDataTime = { min.ToString(), sec.ToString(), millis.ToString() };
+
+            // 计算每帧旋转的角度(角速度 * 时间)
+            float rotationX = gx * (float)deltaTime;  // X轴转动的度数
+            float rotationY = gy * (float)deltaTime;  // Y轴转动的度数
+            float rotationZ = gz * (float)deltaTime;  // Z轴转动的度数
+                                                      //Quaternion quaternion = update(new Vector3(ax, ay, az), new Vector3(rotationX, rotationY, rotationZ));
+                                                      //+ ":Quat:" + quaternion 
+                                                      // Debug.Log("数据:Acc[" + string.Join(", ", parseDataAcc) + "] Gryo:" + string.Join(", ", parseDataGro) + "]. Time:" + string.Join(", ", parseDataTime) + ",Euler:" + quaternion.eulerAngles);
+            return new SixData(new Vector3(ax, ay, az), new Vector3(rotationX, rotationY, rotationZ), min, sec, millis);
+        }
+        float SNum(byte b1, byte b2)
+        {
+            ushort twoByte = (ushort)(b1 * 256 + b2);
+            short shortNum = (short)twoByte;
+            return (float)shortNum;
+        }
+    }
+
+   
+    /// <summary>
+    /// 解析转换的6轴格式
+    /// </summary>
+    public class SixData
+    {
+        public Vector3 Acc;
+        public Vector3 Gryo;
+        public int Min;
+        public int Sec;
+        public float Millis;
+
+        public SixData(Vector3 acc, Vector3 gyro, int min, int sec, float millis)
+        {
+            Acc = acc;
+            Gryo = gyro;
+            Min = min;
+            Sec = sec;
+            Millis = millis;
+        }
+    }
+}

+ 322 - 0
SmartBowSDK/o09AxisAfterXiaMenFromDll_SDK.cs

@@ -0,0 +1,322 @@
+using o0.Geometry;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+
+namespace o0.Bow
+{
+    public class o09AxisAfterXiaMenFromDll_SDK//卡尔曼滤波优化地磁计向量版
+    {
+        public IMU._9AxisPreProcessor Attitude;
+        public IMU.HardwareVariance GyrHardwareVariance;
+        public IMU.HardwareVariance AccHardwareVariance;
+        public IMU.HardwareVariance MagHardwareVariance;
+
+        // public static List<UnityEngine.Quaternion> QuaTest = new List<UnityEngine.Quaternion>();
+        public o09AxisAfterXiaMenFromDll_SDK(Vector<int> GyrByteIndex = default, Vector<int> AccByteIndex = default, Vector<int> MagByteIndex = default)
+        {
+            Attitude = new IMU._9AxisPreProcessor(GyrByteIndex, AccByteIndex, MagByteIndex);
+            GyrHardwareVariance = new IMU.HardwareVariance();
+            AccHardwareVariance = new IMU.HardwareVariance();
+            MagHardwareVariance = new IMU.HardwareVariance();
+
+            // QuaTest.Add(UnityEngine.Quaternion.identity);
+            // QuaTest.Add(UnityEngine.Quaternion.identity);
+        }
+
+        static readonly Vector<double> AccIdentityDefault = new Vector<double>(0, -1, 0);
+        static readonly Vector<double> MagIdentityDefault = new Vector<double>(-1, 2, 0).Normalized;
+        public Vector<double> AccIdentity = new Vector<double>(0, -1, 0);
+        public Vector<double> MagIdentity = new Vector<double>(-1, 2, 0).Normalized;
+        public class State
+        {
+            public double TimeGap;
+            public Vector<double> Acc = AccIdentityDefault;
+            public Vector<double> AccSmooth = AccIdentityDefault;
+
+            public Vector<double> Gyr;
+            public Vector<double> Mag = MagIdentityDefault;
+            public Vector<double> MagSmooth = MagIdentityDefault;
+
+            public double GapMS;
+
+            public Geometry.Quaternion Qua = Geometry.Quaternion.Identity;
+            public Geometry.Quaternion QuaSmooth = Geometry.Quaternion.Identity;
+            public double Variance = 1;
+            public double GyrVariance = 1;
+            public double AccVariance = 1;
+            public double MagVariance = 1;
+            public Geometry.Quaternion QuaAccMag = Geometry.Quaternion.Identity;
+            public int QuaAccMagCount = 0;
+            public double AccMagVariance = 1;
+            public double TotalVariance = 1;
+        }
+
+
+        public List<State> States = new List<State>();
+        public int StatesMaxCount = 1000;
+
+
+        public Geometry.Quaternion Update(IEnumerable<byte> gyrByte, IEnumerable<byte> accByte, IEnumerable<byte> magByte,
+            byte min, byte sec, byte ms1, byte ms2)
+        {
+            var (Gyr, Acc, Mag, TimeGap) = Attitude.Update(gyrByte, accByte, magByte, min, sec, ms1, ms2);
+            if ((Gyr, Acc, Mag, TimeGap) == default)
+                return Geometry.Quaternion.Identity;
+
+
+            Vector<double> Buffer = default;
+            Buffer = GyrHardwareVariance.Update(Attitude.LastGyr);
+            if (Buffer != default)
+                Debug.Log("GyrHardwareVariance: " + Buffer.Length);
+            Buffer = AccHardwareVariance.Update(Acc);
+            if (Buffer != default)
+                Debug.Log("AccHardwareVariance: " + Buffer.Length);
+            Buffer = MagHardwareVariance.Update(Mag);
+            if (Buffer != default)
+                Debug.Log("MagHardwareVariance: " + Buffer.Length);
+
+            // var GyrOperator = Geometry.Quaternion.Euler(Gyr * TimeGap);
+            // TestVector.Update9AxisRotation(GyrOperator.ToUnityQuaternion(), 0);
+            // TestVector.SetAcc(Acc.ToUnityVector(), 0);
+            // TestVector.SetMag(Mag.ToUnityVector(), 0);
+
+
+            var Last = States.LastOrDefault() ?? new State();
+            States.Add(new State());
+            if (States.Count > StatesMaxCount)
+                States.RemoveAt(0);
+            var state = States.Last();
+            state.Acc = Acc;
+
+
+            //Debug.Log(Gyr.magnitude);
+
+            state.Gyr = Gyr;
+            state.Mag = Mag;/**/
+            state.TimeGap = TimeGap;
+            if (States.Count <= 1)
+                return Geometry.Quaternion.Identity;
+            return Process9Axis(Last, state);
+        }
+        int ShakeFrame;
+        int AccVarianceInput;
+        /// //////////////////向前追溯多少帧//向后多少帧计算抖动
+        public void OnShot(int TrackBack, int ShakeFrame, int AccVarianceInput)
+        {
+            this.AccVarianceInput = AccVarianceInput;
+            TrackBack = Math.Min(TrackBack, States.Count);
+            var startI = States.Count - TrackBack;
+            State Last = default;
+            foreach (var i in TrackBack.Range())
+            {
+                var state = States[startI + i];
+
+                if (Last != default)
+                    Process9Axis(Last, state);
+                Last = state;
+            }
+
+            this.ShakeFrame = ShakeFrame;
+            Debug.Log("OnShot");/**/
+        }
+        //public double diff = 0.001;
+        public double diff = 0.0001;
+
+        public void MagSmooth(State Last, State state)
+        {
+            var Acc = state.Acc;
+            var Gyr = state.Gyr;
+            var Mag = state.Mag;
+            double TimeGap = state.TimeGap;
+            double GyrVariance = Last.MagVariance + Math.Pow((Gyr * TimeGap).Length, 2);
+            double AccVariance = AccVarianceInput != default ? AccVarianceInput : Math.Pow((Gyr * TimeGap).Length * 1, 2) + Math.Pow(Math.Abs(Acc.Length - 1) / 1 * 30, 2);
+            double MagVariance = 25;
+
+            state.AccSmooth = Last.AccSmooth + (state.Acc - Last.AccSmooth) * GyrVariance / (AccVariance + GyrVariance);
+            state.AccVariance = (GyrVariance + AccVariance) / GyrVariance / AccVariance;
+            state.MagSmooth = Last.MagSmooth + (state.Mag - Last.MagSmooth) * GyrVariance / (MagVariance + GyrVariance);
+            state.MagVariance = (GyrVariance + MagVariance) / GyrVariance / MagVariance;
+
+            // TestVector.SetAcc(state.AccSmooth.ToUnityVector(), 1);
+            // TestVector.SetMag(state.MagSmooth.ToUnityVector(), 1);
+        }
+        public static double vari = 0.001;
+        public double CalGyrVariance(State Last, State state)
+        {
+            //return Last.Variance + (state.Gyr * state.TimeGap).Length * 0.3 + 0.001;
+            return Last.Variance + (state.Gyr * state.TimeGap).Length * 0.3 + vari * state.TimeGap;//0.00000002331017
+        }
+        public double CalAccVariance(State Last, State state)
+        {
+            double AccLengthToAngle = 10000;//1倍引力差相当于多少度方差
+            return AccVarianceInput != default ? AccVarianceInput :
+                Math.Max(
+                    (state.Gyr * state.TimeGap).Length * 1000,
+                    Math.Abs(state.Acc.Length - 1) * AccLengthToAngle
+                );
+            //return AccVarianceInput != default ? AccVarianceInput : Math.Max((state.Gyr * state.TimeGap).Length, AccAngleDiff) * 1 + Math.Pow(Math.Abs(state.Acc.Length - 1) / 1 * AccLengthToAngle, 4);
+        }
+        public double CalMagVariance(State Last, State state)
+        {
+            return 100;
+        }
+        public (double, double, double) CalVariance(State Last, State state)
+        {
+            var GyrVariance = CalGyrVariance(Last, state);
+            var AccVariance = CalAccVariance(Last, state);
+            var MagVariance = CalMagVariance(Last, state);
+            if (double.IsInfinity(GyrVariance) || double.IsNaN(GyrVariance))
+                GyrVariance = InitVariance;
+            if (double.IsInfinity(AccVariance) || double.IsNaN(AccVariance))
+                AccVariance = InitVariance;
+            if (double.IsInfinity(MagVariance) || double.IsNaN(MagVariance))
+                MagVariance = InitVariance;
+            var max = Math.Max(AccVariance, MagVariance);
+            state.Variance = GyrVariance * max / (GyrVariance + max);
+            if (double.IsInfinity(state.Variance) || double.IsNaN(state.Variance))
+                state.Variance = InitVariance;
+            return (GyrVariance, AccVariance, MagVariance);
+        }
+
+        public void QuaCheck(ref Geometry.Quaternion quaternion, Geometry.Quaternion backUp)
+        {
+            if (double.IsInfinity(quaternion.x) || double.IsInfinity(quaternion.y) || double.IsInfinity(quaternion.z) || double.IsInfinity(quaternion.w)
+                || double.IsNaN(quaternion.x) || double.IsNaN(quaternion.y) || double.IsNaN(quaternion.z) || double.IsNaN(quaternion.w))
+                quaternion = backUp;
+        }
+        // public static string debugInfo = "";
+        public Geometry.Quaternion Process9Axis(State Last, State state)
+        {
+            MagSmooth(Last, state);
+            //var Acc = state.AccSmooth;
+            var Acc = state.Acc;
+            var Gyr = state.Gyr;//误差0.0001-0.0006之间
+            //var Gyr = Vector<double>.One.Normalized * 0.001;//误差0.0001-0.0006之间
+            var Mag = state.Mag;
+            double TimeGap = state.TimeGap;
+
+
+            //Debug.Log(Gyr.Length);
+            // debugInfo = $"Acc:{Acc}\n";
+            // debugInfo += $"AccLen:{Acc.Length}\n";
+            // debugInfo += $"Gyr:{Gyr}\n";
+            // debugInfo += $"GyrLen:{Gyr.Length}\n";
+            // debugInfo += $"Mag:{Mag}\n";
+            // debugInfo += $"MagLen:{Mag.Length}\n";
+            // debugInfo += $"TimeGap:{TimeGap}\n";
+            // debugInfo += $"GyrMeanLen:{Attitude.GyrCalibrater.Mean.Length}\n";
+            // debugInfo += $"MagVariance:{Attitude.MagCalibrater.Variance}\n";
+
+            var LastQuaternion = Last.QuaSmooth;
+
+
+
+            var GyrOperator = Geometry.Quaternion.Euler((Gyr * TimeGap).To<float>());
+            var quaGyr = LastQuaternion * GyrOperator;
+
+
+            // var accTest = Geometry.Quaternion.FromToRotation(Last.Acc, Acc).Inversed;
+            // TestVector.Set9AxisRotation(Last.QuaSmooth.ToUnityQuaternion(), 2);
+            // var magTest = Geometry.Quaternion.FromToRotation(Last.Mag, Mag).Inversed;
+            // TestVector.Update9AxisRotation(GyrOperator.ToUnityQuaternion(), 3);
+            //TestVector.Set9AxisRotation(quaGyr.ToUnityQuaternion(), 3);
+            //TestVector.Set9AxisRotation(Last.Qua, 3);
+
+
+            var (GyrVariance, AccVariance, MagVariance) = CalVariance(Last, state);
+
+            var quaAccMag = Geometry.Quaternion.FormQuaternion(AccIdentity.To<float>(), MagIdentity.To<float>(), Acc.To<float>(), Mag.To<float>(), (float)(AccVariance / (AccVariance + MagVariance)));
+            var quaMinRate = GyrVariance / (GyrVariance + Math.Max(AccVariance, MagVariance));//静止时约0.01效果最好
+            var quaMaxRate = GyrVariance / (GyrVariance + Math.Min(AccVariance, MagVariance));
+            Geometry.Quaternion quaFirst = Geometry.Quaternion.SLerp(quaGyr, quaAccMag, (float)quaMinRate);//运算到这里如果视角校准有漂移,说明需要重新校准陀螺仪//不是代码问题
+            QuaCheck(ref quaFirst, LastQuaternion);//防止数值溢出
+
+            var quaSecondRate = (quaMaxRate - quaMinRate) / (1 - quaMinRate);
+
+            // Debug.Log((quaMinRate, AccVariance, quaSecondRate));
+
+            state.Qua = AccVariance < MagVariance ? Geometry.Quaternion.FormQuaternion(quaFirst, AccIdentity.To<float>(), Acc.To<float>(), (float)quaSecondRate) : Geometry.Quaternion.FormQuaternion(quaFirst, MagIdentity.To<float>(), Mag.To<float>(), (float)quaSecondRate);/**/
+            //state.Qua = quaFirst;
+
+            QuaCheck(ref state.Qua, LastQuaternion);//防止数值溢出
+            state.QuaSmooth = state.Qua;
+
+            if (ShakeFrame > 0)
+            {
+                --ShakeFrame;
+                if (ShakeFrame == 0)
+                    AccVarianceInput = default;
+            }
+            return state.QuaSmooth;
+        }
+
+        public double InitVariance
+        {
+            get
+            {
+                var minVariance = double.MaxValue;
+                foreach (var i in States)
+                    if (!double.IsInfinity(i.Variance)
+                        && !double.IsNaN(i.Variance)
+                        && minVariance > i.Variance)
+                        minVariance = i.Variance;
+                return minVariance;
+            }
+        }
+        public void Init()
+        {
+            States.Last().AccVariance = 1000;
+            States.Last().GyrVariance = 1000;
+            States.Last().MagVariance = 1000;
+            States.Last().AccMagVariance = 1000;
+            States.Last().TotalVariance = 1000;
+        }
+        public Geometry.Quaternion SetIdentity()
+        {
+            //UnityEngine.Quaternion qua = default;
+
+            //int averageCount = Math.Min(States.Count, 5);
+            int averageCount = Math.Min(States.Count, 50);
+            Vector<double> aveAcc = Vector<double>.Zero;
+            Vector<double> aveMag = Vector<double>.Zero;
+            for (var i = States.Count - averageCount; i < States.Count; ++i)
+            {
+                aveAcc += States[i].AccSmooth;
+                aveMag += States[i].MagSmooth;
+            }
+            aveAcc /= averageCount;
+            aveMag /= averageCount;
+            //AccIdentity = AccOld;
+            //MagIdentity = MagOld;
+            AccIdentity = aveAcc;
+            MagIdentity = aveMag;
+            //qua = o0Project.o0.FormQuaternion(Quaternion.identity, Vector3.down,AccIdentity, 1);
+            //AccIdentity=qua*AccIdentity;
+            //MagIdentity = qua*MagIdentity;
+            States.Last().Qua = Geometry.Quaternion.Identity;
+            States.Last().QuaSmooth = Geometry.Quaternion.Identity;
+            //States.Last().Qua = qua*States.Last().Qua;//Quaternion.identity;
+            States.Last().Variance = InitVariance;
+            States.Last().AccVariance = InitVariance;
+            States.Last().GyrVariance = InitVariance;
+            States.Last().MagVariance = InitVariance;
+            States.Last().QuaAccMag = Geometry.Quaternion.Identity;
+            States.Last().QuaAccMagCount = 0;
+            States.Last().AccMagVariance = InitVariance;
+            States.Last().TotalVariance = InitVariance;
+
+
+            return States.Last().Qua;
+        }
+
+        public State getLastState()
+        {
+            return this.States.Last();
+        }
+
+    }
+
+}

+ 12 - 0
SmartBowSDK/o0Bow.cs

@@ -0,0 +1,12 @@
+using o0.Geometry;
+
+namespace o0.Bow
+{
+    public static partial class Extension
+    {
+        public static UnityEngine.Quaternion ToUnityQuaternion(this Quaternion q) => new UnityEngine.Quaternion((float)q.x, (float)q.y, (float)q.z, (float)q.w);
+        public static Quaternion Too0Quaternion(this UnityEngine.Quaternion q) => new Quaternion(q.x, q.y, q.z, q.w);
+        public static UnityEngine.Vector3 ToUnityVector(this Vector<double> v) => new UnityEngine.Vector3((float)v.x, (float)v.y, (float)v.z);
+        public static Vector<double> Too0Vector(this UnityEngine.Vector3 v) => new Vector<double>(v.x, v.y, v.z);
+    }
+}