加ant+相关代码。

This commit is contained in:
suntao committed 2021-03-30 14:23:41 +08:00
1 parent f45711f39f
commit 3c2bb10f07
115 files changed
+9058 -405

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4dc6124d7b036b14d9d24076df7dbc5c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public class ANTMessage
{
public byte Id { get; set; }
public byte Size { get; set; }
public byte[] Data { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cf593b457ef83e4ba833481a93184c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,252 @@
using ANT_Managed_Library;
using Assets.Scripts.Devices.Ant.Messages;
using Assets.Scripts.Devices.Ant.Pages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public abstract class AbstractAntDevice : DataSourceBase
{
public string Name
{
get;
protected set;
}
public AntChannelProfile searchProfile;
public bool isInitialized = false;
private DeviceState state;
public DeviceState State
{
get
{
return state;
}
set
{
state = value;
if (state == DeviceState.Connected)
{
this.GetManufacturingInformation();
}
StateChange?.Invoke(state);
}
}
public SensorType Sensor { get; private set; }
private int _ManufacturerId;
/// <summary>
/// 设备生产商
/// </summary>
public int ManufacturerId
{
get
{
return _ManufacturerId;
}
set
{
_ManufacturerId = value;
}
}
private ushort _DeviceNumber;
public ushort DeviceNumber
{
get
{
return _DeviceNumber;
}
set
{
this._DeviceNumber = value;
searchProfile.deviceNumber = value;
this.InitManufacturer();
}
}
public byte DeviceType
{
get
{
return getDefaultSearchProfile().deviceType;
}
}
public int? AntModelId { get; private set; }
public Action<DeviceState> StateChange = null;
public class AntChannelProfile
{
public byte rfOffset;
public byte transType;
public byte deviceType;
public ushort deviceNumber;
public ushort messagePeriod;
public bool pairingEnabled;
}
//private readonly Rhino.PowerFun.Services.DeviceService deviceService;
//private readonly DeviceDetailService _deviceDetailService;
//private readonly AntManufacturerService antManufacturerService;
protected List<IPageHandler> pageHandlers = new List<IPageHandler>();
public AbstractAntDevice(String defaultSourceName, racerSportType sportType, SensorType sensor)
: base(sportType, true)
{
State = DeviceState.Disconnected;
this.Name = defaultSourceName;
this.searchProfile = getDefaultSearchProfile();
this.Sensor = sensor;
//deviceService = new DeviceService();
// _deviceDetailService = new DeviceDetailService();
//antManufacturerService = new AntManufacturerService();
//this.InitManufacturer();
pageHandlers.Add(new ManufacturerDataPageHandler(md => {
this.ManufacturerId = md.ManufacturerId;
this.AntModelId = md.ModelNumber;
//var deviceId = $"{ this.searchProfile.deviceNumber }:{ this.searchProfile.deviceType }";
//var device = deviceService.Get(PubCommData.CurrentUser.Id, deviceId);
//if (device != null)
//{
// device.AntManufacturerId = _ManufacturerId;
// device.AntModelId = this.AntModelId;
// device.ReportedName = antManufacturerService.GetName(device.AntManufacturerId) + " " + Sensor.ToString();
// if (device.AntModelId.HasValue)
// {
// var deviceDetail = _deviceDetailService.Get(device.AntManufacturerId, device.AntModelId.Value);
// if (deviceDetail != null)
// {
// device.ReportedName = deviceDetail.Name;
// }
// }
// this.Name = device.ReportedName;
// deviceService.Update(device);
//}
}));
}
/// <summary>
/// 从数据库里还原厂商信息
/// </summary>
private void InitManufacturer()
{
//var deviceId = $"{ this.searchProfile.deviceNumber }:{ this.searchProfile.deviceType }";
//Device device = PubCommData.CurrentUser != null ? deviceService.Get(PubCommData.CurrentUser.Id, deviceId) : null;
//if (device != null && device.AntManufacturerId > 0)
//{
// this._ManufacturerId = device.AntManufacturerId;
// this.Name = device.ReportedName;
//}
}
protected abstract AntChannelProfile getDefaultSearchProfile();
public abstract void handleChannelResponse(ANT_Response response);
public override string getDefaultSourceName()
{
//return sourceName;
return String.Format("{0,-14}", Name); //HACK qc to try and even out some of the dynamic sizing issues
}
//public override void reset()
//{
// base.reset();
//}
public void Connect()
{
if (State == DeviceState.Connected || State == DeviceState.Connecting)
return;
AntConnector.Instance().ConnectDevice(this);
}
public void Disconnect(bool save = true)
{
if (State == DeviceState.Disconnected || State == DeviceState.Disconnecting)
return;
AntConnector.Instance().DisconnectDevice(this, save);
}
//public override bool Equals(Object obj)
//{
// //return base.Equals(obj);
// var target = obj as AbstractAntDevice;
// return this.searchProfile.deviceNumber == target.searchProfile.deviceNumber &&
// this.searchProfile.deviceType == target.searchProfile.deviceType;
//}
public bool GetManufacturingInformation()
{
var channel = AntConnector.Instance().usedChannels.SingleOrDefault(u => u.DeviceNumber == this.searchProfile.deviceNumber.ToString() && u.DeviceTypeId == this.searchProfile.deviceType.ToString());
if (channel == null) { return false; }
var channelId = channel.Index;
//byte[] Data = new byte[9]
//{
// channelId,
// (byte)70,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// (byte)4,
// (byte)80,
// (byte)1
//};
//var id = (byte)79;
var msg = new GetManufacturersInformation(channelId);
var result = SendMessage(msg);
return result;
}
public bool GetProductionInformation()
{
var channelId = AntConnector.Instance().usedChannels.SingleOrDefault(u => u.DeviceNumber == this.searchProfile.deviceNumber.ToString() && u.DeviceTypeId == this.searchProfile.deviceType.ToString()).Index;
//byte[] Data = new byte[9]
//{
// channelId,
// (byte)70,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// (byte)4,
// (byte)81,
// (byte)1
//};
//var id = (byte)79;
var msg = new GetProductionInformation(channelId);
var result = SendMessage(msg);
return result;
}
protected bool SendMessage(BaseMessage message)
{
return AntConnector.Instance().SendMessage(message.GetMessage());
}
protected byte GetChannelId()
{
return AntConnector.Instance().usedChannels.SingleOrDefault(u => u.DeviceNumber == this.searchProfile.deviceNumber.ToString() && u.DeviceTypeId == this.searchProfile.deviceType.ToString()).Index;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e3231e9ae6a9704b9ece49d9ad21a30
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+103
View File
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
internal static class AirDensity
{
static double pi = 3.141592653589793;
static double radPerDeg = 0;
static double ae = 6378140.0;
//static double alphae = 1 / 298.257;
static double be = 0;
static double phi0 = 0;
static double R0 = 0;
static double meanRadius = 6371004.0; //all use SI units
//static double semiMajorAxis = 6378140.0;
static double gravityConst = 3.98600445e14;
//static double J2 = 1.08263e-3;
static AirDensity()
{
radPerDeg = pi / 180.0;
be = 297.257 / 298.257 * ae;
phi0 = 29.8336 * radPerDeg;
R0 = ae * be / Math.Sqrt(ae * ae * Math.Sin(phi0) * Math.Sin(phi0) + be * be * Math.Cos(phi0) * Math.Cos(phi0));
}
/// <summary>
/// 计算空气密度
/// </summary>
/// <param name="geoHeight"></param>
/// <returns></returns>
public static double GetAirDensity(double geoHeight)
{
//tested
var H = geoHeightToPotentialH(geoHeight);
var z = H / (1 - H / 6356766);
var w = 0D;
if (z < 11019.1)
{
w = 1.0 - H / 44330.8;
return 1.225 * Math.Pow(w, 4.2559);
}
if (z < 20063.1)
{
w = Math.Exp((14964.7 - H) / 6341.6);
return 1.225 * .15898 * w;
}
if (z < 32161.9)
{
w = 1.0 + (H - 24902.1) / 221552;
return 1.225 * 3.2722e-2 * Math.Pow(w, -35.1629);
}
if (z < 47350.1)
{
w = 1.0 + (H - 39749.9) / 89410.7;
return 1.225 * 3.2618e-3 * Math.Pow(w, -13.2011);
}
if (z < 51412.5)
{
w = Math.Exp((48625.2 - H) / 7922.3);
return 1.225 * 9.4920e-4 * w;
}
if (z < 71802.0)
{
w = 1.0 - (H - 59439.0) / 88221.8;
return 1.225 * 2.5280e-4 * Math.Pow(w, 11.2011);
}
if (z < 86000.0)
{
w = 1.0 - (H - 78030.3) / 100295.0;
return 1.225 * 1.7632e-5 * Math.Pow(w, 16.0816);
}
if (z < 91000.0)
{
w = Math.Exp((87284.8 - H) / 5470.0);
return 1.225 * 3.6411e-6 * w;
}
return .0;//linearInter(90000.0,13000.0,3.416e-6,8.152e-9,z);
}
private static double getRadiusFromLatitude(double latitude)
{
var s = Math.Sin(latitude);
var c = Math.Cos(latitude);
return (ae * be / Math.Pow((ae * ae * s * s + be * be * c * c), .5));
}
private static double geoHeightToPotentialH(double h)
{
return gravityConst * h / meanRadius / (meanRadius + h) / 9.8066;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c99e2df823299b44fb62490c70712b1f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+523
View File
@@ -0,0 +1,523 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ANT_Managed_Library;
using UnityEngine;
namespace Assets.Scripts.Devices.Ant
{
public class AntConnector : IDisposable
{
private readonly byte[] ANTPLUS_NETWORK_KEY = new byte[]
{
//Insert the ANT+ network key here:
0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45
//Distribution of source code containing the ANT+ Network Key is prohibited.
//You may not add the ANT+ Network Key to this source code and republish it.
//The ANT+ Network Key is available to ANT+ Adopters.
//Please refer to http://thisisant.com to become an ANT+ Adopter and access the key.
};
private readonly static object _lock = new object();
private static ANT_Device _antDevice = null;
private ANT_Channel searchChannel;
private readonly List<ANT_Channel> _channels = new List<ANT_Channel>();
private int ChannelCount
{
get
{
//if (_antDevice == null) return 0;
//return _antDevice.getNumChannels();
return _channels.Count;
}
}
private bool IsBackgroundScanning { get; set; }
//int searchingDeviceIndex = -1;
private List<AbstractAntDevice> deviceList = new List<AbstractAntDevice>();
public bool IsAvailable = false;
/// <summary>
/// 设备号,频道编号
/// </summary>
public readonly List<UserdChannel> usedChannels = new List<UserdChannel>();
public readonly List<AbstractAntDevice> discoveredDevices = new List<AbstractAntDevice>();
private static AntConnector _antConnector;
public static AntConnector Instance()
{
if (_antConnector == null)
{
_antConnector = new AntConnector();
}
return _antConnector;
}
//private DeviceService deviceService = new DeviceService();
private AntConnector()
{
if (_antDevice == null)
{
//ANT_Common.enableDebugLogs();
//findUsableAntDevice();
}
//CheckStatus();
deviceList.Add(new FitDevice());
//deviceList.Add(new ds_AntPlus_Power());
//deviceList.Add(new ds_AntPlus_Cadence());
//deviceList.Add(new ds_AntPlus_HeartRate());
//deviceList.Add(new ds_AntPlus_BikeSpdCad());
var timer = new System.Timers.Timer(1000);
timer.AutoReset = true;
timer.Elapsed += Timer_Elapsed;
timer.Enabled = true;
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
CheckStatus();
}
public void CheckStatus()
{
if (_antDevice != null)
return;
//discoveredDevices.Add(new VirtualPowerDevice());
this.CreateAntDevice();
}
private void CreateAntDevice()
{
try
{
if ((int)ANT_Common.getNumDetectedUSBDevices() == 0)
return;
_antDevice = new ANT_Device();
if (_antDevice.getNumChannels() <= 4)
{
_antDevice = null;
return;
}
if (!_antDevice.setNetworkKey(0, ANTPLUS_NETWORK_KEY, 500))
throw new ApplicationException("Failed to set network key");
IsAvailable = true;
//_antDevice.configureAdvancedBurstSplitting(true);
_antDevice.deviceResponse += DeviceResponse;
_antDevice.serialError += new ANT_Device.dSerialErrorHandler(antDevice_serialError);
for (int i = 1; i < _antDevice.getNumChannels(); i++)
{
var channel = _antDevice.getChannel(i);
_channels.Add(channel);
channel.channelResponse += new dChannelResponseHandler(DeviceResponse);
}
if (IsAvailable)
{
usedChannels.Add(new UserdChannel
{
DeviceNumber = "scan",
DeviceTypeId = "",
Index = 0
});
StartNextSearch();
}
}
catch (Exception ex)
{
//ANT_Device.shutdownDeviceInstance(ref _antDevice); //Don't leave here with an invalid device ref
//throw new Exception("Could not connect to valid USB2: " + ex.Message); //forward the exception
//Log.ErrorLog(ex);
//Debug.LogError(ex);
//throw ex;
Log(ex.Message);
}
finally
{
}
}
private void Log(string str)
{
//System.IO.File.AppendAllText(@"D:\work\PowerFun\PowerFun-Unity\Build\log.txt", str+"\r\n");
Debug.LogError(str);
}
private void antDevice_serialError(ANT_Device sender, ANT_Device.serialErrorCode error, bool isCritical)
{
//throw new NotImplementedException();
Log("出错了");
if (error != ANT_Device.serialErrorCode.DeviceConnectionLost)
return;
if (_antDevice != null)
{
_antDevice.deviceResponse -= DeviceResponse;
_antDevice.serialError -= antDevice_serialError;
}
_antDevice = null;
IsAvailable = false;
foreach (var item in _channels)
{
item.channelResponse -= DeviceResponse;
}
searchChannel.channelResponse -= antChannel_channelResponse_FeSearch;
searchChannel = null;
discoveredDevices.Clear();
usedChannels.Clear();
IsBackgroundScanning = false;
}
void StartNextSearch()
{
//Console.WriteLine("startNextSearch");
if (searchChannel != null)
{
//searchChannel.Dispose();
}
//Get new search channel if neccesary
if (searchChannel == null)
{
//if (usedChannels.Count >= ChannelCount)
// return; //no free channels
//Find the first free channel and start the search
//for (int i = 0; i < ChannelCount; ++i)
//{
// if (!usedChannels.Values.Contains((byte)i))
// {
// searchChannel = _antDevice.getChannel(i);
// searchChannel.channelResponse += new dChannelResponseHandler(antChannel_channelResponse_FeSearch);
// break;
// }
//}
searchChannel = _antDevice.getChannel(0); // _antDevice.getChannel(usedChannels["scan"]);
searchChannel.channelResponse += new dChannelResponseHandler(antChannel_channelResponse_FeSearch);
}
IsBackgroundScanning = true;
ReSearch();
}
void DeviceResponse(ANT_Response response)
{
//Debug.Log(response.responseID);
var channelIndex = response.messageContents[0];
var antMessageId = (ANT_ReferenceLibrary.ANTMessageID)response.responseID;
if (antMessageId == ANT_Managed_Library.ANT_ReferenceLibrary.ANTMessageID.RESPONSE_EVENT_0x40)
{
//Trace.WriteLine(string.Join(",", response.messageContents));
var antEventId = (ANT_ReferenceLibrary.ANTEventID)response.messageContents[2];
//Trace.WriteLine(antEventId.ToString() +", " + string.Join(",",response.messageContents));
if (antEventId == ANT_ReferenceLibrary.ANTEventID.EVENT_CHANNEL_CLOSED_0x07)
{
}
//设备断开连接
else if (antEventId == ANT_ReferenceLibrary.ANTEventID.EVENT_RX_FAIL_GO_TO_SEARCH_0x08)
{
Debug.Log(antEventId.ToString() + ", " + string.Join(",", response.messageContents));
var cc1 = usedChannels.Where(d => d.Index == channelIndex).FirstOrDefault();
var dd1 = discoveredDevices.SingleOrDefault(d => d.searchProfile.deviceNumber.ToString() == cc1.DeviceNumber && d.searchProfile.deviceType.ToString() == cc1.DeviceTypeId);
if (dd1 != null)
{
dd1.Disconnect(false);
return;
}
}
}
else
{
//Trace.WriteLine(antMessageId + ", " + string.Join(",", response.messageContents));//EVENT_RX_FAIL_GO_TO_SEARCH_0x08
}
//if (antMessageId == ANT_ReferenceLibrary.ANTMessageID.BURST_DATA_0x50)
if (response.messageContents.Length == 3) return;
var str = string.Join(",", response.messageContents);
var cc = usedChannels.Where(d => d.Index == channelIndex).FirstOrDefault();
var dd = discoveredDevices.SingleOrDefault(d => d.searchProfile.deviceNumber.ToString() == cc.DeviceNumber && d.searchProfile.deviceType.ToString() == cc.DeviceTypeId);
if (dd != null)
{
if (dd.State == DeviceState.Connecting)
{
dd.State = DeviceState.Connected;
//IsBackgroundScanning = true;
//ReSearch();
//StartNextSearch();
return;
}
dd.handleChannelResponse(response);
}
}
public bool SendMessage(byte msgID, byte[] msgData)
{
return _antDevice.writeRawMessageToDevice(msgID, msgData);
}
public bool SendMessage(ANTMessage data)
{
return SendMessage(data.Id, data.Data);
}
void antChannel_channelResponse_FeSearch(ANT_Response response)
{
if (IsBackgroundScanning == false)
{
searchChannel.closeChannel();
return;
}
if (response.messageContents.Length == 3) return;
var message = response.messageContents;
if (response.messageContents.Length >= 14)
{
message = response.messageContents.Skip(10).Take(4).ToArray();
var deviceNumber = message[0] + (message[1] << 8);
var deviceTypeId = message[2] & 127;
var transmissionTypeId = message[3];
//Console.WriteLine($" {deviceNumber}, {deviceTypeId}");
var device2 = discoveredDevices.FirstOrDefault(d => d.searchProfile.deviceNumber == deviceNumber && d.searchProfile.deviceType == deviceTypeId);
if (device2 != null)
{
//ReSearch();
if (device2.State == DeviceState.Connected)
{
device2.handleChannelResponse(response);
return;
}
else if (device2.State == DeviceState.Disconnected)
{
//var deviceInfo = deviceService.Get(PubCommData.CurrentUser.Id, $"{ device2.searchProfile.deviceNumber }:{ device2.searchProfile.deviceType }");
//if (deviceInfo != null && deviceInfo.Paired)
//{
// device2.Connect();
//}
}
return;
}
var device1 = deviceList.SingleOrDefault(d => d.searchProfile.deviceType == deviceTypeId);
if (device1 != null)
{
AbstractAntDevice device = null;
switch (device1.Sensor)
{
case SensorType.None:
break;
case SensorType.Cadence:
//device = new ds_AntPlus_Cadence();
break;
case SensorType.HeartRate:
//device = new ds_AntPlus_HeartRate();
break;
case SensorType.Power:
//device = new ds_AntPlus_Power();
break;
case SensorType.Speed:
break;
case SensorType.SpeedCadence:
//device = new ds_AntPlus_BikeSpdCad();
break;
case SensorType.Trainer:
device = new FitDevice();
break;
case SensorType.VirtualPower:
break;
default:
break;
}
if (device != null)
{
//device.searchProfile.deviceNumber = (ushort)deviceNumber;
device.DeviceNumber = (ushort)deviceNumber;
discoveredDevices.Add(device);
}
//ReSearch();
return;
}
}
}
private void ReSearch()
{
if (IsBackgroundScanning == false) return;
//Console.WriteLine($"research,{ DateTime.Now }");
if (searchChannel != null)
{
//searchChannel.closeChannel(500);
//searchChannel.unassignChannel();
//searchChannel.openChannel();
//return;
}
var channelIndex = (byte)0; //searchChannel.getChannelNum();
//Handle setting the search timeout
byte timeout = 4; //default 4*2.5=10 seconds for each device
//if (deviceList.Count - usedChannels.Count == 1)
timeout = 255; //search forever if we only have one device to find; If one of the other devices resets it will startNextSearch again so we won't get stuck
searchChannel.assignChannelExt(ANT_ReferenceLibrary.ChannelType.ADV_TxRx_Only_or_RxAlwaysWildCard_0x40, 0, ANT_ReferenceLibrary.ChannelTypeExtended.ADV_AlwaysSearch_0x01);
//SendMessage((int)ANT_ReferenceLibrary.ANTMessageID.ASSIGN_CHANNEL_0x42, new byte[] {
// channelIndex,
// (int)ANT_ReferenceLibrary.ChannelType.ADV_TxRx_Only_or_RxAlwaysWildCard_0x40,
// 0
// });
searchChannel.setChannelID(0, false, 0, 0);
searchChannel.setChannelFreq(57);
SendMessage(102, new byte[] { channelIndex, Convert.ToByte(true) });
searchChannel.setLowPrioritySearchTimeout(timeout);
searchChannel.setChannelSearchTimeout(0);
searchChannel.openChannel(500);
}
public void ConnectDevice(AbstractAntDevice device)
{
if (usedChannels.Any(u => u.DeviceNumber == device.searchProfile.deviceNumber.ToString() &&
u.DeviceTypeId == device.searchProfile.deviceType.ToString()))
return;
if (device.Sensor == SensorType.Trainer || device.Sensor == SensorType.Power)
{
if (discoveredDevices.Any(s => s.Sensor == device.Sensor && (s.State == DeviceState.Connected || s.State == DeviceState.Connecting)))
{
return;
}
}
if (device.Sensor == SensorType.Cadence)
{
if (discoveredDevices.Any(s => s.Sensor == device.Sensor && (s.State == DeviceState.Connected || s.State == DeviceState.Connecting)))
{
return;
}
}
if (device.Sensor == SensorType.HeartRate)
{
if (discoveredDevices.Any(s => s.Sensor == device.Sensor && (s.State == DeviceState.Connected || s.State == DeviceState.Connecting)))
{
return;
}
}
for (int i = 0; i < ChannelCount; i++)
{
if (!usedChannels.Any(u => u.Index == (byte)i))
{
usedChannels.Add(new UserdChannel
{
DeviceNumber = device.searchProfile.deviceNumber.ToString(),
DeviceTypeId = device.searchProfile.deviceType.ToString(),
Index = (byte)i
});
break;
}
}
//var deviceService = new DeviceService();
//var info = deviceService.Get(PubCommData.CurrentUser.Id, device.searchProfile.deviceNumber.ToString() + ":" + device.searchProfile.deviceType);
//if (info != null && info.Paired == false)
//{
// info.Paired = true;
// deviceService.Update(info);
//}
#region 配对
device.State = DeviceState.Connecting;
//var r = searchChannel.closeChannel(500);
//if (!r)
//{
// throw new Exception("关闭搜索频道出错");
//}
//CloseChannel(searchChannel);
//IsBackgroundScanning = false;
var channelIndex = usedChannels.Single(c => c.DeviceNumber == device.searchProfile.deviceNumber.ToString() && c.DeviceTypeId == device.searchProfile.deviceType.ToString()).Index;
var channel = _antDevice.getChannel(channelIndex);
//channel.channelResponse += DeviceResponse;
channel.assignChannel(ANT_ReferenceLibrary.ChannelType.BASE_Slave_Receive_0x00, 0);
channel.setChannelID(device.searchProfile.deviceNumber, false, (byte)device.searchProfile.deviceType, device.searchProfile.transType);
channel.setChannelPeriod(device.searchProfile.messagePeriod);
channel.setChannelFreq(device.searchProfile.rfOffset);
channel.openChannel();
//if (!r1)
//{
// throw new Exception($"打开配对频道出错{ channelIndex }");
//}
//Console.WriteLine($" 连接设备{ device.searchProfile.deviceNumber }, 频道{ channelIndex },{ DateTime.Now }");
//Task.Delay(2000).ContinueWith((t) =>
//{
// IsBackgroundScanning = true;
// ReSearch();
//});
#endregion
}
public void DisconnectDevice(AbstractAntDevice device, bool save)
{
var deviceNumber = device.searchProfile.deviceNumber.ToString();
var deviceType = device.searchProfile.deviceType.ToString();
var item = usedChannels.SingleOrDefault(u => u.DeviceNumber == deviceNumber && u.DeviceTypeId == deviceType);
if (item == null)
return;
device.State = DeviceState.Disconnecting;
var channelIndex = item.Index;
var channel = _antDevice.getChannel(channelIndex);
CloseChannel(channel);
usedChannels.Remove(item);
device.State = DeviceState.Disconnected;
if (save)
{
//var deviceService = new DeviceService();
//var info = deviceService.Get(PubCommData.CurrentUser.Id, device.searchProfile.deviceNumber.ToString() + ":" + device.searchProfile.deviceType);
//if (info != null && info.Paired)
//{
// info.Paired = false;
// deviceService.Update(info);
//}
}
}
public void Dispose()
{
discoveredDevices.Clear();
deviceList.Clear();
_channels.Clear();
if (_antDevice != null)
{
_antDevice.Dispose();
}
}
public void CloseChannel(ANT_Channel channel)
{
channel.closeChannel(500);
channel.unassignChannel(500);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9367d67beb5a1e49b5ecfb438961331
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public abstract class DataSourceBase
{
//double distanceTravelled = 0;
//DataSourcePacket lastPacketRcvd = null;
protected racerSportType sportType;
//const double defaultRunStrideLength = 2.5;
//const double defaultRowStrideLength = 5;
//const double defaultBikeStrideLength = 8;
//const double defaultSkiStrideLength = 4.5;
public string customSourceName;
public bool isInUse = false;
public bool isHuman;
static byte uidCount = 0;
public readonly byte uid;
public DataSourceBase(racerSportType sportType, bool isHuman)
{
this.sportType = sportType;
this.isHuman = isHuman;
uid = ++uidCount;
if (uidCount == byte.MaxValue)
uidCount = byte.MaxValue - 1; //If we overflow just stay at max instead of colliding with earlier values
}
Action<DataSourcePacket> distanceUpdate;
//bool running = false;
public racerSportType getSportType()
{
return sportType;
}
//public double getCurrentDistance()
//{
// return distanceTravelled;
//}
//public void incrementDistanceAndUpdate(double distToAdd, double speedMs = 0xFFFF, double cadence = -1, byte heartRate = 0xFF, ushort powerW = 0xFFFF)
//{
// if (running)
// {
// if (cadence == -1)
// cadence = calculateCadence(speedMs);
// distanceTravelled += distToAdd;
// lastPacketRcvd = new DataSourcePacket(distanceTravelled, speedMs, cadence, heartRate, powerW);
// distanceUpdate(lastPacketRcvd);
// }
// else //Just save the last packet for monitoring purposes
// {
// if(lastPacketRcvd == null)
// lastPacketRcvd = new DataSourcePacket(distToAdd, speedMs, cadence, heartRate, powerW);
// else
// lastPacketRcvd = new DataSourcePacket(lastPacketRcvd.distance + distToAdd, speedMs, cadence, heartRate, powerW);
// }
//}
//public virtual void reset()
//{
// stop();
// distanceTravelled = 0;
// lastPacketRcvd = null;
//}
//public virtual void start(Action<DataSourcePacket> distanceUpdateHandler)
//{
// distanceUpdate = distanceUpdateHandler;
// running = true;
//}
//public virtual void stop()
//{
// running = false;
// distanceUpdate = null;
//}
//public bool isRunning()
//{
// return running;
//}
public abstract string getDefaultSourceName();
public string getSourceName()
{
if (String.IsNullOrWhiteSpace(customSourceName))
return getDefaultSourceName();
else
return customSourceName;
}
//public DataSourcePacket getLastDataRcvd()
//{
// return lastPacketRcvd;
//}
//protected double calculateCadence(double speed_Ms)
//{
// switch (sportType)
// {
// case racerSportType.Running:
// return (speed_Ms * 60) / DataSourceBase.defaultRunStrideLength; //[m/min] / [m/str] = str/min
// case racerSportType.Rowing:
// return (speed_Ms * 60) / DataSourceBase.defaultRowStrideLength;
// case racerSportType.Biking:
// return (speed_Ms * 60) / DataSourceBase.defaultBikeStrideLength;
// case racerSportType.Skiing:
// return (speed_Ms * 60) / DataSourceBase.defaultSkiStrideLength;
// default:
// return 0;
// }
//}
}
public class DataSourcePacket
{
public readonly double distance; //m
public readonly double cadence; //rpm
public readonly double speed_ms; //m/s
public readonly byte heartRate; //bpm
public readonly ushort power; //W
public DataSourcePacket(double dist, double spdMs, double cad, byte hr, ushort pwr)
{
distance = dist;
cadence = cad;
speed_ms = spdMs;
heartRate = hr;
power = pwr;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d26362003c1b21f419a01b150b02f110
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public enum DeviceState
{
Connecting,
Connected,
Interrupted,
Disconnecting,
Disconnected,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da476d42626b4894c80f0fc0b46709ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+471
View File
@@ -0,0 +1,471 @@
using Assets.Scripts.Devices.Ant.Interfaces;
using Assets.Scripts.Devices.Ant.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Assets.Scripts.Devices.Ant
{
public class FitDevice : AbstractAntDevice, ISpeedDevice, IPowerDevice, ICadenceDevice, IRequiresRiderWeight
{
public const int MAX_NO_EVENT_STOP_COUNT = 12;
//byte lastElapsedTimeAcc;
//byte lastDistAcc;
//double calcSpeed = 0; //Averaged Speed over several msgs in case instSpeed is not available
int lastInstCadence = -1;
ushort lastInstPower = 0xFFFF;
/// <summary>
/// 当前坡度,当切换到其他模式的时候,需要把坡度设置为0
/// </summary>
private double _grade = 0;
private bool IsCalibrating { get; set; }
public FitDevice()
: base("Ant+ Trainer", racerSportType.Unknown, SensorType.Trainer)
{
this.StateChange = (state) =>
{
if (state == DeviceState.Connected)
{
Calibrate();
}
};
}
/// <summary>
/// 发送校准命令
/// </summary>
private void Calibrate()
{
if (this.IsCalibrating)
return;
this.IsCalibrating = true;
base.SendMessage(new FecCalibrate(GetChannelId()));
}
protected override AntChannelProfile getDefaultSearchProfile()
{
return new AntChannelProfile()
{
rfOffset = 57,
transType = 0,
deviceType = 17,
deviceNumber = 0,
messagePeriod = 8192,
pairingEnabled = false,
};
}
//private byte _previousEventCount;
// Token: 0x0400054F RID: 1359
//private ushort _previousPeriod;
// Token: 0x04000550 RID: 1360
//private ushort _previousAccumulatedTorque;
private DateTime _lastEventChange = DateTime.UtcNow;
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);
}
}
private double _riderWeight = 75.0;
public double RiderWeight
{
get => _riderWeight;
set
{
if (value <= 0) return;
this._riderWeight = value;
this.UpdateUserConfiguration();
}
}
public DateTime LastCalibration { get; protected set; }
public int? Offset { get; set; }
public override void handleChannelResponse(ANT_Managed_Library.ANT_Response response)
{
//Console.WriteLine(string.Join(",", response.messageContents));
//Log.LogInfo(string.Join(",", response.messageContents));
if (response.responseID == (byte)ANT_Managed_Library.ANT_ReferenceLibrary.ANTMessageID.BROADCAST_DATA_0x4E)
{
var pageNumber = response.messageContents[1];
switch (pageNumber)
{
case 16: //Page 16 - General Page
{
HandleGeneralDataPage(response.messageContents.Skip(1).ToArray());
}
break;
//case 17: //general settings Page, not on bike, shows on rower, but not very useful
// {
// System.Console.Out.WriteLine("Cycle Length:" + response.messageContents[4] + ",Incline:" + (response.messageContents[5] + (response.messageContents[5] << 8)) + ",Resist:" + response.messageContents[7]);
// }
// break;
//case 18: //metabolic data, doesn't show on rower or bike
// {
// System.Console.Out.WriteLine("BurnRate:" + (response.messageContents[5] + (response.messageContents[6] << 8)) + ",Cal:" + response.messageContents[7]);
// }
// break;
case 19: //treadmill data
{
lastInstCadence = response.messageContents[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 = response.messageContents[5];
lastInstPower = (ushort)(response.messageContents[6] + (response.messageContents[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(response.messageContents.Skip(1).ToArray());
break;
//case 80:
// new ManufacturerDataPageHandler().Handle(response.messageContents.Skip(1).ToArray(), this);
// break;
case 122:
break;
default:
foreach (var handler in pageHandlers)
{
if (handler.CanHandle(pageNumber))
{
handler.Handle(response.messageContents.Skip(1).ToArray(), this);
}
}
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 void SetErgMode(int targetPower)
{
if (this.State != DeviceState.Connected)
return;
if (_grade > 0)
{
SetTrackResistance(0);
}
var channelId = GetChannelId();
//byte id = (byte)79;
//byte[] Data = new byte[9]
// {
// channelId,
// (byte) 49,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// byte.MaxValue,
// (byte)(targetPower & (int) byte.MaxValue),
// (byte)(targetPower >> 8 & (int) byte.MaxValue)
// };
AntConnector.Instance().SendMessage(new FecErgMode(channelId, targetPower).GetMessage());
}
public void SetResistanceMode(double value)
{
if (this.State != DeviceState.Connected)
return;
if (_grade > 0)
{
SetTrackResistance(0);
}
Console.WriteLine($"阻力模式{ value }");
var channelId = GetChannelId();
double resistance = value / 100 * 200;
byte id = (byte)79;
byte[] Data = new byte[9]
{
channelId,
(byte) 48,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
(byte)resistance
};
AntConnector.Instance().SendMessage(id, Data);
}
/// <summary>
/// 风阻
/// </summary>
/// <param name="height">海拔高度,单位米</param>
public void SetWindResistance(double? height = null)
{
if (this.State != DeviceState.Connected)
return;
if (_grade > 0)
{
SetTrackResistance(0);
}
Console.WriteLine($"自由骑{ height }");
var channelId = GetChannelId();
//double resistance = value / 100 * 200;
byte windResistance = 0; //Product of Frontal Surface Area, Drag Coefficient and Air Density. Use default value: 0xFF, range: 0.00 – 1.86 kg/m
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 = (byte)79;
byte[] Data = new byte[9]
{
channelId,
(byte) 50,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
//Wind Resistance Coefficient 0.00 – 1.86kg/m Product of Frontal Surface Area, Drag Coefficient and Air Density.Use default value: 0xFF
windResistance,
//Wind Speed -127 – +127km/h
0xFF,
0xFF
};
AntConnector.Instance().SendMessage(id, Data);
}
/// <summary>
/// 轨道阻力
/// </summary>
/// <param name="grade">坡度百分比的值,单位是%</param>
public void SetTrackResistance(double grade)
{
if (this.State != DeviceState.Connected)
return;
_grade = grade;
if (_grade > 15)
{
_grade = 15;
}
else if (_grade < -5)
{
_grade = -5;
}
//Console.WriteLine($"轨道阻力{ grade }");
var channelId = GetChannelId();
//double resistance = value / 100 * 200;
//var windResistance = 0.00; //Product of Frontal Surface Area, Drag Coefficient and Air Density. Use default value: 0xFF, range: 0.00 – 1.86kg/m
byte id = (byte)79;
// zwift started
// 0000 a4 09 4e 00 10 19 18 19 1a 41 00 30 80 00 00
// reply a4 09 4f 00 33 ff ff ff ff 45 4e ff 25 # p.74 [33] track resistance, ff ff ff ff reserved, [45] slope lsb, [4e] slope msb in 1/100%, [ff] coefficient rolling resistance
// Simulated Grade(%) = (Raw Grade Value x 0.01%) – 200.00% , 454e = 20037 x 0.01 -200 = 0.37%
//var gradeBytes = BitConverter.GetBytes(0x4E20);
var gradeValue = Convert.ToInt32((grade + 200) * 100);
var gradeBytes = BitConverter.GetBytes(gradeValue);
//var gradeBytes = BitConverter.GetBytes(20004);
byte[] Data = new byte[9]
{
channelId,
(byte) 51,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
gradeBytes[0],
gradeBytes[1],
byte.MaxValue
};
AntConnector.Instance().SendMessage(id, Data);
}
public void UpdateUserConfiguration()
{
if (this.State != DeviceState.Connected)
return;
var channelId = GetChannelId();
//var _weight = (int)(this._riderWeight * 100.0);
//byte id = (byte)79;
//byte[] Data = new byte[9]
// {
// channelId,
// (byte) 55,
// (byte)(_weight & (int) byte.MaxValue),
// (byte)(_weight >> 8 & (int) byte.MaxValue),
// byte.MaxValue,
// (byte) 143,
// (byte) 12,
// (byte) 70,
// (byte) 0
// };
//AntConnector.Instance().SendMessage(id, Data);
AntConnector.Instance().SendMessage(new FecUserConfiguration(channelId, this._riderWeight).GetMessage());
}
internal void DoCalibrationSuccess(int? offset)
{
this.LastCalibration = DateTime.UtcNow;
this.Offset = offset;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7cdd6c7cf126c44b931a62300bb6486
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
/// <summary>
/// 校准接口
/// </summary>
interface ICalibrationPage
{
bool CanHandle(byte calibrationId);
void Handle(byte[] dataPayload, AbstractAntDevice device);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 136a39036e705ed4b8ef9253254d836a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 47189d348e47acc4b8a453cca1b2fe63
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Interfaces
{
public interface ICadenceDevice
{
int Cadence { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bbe1ee0aebb20d84096d93ba512531da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Interfaces
{
public interface IHeartRateDevice
{
int HeartRate { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef25441eb7ec60c47b6e85836c30a62d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Interfaces
{
public interface IPowerDevice
{
int Power { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71445caab189dc14ea3e5f293e495dbf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Interfaces
{
public interface IRequiresRiderWeight
{
double RiderWeight { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11743b109c228f4409238e5bab3df5f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Interfaces
{
public interface ISpeedDevice
{
/// <summary>
/// 公里/小时
/// </summary>
double Speed { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3850cb59e257d534cb09d66e62c526da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public class ManufacturerDataPageContent
{
public int HardwareRevision { get; }
public int ManufacturerId { get; }
public int ModelNumber { get; }
public ManufacturerDataPageContent(int hwRevision, int mfgId, int modelNumber)
{
this.HardwareRevision = hwRevision;
this.ManufacturerId = mfgId;
this.ModelNumber = modelNumber;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 44b81b22dea61054392b3df125113d37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3a0e9980bdeaa124bb4f34c29d4cfb6d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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.Messages
{
public abstract class BaseMessage
{
protected byte Channel;
public BaseMessage(byte channel)
{
Channel = channel;
}
public abstract ANTMessage GetMessage();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 929d7cdce470dec4d973c5a714fba697
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Messages
{
public class FecCalibrate : BaseMessage
{
public FecCalibrate(byte channel) : base(channel)
{
}
public override ANTMessage GetMessage()
{
return new ANTMessage()
{
Id = (byte)79,
Size = (byte)3,
Data = new byte[3]
{
this.Channel,
(byte) 1,
(byte) 192
}
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ec7724b4dc1d247428ceab28a8258c6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Messages
{
public class FecErgMode : BaseMessage
{
private readonly int _targetPower;
public FecErgMode(byte channel, int targetPower)
: base(channel)
{
this._targetPower = targetPower;
}
public override ANTMessage GetMessage()
{
return new ANTMessage()
{
Id = (byte)79,
Size = (byte)9,
Data = new byte[9]
{
this.Channel,
(byte) 49,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
byte.MaxValue,
(byte) (this._targetPower & (int) byte.MaxValue),
(byte) (this._targetPower >> 8 & (int) byte.MaxValue)
}
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d38d163148cfd84faf99c5a2bb04010
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Messages
{
public class FecUserConfiguration : BaseMessage
{
private readonly int _weight;
public FecUserConfiguration(byte channel, double weight)
: base(channel)
{
this._weight = (int)(weight * 100.0);
}
public override ANTMessage GetMessage()
{
return new ANTMessage()
{
Id = (byte)79,
Size = (byte)4,
Data = new byte[9]
{
this.Channel,
(byte) 55,
(byte) (this._weight & (int) byte.MaxValue),
(byte) (this._weight >> 8 & (int) byte.MaxValue),
byte.MaxValue,
(byte) 143,
(byte) 12,
(byte) 70,
(byte) 0
}
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9b2e6e85130827343b3cd09fbae19376
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Messages
{
public class GetManufacturersInformation : RequestDataPage
{
public GetManufacturersInformation(byte channel)
: base(channel, (byte)80, new byte?(), new byte?(), (byte)4)
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c125f730d91490941a69906b4a589f4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Messages
{
public class GetProductionInformation : RequestDataPage
{
public GetProductionInformation(byte channel)
: base(channel, (byte)81, new byte?(), new byte?(), (byte)4)
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8bb48d2b993bd7e4b941f8c75135dad5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Messages
{
public class RequestDataPage: BaseMessage
{
private readonly byte _channel;
private byte _page;
private byte _descriptorByte1;
private byte _descriptorByte2;
private readonly byte _requestedTransmissionResponse;
public RequestDataPage(byte channel, byte page, byte? descriptorByte1 = null, byte? descriptorByte2 = null, byte requestedTransmissionResponse = (byte)4)
: base(channel)
{
this._channel = channel;
this._page = page;//81
byte? nullable = descriptorByte1;
this._descriptorByte1 = nullable.HasValue ? nullable.GetValueOrDefault() : byte.MaxValue;
nullable = descriptorByte2;
this._descriptorByte2 = nullable.HasValue ? nullable.GetValueOrDefault() : byte.MaxValue;
this._requestedTransmissionResponse = requestedTransmissionResponse;
}
public override ANTMessage GetMessage()
{
byte[] numArray1 = new byte[9]
{
this._channel,
(byte) 70,
byte.MaxValue,
byte.MaxValue,
this._descriptorByte1,//byte.MaxValue
this._descriptorByte2,//byte.MaxValue
this._requestedTransmissionResponse,//4
this._page,//81
(byte) 1
};
ANTMessage antMessage = new ANTMessage();
antMessage.Id = (byte)79;
int num = (int)(byte)numArray1.Length;
antMessage.Size = (byte)num;
byte[] numArray2 = numArray1;
antMessage.Data = numArray2;
return antMessage;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15e2a257350cfc24b90dadd3a2d33e60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 725781b502595ff44b581d48d7e20f1b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 099c6c049781ec04fa6d891162fa2971
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Pages.CalibrationPages
{
internal class FecCalibrationPage : ICalibrationPage
{
public bool CanHandle(byte calibrationId)
{
return (int)calibrationId == 20;
}
public void Handle(byte[] dataPayload, AbstractAntDevice device)
{
var fecDevice = device as FitDevice;
if (fecDevice == null)
return;
var offset = (int)(short)((int)dataPayload[7] | (int)dataPayload[6] << 8);
fecDevice.DoCalibrationSuccess(offset);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5af3ac698660b3e4b82e1c6e3b597e19
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Pages
{
public interface IPageHandler
{
bool CanHandle(byte pageNumber);
void Handle(byte[] dataPayload, AbstractAntDevice device);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3041b61cfb7f39844a5b465f7d67f48c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant.Pages
{
public class ManufacturerDataPageHandler : IPageHandler
{
private readonly Action<ManufacturerDataPageContent> _callback;
public ManufacturerDataPageHandler(Action<ManufacturerDataPageContent> callback)
{
_callback = callback;
}
public bool CanHandle(byte pageNumber)
{
return pageNumber == 80;
}
public void Handle(byte[] dataPayload, AbstractAntDevice device)
{
int hwRevision = dataPayload[3];
int mfgId = (int)dataPayload[4] | (int)dataPayload[5] << 8;
int modelNumber = (int)((int)dataPayload[6] | (int)dataPayload[7] << 8);
//device.ManufacturerId = mfgId;
_callback(new ManufacturerDataPageContent(hwRevision, mfgId, modelNumber));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e295cfb6fd138343888b4aefe868f2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public enum racerSportType : byte
{
Biking = 0x00,
Running = 0x01,
Rowing = 0x02,
Skiing = 0x03,
Unknown = 0xFF,
}
public enum racerSourceType
{
TrackRecord,
WorldRecordPace,
FitEquip,
}
public enum targetType
{
Seconds,
Meters,
}
public enum SensorType
{
None,
Cadence,
HeartRate,
Power,
Speed,
SpeedCadence,
Trainer,
VirtualPower,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b075797e7a977fd4e8ab4dbea18719fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices.Ant
{
public class UserdChannel
{
public string DeviceNumber { get; set; }
public string DeviceTypeId { get; set; }
public byte Index { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab9369d05dedaa44eb00a63d47ac95fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assets.Scripts.Devices
{
static class DeviceValueFilter
{
public const int MaxCadence = 255;
public const int MaxHeartRate = 254;
public const int MaxPower = 2000;
public const double MaxSpeed = 150.0;
public static int Cadence(int? value)
{
if (value.HasValue)
{
if (value.GetValueOrDefault(0) < MaxCadence)
{
return value.Value;
}
}
return -1;
}
public static int HeartRate(int? value)
{
if (value.HasValue)
{
if (value.GetValueOrDefault(0) < MaxHeartRate && value.GetValueOrDefault(0) >= 0)
{
return value.Value;
}
}
return -1;
}
public static int Power(int? value)
{
if (value.HasValue)
{
if (value.GetValueOrDefault(0) < MaxPower && value.GetValueOrDefault(0) >= 0)
{
return value.Value;
}
}
return -1;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3aaa2f30cd6bf484b80a372b5f2bcb49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: