From 6febc059878a94ea527b38af227ab7f065498eaf Mon Sep 17 00:00:00 2001 From: vuurbeving Date: Thu, 22 Oct 2020 00:11:52 +0200 Subject: [PATCH] BlueZ bluetooth backend implementation #64 non-breaking --- README.md | 4 + .../BaseExample.cs | 3 +- .../SharpBrick.PoweredUp.Examples/Program.cs | 4 +- .../SharpBrick.PoweredUp.Examples.csproj | 1 + .../BlueZConstants.cs | 8 + .../BlueZPoweredUpBluetoothAdapter.cs | 142 +++ .../BlueZPoweredUpBluetoothCharacteristic.cs | 62 ++ .../BlueZPoweredUpBluetoothDevice.cs | 136 +++ .../BlueZPoweredUpBluetoothService.cs | 46 + .../DBusConnectionExtensions.cs | 30 + .../DBusInterfaces.cs | 944 ++++++++++++++++++ .../ServiceCollectionExtensionsForBlueZ.cs | 13 + .../SharpBrick.PoweredUp.BlueZ.csproj | 17 + .../Utilities/BluetoothAddressFormatter.cs | 14 + .../Utilities/IEnumerableExtensions.cs | 13 + 15 files changed, 1434 insertions(+), 3 deletions(-) create mode 100644 src/SharpBrick.PoweredUp.BlueZ/BlueZConstants.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothAdapter.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothCharacteristic.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothDevice.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothService.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/DBusConnectionExtensions.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/DBusInterfaces.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/ServiceCollectionExtensionsForBlueZ.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/SharpBrick.PoweredUp.BlueZ.csproj create mode 100644 src/SharpBrick.PoweredUp.BlueZ/Utilities/BluetoothAddressFormatter.cs create mode 100644 src/SharpBrick.PoweredUp.BlueZ/Utilities/IEnumerableExtensions.cs diff --git a/README.md b/README.md index 5ffb158..1ed9460 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ var serviceProvider = new ServiceCollection() .AddLogging() .AddPoweredUp() .AddWinRTBluetooth() // using WinRT Bluetooth on Windows + //.AddBlueZBluetooth() // using BlueZ Bluetooth on Linux .BuildServiceProvider(); var host = serviceProvider.GetService(); @@ -155,6 +156,7 @@ var serviceProvider = new ServiceCollection() .AddLogging() .AddPoweredUp() .AddWinRTBluetooth() // using WinRT Bluetooth on Windows + //.AddBlueZBluetooth() // using BlueZ Bluetooth on Linux .BuildServiceProvider(); using (var scope = serviceProvider.CreateScope()) // create a scoped DI container per intented active connection/protocol. If disposed, disposes all disposable artifacts. @@ -236,6 +238,8 @@ DI Container Elements - [X] .NET Core 3.1 (on Windows 10 using WinRT) - Library uses `Span` / C# 8.0 and is therefore not supported in .NET Framework 1.0 - 4.8 and UWP Apps until arrival of .NET 5 (WinForms and WPF work in .NET Core 3.1) - Library uses WinRT for communication therefore only Windows 10 + - [X] .NET Core 3.1 / .NET 5 on Linux using BlueZ + - Requires `bluez` to be installed and configured. - [ ] Xamarin (on iOS / Android using ?) - [ ] Blazor (on Browser using WebBluetooth) - Hub Model diff --git a/examples/SharpBrick.PoweredUp.Examples/BaseExample.cs b/examples/SharpBrick.PoweredUp.Examples/BaseExample.cs index c50531c..91bc1a8 100644 --- a/examples/SharpBrick.PoweredUp.Examples/BaseExample.cs +++ b/examples/SharpBrick.PoweredUp.Examples/BaseExample.cs @@ -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() ; Configure(serviceCollection); diff --git a/examples/SharpBrick.PoweredUp.Examples/Program.cs b/examples/SharpBrick.PoweredUp.Examples/Program.cs index 4bc6a21..824b089 100644 --- a/examples/SharpBrick.PoweredUp.Examples/Program.cs +++ b/examples/SharpBrick.PoweredUp.Examples/Program.cs @@ -18,7 +18,7 @@ static async Task Main(string[] args) //example = new Example.ExampleMotorVirtualPort(); //example = new Example.ExampleHubActions(); //example = new Example.ExampleTechnicMediumHubAccelerometer(); - //example = new Example.ExampleTechnicMediumHubGyroSensor(); + example = new Example.ExampleTechnicMediumHubGyroSensor(); //example = new Example.ExampleVoltage(); //example = new Example.ExampleTechnicMediumTemperatureSensor(); //example = new Example.ExampleMotorInputCombinedMode(); @@ -33,7 +33,7 @@ static async Task Main(string[] args) //example = new Example.ExampleHubPropertyObserving(); //example = new Example.ExampleDiscoverByType(); //example = new Example.ExampleCalibrationSteering(); - example = new Example.ExampleTechnicMediumHubGestSensor(); + //example = new Example.ExampleTechnicMediumHubGestSensor(); // NOTE: Examples are programmed object oriented style. Base class implements methods Configure, DiscoverAsync and ExecuteAsync to be overwriten on demand. await example.InitHostAndDiscoverAsync(enableTrace); diff --git a/examples/SharpBrick.PoweredUp.Examples/SharpBrick.PoweredUp.Examples.csproj b/examples/SharpBrick.PoweredUp.Examples/SharpBrick.PoweredUp.Examples.csproj index 552cede..fc99d6d 100644 --- a/examples/SharpBrick.PoweredUp.Examples/SharpBrick.PoweredUp.Examples.csproj +++ b/examples/SharpBrick.PoweredUp.Examples/SharpBrick.PoweredUp.Examples.csproj @@ -9,6 +9,7 @@ + diff --git a/src/SharpBrick.PoweredUp.BlueZ/BlueZConstants.cs b/src/SharpBrick.PoweredUp.BlueZ/BlueZConstants.cs new file mode 100644 index 0000000..d1c5e43 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/BlueZConstants.cs @@ -0,0 +1,8 @@ +namespace SharpBrick.PoweredUp.BlueZ +{ + internal class BlueZConstants + { + public const string BlueZDBusServiceName = "org.bluez"; + } +} + diff --git a/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothAdapter.cs b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothAdapter.cs new file mode 100644 index 0000000..42e9b21 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothAdapter.cs @@ -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 _logger; + private readonly string _adapterObjectPath; + private readonly Dictionary _devices = new Dictionary(); + private IAdapter1 _adapter; + + public bool Discovering { get; set; } = false; + + public BlueZPoweredUpBluetoothAdapter( + ILogger logger, + string adapterObjectPath = null) //"/org/bluez/hci0") + { + _logger = logger; + _adapterObjectPath = adapterObjectPath; + } + + private async Task GetAdapterAsync() + { + var adapter = !string.IsNullOrEmpty(_adapterObjectPath) ? Connection.System.CreateProxy(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 FindFirstAdapter() + { + var adapters = await Connection.System.FindProxies(); + 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> GetExistingDevicesAsync() + => await Connection.System.FindProxies(); + + private IDevice1 GetSpecificDeviceAsync(ObjectPath objectPath) + => Connection.System.CreateProxy(BlueZConstants.BlueZDBusServiceName, objectPath); + + private async Task IsLegoWirelessProcotolDevice(IDevice1 device) + => (await device.GetUUIDsAsync()).NullToEmpty().Any(x => x.ToUpperInvariant() == PoweredUpBluetoothConstants.LegoHubService); + + public async void Discover(Func 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() + { + { "UUIDs", new string[] { PoweredUpBluetoothConstants.LegoHubService } } + }); + + cancellationToken.Register(async () => + { + if (Discovering) + { + await _adapter.StopDiscoveryAsync(); + } + }); + + await _adapter.StartDiscoveryAsync(); + + async void NewDeviceAddedHandler((ObjectPath objectPath, IDictionary> 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 GetDeviceAsync(ulong bluetoothAddress) + { + if (!_devices.ContainsKey(bluetoothAddress)) + { + throw new ArgumentOutOfRangeException("Requested bluetooth device is not available from this adapter"); + } + + return Task.FromResult(_devices[bluetoothAddress]); + } + } +} diff --git a/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothCharacteristic.cs b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothCharacteristic.cs new file mode 100644 index 0000000..937c3c0 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothCharacteristic.cs @@ -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 NotifyValueChangeAsync(Func 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 WriteValueAsync(byte[] data) + { + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + + await Policy + .Handle() + .WaitAndRetryForeverAsync(_ => TimeSpan.FromMilliseconds(10)) + .ExecuteAsync(() => _characteristic.WriteValueAsync(data, new Dictionary())); + + return true; + } + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothDevice.cs b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothDevice.cs new file mode 100644 index 0000000..59cf0aa --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothDevice.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using SharpBrick.PoweredUp.Bluetooth; +using Tmds.DBus; + +namespace SharpBrick.PoweredUp.BlueZ +{ + internal class BlueZPoweredUpBluetoothDevice : IPoweredUpBluetoothDevice + { + private Func _discoveryHandler; + private IDevice1 _device; + + internal PoweredUpBluetoothDeviceInfo DeviceInfo { get; private set; } = new PoweredUpBluetoothDeviceInfo(); + internal bool Connected { get; private set; } = false; + internal bool ServicesResolved { get; private set;} = false; + + internal BlueZPoweredUpBluetoothDevice(IDevice1 device, Func discoveryHandler = null) + { + _discoveryHandler = discoveryHandler; + _device = device; + } + + public string Name { get; private set; } = string.Empty; + + private async Task InvokeDiscoveryHandlerAsync() + { + if (_discoveryHandler != null && DeviceInfo?.ManufacturerData != null) + { + var tempHandle = _discoveryHandler; + _discoveryHandler = null; // make sure we only execute the handler exactly once. + await tempHandle(DeviceInfo); + } + } + + internal async Task Initialize() + { + await _device.WatchPropertiesAsync(DevicePropertyChangedHandler); + + await GetSafeDeviceInfoAsync(); + } + + internal async Task TryGetManufacturerDataAsync() + { + try + { + var manufacturerData = await _device.GetManufacturerDataAsync(); + DeviceInfo.ManufacturerData = (byte[])manufacturerData.First().Value; + } + catch + { + // we can ignore errors here, this will throw an exception for existing devices from a previous session (only after reboot/restart of bluetoothd) + // for these device, manufacturer data will be returned when discovery is turned on + } + } + + private async void DevicePropertyChangedHandler(PropertyChanges changes) + { + foreach (var propertyChanged in changes.Changed) + { + switch (propertyChanged.Key) + { + case "ManufacturerData": + DeviceInfo.ManufacturerData = (byte[])((IDictionary)propertyChanged.Value).First().Value; + break; + case "Connected": + Connected = (bool)propertyChanged.Value; + break; + case "ServicesResolved": + ServicesResolved = (bool)propertyChanged.Value; + break; + case "RSSI": + // this is the only dependable property that will change from a pre-existing device during discovery + await InvokeDiscoveryHandlerAsync(); + break; + } + } + } + + internal async Task GetSafeDeviceInfoAsync() + { + var btAddress = await _device.GetAddressAsync(); + DeviceInfo.BluetoothAddress = Utilities.BluetoothAddressFormatter.ConvertToInteger(btAddress); + DeviceInfo.Name = Name = await _device.GetNameAsync(); + } + + ~BlueZPoweredUpBluetoothDevice() => Dispose(false); + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + + protected virtual async void Dispose(bool disposing) + { + if (Connected) + await _device?.DisconnectAsync(); // dangerous to await here, try to find a better way + + _device = null; + } + + private async Task WaitForConnectionAndServicesResolved(CancellationToken token) + { + while (!Connected || !ServicesResolved) + { + token.ThrowIfCancellationRequested(); + await Task.Delay(25, token); + } + } + + public async Task GetServiceAsync(Guid serviceId) + { + var connectionTimeout = TimeSpan.FromSeconds(5); + + var cancellationTokenSource = new CancellationTokenSource(); + + await _device.ConnectAsync(); + + cancellationTokenSource.CancelAfter(connectionTimeout); + + await WaitForConnectionAndServicesResolved(cancellationTokenSource.Token); + + var gattServices = await Connection.System.FindProxies(); + + foreach (var gattService in gattServices) + { + var gattUuid = Guid.Parse(await gattService.GetUUIDAsync()); + + if (gattUuid == serviceId) + { + return new BlueZPoweredUpBluetoothService(gattService, gattUuid); + } + } + + throw new ArgumentOutOfRangeException(nameof(serviceId), $"Service with id {serviceId} not found"); + } + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothService.cs b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothService.cs new file mode 100644 index 0000000..3bc65bf --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/BlueZPoweredUpBluetoothService.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using SharpBrick.PoweredUp.Bluetooth; +using Tmds.DBus; + +namespace SharpBrick.PoweredUp.BlueZ +{ + internal class BlueZPoweredUpBluetoothService : IPoweredUpBluetoothService + { + private IGattService1 _gattService; + + public BlueZPoweredUpBluetoothService(IGattService1 gattService, Guid uuid) + { + Uuid = uuid; + _gattService = gattService; + } + + public Guid Uuid { get; } + + ~BlueZPoweredUpBluetoothService() => Dispose(false); + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + + protected virtual void Dispose(bool disposing) + { + _gattService = null; + } + + public async Task GetCharacteristicAsync(Guid characteristicId) + { + var characteristics = await Connection.System.FindProxies(); + + foreach (var characteristic in characteristics) + { + var characteristicUuid = Guid.Parse(await characteristic.GetUUIDAsync()); + + if (characteristicUuid == characteristicId) + { + return new BlueZPoweredUpBluetoothCharacteristic(characteristic, characteristicId); + } + } + + throw new ArgumentOutOfRangeException(nameof(characteristicId), $"Characteristic with id {characteristicId} not found"); + + } + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp.BlueZ/DBusConnectionExtensions.cs b/src/SharpBrick.PoweredUp.BlueZ/DBusConnectionExtensions.cs new file mode 100644 index 0000000..a968f53 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/DBusConnectionExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tmds.DBus; + +namespace SharpBrick.PoweredUp.BlueZ +{ + internal static class DBusConnectionExtensions + { + internal static async Task> FindProxies(this Connection connection) where T : IDBusObject + { + var dbusInterfaceAttribute = typeof(T).GetCustomAttributes(false).Cast().First(); + var objects = await GetObjectManager(connection).GetManagedObjectsAsync(); + + return objects + .Where(x => x.Value.ContainsKey(dbusInterfaceAttribute.Name)) + .Select(x => Connection.System.CreateProxy(BlueZConstants.BlueZDBusServiceName, x.Key)) + .ToList(); + } + + internal static async Task WatchInterfacesAdded(this Connection connection, Action<(ObjectPath objectPath, IDictionary> interfaces)> handler) + { + var disposable = await GetObjectManager(connection).WatchInterfacesAddedAsync(handler); + } + + private static IObjectManager GetObjectManager(Connection connection) + => connection.CreateProxy(BlueZConstants.BlueZDBusServiceName, "/"); + } +} diff --git a/src/SharpBrick.PoweredUp.BlueZ/DBusInterfaces.cs b/src/SharpBrick.PoweredUp.BlueZ/DBusInterfaces.cs new file mode 100644 index 0000000..c4bb1c5 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/DBusInterfaces.cs @@ -0,0 +1,944 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Tmds.DBus; + +[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] +namespace SharpBrick.PoweredUp.BlueZ +{ + [DBusInterface("org.freedesktop.DBus.ObjectManager")] + interface IObjectManager : IDBusObject + { + Task>>> GetManagedObjectsAsync(); + Task WatchInterfacesAddedAsync(Action<(ObjectPath @object, IDictionary> interfaces)> handler, Action onError = null); + Task WatchInterfacesRemovedAsync(Action<(ObjectPath @object, string[] interfaces)> handler, Action onError = null); + } + + [DBusInterface("org.bluez.AgentManager1")] + interface IAgentManager1 : IDBusObject + { + Task RegisterAgentAsync(ObjectPath Agent, string Capability); + Task UnregisterAgentAsync(ObjectPath Agent); + Task RequestDefaultAgentAsync(ObjectPath Agent); + } + + [DBusInterface("org.bluez.ProfileManager1")] + interface IProfileManager1 : IDBusObject + { + Task RegisterProfileAsync(ObjectPath Profile, string UUID, IDictionary Options); + Task UnregisterProfileAsync(ObjectPath Profile); + } + + [DBusInterface("org.bluez.HealthManager1")] + interface IHealthManager1 : IDBusObject + { + Task CreateApplicationAsync(IDictionary Config); + Task DestroyApplicationAsync(ObjectPath Application); + } + + [DBusInterface("org.bluez.Adapter1")] + interface IAdapter1 : IDBusObject + { + Task StartDiscoveryAsync(); + Task SetDiscoveryFilterAsync(IDictionary Properties); + Task StopDiscoveryAsync(); + Task RemoveDeviceAsync(ObjectPath Device); + Task GetDiscoveryFiltersAsync(); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class Adapter1Properties + { + private string _Address = default(string); + public string Address + { + get + { + return _Address; + } + + set + { + _Address = (value); + } + } + + private string _AddressType = default(string); + public string AddressType + { + get + { + return _AddressType; + } + + set + { + _AddressType = (value); + } + } + + private string _Name = default(string); + public string Name + { + get + { + return _Name; + } + + set + { + _Name = (value); + } + } + + private string _Alias = default(string); + public string Alias + { + get + { + return _Alias; + } + + set + { + _Alias = (value); + } + } + + private uint _Class = default(uint); + public uint Class + { + get + { + return _Class; + } + + set + { + _Class = (value); + } + } + + private bool _Powered = default(bool); + public bool Powered + { + get + { + return _Powered; + } + + set + { + _Powered = (value); + } + } + + private bool _Discoverable = default(bool); + public bool Discoverable + { + get + { + return _Discoverable; + } + + set + { + _Discoverable = (value); + } + } + + private uint _DiscoverableTimeout = default(uint); + public uint DiscoverableTimeout + { + get + { + return _DiscoverableTimeout; + } + + set + { + _DiscoverableTimeout = (value); + } + } + + private bool _Pairable = default(bool); + public bool Pairable + { + get + { + return _Pairable; + } + + set + { + _Pairable = (value); + } + } + + private uint _PairableTimeout = default(uint); + public uint PairableTimeout + { + get + { + return _PairableTimeout; + } + + set + { + _PairableTimeout = (value); + } + } + + private bool _Discovering = default(bool); + public bool Discovering + { + get + { + return _Discovering; + } + + set + { + _Discovering = (value); + } + } + + private string[] _UUIDs = default(string[]); + public string[] UUIDs + { + get + { + return _UUIDs; + } + + set + { + _UUIDs = (value); + } + } + + private string _Modalias = default(string); + public string Modalias + { + get + { + return _Modalias; + } + + set + { + _Modalias = (value); + } + } + } + + static class Adapter1Extensions + { + public static Task GetAddressAsync(this IAdapter1 o) => o.GetAsync("Address"); + public static Task GetAddressTypeAsync(this IAdapter1 o) => o.GetAsync("AddressType"); + public static Task GetNameAsync(this IAdapter1 o) => o.GetAsync("Name"); + public static Task GetAliasAsync(this IAdapter1 o) => o.GetAsync("Alias"); + public static Task GetClassAsync(this IAdapter1 o) => o.GetAsync("Class"); + public static Task GetPoweredAsync(this IAdapter1 o) => o.GetAsync("Powered"); + public static Task GetDiscoverableAsync(this IAdapter1 o) => o.GetAsync("Discoverable"); + public static Task GetDiscoverableTimeoutAsync(this IAdapter1 o) => o.GetAsync("DiscoverableTimeout"); + public static Task GetPairableAsync(this IAdapter1 o) => o.GetAsync("Pairable"); + public static Task GetPairableTimeoutAsync(this IAdapter1 o) => o.GetAsync("PairableTimeout"); + public static Task GetDiscoveringAsync(this IAdapter1 o) => o.GetAsync("Discovering"); + public static Task GetUUIDsAsync(this IAdapter1 o) => o.GetAsync("UUIDs"); + public static Task GetModaliasAsync(this IAdapter1 o) => o.GetAsync("Modalias"); + public static Task SetAliasAsync(this IAdapter1 o, string val) => o.SetAsync("Alias", val); + public static Task SetPoweredAsync(this IAdapter1 o, bool val) => o.SetAsync("Powered", val); + public static Task SetDiscoverableAsync(this IAdapter1 o, bool val) => o.SetAsync("Discoverable", val); + public static Task SetDiscoverableTimeoutAsync(this IAdapter1 o, uint val) => o.SetAsync("DiscoverableTimeout", val); + public static Task SetPairableAsync(this IAdapter1 o, bool val) => o.SetAsync("Pairable", val); + public static Task SetPairableTimeoutAsync(this IAdapter1 o, uint val) => o.SetAsync("PairableTimeout", val); + } + + [DBusInterface("org.bluez.GattManager1")] + interface IGattManager1 : IDBusObject + { + Task RegisterApplicationAsync(ObjectPath Application, IDictionary Options); + Task UnregisterApplicationAsync(ObjectPath Application); + } + + [DBusInterface("org.bluez.LEAdvertisingManager1")] + interface ILEAdvertisingManager1 : IDBusObject + { + Task RegisterAdvertisementAsync(ObjectPath Advertisement, IDictionary Options); + Task UnregisterAdvertisementAsync(ObjectPath Service); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class LEAdvertisingManager1Properties + { + private byte _ActiveInstances = default(byte); + public byte ActiveInstances + { + get + { + return _ActiveInstances; + } + + set + { + _ActiveInstances = (value); + } + } + + private byte _SupportedInstances = default(byte); + public byte SupportedInstances + { + get + { + return _SupportedInstances; + } + + set + { + _SupportedInstances = (value); + } + } + + private string[] _SupportedIncludes = default(string[]); + public string[] SupportedIncludes + { + get + { + return _SupportedIncludes; + } + + set + { + _SupportedIncludes = (value); + } + } + } + + static class LEAdvertisingManager1Extensions + { + public static Task GetActiveInstancesAsync(this ILEAdvertisingManager1 o) => o.GetAsync("ActiveInstances"); + public static Task GetSupportedInstancesAsync(this ILEAdvertisingManager1 o) => o.GetAsync("SupportedInstances"); + public static Task GetSupportedIncludesAsync(this ILEAdvertisingManager1 o) => o.GetAsync("SupportedIncludes"); + } + + [DBusInterface("org.bluez.Media1")] + interface IMedia1 : IDBusObject + { + Task RegisterEndpointAsync(ObjectPath Endpoint, IDictionary Properties); + Task UnregisterEndpointAsync(ObjectPath Endpoint); + Task RegisterPlayerAsync(ObjectPath Player, IDictionary Properties); + Task UnregisterPlayerAsync(ObjectPath Player); + } + + [DBusInterface("org.bluez.NetworkServer1")] + interface INetworkServer1 : IDBusObject + { + Task RegisterAsync(string Uuid, string Bridge); + Task UnregisterAsync(string Uuid); + } + + [DBusInterface("org.bluez.Device1")] + interface IDevice1 : IDBusObject + { + Task DisconnectAsync(); + Task ConnectAsync(); + Task ConnectProfileAsync(string UUID); + Task DisconnectProfileAsync(string UUID); + Task PairAsync(); + Task CancelPairingAsync(); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class Device1Properties + { + private string _Address = default(string); + public string Address + { + get + { + return _Address; + } + + set + { + _Address = (value); + } + } + + private string _AddressType = default(string); + public string AddressType + { + get + { + return _AddressType; + } + + set + { + _AddressType = (value); + } + } + + private string _Name = default(string); + public string Name + { + get + { + return _Name; + } + + set + { + _Name = (value); + } + } + + private string _Alias = default(string); + public string Alias + { + get + { + return _Alias; + } + + set + { + _Alias = (value); + } + } + + private uint _Class = default(uint); + public uint Class + { + get + { + return _Class; + } + + set + { + _Class = (value); + } + } + + private ushort _Appearance = default(ushort); + public ushort Appearance + { + get + { + return _Appearance; + } + + set + { + _Appearance = (value); + } + } + + private string _Icon = default(string); + public string Icon + { + get + { + return _Icon; + } + + set + { + _Icon = (value); + } + } + + private bool _Paired = default(bool); + public bool Paired + { + get + { + return _Paired; + } + + set + { + _Paired = (value); + } + } + + private bool _Trusted = default(bool); + public bool Trusted + { + get + { + return _Trusted; + } + + set + { + _Trusted = (value); + } + } + + private bool _Blocked = default(bool); + public bool Blocked + { + get + { + return _Blocked; + } + + set + { + _Blocked = (value); + } + } + + private bool _LegacyPairing = default(bool); + public bool LegacyPairing + { + get + { + return _LegacyPairing; + } + + set + { + _LegacyPairing = (value); + } + } + + private short _RSSI = default(short); + public short RSSI + { + get + { + return _RSSI; + } + + set + { + _RSSI = (value); + } + } + + private bool _Connected = default(bool); + public bool Connected + { + get + { + return _Connected; + } + + set + { + _Connected = (value); + } + } + + private string[] _UUIDs = default(string[]); + public string[] UUIDs + { + get + { + return _UUIDs; + } + + set + { + _UUIDs = (value); + } + } + + private string _Modalias = default(string); + public string Modalias + { + get + { + return _Modalias; + } + + set + { + _Modalias = (value); + } + } + + private ObjectPath _Adapter = default(ObjectPath); + public ObjectPath Adapter + { + get + { + return _Adapter; + } + + set + { + _Adapter = (value); + } + } + + private IDictionary _ManufacturerData = default(IDictionary); + public IDictionary ManufacturerData + { + get + { + return _ManufacturerData; + } + + set + { + _ManufacturerData = (value); + } + } + + private IDictionary _ServiceData = default(IDictionary); + public IDictionary ServiceData + { + get + { + return _ServiceData; + } + + set + { + _ServiceData = (value); + } + } + + private short _TxPower = default(short); + public short TxPower + { + get + { + return _TxPower; + } + + set + { + _TxPower = (value); + } + } + + private bool _ServicesResolved = default(bool); + public bool ServicesResolved + { + get + { + return _ServicesResolved; + } + + set + { + _ServicesResolved = (value); + } + } + } + + static class Device1Extensions + { + public static Task GetAddressAsync(this IDevice1 o) => o.GetAsync("Address"); + public static Task GetAddressTypeAsync(this IDevice1 o) => o.GetAsync("AddressType"); + public static Task GetNameAsync(this IDevice1 o) => o.GetAsync("Name"); + public static Task GetAliasAsync(this IDevice1 o) => o.GetAsync("Alias"); + public static Task GetClassAsync(this IDevice1 o) => o.GetAsync("Class"); + public static Task GetAppearanceAsync(this IDevice1 o) => o.GetAsync("Appearance"); + public static Task GetIconAsync(this IDevice1 o) => o.GetAsync("Icon"); + public static Task GetPairedAsync(this IDevice1 o) => o.GetAsync("Paired"); + public static Task GetTrustedAsync(this IDevice1 o) => o.GetAsync("Trusted"); + public static Task GetBlockedAsync(this IDevice1 o) => o.GetAsync("Blocked"); + public static Task GetLegacyPairingAsync(this IDevice1 o) => o.GetAsync("LegacyPairing"); + public static Task GetRSSIAsync(this IDevice1 o) => o.GetAsync("RSSI"); + public static Task GetConnectedAsync(this IDevice1 o) => o.GetAsync("Connected"); + public static Task GetUUIDsAsync(this IDevice1 o) => o.GetAsync("UUIDs"); + public static Task GetModaliasAsync(this IDevice1 o) => o.GetAsync("Modalias"); + public static Task GetAdapterAsync(this IDevice1 o) => o.GetAsync("Adapter"); + public static Task> GetManufacturerDataAsync(this IDevice1 o) => o.GetAsync>("ManufacturerData"); + public static Task> GetServiceDataAsync(this IDevice1 o) => o.GetAsync>("ServiceData"); + public static Task GetTxPowerAsync(this IDevice1 o) => o.GetAsync("TxPower"); + public static Task GetServicesResolvedAsync(this IDevice1 o) => o.GetAsync("ServicesResolved"); + public static Task SetAliasAsync(this IDevice1 o, string val) => o.SetAsync("Alias", val); + public static Task SetTrustedAsync(this IDevice1 o, bool val) => o.SetAsync("Trusted", val); + public static Task SetBlockedAsync(this IDevice1 o, bool val) => o.SetAsync("Blocked", val); + } + + [DBusInterface("org.bluez.GattService1")] + interface IGattService1 : IDBusObject + { + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class GattService1Properties + { + private string _UUID = default(string); + public string UUID + { + get + { + return _UUID; + } + + set + { + _UUID = (value); + } + } + + private ObjectPath _Device = default(ObjectPath); + public ObjectPath Device + { + get + { + return _Device; + } + + set + { + _Device = (value); + } + } + + private bool _Primary = default(bool); + public bool Primary + { + get + { + return _Primary; + } + + set + { + _Primary = (value); + } + } + + private ObjectPath[] _Includes = default(ObjectPath[]); + public ObjectPath[] Includes + { + get + { + return _Includes; + } + + set + { + _Includes = (value); + } + } + } + + static class GattService1Extensions + { + public static Task GetUUIDAsync(this IGattService1 o) => o.GetAsync("UUID"); + public static Task GetDeviceAsync(this IGattService1 o) => o.GetAsync("Device"); + public static Task GetPrimaryAsync(this IGattService1 o) => o.GetAsync("Primary"); + public static Task GetIncludesAsync(this IGattService1 o) => o.GetAsync("Includes"); + } + + [DBusInterface("org.bluez.GattCharacteristic1")] + interface IGattCharacteristic1 : IDBusObject + { + Task ReadValueAsync(IDictionary Options); + Task WriteValueAsync(byte[] Value, IDictionary Options); + Task<(CloseSafeHandle fd, ushort mtu)> AcquireWriteAsync(IDictionary Options); + Task<(CloseSafeHandle fd, ushort mtu)> AcquireNotifyAsync(IDictionary Options); + Task StartNotifyAsync(); + Task StopNotifyAsync(); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class GattCharacteristic1Properties + { + private string _UUID = default(string); + public string UUID + { + get + { + return _UUID; + } + + set + { + _UUID = (value); + } + } + + private ObjectPath _Service = default(ObjectPath); + public ObjectPath Service + { + get + { + return _Service; + } + + set + { + _Service = (value); + } + } + + private byte[] _Value = default(byte[]); + public byte[] Value + { + get + { + return _Value; + } + + set + { + _Value = (value); + } + } + + private bool _Notifying = default(bool); + public bool Notifying + { + get + { + return _Notifying; + } + + set + { + _Notifying = (value); + } + } + + private string[] _Flags = default(string[]); + public string[] Flags + { + get + { + return _Flags; + } + + set + { + _Flags = (value); + } + } + + private bool _WriteAcquired = default(bool); + public bool WriteAcquired + { + get + { + return _WriteAcquired; + } + + set + { + _WriteAcquired = (value); + } + } + + private bool _NotifyAcquired = default(bool); + public bool NotifyAcquired + { + get + { + return _NotifyAcquired; + } + + set + { + _NotifyAcquired = (value); + } + } + } + + static class GattCharacteristic1Extensions + { + public static Task GetUUIDAsync(this IGattCharacteristic1 o) => o.GetAsync("UUID"); + public static Task GetServiceAsync(this IGattCharacteristic1 o) => o.GetAsync("Service"); + public static Task GetValueAsync(this IGattCharacteristic1 o) => o.GetAsync("Value"); + public static Task GetNotifyingAsync(this IGattCharacteristic1 o) => o.GetAsync("Notifying"); + public static Task GetFlagsAsync(this IGattCharacteristic1 o) => o.GetAsync("Flags"); + public static Task GetWriteAcquiredAsync(this IGattCharacteristic1 o) => o.GetAsync("WriteAcquired"); + public static Task GetNotifyAcquiredAsync(this IGattCharacteristic1 o) => o.GetAsync("NotifyAcquired"); + } + + [DBusInterface("org.bluez.GattDescriptor1")] + interface IGattDescriptor1 : IDBusObject + { + Task ReadValueAsync(IDictionary Options); + Task WriteValueAsync(byte[] Value, IDictionary Options); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class GattDescriptor1Properties + { + private string _UUID = default(string); + public string UUID + { + get + { + return _UUID; + } + + set + { + _UUID = (value); + } + } + + private ObjectPath _Characteristic = default(ObjectPath); + public ObjectPath Characteristic + { + get + { + return _Characteristic; + } + + set + { + _Characteristic = (value); + } + } + + private byte[] _Value = default(byte[]); + public byte[] Value + { + get + { + return _Value; + } + + set + { + _Value = (value); + } + } + } + + static class GattDescriptor1Extensions + { + public static Task GetUUIDAsync(this IGattDescriptor1 o) => o.GetAsync("UUID"); + public static Task GetCharacteristicAsync(this IGattDescriptor1 o) => o.GetAsync("Characteristic"); + public static Task GetValueAsync(this IGattDescriptor1 o) => o.GetAsync("Value"); + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp.BlueZ/ServiceCollectionExtensionsForBlueZ.cs b/src/SharpBrick.PoweredUp.BlueZ/ServiceCollectionExtensionsForBlueZ.cs new file mode 100644 index 0000000..a205f59 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/ServiceCollectionExtensionsForBlueZ.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; +using SharpBrick.PoweredUp.Bluetooth; +using SharpBrick.PoweredUp.BlueZ; +using SharpBrick.PoweredUp.BlueZ.Utilities; + +namespace SharpBrick.PoweredUp +{ + public static class ServiceCollectionExtensionsForBlueZ + { + public static IServiceCollection AddBlueZBluetooth(this IServiceCollection self) + => self.AddSingleton(); + } +} \ No newline at end of file diff --git a/src/SharpBrick.PoweredUp.BlueZ/SharpBrick.PoweredUp.BlueZ.csproj b/src/SharpBrick.PoweredUp.BlueZ/SharpBrick.PoweredUp.BlueZ.csproj new file mode 100644 index 0000000..adaa183 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/SharpBrick.PoweredUp.BlueZ.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + + + + + + + + + + + + + diff --git a/src/SharpBrick.PoweredUp.BlueZ/Utilities/BluetoothAddressFormatter.cs b/src/SharpBrick.PoweredUp.BlueZ/Utilities/BluetoothAddressFormatter.cs new file mode 100644 index 0000000..37ba7c7 --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/Utilities/BluetoothAddressFormatter.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq; + +namespace SharpBrick.PoweredUp.BlueZ.Utilities +{ + public static class BluetoothAddressFormatter + { + public static ulong ConvertToInteger(string bluetoothMacAddress) + => Convert.ToUInt64(bluetoothMacAddress.Replace(":", ""), 16); + + public static string ConvertToMacString(ulong bluetoothAddress) + => string.Join(":", BitConverter.GetBytes(bluetoothAddress).Reverse().Select(b => b.ToString("X2"))).Substring(6); + } +} diff --git a/src/SharpBrick.PoweredUp.BlueZ/Utilities/IEnumerableExtensions.cs b/src/SharpBrick.PoweredUp.BlueZ/Utilities/IEnumerableExtensions.cs new file mode 100644 index 0000000..6c62ddc --- /dev/null +++ b/src/SharpBrick.PoweredUp.BlueZ/Utilities/IEnumerableExtensions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SharpBrick.PoweredUp.BlueZ.Utilities +{ + public static class IEnumerableExtensions + { + public static IEnumerable NullToEmpty(this IEnumerable source) + { + return source ?? Enumerable.Empty(); + } + } +} \ No newline at end of file