From 7729332f41e42665ae4422e3ea2d4c87b3756bc6 Mon Sep 17 00:00:00 2001 From: suntao Date: Fri, 4 Jun 2021 10:35:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E5=9F=BA=E6=9C=AC=E8=AE=BE?= =?UTF-8?q?=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Resources/Images/Bluetooth_0.png | Bin 0 -> 4990 bytes Assets/Resources/Images/Bluetooth_0.png.meta | 104 +++++++ Assets/Resources/Images/Bluetooth_2.png | Bin 0 -> 3731 bytes Assets/Resources/Images/Bluetooth_2.png.meta | 104 +++++++ .../UI/Prefab/Panel/DevicePanel.prefab | 240 +++++++++++++++- Assets/Scripts/Apis/MapApi.cs | 1 + Assets/Scripts/Devices/AbstractDevice.cs | 14 + Assets/Scripts/Devices/Ant/DataSourceBase.cs | 2 +- Assets/Scripts/Devices/Ant/FitDevice.cs | 2 +- .../Devices/Ant/Interfaces/ITrainerDevice.cs | 19 ++ .../Ant/Interfaces/ITrainerDevice.cs.meta | 11 + Assets/Scripts/Devices/Ant/SensorType.cs | 6 + Assets/Scripts/Devices/Ble/BleDevice.cs | 14 +- .../Scripts/Devices/Ble/BleDeviceAdapter.cs | 20 +- .../Ble/Characteristic/BatteryLevel.cs | 49 ++++ .../Ble/Characteristic/BatteryLevel.cs.meta | 11 + .../Characteristic/CyclingPowerMeasurement.cs | 57 ++++ .../CyclingPowerMeasurement.cs.meta | 11 + .../CyclingSpeedCadenceMeasurement.cs | 271 ++++++++++++++++++ .../CyclingSpeedCadenceMeasurement.cs.meta | 11 + .../FtmsFitnessMachineFeature.cs | 45 +++ .../FtmsFitnessMachineFeature.cs.meta | 11 + .../Ble/Characteristic/FtmsIndoorBikeData.cs | 220 ++++++++++++++ .../Characteristic/FtmsIndoorBikeData.cs.meta | 11 + .../Characteristic/HeartRateMeasurement.cs | 16 +- .../ICharacteristicExtensions.cs | 46 +++ .../ICharacteristicExtensions.cs.meta | 11 + .../ManufacturerNameCharacteristic.cs | 31 ++ .../ManufacturerNameCharacteristic.cs.meta | 11 + .../ModelNumberCharacteristic.cs | 31 ++ .../ModelNumberCharacteristic.cs.meta | 11 + .../Ble/Characteristic/TacxFecNotify.cs | 205 +++++++++++++ .../Ble/Characteristic/TacxFecNotify.cs.meta | 11 + .../Ble/Characteristic/TacxFecWrite.cs | 39 +++ .../Ble/Characteristic/TacxFecWrite.cs.meta | 11 + .../Devices/Ble/Devices/CyclingPower.cs | 26 +- Assets/Scripts/Devices/Ble/Devices/Ftms.cs | 107 ++++++- .../Scripts/Devices/Ble/Devices/HeartRate.cs | 48 +++- .../Devices/Ble/Devices/SpeedCadence.cs | 103 +++++++ .../Devices/Ble/Devices/SpeedCadence.cs.meta | 11 + Assets/Scripts/Devices/Ble/Devices/Tacx.cs | 262 +++++++++++++++++ .../Scripts/Devices/Ble/Devices/Tacx.cs.meta | 11 + .../Ble/Extension/PrimitiveExtensions.cs | 21 ++ .../Ble/Extension/PrimitiveExtensions.cs.meta | 11 + Assets/Scripts/Devices/Ble/Interfaces.meta | 8 + .../Devices/Ble/Interfaces/ICharacteristic.cs | 20 ++ .../Ble/Interfaces/ICharacteristic.cs.meta | 11 + .../Devices/Ble/Scan/BleAdvertisementInfo.cs | 13 +- .../Scripts/Devices/Ble/Win/BleDeviceProxy.cs | 2 +- .../Devices/Ble/Win/BleWinHwInterface.cs | 132 ++++++--- .../Devices/Ble/Win/GattDisconnected.cs | 1 + .../Devices/Ble/Win/GattServicesDiscovered.cs | 1 + .../Devices/Ble/Win/WclAlertableWait.cs | 43 +++ .../Devices/Ble/Win/WclAlertableWait.cs.meta | 11 + .../Devices/Ble/Win/WclBleGattThread.cs | 133 ++++++++- .../Devices/Ble/Win/WclBleMainThread.cs | 11 +- .../Scripts/Devices/Ble/Win/WclBleThread.cs | 215 ++++++++++++++ .../Devices/Ble/Win/WclBleThread.cs.meta | 11 + Assets/Scripts/Devices/MainDeviceAdapter.cs | 2 + Assets/Scripts/UI/Prefab/Device/DeviceItem.cs | 10 +- Assets/Scripts/UI/Prefab/MainNav.cs | 2 +- .../UI/Prefab/Panel/DeviceController.cs | 83 +++++- 62 files changed, 2880 insertions(+), 86 deletions(-) create mode 100644 Assets/Resources/Images/Bluetooth_0.png create mode 100644 Assets/Resources/Images/Bluetooth_0.png.meta create mode 100644 Assets/Resources/Images/Bluetooth_2.png create mode 100644 Assets/Resources/Images/Bluetooth_2.png.meta create mode 100644 Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs create mode 100644 Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs create mode 100644 Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs create mode 100644 Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Devices/Tacx.cs create mode 100644 Assets/Scripts/Devices/Ble/Devices/Tacx.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs create mode 100644 Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Interfaces.meta create mode 100644 Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs create mode 100644 Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs create mode 100644 Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs.meta create mode 100644 Assets/Scripts/Devices/Ble/Win/WclBleThread.cs create mode 100644 Assets/Scripts/Devices/Ble/Win/WclBleThread.cs.meta diff --git a/Assets/Resources/Images/Bluetooth_0.png b/Assets/Resources/Images/Bluetooth_0.png new file mode 100644 index 0000000000000000000000000000000000000000..791ebc9624f5a989b36a38309c03b4f318e56bf2 GIT binary patch literal 4990 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 IM6N<$f`W#E*#H0l literal 0 HcmV?d00001 diff --git a/Assets/Resources/Images/Bluetooth_0.png.meta b/Assets/Resources/Images/Bluetooth_0.png.meta new file mode 100644 index 00000000..eef33745 --- /dev/null +++ b/Assets/Resources/Images/Bluetooth_0.png.meta @@ -0,0 +1,104 @@ +fileFormatVersion: 2 +guid: 05133d14d67abd743954cf1d04471e99 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Images/Bluetooth_2.png b/Assets/Resources/Images/Bluetooth_2.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7e336c5b2ba2327a65ccb9fe51b5b84eba9ffd GIT binary patch literal 3731 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 GetEarthData(double lat, double lon) { + //CultureInfo.InvariantCulture var result = await GetAsync>($"Map/GetEarthData?lat={ lat }&lon={ lon }"); return result; diff --git a/Assets/Scripts/Devices/AbstractDevice.cs b/Assets/Scripts/Devices/AbstractDevice.cs index 0ecc01ea..4fccfd2e 100644 --- a/Assets/Scripts/Devices/AbstractDevice.cs +++ b/Assets/Scripts/Devices/AbstractDevice.cs @@ -26,6 +26,20 @@ namespace Assets.Scripts.Devices get; protected set; } + public NetworkType Network { get; internal set; } + + /// + /// 信号强度 + /// 信号从0dbm~97dbm,个人建议是0~30是强,30~70是中,70~97是弱。 + /// + public int SignalStrength { get; internal set; } + + protected AbstractDevice(NetworkType networkType) + { + this.Network = networkType; + } + + public abstract void Connect(); public abstract void Disconnect(bool save = true); diff --git a/Assets/Scripts/Devices/Ant/DataSourceBase.cs b/Assets/Scripts/Devices/Ant/DataSourceBase.cs index 54098f96..978be736 100644 --- a/Assets/Scripts/Devices/Ant/DataSourceBase.cs +++ b/Assets/Scripts/Devices/Ant/DataSourceBase.cs @@ -26,7 +26,7 @@ namespace Assets.Scripts.Devices.Ant public readonly byte uid; - public DataSourceBase(racerSportType sportType, bool isHuman) + public DataSourceBase(racerSportType sportType, bool isHuman):base(NetworkType.ANT) { this.sportType = sportType; this.isHuman = isHuman; diff --git a/Assets/Scripts/Devices/Ant/FitDevice.cs b/Assets/Scripts/Devices/Ant/FitDevice.cs index 947ed54d..08382a5b 100644 --- a/Assets/Scripts/Devices/Ant/FitDevice.cs +++ b/Assets/Scripts/Devices/Ant/FitDevice.cs @@ -9,7 +9,7 @@ using UnityEngine; namespace Assets.Scripts.Devices.Ant { - public class FitDevice : AbstractAntDevice, ISpeedDevice, IPowerDevice, ICadenceDevice, IRequiresRiderWeight + public class FitDevice : AbstractAntDevice, ISpeedDevice, IPowerDevice, ICadenceDevice, IRequiresRiderWeight, ITrainerDevice { public const int MAX_NO_EVENT_STOP_COUNT = 12; diff --git a/Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs b/Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs new file mode 100644 index 00000000..1a5df313 --- /dev/null +++ b/Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ant.Interfaces +{ + public interface ITrainerDevice + { + void SetErgMode(int targetPower); + + void SetResistanceMode(double value); + + void SetWindResistance(double? height = null); + + void SetTrackResistance(double grade); + } +} diff --git a/Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs.meta b/Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs.meta new file mode 100644 index 00000000..4d738a92 --- /dev/null +++ b/Assets/Scripts/Devices/Ant/Interfaces/ITrainerDevice.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 362cacfc69d803d478112e7c293cfa17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ant/SensorType.cs b/Assets/Scripts/Devices/Ant/SensorType.cs index 7b8c6469..259aca9f 100644 --- a/Assets/Scripts/Devices/Ant/SensorType.cs +++ b/Assets/Scripts/Devices/Ant/SensorType.cs @@ -39,4 +39,10 @@ namespace Assets.Scripts.Devices.Ant Trainer, VirtualPower, } + + public enum NetworkType + { + BLE, + ANT + } } diff --git a/Assets/Scripts/Devices/Ble/BleDevice.cs b/Assets/Scripts/Devices/Ble/BleDevice.cs index 6041daef..133543d5 100644 --- a/Assets/Scripts/Devices/Ble/BleDevice.cs +++ b/Assets/Scripts/Devices/Ble/BleDevice.cs @@ -2,6 +2,8 @@ using Assets.Scripts.Ble.HeartRate; using Assets.Scripts.Ble.Service; using Assets.Scripts.Devices.Ant; +using Assets.Scripts.Devices.Ble.Characteristic; +using Assets.Scripts.Devices.Ble.Interfaces; using System; using System.Collections.Generic; using System.Linq; @@ -22,17 +24,23 @@ namespace Assets.Scripts.Devices.Ble public override string Name { get => peripheralInfo.Name; protected set => base.Name = value; } - public BleDevice(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface, SensorType sensor) //: base(peripheralInfo) + public List Characteristics { get; protected set; } = new List(); + + public BleDevice(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface, SensorType sensor) : base(NetworkType.BLE) { this.hwInterface = bleWinHwInterface; this.hwInterface.BluetoothStateChangedEvent += BluetoothStateChangedEvent; this.peripheralInfo = peripheralInfo; base.Sensor = sensor; - + //base.Name = this.peripheralInfo.Name; //Debug.Log(base.Name + "," + sensor.ToString()); + + Characteristics.Add(new BatteryLevel()); + Characteristics.Add(new ManufacturerNameCharacteristic()); + Characteristics.Add(new ModelNumberCharacteristic()); } public void ConnectToPeripheralIfPossible() @@ -230,7 +238,7 @@ namespace Assets.Scripts.Devices.Ble public override void Disconnect(bool save = true) { //throw new NotImplementedException(); - + this.hwInterface.DisconnectPeripheral(this.peripheralInfo, null); } } } diff --git a/Assets/Scripts/Devices/Ble/BleDeviceAdapter.cs b/Assets/Scripts/Devices/Ble/BleDeviceAdapter.cs index d4be45c5..de47e3eb 100644 --- a/Assets/Scripts/Devices/Ble/BleDeviceAdapter.cs +++ b/Assets/Scripts/Devices/Ble/BleDeviceAdapter.cs @@ -43,11 +43,19 @@ namespace Assets.Scripts.Devices.Ble { if (!discoveredDevices.ContainsKey(device.Peripheral.Address)) { - Debug.Log("发现设备" + device.Peripheral.Address + device.Peripheral.Name + ", type:" + device.SensorType); + Debug.Log($"发现设备{ device.Peripheral.Address }, \t\tName:{ device.Peripheral.Name }, type:{ device.SensorType }"); + + if (string.IsNullOrWhiteSpace(device.Peripheral.Name)) + { + return; + } if(device.SensorType == Ant.SensorType.Trainer) { //var device1 = new Ftms(device.Peripheral, hwInterface); //discoveredDevices.Add(device.Peripheral.Address, device1); + + var device1 = new Tacx(device.Peripheral, hwInterface); + discoveredDevices.Add(device.Peripheral.Address, device1); } else if(device.SensorType == Ant.SensorType.HeartRate) { @@ -59,8 +67,18 @@ namespace Assets.Scripts.Devices.Ble var device1 = new CyclingPower(device.Peripheral, hwInterface); discoveredDevices.Add(device.Peripheral.Address, device1); } + else if(device.SensorType == Ant.SensorType.SpeedCadence) + { + var device1 = new SpeedCadence(device.Peripheral, hwInterface); + discoveredDevices.Add(device.Peripheral.Address, device1); + } //discoveredDevices.Add(device.Peripheral.Address, new BleDevice(device.Peripheral, hwInterface, device.SensorType)); } + if (discoveredDevices.ContainsKey(device.Peripheral.Address)) + { + discoveredDevices[device.Peripheral.Address].SignalStrength = device.Rssi; + //Debug.Log($"设备{ device.Peripheral.Name }信号量:{ device.Rssi }"); + } }); } diff --git a/Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs b/Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs new file mode 100644 index 00000000..64a15fb0 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs @@ -0,0 +1,49 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class BatteryLevel : ICharacteristic + { + public Guid Uuid => ServiceUuids.Characteristics.BatteryLevel; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.Battery).IdGuid; + + private ICharacteristic _characteristicUnavailable = null; + public ICharacteristic CharacteristicUnavailable + { + get + { + return this._characteristicUnavailable; + } + } + + public virtual bool IsOptional + { + get + { + return true; + } + } + + public int BatteryLevelValue { get; private set; } + + public void HandleAttributeReceived(byte[] data) + { + BatteryLevelValue = (int)data[0]; + + Debug.Log($"电量:{ BatteryLevelValue }"); + } + + public void SetUnavailable() + { + this._characteristicUnavailable = this; + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs.meta new file mode 100644 index 00000000..29b9995a --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/BatteryLevel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b654b3a52c37e6f47b9823953c8fd5d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs b/Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs new file mode 100644 index 00000000..5015ddec --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs @@ -0,0 +1,57 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class CyclingPowerMeasurement: ICharacteristic + { + public Guid Uuid + { + get + { + return ServiceUuids.Characteristics.CyclingPowerMeasurement; + } + } + + public Guid ServiceUuid + { + get + { + return ServiceUuids.Get(ServiceUuids.CyclingPower).IdGuid; + } + } + + public virtual bool IsOptional + { + get + { + return false; + } + } + + private double wheelCircumference; + + public int Power { get; private set; } + + public CyclingPowerMeasurement(double wheelCircumference) + { + this.wheelCircumference = wheelCircumference; + } + + public void HandleAttributeReceived(byte[] data) + { + + this.Power = BitConverter.ToInt16(data, 2); + } + + public void SetUnavailable() + { + + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs.meta new file mode 100644 index 00000000..90ee0d7c --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/CyclingPowerMeasurement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b0646295dbaa0a4e951de9bd391e475 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs b/Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs new file mode 100644 index 00000000..2a9d6ee2 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs @@ -0,0 +1,271 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Assets.Scripts.Devices.Ble.Extension; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class CyclingSpeedCadenceMeasurement : ICharacteristic + { + private CyclingSpeedCadenceMeasurement.IUpdate _currentUpdate; + private CyclingSpeedCadenceMeasurement.IUpdate _previousUpdate = new CyclingSpeedCadenceMeasurement.NullUpdate(); + private const byte MaxNoEvents = (byte)4; + private byte _noCadenceEventCount; + private byte _noSpeedEventCount; + + + public Guid Uuid => ServiceUuids.Characteristics.CscMeasurement; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.CyclingSpeedCadence).IdGuid; + + public bool IsOptional => false; + + public double Speed { get; private set; } + public int Cadence { get; private set; } + + public double WheelCircumference { private get; set; } + + public CyclingSpeedCadenceMeasurement(double wheelCircumference) + { + this.WheelCircumference = wheelCircumference; + } + + public void HandleAttributeReceived(byte[] data) + { + //throw new NotImplementedException(); + //Debug.Log(string.Join(",", data)); + + this._currentUpdate = new CyclingSpeedCadenceMeasurement.Update(data); + this.HandleCadenceUpdate(); + this.HandleSpeedUpdate(); + this._previousUpdate = this._currentUpdate; + } + + private void HandleCadenceUpdate() + { + if (this._currentUpdate.HasCadence && (int)this._currentUpdate.CadenceEventCount != (int)this._previousUpdate.CadenceEventCount) + { + this._noCadenceEventCount = (byte)0; + if (this.CadenceTimeDifference() <= 0) + return; + this.Cadence = this.CalculateCadence(); + } + else + { + this._noCadenceEventCount = (byte)((uint)this._noCadenceEventCount + 1U); + if ((int)this._noCadenceEventCount < 4) + return; + this.Cadence = 0; + } + } + + private void HandleSpeedUpdate() + { + if (this._currentUpdate.HasSpeed && (int)this._currentUpdate.SpeedEventCount != (int)this._previousUpdate.SpeedEventCount) + { + this._noSpeedEventCount = (byte)0; + double num = this.CalculateSpeed(); + if (num >= 150.0) + return; + this.Speed = num; + } + else + { + this._noSpeedEventCount = (byte)((uint)this._noSpeedEventCount + 1U); + if ((int)this._noSpeedEventCount < 4) + return; + this.Speed = 0.0; + } + } + + private int CalculateCadence() + { + int num = this.CadenceEventDifference() * 61440 / this.CadenceTimeDifference(); + if (num > (int)byte.MaxValue) + return (int)byte.MaxValue; + if (num < 0) + return 0; + return num; + } + + private int CadenceTimeDifference() + { + if ((int)this._currentUpdate.CadenceTime1024 > (int)this._previousUpdate.CadenceTime1024) + return (int)this._currentUpdate.CadenceTime1024 - (int)this._previousUpdate.CadenceTime1024; + return (int)ushort.MaxValue - (int)this._previousUpdate.CadenceTime1024 + (int)this._currentUpdate.CadenceTime1024 + 1; + } + + private int CadenceEventDifference() + { + if ((int)this._currentUpdate.CadenceEventCount > (int)this._previousUpdate.CadenceEventCount) + return (int)this._currentUpdate.CadenceEventCount - (int)this._previousUpdate.CadenceEventCount; + return (int)ushort.MaxValue - (int)this._previousUpdate.CadenceEventCount + (int)this._currentUpdate.CadenceEventCount + 1; + } + + private double CalculateSpeed() + { + double num = (double)(ulong)(this.WheelCircumference / 10.0 * 36864.0 * (double)this.SpeedEventDifference() / (double)this.SpeedTimeDifference()) / 1000.0; + if (num >= 0.0) + return num; + return 0.0; + } + + private int SpeedTimeDifference() + { + if ((int)this._currentUpdate.SpeedTime1024 > (int)this._previousUpdate.SpeedTime1024) + return (int)this._currentUpdate.SpeedTime1024 - (int)this._previousUpdate.SpeedTime1024; + return (int)ushort.MaxValue - (int)this._previousUpdate.SpeedTime1024 + (int)this._currentUpdate.SpeedTime1024 + 1; + } + + private uint SpeedEventDifference() + { + if (this._currentUpdate.SpeedEventCount > this._previousUpdate.SpeedEventCount) + return this._currentUpdate.SpeedEventCount - this._previousUpdate.SpeedEventCount; + return (uint)(-1 - (int)this._previousUpdate.SpeedEventCount + (int)this._currentUpdate.SpeedEventCount + 1); + } + + public void SetUnavailable() + { + //throw new NotImplementedException(); + } + + private interface IUpdate + { + bool HasCadence { get; } + + bool HasSpeed { get; } + + uint SpeedEventCount { get; } + + ushort SpeedTime1024 { get; } + + ushort CadenceEventCount { get; } + + ushort CadenceTime1024 { get; } + } + + private class Update : CyclingSpeedCadenceMeasurement.IUpdate + { + private static readonly byte WheelRevolutionsFlagBit = (byte)0; + private static readonly byte CrankRevolutionsFlagBit = (byte)1; + private readonly byte[] _data; + + private byte Flags + { + get + { + return this._data[0]; + } + } + + public bool HasCadence + { + get + { + return PrimitiveExtensions.IsBitSet(this.Flags, (int)CyclingSpeedCadenceMeasurement.Update.CrankRevolutionsFlagBit); + } + } + + public bool HasSpeed + { + get + { + return PrimitiveExtensions.IsBitSet(this.Flags, (int)CyclingSpeedCadenceMeasurement.Update.WheelRevolutionsFlagBit); + } + } + + public uint SpeedEventCount + { + get + { + return BitConvertHelper.ToUInt32(this._data, 1); + } + } + + public ushort SpeedTime1024 + { + get + { + return BitConvertHelper.ToUInt16(this._data, 5); + } + } + + public ushort CadenceEventCount + { + get + { + return BitConvertHelper.ToUInt16(this._data, this.HasSpeed ? 7 : 1); + } + } + + public ushort CadenceTime1024 + { + get + { + return BitConvertHelper.ToUInt16(this._data, this.HasSpeed ? 9 : 3); + } + } + + public Update(byte[] data) + { + this._data = data; + } + } + + private class NullUpdate : CyclingSpeedCadenceMeasurement.IUpdate + { + public bool HasCadence + { + get + { + return false; + } + } + + public bool HasSpeed + { + get + { + return false; + } + } + + public uint SpeedEventCount + { + get + { + return 0U; + } + } + + public ushort SpeedTime1024 + { + get + { + return (ushort)0; + } + } + + public ushort CadenceEventCount + { + get + { + return (ushort)0; + } + } + + public ushort CadenceTime1024 + { + get + { + return (ushort)0; + } + } + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs.meta new file mode 100644 index 00000000..0f2c2161 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/CyclingSpeedCadenceMeasurement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ea4d73295288ad4a9f561a37246110a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs b/Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs new file mode 100644 index 00000000..a0417bb4 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs @@ -0,0 +1,45 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class FtmsFitnessMachineFeature : ICharacteristic + { + public Guid Uuid => ServiceUuids.Characteristics.FitnessMachineFeature; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.Ftms).IdGuid; + + public bool IsOptional => false; + + public bool IsInclinationSupported + { + get; private set; + } + + public bool IsInclinationTargetSettingSupported + { + get; private set; + } + + public void HandleAttributeReceived(byte[] data) + { + //throw new NotImplementedException(); + if (data.Length != 8) + return; + this.IsInclinationSupported = ByteExtensions.IsFlagSet(data[0], (byte)8); + //this.ResistanceLevelSupported = ByteExtensions.IsFlagSet(data[0], sbyte.MinValue); + + this.IsInclinationTargetSettingSupported = ByteExtensions.IsFlagSet(data[4], 2); + } + + public void SetUnavailable() + { + //throw new NotImplementedException(); + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs.meta new file mode 100644 index 00000000..da4dfc4f --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/FtmsFitnessMachineFeature.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6095c96152da4c47870708061e1d5ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs b/Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs new file mode 100644 index 00000000..f9f2b0d8 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs @@ -0,0 +1,220 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class FtmsIndoorBikeData : ICharacteristic + { + private readonly ushort? _instantSpeedSubject = new ushort?(); + private readonly ushort? _averageSpeedSubject = new ushort?(); + private readonly ushort? _instantCadenceSubject = new ushort?(); + private readonly ushort? _averageCadenceSubject = (new ushort?()); + private readonly uint? _totalDistanceSubject = (new uint?()); + private readonly short? _resistanceLevelSubject = (new short?()); + private readonly short? _instantPowerSubject = (new short?()); + private readonly short? _averagePowerSubject = (new short?()); + private readonly ushort? _totalEnergySubject = (new ushort?()); + private readonly ushort? _energyPerHourSubject = (new ushort?()); + private readonly byte? _energyPerMinuteSubject = (new byte?()); + private readonly byte? _heartRateSubject = (new byte?()); + private readonly byte? _metabolicEquivalentSubject = (new byte?()); + private readonly ushort? _elapsedTimeSubject = (new ushort?()); + private readonly ushort? _remainingTimeSubject = (new ushort?()); + + public short? InstantPower + { + get + { + return this._instantPowerSubject; + } + } + public ushort? InstantCadence + { + get + { + return this._instantCadenceSubject; + } + } + public ushort? InstantSpeed + { + get + { + return this._instantSpeedSubject; + } + } + + + public Guid Uuid => ServiceUuids.Characteristics.IndoorBikeData; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.Ftms).IdGuid; + + public bool IsOptional => false; + + public void HandleAttributeReceived(byte[] data) + { + if(data.Length < 2) + { + return; + } + + List list = new List(); + if (!ByteExtensions.IsFlagSetAtPosition(data[0], (byte)0)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousSpeed); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)1)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.AverageSpeed); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)2)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousCadence); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)3)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.AverageCadence); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)4)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.TotalDistance); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)5)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.ResistanceLevel); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)6)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousPower); + if (ByteExtensions.IsFlagSetAtPosition(data[0], (byte)7)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.AveragePower); + if (ByteExtensions.IsFlagSetAtPosition(data[1], (byte)0)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.ExpendedEnergy); + if (ByteExtensions.IsFlagSetAtPosition(data[1], (byte)1)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.HeartRate); + if (ByteExtensions.IsFlagSetAtPosition(data[1], (byte)2)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.MetabolicEquivalent); + if (ByteExtensions.IsFlagSetAtPosition(data[1], (byte)3)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.ElapsedTime); + if (ByteExtensions.IsFlagSetAtPosition(data[1], (byte)4)) + list.Add(FtmsIndoorBikeData.IndoorBikeDataField.RemainingTime); + int offset = 2; + foreach (FtmsIndoorBikeData.IndoorBikeDataField field in list) + offset += this.ParseField(data, offset, field); + } + + public void SetUnavailable() + { + //throw new NotImplementedException(); + } + + private int ParseField(byte[] attribute, int offset, FtmsIndoorBikeData.IndoorBikeDataField field) + { + int fieldSize = FtmsIndoorBikeData.GetFieldSize(field); + if (attribute.Length < fieldSize + offset) + throw new ArgumentException("attribute"); + switch (field) + { + case FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousSpeed: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._instantSpeedSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.AverageSpeed: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._averageSpeedSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousCadence: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._instantCadenceSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.AverageCadence: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._averageCadenceSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.TotalDistance: + ICharacteristicExtensions.HandleUint24AttributeValue((ICharacteristic)this, attribute, offset, this._totalDistanceSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.ResistanceLevel: + ICharacteristicExtensions.HandleShortAttributeValue((ICharacteristic)this, attribute, offset, this._resistanceLevelSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousPower: + ICharacteristicExtensions.HandleShortAttributeValue((ICharacteristic)this, attribute, offset, this._instantPowerSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.AveragePower: + ICharacteristicExtensions.HandleShortAttributeValue((ICharacteristic)this, attribute, offset, this._averagePowerSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.ExpendedEnergy: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._totalEnergySubject); + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset + 2, this._energyPerHourSubject); + ICharacteristicExtensions.HandleByteAttributeValue((ICharacteristic)this, attribute, offset + 4, this._energyPerMinuteSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.HeartRate: + ICharacteristicExtensions.HandleByteAttributeValue((ICharacteristic)this, attribute, offset, this._heartRateSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.MetabolicEquivalent: + ICharacteristicExtensions.HandleByteAttributeValue((ICharacteristic)this, attribute, offset, this._metabolicEquivalentSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.ElapsedTime: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._elapsedTimeSubject); + break; + case FtmsIndoorBikeData.IndoorBikeDataField.RemainingTime: + ICharacteristicExtensions.HandleUshortAttributeValue((ICharacteristic)this, attribute, offset, this._remainingTimeSubject); + break; + } + return fieldSize; + } + + private static int GetFieldSize(FtmsIndoorBikeData.IndoorBikeDataField field) + { + int num = 0; + switch (field) + { + case FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousSpeed: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.AverageSpeed: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousCadence: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.AverageCadence: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.TotalDistance: + num = 3; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.ResistanceLevel: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.InstantaneousPower: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.AveragePower: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.ExpendedEnergy: + num = 5; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.HeartRate: + num = 1; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.MetabolicEquivalent: + num = 1; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.ElapsedTime: + num = 2; + break; + case FtmsIndoorBikeData.IndoorBikeDataField.RemainingTime: + num = 2; + break; + } + return num; + } + + + private enum IndoorBikeDataField + { + InstantaneousSpeed, + AverageSpeed, + InstantaneousCadence, + AverageCadence, + TotalDistance, + ResistanceLevel, + InstantaneousPower, + AveragePower, + ExpendedEnergy, + HeartRate, + MetabolicEquivalent, + ElapsedTime, + RemainingTime, + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs.meta new file mode 100644 index 00000000..13929831 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/FtmsIndoorBikeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3577d5802131984c9463ba280183935 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/HeartRateMeasurement.cs b/Assets/Scripts/Devices/Ble/Characteristic/HeartRateMeasurement.cs index 00985c01..aefdb328 100644 --- a/Assets/Scripts/Devices/Ble/Characteristic/HeartRateMeasurement.cs +++ b/Assets/Scripts/Devices/Ble/Characteristic/HeartRateMeasurement.cs @@ -1,4 +1,5 @@ using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; using System; using System.Collections.Generic; using System.Linq; @@ -8,7 +9,7 @@ using UnityEngine; namespace Assets.Scripts.Devices.Ble.Characteristic { - public class HeartRateMeasurement + public class HeartRateMeasurement : ICharacteristic { public Guid Uuid { @@ -26,6 +27,13 @@ namespace Assets.Scripts.Devices.Ble.Characteristic } } + public bool IsOptional + { + get + { + return false; + } + } private HrmFormat Format { get; set; } // Token: 0x170005EB RID: 1515 @@ -39,7 +47,7 @@ namespace Assets.Scripts.Devices.Ble.Characteristic if (this.Format == HrmFormat.Short) { this.BeatsPerMinute = BitConvertHelper.ToUInt8(data, 1); - Debug.Log("心率:" + this.BeatsPerMinute); + //Debug.Log("心率:" + this.BeatsPerMinute); return; } if (data.Length < 3) @@ -49,6 +57,10 @@ namespace Assets.Scripts.Devices.Ble.Characteristic this.BeatsPerMinute = BitConvertHelper.ToUInt16(data, 1); } + public void SetUnavailable() + { + } + [Flags] private enum HrmFlags { diff --git a/Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs b/Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs new file mode 100644 index 00000000..1f96eca3 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs @@ -0,0 +1,46 @@ +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public static class ICharacteristicExtensions + { + public static void HandleByteAttributeValue(this ICharacteristic characteristic, byte[] attribute, int offset, byte? subject) + { + if (attribute.Length < 1 + offset) + throw new ArgumentException("attribute"); + byte num = attribute[offset]; + subject =new byte?(num); + } + + public static void HandleShortAttributeValue(this ICharacteristic characteristic, byte[] attribute, int offset, short? subject) + { + if (attribute.Length < 2 + offset) + throw new ArgumentException("attribute"); + short num = BitConverter.ToInt16(attribute, offset); + subject= new short?(num); + } + + public static void HandleUshortAttributeValue(this ICharacteristic characteristic, byte[] attribute, int offset, ushort? subject) + { + if (attribute.Length < 2 + offset) + throw new ArgumentException("attribute"); + ushort num = BitConverter.ToUInt16(attribute, offset); + subject =new ushort?(num); + } + + public static void HandleUint24AttributeValue(this ICharacteristic characteristic, byte[] attribute, int offset, uint? subject) + { + if (attribute.Length < 4 + offset - 1) + throw new ArgumentException("attribute"); + byte[] numArray = new byte[4]; + Buffer.BlockCopy((Array)attribute, offset, (Array)numArray, 0, 3); + uint num = BitConverter.ToUInt32(numArray, 0); + subject = (new uint?(num)); + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs.meta new file mode 100644 index 00000000..00c122d5 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/ICharacteristicExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 238bc68320174c9429b5958b5207b2c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs b/Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs new file mode 100644 index 00000000..cd7aa54a --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs @@ -0,0 +1,31 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class ManufacturerNameCharacteristic : ICharacteristic + { + public Guid Uuid => ServiceUuids.Characteristics.ManufactureNameString; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.DeviceInformation).IdGuid; + + public bool IsOptional => true; + + public void HandleAttributeReceived(byte[] data) + { + //throw new NotImplementedException(); + Debug.Log(Encoding.UTF8.GetString(data)); + } + + public void SetUnavailable() + { + //throw new NotImplementedException(); + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs.meta new file mode 100644 index 00000000..04d9c4e0 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/ManufacturerNameCharacteristic.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f0a2af64ce8fe84cbdf0e5a14bbc8ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs b/Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs new file mode 100644 index 00000000..4f9080be --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs @@ -0,0 +1,31 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class ModelNumberCharacteristic : ICharacteristic + { + public Guid Uuid => ServiceUuids.Characteristics.ModelNumber; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.DeviceInformation).IdGuid; + + public bool IsOptional => true; + + public void HandleAttributeReceived(byte[] data) + { + //throw new NotImplementedException(); + Debug.Log($"model number:\t{ Encoding.UTF8.GetString(data) }"); + } + + public void SetUnavailable() + { + //throw new NotImplementedException(); + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs.meta new file mode 100644 index 00000000..5a6c87f0 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/ModelNumberCharacteristic.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef489f56607ba2540b8954b67e1a4d3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs new file mode 100644 index 00000000..40e5d446 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs @@ -0,0 +1,205 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class TacxFecNotify : ICharacteristic + { + public Guid Uuid => ServiceUuids.Characteristics.TacxFecRead; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.TacxBle).IdGuid; + + public virtual bool IsOptional + { + get + { + return false; + } + } + + public double Speed { get; set; } + + private int _Power; + public int Power + { + get + { + return _Power; + } + set + { + _Power = DeviceValueFilter.Power(value); + //Debug.Log(_Power); + } + } + + private int _Cadence; + public int Cadence + { + get + { + return _Cadence; + } + set + { + _Cadence = DeviceValueFilter.Cadence(value); + } + } + + public void HandleAttributeReceived(byte[] data) + { + //throw new NotImplementedException(); + var responseId = data[2]; + + if (responseId != (byte)ANT_Managed_Library.ANT_ReferenceLibrary.ANTMessageID.BROADCAST_DATA_0x4E) + { + return; + } + var data1 = data.Skip(3).ToArray(); + var pageNumber = data1[1]; + switch (pageNumber) + { + case 16: + { + HandleGeneralDataPage(data1.Skip(1).ToArray()); + } + break; + case 19: + { + //lastInstCadence = data1[5]; + } + break; + case 21: //bike data + case 22: //Row data, same format as bike for cad and power + case 20: //Elliptical, same format as bike for cad and power + case 24: //Nordic Skier, same format as bike for cad and power + { + //lastInstCadence = data1[5]; + //lastInstPower = (ushort)(data1[6] + (data1[7] << 8)); + //System.Console.Out.WriteLine("Cadence:" + response.messageContents[5] + ",Power:" + (response.messageContents[6] + (response.messageContents[7] << 8))); + //System.Console.Out.WriteLine("SPM:"+response.messageContents[5]+"Power:"+(response.messageContents[6] + (response.messageContents[7] << 8))); + } + break; + case 25: + + HandleTrainerDataPage(data1.Skip(1).ToArray()); + break; + default: + break; + } + } + + private void HandleGeneralDataPage(byte[] dataPayload) + { + try + { + byte equipmentType = dataPayload[1]; + byte elapsedTime = dataPayload[2]; + byte distanceTraveled = dataPayload[3]; + + //byte heartRate = dataPayload[6]; + //FecPageHandler.HeartRateDataSource heartRateDataSource = (FecPageHandler.HeartRateDataSource)(dataPayload[7] & 3); + //bool traveledEnabled = (dataPayload[7] & 4) == 1; + //bool distanceTraveledEnabled = (dataPayload[7] & 8) == 1; + //FecPageHandler.FeState feState = (FecPageHandler.FeState)(dataPayload[7] & 128); + //bool lapToggle = (dataPayload[7] & 128) == 1; + //this._fecGeneralData = new FecGeneralData(equipmentType, elapsedTime, distanceTraveled, (short)num, feState, lapToggle, heartRate, heartRateDataSource, traveledEnabled, distanceTraveledEnabled); + bool traveledEnabled = ((int)dataPayload[7] & 4) == 1; + bool distanceTraveledEnabled = ((int)dataPayload[7] & 8) == 1; + + int num1 = (int)dataPayload[4] | (int)dataPayload[5] << 8; // Instantaneous speed, m/s + Speed = (double)num1 / 1000.0 * 60.0 * 60.0 / 1000.0; //转换成公里/小时 + //Console.WriteLine(num1 +"-----"+Speed); + //fecDevice.UpdateSpeed(new double?(value)); + //PubCommData.Speed = num2; + + //byte? b2 = (heartRate == 255) ? null : new byte?(heartRate); + //int? xl = b2.HasValue ? new int?((int)b2.GetValueOrDefault()) : 0; + //Console.WriteLine("最新速度" + Speed); + //Console.WriteLine("最新心率" + xl); + + //string str = ""; + //for (int i = 0; i < response.messageContents.Length; i++) + //{ + // str += "," + response.messageContents[i]; + //} + //PubCommData.sp = "速度:" + str; + } + catch (Exception) + { + } + } + + private void HandleTrainerDataPage(byte[] dataPayload) + { + //if (response.messageContents.Length < 11) break; + //double value = 0; + //for (int i = 0; i < response.messageContents.Length; i++) + //{ + // if (i < 8) + // { + // value += response.messageContents[i]; + // } + //} + + //double value1 = response.messageContents[10] * 256 + response.messageContents[9]; + + + //double power = response.messageContents[7] * 256 + response.messageContents[6]; + //Console.WriteLine("power:" + power); + //PubCommData.power = power; + + try + { + if (dataPayload[5] > 0) + { + //Console.WriteLine(dataPayload[5] + "," + dataPayload[6]);//瞬时功率 + } + byte num1 = dataPayload[1]; + byte num2 = dataPayload[2]; + int accumulatedPower = (int)dataPayload[3] | (int)dataPayload[4] << 8; + int instantaneousPower = (int)dataPayload[5] | ((int)dataPayload[6] & 15) << 8; //Watts + bool bikePowerCalibrationRequired = ((int)dataPayload[6] >> 4 & 1) == 1; + bool resistanceCalibrationRequired = ((int)dataPayload[6] >> 5 & 1) == 1; + bool userConfigurationRequired = ((int)dataPayload[6] >> 6 & 1) == 1; + bool speedIsTooLow = ((int)dataPayload[7] & 1) == 1; + bool speedIsTooHigh = ((int)dataPayload[7] & 2) == 1; + + //PubCommData.power = instantaneousPower; + Power = instantaneousPower; + + //bool bikePowerCalibrationRequired = (response.messageContents[6] >> 4 & 1) == 1; + //bool resistanceCalibrationRequired = (response.messageContents[6] >> 5 & 1) == 1; + //bool flag = (response.messageContents[6] >> 6 & 1) == 1; + //bool speedIsTooLow = (response.messageContents[7] & 1) == 1; + //bool speedIsTooHigh = (response.messageContents[7] & 2) == 1; + //FecPageHandler.FeState feState = (FecPageHandler.FeState)(dataPayload[7] & 112); + //bool lapToggle = (dataPayload[7] & 128) == 1; + //this._fecTrainerData = new FecTrainerData((int)eventCount, (int)b, accumulatedPower, num, bikePowerCalibrationRequired, resistanceCalibrationRequired, flag, speedIsTooLow, speedIsTooHigh, feState, lapToggle); + //device.UpdatePower(new int?(num)); + byte? nullable = (int)num2 == (int)byte.MaxValue ? new byte?() : new byte?(num2); + int? cadence = nullable.HasValue ? new int?((int)nullable.GetValueOrDefault()) : new int?(); + this.Cadence = cadence.GetValueOrDefault(); + + //Console.WriteLine($"power:{ instantaneousPower },Cadence:{ cadence }, data:{ string.Join(",", dataPayload) }"); + + if (!userConfigurationRequired) + return; + //UpdateUserConfiguration(); + } + catch (Exception) + { + } + } + + + public virtual void SetUnavailable() + { + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs.meta new file mode 100644 index 00000000..da2a81db --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecNotify.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 640f21041d0bf6347b1e48669c9a8691 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs new file mode 100644 index 00000000..a27a2d56 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs @@ -0,0 +1,39 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ble.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Characteristic +{ + public class TacxFecWrite : ICharacteristic + { + public virtual bool IsOptional + { + get + { + return false; + } + } + + public Guid Uuid => ServiceUuids.Characteristics.TacxFecRead; + + public Guid ServiceUuid => ServiceUuids.Get(ServiceUuids.TacxBle).IdGuid; + + public void HandleAttributeReceived(byte[] data) + { + + } + + public void WriteMessageToControlPoint() + { + + } + + public virtual void SetUnavailable() + { + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs.meta b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs.meta new file mode 100644 index 00000000..d2f85994 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Characteristic/TacxFecWrite.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c9564a0124aa5946b25bdd1a97ade2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Devices/CyclingPower.cs b/Assets/Scripts/Devices/Ble/Devices/CyclingPower.cs index bbfafbd1..3be1eb54 100644 --- a/Assets/Scripts/Devices/Ble/Devices/CyclingPower.cs +++ b/Assets/Scripts/Devices/Ble/Devices/CyclingPower.cs @@ -1,5 +1,6 @@ using Assets.Scripts.Ble; using Assets.Scripts.Devices.Ant.Interfaces; +using Assets.Scripts.Devices.Ble.Characteristic; using System; using System.Collections.Generic; using System.Linq; @@ -12,12 +13,16 @@ namespace Assets.Scripts.Devices.Ble.Devices public class CyclingPower : BleDevice, IPowerDevice { private List Services; + private CyclingPowerMeasurement cyclingPowerMeasurement; public CyclingPower(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface) : base(peripheralInfo, bleWinHwInterface, Ant.SensorType.Power) { + + cyclingPowerMeasurement = new CyclingPowerMeasurement(1920); + base.Characteristics.Add(cyclingPowerMeasurement); bleWinHwInterface.CharacteristicReadEvent += CharacteristicReadMainCallback; } - public int Power { get => 999; set => throw new NotImplementedException(); } + public int Power { get => cyclingPowerMeasurement.Power; set => throw new NotImplementedException(); } protected override void CreateServices(List discoveredServices) { @@ -35,7 +40,7 @@ namespace Assets.Scripts.Devices.Ble.Devices this.hwInterface.SubscribeCharacteristic(character, (hw, cha, res) => { - Debug.Log("1111111111111111111111"); + //Debug.Log("1111111111111111111111"); }); } } @@ -46,7 +51,22 @@ namespace Assets.Scripts.Devices.Ble.Devices private void CharacteristicReadMainCallback(BleWinHwInterface hwInterface, BleCharacteristicInfo characteristic, BleResponse response) { - Debug.Log("main call" + string.Join(",", response.Data)); + //Debug.Log("power main call" + string.Join(",", response.Data)); + + //Debug.Log(characteristic.MatchGuid(cyclingPowerMeasurement.Uuid)); + //Debug.Log(characteristic.Service.MatchGuid(cyclingPowerMeasurement.ServiceUuid)); + //if (characteristic.MatchGuid(cyclingPowerMeasurement.Uuid)) + //{ + // cyclingPowerMeasurement.HandleAttributeReceived(response.Data); + + //} + foreach (var item in base.Characteristics) + { + if (characteristic.MatchGuid(item.Uuid)) + { + item.HandleAttributeReceived(response.Data); + } + } } } } diff --git a/Assets/Scripts/Devices/Ble/Devices/Ftms.cs b/Assets/Scripts/Devices/Ble/Devices/Ftms.cs index 141fbcfc..5cc33535 100644 --- a/Assets/Scripts/Devices/Ble/Devices/Ftms.cs +++ b/Assets/Scripts/Devices/Ble/Devices/Ftms.cs @@ -1,5 +1,6 @@ using Assets.Scripts.Ble; using Assets.Scripts.Devices.Ant.Interfaces; +using Assets.Scripts.Devices.Ble.Characteristic; using System; using System.Collections.Generic; using System.Linq; @@ -8,19 +9,119 @@ using System.Threading.Tasks; namespace Assets.Scripts.Devices.Ble.Devices { - public class Ftms : BleDevice, IPowerDevice + public class Ftms : BleDevice, ISpeedDevice, IPowerDevice, ICadenceDevice, ITrainerDevice { + public int Power { get => _ftmsIndoorBikeData.InstantPower.GetValueOrDefault(0); set => throw new NotImplementedException(); } + public double Speed { get => _ftmsIndoorBikeData.InstantSpeed.GetValueOrDefault(0); set => throw new NotImplementedException(); } + public int Cadence { get => _ftmsIndoorBikeData.InstantCadence.GetValueOrDefault(0); set => throw new NotImplementedException(); } + + private FtmsIndoorBikeData _ftmsIndoorBikeData; + + private List Services; + private BleCharacteristicInfo controlPointCharacteristic; + public Ftms(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface) :base(peripheralInfo, bleWinHwInterface, Ant.SensorType.Trainer) { + this._ftmsIndoorBikeData = new FtmsIndoorBikeData(); + base.Characteristics.Add(this._ftmsIndoorBikeData); + + base.Characteristics.Add(new FtmsFitnessMachineFeature()); + + bleWinHwInterface.CharacteristicReadEvent += CharacteristicReadMainCallback; } - public int Power { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - protected override void CreateServices(List discoveredServices) + { + this.Services = discoveredServices; + + foreach (var service in this.Services) + { + hwInterface.DiscoverCharacteristic(service, (hwInterface, service1, response) => + { + foreach (var character in response.Data) + { + if (character.MatchGuid(ServiceUuids.Characteristics.IndoorBikeData)) + { + //Debug.Log("功率功能"); + + this.hwInterface.SubscribeCharacteristic(character, (hw, cha, res) => + { + //Debug.Log("1111111111111111111111"); + }); + } + else if (character.MatchGuid(ServiceUuids.Characteristics.FitnessMachineControlPoint)) + { + this.controlPointCharacteristic = character; + } + } + }); + } + } + + private void CharacteristicReadMainCallback(BleWinHwInterface hwInterface, BleCharacteristicInfo characteristic, BleResponse response) + { + foreach (var item in base.Characteristics) + { + if (characteristic.MatchGuid(item.Uuid)) + { + //Debug.Log(string.Join(",", response.Data)); + item.HandleAttributeReceived(response.Data); + } + } + } + + public void SetErgMode(int targetPower) + { + //throw new NotImplementedException(); + byte[] bytes = BitConverter.GetBytes(targetPower); + this.hwInterface.WriteCharacteristic(this.controlPointCharacteristic, new byte[3] { + (byte)5, + bytes[0], + bytes[1] + }); + } + + public void SetResistanceMode(double value) + { + //throw new NotImplementedException(); + var data = new byte[] + { + (byte)4, + (byte)0.1 + }; + } + + public void SetWindResistance(double? height = null) { throw new NotImplementedException(); } + + /// + /// 轨道阻力 + /// + /// + public void SetTrackResistance(double grade) + { + if (this.State != Ant.DeviceState.Connected) + return; + if (controlPointCharacteristic == null) + return; + + short windSpeed = 0; + short value2 = (short)(grade); + byte rollingResistanceCoefficient = (byte)(0.004 * 10000); + byte windResistanceCoefficient = 0; + var data = new List();// { 17, (byte)windSpeed, (byte)value2, rollingResistanceCoefficient, windResistanceCoefficient }; + data.Add(17); + data.AddRange(BitConverter.GetBytes(windSpeed)); + data.AddRange(BitConverter.GetBytes(value2)); + data.AddRange(BitConverter.GetBytes(rollingResistanceCoefficient)); + data.AddRange(BitConverter.GetBytes(windResistanceCoefficient)); + + + this.hwInterface.WriteCharacteristic(this.controlPointCharacteristic, data.ToArray()); + } } } diff --git a/Assets/Scripts/Devices/Ble/Devices/HeartRate.cs b/Assets/Scripts/Devices/Ble/Devices/HeartRate.cs index 437e3798..3f1faf04 100644 --- a/Assets/Scripts/Devices/Ble/Devices/HeartRate.cs +++ b/Assets/Scripts/Devices/Ble/Devices/HeartRate.cs @@ -18,14 +18,13 @@ namespace Assets.Scripts.Devices.Ble.Devices private HeartRateMeasurement heartRateMeasurement; public HeartRate(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface) : base(peripheralInfo, bleWinHwInterface, Ant.SensorType.HeartRate) { - Debug.Log("创建心率设备"); heartRateMeasurement = new HeartRateMeasurement(); - + + base.Characteristics.Add(heartRateMeasurement); + bleWinHwInterface.CharacteristicReadEvent += CharacteristicReadMainCallback; } - - protected override void CreateServices(List discoveredServices) { //throw new NotImplementedException(); @@ -33,18 +32,33 @@ namespace Assets.Scripts.Devices.Ble.Devices foreach (var service in this.Services) { + //Debug.Log($"11111 "+ service.Id.ToString()); hwInterface.DiscoverCharacteristic(service, (hwInterface, service1, response) => { foreach (var character in response.Data) { if (character.MatchGuid(heartRateMeasurement.Uuid)) { - Debug.Log("心率功能"); - this.hwInterface.SubscribeCharacteristic(character, (hw, cha, res) => { - Debug.Log("1111111111111111111111"); + //Debug.Log($"心率计subscribe char:{ }"); }); + continue; + } + } + foreach (var item in Characteristics.Where(c=>c.IsOptional)) + { + //Debug.Log(item.GetType() + "服务可用"+ item.Uuid.ToString() +", service:" + item.ServiceUuid); + var ccc = response.Data.FirstOrDefault(r => r.MatchGuid(item.Uuid)); + if (ccc == null) + { + item.SetUnavailable(); + } + else + { + Debug.Log(item.GetType() + "服务可用"); + + GetBatteryLevel(ccc); } } }); @@ -52,10 +66,26 @@ namespace Assets.Scripts.Devices.Ble.Devices } + public void GetBatteryLevel(BleCharacteristicInfo bbbb) + { + this.hwInterface.ReadCharacteristic(bbbb, (hwInterface1, characteristic1, response1) => { + Debug.Log("read收到消息:" + string.Join(",", response1)); + }); + } + private void CharacteristicReadMainCallback(BleWinHwInterface hwInterface, BleCharacteristicInfo characteristic, BleResponse response) { - Debug.Log("main call" + string.Join(",", response.Data)); - heartRateMeasurement.HandleAttributeReceived(response.Data); + //Debug.Log("heart rate main call" + string.Join(",", response.Data)); + + foreach (var item in base.Characteristics) + { + if (characteristic.MatchGuid(item.Uuid)) + { + item.HandleAttributeReceived(response.Data); + } + } + + //heartRateMeasurement.HandleAttributeReceived(response.Data); } } diff --git a/Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs b/Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs new file mode 100644 index 00000000..61453909 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs @@ -0,0 +1,103 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ant.Interfaces; +using Assets.Scripts.Devices.Ble.Characteristic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Assets.Scripts.Devices.Ble.Devices +{ + public class SpeedCadence : BleDevice, ISpeedDevice, ICadenceDevice + { + private CyclingSpeedCadenceMeasurement _cyclingSpeedCadenceMeasurement; + public int Cadence { get => _cyclingSpeedCadenceMeasurement.Cadence; set => throw new NotImplementedException(); } + public double Speed { get => _cyclingSpeedCadenceMeasurement.Speed; set => throw new NotImplementedException(); } + + private double _wheelCircumference; + private List Services; + public SpeedCadence(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface) : base(peripheralInfo, bleWinHwInterface, Ant.SensorType.SpeedCadence) + { + Debug.Log("创建速度踏频设备"); + _cyclingSpeedCadenceMeasurement = new CyclingSpeedCadenceMeasurement(this._wheelCircumference); + base.Characteristics.Add(_cyclingSpeedCadenceMeasurement); + + bleWinHwInterface.CharacteristicReadEvent += CharacteristicReadMainCallback; + } + + public void SetWheelCircumference(double value) + { + + } + + protected override void CreateServices(List discoveredServices) + { + this.Services = discoveredServices; + + foreach (var service in this.Services) + { + //Debug.Log($"11111 "+ service.Id.ToString()); + hwInterface.DiscoverCharacteristic(service, (hwInterface, service1, response) => + { + Debug.Log($"设备{ this.Name }的char: { string.Join("\r\n", response.Data.Select(d=>d.Id)) }"); + foreach (var character in response.Data) + { + if (character.MatchGuid(_cyclingSpeedCadenceMeasurement.Uuid)) + { + this.hwInterface.SubscribeCharacteristic(character, (hw, cha, res) => + { + //Debug.Log("1111111111111111111111"); + }); + continue; + } + } + foreach (var item in Characteristics.Where(c => c.IsOptional)) + { + //Debug.Log(item.GetType() + "服务可用"+ item.Uuid.ToString() +", service:" + item.ServiceUuid); + var ccc = response.Data.FirstOrDefault(r => r.MatchGuid(item.Uuid)); + if (ccc == null) + { + item.SetUnavailable(); + } + else + { + Debug.Log(item.GetType() + "服务可用"); + + GetBatteryLevel(ccc); + } + } + }); + } + } + + public void GetBatteryLevel(BleCharacteristicInfo bbbb) + { + this.hwInterface.ReadCharacteristic(bbbb, (hwInterface1, characteristic1, response1) => { + Debug.Log("read收到消息:" + string.Join(",", response1)); + }); + } + + + private void CharacteristicReadMainCallback(BleWinHwInterface hwInterface, BleCharacteristicInfo characteristic, BleResponse response) + { + //Debug.Log("power main call" + string.Join(",", response.Data)); + + //Debug.Log(characteristic.MatchGuid(cyclingPowerMeasurement.Uuid)); + //Debug.Log(characteristic.Service.MatchGuid(cyclingPowerMeasurement.ServiceUuid)); + //if (characteristic.MatchGuid(cyclingPowerMeasurement.Uuid)) + //{ + // cyclingPowerMeasurement.HandleAttributeReceived(response.Data); + + //} + foreach (var item in base.Characteristics) + { + if (characteristic.MatchGuid(item.Uuid)) + { + item.HandleAttributeReceived(response.Data); + } + } + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs.meta b/Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs.meta new file mode 100644 index 00000000..9dce6618 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Devices/SpeedCadence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3fc83bb07e841d04bb491332cdfeb669 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Devices/Tacx.cs b/Assets/Scripts/Devices/Ble/Devices/Tacx.cs new file mode 100644 index 00000000..e995f866 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Devices/Tacx.cs @@ -0,0 +1,262 @@ +using Assets.Scripts.Ble; +using Assets.Scripts.Devices.Ant; +using Assets.Scripts.Devices.Ant.Interfaces; +using Assets.Scripts.Devices.Ble.Characteristic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Assets.Scripts.Devices.Ble.Devices +{ + public class Tacx : BleDevice, IPowerDevice, ICadenceDevice, ISpeedDevice, ITrainerDevice + { + public int Power { get => tacxFecNotify.Power; set => throw new NotImplementedException(); } + public int Cadence { get => tacxFecNotify.Cadence; set => throw new NotImplementedException(); } + public double Speed { get => tacxFecNotify.Speed; set => throw new NotImplementedException(); } + + private List Services; + + private TacxFecNotify tacxFecNotify; + private BleCharacteristicInfo tacxFecWriteCharacteristic; + + private TacxFecWrite tacxFecWrite; + + /// + /// 当前坡度,当切换到其他模式的时候,需要把坡度设置为0 + /// + private double _grade = 0; + + public Tacx(BlePeripheralInfo peripheralInfo, BleWinHwInterface bleWinHwInterface) : base(peripheralInfo, bleWinHwInterface, Ant.SensorType.Trainer) + { + tacxFecNotify = new TacxFecNotify(); + base.Characteristics.Add(tacxFecNotify); + + tacxFecWrite = new TacxFecWrite(); + base.Characteristics.Add(tacxFecWrite); + + bleWinHwInterface.CharacteristicReadEvent += CharacteristicReadMainCallback; + + //bleWinHwInterface.WriteCharacteristic() + } + + protected override void CreateServices(List discoveredServices) + { + this.Services = discoveredServices; + + foreach (var service in this.Services) + { + hwInterface.DiscoverCharacteristic(service, (hwInterface, service1, response) => + { + foreach (var character in response.Data) + { + if (character.MatchGuid(ServiceUuids.Characteristics.TacxFecRead)) + { + //Debug.Log("功率功能"); + + this.hwInterface.SubscribeCharacteristic(character, (hw, cha, res) => + { + //Debug.Log("1111111111111111111111"); + }); + } + else if (character.MatchGuid(ServiceUuids.Characteristics.TacxFecWrite)) + { + this.tacxFecWriteCharacteristic = character; + } + } + }); + } + } + + private void CharacteristicReadMainCallback(BleWinHwInterface hwInterface, BleCharacteristicInfo characteristic, BleResponse response) + { + foreach (var item in base.Characteristics) + { + if (characteristic.MatchGuid(item.Uuid)) + { + //Debug.Log(string.Join(",", response.Data)); + item.HandleAttributeReceived(response.Data); + } + } + } + + public void SetErgMode(int targetPower) + { + Debug.Log("目标功率1"); + if (this.State != Ant.DeviceState.Connected) + return; + if (tacxFecWriteCharacteristic == null) + return; + Debug.Log($"目标功率:{ targetPower }"); + List data = new List { 164, 9, 79, 5, 49, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + (byte)(targetPower & byte.MaxValue), + (byte)(targetPower >> 8 & byte.MaxValue) + }; + byte checksum = (byte)0; + data.ForEach(b => checksum ^= b); + data.Add(checksum); + + this.hwInterface.WriteCharacteristic(this.tacxFecWriteCharacteristic, data.ToArray()); + } + + /// + /// 阻力模式 + /// + /// + public void SetResistanceMode(double value) + { + if (this.State != Ant.DeviceState.Connected) + return; + if (tacxFecWriteCharacteristic == null) + return; + if(_grade > 0) + { + this.SetTrackResistance(0); + } + + double resistance = value / 100 * 200; + + List data = new List { 164, 9, 79, 5, 48, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + (byte)resistance + }; + byte checksum = (byte)0; + data.ForEach(b => checksum ^= b); + data.Add(checksum); + + this.hwInterface.WriteCharacteristic(this.tacxFecWriteCharacteristic, data.ToArray()); + } + + /// + /// 风阻 + /// + /// 海拔高度,单位米 + public void SetWindResistance(double? height = null) + { + if (this.State != Ant.DeviceState.Connected) + return; + if (tacxFecWriteCharacteristic == null) + return; + + if (_grade > 0) + { + this.SetTrackResistance(0); + } + + byte windResistance = 0; + + if (height.HasValue) + { + //Wind Resistance Coefficient [kg/m] = Frontal Surface Area [m2] x Drag Coefficient x Air Density[kg / m3] + var wr = 0.40 * 1.0 * AirDensity.GetAirDensity(height.Value); + if (wr > 1.86) + { + wr = 1.86; + } + windResistance = (byte)(Convert.ToInt32(Math.Round(wr, 2) / 0.01)); + + Console.WriteLine($"风阻系数:{ windResistance }"); + } + + byte id = 79; + byte size = 9; + byte channel = 5; + List data = new List { 164, size, id, channel, 50, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + windResistance, + byte.MaxValue, + byte.MaxValue, + }; + byte checksum = (byte)0; + data.ForEach(b => checksum ^= b); + data.Add(checksum); + + this.hwInterface.WriteCharacteristic(this.tacxFecWriteCharacteristic, data.ToArray()); + } + + /// + /// 轨道阻力 + /// + /// 坡度百分比的值,单位是% + public void SetTrackResistance(double grade) + { + if (this.State != Ant.DeviceState.Connected) + return; + if (tacxFecWriteCharacteristic == null) + return; + + _grade = grade; + if (_grade > 15) + { + _grade = 15; + } + else if (_grade < -5) + { + _grade = -5; + } + + var gradeValue = Convert.ToInt32((grade + 200) * 100); + var gradeBytes = BitConverter.GetBytes(gradeValue); + + byte id = 79; + byte size = 9; + byte channel = 5; + List data = new List { 164, size, id, channel, 51, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + byte.MaxValue, + gradeBytes[0], + gradeBytes[1], + byte.MaxValue, + }; + byte checksum = (byte)0; + data.ForEach(b => checksum ^= b); + data.Add(checksum); + + this.hwInterface.WriteCharacteristic(this.tacxFecWriteCharacteristic, data.ToArray()); + } + + private void UpdateUserConfiguration(double weight) + { + var _weight = (int)(weight * 100); + if (this.State != Ant.DeviceState.Connected) + return; + if (tacxFecWriteCharacteristic == null) + return; + + byte id = 79; + byte size = 4; + byte channel = 5; + List data = new List { 164, size, id, channel, 55, + (byte) (_weight & (int) byte.MaxValue), + (byte) (_weight >> 8 & (int) byte.MaxValue), + byte.MaxValue, + (byte) 143, + (byte) 12, + (byte) 70, + (byte) 0 + }; + byte checksum = (byte)0; + data.ForEach(b => checksum ^= b); + data.Add(checksum); + + this.hwInterface.WriteCharacteristic(this.tacxFecWriteCharacteristic, data.ToArray()); + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Devices/Tacx.cs.meta b/Assets/Scripts/Devices/Ble/Devices/Tacx.cs.meta new file mode 100644 index 00000000..5cbe2c59 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Devices/Tacx.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d133d5406fc475841a781b88d91dc139 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs b/Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs new file mode 100644 index 00000000..e2454026 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Extension +{ + public static class PrimitiveExtensions + { + public static bool IsBitSet(this byte value, int position) + { + return ((uint)value & (uint)(1 << position)) > 0U; + } + + public static bool IsBitSet(this ushort value, int position) + { + return ((uint)value & (uint)(1 << position)) > 0U; + } + } +} diff --git a/Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs.meta b/Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs.meta new file mode 100644 index 00000000..bdc5bb7d --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Extension/PrimitiveExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d49278e3c0c3a8a43bc1145abf67a5c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Interfaces.meta b/Assets/Scripts/Devices/Ble/Interfaces.meta new file mode 100644 index 00000000..b33d5ffc --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3f0cb374472e52419b47f6e6d582341 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs b/Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs new file mode 100644 index 00000000..99678344 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Interfaces +{ + public interface ICharacteristic + { + Guid Uuid { get; } + Guid ServiceUuid { get; } + + bool IsOptional { get; } + + void HandleAttributeReceived(byte[] data); + + void SetUnavailable(); + } +} diff --git a/Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs.meta b/Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs.meta new file mode 100644 index 00000000..305813bf --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Interfaces/ICharacteristic.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e333c9c3812a8654c87353be9e87e84e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Scan/BleAdvertisementInfo.cs b/Assets/Scripts/Devices/Ble/Scan/BleAdvertisementInfo.cs index b66dcdf9..b2436be2 100644 --- a/Assets/Scripts/Devices/Ble/Scan/BleAdvertisementInfo.cs +++ b/Assets/Scripts/Devices/Ble/Scan/BleAdvertisementInfo.cs @@ -16,7 +16,7 @@ namespace Assets.Scripts.Ble.Scan public BlePeripheralInfo Peripheral { get; } public int Rssi { get; } - public SensorType SensorType { get; } + public SensorType SensorType { get; internal set; } public BleAdvertisementInfo(BlePeripheralInfo peripheral, int rssi, bool connectible, List services, byte[] manufactureData, SensorType sensor) { this.Peripheral = peripheral; @@ -26,6 +26,8 @@ namespace Assets.Scripts.Ble.Scan //this.ManufactureData = manufactureData; this.SensorType = sensor; + + Index = 1; } private readonly List services; @@ -36,5 +38,14 @@ namespace Assets.Scripts.Ble.Scan return this.services; } } + + public void TryAddService(Guid service) + { + if (this.services.Contains(service)) + return; + this.services.Add(service); + } + + public int Index { get; set; } } } diff --git a/Assets/Scripts/Devices/Ble/Win/BleDeviceProxy.cs b/Assets/Scripts/Devices/Ble/Win/BleDeviceProxy.cs index 498a4540..d52e6bbe 100644 --- a/Assets/Scripts/Devices/Ble/Win/BleDeviceProxy.cs +++ b/Assets/Scripts/Devices/Ble/Win/BleDeviceProxy.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Assets.Scripts.Ble { /// - /// rouvy里的 WinBlePeripheralInfo类 + /// r里的 WinBlePeripheralInfo类 /// public class BleDeviceProxy { diff --git a/Assets/Scripts/Devices/Ble/Win/BleWinHwInterface.cs b/Assets/Scripts/Devices/Ble/Win/BleWinHwInterface.cs index e9a623e5..8f606167 100644 --- a/Assets/Scripts/Devices/Ble/Win/BleWinHwInterface.cs +++ b/Assets/Scripts/Devices/Ble/Win/BleWinHwInterface.cs @@ -2,6 +2,7 @@ using Assets.Scripts.Ble.Win; using Assets.Scripts.Devices.Ant; using Assets.Scripts.Devices.Ble; +using Assets.Scripts.Devices.Ble.Win; using System; using System.Collections.Generic; using System.Linq; @@ -96,53 +97,90 @@ namespace Assets.Scripts.Ble private void WatcherScanInfoReceived(WclBleMainThread sender, long address, string name, int rssi, CPPBridge.WclBleAdvertisementType packetType, Guid? service) { - //Debug.Log($"address:{ address }, name:{ name }, service:{ (service == null ? "" : service.Value.ToString()) }"); + //if(address != 224160707349234) + //{ + //return; + //} + if (service.HasValue) + { + //Debug.Log($"address:{ address }, name:{ name }, service:{ (service == null ? "" : service.Value.ToString()) }"); + } if (!string.IsNullOrWhiteSpace(name)) { if (pCache.ContainsKey(address.ToString())) { - ((pCache[address.ToString()].Peripheral) as WinBlePeripheralInfo).SetName(name); + (pCache[address.ToString()].Peripheral as WinBlePeripheralInfo).SetName(name); } } //Debug.Log("service:" + service.ToString()+",name:" + name); - if (service.HasValue && ServiceUuids.Services.Select(s => s.IdGuid).Any(x => x.Equals(service.Value))) + if (!service.HasValue || ServiceUuids.Services.Select(s => s.IdGuid).All(x => !x.Equals(service.Value))) + { + return; + } + SensorType sensor = SensorType.None; + List services = null; + if (service != null) { - SensorType sensor = SensorType.None; - List services = null; - if (service != null) - { - services = new List { service.Value }; - foreach(var item in ServiceUuids.Services) + services = new List { service.Value }; + foreach(var item in ServiceUuids.Services) + { + if(item.IdGuid != service.Value) { - if(item.IdGuid != service.Value) - { - continue; - } - if(item.IdByteArray == ServiceUuids.Ftms) - { - sensor = SensorType.Trainer; - } - else if(item.IdByteArray == ServiceUuids.HeartRate) - { - sensor = SensorType.HeartRate; - } - else if(item.IdByteArray == ServiceUuids.CyclingPower) - { - sensor = SensorType.Power; - } + continue; } - }; + if(item.IdByteArray == ServiceUuids.Ftms) + { + sensor = SensorType.Trainer; + } + else if(item.IdByteArray == ServiceUuids.HeartRate) + { + sensor = SensorType.HeartRate; + } + else if(item.IdByteArray == ServiceUuids.CyclingPower) + { + sensor = SensorType.Power; + //sensor = SensorType.Trainer; + } + else if(item.IdByteArray == ServiceUuids.CyclingSpeedCadence) + { + sensor = SensorType.SpeedCadence; + } + else if(item.IdByteArray == ServiceUuids.TacxBle) + { + sensor = SensorType.Trainer; + } + } + }; - if (!pCache.ContainsKey(address.ToString())) { - var device = new BleAdvertisementInfo(new WinBlePeripheralInfo(address.ToString(), name), rssi, true, services, null, sensor); - pCache.Add(address.ToString(), device); + if (!pCache.ContainsKey(address.ToString())) + { + var device = new BleAdvertisementInfo(new WinBlePeripheralInfo(address.ToString(), name), rssi, true, services, null, sensor); + pCache.Add(address.ToString(), device); - WclBleGattThread gattClient = this.SetUpGattClient(device.Peripheral); - //this.ConnectInternal(gattClient); + WclBleGattThread gattClient = this.SetUpGattClient(device.Peripheral); + //this.ConnectInternal(gattClient); + } + else + { + //Debug.Log(sensor); + //pCache[address.ToString()].SensorType = sensor; + foreach (var item in services) + { + pCache[address.ToString()].TryAddService(item); + } + pCache[address.ToString()].Index++; + if(pCache[address.ToString()].SensorType == SensorType.Power && pCache[address.ToString()].Services.Any(s=> s.Equals(ServiceUuids.Get(ServiceUuids.TacxBle).IdGuid))) + { + pCache[address.ToString()].SensorType = SensorType.Trainer; + //Debug.Log("纠正为trainer, "+ pCache[address.ToString()].Index); } - _discoveredCallback?.Invoke(pCache[address.ToString()]); + + if (pCache[address.ToString()].Index > 4 || pCache[address.ToString()].SensorType != SensorType.Power) + { + _discoveredCallback?.Invoke(pCache[address.ToString()]); + } } } @@ -179,8 +217,11 @@ namespace Assets.Scripts.Ble private void ConnectInternal(WclBleGattThread gattClient) { - int num = gattClient.Connect(); - Debug.Log("连接设备返回" + num); + //Task.Run(() => + //{ + int num = gattClient.Connect(); + Debug.Log("连接设备返回" + num); + //}); } internal void DisconnectPeripheral(BlePeripheralInfo peripheral, Action callback) @@ -323,7 +364,10 @@ namespace Assets.Scripts.Ble { return; } - this.characteristicsDiscoveredCallbacks.Add(service, callback); + if (!this.characteristicsDiscoveredCallbacks.ContainsKey(service)) + { + this.characteristicsDiscoveredCallbacks.Add(service, callback); + } int num = gattThread.DiscoverCharacteristics(service); if (WclBleErrors.IsSuccessCode(num)) { @@ -355,11 +399,29 @@ namespace Assets.Scripts.Ble this.GattCharacteristicSubscribed(gattThread, characteristic, response); } + public void WriteCharacteristic(BleCharacteristicInfo characteristic, byte[] data) + { + var gattThread = this.wclBleMainThread.GetGattThread(characteristic.Peripheral); + if(gattThread == null) + { + return; + } + + int num = gattThread.WriteCharacteristic(characteristic, data); + if (WclBleErrors.IsSuccessCode(num)) + { + Debug.Log("设置命令成功"); + return; + } + } + + public void Dispose() { this.wclBleMainThread.Dispose(); this.wclBleMainThread = null; hwInterface = null; + pCache.Clear(); } public void ReadCharacteristic(BleCharacteristicInfo characteristic, CharacteristicReadCallback callback) diff --git a/Assets/Scripts/Devices/Ble/Win/GattDisconnected.cs b/Assets/Scripts/Devices/Ble/Win/GattDisconnected.cs index 1d54c17a..3a473f73 100644 --- a/Assets/Scripts/Devices/Ble/Win/GattDisconnected.cs +++ b/Assets/Scripts/Devices/Ble/Win/GattDisconnected.cs @@ -1,4 +1,5 @@ using Assets.Scripts.Devices.Ble; +using Assets.Scripts.Devices.Ble.Win; using System; using System.Collections.Generic; using System.Linq; diff --git a/Assets/Scripts/Devices/Ble/Win/GattServicesDiscovered.cs b/Assets/Scripts/Devices/Ble/Win/GattServicesDiscovered.cs index c39ed4dd..f25cfbfb 100644 --- a/Assets/Scripts/Devices/Ble/Win/GattServicesDiscovered.cs +++ b/Assets/Scripts/Devices/Ble/Win/GattServicesDiscovered.cs @@ -1,4 +1,5 @@ using Assets.Scripts.Devices.Ble; +using Assets.Scripts.Devices.Ble.Win; using System; using System.Collections.Generic; using System.Linq; diff --git a/Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs b/Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs new file mode 100644 index 00000000..3b1b1664 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Assets.Scripts.Devices.Ble.Win +{ + internal class WclAlertableWait + { + [DllImport("WclBlePluginCPP.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "WCLWait")] + [return: MarshalAs(UnmanagedType.U4)] + public static extern uint Wait([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.SysInt, SizeParamIndex = 1)][In] IntPtr[] Handle, [MarshalAs(UnmanagedType.U4)][In] uint Cnt, [MarshalAs(UnmanagedType.U4)][In] uint Timeout); + + // Token: 0x0600207E RID: 8318 + [DllImport("WclBlePluginCPP.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "WCLFlushApc")] + [return: MarshalAs(UnmanagedType.U4)] + public static extern uint FlushApc(); + + // Token: 0x0600207F RID: 8319 + [DllImport("WclBlePluginCPP.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "WCLSetApcSync")] + public static extern void SetApcSync(); + + // Token: 0x04001189 RID: 4489 + public const uint WAIT_OBJECT_0 = 0U; + + // Token: 0x0400118A RID: 4490 + public const uint WAIT_IO_COMPLETION = 192U; + + // Token: 0x0400118B RID: 4491 + public const uint WAIT_TIMEOUT = 258U; + + // Token: 0x0400118C RID: 4492 + public const uint WAIT_FAILED = 4294967295U; + + // Token: 0x0400118D RID: 4493 + public const uint INFINITE = 4294967295U; + + // Token: 0x0400118E RID: 4494 + public const string GuidTemplate = "0000XXXX-0000-1000-8000-00805F9B34FB"; + } +} diff --git a/Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs.meta b/Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs.meta new file mode 100644 index 00000000..d0af7774 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Win/WclAlertableWait.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ec3942a1d98efc4eb36cd00001109b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/Ble/Win/WclBleGattThread.cs b/Assets/Scripts/Devices/Ble/Win/WclBleGattThread.cs index 869a7cb1..ffcb5561 100644 --- a/Assets/Scripts/Devices/Ble/Win/WclBleGattThread.cs +++ b/Assets/Scripts/Devices/Ble/Win/WclBleGattThread.cs @@ -1,15 +1,16 @@ -using Assets.Scripts.Ble.Win; +using Assets.Scripts.Ble; +using Assets.Scripts.Ble.Win; using Assets.Scripts.Ble.Win.CPPBridge; -using Assets.Scripts.Devices.Ble; -using Assets.Scripts.Devices.Ble.Win; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using UnityEngine; -namespace Assets.Scripts.Ble +namespace Assets.Scripts.Devices.Ble.Win { internal class WclBleGattThread { @@ -163,7 +164,12 @@ namespace Assets.Scripts.Ble private readonly long address; public BlePeripheralInfo Peripheral { get; } - + private Thread thread; + private ConcurrentQueue actions = new ConcurrentQueue(); + private bool start = false; + private ManualResetEvent sEvent; + private AutoResetEvent aEvent; + private ManualResetEvent tEvent; internal WclBleGattThread(BlePeripheralInfo bleDevice, IntPtr radio) { this.Peripheral = bleDevice; @@ -175,10 +181,84 @@ namespace Assets.Scripts.Ble public bool Start() { - this.SetUpWorkerThread(); + if (this.start) + { + return false; + } + this.start = true; + + this.sEvent = new ManualResetEvent(false); + this.tEvent = new ManualResetEvent(false); + this.aEvent = new AutoResetEvent(false); + + //thread = new Thread(new ThreadStart(ThreadProc)); + // this.sEvent.Set(); + + // IntPtr[] handle = new IntPtr[] + // { + // this.tEvent.SafeWaitHandle.DangerousGetHandle(), + // this.aEvent.SafeWaitHandle.DangerousGetHandle() + // }; + // while (WclAlertableWait.Wait(handle, 2U, WclAlertableWait.INFINITE) != WclAlertableWait.WAIT_OBJECT_0) + // { + // Action action; + // if (this.actions.TryDequeue(out action)) + // { + // if (action != null) + // { + // Debug.Log("连接设备"); + // action(); + // } + // } + // } + + // })); + //thread.Start(); + + //this.sEvent.WaitOne(); + ThreadProc(); return true; } + public void Stop() + { + Debug.Log("停止"); + start = false; + thread?.Abort(); + + } + + private void ThreadProc() + { + this.SetUpWorkerThread(); + //this.sEvent.Set(); + //this.DoWorkerLoop(); + + //this.CleanUpWorkerThread(); + //this.InternalCleanUp(); + } + + private void CleanUpWorkerThread() + { + this.gatt.Disconnect(); + this.gatt.Dispose(); + this.gatt = null; + } + private void InternalCleanUp() + { + this.sEvent.Close(); + this.sEvent.Dispose(); + this.sEvent = null; + + this.aEvent.Close(); + this.aEvent.Dispose(); + this.aEvent = null; + + this.tEvent.Close(); + this.tEvent.Dispose(); + this.tEvent = null; + } + private void SetUpWorkerThread() { this.gatt = new WclBleGattClient(this.address, this.rPtr) @@ -189,6 +269,28 @@ namespace Assets.Scripts.Ble }; } + private void DoWorkerLoop() + { + IntPtr[] handle = new IntPtr[] + { + this.tEvent.SafeWaitHandle.DangerousGetHandle(), + this.aEvent.SafeWaitHandle.DangerousGetHandle() + }; + while (WclAlertableWait.Wait(handle, 2U, 4294967295U) != 0U) + { + //this.InvokeQueuedActions(); + Action action; + if (this.actions.TryDequeue(out action)) + { + if (action != null) + { + Debug.Log("连接设备"); + action(); + } + } + } + } + private void OnCharacteristicChanged(WclBleGattClient connection, ushort handle, byte[] value) { if (!this.subscribedCharHandles.ContainsKey(handle)) { @@ -260,6 +362,10 @@ namespace Assets.Scripts.Ble public int Connect() { this.gatt.Connect(); + //this.actions.Enqueue(() => + //{ + // this.gatt.Connect(); + //}); return WclBleErrors.WCL_E_SUCCESS; } @@ -381,6 +487,21 @@ namespace Assets.Scripts.Ble this.gattCharacteristicsSubscribed(this, characteristic, response); } + public int WriteCharacteristic(BleCharacteristicInfo characteristicInfo, byte[] data) + { + if (!this.charMapping.ContainsKey(characteristicInfo)) + { + return WclBleErrors.WCL_E_CONNECTION_NOT_ACTIVE; + } + GattCharacteristic gCh = this.charMapping[characteristicInfo]; + + int result = this.gatt.WriteCharacteristic(gCh, data); + + + return WclBleErrors.WCL_E_SUCCESS; + + } + private class WinBleCharacteristicInfo : BleCharacteristicInfo { // Token: 0x06003F85 RID: 16261 RVA: 0x000EA274 File Offset: 0x000E8474 diff --git a/Assets/Scripts/Devices/Ble/Win/WclBleMainThread.cs b/Assets/Scripts/Devices/Ble/Win/WclBleMainThread.cs index 11f9535e..4eb35b05 100644 --- a/Assets/Scripts/Devices/Ble/Win/WclBleMainThread.cs +++ b/Assets/Scripts/Devices/Ble/Win/WclBleMainThread.cs @@ -1,4 +1,5 @@ -using Assets.Scripts.Ble.CPPBridge; +using Assets.Scripts.Ble; +using Assets.Scripts.Ble.CPPBridge; using Assets.Scripts.Devices.Ble; using System; using System.Collections.Generic; @@ -8,7 +9,7 @@ using System.Threading; using System.Threading.Tasks; using UnityEngine; -namespace Assets.Scripts.Ble +namespace Assets.Scripts.Devices.Ble.Win { internal class WclBleMainThread { @@ -99,6 +100,12 @@ namespace Assets.Scripts.Ble public void Dispose() { + Debug.Log("停止thread"); + foreach (var item in this.gattClients.Values) + { + item.Stop(); + } + wclBleManager.Dispose(); } diff --git a/Assets/Scripts/Devices/Ble/Win/WclBleThread.cs b/Assets/Scripts/Devices/Ble/Win/WclBleThread.cs new file mode 100644 index 00000000..08532eee --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Win/WclBleThread.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Threading; + +namespace Assets.Scripts.Devices.Ble.Win +{ + internal abstract class WclBleThread + { + // Token: 0x170005E2 RID: 1506 + // (get) Token: 0x06002114 RID: 8468 RVA: 0x000890FE File Offset: 0x000872FE + public bool IsRunning + { + get + { + return this.started || this.IsTerminating; + } + } + + // Token: 0x170005E3 RID: 1507 + // (get) Token: 0x06002115 RID: 8469 RVA: 0x00089112 File Offset: 0x00087312 + public bool IsTerminating + { + get + { + return this.terminating; + } + } + + // Token: 0x170005E4 RID: 1508 + // (get) Token: 0x06002116 RID: 8470 RVA: 0x0008911C File Offset: 0x0008731C + public bool CanLoadWork + { + get + { + return this.IsRunning && !this.IsTerminating; + } + } + + // Token: 0x06002117 RID: 8471 + protected abstract void SetUpWorkerThread(); + + // Token: 0x06002118 RID: 8472 + protected abstract void CleanUpWorkerThread(); + + // Token: 0x06002119 RID: 8473 RVA: 0x00089131 File Offset: 0x00087331 + protected void TreadProcInitialized() + { + this.sEvent.Set(); + } + + // Token: 0x0600211A RID: 8474 RVA: 0x0008913F File Offset: 0x0008733F + protected void ProcessPendingAPCMessages() + { + while (WclAlertableWait.FlushApc() != 0U) + { + } + } + + // Token: 0x0600211B RID: 8475 RVA: 0x00089148 File Offset: 0x00087348 + protected void InternalCleanUp() + { + this.sEvent.Close(); + this.sEvent.Dispose(); + this.sEvent = null; + this.aEvent.Close(); + this.aEvent.Dispose(); + this.aEvent = null; + this.tEvent.Close(); + this.tEvent.Dispose(); + this.tEvent = null; + this.terminating = false; + } + + // Token: 0x0600211C RID: 8476 RVA: 0x000891B5 File Offset: 0x000873B5 + private void ThreadProc() + { + this.SetUpWorkerThread(); + this.sEvent.Set(); + this.DoWorkerLoop(); + this.CleanUpWorkerThread(); + this.InternalCleanUp(); + } + + // Token: 0x0600211D RID: 8477 RVA: 0x000891DC File Offset: 0x000873DC + private void DoWorkerLoop() + { + IntPtr[] handle = new IntPtr[] + { + this.tEvent.SafeWaitHandle.DangerousGetHandle(), + this.aEvent.SafeWaitHandle.DangerousGetHandle() + }; + while (WclAlertableWait.Wait(handle, 2U, 4294967295U) != 0U && !this.terminating) + { + this.InvokeQueuedActions(); + if (this.terminating) + { + break; + } + } + } + + // Token: 0x0600211E RID: 8478 RVA: 0x0008923C File Offset: 0x0008743C + public virtual bool Start() + { + if (this.IsRunning) + { + return false; + } + this.sEvent = new ManualResetEvent(false); + this.tEvent = new ManualResetEvent(false); + this.aEvent = new AutoResetEvent(false); + this.wThread = new Thread(new ThreadStart(this.ThreadProc)); + this.wThread.Start(); + this.sEvent.WaitOne(); + this.started = true; + return true; + } + + // Token: 0x0600211F RID: 8479 RVA: 0x000892B0 File Offset: 0x000874B0 + public virtual bool Stop() + { + if (!this.started || this.IsTerminating) + { + return false; + } + this.terminating = true; + this.tEvent.Set(); + this.wThread = null; + this.ClearActionQueue(); + this.started = false; + return true; + } + + // Token: 0x06002120 RID: 8480 RVA: 0x000892FD File Offset: 0x000874FD + protected bool EnqueueAction(Action action) + { + if (this.CanLoadWork) + { + this.aQueue.Enqueue(action); + this.aEvent.Set(); + return true; + } + return false; + } + + // Token: 0x06002121 RID: 8481 RVA: 0x00089324 File Offset: 0x00087524 + protected void ClearActionQueue() + { + Action action; + while (this.aQueue.TryDequeue(out action)) + { + } + } + + // Token: 0x06002122 RID: 8482 RVA: 0x00089340 File Offset: 0x00087540 + protected void InvokeQueuedActions() + { + while (this.DequeueAdnInvokeAction()) + { + } + } + + // Token: 0x06002123 RID: 8483 RVA: 0x0008934C File Offset: 0x0008754C + protected bool DequeueAdnInvokeAction() + { + Action action = this.TryDequActione(); + if (action == null) + { + return false; + } + action(); + return true; + } + + // Token: 0x06002124 RID: 8484 RVA: 0x0008936C File Offset: 0x0008756C + private Action TryDequActione() + { + if (!this.CanLoadWork) + { + return null; + } + Action result; + if (!this.aQueue.TryDequeue(out result)) + { + return null; + } + return result; + } + + // Token: 0x04001341 RID: 4929 + protected ManualResetEvent tEvent; + + // Token: 0x04001342 RID: 4930 + protected AutoResetEvent aEvent; + + // Token: 0x04001343 RID: 4931 + private readonly ConcurrentQueue aQueue = new ConcurrentQueue(); + + // Token: 0x04001344 RID: 4932 + protected ManualResetEvent sEvent; + + // Token: 0x04001345 RID: 4933 + protected Thread wThread; + + // Token: 0x04001346 RID: 4934 + protected volatile bool terminating; + + // Token: 0x04001347 RID: 4935 + private volatile bool started; + } +} diff --git a/Assets/Scripts/Devices/Ble/Win/WclBleThread.cs.meta b/Assets/Scripts/Devices/Ble/Win/WclBleThread.cs.meta new file mode 100644 index 00000000..bcce7694 --- /dev/null +++ b/Assets/Scripts/Devices/Ble/Win/WclBleThread.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25f702c985f2a1b41990cc8fcdcb0450 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Devices/MainDeviceAdapter.cs b/Assets/Scripts/Devices/MainDeviceAdapter.cs index c1e93101..fc256c4f 100644 --- a/Assets/Scripts/Devices/MainDeviceAdapter.cs +++ b/Assets/Scripts/Devices/MainDeviceAdapter.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using UnityEngine; namespace Assets.Scripts.Devices { @@ -45,6 +46,7 @@ namespace Assets.Scripts.Devices var adapter = adapters.FirstOrDefault(a => a.Interface == connectionInterface); if(adapter != null) { + Debug.Log("bbbbbb " + (adapter.GetState().ToString())); return adapter.GetState(); } return DeviceAdapterState.Unavailable; diff --git a/Assets/Scripts/UI/Prefab/Device/DeviceItem.cs b/Assets/Scripts/UI/Prefab/Device/DeviceItem.cs index ccee8601..db9ee92f 100644 --- a/Assets/Scripts/UI/Prefab/Device/DeviceItem.cs +++ b/Assets/Scripts/UI/Prefab/Device/DeviceItem.cs @@ -10,6 +10,7 @@ public class DeviceItem : Selectable, IEventSystemHandler, IPointerClickHandler { private bool isOn; private Text mText; + private Text mType; public AbstractDevice DeviceInfo { get;set; @@ -18,6 +19,7 @@ public class DeviceItem : Selectable, IEventSystemHandler, IPointerClickHandler protected override void Awake() { mText = this.transform.Find("Name").GetComponent(); + mType = this.transform.Find("Type").GetComponent(); } // Start is called before the first frame update @@ -25,11 +27,17 @@ public class DeviceItem : Selectable, IEventSystemHandler, IPointerClickHandler { //this.currentSelectionState = SelectionState.Selected - mText.text = DeviceInfo.Name + "-" + DeviceInfo.DeviceNumber; + mText.text = DeviceInfo.Name;// + "-" + DeviceInfo.DeviceNumber; + if(DeviceInfo.Network == NetworkType.ANT) + { + mText.text += "-" + DeviceInfo.DeviceNumber; + } //if(DeviceInfo.State == DeviceState.Connected) { // this.SetSelectedStyle(); } + + mType.text = DeviceInfo.Network.ToString(); } diff --git a/Assets/Scripts/UI/Prefab/MainNav.cs b/Assets/Scripts/UI/Prefab/MainNav.cs index da54177f..6e4dd060 100644 --- a/Assets/Scripts/UI/Prefab/MainNav.cs +++ b/Assets/Scripts/UI/Prefab/MainNav.cs @@ -19,7 +19,7 @@ public class MainNav : MonoBehaviour var device = this.transform.Find("Device"); UIManager.AddEvent(device.gameObject, EventTriggerType.PointerClick, x => { - Debug.Log("click device"); + //Debug.Log("click device"); UIManager.ShowDevicePanel(); }); diff --git a/Assets/Scripts/UI/Prefab/Panel/DeviceController.cs b/Assets/Scripts/UI/Prefab/Panel/DeviceController.cs index 6ff744fc..c49235f8 100644 --- a/Assets/Scripts/UI/Prefab/Panel/DeviceController.cs +++ b/Assets/Scripts/UI/Prefab/Panel/DeviceController.cs @@ -8,6 +8,7 @@ using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using DG.Tweening; +using Assets.Scripts.Devices.Ble.Devices; public class DeviceController : PFUIPanel { @@ -21,6 +22,11 @@ public class DeviceController : PFUIPanel RectTransform circulo; private GameObject statusText; + private Image bleStatus; + private Sprite ble0; + private Sprite ble1; + RectTransform bleCirculo; + private bool _available = false; private bool Available { @@ -54,11 +60,38 @@ public class DeviceController : PFUIPanel } } + private bool _bleAvailable = false; + private bool BleAvailable + { + get + { + return _bleAvailable; + } + set + { + if (value == _bleAvailable) + return; + _bleAvailable = value; + if (_bleAvailable) + { + bleStatus.sprite = ble1; + DOTween.Kill(bleCirculo, false); + bleCirculo.GetComponent().sprite = circuloCompleto; + } + else + { + bleStatus.sprite = ble0; + bleCirculo.GetComponent().sprite = circuloCarga; + bleCirculo.DOLocalRotate(new Vector3(0, 0, 360), 1f, RotateMode.FastBeyond360).SetEase(Ease.Linear).SetLoops(-1); + } + } + } + protected override void Awake() { base.Awake(); - Debug.Log("device awake"); + //Debug.Log("device awake"); } // Start is called before the first frame update @@ -83,14 +116,13 @@ public class DeviceController : PFUIPanel // Debug.Log("aaaaaaaaaaaaaa"); //}); - var bg = this.transform.Find("Status").Find("Bg"); - antStatus = bg.Find("Ant+").GetComponent(); + var bg = this.transform.Find("Status").Find("Bg"); statusText = this.transform.Find("Status").Find("GameObject").gameObject; statusText.SetActive(false); - - circulo = bg.Find("Image").GetComponent(); + antStatus = bg.Find("AntIcon/Ant+").GetComponent(); + circulo = bg.Find("AntIcon/Image").GetComponent(); //circulo.DORotate(new Vector3(0, 0, 360), 1.5f, RotateMode.Fast).SetLoops(-1); circulo.DOLocalRotate(new Vector3(0, 0, 360), 1f, RotateMode.FastBeyond360).SetEase(Ease.Linear).SetLoops(-1); @@ -99,7 +131,42 @@ public class DeviceController : PFUIPanel circuloCarga = Resources.Load("Images/icon-circulo-carga"); circuloCompleto = Resources.Load("Images/icon-circulo-completo"); + + bleStatus = bg.Find("BleIcon/Ble").GetComponent(); + bleCirculo = bg.Find("BleIcon/Image").GetComponent(); + bleCirculo.DOLocalRotate(new Vector3(0, 0, 360), 1f, RotateMode.FastBeyond360).SetEase(Ease.Linear).SetLoops(-1); + ble0 = Resources.Load("Images/Bluetooth_0"); + ble1 = Resources.Load("Images/Bluetooth_2"); + base.SetRounded(bg, 64); + + + //var text = this.transform.Find("InputField").GetComponent(); + + //var btn = this.transform.Find("Button").GetComponent