Skip to content

Commit

Permalink
Merge pull request #22 from StephenHidem/NuGet
Browse files Browse the repository at this point in the history
Merge NuGet branch
  • Loading branch information
StephenHidem authored Sep 23, 2023
2 parents b16759e + 7453229 commit 1387ee5
Show file tree
Hide file tree
Showing 19 changed files with 168 additions and 71 deletions.
6 changes: 3 additions & 3 deletions AntPlus.UnitTests/AntDeviceCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public void TestInitialize()

private AntDeviceCollection CreateAntDeviceCollection()
{
IAntChannel[] mockChannels = new IAntChannel[8];
Array.Fill(mockChannels, mockAntChannel.Object);
mockAntRadio.Setup(r => r.InitializeContinuousScanMode()).Returns(mockChannels);
return new AntDeviceCollection(
mockAntRadio.Object,
null,
Expand All @@ -42,7 +45,6 @@ private AntDeviceCollection CreateAntDeviceCollection()
public void MultithreadedAdd_Collection_ExpectedCount()
{
// Arrange
mockAntRadio.Setup(r => r.GetChannel(It.IsAny<int>())).Returns(mockAntChannel.Object);
Mock<ILogger<UnknownDevice>> mockLogger = new();
var antDeviceCollection = CreateAntDeviceCollection();
int numberOfDevices = 16;
Expand Down Expand Up @@ -73,7 +75,6 @@ public void MultithreadedAdd_Collection_ExpectedCount()
public void MultithreadedRemove_Collection_ExpectedCount()
{
// Arrange
mockAntRadio.Setup(r => r.GetChannel(It.IsAny<int>())).Returns(mockAntChannel.Object);
Mock<ILogger<UnknownDevice>> mockLogger = new();
var antDeviceCollection = CreateAntDeviceCollection();
int numberOfDevices = 16;
Expand Down Expand Up @@ -117,7 +118,6 @@ public void ChannelResponseEvent_Collection_ExpectedDeviceInCollection(byte devi
// Arrange
byte[] id = new byte[4] { 1, 0, deviceClass, 0 };
ChannelId cid = new(BitConverter.ToUInt32(id));
mockAntRadio.Setup(r => r.GetChannel(It.IsAny<int>())).Returns(mockAntChannel.Object);
mockAntChannel.SetupAdd(m => m.ChannelResponse += It.IsAny<EventHandler<AntResponse>>());
mockAntChannel.SetupRemove(m => m.ChannelResponse -= It.IsAny<EventHandler<AntResponse>>());
var mockResponse = new MockResponse(cid, new byte[8]);
Expand Down
4 changes: 2 additions & 2 deletions AntPlus.UnitTests/AntPlus.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\AntPlus\AntPlus.csproj" />
<Folder Include="DeviceProfiles\AssetTracker\" />
</ItemGroup>

<ItemGroup>
<Folder Include="DeviceProfiles\AssetTracker\" />
<ProjectReference Include="..\AntPlus\AntPlus.csproj" />
</ItemGroup>

</Project>
23 changes: 10 additions & 13 deletions AntPlus/AntDeviceCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ public class AntDeviceCollection : ObservableCollection<AntDevice>
/// </remarks>
public object CollectionLock = new object();

private readonly IAntRadio antRadio;
private int channelNum = 0;
private int channelNum = 1;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<AntDeviceCollection> logger;
private readonly ushort timeout;
private readonly IAntChannel[] channels;

/// <summary>Initializes a new instance of the <see cref="AntDeviceCollection" /> class.</summary>
/// <summary>
/// Initializes a new instance of the <see cref="AntDeviceCollection" /> class. The ANT radio is configured
/// for continuous scan mode.
/// </summary>
/// <param name="antRadio">The ANT radio interface.</param>
/// <param name="loggerFactory">Logger factory to generate type specific ILogger from. Can be null.</param>
/// <param name="antDeviceTimeout">ANT device timeout in milliseconds. The default is 2000 milliseconds.</param>
Expand All @@ -51,18 +54,12 @@ public class AntDeviceCollection : ObservableCollection<AntDevice>
/// </remarks>
public AntDeviceCollection(IAntRadio antRadio, ILoggerFactory loggerFactory, ushort antDeviceTimeout = 2000)
{
this.antRadio = antRadio;
_loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
logger = _loggerFactory.CreateLogger<AntDeviceCollection>();
logger.LogInformation("Created AntDeviceCollection");
timeout = antDeviceTimeout;
antRadio.GetChannel(0).ChannelResponse += Channel_ChannelResponse;

// assign channels for devices to use for sending messages
for (int i = 1; i < antRadio.NumChannels; i++)
{
_ = antRadio.GetChannel(i).AssignChannel(ChannelType.BaseSlaveReceive, 0, 500);
}
channels = antRadio.InitializeContinuousScanMode();
channels[0].ChannelResponse += Channel_ChannelResponse;
}

private void Channel_ChannelResponse(object sender, AntResponse e)
Expand Down Expand Up @@ -114,8 +111,8 @@ private void DeviceOffline(object sender, EventArgs e)

private AntDevice CreateAntDevice(ChannelId channelId)
{
if (++channelNum == antRadio.NumChannels) channelNum = 1;
IAntChannel channel = antRadio.GetChannel(channelNum);
IAntChannel channel = channels[channelNum++];
if (channelNum == channels.Length) { channelNum = 1; }

switch (channelId.DeviceType)
{
Expand Down
25 changes: 15 additions & 10 deletions AntPlus/AntPlus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
<PackageId>SmallEarthTech.$(AssemblyName)</PackageId>
<Version>$(AssemblyVersion)</Version>
<Title>ANT+ Class Library</Title>
<PackageProjectUrl>https://github.com/StephenHidem/AntPlus</PackageProjectUrl>
<PackageProjectUrl>http://stephenhidem.github.io/AntPlus</PackageProjectUrl>
<Authors>Stephen Hidem</Authors>
<Copyright>© $(Authors). All rights reserved.</Copyright>
<Copyright>© $(Authors) 2023. All rights reserved.</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>ant; ant+; ant plus; smallearthtech</PackageTags>
<Description>Enables applications to interface and acquire data from a variety of ANT+ sensor sources. The primary class is AntPlus and it contains device profiles of ANT+ devices and common data pages.</Description>
<RepositoryUrl>https://github.com/StephenHidem/AntPlus</RepositoryUrl>
Expand All @@ -23,6 +22,8 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AssemblyName>SmallEarthTech.$(MSBuildProjectName)</AssemblyName>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageIcon>PackageLogo.png</PackageIcon>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Expand Down Expand Up @@ -68,13 +69,6 @@
<EmbeddedResource Include="Images\Unknown.png" />
</ItemGroup>

<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
</ItemGroup>
Expand All @@ -89,4 +83,15 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Update="PackageLogo.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="readme.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>
Binary file added AntPlus/PackageLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions AntPlus/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Small Earth Technology ANT+ Class Library
Add a reference to this class in your application to interact with ANT+ devices and display sensor data.
##### Supported ANT+ profiles:
- Asset tracker
- Bicycle power
- Bike speed and cadence
- Fitness equipment
- Geocache
- Heart rate monitors
- Muscle oxygen
- Stride based speed and distance

Unknown devices are supported by the UnknownDevice class.
31 changes: 20 additions & 11 deletions AntRadioInterface/AntRadioInterface.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
<Authors>Stephen Hidem</Authors>
<Title>ANT+ Radio Interface Class Library</Title>
<Version>$(AssemblyVersion)</Version>
<Copyright>© $(Authors). All rights reserved.</Copyright>
<PackageProjectUrl>https://github.com/StephenHidem/AntPlus</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>ant; ant+; ant plus; smallearthtech</PackageTags>
<Copyright>© $(Authors) 2023. All rights reserved.</Copyright>
<PackageProjectUrl>http://stephenhidem.github.io/AntPlus</PackageProjectUrl>
<PackageTags>ant; ant+; ant plus; dynastream; smallearthtech</PackageTags>
<RepositoryUrl>https://github.com/StephenHidem/AntPlus</RepositoryUrl>
<Description>The AntRadioInterface defines an interface to interact with an ANT radio to send and receive from an ANT device. Use this package to create a concrete implementation of an ANT radio.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -22,21 +21,20 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AssemblyName>SmallEarthTech.$(MSBuildProjectName)</AssemblyName>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageIcon>PackageLogo.png</PackageIcon>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<Compile Remove="IAntResponse.cs" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'" />

<ItemGroup>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<Compile Remove="IAntResponse.cs" />
</ItemGroup>

<ItemGroup>
Expand All @@ -46,4 +44,15 @@
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Update="PackageLogo.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="readme.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions AntRadioInterface/IAntChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ public interface IAntChannel : IDisposable

#region ANT Channel Functions

/// <summary>Gets the channel number.</summary>
/// <returns>ANT channel number.</returns>
byte ChannelNumber { get; }

/// <summary>
/// Returns current channel status.
/// Throws exception on timeout.
Expand Down
34 changes: 34 additions & 0 deletions AntRadioInterface/IAntRadio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,40 @@ public interface IAntRadio
{
/// <summary>Occurs when radio response has been received.</summary>
event EventHandler<AntResponse> RadioResponse;

/// <summary>Initializes the ANT radio for continuous scan mode.</summary>
/// <returns>
/// Returns an array of ANT channels. The first element of the array (ANT channel 0) is used for continuous
/// scan mode to receive broadcast messages from ANT master devices. The remaining channels
/// should be configured so messages may be sent to ANT master devices.
/// </returns>
/// <remarks>
/// Implementors typically would perform the following setup -
/// <code>
/// public IAntChannel[] InitializeContinuousScanMode()
/// {
/// IAntChannel[] channels = new IAntChannel[NumChannels];
///
/// // configure channel 0 for continuous scan mode
/// SetNetworkKey(0, new byte[] { 0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45 });
/// EnableRxExtendedMessages(true);
/// channels[0] = GetChannel(0);
/// channels[0].AssignChannel(ChannelType.BaseSlaveReceive, 0, 500);
/// channels[0].SetChannelID(new ChannelId(0), 500);
/// channels[0].SetChannelFreq(57, 500);
/// OpenRxScanMode();
///
/// // assign channels for devices to use for sending messages
/// for (int i = 1; i &lt; NumChannels; i++)
/// {
/// channels[i] = GetChannel(i);
/// _ = channels[i].AssignChannel(ChannelType.BaseSlaveReceive, 0, 500);
/// }
/// return channels;
/// }
/// </code>
/// </remarks>
IAntChannel[] InitializeContinuousScanMode();
/// <summary>Cancels the transfers.</summary>
/// <param name="cancelWaitTime">The cancel wait time.</param>
void CancelTransfers(int cancelWaitTime);
Expand Down
Binary file added AntRadioInterface/PackageLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions AntRadioInterface/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## Small Earth Technology ANT+ Radio Interface Library
Derive concrete class implementations from the AntRadioInterface class to support the hardware being used. This largely
follows Dynastream's .NET and native implementations of their ANT USB stick. Multiple interfaces are defined along with
classes to derive from (AntResponse and DeviceCapabilities). These classes and interfaces are used by the ANT+ class library.

See AntUsbStick in the [repository](https://github.com/StephenHidem/AntPlus/tree/master/Examples/AntUsbStick) for an example
that supports the Dynastream/Garmin ANT USB stick.
One important thing to note is this example uses the .NET and native DLLs provided in Dynastream's PC SDK.
3 changes: 3 additions & 0 deletions Examples/AntMulticastServer/AntMulticastServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
24 changes: 13 additions & 11 deletions Examples/AntMulticastServer/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// See https://aka.ms/new-console-template for more information
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using SmallEarthTech.AntRadioInterface;
using SmallEarthTech.AntUsbStick;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
Expand All @@ -14,8 +14,16 @@
Console.WriteLine(string.Format("ANT Multicast Server - Version {0}.", Assembly.GetExecutingAssembly().GetName().Version?.ToString(3)));
Console.WriteLine("Copyright 2023 Stephen Hidem.");

// Initialize early, without access to configuration or services
Log.Logger = new LoggerConfiguration()
.WriteTo.Debug(outputTemplate:
"[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj}{NewLine}{Exception}") // + file or centralized logging
.MinimumLevel.Debug()
.CreateLogger();

// dependency services
IHost host = Host.CreateDefaultBuilder(Environment.GetCommandLineArgs()).
UseSerilog().
ConfigureServices(s =>
{
s.AddSingleton<IAntRadio, AntRadio>();
Expand All @@ -32,14 +40,8 @@
// create and configure ANT radio
Console.WriteLine("Configuring ANT radio (uses the first USB stick found).");
AntRadio antRadio = (AntRadio)host.Services.GetRequiredService<IAntRadio>();
IAntChannel channel = antRadio.GetChannel(0);
antRadio.SetNetworkKey(0, new byte[] { 0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45 });
antRadio.EnableRxExtendedMessages(true);
channel.AssignChannel(ChannelType.BaseSlaveReceive, 0, 500);
channel.SetChannelID(new ChannelId(0), 500);
channel.SetChannelFreq(57, 500);
antRadio.OpenRxScanMode();
channel.ChannelResponse += Channel_ChannelResponse;
IAntChannel[] channel = antRadio.InitializeContinuousScanMode();
channel[0].ChannelResponse += Channel_ChannelResponse;

// create background task to receive UDP directed to this server from any clients
_ = Task.Run(async () =>
Expand All @@ -48,7 +50,7 @@
while (true)
{
UdpReceiveResult result = await udpServer.ReceiveAsync();
Debug.WriteLine(BitConverter.ToString(result.Buffer));
Log.Debug(BitConverter.ToString(result.Buffer));
ChannelId channelId = new(BitConverter.ToUInt32(result.Buffer, 0));
byte[] msg = result.Buffer.Skip(4).Take(8).ToArray();
uint ackWaitTime = BitConverter.ToUInt32(result.Buffer, 12);
Expand All @@ -63,7 +65,7 @@
Console.ReadLine();

// clean up
channel.ChannelResponse -= Channel_ChannelResponse;
channel[0].ChannelResponse -= Channel_ChannelResponse;

void Channel_ChannelResponse(object? sender, AntResponse e)
{
Expand Down
9 changes: 6 additions & 3 deletions Examples/AntUsbStick/AntChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ internal AntChannel(ANT_Channel channel, ILogger<AntChannel> logger)
_logger = logger;
antChannel = channel;
channel.channelResponse += Channel_channelResponse;
_logger.LogDebug("Created AntChannel {Channel}", channel.getChannelNum());
_logger.LogDebug("Created AntChannel {Channel}", ChannelNumber);
}

private void Channel_channelResponse(ANT_Response response)
{
AntResponse antResponse = new UsbAntResponse(response);
_logger.LogTrace("Channel response. Channel # = {ChannelNumber}, Response ID = {ResponseID}, Payload = {Payload}", antChannel.getChannelNum(), (MessageId)antResponse.ResponseId, BitConverter.ToString(antResponse.Payload ?? new byte[] { 0 }));
_logger.LogTrace("Channel response. Channel # = {ChannelNumber}, Response ID = {ResponseID}, Payload = {Payload}", ChannelNumber, (MessageId)antResponse.ResponseId, BitConverter.ToString(antResponse.Payload ?? new byte[] { 0 }));
ChannelResponse?.Invoke(this, antResponse);
}

Expand Down Expand Up @@ -56,6 +56,9 @@ public bool ConfigFrequencyAgility(byte freq1, byte freq2, byte freq3, uint resp
return antChannel.configFrequencyAgility(freq1, freq2, freq3, responseWaitTime);
}

/// <inheritdoc/>
public byte ChannelNumber => antChannel.getChannelNum();

/// <inheritdoc/>
public bool IncludeExcludeListAddChannel(ChannelId channelId, byte listIndex, uint responseWaitTime)
{
Expand Down Expand Up @@ -121,7 +124,7 @@ public async Task<MessagingReturnCode> SendExtAcknowledgedData(ChannelId channel
BitConverter.GetBytes(channelId.Id)[3], data, ackWaitTime);
}
});
_logger.LogDebug("SendExtAcknowledgedData: Channel # = {ChannelNumber}, Channel ID = 0x{ChannelId:X8}, Return code = {MRC}, data = {Data}", antChannel.getChannelNum(), channelId.Id, rc, BitConverter.ToString(data));
_logger.LogDebug("SendExtAcknowledgedData: Channel # = {ChannelNumber}, Channel ID = 0x{ChannelId:X8}, Return code = {MRC}, data = {Data}", ChannelNumber, channelId.Id, rc, BitConverter.ToString(data));
return rc;
}

Expand Down
Loading

0 comments on commit 1387ee5

Please sign in to comment.