├── .github
└── FUNDING.yml
├── icon.png
├── .gitignore
├── src
├── WinBeacon
│ ├── _Visibility.cs
│ ├── BeaconEventArgs.cs
│ ├── EddystoneEventArgs.cs
│ ├── Extensions
│ │ ├── ComparableExtensions.cs
│ │ └── BeaconExtensions.cs
│ ├── WinBeacon.csproj
│ ├── Hub.cs
│ ├── Beacon.cs
│ └── Eddystone.cs
├── WinBeacon.Stack
│ ├── _Visibility.cs
│ ├── Hci
│ │ ├── ICommandParameter.cs
│ │ ├── Opcodes
│ │ │ ├── InformationalParametersOpcode.cs
│ │ │ ├── ControllerBasebandOpcode.cs
│ │ │ └── LeControllerOpcode.cs
│ │ ├── Commands
│ │ │ ├── ResetCommand.cs
│ │ │ ├── ReadLocalVersionCommand.cs
│ │ │ ├── LeReadHostSupportedCommand.cs
│ │ │ ├── LeSetEventMaskCommand.cs
│ │ │ ├── SetEventMaskCommand.cs
│ │ │ ├── LeSetAdvertisingEnableCommand.cs
│ │ │ ├── LeSetScanEnableCommand.cs
│ │ │ ├── LeWriteHostSupportedCommand.cs
│ │ │ ├── LeSetAdvertisingDataCommand.cs
│ │ │ ├── ReadBdAddrCommand.cs
│ │ │ ├── LeSetScanParametersCommand.cs
│ │ │ └── LeSetAdvertisingParametersCommand.cs
│ │ ├── OpcodeGroup.cs
│ │ ├── DataType.cs
│ │ ├── Parameters
│ │ │ ├── ByteCommandParameter.cs
│ │ │ ├── ByteArrayCommandParameter.cs
│ │ │ ├── BoolCommandParameter.cs
│ │ │ └── UshortCommandParameter.cs
│ │ ├── Events
│ │ │ ├── LeAdvertisingEventType.cs
│ │ │ ├── CommandCompleteEvent.cs
│ │ │ ├── LeAdvertisingEvent.cs
│ │ │ └── LeMetaEvent.cs
│ │ ├── DataReceivedEventArgs.cs
│ │ ├── Event.cs
│ │ ├── Command.cs
│ │ └── EventCode.cs
│ ├── DeviceAddressReceivedEventArgs.cs
│ ├── LeMetaEventReceivedEventArgs.cs
│ ├── Transports
│ │ ├── UsbHciEndpointType.cs
│ │ ├── LibUsb
│ │ │ ├── ILibUsbDevice.cs
│ │ │ ├── LibUsbDevice.cs
│ │ │ └── LibUsbTransport.cs
│ │ └── UsbHciEndpointInfo.cs
│ ├── Controllers
│ │ ├── LibUsbController.cs
│ │ └── LeController.cs
│ ├── Extensions
│ │ ├── UshortExtensions.cs
│ │ ├── QueueExtensions.cs
│ │ └── LibUsbDeviceExtensions.cs
│ ├── WinBeaconException.cs
│ ├── ITransport.cs
│ ├── DeviceAddress.cs
│ ├── WinBeacon.Stack.csproj
│ └── ILeController.cs
└── GlobalAssemblyInfo.cs
├── test
├── WinBeacon.Stack.Tests
│ ├── _Visibility.cs
│ ├── WinBeacon.Stack.Tests.csproj
│ ├── Extensions
│ │ ├── UshortExtensionsTests.cs
│ │ └── QueueExtensionsTests.cs
│ ├── Hci
│ │ ├── ParameterTests.cs
│ │ ├── EventTests.cs
│ │ └── CommandTests.cs
│ ├── Controllers
│ │ ├── LibUsbLeControllerTests.cs
│ │ └── LeControllerTests.cs
│ └── Transports
│ │ └── LibUsb
│ │ └── LibUsbTransportTests.cs
└── WinBeacon.Tests
│ ├── WinBeacon.Tests.csproj
│ ├── Extensions
│ ├── BeaconExtensionsTests.cs
│ └── ComparableExtensionsTests.cs
│ ├── BeaconTests.cs
│ ├── EddystoneUidTests.cs
│ └── EddystoneUrlTests.cs
├── .nuget
├── NuGet.Config
└── NuGet.targets
├── samples
└── SampleConsole
│ ├── App.config
│ ├── SampleConsole.csproj
│ └── Program.cs
├── LICENSE
├── ReadMe.md
└── WinBeacon.sln
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: huysentruitw
2 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huysentruitw/win-beacon/HEAD/icon.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | *.vshost.*
3 | **/bin/*
4 | **/obj/*
5 | *.exe
6 | packages/
7 | *.nupkg
8 | .vs/
9 |
--------------------------------------------------------------------------------
/src/WinBeacon/_Visibility.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly:InternalsVisibleTo("WinBeacon.Tests")]
4 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/_Visibility.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
4 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/samples/SampleConsole/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/_Visibility.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly:InternalsVisibleTo("WinBeacon.Tests")]
4 | [assembly:InternalsVisibleTo("WinBeacon.Stack.Tests")]
5 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
6 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/ICommandParameter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci
8 | {
9 | internal interface ICommandParameter
10 | {
11 | byte[] ToByteArray();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Opcodes/InformationalParametersOpcode.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Opcodes
8 | {
9 | internal enum InformationalParametersOpcode : ushort
10 | {
11 | ReadLocalVersion = 0x0001,
12 | ReadBdAddr = 0x0009
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/samples/SampleConsole/SampleConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 |
6 | Exe
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Opcodes/ControllerBasebandOpcode.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Opcodes
8 | {
9 | internal enum ControllerBasebandOpcode : ushort
10 | {
11 | SetEventMask = 0x0001,
12 | Reset = 0x0003,
13 | LeReadHostSupported = 0x006C,
14 | LeWriteHostSupported = 0x006D
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/ResetCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 |
9 | namespace WinBeacon.Stack.Hci.Commands
10 | {
11 | internal class ResetCommand : Command
12 | {
13 | public ResetCommand()
14 | : base(OpcodeGroup.ControllerBaseband, (int)ControllerBasebandOpcode.Reset)
15 | {
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/OpcodeGroup.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci
8 | {
9 | internal enum OpcodeGroup : byte
10 | {
11 | LinkControl = 0x01,
12 | LinkPolicy = 0x02,
13 | ControllerBaseband = 0x03,
14 | InformationalParameters = 0x04,
15 | StatusParameters = 0x05,
16 | LeController = 0x08,
17 | Vendor = 0x3F
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/ReadLocalVersionCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 |
9 | namespace WinBeacon.Stack.Hci.Commands
10 | {
11 | internal class ReadLocalVersionCommand : Command
12 | {
13 | public ReadLocalVersionCommand()
14 | : base(OpcodeGroup.InformationalParameters, (int)InformationalParametersOpcode.ReadLocalVersion)
15 | {
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeReadHostSupportedCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 |
9 | namespace WinBeacon.Stack.Hci.Commands
10 | {
11 | internal class LeReadHostSupportedCommand : Command
12 | {
13 | public LeReadHostSupportedCommand()
14 | : base(OpcodeGroup.ControllerBaseband, (int)ControllerBasebandOpcode.LeReadHostSupported)
15 | {
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/DataType.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci
8 | {
9 | ///
10 | /// Transport data types.
11 | ///
12 | internal enum DataType
13 | {
14 | ///
15 | /// Asynchronous Connectionless data.
16 | ///
17 | Acl,
18 | ///
19 | /// Command data.
20 | ///
21 | Command
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/WinBeacon.Stack.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Parameters/ByteCommandParameter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Parameters
8 | {
9 | internal class ByteCommandParameter : ICommandParameter
10 | {
11 | public byte Value { get; private set; }
12 |
13 | public ByteCommandParameter(byte value)
14 | {
15 | Value = value;
16 | }
17 |
18 | public byte[] ToByteArray()
19 | {
20 | return new byte[] { Value };
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Parameters/ByteArrayCommandParameter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Parameters
8 | {
9 | internal class ByteArrayCommandParameter : ICommandParameter
10 | {
11 | public byte[] Value { get; private set; }
12 |
13 | public ByteArrayCommandParameter(byte[] value)
14 | {
15 | Value = value;
16 | }
17 |
18 | public byte[] ToByteArray()
19 | {
20 | return Value;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Parameters/BoolCommandParameter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Parameters
8 | {
9 | internal class BoolCommandParameter : ICommandParameter
10 | {
11 | public bool Value { get; private set; }
12 |
13 | public BoolCommandParameter(bool value)
14 | {
15 | Value = value;
16 | }
17 |
18 | public byte[] ToByteArray()
19 | {
20 | return new byte[] { (byte)(Value ? 1 : 0) };
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeSetEventMaskCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 | using WinBeacon.Stack.Hci.Parameters;
9 |
10 | namespace WinBeacon.Stack.Hci.Commands
11 | {
12 | internal class LeSetEventMaskCommand : Command
13 | {
14 | public LeSetEventMaskCommand(byte[] mask)
15 | : base(OpcodeGroup.LeController, (int)LeControllerOpcode.SetEventMask)
16 | {
17 | Parameters.Add(new ByteArrayCommandParameter(mask));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/SetEventMaskCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 | using WinBeacon.Stack.Hci.Parameters;
9 |
10 | namespace WinBeacon.Stack.Hci.Commands
11 | {
12 | internal class SetEventMaskCommand : Command
13 | {
14 | public SetEventMaskCommand(byte[] mask)
15 | : base(OpcodeGroup.ControllerBaseband, (int)ControllerBasebandOpcode.SetEventMask)
16 | {
17 | Parameters.Add(new ByteArrayCommandParameter(mask));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Opcodes/LeControllerOpcode.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Opcodes
8 | {
9 | internal enum LeControllerOpcode : ushort
10 | {
11 | SetEventMask = 0x0001,
12 | SetAdvertisingParameters = 0x0006, // http://stackoverflow.com/questions/21124993/is-there-a-way-to-increase-ble-advertisement-frequency-in-bluez
13 | SetAdvertisingData = 0x0008,
14 | SetAdvertisingEnable = 0x000A,
15 | SetScanParameters = 0x000B,
16 | SetScanEnable = 0x000C,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/WinBeacon.Tests/WinBeacon.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Parameters/UshortCommandParameter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Parameters
8 | {
9 | internal class UshortCommandParameter : ICommandParameter
10 | {
11 | public ushort Value { get; private set; }
12 |
13 | public UshortCommandParameter(ushort value)
14 | {
15 | Value = value;
16 | }
17 |
18 | public byte[] ToByteArray()
19 | {
20 | return new byte[] { (byte)(Value & 0xFF), (byte)(Value >> 8) };
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/WinBeacon/BeaconEventArgs.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon
10 | {
11 | ///
12 | /// Event arguments for Apple iBeacon events.
13 | ///
14 | public class BeaconEventArgs : EventArgs
15 | {
16 | ///
17 | /// Gets the detected beacon.
18 | ///
19 | public Beacon Beacon { get; private set; }
20 |
21 | internal BeaconEventArgs(Beacon beacon)
22 | {
23 | Beacon = beacon;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeSetAdvertisingEnableCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 | using WinBeacon.Stack.Hci.Parameters;
9 |
10 | namespace WinBeacon.Stack.Hci.Commands
11 | {
12 | internal class LeSetAdvertisingEnableCommand : Command
13 | {
14 | public LeSetAdvertisingEnableCommand(bool enable)
15 | : base(OpcodeGroup.LeController, (int)LeControllerOpcode.SetAdvertisingEnable)
16 | {
17 | Parameters.Add(new BoolCommandParameter(enable));
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/WinBeacon/EddystoneEventArgs.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon
10 | {
11 | ///
12 | /// Event arguments for Eddystone events.
13 | ///
14 | public class EddystoneEventArgs : EventArgs
15 | {
16 | ///
17 | /// Gets the detected beacon.
18 | ///
19 | public Eddystone Eddystone { get; private set; }
20 |
21 | internal EddystoneEventArgs(Eddystone eddystone)
22 | {
23 | Eddystone = eddystone;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeSetScanEnableCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 | using WinBeacon.Stack.Hci.Parameters;
9 |
10 | namespace WinBeacon.Stack.Hci.Commands
11 | {
12 | internal class LeSetScanEnableCommand : Command
13 | {
14 | public LeSetScanEnableCommand(bool enable, bool filterDuplicates)
15 | : base(OpcodeGroup.LeController, (int)LeControllerOpcode.SetScanEnable)
16 | {
17 | Parameters.Add(new BoolCommandParameter(enable));
18 | Parameters.Add(new BoolCommandParameter(filterDuplicates));
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/DeviceAddressReceivedEventArgs.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon.Stack
10 | {
11 | ///
12 | /// Event arguments for the DeviceAddressReceived event.
13 | ///
14 | public class DeviceAddressReceivedEventArgs : EventArgs
15 | {
16 | ///
17 | /// Received device address.
18 | ///
19 | public DeviceAddress DeviceAddress { get; private set; }
20 |
21 | internal DeviceAddressReceivedEventArgs(DeviceAddress deviceAddress)
22 | {
23 | DeviceAddress = deviceAddress;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/LeMetaEventReceivedEventArgs.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using WinBeacon.Stack.Hci.Events;
9 |
10 | namespace WinBeacon.Stack
11 | {
12 | ///
13 | /// Event arguments for the LeMetaEventReceived event.
14 | ///
15 | public class LeMetaEventReceivedEventArgs : EventArgs
16 | {
17 | ///
18 | /// Received LeMetaEvent.
19 | ///
20 | public LeMetaEvent LeMetaEvent { get; private set; }
21 |
22 | internal LeMetaEventReceivedEventArgs(LeMetaEvent leMetaEvent)
23 | {
24 | LeMetaEvent = leMetaEvent;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeWriteHostSupportedCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Hci.Opcodes;
8 | using WinBeacon.Stack.Hci.Parameters;
9 |
10 | namespace WinBeacon.Stack.Hci.Commands
11 | {
12 | internal class LeWriteHostSupportedCommand : Command
13 | {
14 | public LeWriteHostSupportedCommand(bool supportedHost, bool simultaneousHost)
15 | : base(OpcodeGroup.ControllerBaseband, (int)ControllerBasebandOpcode.LeWriteHostSupported)
16 | {
17 | Parameters.Add(new BoolCommandParameter(supportedHost));
18 | Parameters.Add(new BoolCommandParameter(simultaneousHost));
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Extensions/UshortExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using NUnit.Framework;
8 |
9 | namespace WinBeacon.Stack.Tests.Extensions
10 | {
11 | [TestFixture]
12 | public class UshortExtensionsTests
13 | {
14 | [Test]
15 | public void UshortExtensions_LoByte()
16 | {
17 | ushort input = 0x5386;
18 | byte output = input.LoByte();
19 | Assert.AreEqual(0x86, output);
20 | }
21 |
22 | [Test]
23 | public void UshortExtensions_HiByte()
24 | {
25 | ushort input = 0x8429;
26 | byte output = input.HiByte();
27 | Assert.AreEqual(0x84, output);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Transports/UsbHciEndpointType.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Transport
8 | {
9 | ///
10 | /// Bluetooth specific USB endpoint types.
11 | ///
12 | internal enum UsbBluetoothEndpointType
13 | {
14 | ///
15 | /// Command endpoint.
16 | ///
17 | Commands,
18 | ///
19 | /// Event endpoint.
20 | ///
21 | Events,
22 | ///
23 | /// ACL data in endpoint.
24 | ///
25 | AclDataIn,
26 | ///
27 | /// ACL data out endpoint.
28 | ///
29 | AclDataOut
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeSetAdvertisingDataCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using WinBeacon.Stack.Hci.Opcodes;
9 | using WinBeacon.Stack.Hci.Parameters;
10 |
11 | namespace WinBeacon.Stack.Hci.Commands
12 | {
13 | internal class LeSetAdvertisingDataCommand : Command
14 | {
15 | public LeSetAdvertisingDataCommand(byte[] data)
16 | : base(OpcodeGroup.LeController, (int)LeControllerOpcode.SetAdvertisingData)
17 | {
18 | if (data.Length > 31)
19 | throw new ArgumentOutOfRangeException();
20 | Parameters.Add(new ByteCommandParameter((byte)data.Length));
21 | Parameters.Add(new ByteArrayCommandParameter(data));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/ReadBdAddrCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Linq;
8 | using WinBeacon.Stack.Hci.Opcodes;
9 |
10 | namespace WinBeacon.Stack.Hci.Commands
11 | {
12 | internal class ReadBdAddrCommand : Command
13 | {
14 | public ReadBdAddrCommand()
15 | : base(OpcodeGroup.InformationalParameters, (int)InformationalParametersOpcode.ReadBdAddr)
16 | {
17 | }
18 |
19 | internal override DeviceAddress ParseCommandResult(Events.CommandCompleteEvent e)
20 | {
21 | if (e.ResultData == null || e.ResultData.Length != 6)
22 | return DeviceAddress.Empty;
23 | return new DeviceAddress(e.ResultData.Reverse().ToArray());
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Controllers/LibUsbController.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using WinBeacon.Stack.Transports.LibUsb;
8 |
9 | namespace WinBeacon.Stack.Controllers
10 | {
11 | ///
12 | /// LibUsb Bluetooth Low Energy controller.
13 | ///
14 | public class LibUsbLeController : LeController
15 | {
16 | ///
17 | /// Create a new LibUsbLeController instance.
18 | ///
19 | /// The Bluetooth dongle USB vendor identifier.
20 | /// The Bluetooth dongle USB product identifier.
21 | public LibUsbLeController(int vid, int pid)
22 | : base(new LibUsbTransport(new LibUsbDevice(vid, pid)))
23 | {
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/WinBeacon.Tests/Extensions/BeaconExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using NUnit.Framework;
8 |
9 | namespace WinBeacon.Tests.Extensions
10 | {
11 | [TestFixture]
12 | public class BeaconExtensionsTests
13 | {
14 | [Test]
15 | public void BeaconExtension_GetRange()
16 | {
17 | var beacon = new Beacon("", 0, 0, -39) { Rssi = -52 };
18 | Assert.AreEqual(4.4668359215096309d, beacon.GetRange());
19 | }
20 |
21 | [Test]
22 | public void BeaconExtension_GetApproximateIosRange()
23 | {
24 | var beacon = new Beacon("", 0, 0, -39) { Rssi = -52 };
25 | Assert.AreEqual(8.3781601753285457d, beacon.GetApproximateIosRange());
26 | beacon.Rssi = 0;
27 | Assert.AreEqual(-1d, beacon.GetApproximateIosRange());
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Transports/LibUsb/ILibUsbDevice.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Collections.Generic;
8 | using LibUsbDotNet;
9 | using LibUsbDotNet.Info;
10 | using LibUsbDotNet.Main;
11 |
12 | namespace WinBeacon.Stack.Transports.LibUsb
13 | {
14 | ///
15 | /// Interface for wrapping LibUsbDotNet.UsbDevice.
16 | ///
17 | internal interface ILibUsbDevice
18 | {
19 | int Vid { get; }
20 | int Pid { get; }
21 | void Open();
22 | void Close();
23 | IEnumerable Configs { get; }
24 | UsbEndpointReader OpenEndpointReader(ReadEndpointID readEndpointID);
25 | UsbEndpointWriter OpenEndpointWriter(WriteEndpointID writeEndpointID);
26 | bool ControlTransfer(ref UsbSetupPacket setupPacket, object buffer, int bufferLength, out int lengthTransferred);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Extensions/UshortExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack
8 | {
9 | ///
10 | /// ushort extension methods.
11 | ///
12 | internal static class UshortExtensions
13 | {
14 | ///
15 | /// Get the least significant byte.
16 | ///
17 | /// The ushort.
18 | /// The least significant byte.
19 | public static byte LoByte(this ushort value)
20 | => (byte)(value & 0xFF);
21 |
22 | ///
23 | /// Get the most significant byte.
24 | ///
25 | /// The ushort.
26 | /// The most significant byte.
27 | public static byte HiByte(this ushort value)
28 | => (byte)(value >> 8);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/WinBeacon/Extensions/ComparableExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace System
8 | {
9 | ///
10 | /// IComparable extension methods.
11 | ///
12 | public static class ComparableExtensions
13 | {
14 | ///
15 | /// Same as CompareTo but returns null instead of 0 if both items are equal.
16 | ///
17 | /// IComparable type.
18 | /// This instance.
19 | /// The other instance.
20 | /// Lexical relation between this and the other instance or null if both are equal.
21 | public static int? NullableCompareTo(this T @this, T other) where T : IComparable
22 | {
23 | var result = @this.CompareTo(other);
24 | return result != 0 ? result : (int?)null;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Events/LeAdvertisingEventType.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci.Events
8 | {
9 | ///
10 | /// Low Energy Advertising event types
11 | ///
12 | public enum LeAdvertisingEventType : byte
13 | {
14 | ///
15 | /// Connectable undirected advertising
16 | ///
17 | Ind = 0x00,
18 | ///
19 | /// Connectable directed advertising event.
20 | ///
21 | DirectInd = 0x01,
22 | ///
23 | /// Scannable undirected advertising event.
24 | ///
25 | ScanInd = 0x02,
26 | ///
27 | /// Non-connectable and non-scannable advertisement packets.
28 | ///
29 | NonConnInd = 0x03,
30 | ///
31 | /// Scan response.
32 | ///
33 | ScanRsp = 0x04
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015-2019 Wouter Huysentruit
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/WinBeaconException.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon.Stack
10 | {
11 | ///
12 | /// WinBeacon specific exception.
13 | ///
14 | public class WinBeaconException : Exception
15 | {
16 | ///
17 | /// Creates a new exception containing a formatted message.
18 | ///
19 | /// The message.
20 | public WinBeaconException(string message)
21 | : base(message)
22 | {
23 | }
24 |
25 | ///
26 | /// Creates a new exception containing a message and inner-exception.
27 | ///
28 | /// The message.
29 | /// The inner-exception.
30 | public WinBeaconException(string message, Exception innerException)
31 | : base(message, innerException)
32 | {
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/ITransport.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using WinBeacon.Stack.Hci;
9 |
10 | namespace WinBeacon.Stack
11 | {
12 | ///
13 | /// Interface for classes that enable a transport link with the Bluetooth dongle.
14 | ///
15 | internal interface ITransport : IDisposable
16 | {
17 | ///
18 | /// Open the transport link.
19 | ///
20 | /// True on success, false on failure.
21 | void Open();
22 |
23 | ///
24 | /// Close the transport link.
25 | ///
26 | void Close();
27 |
28 | ///
29 | /// Send data to the dongle.
30 | ///
31 | void Send(byte[] data, DataType dataType);
32 |
33 | ///
34 | /// Event triggered on data reception from the dongle.
35 | ///
36 | event EventHandler DataReceived;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/WinBeacon.Tests/Extensions/ComparableExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using NUnit.Framework;
9 |
10 | namespace WinBeacon.Tests.Extensions
11 | {
12 | [TestFixture]
13 | public class ComparableExtensionsTests
14 | {
15 | [Test]
16 | public void Comparable_Equal()
17 | {
18 | Assert.IsNull("abc".NullableCompareTo("abc"), "Should return null when both items are equal");
19 | }
20 |
21 | [Test]
22 | public void Coparable_Bigger()
23 | {
24 | var result = (10).NullableCompareTo(2);
25 | Assert.NotNull(result);
26 | Assert.Greater(result.Value, 0, "Should return a value greater than zero");
27 | }
28 |
29 | [Test]
30 | public void Coparable_Smaller()
31 | {
32 | var result = (55.9).NullableCompareTo(56.0);
33 | Assert.NotNull(result);
34 | Assert.Less(result.Value, 0, "Should return a value greater than zero");
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/DataReceivedEventArgs.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon.Stack.Hci
10 | {
11 | ///
12 | /// Event arguments for the DataReceived event.
13 | ///
14 | internal class DataReceivedEventArgs : EventArgs
15 | {
16 | ///
17 | /// The received data.
18 | ///
19 | public byte[] Data { get; private set; }
20 | ///
21 | /// The type of the received data.
22 | ///
23 | public DataType DataType { get; private set; }
24 |
25 | ///
26 | /// Constructs a new DataReceivedEventArgs instance.
27 | ///
28 | /// The received data.
29 | /// The type of the received data.
30 | public DataReceivedEventArgs(byte[] data, DataType dataType)
31 | {
32 | Data = data;
33 | DataType = dataType;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Transports/UsbHciEndpointInfo.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Transport
8 | {
9 | ///
10 | /// USB endpoint info specific for Bluetooth usage.
11 | ///
12 | internal class UsbBluetoothEndpointInfo
13 | {
14 | ///
15 | /// The Bluetooth endpoint type.
16 | ///
17 | public UsbBluetoothEndpointType Type { get; private set; }
18 |
19 | ///
20 | /// The USB endpoint identifier.
21 | ///
22 | public byte Id { get; private set; }
23 |
24 | ///
25 | /// Creates a new UsbBluetoothEndpoint instance.
26 | ///
27 | /// The Bluetooth endpoint type.
28 | /// The USB endpoint identifier.
29 | public UsbBluetoothEndpointInfo(UsbBluetoothEndpointType type, byte id)
30 | {
31 | Type = type;
32 | Id = id;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/GlobalAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyCompany("Huysentruit")]
6 | [assembly: AssemblyProduct("WinBeacon")]
7 | [assembly: AssemblyCopyright("Copyright © 2015 Huysentruit")]
8 | [assembly: AssemblyTrademark("")]
9 | [assembly: AssemblyCulture("")]
10 |
11 | // Setting ComVisible to false makes the types in this assembly not visible
12 | // to COM components. If you need to access a type in this assembly from
13 | // COM, set the ComVisible attribute to true on that type.
14 | [assembly: ComVisible(false)]
15 |
16 | // Version information for an assembly consists of the following four values:
17 | //
18 | // Major Version
19 | // Minor Version
20 | // Build Number
21 | // Revision
22 | //
23 | // You can specify all the values or you can default the Build and Revision Numbers
24 | // by using the '*' as shown below:
25 | // [assembly: AssemblyVersion("1.0.*")]
26 | [assembly: AssemblyVersion("1.0.0.0")]
27 | [assembly: AssemblyFileVersion("1.0.0.0")]
28 |
29 | [assembly: InternalsVisibleTo("WinBeacon.Tests")]
30 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // For Moq
--------------------------------------------------------------------------------
/samples/SampleConsole/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using WinBeacon;
3 |
4 | namespace SampleConsole
5 | {
6 | class Program
7 | {
8 | static void Main(string[] args)
9 | {
10 | using (var hub = new Hub(0x0A5C, 0x21E8))
11 | {
12 | hub.BeaconDetected += (sender, e) => Console.WriteLine(e.Beacon.ToString());
13 |
14 | hub.EddystoneDetected += (sender, e) =>
15 | {
16 | switch (e.Eddystone)
17 | {
18 | case EddystoneUid eddystoneUid:
19 | Console.WriteLine($"Eddystone UID: {eddystoneUid}");
20 | break;
21 | case EddystoneUrl eddystoneUrl:
22 | Console.WriteLine($"Eddystone URL: {eddystoneUrl}");
23 | break;
24 | }
25 | };
26 |
27 | hub.EnableAdvertising(new Beacon("B9407F30-F5F8-466E-AFF9-25556B57FE6D", 1000, 2000, -52), TimeSpan.FromMilliseconds(200));
28 |
29 | Console.ReadKey();
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/DeviceAddress.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon.Stack
10 | {
11 | ///
12 | /// Class that holds a 48-bit device address.
13 | ///
14 | public class DeviceAddress
15 | {
16 | ///
17 | /// The device address.
18 | ///
19 | public byte[] Address { get; private set; }
20 |
21 | ///
22 | /// Empty device address.
23 | ///
24 | public static DeviceAddress Empty { get; } = new DeviceAddress(new byte[6]);
25 |
26 | ///
27 | /// Creates a new instance.
28 | ///
29 | /// The 6 address bytes
30 | public DeviceAddress(byte[] address)
31 | {
32 | if (address == null)
33 | throw new ArgumentNullException(nameof(address));
34 | if (address.Length != 6)
35 | throw new ArgumentOutOfRangeException("address", "Invalid length, should contain 6 bytes");
36 | Address = address;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/WinBeacon/WinBeacon.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45;net471;netstandard2.0
5 | true
6 | WinBeacon
7 | 1.0.0.0
8 | WinBeacon
9 | Wouter Huysentruit
10 | WinBeacon
11 | WinBeacon library for detecting and advertising of Bluetooth beacons
12 | https://github.com/huysentruitw/win-beacon
13 | false
14 | MIT
15 | https://github.com/huysentruitw/win-beacon
16 |
17 |
18 |
19 | true
20 |
21 | bin\WinBeacon.xml
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/WinBeacon.Stack.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45;net471;netstandard2.0
5 | true
6 | WinBeacon.Stack
7 | 1.0.0.0
8 | WinBeacon.Stack
9 | Wouter Huysentruit
10 | WinBeacon
11 | WinBeacon library for detecting and advertising of Bluetooth beacons
12 | https://github.com/huysentruitw/win-beacon
13 | false
14 | MIT
15 | https://github.com/huysentruitw/win-beacon
16 |
17 |
18 |
19 | true
20 |
21 | bin\WinBeacon.Stack.xml
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Events/CommandCompleteEvent.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 |
10 | namespace WinBeacon.Stack.Hci.Events
11 | {
12 | ///
13 | /// Event that occurs when a command has been executed.
14 | ///
15 | internal class CommandCompleteEvent : Event
16 | {
17 | public int NumberOfCommandsAllowedToSend { get; private set; }
18 | public ushort CommandOpcode { get; private set; }
19 | public byte CommandParameterDataLength { get; private set; }
20 | public byte[] ResultData { get; private set; }
21 |
22 | public static CommandCompleteEvent Parse(EventCode code, Queue data)
23 | {
24 | if (data.Count < 4)
25 | return null;
26 | return new CommandCompleteEvent
27 | {
28 | Code = code,
29 | NumberOfCommandsAllowedToSend = data.Dequeue(),
30 | CommandOpcode = (ushort)(data.Dequeue() + (data.Dequeue() << 8)),
31 | CommandParameterDataLength = data.Dequeue(),
32 | ResultData = data.DequeueAll()
33 | };
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeSetScanParametersCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Diagnostics;
9 | using WinBeacon.Stack.Hci.Opcodes;
10 | using WinBeacon.Stack.Hci.Parameters;
11 |
12 | namespace WinBeacon.Stack.Hci.Commands
13 | {
14 | internal class LeSetScanParametersCommand : Command
15 | {
16 | public LeSetScanParametersCommand(bool activeScanning, ushort intervalInMs, ushort windowInMs, bool useRandomAddress, bool onlyAcceptWhitelistedAdvertisers)
17 | : base(OpcodeGroup.LeController, (int)LeControllerOpcode.SetScanParameters)
18 | {
19 | ushort intervalCode = (ushort)Math.Ceiling(intervalInMs / 0.625);
20 | ushort windowCode = (ushort)Math.Ceiling(windowInMs / 0.625);
21 | Debug.Assert(intervalCode >= 0x0004);
22 | Debug.Assert(intervalCode <= 0x4000);
23 | Debug.Assert(windowCode >= 0x0004);
24 | Debug.Assert(windowCode <= 0x4000);
25 | Parameters.Add(new BoolCommandParameter(activeScanning));
26 | Parameters.Add(new UshortCommandParameter(intervalCode));
27 | Parameters.Add(new UshortCommandParameter(windowCode));
28 | Parameters.Add(new BoolCommandParameter(useRandomAddress));
29 | Parameters.Add(new BoolCommandParameter(onlyAcceptWhitelistedAdvertisers));
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Hci/ParameterTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using NUnit.Framework;
8 | using WinBeacon.Stack.Hci.Parameters;
9 |
10 | namespace WinBeacon.Stack.Tests.Hci
11 | {
12 | [TestFixture]
13 | public class ParameterTests
14 | {
15 | [Test]
16 | public void Parameter_BoolCommandParameter()
17 | {
18 | var parameter = new BoolCommandParameter(false);
19 | Assert.AreEqual(new byte[] { 0x00 }, parameter.ToByteArray());
20 | parameter = new BoolCommandParameter(true);
21 | Assert.AreEqual(new byte[] { 0x01 }, parameter.ToByteArray());
22 | }
23 |
24 | [Test]
25 | public void Parameter_ByteArrayCommandParameter()
26 | {
27 | var data = new byte[] { 0x01, 0xAA, 0x55 };
28 | var parameter = new ByteArrayCommandParameter(data);
29 | Assert.AreEqual(data, parameter.ToByteArray());
30 | }
31 |
32 | [Test]
33 | public void Parameter_ByteCommandParameter()
34 | {
35 | var parameter = new ByteCommandParameter(0x57);
36 | Assert.AreEqual(new byte[] { 0x57 }, parameter.ToByteArray());
37 | }
38 |
39 | [Test]
40 | public void Parameter_UshortCommandParameter()
41 | {
42 | var parameter = new UshortCommandParameter(0x5865);
43 | Assert.AreEqual(new byte[] { 0x65, 0x58 }, parameter.ToByteArray());
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Controllers/LibUsbLeControllerTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Threading;
8 | using NUnit.Framework;
9 | using WinBeacon.Stack.Controllers;
10 |
11 | namespace WinBeacon.Stack.Tests.Controllers
12 | {
13 | [TestFixture]
14 | public class LibUsbLeControllerTests
15 | {
16 | private const int vid = 0x050D;
17 | private const int pid = 0x065A;
18 |
19 | ///
20 | /// This is a live test that needs some pre-requisites:
21 | /// * A BLE compatible dongle with WinUSB driver
22 | /// * The correct vid and pid combination in the consts above
23 | /// * A beacon that broadcasts at minimum 2Hz rate (iPad users can use the BLEBeacon app to advertise as a beacon)
24 | ///
25 | [Test]
26 | [Ignore("Can only be used with a real device")]
27 | public void LibUsbLeController_WaitForLeMetaEvent()
28 | {
29 | var leMetaEventReceived = false;
30 | using (var controller = new LibUsbLeController(vid, pid))
31 | {
32 | controller.Open();
33 | controller.EnableScanning();
34 | controller.LeMetaEventReceived += (sender, e) =>
35 | {
36 | if (e.LeMetaEvent.Code == Stack.Hci.EventCode.LeMeta)
37 | leMetaEventReceived = true;
38 | };
39 | Thread.Sleep(1 * 1000);
40 | }
41 | Assert.IsTrue(leMetaEventReceived, "No meta event received");
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Event.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Collections.Generic;
8 | using WinBeacon.Stack.Hci.Events;
9 |
10 | namespace WinBeacon.Stack.Hci
11 | {
12 | ///
13 | /// Base class for Bluetooth events.
14 | ///
15 | public class Event
16 | {
17 | ///
18 | /// The event code.
19 | ///
20 | public EventCode Code { get; protected set; }
21 |
22 | ///
23 | /// Creates an event object.
24 | ///
25 | protected Event()
26 | {
27 | }
28 |
29 | ///
30 | /// Creates an event object with event code.
31 | ///
32 | protected Event(EventCode code)
33 | {
34 | Code = code;
35 | }
36 |
37 | internal static Event Parse(byte[] data)
38 | {
39 | return Parse(new Queue(data));
40 | }
41 |
42 | internal static Event Parse(Queue data)
43 | {
44 | if (data.Count < 2)
45 | return null;
46 | EventCode code = (EventCode)data.Dequeue();
47 | int payloadSize = data.Dequeue();
48 | switch (code)
49 | {
50 | case EventCode.CommandComplete:
51 | return CommandCompleteEvent.Parse(code, data);
52 | case EventCode.LeMeta:
53 | return LeMetaEvent.Parse(code, data);
54 | default:
55 | return new Event(code);
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/ILeController.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 |
9 | namespace WinBeacon.Stack
10 | {
11 | ///
12 | /// Interface for Bluetooth Low Energy controller classes.
13 | ///
14 | public interface ILeController : IDisposable
15 | {
16 | ///
17 | /// Open underlying transport connection.
18 | ///
19 | void Open();
20 |
21 | ///
22 | /// Close underlying transport connection.
23 | ///
24 | void Close();
25 |
26 | ///
27 | /// Enable Low Energy device scanning.
28 | ///
29 | void EnableScanning();
30 |
31 | ///
32 | /// Disable Low Energy device scanning.
33 | ///
34 | void DisableScanning();
35 |
36 | ///
37 | /// Enable Low Energy advertising.
38 | ///
39 | /// The advertisement data.
40 | /// Interval should be between 20 and 10240 ms. Defaults to 1280 ms.
41 | void EnableAdvertising(byte[] advertisementData, int advertisingIntervalInMs = 1280);
42 |
43 | ///
44 | /// Disable Low Energy advertising.
45 | ///
46 | void DisableAdvertising();
47 |
48 | ///
49 | /// Fired for each received Low Energy meta event.
50 | ///
51 | event EventHandler LeMetaEventReceived;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Extensions/QueueExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using NUnit.Framework;
10 |
11 | namespace WinBeacon.Stack.Tests.Extensions
12 | {
13 | [TestFixture]
14 | public class QueueExtensionsTests
15 | {
16 | [Test]
17 | public void QueueExtensions_DequeueAll()
18 | {
19 | var input = new byte[] { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 };
20 | var queue = new Queue(input);
21 | byte[] output = queue.DequeueAll();
22 | Assert.AreEqual(input, output);
23 | }
24 |
25 | [Test]
26 | public void QueueExtensions_Dequeue_Exact()
27 | {
28 | var input = new byte[] { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 };
29 | var queue = new Queue(input);
30 | byte[] output = queue.Dequeue(3);
31 | Assert.AreEqual(new byte[] { 0x12, 0x34, 0x56 }, output);
32 | }
33 |
34 | [Test]
35 | public void QueueExtensions_Dequeue_TooMuch()
36 | {
37 | var input = new byte[] { 0x9A, 0xBC, 0xDE, 0xF0 };
38 | var queue = new Queue(input);
39 |
40 | Assert.Throws(() => queue.Dequeue(6));
41 | }
42 |
43 | [Test]
44 | public void QueueExtensions_Enqueue()
45 | {
46 | var input = new byte[] { 0x9A, 0xBC, 0xDE, 0xF0 };
47 | var queue = new Queue();
48 | queue.Enqueue(input);
49 | Assert.AreEqual(input.Length, queue.Count);
50 | Assert.AreEqual(input, queue.DequeueAll());
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Extensions/QueueExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 |
10 | namespace WinBeacon.Stack
11 | {
12 | ///
13 | /// Queue extension methods.
14 | ///
15 | public static class QueueExtensions
16 | {
17 | ///
18 | /// Dequeue all items from the queue.
19 | ///
20 | /// Item type.
21 | /// The queue.
22 | /// Array of dequeued items.
23 | public static T[] DequeueAll(this Queue queue)
24 | {
25 | var result = new List();
26 | while (queue.Count > 0)
27 | result.Add(queue.Dequeue());
28 | return result.ToArray();
29 | }
30 |
31 | ///
32 | /// Dequeue the specified number of items.
33 | ///
34 | /// Item type.
35 | /// The queue.
36 | /// Number of items to dequeue.
37 | /// Array of dequeued items.
38 | public static T[] Dequeue(this Queue queue, int count)
39 | {
40 | var result = new List();
41 | for (int i = 0; i < count; i++)
42 | result.Add(queue.Dequeue());
43 | return result.ToArray();
44 | }
45 |
46 | ///
47 | /// Enqueue an array of items.
48 | ///
49 | /// Item type.
50 | /// The queue.
51 | /// The items.
52 | public static void Enqueue(this Queue queue, IEnumerable items)
53 | {
54 | foreach (T item in items)
55 | queue.Enqueue(item);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Transports/LibUsb/LibUsbDevice.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Collections.Generic;
8 | using LibUsbDotNet;
9 | using LibUsbDotNet.Info;
10 | using LibUsbDotNet.Main;
11 |
12 | namespace WinBeacon.Stack.Transports.LibUsb
13 | {
14 | ///
15 | /// LibUsbDotNet.UsbDevice wrapper that implements ILibUsbDevice.
16 | ///
17 | internal class LibUsbDevice : ILibUsbDevice
18 | {
19 | private UsbDevice _usbDevice;
20 |
21 | public int Vid { get; private set; }
22 | public int Pid { get; private set; }
23 |
24 | public LibUsbDevice(int vid, int pid)
25 | {
26 | Vid = vid;
27 | Pid = pid;
28 | }
29 |
30 | public void Open()
31 | {
32 | if (_usbDevice != null)
33 | return;
34 | _usbDevice = UsbDevice.OpenUsbDevice(new UsbDeviceFinder(Vid, Pid))
35 | ?? throw new WinBeaconException("USB device not found, check VID & PID");
36 | }
37 |
38 | public void Close()
39 | {
40 | if (_usbDevice == null)
41 | return;
42 | _usbDevice.Close();
43 | _usbDevice = null;
44 | }
45 |
46 | public IEnumerable Configs => _usbDevice.Configs;
47 |
48 | public UsbEndpointReader OpenEndpointReader(ReadEndpointID readEndpointID)
49 | {
50 | return _usbDevice.OpenEndpointReader(readEndpointID);
51 | }
52 |
53 | public UsbEndpointWriter OpenEndpointWriter(WriteEndpointID writeEndpointID)
54 | {
55 | return _usbDevice.OpenEndpointWriter(writeEndpointID);
56 | }
57 |
58 | public bool ControlTransfer(ref UsbSetupPacket setupPacket, object buffer, int bufferLength, out int lengthTransferred)
59 | {
60 | return _usbDevice.ControlTransfer(ref setupPacket, buffer, bufferLength, out lengthTransferred);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Command.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using WinBeacon.Stack.Hci.Events;
10 |
11 | namespace WinBeacon.Stack.Hci
12 | {
13 | internal class Command
14 | {
15 | public ushort Opcode { get; private set; }
16 | public List Parameters { get; private set; }
17 |
18 | internal Command(OpcodeGroup opcodeGroup, int opcodeCommand)
19 | {
20 | Opcode = (ushort)(opcodeCommand | ((byte)opcodeGroup << 10));
21 | Parameters = new List();
22 | }
23 |
24 | public byte[] ToByteArray()
25 | {
26 | var result = new List();
27 | result.Add(Opcode.LoByte());
28 | result.Add(Opcode.HiByte());
29 | result.Add(0); // Length placeholder
30 | Parameters.ForEach(param => result.AddRange(param.ToByteArray()));
31 | result[2] = (byte)(result.Count - 3);
32 | return result.ToArray();
33 | }
34 |
35 | internal Action CommandCompleteCallback = null;
36 |
37 | internal virtual void OnCommandComplete(CommandCompleteEvent e)
38 | {
39 | if (CommandCompleteCallback != null)
40 | CommandCompleteCallback(this, e);
41 | }
42 | }
43 |
44 | internal abstract class Command : Command
45 | {
46 | internal Command(OpcodeGroup opcodeGroup, int opcodeCommand)
47 | : base(opcodeGroup, opcodeCommand)
48 | {
49 | }
50 |
51 | internal abstract TResult ParseCommandResult(CommandCompleteEvent e);
52 |
53 | internal new Action CommandCompleteCallback = null;
54 |
55 | internal override void OnCommandComplete(CommandCompleteEvent e)
56 | {
57 | if (CommandCompleteCallback != null)
58 | CommandCompleteCallback(this, ParseCommandResult(e));
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Hci/EventTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using NUnit.Framework;
8 | using WinBeacon.Stack.Hci;
9 | using WinBeacon.Stack.Hci.Events;
10 |
11 | namespace WinBeacon.Stack.Tests.Hci
12 | {
13 | [TestFixture]
14 | public class EventTests
15 | {
16 | [Test]
17 | public void Event_NullEvent()
18 | {
19 | var e = Event.Parse(new byte[] { 0x12 });
20 | Assert.IsNull(e);
21 | }
22 |
23 | [Test]
24 | public void Event_SimpleEvent()
25 | {
26 | var e = Event.Parse(new byte[] { 0x12, 0x00 });
27 | Assert.AreEqual(EventCode.RoleChanged, e.Code);
28 | }
29 |
30 | [Test]
31 | public void Event_CommandCompleteEventParse()
32 | {
33 | var e = (Event.Parse(new byte[] { 0x0E, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70 }) as CommandCompleteEvent);
34 | Assert.AreEqual(EventCode.CommandComplete, e.Code);
35 | Assert.AreEqual(0x4030, e.CommandOpcode);
36 | Assert.AreEqual(0x50, e.CommandParameterDataLength);
37 | Assert.AreEqual(0x20, e.NumberOfCommandsAllowedToSend);
38 | Assert.AreEqual(new byte[] { 0x60, 0x70 }, e.ResultData);
39 | }
40 |
41 | [Test]
42 | public void Event_LeAdvertisingEventParse()
43 | {
44 | var e = (Event.Parse(new byte[] { 0x3E, 0x10, 0x02, 0x01, 0x02, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x03, 0xBB, 0xCC, 0xDD, 0xEE }) as LeMetaEvent);
45 | Assert.AreEqual(EventCode.LeMeta, e.Code);
46 | Assert.AreEqual(LeMetaEvent.LeMetaSubEvent.AdvertisingReport, e.SubEvent);
47 | Assert.AreEqual(1, e.AdvertisingEvents.Length);
48 | var ae = e.AdvertisingEvents[0];
49 | Assert.AreEqual(new byte[] { 0x99, 0x88, 0x77, 0x66, 0x55, 0x44 }, ae.Address);
50 | Assert.AreEqual(0x33, ae.AddressType);
51 | Assert.AreEqual(LeAdvertisingEventType.ScanInd, ae.EventType);
52 | Assert.AreEqual(new byte[] { 0xBB, 0xCC, 0xDD }, ae.Payload);
53 | Assert.AreEqual(-18, ae.Rssi);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Events/LeAdvertisingEvent.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Text;
11 |
12 | namespace WinBeacon.Stack.Hci.Events
13 | {
14 | ///
15 | /// Low energy advertising event.
16 | ///
17 | public class LeAdvertisingEvent
18 | {
19 | ///
20 | /// The event type.
21 | ///
22 | public LeAdvertisingEventType EventType { get; private set; }
23 | ///
24 | /// The address type.
25 | ///
26 | public byte AddressType { get; private set; }
27 | ///
28 | /// The address.
29 | ///
30 | public byte[] Address { get; private set; }
31 | ///
32 | /// The payload.
33 | ///
34 | public byte[] Payload { get; private set; }
35 | ///
36 | /// The RSSI.
37 | ///
38 | public sbyte Rssi { get; private set; }
39 |
40 | ///
41 | /// Creates a string representation of this object.
42 | ///
43 | /// String representation.
44 | public override string ToString()
45 | {
46 | var sb = new StringBuilder();
47 | sb.AppendFormat("EventType: {0}", EventType);
48 | sb.AppendFormat("AddressType: {0}", AddressType);
49 | sb.AppendFormat("Address: {0}", BitConverter.ToString(Address));
50 | sb.AppendFormat("Payload = {0}", BitConverter.ToString(Payload).Replace("-", ""));
51 | sb.AppendFormat("RSSI = {0}", Rssi);
52 | return sb.ToString();
53 | }
54 |
55 | internal static LeAdvertisingEvent Parse(Queue data)
56 | {
57 | return new LeAdvertisingEvent
58 | {
59 | EventType = (LeAdvertisingEventType)data.Dequeue(),
60 | AddressType = data.Dequeue(),
61 | Address = data.Dequeue(6).Reverse().ToArray(),
62 | Payload = data.Dequeue(data.Dequeue()),
63 | Rssi = (sbyte)data.Dequeue()
64 | };
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/WinBeacon/Extensions/BeaconExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Text.RegularExpressions;
11 |
12 | namespace WinBeacon
13 | {
14 | ///
15 | /// Beacon extension methods.
16 | ///
17 | public static class BeaconExtensions
18 | {
19 | ///
20 | /// Gets the calculated range in meters.
21 | ///
22 | /// The beacon instance.
23 | /// The calculated range in meters.
24 | public static double GetRange(this Beacon beacon)
25 | {
26 | double dbRatio = beacon.CalibratedTxPower - beacon.Rssi;
27 | double linearRatio = Math.Pow(10, dbRatio / 10);
28 | return Math.Sqrt(linearRatio);
29 | }
30 |
31 | ///
32 | /// Gets the calculated range in meters using a curve that approximates the iOS ranging.
33 | ///
34 | /// The beacon instance.
35 | /// The calculated range in meters.
36 | public static double GetApproximateIosRange(this Beacon beacon)
37 | {
38 | if (beacon.Rssi == 0)
39 | return -1;
40 | var ratio = (double)beacon.Rssi / (double)beacon.CalibratedTxPower;
41 | if (ratio < 1.0)
42 | return Math.Pow(ratio, 10);
43 | return 0.89979 * Math.Pow(ratio, 7.7095) + 0.111;
44 | }
45 |
46 | internal static string ToLittleEndianFormattedUuidString(this byte[] uuid)
47 | {
48 | var digits = new Queue(BitConverter.ToString(uuid).Replace("-", "").ToLower());
49 | return new string((from c in "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" select c == '-' ? '-' : digits.Dequeue()).ToArray());
50 | }
51 |
52 | internal static byte[] FromLittleEndianFormattedUuidString(this string uuid)
53 | {
54 | if (!Regex.IsMatch(uuid, @"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", RegexOptions.IgnoreCase))
55 | throw new FormatException("Incorrect uuid format");
56 | return (from Match m in Regex.Matches(uuid, @"[0-9a-f]{2}", RegexOptions.IgnoreCase) select Convert.ToByte(m.Value, 16)).ToArray();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Extensions/LibUsbDeviceExtensions.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Diagnostics;
10 | using System.Linq;
11 | using LibUsbDotNet;
12 | using LibUsbDotNet.Main;
13 | using WinBeacon.Stack.Transport;
14 | using WinBeacon.Stack.Transports.LibUsb;
15 |
16 | namespace WinBeacon.Stack
17 | {
18 | internal static class WinBeaconLibUsbExtensions
19 | {
20 | public static IEnumerable EnumerateBluetoothEndpointInfo(this ILibUsbDevice usbDevice)
21 | {
22 | var config0 = usbDevice.Configs.FirstOrDefault()
23 | ?? throw new WinBeaconException("USB device has no configurations");
24 |
25 | var interface0Info = config0.InterfaceInfoList.FirstOrDefault()
26 | ?? throw new WinBeaconException("USB configuration does not contain an interface");
27 |
28 | foreach (var endpointInfo in interface0Info.EndpointInfoList)
29 | {
30 | switch (endpointInfo.Descriptor.Attributes & 0x03)
31 | {
32 | case 0x02:
33 | yield return new UsbBluetoothEndpointInfo(
34 | type: (endpointInfo.Descriptor.EndpointID & 0x80) == 0x80 ? UsbBluetoothEndpointType.AclDataIn : UsbBluetoothEndpointType.AclDataOut,
35 | id: endpointInfo.Descriptor.EndpointID);
36 | break;
37 | case 0x03:
38 | yield return new UsbBluetoothEndpointInfo(
39 | type: UsbBluetoothEndpointType.Events,
40 | id: endpointInfo.Descriptor.EndpointID);
41 | break;
42 | }
43 | }
44 | }
45 |
46 | public static void SubscribeForDataReceived(this UsbEndpointBase endpoint, Action handler)
47 | {
48 | Debug.Assert(endpoint is UsbEndpointReader);
49 | (endpoint as UsbEndpointReader).SubscribeForDataReceived(handler);
50 | }
51 |
52 | public static void SubscribeForDataReceived(this UsbEndpointReader endpointReader, Action handler)
53 | {
54 | endpointReader.DataReceived += (sender, e) =>
55 | {
56 | var data = new byte[e.Count];
57 | if (e.Count > 0)
58 | Buffer.BlockCopy(e.Buffer, 0, data, 0, e.Count);
59 | handler(data);
60 | };
61 | endpointReader.DataReceivedEnabled = true;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Events/LeMetaEvent.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Collections.Generic;
8 | using System.Text;
9 |
10 | namespace WinBeacon.Stack.Hci.Events
11 | {
12 | ///
13 | /// Low energy meta event.
14 | ///
15 | public class LeMetaEvent : Event
16 | {
17 | ///
18 | /// Low energy meta sub event.
19 | ///
20 | public enum LeMetaSubEvent : byte
21 | {
22 | ///
23 | /// Connection complete event.
24 | ///
25 | ConnectionComplete = 0x01,
26 | ///
27 | /// Advertising report event.
28 | ///
29 | AdvertisingReport = 0x02
30 | }
31 |
32 | ///
33 | /// The sub event of this meta event.
34 | ///
35 | public LeMetaSubEvent SubEvent { get; private set; }
36 | ///
37 | /// Advertising events included in this meta event.
38 | ///
39 | public LeAdvertisingEvent[] AdvertisingEvents { get; private set; }
40 |
41 | ///
42 | /// Creates a string representation of this object.
43 | ///
44 | /// String representation.
45 | public override string ToString()
46 | {
47 | var sb = new StringBuilder();
48 | sb.AppendFormat("SubEvent = {0}", SubEvent);
49 | if (AdvertisingEvents != null)
50 | foreach (var e in AdvertisingEvents)
51 | sb.AppendFormat(", {0}", e);
52 | return sb.ToString();
53 | }
54 |
55 | internal static LeMetaEvent Parse(EventCode code, Queue data)
56 | {
57 | var subEvent = (LeMetaSubEvent)data.Dequeue();
58 | LeAdvertisingEvent[] events = null;
59 | switch (subEvent)
60 | {
61 | case LeMetaSubEvent.ConnectionComplete:
62 | // Not implemented
63 | break;
64 | case LeMetaSubEvent.AdvertisingReport:
65 | events = ParseAdvertisingReport(data);
66 | break;
67 | }
68 | return new LeMetaEvent
69 | {
70 | Code = code,
71 | SubEvent = subEvent,
72 | AdvertisingEvents = events
73 | };
74 | }
75 |
76 | private static LeAdvertisingEvent[] ParseAdvertisingReport(Queue data)
77 | {
78 | int numberOfEvents = data.Dequeue();
79 | var events = new LeAdvertisingEvent[numberOfEvents];
80 | for (int i = 0; i < numberOfEvents; i++)
81 | events[i] = LeAdvertisingEvent.Parse(data);
82 | return events;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/ReadMe.md:
--------------------------------------------------------------------------------
1 | # WinBeacon
2 |
3 | [](https://ci.appveyor.com/project/huysentruitw/win-beacon/branch/master)
4 |
5 | ## Overview
6 |
7 | WinBeacon is a managed .NET (C#) library with a minimal Bluetooth LE Stack that is able to detect and act as iBeacon¹ and Eddystone. This stack doesn't support BLE devices, only the detection and transmission of BLE advertisement packets used by beacons.
8 |
9 | ## Supported operating systems
10 |
11 | * Windows XP (not tested, but it should work)
12 | * Windows 7
13 | * Windows 8
14 | * Windows 10*
15 |
16 | \* For Windows 10, you should be able to use [BluetoothLEAdvertisementWatcher](https://msdn.microsoft.com/en-us/library/windows.devices.bluetooth.advertisement.bluetoothleadvertisementwatcher.aspx) instead of this library.
17 |
18 | ## Supported BT4.0 LE dongles
19 |
20 | | Manufacturer | Product | Chipset | VID / PID | Compatible |
21 | | ------------ |:------- |:------- |:--------- |:---------- |
22 | | ASUS | USB-BT400 | BCM20702A0 | VID_0B05 PID_17CB | Yes |
23 | | Belkin | Mini Bluetooth 4.0 Adapter Class 2.10M | BCM20702A0 | VID_050D PID_065A | Yes |
24 | | Pluggable | USB Bluetooth 4.0 Low Energy Micro Adapter | BCM20702A0 | VID_0A5C PID_21E8 | Yes |
25 | | CSR | USB Bluetooth 4.0 | CSR8510 | VID_0A12 PID_0001 | Yes |
26 |
27 | If anyone can test with other BT4.0 dongle types, please let me know how it works out or send us a pull request.
28 |
29 | ## Installation
30 |
31 | This library needs raw USB access to a BT4.0 dongle. Therefore you should replace the original driver of the dongle with a WinUSB driver.
32 | This also means that the default Bluetooth stack is no longer used and Windows will no longer detect the dongle as a Bluetooth dongle until you re-install the original drivers.
33 |
34 | To replace or create a WinUSB driver for the BT4.0 dongle, we advise you to use the [Zadig tool](http://zadig.akeo.ie/).
35 |
36 | ## Get it on NuGet
37 |
38 | Install-Package WinBeacon
39 |
40 | ## Usage
41 |
42 | ### Detecting beacons
43 |
44 | ```C#
45 | using (var hub = new Hub(0x050D, 0x065A))
46 | {
47 | hub.BeaconDetected += (sender, e) =>
48 | {
49 | Console.WriteLine("Detected beacon: {0}", e.Beacon);
50 | };
51 | Console.ReadKey();
52 | }
53 | ```
54 |
55 | ### Advertise as a beacon
56 |
57 | ```C#
58 | using (var hub = new Hub(0x050D, 0x065A))
59 | {
60 | hub.EnableAdvertising(new Beacon("B9407F30-F5F8-466E-AFF9-25556B57FE6D", 1000, 2000, -52));
61 | Console.ReadKey();
62 | }
63 | ```
64 |
65 | ### Detecting Eddystone
66 |
67 | Currently only the detection of Eddystone UID and URL is supported.
68 |
69 | ```C#
70 | using (var hub = new Hub(0x050D, 0x065A))
71 | {
72 | hub.EddystoneDetected += (sender, e) =>
73 | {
74 | switch (e.Eddystone)
75 | {
76 | case EddystoneUid eddystoneUid:
77 | Console.WriteLine($"Eddystone UID: {eddystoneUid}");
78 | break;
79 | case EddystoneUrl eddystoneUrl:
80 | Console.WriteLine($"Eddystone URL: {eddystoneUrl}");
81 | break;
82 | }
83 | };
84 | Console.ReadKey();
85 | }
86 | ```
87 |
88 | ¹ iBeacon is a trademark of Apple inc.
89 |
--------------------------------------------------------------------------------
/test/WinBeacon.Tests/BeaconTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Collections.Generic;
8 | using NUnit.Framework;
9 | using WinBeacon.Stack.Hci.Events;
10 |
11 | namespace WinBeacon.Tests
12 | {
13 | [TestFixture]
14 | public class BeaconTests
15 | {
16 | private Beacon beacon;
17 |
18 | [SetUp]
19 | public void Setup()
20 | {
21 | beacon = new Beacon("4fe5d5f6-abce-ddfe-1587-123d1a4b567f", 1234, 5678, -48, 0xAABB) { Rssi = -52 };
22 | }
23 |
24 | [Test]
25 | public void Beacon_Constructor()
26 | {
27 | Assert.AreEqual("4fe5d5f6-abce-ddfe-1587-123d1a4b567f", beacon.Uuid);
28 | Assert.AreEqual(1234, beacon.Major);
29 | Assert.AreEqual(5678, beacon.Minor);
30 | Assert.AreEqual(-48, beacon.CalibratedTxPower);
31 | Assert.AreEqual(0xAABB, beacon.CompanyId);
32 | }
33 |
34 | [Test]
35 | public void Beacon_ToAdvertisingData()
36 | {
37 | var data = new byte[] {
38 | 0x02, 0x01, 0x1A, 0x1A, 0xFF, 0xAA, 0xBB, 0x02, 0x15, 0x4F, 0xE5, 0xD5, 0xF6, 0xAB, 0xCE,
39 | 0xDD, 0xFE, 0x15, 0x87, 0x12, 0x3D, 0x1A, 0x4B, 0x56, 0x7F, 0x04, 0xD2, 0x16, 0x2E, 0xD0,
40 | 0x00
41 | };
42 | Assert.AreEqual(data, beacon.ToAdvertisingData());
43 | }
44 |
45 | [Test]
46 | public void Beacon_ParseDataWithFlags()
47 | {
48 | var data = new byte[] {
49 | 0x00, 0x00, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12, 0x1E, 0x02, 0x01, 0x1A, 0x1A, 0xFF, 0xAA,
50 | 0xBB, 0x02, 0x15, 0x4F, 0xE5, 0xD5, 0xF6, 0xAB, 0xCE, 0xDD, 0xFE, 0x15, 0x87, 0x12, 0x3D,
51 | 0x1A, 0x4B, 0x56, 0x7F, 0x04, 0xD2, 0x16, 0x2E, 0xD0, 0xCC
52 | };
53 | var queue = new Queue(data);
54 | var beacon = Beacon.Parse(LeAdvertisingEvent.Parse(queue));
55 | Assert.AreEqual(new byte[] { 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC }, beacon.Address);
56 | Assert.AreEqual("4fe5d5f6-abce-ddfe-1587-123d1a4b567f", beacon.Uuid);
57 | Assert.AreEqual(1234, beacon.Major);
58 | Assert.AreEqual(5678, beacon.Minor);
59 | Assert.AreEqual(-48, beacon.CalibratedTxPower);
60 | Assert.AreEqual(0xAABB, beacon.CompanyId);
61 | Assert.AreEqual(-52, beacon.Rssi);
62 | Assert.IsFalse(beacon.IsAppleIBeacon);
63 | }
64 |
65 | [Test]
66 | public void Beacon_ParseDataWithoutFlags()
67 | {
68 | var data = new byte[] {
69 | 0x00, 0x00, 0x28, 0xEA, 0x6B, 0xB8, 0x5F, 0xD0, 0x1B, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15,
70 | 0xA4, 0x95, 0x00, 0x00, 0xC5, 0xB1, 0x4B, 0x44, 0xB5, 0x12, 0x13, 0x70, 0xF0, 0x2D, 0x74,
71 | 0xDE, 0x00, 0x02, 0x6D, 0x65, 0xC5, 0xBC
72 | };
73 | var queue = new Queue(data);
74 | var beacon = Beacon.Parse(LeAdvertisingEvent.Parse(queue));
75 | Assert.NotNull(beacon);
76 | Assert.AreEqual(0x4C00, beacon.CompanyId);
77 | Assert.AreEqual(-59, beacon.CalibratedTxPower);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/test/WinBeacon.Tests/EddystoneUidTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 | using WinBeacon.Stack.Hci.Events;
4 |
5 | namespace WinBeacon.Tests
6 | {
7 | [TestFixture]
8 | public class EddystoneUidTests
9 | {
10 | [Test]
11 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldNotBeNull()
12 | {
13 | // Act
14 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent);
15 |
16 | // Assert
17 | Assert.That(eddystone, Is.Not.Null);
18 | }
19 |
20 | [Test]
21 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldNotBeOfTypeEddystoneUid()
22 | {
23 | // Act
24 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent);
25 |
26 | // Assert
27 | Assert.That(eddystone, Is.InstanceOf());
28 | }
29 |
30 | [Test]
31 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldReturnAddress()
32 | {
33 | // Act
34 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent) as EddystoneUid;
35 |
36 | // Assert
37 | Assert.That(eddystone.Address, Is.EqualTo(new byte[] { 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }));
38 | }
39 |
40 | [Test]
41 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldReturnRssi()
42 | {
43 | // Act
44 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent) as EddystoneUid;
45 |
46 | // Assert
47 | Assert.That(eddystone.Rssi, Is.EqualTo(-52));
48 | }
49 |
50 | [Test]
51 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldReturnNamespace()
52 | {
53 | // Act
54 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent) as EddystoneUid;
55 |
56 | // Assert
57 | Assert.That(eddystone.Namespace, Is.EqualTo(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A }));
58 | }
59 |
60 | [Test]
61 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldReturnInstance()
62 | {
63 | // Act
64 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent) as EddystoneUid;
65 |
66 | // Assert
67 | Assert.That(eddystone.Instance, Is.EqualTo(new byte[] { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }));
68 | }
69 |
70 | [Test]
71 | public void Eddystone_Parse_EddystoneUidAdvertisingEvent_ShouldReturnCalibratedTxPower()
72 | {
73 | // Act
74 | var eddystone = Eddystone.Parse(EddystoneUidAdvertisingEvent) as EddystoneUid;
75 |
76 | // Assert
77 | Assert.That(eddystone.CalibratedTxPower, Is.EqualTo(-18));
78 | }
79 |
80 | private static readonly LeAdvertisingEvent EddystoneUidAdvertisingEvent = LeAdvertisingEvent.Parse(new Queue(new byte[]
81 | {
82 | 0x02, // Low Energy Advertising event type: Scan IND
83 | 0x00, // Address type
84 | 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, // Bluetooth MAC address
85 | 0x1F, // Payload length
86 | 0x02, 0x01, 0x06, // Flags data
87 | 0x03, 0x03, 0xAA, 0xFE, // Complete list of 16-bit Service UUID (including 16-bit Eddystone UUID)
88 | 0x17,
89 | 0x16, // Service Data data type value
90 | 0xAA, 0xFE, // 16-bit Eddystone UUID
91 | 0x00, // UID frame type
92 | 0xEE, // Calibrated TX power
93 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, // Namespace
94 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // Instance
95 | 0x00, 0x00, // Reserved for future use
96 | 0xCC, // RSSI
97 | }));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Transports/LibUsb/LibUsbTransportTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using LibUsbDotNet;
10 | using LibUsbDotNet.Info;
11 | using LibUsbDotNet.Main;
12 | using Moq;
13 | using NUnit.Framework;
14 | using WinBeacon.Stack.Hci;
15 | using WinBeacon.Stack.Transports.LibUsb;
16 |
17 | namespace WinBeacon.Stack.Tests.Transports.LibUsb
18 | {
19 | [TestFixture]
20 | public class LibUsbTransportTests
21 | {
22 | [Test]
23 | public void LibUsbTransport_Constructor()
24 | {
25 | using (var transport = new LibUsbTransport(new LibUsbDevice(0x1234, 0x5678)))
26 | {
27 | Assert.AreEqual(0x1234, transport.Vid);
28 | Assert.AreEqual(0x5678, transport.Pid);
29 | }
30 | }
31 |
32 | [Test]
33 | public void LibUsbTransport_OpenClose()
34 | {
35 | var usbDeviceMock = new Mock();
36 | var usbTransportMock = new Mock(usbDeviceMock.Object);
37 | usbDeviceMock.Verify(x => x.Open(), Times.Never);
38 | usbTransportMock.Verify(x => x.OpenEndpoints(), Times.Never);
39 |
40 | usbTransportMock.Object.Open();
41 | usbDeviceMock.Verify(x => x.Open(), Times.Once);
42 | usbTransportMock.Verify(x => x.OpenEndpoints(), Times.Once);
43 | usbDeviceMock.Verify(x => x.Close(), Times.Never);
44 | usbTransportMock.Verify(x => x.CloseEndpoints(), Times.Never);
45 |
46 | usbTransportMock.Object.Dispose();
47 | usbDeviceMock.Verify(x => x.Close(), Times.Once);
48 | usbTransportMock.Verify(x => x.CloseEndpoints(), Times.Once);
49 | }
50 |
51 | [Test]
52 | public void LibUsbTransport_Send()
53 | {
54 | var aclData = new byte[] { 0x12, 0x34, 0x56 };
55 | var commandData = new byte[] { 0x78, 0x9A, 0xBC };
56 | var usbDeviceMock = new Mock();
57 | var usbTransportMock = new Mock(usbDeviceMock.Object);
58 |
59 | usbTransportMock.Object.Send(aclData, DataType.Acl);
60 | usbTransportMock.Verify(x => x.SendCommand(It.IsAny()), Times.Never);
61 | usbTransportMock.Verify(x => x.SendAcl(It.Is(y => y.SequenceEqual(aclData))), Times.Once);
62 |
63 | usbTransportMock.Object.Send(commandData, DataType.Command);
64 | usbTransportMock.Verify(x => x.SendCommand(It.Is(y => y.SequenceEqual(commandData))), Times.Once);
65 | usbTransportMock.Verify(x => x.SendAcl(It.Is(y => y.SequenceEqual(aclData))), Times.Once);
66 | }
67 |
68 | [Test]
69 | public void LibUsbTransport_NoConfigurations()
70 | {
71 | var ex = Assert.Throws(() =>
72 | {
73 | var usbDeviceMock = new Mock();
74 | using (var transport = new LibUsbTransport(usbDeviceMock.Object))
75 | transport.Open();
76 | });
77 |
78 | Assert.That(ex.Message, Is.EqualTo("USB device has no configurations"));
79 | }
80 |
81 | #region Helpers
82 |
83 | internal abstract class TestLibUsbDevice : ILibUsbDevice
84 | {
85 | public abstract int Vid { get; }
86 | public abstract int Pid { get; }
87 | public abstract void Open();
88 | public abstract void Close();
89 |
90 | public abstract UsbEndpointReader OpenEndpointReader(ReadEndpointID readEndpointID);
91 | public abstract UsbEndpointWriter OpenEndpointWriter(WriteEndpointID writeEndpointID);
92 | public abstract bool ControlTransfer(ref UsbSetupPacket setupPacket, object buffer, int bufferLength, out int lengthTransferred);
93 |
94 | public IEnumerable Configs
95 | {
96 | get { return Enumerable.Empty(); }
97 | }
98 | }
99 |
100 | #endregion
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/WinBeacon.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29001.49
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinBeacon", "src\WinBeacon\WinBeacon.csproj", "{276A318E-8E7C-424B-BF6D-263E98BBADFD}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F5C4ADE2-B6BF-4A85-877E-2EBCF8DFDA48}"
9 | ProjectSection(SolutionItems) = preProject
10 | .nuget\NuGet.Config = .nuget\NuGet.Config
11 | .nuget\NuGet.exe = .nuget\NuGet.exe
12 | .nuget\NuGet.targets = .nuget\NuGet.targets
13 | EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{67875218-D5C5-4940-B7D2-A104093D4084}"
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8D7F4EC7-F536-4CA1-A1A3-C9B46757F5E8}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E6F37106-FB24-4548-B783-DF926AB82E4C}"
20 | ProjectSection(SolutionItems) = preProject
21 | LICENSE = LICENSE
22 | ReadMe.md = ReadMe.md
23 | EndProjectSection
24 | EndProject
25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinBeacon.Tests", "test\WinBeacon.Tests\WinBeacon.Tests.csproj", "{5B839047-8FD5-4DF9-BFDD-FDF4912FF5BE}"
26 | EndProject
27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinBeacon.Stack", "src\WinBeacon.Stack\WinBeacon.Stack.csproj", "{40BE88D6-A9CF-4F91-9AC1-9C0ED796CF3A}"
28 | EndProject
29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleConsole", "samples\SampleConsole\SampleConsole.csproj", "{8EDF3357-CD44-4DAA-B1BA-C22399CEA790}"
30 | EndProject
31 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E8343CD3-E188-4C61-B9E4-CFEFA08AF50C}"
32 | EndProject
33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinBeacon.Stack.Tests", "test\WinBeacon.Stack.Tests\WinBeacon.Stack.Tests.csproj", "{F1326609-5E73-4D68-9D32-4CB9CFF1A4CA}"
34 | EndProject
35 | Global
36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
37 | Debug|Any CPU = Debug|Any CPU
38 | Release|Any CPU = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
41 | {276A318E-8E7C-424B-BF6D-263E98BBADFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {276A318E-8E7C-424B-BF6D-263E98BBADFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {276A318E-8E7C-424B-BF6D-263E98BBADFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {276A318E-8E7C-424B-BF6D-263E98BBADFD}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {5B839047-8FD5-4DF9-BFDD-FDF4912FF5BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {5B839047-8FD5-4DF9-BFDD-FDF4912FF5BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {5B839047-8FD5-4DF9-BFDD-FDF4912FF5BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {5B839047-8FD5-4DF9-BFDD-FDF4912FF5BE}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {40BE88D6-A9CF-4F91-9AC1-9C0ED796CF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {40BE88D6-A9CF-4F91-9AC1-9C0ED796CF3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {40BE88D6-A9CF-4F91-9AC1-9C0ED796CF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {40BE88D6-A9CF-4F91-9AC1-9C0ED796CF3A}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {8EDF3357-CD44-4DAA-B1BA-C22399CEA790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {8EDF3357-CD44-4DAA-B1BA-C22399CEA790}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {8EDF3357-CD44-4DAA-B1BA-C22399CEA790}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {8EDF3357-CD44-4DAA-B1BA-C22399CEA790}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {F1326609-5E73-4D68-9D32-4CB9CFF1A4CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {F1326609-5E73-4D68-9D32-4CB9CFF1A4CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {F1326609-5E73-4D68-9D32-4CB9CFF1A4CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {F1326609-5E73-4D68-9D32-4CB9CFF1A4CA}.Release|Any CPU.Build.0 = Release|Any CPU
61 | EndGlobalSection
62 | GlobalSection(SolutionProperties) = preSolution
63 | HideSolutionNode = FALSE
64 | EndGlobalSection
65 | GlobalSection(NestedProjects) = preSolution
66 | {276A318E-8E7C-424B-BF6D-263E98BBADFD} = {67875218-D5C5-4940-B7D2-A104093D4084}
67 | {5B839047-8FD5-4DF9-BFDD-FDF4912FF5BE} = {8D7F4EC7-F536-4CA1-A1A3-C9B46757F5E8}
68 | {40BE88D6-A9CF-4F91-9AC1-9C0ED796CF3A} = {67875218-D5C5-4940-B7D2-A104093D4084}
69 | {8EDF3357-CD44-4DAA-B1BA-C22399CEA790} = {E8343CD3-E188-4C61-B9E4-CFEFA08AF50C}
70 | {F1326609-5E73-4D68-9D32-4CB9CFF1A4CA} = {8D7F4EC7-F536-4CA1-A1A3-C9B46757F5E8}
71 | EndGlobalSection
72 | GlobalSection(ExtensibilityGlobals) = postSolution
73 | SolutionGuid = {27350773-DADB-4EFA-A33E-5C2285875AE1}
74 | EndGlobalSection
75 | EndGlobal
76 |
--------------------------------------------------------------------------------
/test/WinBeacon.Tests/EddystoneUrlTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 | using WinBeacon.Stack.Hci.Events;
4 |
5 | namespace WinBeacon.Tests
6 | {
7 | [TestFixture]
8 | public class EddystoneUrlTests
9 | {
10 | [Test]
11 | public void Eddystone_Parse_EddystoneUrlAdvertisingEvent_ShouldNotBeNull()
12 | {
13 | // Act
14 | var eddystone = Eddystone.Parse(EddystoneUrlAdvertisingEvent());
15 |
16 | // Assert
17 | Assert.That(eddystone, Is.Not.Null);
18 | }
19 |
20 | [Test]
21 | public void Eddystone_Parse_EddystoneUrlAdvertisingEvent_ShouldNotBeOfTypeEddystoneUrl()
22 | {
23 | // Act
24 | var eddystone = Eddystone.Parse(EddystoneUrlAdvertisingEvent());
25 |
26 | // Assert
27 | Assert.That(eddystone, Is.InstanceOf());
28 | }
29 |
30 | [Test]
31 | public void Eddystone_Parse_EddystoneUrlAdvertisingEvent_ShouldReturnAddress()
32 | {
33 | // Act
34 | var eddystone = Eddystone.Parse(EddystoneUrlAdvertisingEvent()) as EddystoneUrl;
35 |
36 | // Assert
37 | Assert.That(eddystone.Address, Is.EqualTo(new byte[] { 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }));
38 | }
39 |
40 | [Test]
41 | public void Eddystone_Parse_EddystoneUrlAdvertisingEvent_ShouldReturnRssi()
42 | {
43 | // Act
44 | var eddystone = Eddystone.Parse(EddystoneUrlAdvertisingEvent()) as EddystoneUrl;
45 |
46 | // Assert
47 | Assert.That(eddystone.Rssi, Is.EqualTo(-52));
48 | }
49 |
50 | [Test]
51 | public void Eddystone_Parse_EddystoneUrlAdvertisingEvent_ShouldReturnDecodedUrl()
52 | {
53 | var theories = new (byte SchemePrefix, byte ExpansionText, string ExpectedUrl)[]
54 | {
55 | (SchemePrefix: 0x00, ExpansionText: 0x00, ExpectedUrl: "http://www.google.com/"),
56 | (SchemePrefix: 0x01, ExpansionText: 0x00, ExpectedUrl: "https://www.google.com/"),
57 | (SchemePrefix: 0x02, ExpansionText: 0x00, ExpectedUrl: "http://google.com/"),
58 | (SchemePrefix: 0x03, ExpansionText: 0x00, ExpectedUrl: "https://google.com/"),
59 |
60 | (SchemePrefix: 0x00, ExpansionText: 0x01, ExpectedUrl: "http://www.google.org/"),
61 | (SchemePrefix: 0x00, ExpansionText: 0x02, ExpectedUrl: "http://www.google.edu/"),
62 | (SchemePrefix: 0x00, ExpansionText: 0x03, ExpectedUrl: "http://www.google.net/"),
63 | (SchemePrefix: 0x00, ExpansionText: 0x04, ExpectedUrl: "http://www.google.info/"),
64 | (SchemePrefix: 0x00, ExpansionText: 0x05, ExpectedUrl: "http://www.google.biz/"),
65 | (SchemePrefix: 0x00, ExpansionText: 0x06, ExpectedUrl: "http://www.google.gov/"),
66 | (SchemePrefix: 0x00, ExpansionText: 0x07, ExpectedUrl: "http://www.google.com"),
67 | (SchemePrefix: 0x00, ExpansionText: 0x08, ExpectedUrl: "http://www.google.org"),
68 | (SchemePrefix: 0x00, ExpansionText: 0x09, ExpectedUrl: "http://www.google.edu"),
69 | (SchemePrefix: 0x00, ExpansionText: 0x0A, ExpectedUrl: "http://www.google.net"),
70 | (SchemePrefix: 0x00, ExpansionText: 0x0B, ExpectedUrl: "http://www.google.info"),
71 | (SchemePrefix: 0x00, ExpansionText: 0x0C, ExpectedUrl: "http://www.google.biz"),
72 | (SchemePrefix: 0x00, ExpansionText: 0x0D, ExpectedUrl: "http://www.google.gov"),
73 | };
74 |
75 | foreach (var theory in theories)
76 | {
77 | // Act
78 | var eddystone = Eddystone.Parse(EddystoneUrlAdvertisingEvent(theory.SchemePrefix, theory.ExpansionText)) as EddystoneUrl;
79 |
80 | // Assert
81 | Assert.That(eddystone.Url, Is.EqualTo(theory.ExpectedUrl));
82 | }
83 | }
84 |
85 | [Test]
86 | public void Eddystone_Parse_EddystoneUrlAdvertisingEvent_ShouldReturnCalibratedTxPower()
87 | {
88 | // Act
89 | var eddystone = Eddystone.Parse(EddystoneUrlAdvertisingEvent()) as EddystoneUrl;
90 |
91 | // Assert
92 | Assert.That(eddystone.CalibratedTxPower, Is.EqualTo(-18));
93 | }
94 |
95 | private static LeAdvertisingEvent EddystoneUrlAdvertisingEvent(byte schemePrefix = 0, byte expansionText = 0)
96 | => LeAdvertisingEvent.Parse(new Queue(new byte[]
97 | {
98 | 0x02, // Low Energy Advertising event type: Scan IND
99 | 0x00, // Address type
100 | 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, // Bluetooth MAC address
101 | 0x15, // Payload length
102 | 0x02, 0x01, 0x06, // Flags data
103 | 0x03, 0x03, 0xAA, 0xFE, // Complete list of 16-bit Service UUID (including 16-bit Eddystone UUID)
104 | 0x0D,
105 | 0x16, // Service Data data type value
106 | 0xAA, 0xFE, // 16-bit Eddystone UUID
107 | 0x10, // URL frame type
108 | 0xEE, // Calibrated TX power
109 | schemePrefix,
110 | 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, // google
111 | expansionText,
112 | 0xCC, // RSSI
113 | }));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/WinBeacon/Hub.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using WinBeacon.Stack;
9 | using WinBeacon.Stack.Controllers;
10 | using WinBeacon.Stack.Hci.Events;
11 |
12 | namespace WinBeacon
13 | {
14 | ///
15 | /// Hub for detecting beacons and advertise as a beacon.
16 | ///
17 | public class Hub : IDisposable
18 | {
19 | private ILeController _controller;
20 |
21 | ///
22 | /// Creates a hub instance that uses LibUsb or WinUSB as transport.
23 | ///
24 | /// The VID of the BT4.0 dongle.
25 | /// The PID of the BT4.0 dongle.
26 | public Hub(int usbVid, int usbPid)
27 | {
28 | _controller = new LibUsbLeController(usbVid, usbPid);
29 | _controller.LeMetaEventReceived += controller_LeMetaEventReceived;
30 | _controller.Open();
31 | _controller.EnableScanning();
32 | }
33 |
34 | ///
35 | /// Destructor.
36 | ///
37 | ~Hub()
38 | {
39 | Dispose();
40 | }
41 |
42 | ///
43 | /// Release used resources.
44 | ///
45 | public void Dispose()
46 | {
47 | if (_controller != null)
48 | {
49 | _controller.LeMetaEventReceived -= controller_LeMetaEventReceived;
50 | _controller.Dispose();
51 | }
52 |
53 | _controller = null;
54 | }
55 |
56 | ///
57 | /// Enable advertising as an Apple iBeacon.
58 | ///
59 | /// The Apple iBeacon to emulate.
60 | /// The advertising interval. Interval should be between 20 ms and 10.24 seconds. Defaults to 1.28 seconds.
61 | public void EnableAdvertising(Beacon beacon, TimeSpan? advertisingInterval = null)
62 | {
63 | if (_controller == null)
64 | throw new ObjectDisposedException("controller");
65 | if (beacon == null)
66 | throw new ArgumentNullException(nameof(beacon));
67 |
68 | if (advertisingInterval.HasValue)
69 | {
70 | if (advertisingInterval.Value.TotalMilliseconds < 20 || advertisingInterval.Value.TotalMilliseconds > 10240)
71 | throw new ArgumentOutOfRangeException("advertisingInterval", "Interval should be between 20 ms and 10.24 seconds");
72 | _controller.EnableAdvertising(beacon.ToAdvertisingData(), (ushort)advertisingInterval.Value.TotalMilliseconds);
73 | }
74 | else
75 | {
76 | _controller.EnableAdvertising(beacon.ToAdvertisingData());
77 | }
78 | }
79 |
80 | ///
81 | /// Enable advertising as an Eddystone.
82 | ///
83 | /// The Eddystone to emulate.
84 | /// The advertising interval. Interval should be between 20 ms and 10.24 seconds. Defaults to 1.28 seconds.
85 | public void EnableAdvertising(Eddystone eddystone, TimeSpan? advertisingInterval = null)
86 | {
87 | throw new NotImplementedException();
88 | }
89 |
90 | ///
91 | /// Disable advertising as a beacon.
92 | ///
93 | public void DisableAdvertising()
94 | {
95 | if (_controller == null)
96 | throw new ObjectDisposedException(nameof(_controller));
97 | _controller.DisableAdvertising();
98 | }
99 |
100 | ///
101 | /// Event fired when an Apple iBeacon is detected. This happens when the dongle receives an Apple iBeacon compatible advertising packet.
102 | ///
103 | public event EventHandler BeaconDetected;
104 |
105 | ///
106 | /// Event fired when an Eddystone is detected. This happens when the dongle receives an Eddystone compatible advertising packet.
107 | ///
108 | public event EventHandler EddystoneDetected;
109 |
110 | private void controller_LeMetaEventReceived(object sender, LeMetaEventReceivedEventArgs e)
111 | {
112 | if (e.LeMetaEvent == null)
113 | return;
114 | if (e.LeMetaEvent.SubEvent != LeMetaEvent.LeMetaSubEvent.AdvertisingReport)
115 | return;
116 | if (e.LeMetaEvent.AdvertisingEvents == null)
117 | return;
118 |
119 | foreach (var advertisingEvent in e.LeMetaEvent.AdvertisingEvents)
120 | {
121 | var beacon = Beacon.Parse(advertisingEvent);
122 | if (beacon != null)
123 | {
124 | BeaconDetected?.Invoke(this, new BeaconEventArgs(beacon));
125 | continue;
126 | }
127 |
128 | var eddystone = Eddystone.Parse(advertisingEvent);
129 | if (eddystone != null)
130 | {
131 | EddystoneDetected?.Invoke(this, new EddystoneEventArgs(eddystone));
132 | continue;
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Controllers/LeControllerTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Threading;
11 | using Moq;
12 | using NUnit.Framework;
13 | using WinBeacon.Stack.Controllers;
14 | using WinBeacon.Stack.Hci;
15 | using WinBeacon.Stack.Hci.Commands;
16 |
17 | namespace WinBeacon.Stack.Tests.Controllers
18 | {
19 | [TestFixture]
20 | public class LeControllerTests
21 | {
22 | [Test]
23 | public void LeController_OpenUnderlyingTransport()
24 | {
25 | var transportMock = new Mock();
26 | var controller = new LeController(transportMock.Object);
27 | controller.Open();
28 | transportMock.Verify(x => x.Open(), Times.Once());
29 | transportMock.Verify(x => x.Close(), Times.Never());
30 | }
31 |
32 | [Test]
33 | public void LeController_CloseUnderlyingTransport()
34 | {
35 | var transportMock = new Mock();
36 | var controller = new LeController(transportMock.Object);
37 | controller.Open();
38 | controller.Close();
39 | transportMock.Verify(x => x.Open(), Times.Once());
40 | transportMock.Verify(x => x.Close(), Times.Once());
41 | }
42 |
43 | [Test]
44 | public void LeController_Dispose()
45 | {
46 | var transportMock = new Mock();
47 | using (var controller = new LeController(transportMock.Object))
48 | controller.Open();
49 | transportMock.Verify(x => x.Open(), Times.Once());
50 | transportMock.Verify(x => x.Close(), Times.Once());
51 | }
52 |
53 | [Test]
54 | public void LeController_InitCommands()
55 | {
56 | var transportMock = new Mock { CallBase = true };
57 | using (var controller = new LeController(transportMock.Object))
58 | {
59 | controller.Open();
60 | Thread.Sleep(10);
61 | }
62 | transportMock.Verify(
63 | transport => transport.Send(
64 | It.Is(x => x.SequenceEqual(new ResetCommand().ToByteArray())),
65 | It.Is(x => x == DataType.Command)),
66 | Times.Exactly(1));
67 | transportMock.Verify(
68 | transport => transport.Send(It.IsAny(), It.Is(x => x == DataType.Command)),
69 | Times.Exactly(7));
70 | }
71 |
72 | [Test]
73 | public void LeController_DeviceAddressReceived()
74 | {
75 | var transportMock = new Mock { CallBase = true };
76 | transportMock
77 | .Setup(x => x.GetCommandCompletePayload(It.IsAny(), It.IsAny()))
78 | .Returns(new Func((data, dataType) =>
79 | {
80 | var readBdAddrData = new ReadBdAddrCommand().ToByteArray();
81 | if (!Enumerable.SequenceEqual(data, readBdAddrData))
82 | return null;
83 | return new byte[] { 0x60, 0x50, 0x40, 0x30, 0x20, 0x10 };
84 | }));
85 |
86 | using (var controller = new LeController(transportMock.Object))
87 | {
88 | DeviceAddress deviceAddress = null;
89 | var done = new ManualResetEvent(false);
90 | controller.DeviceAddressReceived += (sender, e) =>
91 | {
92 | deviceAddress = e.DeviceAddress;
93 | done.Set();
94 | };
95 |
96 | controller.Open();
97 | Assert.IsTrue(done.WaitOne(1000, false));
98 | Assert.AreEqual(new byte[] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, deviceAddress.Address);
99 | }
100 | }
101 |
102 | #region Helpers
103 |
104 | internal class FakeCommandTransport : ITransport
105 | {
106 | public virtual void Send(byte[] data, DataType dataType)
107 | {
108 | var payload = GetCommandCompletePayload(data, dataType);
109 | ushort opcode = (ushort)(data[0] + (data[1] << 8));
110 | var commandCompleteEvent = new List
111 | {
112 | (byte)EventCode.CommandComplete,
113 | (byte)(payload != null ? payload.Length : 0),
114 | 0x00, // NumberOfCommandsAllowedToSend
115 | data[0], // CommandOpcode lsb
116 | data[1], // CommandOpcode msb
117 | 0x00 // CommandParameterDataLength
118 | };
119 | if (payload != null)
120 | commandCompleteEvent.AddRange(payload);
121 | if (DataReceived != null)
122 | DataReceived(this, new DataReceivedEventArgs(commandCompleteEvent.ToArray(), DataType.Command));
123 | }
124 | public event EventHandler DataReceived;
125 | public virtual void Open() { }
126 | public virtual void Close() { }
127 | public virtual void Dispose() { }
128 | public virtual byte[] GetCommandCompletePayload(byte[] data, DataType dataType) { return null; }
129 | }
130 |
131 | #endregion
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/EventCode.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | namespace WinBeacon.Stack.Hci
8 | {
9 | ///
10 | /// Low Energy event codes
11 | ///
12 | public enum EventCode : byte
13 | {
14 | ///
15 | /// Inquiry complete.
16 | ///
17 | InquiryComplete = 0x01,
18 | ///
19 | /// Inquiry result.
20 | ///
21 | InquiryResult = 0x02,
22 | ///
23 | /// Connection complete.
24 | ///
25 | ConnectionComplete = 0x03,
26 | ///
27 | /// Connection request.
28 | ///
29 | ConnectionRequest = 0x04,
30 | ///
31 | /// Disconnection complete.
32 | ///
33 | DisconnectionComplete = 0x05,
34 | ///
35 | /// Authentication complete.
36 | ///
37 | AuthenticationComplete = 0x06,
38 | ///
39 | /// Remote name request complete.
40 | ///
41 | RemoteNameRequestComplete = 0x07,
42 | ///
43 | /// Encryption change.
44 | ///
45 | EncryptionChange = 0x08,
46 | ///
47 | /// Change connection link key complete.
48 | ///
49 | ChangeConnectionLinkKeyComplete = 0x09,
50 | ///
51 | /// Master link key complete.
52 | ///
53 | MasterLinkKeyComplete = 0x0A,
54 | ///
55 | /// Read remote supported features complete.
56 | ///
57 | ReadRemoteSupportedFeaturesComplete = 0x0B,
58 | ///
59 | /// Read remote version information complete.
60 | ///
61 | ReadRemoteVersionInformationComplete = 0x0C,
62 | ///
63 | /// Quality of service setup complete.
64 | ///
65 | QosSetupComplete = 0x0D,
66 | ///
67 | /// Command complete.
68 | ///
69 | CommandComplete = 0x0E,
70 | ///
71 | /// Command status.
72 | ///
73 | CommandStatus = 0x0F,
74 | ///
75 | /// Hardware error.
76 | ///
77 | HardwareError = 0x10,
78 | ///
79 | /// Flush occurred.
80 | ///
81 | FlushOccurred = 0x11,
82 | ///
83 | /// Role changed.
84 | ///
85 | RoleChanged = 0x12,
86 | ///
87 | /// Number of completed packets.
88 | ///
89 | NumberOfCompletedPackets = 0x13,
90 | ///
91 | /// Mode change.
92 | ///
93 | ModeChange = 0x14,
94 | ///
95 | /// Return link keys.
96 | ///
97 | ReturnLinkKeys = 0x15,
98 | ///
99 | /// Pin code request.
100 | ///
101 | PinCodeRequest = 0x16,
102 | ///
103 | /// Link key request.
104 | ///
105 | LinkKeyRequest = 0x17,
106 | ///
107 | /// Link key notification.
108 | ///
109 | LinkKeyNotification = 0x18,
110 | ///
111 | /// Loopback command.
112 | ///
113 | LoopbackCommand = 0x19,
114 | ///
115 | /// Data buffer overflow.
116 | ///
117 | DataBufferOverflow = 0x1A,
118 | ///
119 | /// Max slots change.
120 | ///
121 | MaxSlotsChange = 0x1B,
122 | ///
123 | /// Read clock offset complete.
124 | ///
125 | ReadClockOffsetComplete = 0x1C,
126 | ///
127 | /// Connection packet type changed.
128 | ///
129 | ConnectionPacketTypeChanged = 0x1D,
130 | ///
131 | /// Quality of service violation.
132 | ///
133 | QosViolation = 0x1E,
134 | ///
135 | /// Page scan mode change.
136 | ///
137 | PageScanModeChange = 0x1F,
138 | ///
139 | /// Page scan repetition mode change.
140 | ///
141 | PageScanRepetitionModeChange = 0x20,
142 | ///
143 | /// HCI flow specification complete.
144 | ///
145 | HciFlowSpecificationComplete = 0x21,
146 | ///
147 | /// Inquiry result with RSSI.
148 | ///
149 | InquiryResultWithRssi = 0x22,
150 | ///
151 | /// Read remote extended features complete.
152 | ///
153 | ReadRemoteExtendedFeaturesComplete = 0x23,
154 | ///
155 | /// Synchronous connection complete.
156 | ///
157 | SynchronousConnectionComplete = 0x2C,
158 | ///
159 | /// Synchronous connection changed.
160 | ///
161 | SynchronousConnectionChanged = 0x2D,
162 | ///
163 | /// Low energy meta event.
164 | ///
165 | LeMeta = 0x3E,
166 | ///
167 | /// Reserved for Bluetooth logo testing.
168 | ///
169 | ReservedBluetoothLogoTesting = 0xFE,
170 | ///
171 | /// Vendor specific events.
172 | ///
173 | VendorSpecificEvents = 0xFF
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Transports/LibUsb/LibUsbTransport.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using LibUsbDotNet;
11 | using LibUsbDotNet.Main;
12 | using WinBeacon.Stack.Hci;
13 | using WinBeacon.Stack.Transport;
14 |
15 | namespace WinBeacon.Stack.Transports.LibUsb
16 | {
17 | internal class LibUsbTransport : ITransport
18 | {
19 | private static readonly TimeSpan writeTimeout = TimeSpan.FromSeconds(5);
20 |
21 | private readonly ILibUsbDevice _usbDevice;
22 | private readonly Dictionary _endpoints = new Dictionary();
23 |
24 | internal LibUsbTransport(ILibUsbDevice usbDevice)
25 | {
26 | _usbDevice = usbDevice ?? throw new ArgumentNullException(nameof(usbDevice));
27 | }
28 |
29 | ~LibUsbTransport()
30 | {
31 | Dispose();
32 | }
33 |
34 | #region ITransport implementation
35 |
36 | public void Dispose()
37 | {
38 | Close();
39 | }
40 |
41 | public void Open()
42 | {
43 | _usbDevice.Open();
44 | OpenEndpoints();
45 | }
46 |
47 | public void Close()
48 | {
49 | CloseEndpoints();
50 | _usbDevice.Close();
51 | }
52 |
53 | public void Send(byte[] data, DataType dataType)
54 | {
55 | if (_usbDevice == null)
56 | throw new WinBeaconException("Transport not open");
57 | if (data == null)
58 | throw new ArgumentNullException("data");
59 | switch (dataType)
60 | {
61 | case DataType.Command:
62 | SendCommand(data);
63 | break;
64 | case DataType.Acl:
65 | SendAcl(data);
66 | break;
67 | default:
68 | throw new ArgumentException("Invalid dataType");
69 | }
70 | }
71 |
72 | public event EventHandler DataReceived;
73 | private void OnDataReceived(byte[] data, DataType dataType)
74 | {
75 | if (DataReceived != null)
76 | DataReceived(this, new DataReceivedEventArgs(data, dataType));
77 | }
78 |
79 | #endregion
80 |
81 | public int Vid => _usbDevice.Vid;
82 |
83 | public int Pid => _usbDevice.Pid;
84 |
85 | private Dictionary GetBluetoothEndpointIds()
86 | {
87 | var endpointIds = new Dictionary
88 | {
89 | { UsbBluetoothEndpointType.Commands, 0x00 },
90 | { UsbBluetoothEndpointType.Events, 0x81 },
91 | { UsbBluetoothEndpointType.AclDataOut, 0x02 },
92 | { UsbBluetoothEndpointType.AclDataIn, 0x82 }
93 | };
94 | foreach (var info in _usbDevice.EnumerateBluetoothEndpointInfo())
95 | endpointIds[info.Type] = info.Id;
96 | return endpointIds;
97 | }
98 |
99 | internal virtual void OpenEndpoints()
100 | {
101 | var endpointIds = GetBluetoothEndpointIds();
102 | _endpoints[UsbBluetoothEndpointType.Commands] = _usbDevice.OpenEndpointWriter((WriteEndpointID)endpointIds[UsbBluetoothEndpointType.Commands]);
103 | _endpoints[UsbBluetoothEndpointType.Events] = _usbDevice.OpenEndpointReader((ReadEndpointID)endpointIds[UsbBluetoothEndpointType.Events]);
104 | _endpoints[UsbBluetoothEndpointType.AclDataOut] = _usbDevice.OpenEndpointWriter((WriteEndpointID)endpointIds[UsbBluetoothEndpointType.AclDataOut]);
105 | _endpoints[UsbBluetoothEndpointType.AclDataIn] = _usbDevice.OpenEndpointReader((ReadEndpointID)endpointIds[UsbBluetoothEndpointType.AclDataIn]);
106 | var failedEndpointTypes = from x in _endpoints where x.Value == null select x.Key;
107 | if (failedEndpointTypes.Count() > 0)
108 | throw new WinBeaconException($"Failed to open endpoint(s): {string.Join(" ", failedEndpointTypes)}");
109 | _endpoints[UsbBluetoothEndpointType.Events].SubscribeForDataReceived(data => OnDataReceived(data, DataType.Command));
110 | _endpoints[UsbBluetoothEndpointType.AclDataIn].SubscribeForDataReceived(data => OnDataReceived(data, DataType.Acl));
111 | }
112 |
113 | internal virtual void CloseEndpoints()
114 | {
115 | var openEndpoints = from x in _endpoints where x.Value != null select x.Value;
116 | foreach (var endpoint in openEndpoints)
117 | {
118 | if (endpoint is UsbEndpointReader)
119 | (endpoint as UsbEndpointReader).DataReceivedEnabled = false;
120 | endpoint.Dispose();
121 | }
122 |
123 | _endpoints.Clear();
124 | }
125 |
126 | internal virtual void SendCommand(byte[] data)
127 | {
128 | var setupPacket = new UsbSetupPacket((byte)UsbRequestType.TypeClass | (byte)UsbRequestRecipient.RecipInterface, 0, 0, 0, (short)data.Length);
129 | if (!_usbDevice.ControlTransfer(ref setupPacket, data, data.Length, out int lengthTransferred))
130 | throw new WinBeaconException("USB ControlTransfer failed");
131 | if (lengthTransferred != data.Length)
132 | throw new WinBeaconException($"USB ControlTransfer didn't send all bytes. Sent {lengthTransferred} out of {data.Length} bytes");
133 | }
134 |
135 | internal virtual void SendAcl(byte[] data)
136 | {
137 | var endpoint = _endpoints[UsbBluetoothEndpointType.AclDataOut] as UsbEndpointWriter;
138 | var errorCode = endpoint.Write(data, (int)writeTimeout.TotalMilliseconds, out int transferLength);
139 | if (errorCode != ErrorCode.Ok)
140 | throw new WinBeaconException($"USB write operation failed with error code: {errorCode}");
141 | if (transferLength != data.Length)
142 | throw new WinBeaconException($"USB write operation didn't send all bytes. Sent {transferLength} out of {data.Length} bytes");
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/WinBeacon/Beacon.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using WinBeacon.Stack;
10 | using WinBeacon.Stack.Hci.Events;
11 |
12 | namespace WinBeacon
13 | {
14 | ///
15 | /// Class that represents an Apple iBeacon.
16 | ///
17 | public class Beacon : IComparable
18 | {
19 | private const int AppleCompanyId = 0x4C00;
20 |
21 | ///
22 | /// UUID of the beacon.
23 | ///
24 | public string Uuid { get; private set; }
25 |
26 | ///
27 | /// Bluetooth MAC-address of the beacon.
28 | ///
29 | public byte[] Address { get; private set; }
30 |
31 | ///
32 | /// Major number of the beacon.
33 | ///
34 | public int Major { get; private set; }
35 |
36 | ///
37 | /// Minor number of the beacon.
38 | ///
39 | public int Minor { get; private set; }
40 |
41 | ///
42 | /// RSSI power of the beacon in dB.
43 | ///
44 | public int Rssi { get; internal set; }
45 |
46 | ///
47 | /// Calibrated TX power of the beacon in dB.
48 | ///
49 | public int CalibratedTxPower { get; private set; }
50 |
51 | ///
52 | /// CompanyId of the beacon (0x4C00 for Apple iBeacon).
53 | ///
54 | public int CompanyId { get; private set; }
55 |
56 | ///
57 | /// True if the beacon is an (emulation of) Apple iBeacon.
58 | ///
59 | public bool IsAppleIBeacon
60 | {
61 | get { return CompanyId == AppleCompanyId; }
62 | }
63 |
64 | private Beacon()
65 | {
66 | }
67 |
68 | ///
69 | /// Creates a beacon.
70 | ///
71 | /// UUID of the beacon.
72 | /// Major number of the beacon.
73 | /// Minor number of the beacon.
74 | /// CompanyId, defaults to comapny id of Apple (0x4C00).
75 | /// Calibrated TX power of the beacon in dB.
76 | public Beacon(string uuid, int major, int minor, int calibratedTxPower, int companyId = AppleCompanyId)
77 | {
78 | Uuid = uuid;
79 | Major = major;
80 | Minor = minor;
81 | CalibratedTxPower = calibratedTxPower;
82 | CompanyId = companyId;
83 | }
84 |
85 | ///
86 | /// Compare this beacon to an other instance.
87 | ///
88 | /// The other instance.
89 | /// A value that indicates the lexical relationship between the two comparands.
90 | public int CompareTo(object obj)
91 | {
92 | var other = obj as Beacon ?? throw new ArgumentException("Must be of type Beacon", nameof(obj));
93 | return Uuid.NullableCompareTo(other.Uuid)
94 | ?? Major.NullableCompareTo(other.Major)
95 | ?? Minor.CompareTo(other.Minor);
96 | }
97 |
98 | ///
99 | /// Returns a string representation of the beacon data.
100 | ///
101 | /// String representation of the beacon data.
102 | public override string ToString()
103 | => $"UUID: {Uuid}, Address: {BitConverter.ToString(Address)}, Major: {Major}, Minor: {Minor}, RSSI: {Rssi}, TxPower: {CalibratedTxPower}dB, CompanyId: 0x{CompanyId:X}, Distance: {this.GetRange():0.00}m";
104 |
105 | ///
106 | /// Parse low energy advertising event to a beacon instance.
107 | ///
108 | /// The event.
109 | /// The beacon or null in case of failure.
110 | internal static Beacon Parse(LeAdvertisingEvent e)
111 | {
112 | if (e.EventType == LeAdvertisingEventType.ScanRsp)
113 | return null;
114 | var payload = new Queue(e.Payload);
115 | while (payload.Count > 0)
116 | {
117 | var adLength = payload.Dequeue();
118 | if (payload.Count < adLength)
119 | break;
120 | var adType = payload.Dequeue();
121 | if (adType == 0xFF && adLength >= 26)
122 | {
123 | var companyId = (ushort)((payload.Dequeue() << 8) + payload.Dequeue());
124 | var b0advInd = payload.Dequeue();
125 | var b1advInd = payload.Dequeue();
126 | var uuid = payload.Dequeue(16);
127 | var major = (ushort)((payload.Dequeue() << 8) + payload.Dequeue());
128 | var minor = (ushort)((payload.Dequeue() << 8) + payload.Dequeue());
129 | var txPower = (sbyte)payload.Dequeue();
130 | return new Beacon
131 | {
132 | Uuid = uuid.ToLittleEndianFormattedUuidString(),
133 | Address = e.Address,
134 | Major = major,
135 | Minor = minor,
136 | Rssi = e.Rssi,
137 | CalibratedTxPower = txPower,
138 | CompanyId = companyId
139 | };
140 | }
141 | else
142 | {
143 | if (adLength > 0)
144 | payload.Dequeue(adLength - 1);
145 | }
146 | }
147 |
148 | return null;
149 | }
150 |
151 | internal byte[] ToAdvertisingData()
152 | {
153 | var result = new List();
154 | result.AddRange(new byte[]
155 | {
156 | 0x02, // ad1Length
157 | 0x01, // ad1Type
158 | 0x1A, // flags
159 | 0x1A, // ad2Length
160 | 0xFF, // ad2Type
161 | (byte)(CompanyId >> 8),
162 | (byte)(CompanyId & 0xFF),
163 | 0x02, // b0advInd
164 | 0x15 // b1advInd
165 | });
166 | result.AddRange(Uuid.FromLittleEndianFormattedUuidString());
167 | result.AddRange(new byte[]
168 | {
169 | (byte)(Major >> 8),
170 | (byte)(Major & 0xFF),
171 | (byte)(Minor >> 8),
172 | (byte)(Minor & 0xFF),
173 | (byte)CalibratedTxPower
174 | });
175 | result.Add(0);
176 | return result.ToArray();
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/.nuget/NuGet.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | false
8 |
9 |
10 | false
11 |
12 |
13 | true
14 |
15 |
16 | true
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
31 |
32 |
33 |
34 |
35 | $(SolutionDir).nuget
36 |
37 |
38 |
39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config
40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config
41 |
42 |
43 |
44 | $(MSBuildProjectDirectory)\packages.config
45 | $(PackagesProjectConfig)
46 |
47 |
48 |
49 |
50 | $(NuGetToolsPath)\NuGet.exe
51 | @(PackageSource)
52 |
53 | "$(NuGetExePath)"
54 | mono --runtime=v4.0.30319 "$(NuGetExePath)"
55 |
56 | $(TargetDir.Trim('\\'))
57 |
58 | -RequireConsent
59 | -NonInteractive
60 |
61 | "$(SolutionDir) "
62 | "$(SolutionDir)"
63 |
64 |
65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
67 |
68 |
69 |
70 | RestorePackages;
71 | $(BuildDependsOn);
72 |
73 |
74 |
75 |
76 | $(BuildDependsOn);
77 | BuildPackage;
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
99 |
100 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Controllers/LeController.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Threading;
10 | using WinBeacon.Stack.Hci;
11 | using WinBeacon.Stack.Hci.Commands;
12 | using WinBeacon.Stack.Hci.Events;
13 |
14 | namespace WinBeacon.Stack.Controllers
15 | {
16 | ///
17 | /// Base class for Bluetooth Low Energy controllers.
18 | ///
19 | public class LeController : ILeController
20 | {
21 | private readonly ITransport _transport;
22 | private readonly ManualResetEvent _cancelThread = new ManualResetEvent(false);
23 | private readonly Queue _commandQueue = new Queue();
24 | private readonly AutoResetEvent _executeNextCommand = new AutoResetEvent(false);
25 | private Thread _commandExecutionThread;
26 |
27 | internal LeController(ITransport transport)
28 | {
29 | _transport = transport;
30 | }
31 |
32 | ///
33 | /// Destructor.
34 | ///
35 | ~LeController()
36 | {
37 | Dispose();
38 | }
39 |
40 | ///
41 | /// Release all used resources.
42 | ///
43 | public void Dispose()
44 | {
45 | Close();
46 | }
47 |
48 | ///
49 | /// Open underlying transport connection.
50 | ///
51 | public void Open()
52 | {
53 | if (_commandExecutionThread != null)
54 | return;
55 | _transport.Open();
56 | _transport.DataReceived += transport_DataReceived;
57 | _cancelThread.Reset();
58 | _commandExecutionThread = new Thread(CommandExecutionThreadProc);
59 | _commandExecutionThread.Start();
60 | SendCommand(new ResetCommand());
61 | SendCommand(new SetEventMaskCommand(new byte[] { 0xFF, 0xFF, 0xFB, 0xFF, 0x07, 0xF8, 0xBF, 0x3D }));
62 | SendCommand(new LeSetEventMaskCommand(new byte[] { 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }));
63 | SendCommand(new LeWriteHostSupportedCommand(true, false));
64 | SendCommand(new LeSetScanEnableCommand(false, false));
65 | SendCommand(new LeSetScanParametersCommand(true, 10000, 10000, false, false));
66 | SendCommand(new ReadBdAddrCommand()
67 | {
68 | CommandCompleteCallback = (cmd, deviceAddress) =>
69 | {
70 | DeviceAddressReceived?.Invoke(this, new DeviceAddressReceivedEventArgs(deviceAddress));
71 | }
72 | });
73 | }
74 |
75 | ///
76 | /// Close underlying transport connection.
77 | ///
78 | public void Close()
79 | {
80 | if (_commandExecutionThread == null)
81 | return;
82 | _cancelThread.Set();
83 | _commandExecutionThread.Join();
84 | _commandExecutionThread = null;
85 | lock (_commandQueue)
86 | _commandQueue.Clear();
87 | _transport.DataReceived -= transport_DataReceived;
88 | _transport.Close();
89 | }
90 |
91 | ///
92 | /// Enable Low Energy device scanning.
93 | ///
94 | public void EnableScanning()
95 | {
96 | SendCommand(new LeSetScanEnableCommand(true, false));
97 | }
98 |
99 | ///
100 | /// Disable Low Energy device scanning.
101 | ///
102 | public void DisableScanning()
103 | {
104 | SendCommand(new LeSetScanEnableCommand(false, false));
105 | }
106 |
107 | ///
108 | /// Enable Low Energy advertising.
109 | ///
110 | /// The advertisement data.
111 | /// Interval should be between 20 and 10240 ms. Defaults to 1280 ms.
112 | public void EnableAdvertising(byte[] advertisementData, int advertisingIntervalInMs = 1280)
113 | {
114 | SendCommand(new LeSetAdvertisingDataCommand(advertisementData));
115 | SendCommand(new LeSetAdvertisingParametersCommand(
116 | advertisingIntervalInMs,
117 | advertisingIntervalInMs,
118 | AdvertisingType.ConnectableUndirected,
119 | OwnAddressType.Public,
120 | PeerAddressType.Public,
121 | new byte[6],
122 | AdvertisingChannelMap.UseAllChannels,
123 | AdvertisingFilterPolicy.ConnectAllScanAll
124 | ));
125 | SendCommand(new LeSetAdvertisingEnableCommand(true));
126 | }
127 |
128 | ///
129 | /// Disable Low Energy advertising.
130 | ///
131 | public void DisableAdvertising()
132 | {
133 | SendCommand(new LeSetAdvertisingEnableCommand(false));
134 | }
135 |
136 | ///
137 | /// Fired for each received Low Energy meta event.
138 | ///
139 | public event EventHandler LeMetaEventReceived;
140 |
141 | ///
142 | /// Fired when the device address of this Bluetooth device was received.
143 | ///
144 | public event EventHandler DeviceAddressReceived;
145 |
146 | private void transport_DataReceived(object sender, Hci.DataReceivedEventArgs e)
147 | {
148 | if (e.DataType != DataType.Command)
149 | return;
150 | var evt = Event.Parse(e.Data);
151 | if (evt is CommandCompleteEvent)
152 | {
153 | var commandCompleteEvent = (evt as CommandCompleteEvent);
154 | Command command = null;
155 | lock (_commandQueue)
156 | {
157 | if (_commandQueue.Count > 0)
158 | {
159 | command = _commandQueue.Peek();
160 | if (command.Opcode == commandCompleteEvent.CommandOpcode)
161 | _commandQueue.Dequeue();
162 | else
163 | command = null;
164 | }
165 | }
166 | if (command != null)
167 | command.OnCommandComplete(commandCompleteEvent);
168 | _executeNextCommand.Set();
169 | }
170 | else if (evt is LeMetaEvent)
171 | {
172 | LeMetaEventReceived?.Invoke(this, new LeMetaEventReceivedEventArgs(evt as LeMetaEvent));
173 | }
174 | }
175 |
176 | private void CommandExecutionThreadProc()
177 | {
178 | var waitHandles = new WaitHandle[] { _cancelThread, _executeNextCommand };
179 | while (WaitHandle.WaitAny(waitHandles) == 1)
180 | {
181 | Command command = null;
182 | lock (_commandQueue)
183 | if (_commandQueue.Count > 0)
184 | command = _commandQueue.Peek();
185 | if (command != null)
186 | _transport.Send(command.ToByteArray(), DataType.Command);
187 | }
188 | }
189 |
190 | private void SendCommand(Command command)
191 | {
192 | lock (_commandQueue)
193 | {
194 | _commandQueue.Enqueue(command);
195 | if (_commandQueue.Count == 1)
196 | _executeNextCommand.Set();
197 | }
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/WinBeacon/Eddystone.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Text;
11 | using WinBeacon.Stack;
12 | using WinBeacon.Stack.Hci.Events;
13 |
14 | namespace WinBeacon
15 | {
16 | ///
17 | /// Abstract base class that represents an Eddystone Beacon.
18 | ///
19 | public abstract class Eddystone
20 | {
21 | ///
22 | /// Bluetooth MAC-address of the beacon.
23 | ///
24 | public byte[] Address { get; private set; }
25 |
26 | ///
27 | /// RSSI power of the beacon in dB.
28 | ///
29 | public int Rssi { get; internal set; }
30 |
31 | ///
32 | /// Constructor.
33 | ///
34 | protected Eddystone()
35 | {
36 | }
37 |
38 | ///
39 | /// Parses an incoming for Eddystone frames.
40 | ///
41 | /// The incoming event frame.
42 | /// An instance or null in case the event could not be parsed as an Eddystone event.
43 | /// Implemented according to https://github.com/google/eddystone/blob/master/protocol-specification.md
44 | internal static Eddystone Parse(LeAdvertisingEvent e)
45 | {
46 | if (e.EventType == LeAdvertisingEventType.ScanRsp)
47 | return null;
48 |
49 | var payload = new Queue(e.Payload);
50 |
51 | // Skip flags
52 | var flagsLength = payload.Dequeue();
53 | payload.Dequeue(flagsLength);
54 |
55 | // Extract and verify Eddystone UUID
56 | // The Complete List of 16-bit Service UUIDs as defined in The Bluetooth Core Specification Supplement (CSS) v5, Part A, § 1.1.
57 | // The Complete List of 16-bit Service UUIDs must contain the Eddystone Service UUID of 0xFEAA. This is included to allow background scanning on iOS devices.
58 | var eddystoneUuidLength = payload.Dequeue();
59 | if (eddystoneUuidLength != 3)
60 | return null;
61 | payload.Dequeue();
62 | var eddystoneUuid = (ushort)((payload.Dequeue() << 8) + payload.Dequeue());
63 | if (eddystoneUuid != 0xAAFE)
64 | return null;
65 |
66 | // Extract service data
67 | // The Service Data data type, Ibid., § 1.11.
68 | // The Service Data - 16 bit UUID data type must be the Eddystone Service UUID of 0xFEAA
69 | var serviceDataLength = payload.Dequeue();
70 | var serviceDataTypeValue = payload.Dequeue();
71 | eddystoneUuid = (ushort)((payload.Dequeue() << 8) + payload.Dequeue());
72 | if (eddystoneUuid != 0xAAFE)
73 | return null;
74 | var eddystoneFrameType = (EddystoneFrameType)payload.Dequeue();
75 | Eddystone result = null;
76 | switch (eddystoneFrameType)
77 | {
78 | case EddystoneFrameType.Uid:
79 | result = EddystoneUid.ParseFrame(payload);
80 | break;
81 | case EddystoneFrameType.Url:
82 | result = EddystoneUrl.ParseFrame(payload);
83 | break;
84 | case EddystoneFrameType.Tlm:
85 | result = EddystoneTlm.ParseFrame(payload);
86 | break;
87 | case EddystoneFrameType.Eid:
88 | result = EddystoneEid.ParseFrame(payload);
89 | break;
90 | }
91 |
92 | if (result != null)
93 | {
94 | result.Address = e.Address;
95 | result.Rssi = e.Rssi;
96 | }
97 |
98 | return result;
99 | }
100 | }
101 |
102 | internal enum EddystoneFrameType : byte
103 | {
104 | Uid = 0x00,
105 | Url = 0x10,
106 | Tlm = 0x20,
107 | Eid = 0x30,
108 | }
109 |
110 | ///
111 | /// Class that represents an Eddystone Beacon sending Eddystone-UID frames.
112 | ///
113 | public sealed class EddystoneUid : Eddystone
114 | {
115 | ///
116 | /// Calibrated TX power of the beacon in dB at 0m.
117 | ///
118 | public int CalibratedTxPower { get; private set; }
119 |
120 | ///
121 | /// The 10-byte namespace.
122 | ///
123 | public byte[] Namespace { get; private set; }
124 |
125 | ///
126 | /// The 6-byte instance.
127 | ///
128 | public byte[] Instance { get; private set; }
129 |
130 | internal static EddystoneUid ParseFrame(Queue payload)
131 | {
132 | if (payload.Count != 19)
133 | return null;
134 |
135 | return new EddystoneUid
136 | {
137 | CalibratedTxPower = (sbyte)payload.Dequeue(),
138 | Namespace = payload.Dequeue(10),
139 | Instance = payload.Dequeue(6),
140 | };
141 | }
142 |
143 | ///
144 | /// Returns a string representation of the Eddystone UID data.
145 | ///
146 | /// String representation of the Eddystone UID data.
147 | public override string ToString()
148 | => $"Namespace: {BitConverter.ToString(Namespace)}, Instance: {BitConverter.ToString(Instance)}, TxPower: {CalibratedTxPower}dB";
149 | }
150 |
151 | ///
152 | /// Class that represents an Eddystone Beacon sending Eddystone-URL frames.
153 | ///
154 | public sealed class EddystoneUrl : Eddystone
155 | {
156 | ///
157 | /// Calibrated TX power of the beacon in dB at 0m.
158 | ///
159 | public int CalibratedTxPower { get; private set; }
160 |
161 | ///
162 | /// The URL broadcasted by the Eddystone Beacon.
163 | ///
164 | public string Url { get; private set; }
165 |
166 | ///
167 | /// Returns a string representation of the Eddystone UID data.
168 | ///
169 | /// String representation of the Eddystone UID data.
170 | public override string ToString()
171 | => $"URL: {Url}, TxPower: {CalibratedTxPower}dB";
172 |
173 | internal static EddystoneUrl ParseFrame(Queue payload)
174 | {
175 | if (payload.Count < 3)
176 | return null;
177 |
178 | var calibratedTxPower = (sbyte)payload.Dequeue();
179 | var schemePrefix = payload.Dequeue();
180 | var encodedUrl = payload.DequeueAll();
181 |
182 | return new EddystoneUrl
183 | {
184 | CalibratedTxPower = calibratedTxPower,
185 | Url = $"{GetSchemePrefix(schemePrefix)}{string.Join(string.Empty, encodedUrl.Select(DecodeEncodedChar))}",
186 | };
187 | }
188 |
189 | private static string GetSchemePrefix(byte schemePrefix)
190 | {
191 | switch (schemePrefix)
192 | {
193 | case 0x00: return "http://www.";
194 | case 0x01: return "https://www.";
195 | case 0x02: return "http://";
196 | case 0x03: return "https://";
197 | default: return string.Empty;
198 | }
199 | }
200 |
201 | private static string DecodeEncodedChar(byte encodedChar)
202 | {
203 | switch (encodedChar)
204 | {
205 | case 0x00: return ".com/";
206 | case 0x01: return ".org/";
207 | case 0x02: return ".edu/";
208 | case 0x03: return ".net/";
209 | case 0x04: return ".info/";
210 | case 0x05: return ".biz/";
211 | case 0x06: return ".gov/";
212 | case 0x07: return ".com";
213 | case 0x08: return ".org";
214 | case 0x09: return ".edu";
215 | case 0x0A: return ".net";
216 | case 0x0B: return ".info";
217 | case 0x0C: return ".biz";
218 | case 0x0D: return ".gov";
219 | }
220 |
221 | if (encodedChar <= 0x20 || encodedChar >= 0x7F)
222 | return string.Empty;
223 |
224 | return new string((char)encodedChar, 1);
225 | }
226 | }
227 |
228 | ///
229 | /// Class that represents an Eddystone Beacon sending Eddystone-TLM frames.
230 | ///
231 | public sealed class EddystoneTlm : Eddystone
232 | {
233 | internal static EddystoneTlm ParseFrame(Queue payload)
234 | {
235 | return null;
236 | }
237 | }
238 |
239 | ///
240 | /// Class that represents an Eddystone Beacon sending Eddystone-EID frames.
241 | ///
242 | public sealed class EddystoneEid : Eddystone
243 | {
244 | internal static EddystoneEid ParseFrame(Queue payload)
245 | {
246 | return null;
247 | }
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/src/WinBeacon.Stack/Hci/Commands/LeSetAdvertisingParametersCommand.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using WinBeacon.Stack.Hci.Opcodes;
9 | using WinBeacon.Stack.Hci.Parameters;
10 |
11 | namespace WinBeacon.Stack.Hci.Commands
12 | {
13 | internal class LeSetAdvertisingParametersCommand : Command
14 | {
15 | ///
16 | /// Set the advertising parameters
17 | /// The Advertising_Interval_Min and Advertising_Interval_Max shall not be set to less than 0x00A0 (100 ms) if the Advertising_Type is set to 0x02 (ADV_SCAN_IND) or 0x03 (ADV_NONCONN_IND).
18 | /// The Host shall not issue this command when advertising is enabled in the Controller; if it is the Command Disallowed error code shall be used.
19 | ///
20 | /// Minimum advertising interval for undirected and low duty cycle directed advertising. Ranges from 20 ms to 10.24 sec. Defaults to 1.28 sec.
21 | /// Maximum advertising interval for undirected and low duty cycle directed advertising. Ranges from 20 ms to 10.24 sec. Defaults to 1.28 sec.
22 | /// Used to determine the packet type that is used for advertising when advertising is enabled.
23 | /// This parameter indicates the type of address being used in the advertising packets.
24 | /// This parameter contains the Peer’s Identity Type.
25 | /// This parameter contains the peer’s Identity Address.
26 | /// This is a bit field that indicates the advertising channels that shall be used when transmitting advertising packets.At least one channel bit shall be set in the Advertising_Channel_Map parameter.
27 | /// This parameter shall be ignored when directed advertising is enabled.
28 | public LeSetAdvertisingParametersCommand(
29 | int minimumAdvertisingIntervalInMs,
30 | int maximumAdvertisingIntervalInMs,
31 | AdvertisingType advertisingType,
32 | OwnAddressType ownAdressType,
33 | PeerAddressType peerAdressType,
34 | byte[] peerAddress,
35 | AdvertisingChannelMap advertisingChannelMap,
36 | AdvertisingFilterPolicy advertisingFilterPolicy)
37 | : base(OpcodeGroup.LeController, (int)LeControllerOpcode.SetAdvertisingParameters)
38 | {
39 | if (peerAddress == null) throw new ArgumentNullException("peerAddress");
40 | if (peerAddress.Length != 6) throw new ArgumentOutOfRangeException("peerAddress", "The peer address should consist of exactly 6 bytes.");
41 |
42 | ushort minimumAdvertisingIntervalCode = (ushort)Math.Ceiling(minimumAdvertisingIntervalInMs / 0.625);
43 | ushort maximumAdvertisingIntervalCode = (ushort)Math.Ceiling(maximumAdvertisingIntervalInMs / 0.625);
44 | if (advertisingType != AdvertisingType.ConnectableHighDutyCycleDirected)
45 | {
46 | if (minimumAdvertisingIntervalCode > 0x4000) throw new ArgumentOutOfRangeException("minimumAdvertisingIntervalCode", "Interval too long, should be less than 10240 ms");
47 | if (maximumAdvertisingIntervalCode > 0x4000) throw new ArgumentOutOfRangeException("maximumAdvertisingIntervalCode", "Interval too long, should be less than 10240 ms");
48 | if (advertisingType == AdvertisingType.ScannableUndirectedAdvertising || advertisingType == AdvertisingType.NonConnectableUndirected)
49 | {
50 | if (minimumAdvertisingIntervalCode < 0x00A0) throw new ArgumentOutOfRangeException("minimumAdvertisingIntervalCode", "Interval too short, should not be less than 100 ms");
51 | if (maximumAdvertisingIntervalCode < 0x00A0) throw new ArgumentOutOfRangeException("maximumAdvertisingIntervalCode", "Interval too short, should not be less than 100 ms");
52 | }
53 | else
54 | {
55 | if (minimumAdvertisingIntervalCode < 0x0020) throw new ArgumentOutOfRangeException("minimumAdvertisingIntervalCode", "Interval too short, should be more than 20 ms");
56 | if (maximumAdvertisingIntervalCode < 0x0020) throw new ArgumentOutOfRangeException("maximumAdvertisingIntervalCode", "Interval too short, should be more than 20 ms");
57 | }
58 | }
59 |
60 | Parameters.Add(new UshortCommandParameter(minimumAdvertisingIntervalCode));
61 | Parameters.Add(new UshortCommandParameter(maximumAdvertisingIntervalCode));
62 | Parameters.Add(new ByteCommandParameter((byte)advertisingType));
63 | Parameters.Add(new ByteCommandParameter((byte)ownAdressType));
64 | Parameters.Add(new ByteCommandParameter((byte)peerAdressType));
65 | Parameters.Add(new ByteArrayCommandParameter(peerAddress));
66 | Parameters.Add(new ByteCommandParameter((byte)advertisingChannelMap));
67 | Parameters.Add(new ByteCommandParameter((byte)advertisingFilterPolicy));
68 | }
69 | }
70 |
71 | #region Related enums
72 |
73 | ///
74 | /// The AdvertisingType is used to determine the packet type during advertising.
75 | ///
76 | internal enum AdvertisingType : byte
77 | {
78 | ///
79 | /// Connectable undirected advertising (ADV_IND) (default).
80 | ///
81 | ConnectableUndirected = 0x00,
82 | ///
83 | /// Connectable high duty cycle directed advertising (ADV_DIRECT_IND, high duty cycle).
84 | ///
85 | ConnectableHighDutyCycleDirected = 0x01,
86 | ///
87 | /// Scannable undirected advertising (ADV_SCAN_IND).
88 | ///
89 | ScannableUndirectedAdvertising = 0x02,
90 | ///
91 | /// Non connectable undirected advertising (ADV_NONCONN_IND).
92 | ///
93 | NonConnectableUndirected = 0x03,
94 | ///
95 | /// Connectable low duty cycle directed advertising (ADV_DIRECT_IND, low duty cycle).
96 | ///
97 | ConnectableLowDutyCycleDirected = 0x04
98 | }
99 |
100 | ///
101 | /// OwnAddressType parameter indicates the type of address being used in the advertising packets.
102 | ///
103 | internal enum OwnAddressType : byte
104 | {
105 | ///
106 | /// Public Device Address (default).
107 | ///
108 | Public = 0x00,
109 | ///
110 | /// Random Device Address.
111 | ///
112 | Random = 0x01,
113 | ///
114 | /// Controller generates Resolvable Private Address based on the local IRK from resolving list. If resolving list contains no matching entry, use public address.
115 | ///
116 | ResolveFromListOrPublic = 0x02,
117 | ///
118 | /// Controller generates Resolvable Private Address based on the local IRK from resolving list. If resolving list contains no matching entry, use random address from LE_Set_Random_Address.
119 | ///
120 | ResolveFromListOrRandom = 0x03
121 | }
122 |
123 | internal enum PeerAddressType : byte
124 | {
125 | ///
126 | /// Public Device Address (default) or Public Identity Address.
127 | ///
128 | Public = 0x00,
129 | ///
130 | /// Random Device Address or Random (static) Identity Address.
131 | ///
132 | Random = 0x01
133 | }
134 |
135 | ///
136 | /// The AdvertisingChannelMap is a flags field that indicates the advertising channels that shall be used when transmitting advertising packets.
137 | ///
138 | [Flags]
139 | internal enum AdvertisingChannelMap : byte
140 | {
141 | ///
142 | /// Channel 37 shall be used.
143 | ///
144 | UseChannel37 = 0x01,
145 | ///
146 | /// Channel 38 shall be used.
147 | ///
148 | UseChannel38 = 0x02,
149 | ///
150 | /// Channel 39 shall be used.
151 | ///
152 | UseChannel39 = 0x04,
153 | ///
154 | /// Default (all channels enabled).
155 | ///
156 | UseAllChannels = UseChannel37 | UseChannel38 | UseChannel39
157 | }
158 |
159 | internal enum AdvertisingFilterPolicy : byte
160 | {
161 | ///
162 | /// Process scan and connection requests from all devices (i.e., the White List is not in use) (default).
163 | ///
164 | ConnectAllScanAll = 0x00,
165 | ///
166 | /// Process connection requests from all devices and only scan requests from devices that are in the White List.
167 | ///
168 | ConnectAllScanWhiteList = 0x01,
169 | ///
170 | /// Process scan requests from all devices and only connection requests from devices that are in the White List..
171 | ///
172 | ConnectWhiteListScanAll = 0x02,
173 | ///
174 | /// Process scan and connection requests only from devices in the White List.
175 | ///
176 | ConnectWhiteListScanWhiteList = 0x03
177 | }
178 |
179 | #endregion
180 | }
181 |
--------------------------------------------------------------------------------
/test/WinBeacon.Stack.Tests/Hci/CommandTests.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Huysentruit Wouter
3 | *
4 | * See LICENSE file.
5 | */
6 |
7 | using System;
8 | using NUnit.Framework;
9 | using WinBeacon.Stack.Hci;
10 | using WinBeacon.Stack.Hci.Commands;
11 | using WinBeacon.Stack.Hci.Events;
12 |
13 | namespace WinBeacon.Stack.Tests.Hci
14 | {
15 | [TestFixture]
16 | public class CommandTests
17 | {
18 | [Test]
19 | public void Command_SimpleToByteArray()
20 | {
21 | var command = new Command(OpcodeGroup.LeController, 0x1234);
22 | Assert.AreEqual(command.ToByteArray(), new byte[] { 0x34, 0x32, 0x00 });
23 | }
24 |
25 | [Test]
26 | public void Command_WithParamToByteArray()
27 | {
28 | var parameter = new TestParameter { Data = new byte[] { 0x98, 0x76, 0x54, 0x32, 0x10 } };
29 | var command = new Command(OpcodeGroup.InformationalParameters, 0x12345);
30 | command.Parameters.Add(parameter);
31 | Assert.AreEqual(command.ToByteArray(), new byte[] { 0x45, 0x33, 0x05, 0x98, 0x76, 0x54, 0x32, 0x10 });
32 | }
33 |
34 | [Test]
35 | public void Command_OnCommandComplete()
36 | {
37 | Command callbackCommand = null;
38 | var command = new Command(OpcodeGroup.LeController, 0x1234);
39 | command.CommandCompleteCallback = (cmd, evt) => callbackCommand = cmd;
40 | Assert.IsNull(callbackCommand, "Callback called too early");
41 | command.OnCommandComplete(new CommandCompleteEvent());
42 | Assert.AreEqual(command, callbackCommand, "Command from callback differs");
43 | }
44 |
45 | [Test]
46 | public void Command_GenericOnCommandComplete()
47 | {
48 | Command callbackCommand = null;
49 | var command = new GenericTestCommand();
50 | command.CommandCompleteCallback = (cmd, result) =>
51 | {
52 | callbackCommand = cmd;
53 | Assert.AreEqual(result, 0x4687, "Result incorrect");
54 | };
55 | command.OnCommandComplete(new CommandCompleteEvent());
56 | Assert.AreEqual(command, callbackCommand, "Command from callback differs");
57 | }
58 |
59 | [Test]
60 | public void Command_LeSetAdvertisingDataCommand()
61 | {
62 | var command = new LeSetAdvertisingDataCommand(new byte[] { 0x12, 0x57, 0x78, 0x32 });
63 | Assert.AreEqual(new byte[] { 0x08, 0x20, 0x05, 0x04, 0x12, 0x57, 0x78, 0x32 }, command.ToByteArray());
64 | }
65 |
66 | [Test]
67 | public void Command_LeSetAdvertisingEnableCommand()
68 | {
69 | var command = new LeSetAdvertisingEnableCommand(false);
70 | Assert.AreEqual(new byte[] { 0x0A, 0x20, 0x01, 0x00 }, command.ToByteArray());
71 | command = new LeSetAdvertisingEnableCommand(true);
72 | Assert.AreEqual(new byte[] { 0x0A, 0x20, 0x01, 0x01 }, command.ToByteArray());
73 | }
74 |
75 | [Test]
76 | public void Command_LeSetAdvertisingParametersCommand()
77 | {
78 | Assert.That(() => new LeSetAdvertisingParametersCommand(20, 20, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll), Throws.Nothing);
79 | Assert.That(() => new LeSetAdvertisingParametersCommand(19, 20, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll), Throws.InstanceOf());
80 | Assert.That(() => new LeSetAdvertisingParametersCommand(20, 19, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll), Throws.InstanceOf());
81 | Assert.That(() => new LeSetAdvertisingParametersCommand(10240, 10240, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll), Throws.Nothing);
82 | Assert.That(() => new LeSetAdvertisingParametersCommand(10241, 10240, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll), Throws.InstanceOf());
83 | Assert.That(() => new LeSetAdvertisingParametersCommand(10240, 10241, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll), Throws.InstanceOf());
84 | var command = new LeSetAdvertisingParametersCommand(200, 400, AdvertisingType.ConnectableUndirected, OwnAddressType.Public, PeerAddressType.Public, new byte[6], AdvertisingChannelMap.UseAllChannels, AdvertisingFilterPolicy.ConnectAllScanAll);
85 | Assert.That(command.ToByteArray(), Is.EqualTo(new byte[] { 0x06, 0x20, 0x0F, 0x40, 0x01, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00 }));
86 | }
87 |
88 | [Test]
89 | public void Command_LeSetScanEnableCommand()
90 | {
91 | var command = new LeSetScanEnableCommand(false, false);
92 | Assert.AreEqual(new byte[] { 0x0C, 0x20, 0x02, 0x00, 0x00 }, command.ToByteArray());
93 | command = new LeSetScanEnableCommand(false, true);
94 | Assert.AreEqual(new byte[] { 0x0C, 0x20, 0x02, 0x00, 0x01 }, command.ToByteArray());
95 | command = new LeSetScanEnableCommand(true, false);
96 | Assert.AreEqual(new byte[] { 0x0C, 0x20, 0x02, 0x01, 0x00 }, command.ToByteArray());
97 | command = new LeSetScanEnableCommand(true, true);
98 | Assert.AreEqual(new byte[] { 0x0C, 0x20, 0x02, 0x01, 0x01 }, command.ToByteArray());
99 | }
100 |
101 | [Test]
102 | public void Command_LeSetScanParametersCommand()
103 | {
104 | var command = new LeSetScanParametersCommand(false, 0x1234, 0x2345, false, false);
105 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x00, 0x20, 0x1D, 0x6F, 0x38, 0x00, 0x00 }, command.ToByteArray());
106 | command = new LeSetScanParametersCommand(false, 0x2345, 0x1234, false, true);
107 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x00, 0x6F, 0x38, 0x20, 0x1D, 0x00, 0x01 }, command.ToByteArray());
108 | command = new LeSetScanParametersCommand(false, 0x1234, 0x2345, true, false);
109 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x00, 0x20, 0x1D, 0x6F, 0x38, 0x01, 0x00 }, command.ToByteArray());
110 | command = new LeSetScanParametersCommand(false, 0x2345, 0x1234, true, true);
111 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x00, 0x6F, 0x38, 0x20, 0x1D, 0x01, 0x01 }, command.ToByteArray());
112 | command = new LeSetScanParametersCommand(true, 0x1234, 0x2345, false, false);
113 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x01, 0x20, 0x1D, 0x6F, 0x38, 0x00, 0x00 }, command.ToByteArray());
114 | command = new LeSetScanParametersCommand(true, 0x2345, 0x1234, false, true);
115 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x01, 0x6F, 0x38, 0x20, 0x1D, 0x00, 0x01 }, command.ToByteArray());
116 | command = new LeSetScanParametersCommand(true, 0x1234, 0x2345, true, false);
117 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x01, 0x20, 0x1D, 0x6F, 0x38, 0x01, 0x00 }, command.ToByteArray());
118 | command = new LeSetScanParametersCommand(true, 0x2345, 0x1234, true, true);
119 | Assert.AreEqual(new byte[] { 0x0B, 0x20, 0x07, 0x01, 0x6F, 0x38, 0x20, 0x1D, 0x01, 0x01 }, command.ToByteArray());
120 | }
121 |
122 | [Test]
123 | public void Command_LeWriteHostSupportedCommand()
124 | {
125 | var command = new LeWriteHostSupportedCommand(false, false);
126 | Assert.AreEqual(new byte[] { 0x6D, 0x0C, 0x02, 0x00, 0x00 }, command.ToByteArray());
127 | command = new LeWriteHostSupportedCommand(false, true);
128 | Assert.AreEqual(new byte[] { 0x6D, 0x0C, 0x02, 0x00, 0x01 }, command.ToByteArray());
129 | command = new LeWriteHostSupportedCommand(true, false);
130 | Assert.AreEqual(new byte[] { 0x6D, 0x0C, 0x02, 0x01, 0x00 }, command.ToByteArray());
131 | command = new LeWriteHostSupportedCommand(true, true);
132 | Assert.AreEqual(new byte[] { 0x6D, 0x0C, 0x02, 0x01, 0x01 }, command.ToByteArray());
133 | }
134 |
135 | [Test]
136 | public void Command_ResetCommand()
137 | {
138 | var command = new ResetCommand();
139 | Assert.AreEqual(new byte[] { 0x03, 0x0C, 0x00 }, command.ToByteArray());
140 | }
141 |
142 | [Test]
143 | public void Command_ReadBdAddrCommand()
144 | {
145 | var command = new ReadBdAddrCommand();
146 | Assert.AreEqual(new byte[] { 0x09, 0x10, 0x00 }, command.ToByteArray());
147 | }
148 |
149 | [Test]
150 | public void Command_ReadLocalVersionCommand()
151 | {
152 | var command = new ReadLocalVersionCommand();
153 | Assert.AreEqual(new byte[] { 0x01, 0x10, 0x00 }, command.ToByteArray());
154 | }
155 |
156 | [Test]
157 | public void Command_LeReadHostSupportedCommand()
158 | {
159 | var command = new LeReadHostSupportedCommand();
160 | Assert.AreEqual(new byte[] { 0x6C, 0x0C, 0x00 }, command.ToByteArray());
161 | }
162 |
163 | [Test]
164 | public void Command_LeSetEventMaskCommand()
165 | {
166 | var command = new LeSetEventMaskCommand(new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 });
167 | Assert.AreEqual(new byte[] { 0x01, 0x20, 0x08, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }, command.ToByteArray());
168 | }
169 |
170 | [Test]
171 | public void Command_SetEventMaskCommand()
172 | {
173 | var command = new SetEventMaskCommand(new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 });
174 | Assert.AreEqual(new byte[] { 0x01, 0x0C, 0x08, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }, command.ToByteArray());
175 | }
176 |
177 | #region Helpers
178 |
179 | private class TestParameter : ICommandParameter
180 | {
181 | public byte[] Data { get; set; }
182 | public byte[] ToByteArray() { return Data; }
183 | }
184 |
185 | private class GenericTestCommand : Command
186 | {
187 | public GenericTestCommand()
188 | : base(OpcodeGroup.LinkControl, 0x7854)
189 | {
190 | }
191 | internal override ushort ParseCommandResult(CommandCompleteEvent e)
192 | {
193 | return 0x4687;
194 | }
195 | }
196 |
197 | #endregion
198 | }
199 | }
200 |
--------------------------------------------------------------------------------