-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BlueZ bluetooth backend implementation
#64 non-breaking
- Loading branch information
1 parent
9a3831e
commit 6febc05
Showing
15 changed files
with
1,434 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace SharpBrick.PoweredUp.BlueZ | ||
{ | ||
internal class BlueZConstants | ||
{ | ||
public const string BlueZDBusServiceName = "org.bluez"; | ||
} | ||
} | ||
|
142 changes: 142 additions & 0 deletions
142
src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothAdapter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using SharpBrick.PoweredUp.Bluetooth; | ||
using SharpBrick.PoweredUp.BlueZ.Utilities; | ||
using Tmds.DBus; | ||
|
||
namespace SharpBrick.PoweredUp.BlueZ | ||
{ | ||
public class BlueZPoweredUpBluetoothAdapter : IPoweredUpBluetoothAdapter | ||
{ | ||
private readonly ILogger<BlueZPoweredUpBluetoothAdapter> _logger; | ||
private readonly string _adapterObjectPath; | ||
private readonly Dictionary<ulong, IPoweredUpBluetoothDevice> _devices = new Dictionary<ulong, IPoweredUpBluetoothDevice>(); | ||
private IAdapter1 _adapter; | ||
|
||
public bool Discovering { get; set; } = false; | ||
|
||
public BlueZPoweredUpBluetoothAdapter( | ||
ILogger<BlueZPoweredUpBluetoothAdapter> logger, | ||
string adapterObjectPath = null) //"/org/bluez/hci0") | ||
{ | ||
_logger = logger; | ||
_adapterObjectPath = adapterObjectPath; | ||
} | ||
|
||
private async Task<IAdapter1> GetAdapterAsync() | ||
{ | ||
var adapter = !string.IsNullOrEmpty(_adapterObjectPath) ? Connection.System.CreateProxy<IAdapter1>(BlueZConstants.BlueZDBusServiceName, _adapterObjectPath) : await FindFirstAdapter(); | ||
|
||
// validate the adapter | ||
await adapter.GetAliasAsync(); | ||
|
||
// make sure it is powered on | ||
if (!await adapter.GetPoweredAsync()) | ||
{ | ||
await adapter.SetPoweredAsync(true); | ||
} | ||
|
||
await adapter.WatchPropertiesAsync(AdapterPropertyChangedHandler); | ||
|
||
return adapter; | ||
} | ||
|
||
private async Task<IAdapter1> FindFirstAdapter() | ||
{ | ||
var adapters = await Connection.System.FindProxies<IAdapter1>(); | ||
return adapters.FirstOrDefault(); | ||
} | ||
|
||
private void AdapterPropertyChangedHandler(PropertyChanges changes) | ||
{ | ||
_logger.LogDebug("Property changed {ChangedProperties}", changes.Changed); | ||
|
||
foreach (var propertyChanged in changes.Changed) | ||
{ | ||
switch (propertyChanged.Key) | ||
{ | ||
case "Discovering": | ||
Discovering = (bool)propertyChanged.Value; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
private async Task<ICollection<IDevice1>> GetExistingDevicesAsync() | ||
=> await Connection.System.FindProxies<IDevice1>(); | ||
|
||
private IDevice1 GetSpecificDeviceAsync(ObjectPath objectPath) | ||
=> Connection.System.CreateProxy<IDevice1>(BlueZConstants.BlueZDBusServiceName, objectPath); | ||
|
||
private async Task<bool> IsLegoWirelessProcotolDevice(IDevice1 device) | ||
=> (await device.GetUUIDsAsync()).NullToEmpty().Any(x => x.ToUpperInvariant() == PoweredUpBluetoothConstants.LegoHubService); | ||
|
||
public async void Discover(Func<PoweredUpBluetoothDeviceInfo, Task> discoveryHandler, CancellationToken cancellationToken = default) | ||
{ | ||
_adapter ??= await GetAdapterAsync(); | ||
|
||
var existingDevices = await GetExistingDevicesAsync(); | ||
|
||
foreach (var device in existingDevices) | ||
{ | ||
if (await IsLegoWirelessProcotolDevice(device)) | ||
{ | ||
var poweredUpDevice = new BlueZPoweredUpBluetoothDevice(device, discoveryHandler); | ||
await poweredUpDevice.Initialize(); | ||
|
||
_devices.Add(poweredUpDevice.DeviceInfo.BluetoothAddress, poweredUpDevice); | ||
|
||
await poweredUpDevice.TryGetManufacturerDataAsync(); | ||
} | ||
} | ||
|
||
await Connection.System.WatchInterfacesAdded(NewDeviceAddedHandler); | ||
|
||
await _adapter.SetDiscoveryFilterAsync(new Dictionary<string,object>() | ||
{ | ||
{ "UUIDs", new string[] { PoweredUpBluetoothConstants.LegoHubService } } | ||
}); | ||
|
||
cancellationToken.Register(async () => | ||
{ | ||
if (Discovering) | ||
{ | ||
await _adapter.StopDiscoveryAsync(); | ||
} | ||
}); | ||
|
||
await _adapter.StartDiscoveryAsync(); | ||
|
||
async void NewDeviceAddedHandler((ObjectPath objectPath, IDictionary<string, IDictionary<string, object>> interfaces) args) | ||
{ | ||
if (!args.interfaces.ContainsKey("org.bluez.Device1")) | ||
{ | ||
return; | ||
} | ||
|
||
var device = GetSpecificDeviceAsync(args.objectPath); | ||
var poweredUpDevice = new BlueZPoweredUpBluetoothDevice(device, discoveryHandler); | ||
|
||
await poweredUpDevice.Initialize(); | ||
|
||
_devices.Add(poweredUpDevice.DeviceInfo.BluetoothAddress, poweredUpDevice); | ||
|
||
await poweredUpDevice.TryGetManufacturerDataAsync(); | ||
} | ||
} | ||
|
||
public Task<IPoweredUpBluetoothDevice> GetDeviceAsync(ulong bluetoothAddress) | ||
{ | ||
if (!_devices.ContainsKey(bluetoothAddress)) | ||
{ | ||
throw new ArgumentOutOfRangeException("Requested bluetooth device is not available from this adapter"); | ||
} | ||
|
||
return Task.FromResult<IPoweredUpBluetoothDevice>(_devices[bluetoothAddress]); | ||
} | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothCharacteristic.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using Polly; | ||
using SharpBrick.PoweredUp.Bluetooth; | ||
using Tmds.DBus; | ||
|
||
namespace SharpBrick.PoweredUp.BlueZ | ||
{ | ||
internal class BlueZPoweredUpBluetoothCharacteristic : IPoweredUpBluetoothCharacteristic | ||
{ | ||
private IGattCharacteristic1 _characteristic; | ||
|
||
public BlueZPoweredUpBluetoothCharacteristic(IGattCharacteristic1 characteristic, Guid uuid) | ||
{ | ||
Uuid = uuid; | ||
_characteristic = characteristic ?? throw new ArgumentNullException(nameof(characteristic)); | ||
} | ||
|
||
public Guid Uuid { get; } | ||
|
||
public async Task<bool> NotifyValueChangeAsync(Func<byte[], Task> notificationHandler) | ||
{ | ||
if (notificationHandler is null) | ||
{ | ||
throw new ArgumentNullException(nameof(notificationHandler)); | ||
} | ||
|
||
await _characteristic.WatchPropertiesAsync(PropertyChangedHandler); | ||
|
||
await _characteristic.StartNotifyAsync(); | ||
|
||
return true; | ||
|
||
void PropertyChangedHandler(PropertyChanges propertyChanges) | ||
{ | ||
foreach (var propertyChanged in propertyChanges.Changed) | ||
{ | ||
if (propertyChanged.Key == "Value") | ||
{ | ||
notificationHandler((byte[])propertyChanged.Value); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public async Task<bool> WriteValueAsync(byte[] data) | ||
{ | ||
if (data is null) | ||
{ | ||
throw new ArgumentNullException(nameof(data)); | ||
} | ||
|
||
await Policy | ||
.Handle<Tmds.DBus.DBusException>() | ||
.WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(10)) | ||
.ExecuteAsync(() => _characteristic.WriteValueAsync(data, new Dictionary<string, object>())); | ||
|
||
return true; | ||
} | ||
} | ||
} |
Oops, something went wrong.