Skip to content

Commit

Permalink
* Improve code structure
Browse files Browse the repository at this point in the history
* Add "list-adapters" command line option
* Use a BluetoothDevice.FromBluetoothAddressAsync method for discovering a device by mac address. It is simpler than using previously implemented approach.
  • Loading branch information
PolarGoose committed Jan 23, 2022
1 parent d086e5c commit 9b8473a
Show file tree
Hide file tree
Showing 23 changed files with 251 additions and 217 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ BluetoothDevicePairing.exe unpair-by-mac --mac 12:34:56:78:9A:BC --type Bluetoot
```
BluetoothDevicePairing.exe unpair-by-name --name "MX Ergo" --type BluetoothLE
```
* List all Bluetooth adapters available you your machine
```
BluetoothDevicePairing.exe list-adapters
```

# How it works
The program uses [Windows.Devices.Enumeration API](https://docs.microsoft.com/en-us/uwp/api/Windows.Devices.Enumeration?redirectedfrom=MSDN&view=winrt-22000) to work with Bluetooth.
Expand Down
2 changes: 1 addition & 1 deletion src/Bluetooth/Adapters/Adapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public Adapter(Windows.Devices.Enumeration.DeviceInformation bluetoothAdapterInf
adapterDevice = Windows.Devices.Bluetooth.BluetoothAdapter.FromIdAsync(bluetoothAdapterInfo.Id).GetAwaiter().GetResult();
radio = adapterDevice.GetRadioAsync().GetAwaiter().GetResult();
MacAddress = new AdapterMacAddress(adapterDevice);
IsDefault = MacAddress.Equals(defaultAdapterMacAddress);
IsDefault = MacAddress.RawAddess == defaultAdapterMacAddress.RawAddess;
}

public override string ToString()
Expand Down
42 changes: 3 additions & 39 deletions src/Bluetooth/Adapters/AdapterMacAddress.cs
Original file line number Diff line number Diff line change
@@ -1,49 +1,13 @@
using System;
using System.Linq;
using Windows.Devices.Bluetooth;

namespace BluetoothDevicePairing.Bluetooth.Adapter
{
internal sealed class AdapterMacAddress : IEquatable<AdapterMacAddress>
internal sealed class AdapterMacAddress : MacAddress
{
public string Address { get; }

public AdapterMacAddress(BluetoothAdapter adapter)
{
Address = string.Join(":", BitConverter.GetBytes(adapter.BluetoothAddress)
.Reverse()
.Skip(2)
.Select(b => b.ToString("x2")));
}

public override string ToString()
{
return Address;
}

public bool Equals(AdapterMacAddress other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return Address == other.Address;
}

public override bool Equals(object obj)
public AdapterMacAddress(Windows.Devices.Bluetooth.BluetoothAdapter adapter) : base(adapter.BluetoothAddress)
{
return ReferenceEquals(this, obj) || obj is AdapterMacAddress other && Equals(other);
}

public override int GetHashCode()
public AdapterMacAddress(string mac) : base(mac)
{
return Address != null ? Address.GetHashCode() : 0;
}
}
}
25 changes: 25 additions & 0 deletions src/Bluetooth/Devices/BluetoothDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace BluetoothDevicePairing.Bluetooth.Devices
{
internal sealed class BluetoothDevice : Device
{
public readonly Windows.Devices.Bluetooth.BluetoothDevice device;
protected override bool IsConnected => device.ConnectionStatus == Windows.Devices.Bluetooth.BluetoothConnectionStatus.Connected;

private BluetoothDevice(Windows.Devices.Bluetooth.BluetoothDevice device) : base(device.DeviceInformation)
{
this.device = device;
}

public static BluetoothDevice FromDeviceInfo(Windows.Devices.Enumeration.DeviceInformation info)
{
return new BluetoothDevice(Windows.Devices.Bluetooth.BluetoothDevice.FromIdAsync(info.Id).GetAwaiter().GetResult());
}

public static BluetoothDevice FromMac(DeviceMacAddress mac)
{
return new BluetoothDevice(Windows.Devices.Bluetooth.BluetoothDevice.FromBluetoothAddressAsync(mac.RawAddess).GetAwaiter().GetResult());
}
}
}
30 changes: 30 additions & 0 deletions src/Bluetooth/Devices/BluetoothLeDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;

namespace BluetoothDevicePairing.Bluetooth.Devices
{
internal class BluetoothLeDevice : Device
{
private readonly Windows.Devices.Bluetooth.BluetoothLEDevice device;
protected override bool IsConnected => device.ConnectionStatus == Windows.Devices.Bluetooth.BluetoothConnectionStatus.Connected;

private BluetoothLeDevice(Windows.Devices.Bluetooth.BluetoothLEDevice device) : base(device.DeviceInformation)
{
this.device = device;
}

public static BluetoothLeDevice FromDeviceInfo(Windows.Devices.Enumeration.DeviceInformation info)
{
return new BluetoothLeDevice(Windows.Devices.Bluetooth.BluetoothLEDevice.FromIdAsync(info.Id).GetAwaiter().GetResult());
}

public static BluetoothLeDevice FromMac(DeviceMacAddress mac)
{
var device = Windows.Devices.Bluetooth.BluetoothLEDevice.FromBluetoothAddressAsync(mac.RawAddess).GetAwaiter().GetResult();
if(device == null)
{
throw new Exception($"Can't create a BluetoothLE device from the provided mac address '{mac}'. Device with this mac address doesn't exist");
}
return new BluetoothLeDevice(device);
}
}
}
75 changes: 19 additions & 56 deletions src/Bluetooth/Devices/Device.cs
Original file line number Diff line number Diff line change
@@ -1,79 +1,42 @@
using System;
using System.Text.RegularExpressions;

namespace BluetoothDevicePairing.Bluetooth.Devices
{
internal enum ConnectionStatus
{
NotPaired,
Paired,
Connected
}

internal enum DeviceType
{
Bluetooth,
BluetoothLE
}

internal sealed class Device
internal abstract class Device
{
private readonly Windows.Devices.Enumeration.DeviceInformation info;
private readonly Windows.Devices.Bluetooth.BluetoothDevice bluetoothDevice;
private readonly Windows.Devices.Bluetooth.BluetoothLEDevice bluetoothLeDevice;
protected abstract bool IsConnected { get; }

public string Id => info.Id;
public ConnectionStatus ConnectionStatus =>
IsConnected
? ConnectionStatus.Connected
: PairingInfo.IsPaired
? ConnectionStatus.Paired
: ConnectionStatus.NotPaired;
public Windows.Devices.Enumeration.DeviceInformationPairing PairingInfo => info.Pairing;
public bool IsPaired => info.Pairing.IsPaired;
public DeviceMacAddress Mac { get; }
public DeviceType Type { get; }
public DeviceInfoId Id { get; }
public string Name => info.Name;
public bool IsConnected
{
get
{
if (bluetoothDevice != null)
{
return bluetoothDevice.ConnectionStatus == Windows.Devices.Bluetooth.BluetoothConnectionStatus.Connected;
}
else
{
return bluetoothLeDevice.ConnectionStatus == Windows.Devices.Bluetooth.BluetoothConnectionStatus.Connected;
}
}
}

public Device(Windows.Devices.Enumeration.DeviceInformation info)
protected Device(Windows.Devices.Enumeration.DeviceInformation info)
{
this.info = info;
Mac = new DeviceMacAddress(info);
Type = GetDeviceType(info);
if (Type == DeviceType.Bluetooth)
{
bluetoothDevice = Windows.Devices.Bluetooth.BluetoothDevice.FromIdAsync(info.Id).GetAwaiter().GetResult();
}
else
{
bluetoothLeDevice = Windows.Devices.Bluetooth.BluetoothLEDevice.FromIdAsync(info.Id).GetAwaiter().GetResult();
}
Id = new DeviceInfoId(info);
}

public override string ToString()
{
return $"name:'{Name}' mac:'{Mac}' type:'{Type}' Connected:'{IsConnected}' Paired:'{IsPaired}'";
}

private static DeviceType GetDeviceType(Windows.Devices.Enumeration.DeviceInformation device)
{
var match = Regex.Match(device.Id, @"(^\w*)(#)");
if (!match.Success)
{
throw new Exception($"Failed to extract the device type from the string '{device.Id}'");
}

var type = match.Groups[1].Value;
switch (type)
{
case "Bluetooth":
return DeviceType.Bluetooth;
case "BluetoothLE":
return DeviceType.BluetoothLE;
default:
throw new Exception($"Wrong device type '{type}' extracted from '{device.Id}'");
}
return $"name:'{Name}' mac:'{Id.DeviceMac}' type:'{Id.DeviceType}' ConnectionStatus:'{ConnectionStatus}'";
}
}
}
44 changes: 31 additions & 13 deletions src/Bluetooth/Devices/DeviceDiscoverer.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,54 @@
using BluetoothDevicePairing.Bluetooth.Devices.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace BluetoothDevicePairing.Bluetooth.Devices
{
internal static class DeviceDiscoverer
internal class DiscoveryTime
{
public static List<Device> DiscoverBluetoothDevices(int timeoutInSec)
public int Seconds { get; }

public DiscoveryTime(int timeInSeconds)
{
return Discover(AsqFilter.BluetoothDevicesFilter(), timeoutInSec);
if (timeInSeconds < 1 || timeInSeconds > 30)
{
throw new Exception($"discovery time should be in range [1; 30] but was {timeInSeconds}");
}

Seconds = timeInSeconds;
}
}

public static List<Device> DiscoverPairedBluetoothDevices(int timeoutInSec)
internal static class DeviceDiscoverer
{
public static List<Device> DiscoverBluetoothDevices(DiscoveryTime time)
{
return Discover(AsqFilter.PairedBluetoothDevicesFilter(), timeoutInSec);
return Discover(AsqFilter.BluetoothDevicesFilter(), time);
}

private static List<Device> Discover(AsqFilter filter, int discoveryTimeInSec)
public static List<Device> DiscoverPairedBluetoothDevices(DiscoveryTime time)
{
Console.WriteLine($"Start discovering devices for {discoveryTimeInSec} seconds");
return Discover(AsqFilter.PairedBluetoothDevicesFilter(), time);
}

if (discoveryTimeInSec < 1 || discoveryTimeInSec > 30)
{
throw new Exception($"discovery time should be in range [1; 30] but was {discoveryTimeInSec}");
}
private static List<Device> Discover(AsqFilter filter, DiscoveryTime time)
{
Console.WriteLine($"Start discovering devices for {time.Seconds} seconds");

var watcher = new DeviceWatcher(filter);
watcher.Start();
Thread.Sleep(discoveryTimeInSec * 1000);
Thread.Sleep(time.Seconds * 1000);
var devices = watcher.Stop();
return devices.Select(d => new Device(d)).ToList();
return devices.Select(info => CreateDevice(info)).ToList();
}

private static Device CreateDevice(Windows.Devices.Enumeration.DeviceInformation info)
{
return new DeviceInfoId(info).DeviceType == DeviceType.Bluetooth
? BluetoothDevice.FromDeviceInfo(info)
: BluetoothLeDevice.FromDeviceInfo(info);
}
}
}
26 changes: 26 additions & 0 deletions src/Bluetooth/Devices/DeviceInfoId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using BluetoothDevicePairing.Bluetooth.Adapter;
using System;
using System.Text.RegularExpressions;

namespace BluetoothDevicePairing.Bluetooth.Devices
{
internal sealed class DeviceInfoId
{
public DeviceType DeviceType { get; }
public AdapterMacAddress AdapterMac { get; }
public DeviceMacAddress DeviceMac { get; }

public DeviceInfoId(Windows.Devices.Enumeration.DeviceInformation info)
{
var match = Regex.Match(info.Id, @"(^\w+)#(?<Type>Bluetooth|BluetoothLE)(?<AdapterMac>(..:){5}(..))-(?<DeviceMac>(..:){5}(..))$");
if (!match.Success)
{
throw new Exception($"Failed to parse DeviceInformation.Id '{info.Id}'");
}

DeviceType = match.Groups["Type"].Value == "Bluetooth" ? DeviceType.Bluetooth : DeviceType.BluetoothLE;
AdapterMac = new AdapterMacAddress(match.Groups["AdapterMac"].Value);
DeviceMac = new DeviceMacAddress(match.Groups["DeviceMac"].Value);
}
}
}
56 changes: 2 additions & 54 deletions src/Bluetooth/Devices/DeviceMacAddress.cs
Original file line number Diff line number Diff line change
@@ -1,61 +1,9 @@
using System;
using System.Text.RegularExpressions;
using Windows.Devices.Enumeration;

namespace BluetoothDevicePairing.Bluetooth.Devices
{
internal sealed class DeviceMacAddress : IEquatable<DeviceMacAddress>
internal sealed class DeviceMacAddress : MacAddress
{
public DeviceMacAddress(DeviceInformation device)
public DeviceMacAddress(string mac) : base(mac)
{
var match = Regex.Match(device.Id, @"(..:){5}(..)$");
if (!match.Success)
{
throw new Exception($"Failed to extract mac address from the string '{device.Id}'");
}
Address = match.Value.ToUpper();
}

public DeviceMacAddress(string mac)
{
var match = Regex.Match(mac, @"^(..:){5}(..)$");
if (!match.Success)
{
throw new Exception($"MacAddress address '{mac}' is not a valid mac address");
}
Address = mac;
}

public string Address { get; }

public override string ToString()
{
return Address;
}

public bool Equals(DeviceMacAddress other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

return Address == other.Address;
}

public override bool Equals(object obj)
{
return ReferenceEquals(this, obj) || obj is DeviceMacAddress other && Equals(other);
}

public override int GetHashCode()
{
return Address != null ? Address.GetHashCode() : 0;
}
}
}
Loading

0 comments on commit 9b8473a

Please sign in to comment.