-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BlueZ Bluetooth Stack #118
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,9 +84,10 @@ public void InitHost(bool enableTrace) | |
if (enableTrace) | ||
{ | ||
builder.AddFilter("SharpBrick.PoweredUp.Bluetooth.BluetoothKernel", LogLevel.Debug); | ||
builder.AddFilter("SharpBrick.PoweredUp.BlueZ.BlueZPoweredUpBluetoothAdapter", LogLevel.Debug); | ||
} | ||
}) | ||
.AddWinRTBluetooth() | ||
.AddBlueZBluetooth() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think once we have this in the master, I will thinking about incorporating the Bluetooth selection into the core .AddPoweredUp() call within a later release. I mean, Windows will be always WinRT and Linux will be bluez realistically. Should be branchable and auto-addable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent idea |
||
; | ||
|
||
Configure(serviceCollection); | ||
|
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"; | ||
} | ||
} | ||
|
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(); | ||
rickjansen-dev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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) | ||
rickjansen-dev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why adding it to the collection before querying the manufacturerdata There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well, this is related to using certain property changes (RSSI in this case) as the trigger for the discovery handler callback. It could very well be that the manufacturer data comes along with other properties directly during the discovery. If the device is not added to the list before the discovery handler is invoked, the GetDeviceAsync will be called and it would not be able to return the device because it's not in the list of devices. I know this is not pretty, plus come to think of it, adding it to the list might actually need to be done before the call to Initialize(), i'll have to check that to make sure it always works There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had the same problem during my use of BlueGiga-adapter: The advertising-packets from the BLE-device(Hubs) are coming in an "unordered" sequence. You've got to fetch them all until you're sure to have alos the manufacturer-data (because this is needed in Bluetooth-kernel of poweredup). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with the bluegiga adapter, does that even use bluez? i'm unsure if unordered sequence of advertisement packets is an actual issue here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the BlueGiga-adapter ("dongle") BLED112 is a serial-device attached to USB. The client (poweredup-program here) talks to it just over a Serial-object. So it doesn't need Bluez or any other BLE-stack in the operating system, because the device itself handles the BLE-communication (timing and alike) |
||
} | ||
} | ||
|
||
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]); | ||
} | ||
} | ||
} |
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; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work in adding advertisement. We can make that even more prominent in the first section "Features" or however it is called.
Also - with your agreement - I will not be shy promoting your contribution about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure!