From 05cc780a38c8b6b1431311c511855a7e73b8ec56 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 20 Nov 2023 21:04:55 +0100 Subject: [PATCH 01/13] version: updated TMDS.BUS nuget version. advertisement: bluetooth is advertising, with properties like Appearance and LocalName. clean: removed redundant ILEAdversitement1 class --- .../Advertisements/Advertisement.cs | 32 +++- .../Advertisements/AdvertisementProperties.cs | 62 -------- .../Advertisements/AdvertisingManager.cs | 15 +- .../Advertisements/ILEAdvertisement1.cs | 16 -- DotnetBleServer/Core/BlueZ.cs | 10 +- DotnetBleServer/DotnetBleServer.csproj | 5 +- DotnetBleServer/Gatt/BlueZModel/BlueZGatt.cs | 140 ++++++++---------- .../Gatt/BlueZModel/GattCharacteristic.cs | 3 +- .../Gatt/GattApplicationManager.cs | 14 +- .../PublishProfiles/FolderProfile.pubxml | 13 ++ .../Utilities/AdapterExtensions.cs | 28 ++++ Examples/Examples.csproj | 2 +- Examples/Program.cs | 35 ++++- Examples/Properties/launchSettings.json | 13 ++ Examples/SampleAdvertisement.cs | 21 ++- Examples/SampleGattApplication.cs | 4 +- 16 files changed, 214 insertions(+), 199 deletions(-) delete mode 100755 DotnetBleServer/Advertisements/AdvertisementProperties.cs delete mode 100755 DotnetBleServer/Advertisements/ILEAdvertisement1.cs create mode 100644 DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 DotnetBleServer/Utilities/AdapterExtensions.cs create mode 100644 Examples/Properties/launchSettings.json diff --git a/DotnetBleServer/Advertisements/Advertisement.cs b/DotnetBleServer/Advertisements/Advertisement.cs index 1bd0422..cb5f991 100755 --- a/DotnetBleServer/Advertisements/Advertisement.cs +++ b/DotnetBleServer/Advertisements/Advertisement.cs @@ -1,15 +1,39 @@ -using System; +using DotnetBleServer.Core; +using DotnetBleServer.Gatt.BlueZModel; +using System; +using System.Collections.Generic; using System.Threading.Tasks; -using DotnetBleServer.Core; namespace DotnetBleServer.Advertisements { - public class Advertisement : PropertiesBase, ILEAdvertisement1 + public class Advertisement : PropertiesBase, ILEAdvertisement1, IObjectManagerProperties { - public Advertisement(string objectPath, AdvertisementProperties properties) : base(objectPath, properties) + public Advertisement(string objectPath, LEAdvertisement1Properties properties) : base(objectPath, properties) { } + /// + /// TODO: Keep or remove ? + /// Not sure it is mandatory for LEAdvertisement1 + /// Should try setting properties without implementing IObjectManagerProperties + /// + /// + public IDictionary> GetProperties() + { + return new Dictionary> + { + { + "org.bluez.LEAdvertisement1", new Dictionary + { + {"LocalName", Properties.LocalName}, + {"Type", Properties.Type}, + {"ServiceUUIDs", Properties.ServiceUUIDs}, + {"Appearance", Properties.Appearance} + } + } + }; + } + public Task ReleaseAsync() { throw new NotImplementedException(); diff --git a/DotnetBleServer/Advertisements/AdvertisementProperties.cs b/DotnetBleServer/Advertisements/AdvertisementProperties.cs deleted file mode 100755 index f991be1..0000000 --- a/DotnetBleServer/Advertisements/AdvertisementProperties.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Tmds.DBus; - -namespace DotnetBleServer.Advertisements -{ - [Dictionary] - [SuppressMessage("ReSharper", "ConvertToAutoProperty")] - [SuppressMessage("ReSharper", "UnusedMember.Global")] - public class AdvertisementProperties - { - private string _Type; - public string Type - { - get => _Type; - set => _Type = value; - } - - private string[] _ServiceUUIDs; - public string[] ServiceUUIDs - { - get => _ServiceUUIDs; - set => _ServiceUUIDs = value; - } - - private IDictionary _ManufacturerData; - public IDictionary ManufacturerData - { - get => _ManufacturerData; - set => _ManufacturerData = value; - } - - private string[] _SolicitUUIDs; - public string[] SolicitUUIDs - { - get => _SolicitUUIDs; - set => _SolicitUUIDs = value; - } - - private IDictionary _ServiceData; - public IDictionary ServiceData - { - get => _ServiceData; - set => _ServiceData = value; - } - - private bool _IncludeTxPower; - public bool IncludeTxPower - { - get => _IncludeTxPower; - set => _IncludeTxPower = value; - } - - private string _LocalName; - public string LocalName - { - get => _LocalName; - set => _LocalName = value; - } - - } -} \ No newline at end of file diff --git a/DotnetBleServer/Advertisements/AdvertisingManager.cs b/DotnetBleServer/Advertisements/AdvertisingManager.cs index a8989d5..a3672b8 100755 --- a/DotnetBleServer/Advertisements/AdvertisingManager.cs +++ b/DotnetBleServer/Advertisements/AdvertisingManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using DotnetBleServer.Core; +using DotnetBleServer.Gatt.BlueZModel; using Tmds.DBus; namespace DotnetBleServer.Advertisements @@ -9,10 +10,12 @@ namespace DotnetBleServer.Advertisements public class AdvertisingManager { private readonly ServerContext _Context; + private readonly string _AdapterPath; - public AdvertisingManager(ServerContext context) + public AdvertisingManager(ServerContext context, string adapterPath) { _Context = context; + _AdapterPath = adapterPath; } public async Task RegisterAdvertisement(Advertisement advertisement) @@ -20,7 +23,7 @@ public async Task RegisterAdvertisement(Advertisement advertisement) await _Context.Connection.RegisterObjectAsync(advertisement); Console.WriteLine($"advertisement object {advertisement.ObjectPath} created"); - await GetAdvertisingManager().RegisterAdvertisementAsync(((IDBusObject) advertisement).ObjectPath, + await GetAdvertisingManager().RegisterAdvertisementAsync(((IDBusObject)advertisement).ObjectPath, new Dictionary()); Console.WriteLine($"advertisement {advertisement.ObjectPath} registered in BlueZ advertising manager"); @@ -28,13 +31,13 @@ await GetAdvertisingManager().RegisterAdvertisementAsync(((IDBusObject) advertis private ILEAdvertisingManager1 GetAdvertisingManager() { - return _Context.Connection.CreateProxy("org.bluez", "/org/bluez/hci0"); + return _Context.Connection.CreateProxy("org.bluez", new ObjectPath(_AdapterPath)); } - public async Task CreateAdvertisement(AdvertisementProperties advertisementProperties) + public async Task CreateAdvertisement(LEAdvertisement1Properties advertisementProperties) { - var advertisement = new Advertisement("/org/bluez/example/advertisement0", advertisementProperties); - await new AdvertisingManager(_Context).RegisterAdvertisement(advertisement); + var advertisement = new Advertisement($"{_AdapterPath}/advertisement0", advertisementProperties); + await new AdvertisingManager(_Context, _AdapterPath).RegisterAdvertisement(advertisement); } } } \ No newline at end of file diff --git a/DotnetBleServer/Advertisements/ILEAdvertisement1.cs b/DotnetBleServer/Advertisements/ILEAdvertisement1.cs deleted file mode 100755 index a205490..0000000 --- a/DotnetBleServer/Advertisements/ILEAdvertisement1.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Threading.Tasks; -using Tmds.DBus; - -namespace DotnetBleServer.Advertisements -{ - [DBusInterface("org.bluez.LEAdvertisement1")] - internal interface ILEAdvertisement1 : IDBusObject - { - Task ReleaseAsync(); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } -} \ No newline at end of file diff --git a/DotnetBleServer/Core/BlueZ.cs b/DotnetBleServer/Core/BlueZ.cs index f6417c1..8b73191 100755 --- a/DotnetBleServer/Core/BlueZ.cs +++ b/DotnetBleServer/Core/BlueZ.cs @@ -36,7 +36,7 @@ interface IHealthManager1 : IDBusObject } [DBusInterface("org.bluez.Adapter1")] - interface IAdapter1 : IDBusObject + public interface IAdapter1 : IDBusObject { Task StartDiscoveryAsync(); Task SetDiscoveryFilterAsync(IDictionary Properties); @@ -50,7 +50,7 @@ interface IAdapter1 : IDBusObject } [Dictionary] - class Adapter1Properties + public class Adapter1Properties { private string _Address = default (string); public string Address @@ -235,7 +235,7 @@ public string Modalias } } - static class Adapter1Extensions + public static class Adapter1Extensions { public static Task GetAddressAsync(this IAdapter1 o) => o.GetAsync("Address"); public static Task GetAddressTypeAsync(this IAdapter1 o) => o.GetAsync("AddressType"); @@ -266,7 +266,7 @@ interface IGattManager1 : IDBusObject } [DBusInterface("org.bluez.LEAdvertisingManager1")] - interface ILEAdvertisingManager1 : IDBusObject + public interface ILEAdvertisingManager1 : IDBusObject { Task RegisterAdvertisementAsync(ObjectPath Advertisement, IDictionary Options); Task UnregisterAdvertisementAsync(ObjectPath Service); @@ -277,7 +277,7 @@ interface ILEAdvertisingManager1 : IDBusObject } [Dictionary] - class LEAdvertisingManager1Properties + public class LEAdvertisingManager1Properties { private byte _ActiveInstances = default (byte); public byte ActiveInstances diff --git a/DotnetBleServer/DotnetBleServer.csproj b/DotnetBleServer/DotnetBleServer.csproj index 5ea5864..499d110 100755 --- a/DotnetBleServer/DotnetBleServer.csproj +++ b/DotnetBleServer/DotnetBleServer.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net7.0 DotnetBleServer @@ -19,7 +19,8 @@ - + + \ No newline at end of file diff --git a/DotnetBleServer/Gatt/BlueZModel/BlueZGatt.cs b/DotnetBleServer/Gatt/BlueZModel/BlueZGatt.cs index 20d715c..9a3ce4b 100755 --- a/DotnetBleServer/Gatt/BlueZModel/BlueZGatt.cs +++ b/DotnetBleServer/Gatt/BlueZModel/BlueZGatt.cs @@ -19,7 +19,7 @@ internal interface IGattService1 : IDBusObject [Dictionary] internal class GattService1Properties { - private string _UUID = default (string); + private string _UUID = default(string); public string UUID { get @@ -33,7 +33,7 @@ public string UUID } } - private bool _Primary = default (bool); + private bool _Primary = default(bool); public bool Primary { get @@ -47,7 +47,7 @@ public bool Primary } } - private ObjectPath[] _Characteristics = default (ObjectPath[]); + private ObjectPath[] _Characteristics = default(ObjectPath[]); public ObjectPath[] Characteristics { get @@ -78,7 +78,7 @@ interface IGattCharacteristic1 : IDBusObject [Dictionary] public class GattCharacteristic1Properties { - private string _UUID = default (string); + private string _UUID = default(string); public string UUID { get @@ -92,7 +92,7 @@ public string UUID } } - private ObjectPath _Service = default (ObjectPath); + private ObjectPath _Service = default(ObjectPath); public ObjectPath Service { get @@ -106,7 +106,7 @@ public ObjectPath Service } } - private byte[] _Value = default (byte[]); + private byte[] _Value = default(byte[]); public byte[] Value { get @@ -120,7 +120,7 @@ public byte[] Value } } - private bool _Notifying = default (bool); + private bool _Notifying = default(bool); public bool Notifying { get @@ -134,7 +134,7 @@ public bool Notifying } } - private string[] _Flags = default (string[]); + private string[] _Flags = default(string[]); public string[] Flags { get @@ -162,7 +162,7 @@ interface IGattDescriptor1 : IDBusObject [Dictionary] public class GattDescriptor1Properties { - private string _UUID = default (string); + private string _UUID = default(string); public string UUID { get @@ -176,7 +176,7 @@ public string UUID } } - private ObjectPath _Characteristic = default (ObjectPath); + private ObjectPath _Characteristic = default(ObjectPath); public ObjectPath Characteristic { get @@ -190,7 +190,7 @@ public ObjectPath Characteristic } } - private byte[] _Value = default (byte[]); + private byte[] _Value = default(byte[]); public byte[] Value { get @@ -204,7 +204,7 @@ public byte[] Value } } - private string[] _Flags = default (string[]); + private string[] _Flags = default(string[]); public string[] Flags { get @@ -220,110 +220,90 @@ public string[] Flags } [DBusInterface("org.bluez.LEAdvertisement1")] - interface ILEAdvertisement1 : IDBusObject + public interface ILEAdvertisement1 : IDBusObject { Task ReleaseAsync(); - Task GetAsync(string prop); + Task GetAsync(string prop); Task GetAllAsync(); Task SetAsync(string prop, object val); Task WatchPropertiesAsync(Action handler); } [Dictionary] - class LEAdvertisement1Properties + public class LEAdvertisement1Properties { - private string _Type = default (string); + private string _Type; public string Type { - get - { - return _Type; - } - - set - { - _Type = (value); - } + get => _Type; + set => _Type = value; } - private string[] _ServiceUUIDs = default (string[]); + private string[] _ServiceUUIDs; public string[] ServiceUUIDs { - get - { - return _ServiceUUIDs; - } - - set - { - _ServiceUUIDs = (value); - } + get => _ServiceUUIDs; + set => _ServiceUUIDs = value; } - private IDictionary _ManufacturerData = default (IDictionary); + private IDictionary _ManufacturerData; public IDictionary ManufacturerData { - get - { - return _ManufacturerData; - } - - set - { - _ManufacturerData = (value); - } + get => _ManufacturerData; + set => _ManufacturerData = value; } - private string[] _SolicitUUIDs = default (string[]); + private string[] _SolicitUUIDs; public string[] SolicitUUIDs { - get - { - return _SolicitUUIDs; - } - - set - { - _SolicitUUIDs = (value); - } + get => _SolicitUUIDs; + set => _SolicitUUIDs = value; } - private IDictionary _ServiceData = default (IDictionary); + private IDictionary _ServiceData; public IDictionary ServiceData { - get - { - return _ServiceData; - } - - set - { - _ServiceData = (value); - } + get => _ServiceData; + set => _ServiceData = value; } - private bool _IncludeTxPower = default (bool); + private bool _IncludeTxPower; public bool IncludeTxPower { - get - { - return _IncludeTxPower; - } + get => _IncludeTxPower; + set => _IncludeTxPower = value; + } - set - { - _IncludeTxPower = (value); - } + private string _LocalName; + public string LocalName + { + get => _LocalName; + set => _LocalName = value; + } + + private UInt16 _Appearance; + public UInt16 Appearance + { + get => _Appearance; + set => _Appearance = value; + } + + private bool _Discoverable; + public bool Discoverable + { + get => _Discoverable; + set => _Discoverable = value; } } static class LEAdvertisement1Extensions { - public static Task GetTypeAsync(this ILEAdvertisement1 o) => o.GetAsync("Type"); - public static Task GetServiceUUIDsAsync(this ILEAdvertisement1 o) => o.GetAsync("ServiceUUIDs"); - public static Task> GetManufacturerDataAsync(this ILEAdvertisement1 o) => o.GetAsync>("ManufacturerData"); - public static Task GetSolicitUUIDsAsync(this ILEAdvertisement1 o) => o.GetAsync("SolicitUUIDs"); - public static Task> GetServiceDataAsync(this ILEAdvertisement1 o) => o.GetAsync>("ServiceData"); - public static Task GetIncludeTxPowerAsync(this ILEAdvertisement1 o) => o.GetAsync("IncludeTxPower"); + //public static Task GetTypeAsync(this ILEAdvertisement1 o) => o.GetAsync("Type"); + //public static Task GetServiceUUIDsAsync(this ILEAdvertisement1 o) => o.GetAsync("ServiceUUIDs"); + //public static Task> GetManufacturerDataAsync(this ILEAdvertisement1 o) => o.GetAsync>("ManufacturerData"); + //public static Task GetSolicitUUIDsAsync(this ILEAdvertisement1 o) => o.GetAsync("SolicitUUIDs"); + //public static Task> GetServiceDataAsync(this ILEAdvertisement1 o) => o.GetAsync>("ServiceData"); + //public static Task GetIncludeTxPowerAsync(this ILEAdvertisement1 o) => o.GetAsync("IncludeTxPower"); + public static Task SetLocalNameAsync(this ILEAdvertisement1 o, string val) => o.SetAsync("LocalName", val); } } \ No newline at end of file diff --git a/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs b/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs index a0627c2..a20ead8 100755 --- a/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs +++ b/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs @@ -7,8 +7,7 @@ namespace DotnetBleServer.Gatt.BlueZModel { - internal class GattCharacteristic : PropertiesBase, IGattCharacteristic1, - IObjectManagerProperties + internal class GattCharacteristic : PropertiesBase, IGattCharacteristic1, IObjectManagerProperties { public IList Descriptors { get; } = new List(); diff --git a/DotnetBleServer/Gatt/GattApplicationManager.cs b/DotnetBleServer/Gatt/GattApplicationManager.cs index e9a8328..b583cf8 100755 --- a/DotnetBleServer/Gatt/GattApplicationManager.cs +++ b/DotnetBleServer/Gatt/GattApplicationManager.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using DotnetBleServer.Core; +using DotnetBleServer.Core; using DotnetBleServer.Gatt.BlueZModel; using DotnetBleServer.Gatt.Description; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Tmds.DBus; namespace DotnetBleServer.Gatt @@ -11,10 +11,12 @@ namespace DotnetBleServer.Gatt public class GattApplicationManager { private readonly ServerContext _ServerContext; + private readonly string _AdapterPath; - public GattApplicationManager(ServerContext serverContext) + public GattApplicationManager(ServerContext serverContext, string adapterPath) { _ServerContext = serverContext; + _AdapterPath = adapterPath; } public async Task RegisterGattApplication(IEnumerable gattServiceDescriptions) @@ -53,7 +55,7 @@ private async Task BuildApplicationTree(string applicationObjectPath, IEnumerabl private async Task RegisterApplicationInBluez(string applicationObjectPath) { - var gattManager = _ServerContext.Connection.CreateProxy("org.bluez", "/org/bluez/hci0"); + var gattManager = _ServerContext.Connection.CreateProxy("org.bluez", new ObjectPath(_AdapterPath)); await gattManager.RegisterApplicationAsync(new ObjectPath(applicationObjectPath), new Dictionary()); } diff --git a/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml b/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..825527c --- /dev/null +++ b/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + Release + Any CPU + bin\Release\netstandard2.1\publish\ + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/DotnetBleServer/Utilities/AdapterExtensions.cs b/DotnetBleServer/Utilities/AdapterExtensions.cs new file mode 100644 index 0000000..cfcf912 --- /dev/null +++ b/DotnetBleServer/Utilities/AdapterExtensions.cs @@ -0,0 +1,28 @@ +using DotnetBleServer.Core; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Tmds.DBus; + +namespace DotnetBleServer.Utilities +{ + public class AdapterExtensions + { + private const string BluezPrependDevicePath = "/org/bluez/"; + public async static Task> GetBluetoothAdapters(Connection dbusConnection) + { + List bluezDeviceList = new List(); + foreach (var itemDict in await dbusConnection.CreateProxy("org.bluez", "/").GetManagedObjectsAsync()) + { + string keyString = itemDict.Key.ToString(); + if (keyString.Length < BluezPrependDevicePath.Length) + continue; + + bluezDeviceList.Add(keyString); + } + + return bluezDeviceList; + } + } +} \ No newline at end of file diff --git a/Examples/Examples.csproj b/Examples/Examples.csproj index 346b16a..2131501 100755 --- a/Examples/Examples.csproj +++ b/Examples/Examples.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net7.0 diff --git a/Examples/Program.cs b/Examples/Program.cs index 27961e5..9f7107b 100755 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using DotnetBleServer.Core; +using DotnetBleServer.Utilities; namespace Examples { @@ -8,19 +9,41 @@ internal class Program { private static void Main() { - Console.WriteLine("Hello Bluetooth"); - Console.ReadKey(); + Console.WriteLine("Hello Bluetooth !"); + Console.WriteLine("Press a key to start (let you attach debugger from Visual Studio using SSH)"); + Console.ReadLine(); Task.Run(async () => { using (var serverContext = new ServerContext()) { await serverContext.Connect(); - await SampleAdvertisement.RegisterSampleAdvertisement(serverContext); - await SampleGattApplication.RegisterGattApplication(serverContext); - Console.WriteLine("Press CTRL+C to quit"); - await Task.Delay(-1); + string adapterPath = (await AdapterExtensions.GetBluetoothAdapters(serverContext.Connection))[0]; + + // Trying to turn on the bluetooth device within the code. + Console.WriteLine($"Trying to turn on the bluetooth adapter: {adapterPath}"); + + IAdapter1 BLEAdapter = serverContext.Connection.CreateProxy("org.bluez", adapterPath); + + // Uncomment if needed. + //await Adapter1Extensions.SetAliasAsync(BLEAdapter, "Debian (raspberry Pi)"); + //await Adapter1Extensions.SetDiscoverableAsync(BLEAdapter, true); + + await Adapter1Extensions.SetPoweredAsync(BLEAdapter, true); + + if (!await Adapter1Extensions.GetPoweredAsync(BLEAdapter)) + throw new Exception($"Can't power on adapter {adapterPath}"); + + await SampleAdvertisement.RegisterSampleAdvertisement(serverContext, adapterPath); + await SampleGattApplication.RegisterGattApplication(serverContext, adapterPath); + + Console.WriteLine("Press ENTER to quit"); + Console.ReadLine(); + + // Power off adapter (Bluetooth is no more Discoverable) + await Adapter1Extensions.SetPoweredAsync(BLEAdapter, false); + } }).Wait(); } diff --git a/Examples/Properties/launchSettings.json b/Examples/Properties/launchSettings.json new file mode 100644 index 0000000..8f38ba8 --- /dev/null +++ b/Examples/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "Examples": { + "commandName": "Project", + "remoteDebugEnabled": false, + "remoteDebugMachine": "192.168.1.25" + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/Examples/SampleAdvertisement.cs b/Examples/SampleAdvertisement.cs index 1c32629..abe97a8 100755 --- a/Examples/SampleAdvertisement.cs +++ b/Examples/SampleAdvertisement.cs @@ -1,21 +1,28 @@ -using System.Threading.Tasks; -using DotnetBleServer.Advertisements; +using DotnetBleServer.Advertisements; using DotnetBleServer.Core; +using DotnetBleServer.Gatt.BlueZModel; +using System; +using System.Threading.Tasks; namespace Examples { public class SampleAdvertisement { - public static async Task RegisterSampleAdvertisement(ServerContext serverContext) + public static async Task RegisterSampleAdvertisement(ServerContext serverContext, string adapterPath) { - var advertisementProperties = new AdvertisementProperties + var advertisementProperties = new LEAdvertisement1Properties { Type = "peripheral", - ServiceUUIDs = new[] { "12345678-1234-5678-1234-56789abcdef0"}, - LocalName = "A", + ServiceUUIDs = new[] { "E11EFB5D-E8BD-46B5-814A-1C4322F80067" }, + LocalName = "DISPLAYED NAME", + // Check bluetooth spec here: https://www.bluetooth.com/specifications/an/ (Computer appearance is 64 => convert hex value to uint: 0x0080 = 64) + // I don't know why, for some reasons, changing appearance is not always working. + Appearance = (ushort)Convert.ToUInt32("0x0080", 16), + // Set to false if Type = "broadcast" + Discoverable = true, }; - await new AdvertisingManager(serverContext).CreateAdvertisement(advertisementProperties); + await new AdvertisingManager(serverContext, adapterPath).CreateAdvertisement(advertisementProperties); } } } \ No newline at end of file diff --git a/Examples/SampleGattApplication.cs b/Examples/SampleGattApplication.cs index 153cc57..966a521 100755 --- a/Examples/SampleGattApplication.cs +++ b/Examples/SampleGattApplication.cs @@ -9,7 +9,7 @@ namespace Examples { internal class SampleGattApplication { - public static async Task RegisterGattApplication(ServerContext serverContext) + public static async Task RegisterGattApplication(ServerContext serverContext, string adapterPath) { var gattServiceDescription = new GattServiceDescription { @@ -34,7 +34,7 @@ public static async Task RegisterGattApplication(ServerContext serverContext) .AddService(gattServiceDescription) .WithCharacteristic(gattCharacteristicDescription, new[] {gattDescriptorDescription}); - await new GattApplicationManager(serverContext).RegisterGattApplication(gab.BuildServiceDescriptions()); + await new GattApplicationManager(serverContext, adapterPath).RegisterGattApplication(gab.BuildServiceDescriptions()); } internal class ExampleCharacteristicSource : ICharacteristicSource From 2fe3f9c877cece1b6f07f868bd562761044362c4 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 22 Nov 2023 15:42:46 +0100 Subject: [PATCH 02/13] sample: improve / add details to the sample app, using a Func as callback. --- Examples/Program.cs | 76 ++++++++++++++++++++++++++----- Examples/SampleAdvertisement.cs | 5 +- Examples/SampleGattApplication.cs | 38 ++++++---------- 3 files changed, 82 insertions(+), 37 deletions(-) diff --git a/Examples/Program.cs b/Examples/Program.cs index 9f7107b..1796da3 100755 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -1,7 +1,10 @@ -using System; -using System.Threading.Tasks; -using DotnetBleServer.Core; +using DotnetBleServer.Core; +using DotnetBleServer.Gatt; using DotnetBleServer.Utilities; +using System; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; namespace Examples { @@ -21,31 +24,82 @@ private static void Main() string adapterPath = (await AdapterExtensions.GetBluetoothAdapters(serverContext.Connection))[0]; - // Trying to turn on the bluetooth device within the code. - Console.WriteLine($"Trying to turn on the bluetooth adapter: {adapterPath}"); + Console.WriteLine($"Current bluetooth adapter: {adapterPath}"); + // Retrieve adapter IAdapter1 BLEAdapter = serverContext.Connection.CreateProxy("org.bluez", adapterPath); // Uncomment if needed. //await Adapter1Extensions.SetAliasAsync(BLEAdapter, "Debian (raspberry Pi)"); //await Adapter1Extensions.SetDiscoverableAsync(BLEAdapter, true); + //await Adapter1Extensions.SetPairableAsync(BLEAdapter, true); + // Turn on adapter. await Adapter1Extensions.SetPoweredAsync(BLEAdapter, true); if (!await Adapter1Extensions.GetPoweredAsync(BLEAdapter)) throw new Exception($"Can't power on adapter {adapterPath}"); await SampleAdvertisement.RegisterSampleAdvertisement(serverContext, adapterPath); - await SampleGattApplication.RegisterGattApplication(serverContext, adapterPath); - - Console.WriteLine("Press ENTER to quit"); - Console.ReadLine(); - // Power off adapter (Bluetooth is no more Discoverable) - await Adapter1Extensions.SetPoweredAsync(BLEAdapter, false); + var source = new ExampleCharacteristicSource() { ExecuteMeDuringWrite = CommandReceived }; + await SampleGattApplication.RegisterGattApplication(serverContext, adapterPath, source); + Console.WriteLine("Press Ctrl+C to quit"); + await Task.Delay(-1); } }).Wait(); } + + private static Task CommandReceived(YourDTOClass parameter) + { + Console.WriteLine("Executing some code..."); + Console.WriteLine(parameter.SomeProperty); + + return Task.FromResult(JsonSerializer.Serialize(new YourDTOClass() { SomeProperty = "Whatever you want to execute during Write, before returning new value to Read" })); + } + + class YourDTOClass + { + public string SomeProperty { get; set; } + } + + class ExampleCharacteristicSource : ICharacteristicSource + { + public Func> ExecuteMeDuringWrite { get; set; } + + private string _data; + + public async Task WriteValueAsync(byte[] value) + { + Console.WriteLine("Writing value"); + + string message = new string(Encoding.ASCII.GetChars(value)); + + Console.WriteLine($"Value received: {message}"); + + await DoSomeStuff(message); + } + + public Task ReadValueAsync() + { + Console.WriteLine("Reading value"); + return Task.FromResult(Encoding.ASCII.GetBytes(_data)); + } + + /// + /// Could be used to notify or execute code according to received value from client + /// + /// + /// + private async Task DoSomeStuff(string receivedValue) + { + // Considering the receivedValue is serialized data as { SomeProperty = \"data\" } + var deserializedObject = JsonSerializer.Deserialize(receivedValue); + + string serializedData = await ExecuteMeDuringWrite(deserializedObject); + _data = serializedData; + } + } } } diff --git a/Examples/SampleAdvertisement.cs b/Examples/SampleAdvertisement.cs index abe97a8..267ddd5 100755 --- a/Examples/SampleAdvertisement.cs +++ b/Examples/SampleAdvertisement.cs @@ -11,7 +11,7 @@ public class SampleAdvertisement public static async Task RegisterSampleAdvertisement(ServerContext serverContext, string adapterPath) { var advertisementProperties = new LEAdvertisement1Properties - { + { Type = "peripheral", ServiceUUIDs = new[] { "E11EFB5D-E8BD-46B5-814A-1C4322F80067" }, LocalName = "DISPLAYED NAME", @@ -19,7 +19,8 @@ public static async Task RegisterSampleAdvertisement(ServerContext serverContext // I don't know why, for some reasons, changing appearance is not always working. Appearance = (ushort)Convert.ToUInt32("0x0080", 16), // Set to false if Type = "broadcast" - Discoverable = true, + Discoverable = true, + IncludeTxPower = true, }; await new AdvertisingManager(serverContext, adapterPath).CreateAdvertisement(advertisementProperties); diff --git a/Examples/SampleGattApplication.cs b/Examples/SampleGattApplication.cs index 966a521..bead65e 100755 --- a/Examples/SampleGattApplication.cs +++ b/Examples/SampleGattApplication.cs @@ -1,5 +1,8 @@ using System; +using System.Net.Http.Json; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using DotnetBleServer.Core; using DotnetBleServer.Gatt; @@ -7,49 +10,36 @@ namespace Examples { - internal class SampleGattApplication + internal static class SampleGattApplication { - public static async Task RegisterGattApplication(ServerContext serverContext, string adapterPath) + public static async Task RegisterGattApplication(ServerContext serverContext, string adapterPath, ICharacteristicSource source) { var gattServiceDescription = new GattServiceDescription { - UUID = "12345678-1234-5678-1234-56789abcdef0", + UUID = "E11EFB5D-E8BD-46B5-814A-1C4322F80067", Primary = true }; var gattCharacteristicDescription = new GattCharacteristicDescription { - CharacteristicSource = new ExampleCharacteristicSource(), - UUID = "12345678-1234-5678-1234-56789abcdef1", - Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries + CharacteristicSource = source, + UUID = "3929F0D8-D461-43B0-BCF3-DF228CDD4A35", + Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify }; + var gattDescriptorDescription = new GattDescriptorDescription { Value = new[] {(byte) 't'}, - UUID = "12345678-1234-5678-1234-56789abcdef2", + UUID = "3929F0D8-D461-43B0-BCF3-DF228CDD4A35", Flags = new[] {"read", "write"} }; - var gab = new GattApplicationBuilder(); + + var gab = new GattApplicationBuilder(); gab .AddService(gattServiceDescription) .WithCharacteristic(gattCharacteristicDescription, new[] {gattDescriptorDescription}); - + await new GattApplicationManager(serverContext, adapterPath).RegisterGattApplication(gab.BuildServiceDescriptions()); } - - internal class ExampleCharacteristicSource : ICharacteristicSource - { - public Task WriteValueAsync(byte[] value) - { - Console.WriteLine("Writing value"); - return Task.Run(() => Console.WriteLine(Encoding.ASCII.GetChars(value))); - } - - public Task ReadValueAsync() - { - Console.WriteLine("Reading value"); - return Task.FromResult(Encoding.ASCII.GetBytes("Hello BLE")); - } - } } } \ No newline at end of file From 296629bfc57ad188a8764e4fd0323847bf874cd7 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 22 Nov 2023 15:47:49 +0100 Subject: [PATCH 03/13] fix: #9 NextCharacteristicPath() being called twice --- DotnetBleServer/Gatt/BlueZModel/GattService.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DotnetBleServer/Gatt/BlueZModel/GattService.cs b/DotnetBleServer/Gatt/BlueZModel/GattService.cs index f873acf..04297cc 100755 --- a/DotnetBleServer/Gatt/BlueZModel/GattService.cs +++ b/DotnetBleServer/Gatt/BlueZModel/GattService.cs @@ -32,10 +32,12 @@ public IDictionary> GetProperties() public GattCharacteristic AddCharacteristic(GattCharacteristic1Properties characteristic, ICharacteristicSource characteristicSource) { characteristic.Service = ObjectPath; - var gattCharacteristic = new GattCharacteristic(NextCharacteristicPath(), characteristic, characteristicSource); + var characteristicPath = NextCharacteristicPath(); + + var gattCharacteristic = new GattCharacteristic(characteristicPath, characteristic, characteristicSource); _Characteristics.Add(gattCharacteristic); - Properties.Characteristics = Properties.Characteristics.Append(NextCharacteristicPath()).ToArray(); + Properties.Characteristics = Properties.Characteristics.Append(characteristicPath).ToArray(); return gattCharacteristic; } From ed98ad2bbf867c6c801f7ddff8e1e4d0b0f03832 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 22 Nov 2023 15:49:23 +0100 Subject: [PATCH 04/13] version: updated to NET7.0 --- DotnetBleServer/DotnetBleServer.csproj | 1 - .../Properties/PublishProfiles/FolderProfile.pubxml | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DotnetBleServer/DotnetBleServer.csproj b/DotnetBleServer/DotnetBleServer.csproj index 499d110..9870f0d 100755 --- a/DotnetBleServer/DotnetBleServer.csproj +++ b/DotnetBleServer/DotnetBleServer.csproj @@ -20,7 +20,6 @@ - \ No newline at end of file diff --git a/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml b/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml index 825527c..d348e50 100644 --- a/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml +++ b/DotnetBleServer/Properties/PublishProfiles/FolderProfile.pubxml @@ -6,8 +6,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Release Any CPU - bin\Release\netstandard2.1\publish\ + bin\Release\net7.0\publish\ FileSystem <_TargetId>Folder + net7.0 + false \ No newline at end of file From 2c2540290e417d6983b455a4f1cb92ab0032f892 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 22 Nov 2023 15:50:29 +0100 Subject: [PATCH 05/13] feature: add notify flag --- DotnetBleServer/Gatt/BlueZModel/CharacteristicFlagConverter.cs | 1 + DotnetBleServer/Gatt/Description/CharacteristicFlags.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/DotnetBleServer/Gatt/BlueZModel/CharacteristicFlagConverter.cs b/DotnetBleServer/Gatt/BlueZModel/CharacteristicFlagConverter.cs index b8218d3..7b6541c 100755 --- a/DotnetBleServer/Gatt/BlueZModel/CharacteristicFlagConverter.cs +++ b/DotnetBleServer/Gatt/BlueZModel/CharacteristicFlagConverter.cs @@ -12,6 +12,7 @@ internal class CharacteristicFlagConverter { {CharacteristicFlags.Read, "read"}, {CharacteristicFlags.Write, "write"}, + {CharacteristicFlags.Notify, "notify"}, {CharacteristicFlags.WritableAuxiliaries, "writable-auxiliaries"} }; diff --git a/DotnetBleServer/Gatt/Description/CharacteristicFlags.cs b/DotnetBleServer/Gatt/Description/CharacteristicFlags.cs index 6c17428..2d5af1b 100755 --- a/DotnetBleServer/Gatt/Description/CharacteristicFlags.cs +++ b/DotnetBleServer/Gatt/Description/CharacteristicFlags.cs @@ -7,6 +7,7 @@ public enum CharacteristicFlags { Read = 1, Write = 2, + Notify = 3, WritableAuxiliaries = 4 } } \ No newline at end of file From 27afbbce4a9c6d2bb8d2fff4a511571ce7155d35 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 22 Nov 2023 17:28:20 +0100 Subject: [PATCH 06/13] feature: notify working (tested using the Plugin.BLE client for MAUI and got ValueUpdated raised) improve: optimized syntax to keep hands on Write and Read methods --- .../Gatt/BlueZModel/GattCharacteristic.cs | 9 +-- .../Gatt/BlueZModel/GattService.cs | 2 +- .../Gatt/CharacteristicUpdatedEventArgs.cs | 23 +++++++ .../GattCharacteristicDescription.cs | 35 +++++++++-- .../Gatt/GattApplicationManager.cs | 2 +- DotnetBleServer/Gatt/ICharacteristic.cs | 39 ++++++++++++ DotnetBleServer/Gatt/ICharacteristicSource.cs | 10 --- Examples/Program.cs | 63 +------------------ Examples/SampleAdvertisement.cs | 6 +- Examples/SampleGattApplication.cs | 46 +++++++++----- 10 files changed, 135 insertions(+), 100 deletions(-) create mode 100644 DotnetBleServer/Gatt/CharacteristicUpdatedEventArgs.cs create mode 100644 DotnetBleServer/Gatt/ICharacteristic.cs delete mode 100755 DotnetBleServer/Gatt/ICharacteristicSource.cs diff --git a/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs b/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs index a20ead8..a49c9b5 100755 --- a/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs +++ b/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs @@ -11,9 +11,9 @@ internal class GattCharacteristic : PropertiesBase Descriptors { get; } = new List(); - private readonly ICharacteristicSource _CharacteristicSource; + private readonly ICharacteristic _CharacteristicSource; - public GattCharacteristic(ObjectPath objectPath, GattCharacteristic1Properties properties, ICharacteristicSource characteristicSource) : base(objectPath, properties) + public GattCharacteristic(ObjectPath objectPath, GattCharacteristic1Properties properties, ICharacteristic characteristicSource) : base(objectPath, properties) { _CharacteristicSource = characteristicSource; } @@ -30,12 +30,12 @@ public Task WriteValueAsync(byte[] value, IDictionary options) public Task StartNotifyAsync() { - throw new NotImplementedException(); + return _CharacteristicSource.StartUpdatesAsync(); } public Task StopNotifyAsync() { - throw new NotImplementedException(); + return _CharacteristicSource.StopUpdatesAsync(); } public IDictionary> GetProperties() @@ -48,6 +48,7 @@ public IDictionary> GetProperties() {"Service", Properties.Service}, {"UUID", Properties.UUID}, {"Flags", Properties.Flags}, + {"Notifying", Properties.Notifying }, {"Descriptors", Descriptors.Select(d => d.ObjectPath).ToArray()} } } diff --git a/DotnetBleServer/Gatt/BlueZModel/GattService.cs b/DotnetBleServer/Gatt/BlueZModel/GattService.cs index 04297cc..9368411 100755 --- a/DotnetBleServer/Gatt/BlueZModel/GattService.cs +++ b/DotnetBleServer/Gatt/BlueZModel/GattService.cs @@ -29,7 +29,7 @@ public IDictionary> GetProperties() }; } - public GattCharacteristic AddCharacteristic(GattCharacteristic1Properties characteristic, ICharacteristicSource characteristicSource) + public GattCharacteristic AddCharacteristic(GattCharacteristic1Properties characteristic, ICharacteristic characteristicSource) { characteristic.Service = ObjectPath; var characteristicPath = NextCharacteristicPath(); diff --git a/DotnetBleServer/Gatt/CharacteristicUpdatedEventArgs.cs b/DotnetBleServer/Gatt/CharacteristicUpdatedEventArgs.cs new file mode 100644 index 0000000..3713d88 --- /dev/null +++ b/DotnetBleServer/Gatt/CharacteristicUpdatedEventArgs.cs @@ -0,0 +1,23 @@ +using System; + +namespace DotnetBleServer.Gatt +{ + /// + /// Event arguments for ICharacteristic.ValueUpdated + /// + public class CharacteristicUpdatedEventArgs : EventArgs + { + /// + /// The characteristic. + /// + public ICharacteristic Characteristic { get; set; } + + /// + /// Constructor. + /// + public CharacteristicUpdatedEventArgs(ICharacteristic characteristic) + { + Characteristic = characteristic; + } + } +} \ No newline at end of file diff --git a/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs b/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs index 7d1b2d3..b5d31dd 100755 --- a/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs +++ b/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs @@ -1,21 +1,48 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace DotnetBleServer.Gatt.Description { - public class GattCharacteristicDescription + public abstract class GattCharacteristicDescription : ICharacteristic { + private bool _notify; private readonly IList _Descriptors = new List(); public IEnumerable Descriptors => _Descriptors; - public ICharacteristicSource CharacteristicSource { get; set; } - public string UUID { get; set; } + public CharacteristicFlags Flags { get; set; } public void AddDescriptor(GattDescriptorDescription gattDescriptorDescription) { _Descriptors.Add(gattDescriptorDescription); } + + public bool CanUpdate => Flags.HasFlag(CharacteristicFlags.Notify); + + public event EventHandler ValueUpdated; + + public virtual Task WriteValueAsync(byte[] value) + { + if (_notify) + ValueUpdated?.Invoke(this, new CharacteristicUpdatedEventArgs(this)); + + return Task.FromResult(0); + } + + public abstract Task ReadValueAsync(); + + public Task StartUpdatesAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(() => _notify = true); + } + + public Task StopUpdatesAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(() => _notify = false); + } } } \ No newline at end of file diff --git a/DotnetBleServer/Gatt/GattApplicationManager.cs b/DotnetBleServer/Gatt/GattApplicationManager.cs index b583cf8..06b41bf 100755 --- a/DotnetBleServer/Gatt/GattApplicationManager.cs +++ b/DotnetBleServer/Gatt/GattApplicationManager.cs @@ -78,7 +78,7 @@ private async Task AddNewService(GattApplication application, private async Task AddNewCharacteristic(GattService gattService, GattCharacteristicDescription characteristic) { var gattCharacteristic1Properties = GattPropertiesFactory.CreateGattCharacteristic(characteristic); - var gattCharacteristic = gattService.AddCharacteristic(gattCharacteristic1Properties, characteristic.CharacteristicSource); + var gattCharacteristic = gattService.AddCharacteristic(gattCharacteristic1Properties, characteristic); await _ServerContext.Connection.RegisterObjectAsync(gattCharacteristic); return gattCharacteristic; } diff --git a/DotnetBleServer/Gatt/ICharacteristic.cs b/DotnetBleServer/Gatt/ICharacteristic.cs new file mode 100644 index 0000000..6946ca7 --- /dev/null +++ b/DotnetBleServer/Gatt/ICharacteristic.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace DotnetBleServer.Gatt +{ + public interface ICharacteristic + { + /// + /// Indicates wheter the characteristic supports notify or not. + /// + bool CanUpdate { get; } + + /// + /// Event gets raised, when the davice notifies a value change on this characteristic. + /// To start listening, call . + /// + event EventHandler ValueUpdated; + + Task WriteValueAsync(byte[] value); + + Task ReadValueAsync(); + + /// + /// Starts listening for notify events on this characteristic. + /// + /// + /// Thrown if characteristic doesn't support notify. See: + /// Thrown if an error occurs while starting notifications + Task StartUpdatesAsync(CancellationToken cancellationToken = default); + + /// + /// Stops listening for notify events on this characteristic. + /// + /// + /// Thrown if an error occurs while starting notifications + Task StopUpdatesAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/DotnetBleServer/Gatt/ICharacteristicSource.cs b/DotnetBleServer/Gatt/ICharacteristicSource.cs deleted file mode 100755 index f125046..0000000 --- a/DotnetBleServer/Gatt/ICharacteristicSource.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; - -namespace DotnetBleServer.Gatt -{ - public interface ICharacteristicSource - { - Task WriteValueAsync(byte[] value); - Task ReadValueAsync(); - } -} \ No newline at end of file diff --git a/Examples/Program.cs b/Examples/Program.cs index 1796da3..516c126 100755 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -1,9 +1,6 @@ using DotnetBleServer.Core; -using DotnetBleServer.Gatt; using DotnetBleServer.Utilities; using System; -using System.Text; -using System.Text.Json; using System.Threading.Tasks; namespace Examples @@ -29,11 +26,6 @@ private static void Main() // Retrieve adapter IAdapter1 BLEAdapter = serverContext.Connection.CreateProxy("org.bluez", adapterPath); - // Uncomment if needed. - //await Adapter1Extensions.SetAliasAsync(BLEAdapter, "Debian (raspberry Pi)"); - //await Adapter1Extensions.SetDiscoverableAsync(BLEAdapter, true); - //await Adapter1Extensions.SetPairableAsync(BLEAdapter, true); - // Turn on adapter. await Adapter1Extensions.SetPoweredAsync(BLEAdapter, true); @@ -41,65 +33,12 @@ private static void Main() throw new Exception($"Can't power on adapter {adapterPath}"); await SampleAdvertisement.RegisterSampleAdvertisement(serverContext, adapterPath); - - var source = new ExampleCharacteristicSource() { ExecuteMeDuringWrite = CommandReceived }; - await SampleGattApplication.RegisterGattApplication(serverContext, adapterPath, source); + await SampleGattApplication.RegisterGattApplication(serverContext, adapterPath); Console.WriteLine("Press Ctrl+C to quit"); await Task.Delay(-1); } }).Wait(); } - - private static Task CommandReceived(YourDTOClass parameter) - { - Console.WriteLine("Executing some code..."); - Console.WriteLine(parameter.SomeProperty); - - return Task.FromResult(JsonSerializer.Serialize(new YourDTOClass() { SomeProperty = "Whatever you want to execute during Write, before returning new value to Read" })); - } - - class YourDTOClass - { - public string SomeProperty { get; set; } - } - - class ExampleCharacteristicSource : ICharacteristicSource - { - public Func> ExecuteMeDuringWrite { get; set; } - - private string _data; - - public async Task WriteValueAsync(byte[] value) - { - Console.WriteLine("Writing value"); - - string message = new string(Encoding.ASCII.GetChars(value)); - - Console.WriteLine($"Value received: {message}"); - - await DoSomeStuff(message); - } - - public Task ReadValueAsync() - { - Console.WriteLine("Reading value"); - return Task.FromResult(Encoding.ASCII.GetBytes(_data)); - } - - /// - /// Could be used to notify or execute code according to received value from client - /// - /// - /// - private async Task DoSomeStuff(string receivedValue) - { - // Considering the receivedValue is serialized data as { SomeProperty = \"data\" } - var deserializedObject = JsonSerializer.Deserialize(receivedValue); - - string serializedData = await ExecuteMeDuringWrite(deserializedObject); - _data = serializedData; - } - } } } diff --git a/Examples/SampleAdvertisement.cs b/Examples/SampleAdvertisement.cs index 267ddd5..cec5c1b 100755 --- a/Examples/SampleAdvertisement.cs +++ b/Examples/SampleAdvertisement.cs @@ -11,15 +11,15 @@ public class SampleAdvertisement public static async Task RegisterSampleAdvertisement(ServerContext serverContext, string adapterPath) { var advertisementProperties = new LEAdvertisement1Properties - { + { Type = "peripheral", - ServiceUUIDs = new[] { "E11EFB5D-E8BD-46B5-814A-1C4322F80067" }, + ServiceUUIDs = new[] { "12345678-1234-5678-1234-56789abcdef0" }, LocalName = "DISPLAYED NAME", // Check bluetooth spec here: https://www.bluetooth.com/specifications/an/ (Computer appearance is 64 => convert hex value to uint: 0x0080 = 64) // I don't know why, for some reasons, changing appearance is not always working. Appearance = (ushort)Convert.ToUInt32("0x0080", 16), // Set to false if Type = "broadcast" - Discoverable = true, + Discoverable = true, IncludeTxPower = true, }; diff --git a/Examples/SampleGattApplication.cs b/Examples/SampleGattApplication.cs index bead65e..0406a35 100755 --- a/Examples/SampleGattApplication.cs +++ b/Examples/SampleGattApplication.cs @@ -1,18 +1,15 @@ -using System; -using System.Net.Http.Json; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using DotnetBleServer.Core; +using DotnetBleServer.Core; using DotnetBleServer.Gatt; using DotnetBleServer.Gatt.Description; +using System; +using System.Text; +using System.Threading.Tasks; namespace Examples { internal static class SampleGattApplication { - public static async Task RegisterGattApplication(ServerContext serverContext, string adapterPath, ICharacteristicSource source) + public static async Task RegisterGattApplication(ServerContext serverContext, string adapterPath) { var gattServiceDescription = new GattServiceDescription { @@ -20,26 +17,45 @@ public static async Task RegisterGattApplication(ServerContext serverContext, st Primary = true }; - var gattCharacteristicDescription = new GattCharacteristicDescription + var gattCharacteristicDescription = new MyCustomGattCharacteristicDescription { - CharacteristicSource = source, UUID = "3929F0D8-D461-43B0-BCF3-DF228CDD4A35", Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify }; var gattDescriptorDescription = new GattDescriptorDescription { - Value = new[] {(byte) 't'}, + Value = new[] { (byte)'t' }, UUID = "3929F0D8-D461-43B0-BCF3-DF228CDD4A35", - Flags = new[] {"read", "write"} + Flags = new[] { "read", "write" } }; - var gab = new GattApplicationBuilder(); + var gab = new GattApplicationBuilder(); gab .AddService(gattServiceDescription) - .WithCharacteristic(gattCharacteristicDescription, new[] {gattDescriptorDescription}); - + .WithCharacteristic(gattCharacteristicDescription, new[] { gattDescriptorDescription }); + await new GattApplicationManager(serverContext, adapterPath).RegisterGattApplication(gab.BuildServiceDescriptions()); } } + + class MyCustomGattCharacteristicDescription : GattCharacteristicDescription + { + public override Task WriteValueAsync(byte[] value) + { + Console.WriteLine("Writing value"); + + string message = new string(Encoding.ASCII.GetChars(value)); + + Console.WriteLine($"Value received: {message}"); + + return base.WriteValueAsync(value); + } + + public override Task ReadValueAsync() + { + Console.WriteLine("Reading value"); + return Task.FromResult(Encoding.ASCII.GetBytes("Some data")); + } + } } \ No newline at end of file From 980a3ba23a9a8d1945baf6a54ba234755c1def70 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 22 Nov 2023 17:30:06 +0100 Subject: [PATCH 07/13] doc: restored default sample UUIDs --- Examples/SampleGattApplication.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/SampleGattApplication.cs b/Examples/SampleGattApplication.cs index 0406a35..a77fe34 100755 --- a/Examples/SampleGattApplication.cs +++ b/Examples/SampleGattApplication.cs @@ -13,20 +13,20 @@ public static async Task RegisterGattApplication(ServerContext serverContext, st { var gattServiceDescription = new GattServiceDescription { - UUID = "E11EFB5D-E8BD-46B5-814A-1C4322F80067", + UUID = "12345678-1234-5678-1234-56789abcdef0", Primary = true }; var gattCharacteristicDescription = new MyCustomGattCharacteristicDescription { - UUID = "3929F0D8-D461-43B0-BCF3-DF228CDD4A35", + UUID = "12345678-1234-5678-1234-56789abcdef1", Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify }; var gattDescriptorDescription = new GattDescriptorDescription { Value = new[] { (byte)'t' }, - UUID = "3929F0D8-D461-43B0-BCF3-DF228CDD4A35", + UUID = "12345678-1234-5678-1234-56789abcdef2", Flags = new[] { "read", "write" } }; From b5e96b8745412475442ce35870d526b261d722c1 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 23 Nov 2023 01:29:16 +0100 Subject: [PATCH 08/13] clean: add BlueZManager to improve readness --- .../Advertisements/AdvertisingManager.cs | 28 +++---- DotnetBleServer/Core/BlueZManager.cs | 80 +++++++++++++++++++ DotnetBleServer/Core/Constants.cs | 8 ++ .../Gatt/BlueZModel/GattCharacteristic.cs | 5 ++ .../GattCharacteristicDescription.cs | 2 +- .../Gatt/GattApplicationManager.cs | 26 +++--- .../Utilities/AdapterExtensions.cs | 28 ------- Examples/Program.cs | 31 ++++--- Examples/SampleAdvertisement.cs | 4 +- Examples/SampleGattApplication.cs | 4 +- 10 files changed, 142 insertions(+), 74 deletions(-) create mode 100644 DotnetBleServer/Core/BlueZManager.cs create mode 100644 DotnetBleServer/Core/Constants.cs delete mode 100644 DotnetBleServer/Utilities/AdapterExtensions.cs diff --git a/DotnetBleServer/Advertisements/AdvertisingManager.cs b/DotnetBleServer/Advertisements/AdvertisingManager.cs index a3672b8..a2573a9 100755 --- a/DotnetBleServer/Advertisements/AdvertisingManager.cs +++ b/DotnetBleServer/Advertisements/AdvertisingManager.cs @@ -1,43 +1,41 @@ -using System; +using DotnetBleServer.Core; +using DotnetBleServer.Gatt.BlueZModel; +using System; using System.Collections.Generic; using System.Threading.Tasks; -using DotnetBleServer.Core; -using DotnetBleServer.Gatt.BlueZModel; using Tmds.DBus; namespace DotnetBleServer.Advertisements { public class AdvertisingManager { - private readonly ServerContext _Context; - private readonly string _AdapterPath; + private readonly ServerContext _context; + private readonly IAdapter1 _adapter; - public AdvertisingManager(ServerContext context, string adapterPath) + public AdvertisingManager(ServerContext context, IAdapter1 adapter) { - _Context = context; - _AdapterPath = adapterPath; + _context = context; + _adapter = adapter; } public async Task RegisterAdvertisement(Advertisement advertisement) { - await _Context.Connection.RegisterObjectAsync(advertisement); + await _context.Connection.RegisterObjectAsync(advertisement); Console.WriteLine($"advertisement object {advertisement.ObjectPath} created"); - await GetAdvertisingManager().RegisterAdvertisementAsync(((IDBusObject)advertisement).ObjectPath, - new Dictionary()); - + await GetAdvertisingManager().RegisterAdvertisementAsync(((IDBusObject)advertisement).ObjectPath, new Dictionary()); Console.WriteLine($"advertisement {advertisement.ObjectPath} registered in BlueZ advertising manager"); } private ILEAdvertisingManager1 GetAdvertisingManager() { - return _Context.Connection.CreateProxy("org.bluez", new ObjectPath(_AdapterPath)); + return _context.Connection.CreateProxy(Constants.DbusServicePath, _adapter.ObjectPath); } public async Task CreateAdvertisement(LEAdvertisement1Properties advertisementProperties) { - var advertisement = new Advertisement($"{_AdapterPath}/advertisement0", advertisementProperties); - await new AdvertisingManager(_Context, _AdapterPath).RegisterAdvertisement(advertisement); + var advertisement = new Advertisement($"{_adapter.ObjectPath}/advertisement0", advertisementProperties); + await new AdvertisingManager(_context, _adapter).RegisterAdvertisement(advertisement); } } } \ No newline at end of file diff --git a/DotnetBleServer/Core/BlueZManager.cs b/DotnetBleServer/Core/BlueZManager.cs new file mode 100644 index 0000000..a34cb95 --- /dev/null +++ b/DotnetBleServer/Core/BlueZManager.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tmds.DBus; + +namespace DotnetBleServer.Core +{ + public class BlueZManager + { + private ServerContext _context; + + public BlueZManager(ServerContext context) + { + _context = context; + } + + /// Get Bluetooth Adapter object based on name. + /// Adapter Name. + /// False allows shortened adapter name ('hci0'). True, for full name ('/org/bluez/hci0'). + /// Adapter object + /// + public async Task GetAdapterAsync(string adapterName, bool fullName = false) + { + var adapterObjectPath = !fullName ? $"/org/bluez/{adapterName}" : adapterName; + var adapter = _context.Connection.CreateProxy(Constants.DbusServicePath, adapterObjectPath); + + try + { + await adapter.GetAliasAsync(); + } + catch (Exception) + { + throw new Exception($"Bluetooth adapter {adapterName} not found."); + } + + return adapter; + } + + public async Task> GetAdaptersAsync() + { + return await GetProxiesAsync(Constants.AdapterInterfacePath, rootObject: null); + } + + /// The interface to search for + /// The DBus object to search under. Can be null + internal async Task> GetProxiesAsync(string interfaceName, IDBusObject rootObject) + { + // Console.WriteLine("GetProxiesAsync called."); + var objectManager = _context.Connection.CreateProxy(Constants.DbusServicePath, "/"); + var objects = await objectManager.GetManagedObjectsAsync(); + + var matchingObjectPaths = objects + .Where(obj => IsMatch(interfaceName, obj.Key, obj.Value, rootObject)) + .Select(obj => obj.Key); + + var proxies = matchingObjectPaths + .Select(objectPath => _context.Connection.CreateProxy(Constants.DbusServicePath, objectPath)) + .ToList(); + + Console.WriteLine($"GetProxiesAsync returning {proxies.Count} proxies of type {typeof(T)}."); + return proxies; + } + + internal static bool IsMatch(string interfaceName, ObjectPath objectPath, IDictionary> interfaces, IDBusObject rootObject) + { + return IsMatch(interfaceName, objectPath, interfaces.Keys, rootObject); + } + + internal static bool IsMatch(string interfaceName, ObjectPath objectPath, ICollection interfaces, IDBusObject rootObject) + { + if (rootObject != null && !objectPath.ToString().StartsWith($"{rootObject.ObjectPath}/")) + { + return false; + } + + return interfaces.Contains(interfaceName); + } + } +} \ No newline at end of file diff --git a/DotnetBleServer/Core/Constants.cs b/DotnetBleServer/Core/Constants.cs new file mode 100644 index 0000000..bfe8f82 --- /dev/null +++ b/DotnetBleServer/Core/Constants.cs @@ -0,0 +1,8 @@ +namespace DotnetBleServer.Core +{ + public static class Constants + { + public const string DbusServicePath = "org.bluez"; + public const string AdapterInterfacePath = "org.bluez.Adapter1"; + } +} diff --git a/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs b/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs index a49c9b5..3ef2a02 100755 --- a/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs +++ b/DotnetBleServer/Gatt/BlueZModel/GattCharacteristic.cs @@ -30,6 +30,11 @@ public Task WriteValueAsync(byte[] value, IDictionary options) public Task StartNotifyAsync() { + if (Properties.Notifying) + { + return Task.CompletedTask; + } + return _CharacteristicSource.StartUpdatesAsync(); } diff --git a/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs b/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs index b5d31dd..f55471f 100755 --- a/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs +++ b/DotnetBleServer/Gatt/Description/GattCharacteristicDescription.cs @@ -30,7 +30,7 @@ public virtual Task WriteValueAsync(byte[] value) if (_notify) ValueUpdated?.Invoke(this, new CharacteristicUpdatedEventArgs(this)); - return Task.FromResult(0); + return Task.CompletedTask; } public abstract Task ReadValueAsync(); diff --git a/DotnetBleServer/Gatt/GattApplicationManager.cs b/DotnetBleServer/Gatt/GattApplicationManager.cs index 06b41bf..f98de92 100755 --- a/DotnetBleServer/Gatt/GattApplicationManager.cs +++ b/DotnetBleServer/Gatt/GattApplicationManager.cs @@ -10,13 +10,13 @@ namespace DotnetBleServer.Gatt { public class GattApplicationManager { - private readonly ServerContext _ServerContext; - private readonly string _AdapterPath; + private readonly ServerContext _context; + private readonly IAdapter1 _adapter; - public GattApplicationManager(ServerContext serverContext, string adapterPath) + public GattApplicationManager(ServerContext context, IAdapter1 adapter) { - _ServerContext = serverContext; - _AdapterPath = adapterPath; + _context = context; + _adapter = adapter; } public async Task RegisterGattApplication(IEnumerable gattServiceDescriptions) @@ -55,23 +55,22 @@ private async Task BuildApplicationTree(string applicationObjectPath, IEnumerabl private async Task RegisterApplicationInBluez(string applicationObjectPath) { - var gattManager = _ServerContext.Connection.CreateProxy("org.bluez", new ObjectPath(_AdapterPath)); + var gattManager = _context.Connection.CreateProxy(Constants.DbusServicePath, _adapter.ObjectPath); await gattManager.RegisterApplicationAsync(new ObjectPath(applicationObjectPath), new Dictionary()); } private async Task BuildGattApplication(string applicationObjectPath) { var application = new GattApplication(applicationObjectPath); - await _ServerContext.Connection.RegisterObjectAsync(application); + await _context.Connection.RegisterObjectAsync(application); return application; } - private async Task AddNewService(GattApplication application, - GattServiceDescription serviceDescription) + private async Task AddNewService(GattApplication application, GattServiceDescription serviceDescription) { var gattService1Properties = GattPropertiesFactory.CreateGattService(serviceDescription); var gattService = application.AddService(gattService1Properties); - await _ServerContext.Connection.RegisterObjectAsync(gattService); + await _context.Connection.RegisterObjectAsync(gattService); return gattService; } @@ -79,16 +78,15 @@ private async Task AddNewCharacteristic(GattService gattServ { var gattCharacteristic1Properties = GattPropertiesFactory.CreateGattCharacteristic(characteristic); var gattCharacteristic = gattService.AddCharacteristic(gattCharacteristic1Properties, characteristic); - await _ServerContext.Connection.RegisterObjectAsync(gattCharacteristic); + await _context.Connection.RegisterObjectAsync(gattCharacteristic); return gattCharacteristic; } - private async Task AddNewDescriptor(GattCharacteristic gattCharacteristic, - GattDescriptorDescription descriptor) + private async Task AddNewDescriptor(GattCharacteristic gattCharacteristic, GattDescriptorDescription descriptor) { var gattDescriptor1Properties = GattPropertiesFactory.CreateGattDescriptor(descriptor); var gattDescriptor = gattCharacteristic.AddDescriptor(gattDescriptor1Properties); - await _ServerContext.Connection.RegisterObjectAsync(gattDescriptor); + await _context.Connection.RegisterObjectAsync(gattDescriptor); } } } \ No newline at end of file diff --git a/DotnetBleServer/Utilities/AdapterExtensions.cs b/DotnetBleServer/Utilities/AdapterExtensions.cs deleted file mode 100644 index cfcf912..0000000 --- a/DotnetBleServer/Utilities/AdapterExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using DotnetBleServer.Core; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Tmds.DBus; - -namespace DotnetBleServer.Utilities -{ - public class AdapterExtensions - { - private const string BluezPrependDevicePath = "/org/bluez/"; - public async static Task> GetBluetoothAdapters(Connection dbusConnection) - { - List bluezDeviceList = new List(); - foreach (var itemDict in await dbusConnection.CreateProxy("org.bluez", "/").GetManagedObjectsAsync()) - { - string keyString = itemDict.Key.ToString(); - if (keyString.Length < BluezPrependDevicePath.Length) - continue; - - bluezDeviceList.Add(keyString); - } - - return bluezDeviceList; - } - } -} \ No newline at end of file diff --git a/Examples/Program.cs b/Examples/Program.cs index 516c126..3279cd4 100755 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -1,6 +1,6 @@ using DotnetBleServer.Core; -using DotnetBleServer.Utilities; using System; +using System.Linq; using System.Threading.Tasks; namespace Examples @@ -15,29 +15,36 @@ private static void Main() Task.Run(async () => { - using (var serverContext = new ServerContext()) + using (ServerContext context = new ServerContext()) { - await serverContext.Connect(); + // Mandatory for BLE Advertisement (connection must be manual) (ie. Tmds.DBus.Connection.RegisterObject methods) + await context.Connect(); - string adapterPath = (await AdapterExtensions.GetBluetoothAdapters(serverContext.Connection))[0]; + var manager = new BlueZManager(context); - Console.WriteLine($"Current bluetooth adapter: {adapterPath}"); + var adapters = await manager.GetAdaptersAsync(); + if (adapters.Count == 0) + { + throw new Exception("No Bluetooth adapters found."); + } - // Retrieve adapter - IAdapter1 BLEAdapter = serverContext.Connection.CreateProxy("org.bluez", adapterPath); + var adapter = adapters.First(); + + Console.WriteLine($"Current bluetooth adapter: {adapter.ObjectPath}"); // Turn on adapter. - await Adapter1Extensions.SetPoweredAsync(BLEAdapter, true); + await adapter.SetPoweredAsync(true); - if (!await Adapter1Extensions.GetPoweredAsync(BLEAdapter)) - throw new Exception($"Can't power on adapter {adapterPath}"); + if (!await adapter.GetPoweredAsync()) + throw new Exception($"Can't power on adapter {adapter.ObjectPath}"); - await SampleAdvertisement.RegisterSampleAdvertisement(serverContext, adapterPath); - await SampleGattApplication.RegisterGattApplication(serverContext, adapterPath); + await SampleAdvertisement.RegisterSampleAdvertisement(context, adapter); + await SampleGattApplication.RegisterGattApplication(context, adapter); Console.WriteLine("Press Ctrl+C to quit"); await Task.Delay(-1); } + }).Wait(); } } diff --git a/Examples/SampleAdvertisement.cs b/Examples/SampleAdvertisement.cs index cec5c1b..90b5033 100755 --- a/Examples/SampleAdvertisement.cs +++ b/Examples/SampleAdvertisement.cs @@ -8,7 +8,7 @@ namespace Examples { public class SampleAdvertisement { - public static async Task RegisterSampleAdvertisement(ServerContext serverContext, string adapterPath) + public static async Task RegisterSampleAdvertisement(ServerContext context, IAdapter1 adapter) { var advertisementProperties = new LEAdvertisement1Properties { @@ -23,7 +23,7 @@ public static async Task RegisterSampleAdvertisement(ServerContext serverContext IncludeTxPower = true, }; - await new AdvertisingManager(serverContext, adapterPath).CreateAdvertisement(advertisementProperties); + await new AdvertisingManager(context, adapter).CreateAdvertisement(advertisementProperties); } } } \ No newline at end of file diff --git a/Examples/SampleGattApplication.cs b/Examples/SampleGattApplication.cs index a77fe34..3fb24e8 100755 --- a/Examples/SampleGattApplication.cs +++ b/Examples/SampleGattApplication.cs @@ -9,7 +9,7 @@ namespace Examples { internal static class SampleGattApplication { - public static async Task RegisterGattApplication(ServerContext serverContext, string adapterPath) + public static async Task RegisterGattApplication(ServerContext context, IAdapter1 adapter) { var gattServiceDescription = new GattServiceDescription { @@ -35,7 +35,7 @@ public static async Task RegisterGattApplication(ServerContext serverContext, st .AddService(gattServiceDescription) .WithCharacteristic(gattCharacteristicDescription, new[] { gattDescriptorDescription }); - await new GattApplicationManager(serverContext, adapterPath).RegisterGattApplication(gab.BuildServiceDescriptions()); + await new GattApplicationManager(context, adapter).RegisterGattApplication(gab.BuildServiceDescriptions()); } } From 262f8e16212af152a4755d3d9d048a0fc2fff33e Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 23 Nov 2023 02:11:59 +0100 Subject: [PATCH 09/13] improve: add Adapter property in ServerContext & simplify sample --- .../Advertisements/AdvertisingManager.cs | 10 +-- DotnetBleServer/Core/BlueZManager.cs | 80 ------------------- DotnetBleServer/Core/ServerContext.cs | 78 ++++++++++++++++-- DotnetBleServer/Core/ServerContextBase.cs | 26 ++++++ .../Gatt/GattApplicationManager.cs | 6 +- Examples/Program.cs | 33 +++----- Examples/SampleAdvertisement.cs | 6 +- Examples/SampleGattApplication.cs | 4 +- README.md | 6 +- 9 files changed, 122 insertions(+), 127 deletions(-) delete mode 100644 DotnetBleServer/Core/BlueZManager.cs create mode 100644 DotnetBleServer/Core/ServerContextBase.cs diff --git a/DotnetBleServer/Advertisements/AdvertisingManager.cs b/DotnetBleServer/Advertisements/AdvertisingManager.cs index a2573a9..db24c3e 100755 --- a/DotnetBleServer/Advertisements/AdvertisingManager.cs +++ b/DotnetBleServer/Advertisements/AdvertisingManager.cs @@ -10,12 +10,10 @@ namespace DotnetBleServer.Advertisements public class AdvertisingManager { private readonly ServerContext _context; - private readonly IAdapter1 _adapter; - public AdvertisingManager(ServerContext context, IAdapter1 adapter) + public AdvertisingManager(ServerContext context) { _context = context; - _adapter = adapter; } public async Task RegisterAdvertisement(Advertisement advertisement) @@ -29,13 +27,13 @@ public async Task RegisterAdvertisement(Advertisement advertisement) private ILEAdvertisingManager1 GetAdvertisingManager() { - return _context.Connection.CreateProxy(Constants.DbusServicePath, _adapter.ObjectPath); + return _context.Connection.CreateProxy(Constants.DbusServicePath, _context.Adapter.ObjectPath); } public async Task CreateAdvertisement(LEAdvertisement1Properties advertisementProperties) { - var advertisement = new Advertisement($"{_adapter.ObjectPath}/advertisement0", advertisementProperties); - await new AdvertisingManager(_context, _adapter).RegisterAdvertisement(advertisement); + var advertisement = new Advertisement($"{_context.Adapter.ObjectPath}/advertisement0", advertisementProperties); + await new AdvertisingManager(_context).RegisterAdvertisement(advertisement); } } } \ No newline at end of file diff --git a/DotnetBleServer/Core/BlueZManager.cs b/DotnetBleServer/Core/BlueZManager.cs deleted file mode 100644 index a34cb95..0000000 --- a/DotnetBleServer/Core/BlueZManager.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Tmds.DBus; - -namespace DotnetBleServer.Core -{ - public class BlueZManager - { - private ServerContext _context; - - public BlueZManager(ServerContext context) - { - _context = context; - } - - /// Get Bluetooth Adapter object based on name. - /// Adapter Name. - /// False allows shortened adapter name ('hci0'). True, for full name ('/org/bluez/hci0'). - /// Adapter object - /// - public async Task GetAdapterAsync(string adapterName, bool fullName = false) - { - var adapterObjectPath = !fullName ? $"/org/bluez/{adapterName}" : adapterName; - var adapter = _context.Connection.CreateProxy(Constants.DbusServicePath, adapterObjectPath); - - try - { - await adapter.GetAliasAsync(); - } - catch (Exception) - { - throw new Exception($"Bluetooth adapter {adapterName} not found."); - } - - return adapter; - } - - public async Task> GetAdaptersAsync() - { - return await GetProxiesAsync(Constants.AdapterInterfacePath, rootObject: null); - } - - /// The interface to search for - /// The DBus object to search under. Can be null - internal async Task> GetProxiesAsync(string interfaceName, IDBusObject rootObject) - { - // Console.WriteLine("GetProxiesAsync called."); - var objectManager = _context.Connection.CreateProxy(Constants.DbusServicePath, "/"); - var objects = await objectManager.GetManagedObjectsAsync(); - - var matchingObjectPaths = objects - .Where(obj => IsMatch(interfaceName, obj.Key, obj.Value, rootObject)) - .Select(obj => obj.Key); - - var proxies = matchingObjectPaths - .Select(objectPath => _context.Connection.CreateProxy(Constants.DbusServicePath, objectPath)) - .ToList(); - - Console.WriteLine($"GetProxiesAsync returning {proxies.Count} proxies of type {typeof(T)}."); - return proxies; - } - - internal static bool IsMatch(string interfaceName, ObjectPath objectPath, IDictionary> interfaces, IDBusObject rootObject) - { - return IsMatch(interfaceName, objectPath, interfaces.Keys, rootObject); - } - - internal static bool IsMatch(string interfaceName, ObjectPath objectPath, ICollection interfaces, IDBusObject rootObject) - { - if (rootObject != null && !objectPath.ToString().StartsWith($"{rootObject.ObjectPath}/")) - { - return false; - } - - return interfaces.Contains(interfaceName); - } - } -} \ No newline at end of file diff --git a/DotnetBleServer/Core/ServerContext.cs b/DotnetBleServer/Core/ServerContext.cs index 8f659d8..d170a49 100755 --- a/DotnetBleServer/Core/ServerContext.cs +++ b/DotnetBleServer/Core/ServerContext.cs @@ -1,26 +1,88 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Tmds.DBus; namespace DotnetBleServer.Core { - public class ServerContext : IDisposable + public class ServerContext : ServerContextBase { - public ServerContext() + public IAdapter1 Adapter { get; set; } + + public async Task ConnectAndSetDefaultAdapter() { - Connection = new Connection(Address.System); + await Connect(); + + var adapters = await GetAdaptersAsync(); + if (adapters.Count == 0) + { + throw new Exception("No Bluetooth adapters found."); + } + + Adapter = adapters[0]; } - public async Task Connect() + /// Get Bluetooth Adapter object based on name. + /// Adapter Name. + /// False allows shortened adapter name ('hci0'). True, for full name ('/org/bluez/hci0'). + /// Adapter object + /// + public async Task GetAdapterAsync(string adapterName, bool fullName = false) { - await Connection.ConnectAsync(); + var adapterObjectPath = !fullName ? $"/org/bluez/{adapterName}" : adapterName; + var adapter = Connection.CreateProxy(Constants.DbusServicePath, adapterObjectPath); + + try + { + await adapter.GetAliasAsync(); + } + catch (Exception) + { + throw new Exception($"Bluetooth adapter {adapterName} not found."); + } + + return adapter; } - public Connection Connection { get; } + public async Task> GetAdaptersAsync() + { + return await GetProxiesAsync(Constants.AdapterInterfacePath, rootObject: null); + } - public void Dispose() + /// The interface to search for + /// The DBus object to search under. Can be null + internal async Task> GetProxiesAsync(string interfaceName, IDBusObject rootObject) { - Connection.Dispose(); + // Console.WriteLine("GetProxiesAsync called."); + var objectManager = Connection.CreateProxy(Constants.DbusServicePath, "/"); + var objects = await objectManager.GetManagedObjectsAsync(); + + var matchingObjectPaths = objects + .Where(obj => IsMatch(interfaceName, obj.Key, obj.Value, rootObject)) + .Select(obj => obj.Key); + + var proxies = matchingObjectPaths + .Select(objectPath => Connection.CreateProxy(Constants.DbusServicePath, objectPath)) + .ToList(); + + //Console.WriteLine($"GetProxiesAsync returning {proxies.Count} proxies of type {typeof(T)}."); + return proxies; + } + + internal static bool IsMatch(string interfaceName, ObjectPath objectPath, IDictionary> interfaces, IDBusObject rootObject) + { + return IsMatch(interfaceName, objectPath, interfaces.Keys, rootObject); + } + + internal static bool IsMatch(string interfaceName, ObjectPath objectPath, ICollection interfaces, IDBusObject rootObject) + { + if (rootObject != null && !objectPath.ToString().StartsWith($"{rootObject.ObjectPath}/")) + { + return false; + } + + return interfaces.Contains(interfaceName); } } } \ No newline at end of file diff --git a/DotnetBleServer/Core/ServerContextBase.cs b/DotnetBleServer/Core/ServerContextBase.cs new file mode 100644 index 0000000..79e34f0 --- /dev/null +++ b/DotnetBleServer/Core/ServerContextBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +using Tmds.DBus; + +namespace DotnetBleServer.Core +{ + public class ServerContextBase : IDisposable + { + public ServerContextBase() + { + Connection = new Connection(Address.System); + } + + public async Task Connect() + { + await Connection.ConnectAsync(); + } + + public Connection Connection { get; } + + public void Dispose() + { + Connection.Dispose(); + } + } +} \ No newline at end of file diff --git a/DotnetBleServer/Gatt/GattApplicationManager.cs b/DotnetBleServer/Gatt/GattApplicationManager.cs index f98de92..dfc22ec 100755 --- a/DotnetBleServer/Gatt/GattApplicationManager.cs +++ b/DotnetBleServer/Gatt/GattApplicationManager.cs @@ -11,12 +11,10 @@ namespace DotnetBleServer.Gatt public class GattApplicationManager { private readonly ServerContext _context; - private readonly IAdapter1 _adapter; - public GattApplicationManager(ServerContext context, IAdapter1 adapter) + public GattApplicationManager(ServerContext context) { _context = context; - _adapter = adapter; } public async Task RegisterGattApplication(IEnumerable gattServiceDescriptions) @@ -55,7 +53,7 @@ private async Task BuildApplicationTree(string applicationObjectPath, IEnumerabl private async Task RegisterApplicationInBluez(string applicationObjectPath) { - var gattManager = _context.Connection.CreateProxy(Constants.DbusServicePath, _adapter.ObjectPath); + var gattManager = _context.Connection.CreateProxy(Constants.DbusServicePath, _context.Adapter.ObjectPath); await gattManager.RegisterApplicationAsync(new ObjectPath(applicationObjectPath), new Dictionary()); } diff --git a/Examples/Program.cs b/Examples/Program.cs index 3279cd4..581a208 100755 --- a/Examples/Program.cs +++ b/Examples/Program.cs @@ -1,6 +1,5 @@ using DotnetBleServer.Core; using System; -using System.Linq; using System.Threading.Tasks; namespace Examples @@ -10,36 +9,26 @@ internal class Program private static void Main() { Console.WriteLine("Hello Bluetooth !"); - Console.WriteLine("Press a key to start (let you attach debugger from Visual Studio using SSH)"); + Console.WriteLine("Press a key to start (it's time to attach debugger from Visual Studio using SSH)"); Console.ReadLine(); Task.Run(async () => { - using (ServerContext context = new ServerContext()) + using (var context = new ServerContext()) { - // Mandatory for BLE Advertisement (connection must be manual) (ie. Tmds.DBus.Connection.RegisterObject methods) - await context.Connect(); + await context.ConnectAndSetDefaultAdapter(); - var manager = new BlueZManager(context); - - var adapters = await manager.GetAdaptersAsync(); - if (adapters.Count == 0) - { - throw new Exception("No Bluetooth adapters found."); - } - - var adapter = adapters.First(); - - Console.WriteLine($"Current bluetooth adapter: {adapter.ObjectPath}"); + Console.WriteLine($"Using bluetooth adapter: {context.Adapter.ObjectPath}"); + Console.WriteLine("(switch adapter using context.GetAdapters() and then set context.Adapter)"); // Turn on adapter. - await adapter.SetPoweredAsync(true); + await context.Adapter.SetPoweredAsync(true); - if (!await adapter.GetPoweredAsync()) - throw new Exception($"Can't power on adapter {adapter.ObjectPath}"); + if (!await context.Adapter.GetPoweredAsync()) + throw new Exception($"Can't power on adapter {context.Adapter.ObjectPath}"); - await SampleAdvertisement.RegisterSampleAdvertisement(context, adapter); - await SampleGattApplication.RegisterGattApplication(context, adapter); + await SampleAdvertisement.RegisterSampleAdvertisement(context); + await SampleGattApplication.RegisterGattApplication(context); Console.WriteLine("Press Ctrl+C to quit"); await Task.Delay(-1); @@ -48,4 +37,4 @@ private static void Main() }).Wait(); } } -} +} \ No newline at end of file diff --git a/Examples/SampleAdvertisement.cs b/Examples/SampleAdvertisement.cs index 90b5033..59388af 100755 --- a/Examples/SampleAdvertisement.cs +++ b/Examples/SampleAdvertisement.cs @@ -8,13 +8,13 @@ namespace Examples { public class SampleAdvertisement { - public static async Task RegisterSampleAdvertisement(ServerContext context, IAdapter1 adapter) + public static async Task RegisterSampleAdvertisement(ServerContext context) { var advertisementProperties = new LEAdvertisement1Properties { Type = "peripheral", ServiceUUIDs = new[] { "12345678-1234-5678-1234-56789abcdef0" }, - LocalName = "DISPLAYED NAME", + LocalName = "A", // Check bluetooth spec here: https://www.bluetooth.com/specifications/an/ (Computer appearance is 64 => convert hex value to uint: 0x0080 = 64) // I don't know why, for some reasons, changing appearance is not always working. Appearance = (ushort)Convert.ToUInt32("0x0080", 16), @@ -23,7 +23,7 @@ public static async Task RegisterSampleAdvertisement(ServerContext context, IAda IncludeTxPower = true, }; - await new AdvertisingManager(context, adapter).CreateAdvertisement(advertisementProperties); + await new AdvertisingManager(context).CreateAdvertisement(advertisementProperties); } } } \ No newline at end of file diff --git a/Examples/SampleGattApplication.cs b/Examples/SampleGattApplication.cs index 3fb24e8..0fcac9f 100755 --- a/Examples/SampleGattApplication.cs +++ b/Examples/SampleGattApplication.cs @@ -9,7 +9,7 @@ namespace Examples { internal static class SampleGattApplication { - public static async Task RegisterGattApplication(ServerContext context, IAdapter1 adapter) + public static async Task RegisterGattApplication(ServerContext context) { var gattServiceDescription = new GattServiceDescription { @@ -35,7 +35,7 @@ public static async Task RegisterGattApplication(ServerContext context, IAdapter .AddService(gattServiceDescription) .WithCharacteristic(gattCharacteristicDescription, new[] { gattDescriptorDescription }); - await new GattApplicationManager(context, adapter).RegisterGattApplication(gab.BuildServiceDescriptions()); + await new GattApplicationManager(context).RegisterGattApplication(gab.BuildServiceDescriptions()); } } diff --git a/README.md b/README.md index 878c2f4..6f58912 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ using (var serverContext = new ServerContext()) Type = "peripheral", ServiceUUIDs = new[] { "12345678-1234-5678-1234-56789abcdef0"}, LocalName = "A", + Appearance = (ushort)Convert.ToUInt32("0x0080", 16), + Discoverable = true, + IncludeTxPower = true, }; await new AdvertisingManager(serverContext).CreateAdvertisement(advertisementProperties); @@ -34,9 +37,8 @@ using (var serverContext = new ServerContext()) var gattCharacteristicDescription = new GattCharacteristicDescription { - CharacteristicSource = new SampleGattApplication.ExampleCharacteristicSource(), UUID = "12345678-1234-5678-1234-56789abcdef1", - Flags = new[] {"read", "write", "writable-auxiliaries"} + Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify }; var gattDescriptorDescription = new GattDescriptorDescription { From 87aa7fb427cb8e418ce81a3af11bb0b075a37384 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 23 Nov 2023 15:12:18 +0100 Subject: [PATCH 10/13] package: appropriate the project: creating its own nuget package --- .../DotnetBleServer - Backup.csproj | 36 ++++++++++++++++ DotnetBleServer/DotnetBleServer.csproj | 39 ++++++++++++++---- README.md | 2 +- icon_small.png | Bin 0 -> 9029 bytes 4 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 DotnetBleServer/DotnetBleServer - Backup.csproj create mode 100644 icon_small.png diff --git a/DotnetBleServer/DotnetBleServer - Backup.csproj b/DotnetBleServer/DotnetBleServer - Backup.csproj new file mode 100644 index 0000000..e7f7517 --- /dev/null +++ b/DotnetBleServer/DotnetBleServer - Backup.csproj @@ -0,0 +1,36 @@ + + + + net7.0 + + + BluetoothLE.Server.Linux + 1.0.0 + G.D.M + + Bluetooth Advertising and GATT server functionality on Linux using .NET + + Fork of .NET BLE Server + + It uses BlueZ and Tmds.DBus. The provided API aims to preserve the object structure of BlueZ whilst minimizing the aspects of the D-Bus communication. + + Roman Scheidegger + https://github.com/CarteKiwi/dotnet-ble-server + https://github.com/CarteKiwi/dotnet-ble-server + MIT + icon_small.png + bluetooth, bluez, ble, .net, linux, uno, dotnet + + + + + True + \ + + + + + + + + \ No newline at end of file diff --git a/DotnetBleServer/DotnetBleServer.csproj b/DotnetBleServer/DotnetBleServer.csproj index 9870f0d..fe68093 100755 --- a/DotnetBleServer/DotnetBleServer.csproj +++ b/DotnetBleServer/DotnetBleServer.csproj @@ -4,22 +4,47 @@ net7.0 - DotnetBleServer - 0.1.0-snapshot - phylomeno + BluetoothLE.Server.Linux + 1.0.0 + G.D.M - .NET BLE Server is a library to support creation of BLE peripherals on Linux using .NET Core. + Bluetooth Advertising and GATT server functionality on Linux using .NET - Under the hood the library uses BlueZ. The provided API aims to preserve the object structure of BlueZ whilst minimizing the aspects of the D-Bus communication. + Fork of .NET BLE Server + + It uses BlueZ and Tmds.DBus. The provided API aims to preserve the object structure of BlueZ whilst minimizing the aspects of the D-Bus communication. Roman Scheidegger - https://github.com/phylomeno/dotnet-gatt-server - https://github.com/phylomeno/dotnet-gatt-server + https://github.com/CarteKiwi/dotnet-ble-server + https://github.com/CarteKiwi/dotnet-ble-server MIT + icon_small.png + bluetooth, bluez, ble, .net, linux, uno, dotnet + True + Bluetooth Low Energy (BLE) server for .NET on Linux (Bluez wrapper) + README.md + + + True + \ + + + True + \ + + + + + + True + \ + + + \ No newline at end of file diff --git a/README.md b/README.md index 6f58912..44e2917 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # .NET BLE Server -![Main Pipeline](https://github.com/phylomeno/dotnet-ble-server/workflows/Main%20Pipeline/badge.svg) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![NuGet](https://img.shields.io/nuget/vpre/DotnetBleServer)](https://www.nuget.org/packages/DotnetBleServer/) +![Main Pipeline](https://github.com/phylomeno/dotnet-ble-server/workflows/Main%20Pipeline/badge.svg) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![NuGet](https://img.shields.io/nuget/vpre/BluetoothLE.Server.Linux)](https://www.nuget.org/packages/BluetoothLE.Server.Linux/) .NET BLE Server is a library to support creation of BLE peripherals on Linux using .NET Core. diff --git a/icon_small.png b/icon_small.png new file mode 100644 index 0000000000000000000000000000000000000000..3aff76dc383c1ae0174d3bc4a5ff937f80f028ce GIT binary patch literal 9029 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 r=1$vjy>Wcqu2HQ~ literal 0 HcmV?d00001 From b8b045af167331c408d823c84b76acdebfd416a3 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Fri, 24 Nov 2023 17:33:05 +0100 Subject: [PATCH 11/13] sample: add client sample (using Plugin.BLE on WinUI) --- DotnetBleServer.sln | 80 ++++++++- .../{Examples.csproj => Sample.Server.csproj} | 24 +-- Sample.Client.Win/App.xaml | 16 ++ Sample.Client.Win/App.xaml.cs | 34 ++++ .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 432 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 5372 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 1755 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 637 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 283 bytes Sample.Client.Win/Assets/StoreLogo.png | Bin 0 -> 456 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 2097 bytes Sample.Client.Win/BluetoothService.cs | 162 ++++++++++++++++++ Sample.Client.Win/MainWindow.xaml | 51 ++++++ Sample.Client.Win/MainWindow.xaml.cs | 57 ++++++ Sample.Client.Win/Package.appxmanifest | 51 ++++++ .../Properties/launchSettings.json | 10 ++ Sample.Client.Win/Sample.Client.Win.csproj | 50 ++++++ Sample.Client.Win/app.manifest | 19 ++ Sample.Client/Models.cs | 29 ++++ Sample.Client/Sample.Common.csproj | 14 ++ 20 files changed, 582 insertions(+), 15 deletions(-) rename Examples/{Examples.csproj => Sample.Server.csproj} (95%) mode change 100755 => 100644 create mode 100644 Sample.Client.Win/App.xaml create mode 100644 Sample.Client.Win/App.xaml.cs create mode 100644 Sample.Client.Win/Assets/LockScreenLogo.scale-200.png create mode 100644 Sample.Client.Win/Assets/SplashScreen.scale-200.png create mode 100644 Sample.Client.Win/Assets/Square150x150Logo.scale-200.png create mode 100644 Sample.Client.Win/Assets/Square44x44Logo.scale-200.png create mode 100644 Sample.Client.Win/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 Sample.Client.Win/Assets/StoreLogo.png create mode 100644 Sample.Client.Win/Assets/Wide310x150Logo.scale-200.png create mode 100644 Sample.Client.Win/BluetoothService.cs create mode 100644 Sample.Client.Win/MainWindow.xaml create mode 100644 Sample.Client.Win/MainWindow.xaml.cs create mode 100644 Sample.Client.Win/Package.appxmanifest create mode 100644 Sample.Client.Win/Properties/launchSettings.json create mode 100644 Sample.Client.Win/Sample.Client.Win.csproj create mode 100644 Sample.Client.Win/app.manifest create mode 100644 Sample.Client/Models.cs create mode 100644 Sample.Client/Sample.Common.csproj diff --git a/DotnetBleServer.sln b/DotnetBleServer.sln index 1ab3771..6d8b6ec 100755 --- a/DotnetBleServer.sln +++ b/DotnetBleServer.sln @@ -1,26 +1,100 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29418.71 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34316.72 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples", "Examples\Examples.csproj", "{AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Server", "Examples\Sample.Server.csproj", "{AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotnetBleServer", "DotnetBleServer\DotnetBleServer.csproj", "{0D775919-ECF4-4C71-92ED-D7D70D9BF59F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Common", "Sample.Client\Sample.Common.csproj", "{8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Client.Win", "Sample.Client.Win\Sample.Client.Win.csproj", "{6D2973EE-F461-4A6A-990C-54F191FFD19C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|ARM64.Build.0 = Debug|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|x64.Build.0 = Debug|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Debug|x86.Build.0 = Debug|Any CPU {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|Any CPU.Build.0 = Release|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|ARM64.ActiveCfg = Release|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|ARM64.Build.0 = Release|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|x64.ActiveCfg = Release|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|x64.Build.0 = Release|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|x86.ActiveCfg = Release|Any CPU + {AE3AAA32-DFE6-4CAD-BE3D-0CFBA5C75EC4}.Release|x86.Build.0 = Release|Any CPU {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|ARM64.Build.0 = Debug|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|x64.ActiveCfg = Debug|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|x64.Build.0 = Debug|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|x86.ActiveCfg = Debug|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Debug|x86.Build.0 = Debug|Any CPU {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|Any CPU.Build.0 = Release|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|ARM64.ActiveCfg = Release|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|ARM64.Build.0 = Release|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|x64.ActiveCfg = Release|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|x64.Build.0 = Release|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|x86.ActiveCfg = Release|Any CPU + {0D775919-ECF4-4C71-92ED-D7D70D9BF59F}.Release|x86.Build.0 = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|ARM64.Build.0 = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|x64.Build.0 = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Debug|x86.Build.0 = Debug|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|Any CPU.Build.0 = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|ARM64.ActiveCfg = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|ARM64.Build.0 = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|x64.ActiveCfg = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|x64.Build.0 = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|x86.ActiveCfg = Release|Any CPU + {8A7F5783-4ADB-4BE2-8D48-4FA383F166CE}.Release|x86.Build.0 = Release|Any CPU + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|Any CPU.ActiveCfg = Debug|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|Any CPU.Build.0 = Debug|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|Any CPU.Deploy.0 = Debug|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|ARM64.Build.0 = Debug|ARM64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|x64.ActiveCfg = Debug|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|x64.Build.0 = Debug|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|x64.Deploy.0 = Debug|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|x86.ActiveCfg = Debug|x86 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|x86.Build.0 = Debug|x86 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Debug|x86.Deploy.0 = Debug|x86 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|Any CPU.ActiveCfg = Release|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|Any CPU.Build.0 = Release|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|Any CPU.Deploy.0 = Release|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|ARM64.ActiveCfg = Release|ARM64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|ARM64.Build.0 = Release|ARM64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|ARM64.Deploy.0 = Release|ARM64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|x64.ActiveCfg = Release|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|x64.Build.0 = Release|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|x64.Deploy.0 = Release|x64 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|x86.ActiveCfg = Release|x86 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|x86.Build.0 = Release|x86 + {6D2973EE-F461-4A6A-990C-54F191FFD19C}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Examples/Examples.csproj b/Examples/Sample.Server.csproj old mode 100755 new mode 100644 similarity index 95% rename from Examples/Examples.csproj rename to Examples/Sample.Server.csproj index 2131501..001ddba --- a/Examples/Examples.csproj +++ b/Examples/Sample.Server.csproj @@ -1,12 +1,12 @@ - - - - Exe - net7.0 - - - - - - - + + + + Exe + net7.0 + + + + + + + diff --git a/Sample.Client.Win/App.xaml b/Sample.Client.Win/App.xaml new file mode 100644 index 0000000..b58e9ed --- /dev/null +++ b/Sample.Client.Win/App.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Sample.Client.Win/App.xaml.cs b/Sample.Client.Win/App.xaml.cs new file mode 100644 index 0000000..4028809 --- /dev/null +++ b/Sample.Client.Win/App.xaml.cs @@ -0,0 +1,34 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace Sample.Client.Win +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// Invoked when the application is launched. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + { + m_window = new MainWindow(); + m_window.Activate(); + } + + private Window m_window; + } +} diff --git a/Sample.Client.Win/Assets/LockScreenLogo.scale-200.png b/Sample.Client.Win/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..7440f0d4bf7c7e26e4e36328738c68e624ee851e GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(FqV6|IEGZ*x-#9g>~Mkr+x6^F zy~CDX2QIMs&Gcs3RnRBoxBA!*(Mfw0KTCYuYk0WlEIV>qBmPl! zq4ukrvfADX@#p8fbLY(H47N+k`FZ(FZh?cDro7>{8mkBO3>^oaIx`3!Jl)Qq)HI!+ z(S=1{o~eT)&W^=Ea8C`-17(Jv5(nHFJ{dOjGdxLVkY_y6&S1whfuFI4MM0kF0f&cO zPDVpV%nz;Id$>+0Ga5e9625-JcI)oq=#Pa3p^>8BB}21BUw@eN!-6@w%X+^`+Vn?! zryu|3T>kVWNBYyBc=7Y6H#s1Ah!OI_nezW zXTqOdkv2Az6KKBV=$yHdF^R3Fqw(TZEoNSZX>reXJ#bwX42%f|Pgg&ebxsLQ010xn AssI20 literal 0 HcmV?d00001 diff --git a/Sample.Client.Win/Assets/SplashScreen.scale-200.png b/Sample.Client.Win/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..32f486a86792a5e34cd9a8261b394c49b48f86be GIT binary patch literal 5372 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 literal 0 HcmV?d00001 diff --git a/Sample.Client.Win/Assets/Square44x44Logo.scale-200.png b/Sample.Client.Win/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..f713bba67f551ef91020b75716a4dc8ebd744b1c GIT binary patch literal 637 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8kjIJFu8cTIEGZ*dUI*J;2{SImxtDO zm%3!R$UazoY}x{$j0P5ABYXWr(l=jxJ6ps1W{tV=^>{Dl><3nv3A}sm=EZ)#l3`NR zpZda3^rNox*D1%NC98Z~L*6zipLw~Gxn&(Y-;KmJ+aR6eLabU-L#y8HW%7P-E_-VlLqIabbHPHKT*)fT@9iWJ7iWgOT9%0}Lrj>lztPxWq6sPw3pi z#-<=#$jjrP_DD*i!RLsn0mIA=>4~N)IMYWIf=j%-zuKCdMG%tHYot70D1| zvWa0wMhauW#S>1CnI_;>!1Q3zMA17@DOVq{MQ+{U7^a&yA+%dMCG;WNPV0i;w$tu; zX^b}UKziPM)(<;)ruW;-`)bBN+rQNM*Zs_>?n$|FVFo-e*PZb*@U7VAd+tHb4e?=Blc~}S6K)wL}r*Gf`BM#QB z+y>N$mCswb4d{^{S9v_!eQj4fTRMOwOCi?lSk9%<=vAz}jM-*PQtH@Odn1LZcd^j#o> hW$4xn+CT+ep9lJ{OAO?njobhL002ovPDHLkV1nYebbkN< literal 0 HcmV?d00001 diff --git a/Sample.Client.Win/Assets/StoreLogo.png b/Sample.Client.Win/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..a4586f26bdf7841cad10f39cdffe2aca3af252c1 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2o;fF!p=8IEGZ*dUM0H=rDtTTVkd2 z(%lbKn@VS_lUaADVB&;Z6F#LM+mPsa?e>FnHo;HND^!P`-lX%BH~FOg%y&x+t*x!? zg$#_1A1kgsSvO(fw`bOmo;lrJX8byO1j^gf7qohR%mmt z@L)WX;>gqgK|tWJvQ5j;4;=gt4HXVKSMYRv5RhY5vS~TqfK_NAP*r{h!!g^BZ;w4r z7CGdsai)y;fJQc`7{Zc2b==h%o`Op$|bg6a&nL{*m7-=0>k4M4-PXlU;G-?%*(*g>iFt^ U$m#7DfHB12>FVdQ&MBb@0G`#n8vpc0sq%A~kJcD9FY~qQRMt?ZR3YyDZt}Od;|mgpc{2dv9AHF){kXU%k({ z=Y8JidEayHTkG@twPZ|U3_^%3ct-OgLSiFAqDN!|tbCX@c@?4P`2x*TMK!+Q4b?k0 ziW7!!KF6dPWcF<%I|iznM~`QJ_V7sHGV_D`dhgpA9Vd@&X}ErK+j~_rdv;Bp?OA@a zFXOk7eWOJe5NcK;6h$FaM&7JxNc#-@QTwzW6x#d_zmQNkz5) zPI;kh;3d;5UCJU+9a(cOxX(|edWoOiAEdGU#kPJ&xnc2||3vDbuhBCkj-pb0as$Zl z5;}4n=**n6(1g`JEtSy;SG6X;#-F~Oz3lESG2b5`j@wAwY4Yp<=4Xeb>iH=6aicF?DxD&q{`!&}ct zBI)aycwuobQAf&678Uf+Mmh-@9RUhyH~>?w0dixO0#jZjEc9R^=5NZw=|a(kcB?9^ zfnTiEFXp-q#B;Tn>(O%$A*ud^Rg&eVH6Y_5Y%!E39RR&s?XpG`gKwU!6FE1 z7X)DC7)*(5g}lh`4`{i~DZcWupZI`K)_4P)VE{@gc7@Xsd^86zl~_mOYH?I4!aGeX z^E(_=L6?PgveDQ+r%P@UISEXrkn`LHJZ##+!-anV>6h)IkKp;E@p8+3&(5%kS2)ld*J*rJccZM0iyaAx7+F~GW1UWFK&3X$PE1^}NH zgAG9ck5K!{07OwU@j@Do>TbH=CDEo#4m0cEyAuXy_<&jlzJVcKweSJ5 z&=q~iIn18$w8yb=rmEmHxVEUA^?RwnB?6Qlp1os8@*dWTGL2bhzZ!s*xqScR?EPL` zo(JwNdKUUYy7GtvZ3asXm)cgFvCx9EmAi;|w=a0iGiv%%VYKh`P0Wma4y`Xyx|T~( zAmfGbgbEEC7)j8b@WA@+5W3a61HJXC1dX@6_T|Czk0I0zBk%tnW~()VWITGI!`$c< gARL?UBrYYkwoDw4eo*CrzXGTrZ@;GF>596)00d&n@&Et; literal 0 HcmV?d00001 diff --git a/Sample.Client.Win/BluetoothService.cs b/Sample.Client.Win/BluetoothService.cs new file mode 100644 index 0000000..5e7b1a2 --- /dev/null +++ b/Sample.Client.Win/BluetoothService.cs @@ -0,0 +1,162 @@ +using Plugin.BLE; +using Plugin.BLE.Abstractions; +using Plugin.BLE.Abstractions.Contracts; +using Plugin.BLE.Abstractions.Extensions; +using Sample.Common.Models; +using System; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Sample.Client.Win +{ + internal class BluetoothService + { + public Action StatusChanged { get; set; } + public Action Discovered { get; set; } + + private static readonly Guid SERVICE_CHARACTERISTIC_UUID = new Guid("12345678-1234-5678-1234-56789abcdef1"); + private const int TIMEOUT = 5000; + private IDevice _connectedDevice; + private Guid _serviceUuid; + private IAdapter _adapter; + private CancellationTokenSource _cts = new CancellationTokenSource(); + + private async void Scan(Guid serviceUuid) + { + if (_adapter.IsScanning) + await StopAsync(); + + _serviceUuid = serviceUuid; + _adapter.ScanTimeout = TIMEOUT; + _adapter.DeviceDiscovered += DeviceDiscovered; + _adapter.ScanTimeoutElapsed += ScanTimeoutElapsed; + + var scanFilterOptions = new ScanFilterOptions(); + scanFilterOptions.ServiceUuids = [serviceUuid]; // cross platform filter + + Debug.WriteLine("Scanning started"); + StatusChanged.Invoke("Scanning..."); + await _adapter.StartScanningForDevicesAsync(scanFilterOptions, _cts.Token); + } + + private void ScanTimeoutElapsed(object sender, EventArgs e) + { + StatusChanged.Invoke("Stopped. Try again."); + } + + private void DeviceDiscovered(object sender, Plugin.BLE.Abstractions.EventArgs.DeviceEventArgs scanResult) + { + var device = $"{scanResult.Device.Name} - {scanResult.Device.Id}"; + Debug.WriteLine($"Discovered {device}"); + + Discovered?.Invoke(new RomeRemoteSystem(scanResult.Device) + { + DisplayName = scanResult.Device.Name, + Status = scanResult.Device.State.ToString(), + Id = scanResult.Device.Id.ToString(), + }); + } + + private async Task ResetAsync() + { + if (_adapter == null) return; + + _cts?.Cancel(); + + if (_adapter.ConnectedDevices.Count > 0) + { + await _adapter.DisconnectDeviceAsync(_adapter.ConnectedDevices[0]); + } + + _adapter.DeviceDiscovered -= DeviceDiscovered; + _adapter.ScanTimeoutElapsed -= ScanTimeoutElapsed; + + _cts.Dispose(); + _cts = new CancellationTokenSource(); + } + + private async Task StopAsync() + { + if (_adapter.IsScanning) + await _adapter.StopScanningForDevicesAsync(); + } + + public async Task ConnectAsync(IDevice device) + { + try + { + await StopAsync(); + + Debug.WriteLine($"Connecting..."); + + _connectedDevice = device; + + await _adapter.ConnectToDeviceAsync(_connectedDevice, cancellationToken: _cts.Token); + + Debug.WriteLine($"Connected: {_connectedDevice != null}"); + + return _connectedDevice != null; + } + catch (Exception ex) + { + Debug.WriteLine($"Unable to connect : " + ex.Message); + return false; + } + } + + public async Task InitializeAsync(Guid serviceUuid, string? hostName) + { + StatusChanged.Invoke("Initializing..."); + + await ResetAsync(); + + _adapter = CrossBluetoothLE.Current.Adapter; + + if (CrossBluetoothLE.Current.State == BluetoothState.On) + Scan(serviceUuid); + else + { + StatusChanged.Invoke("waiting for initialization..."); + CrossBluetoothLE.Current.StateChanged += (s, e) => + { + if (CrossBluetoothLE.Current.State == BluetoothState.On) + Scan(serviceUuid); + }; + } + } + + public async Task GetAsync(string value) + { + try + { + Debug.WriteLine("Get service"); + var service = await _connectedDevice.GetServiceAsync(_serviceUuid, _cts.Token); + + Debug.WriteLine("Get write characteristic"); + var characteristic = await service?.GetCharacteristicAsync(SERVICE_CHARACTERISTIC_UUID); + + Debug.WriteLine("Write value"); + await characteristic?.WriteAsync(Encoding.ASCII.GetBytes(value), _cts.Token); + + Debug.WriteLine("Read value"); + var result = await characteristic?.ReadAsync(_cts.Token); + + // You can also use ValueUpdated + //characteristic.ValueUpdated += (a, b) => + //{ + // Console.WriteLine("Value updated: " + Encoding.ASCII.GetString(b.Characteristic.Value)); + //}; + //await characteristic.StartUpdatesAsync(); + + return Encoding.ASCII.GetString(result.data); + } + catch (Exception ex) + { + Debug.WriteLine($"GetAsync failed: {ex.Message}"); + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/Sample.Client.Win/MainWindow.xaml b/Sample.Client.Win/MainWindow.xaml new file mode 100644 index 0000000..434d3f0 --- /dev/null +++ b/Sample.Client.Win/MainWindow.xaml @@ -0,0 +1,51 @@ + + + + + + + + Discovered devices : + + + + + + + + + + + + + + + ===== Logs ===== + + + + + + + + + + + + + + + + diff --git a/Sample.Client.Win/MainWindow.xaml.cs b/Sample.Client.Win/MainWindow.xaml.cs new file mode 100644 index 0000000..768716b --- /dev/null +++ b/Sample.Client.Win/MainWindow.xaml.cs @@ -0,0 +1,57 @@ +using Microsoft.UI.Xaml; +using Plugin.BLE.Abstractions.Contracts; +using Sample.Common.Models; +using System; +using System.Text.Json; + +namespace Sample.Client.Win +{ + public sealed partial class MainWindow : Window + { + private BluetoothService _service; + + public MainWindow() + { + this.InitializeComponent(); + _service = new BluetoothService(); + } + + private async void myButton_Click(object sender, RoutedEventArgs e) + { + devices.Items.Clear(); + logs.Items.Clear(); + + _service.Discovered = (device) => + { + DispatcherQueue.TryEnqueue(() => + { + devices.Items.Add(device); + }); + }; + + _service.StatusChanged = (state) => + { + myButton.Content = state; + }; + + await _service.InitializeAsync(new Guid("12345678-1234-5678-1234-56789abcdef0"), null); + } + + private async void devices_SelectionChanged(object sender, Microsoft.UI.Xaml.Controls.SelectionChangedEventArgs e) + { + if (e.AddedItems.Count == 0) return; + + var device = (RomeRemoteSystem)e.AddedItems[0]; + myButton.Content = "Start"; + + logs.Items.Add($"Connecting to {device.DisplayName}..."); + var connected = await _service.ConnectAsync((IDevice)device.NativeObject); + logs.Items.Add(connected ? "Connected" : "Unable to connect"); + + var data = JsonSerializer.Serialize(new RemoteParameter() { Command = RemoteCommands.Command1 }); + logs.Items.Add($"Sending data... {data}"); + var response = await _service.GetAsync(data); + logs.Items.Add($"Data received: {response}"); + } + } +} \ No newline at end of file diff --git a/Sample.Client.Win/Package.appxmanifest b/Sample.Client.Win/Package.appxmanifest new file mode 100644 index 0000000..6611ef1 --- /dev/null +++ b/Sample.Client.Win/Package.appxmanifest @@ -0,0 +1,51 @@ + + + + + + + + + + Sample.Client.Win + Guillaume + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sample.Client.Win/Properties/launchSettings.json b/Sample.Client.Win/Properties/launchSettings.json new file mode 100644 index 0000000..2378334 --- /dev/null +++ b/Sample.Client.Win/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Sample.Client.Win (Package)": { + "commandName": "MsixPackage" + }, + "Sample.Client.Win (Unpackaged)": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Sample.Client.Win/Sample.Client.Win.csproj b/Sample.Client.Win/Sample.Client.Win.csproj new file mode 100644 index 0000000..b4b215e --- /dev/null +++ b/Sample.Client.Win/Sample.Client.Win.csproj @@ -0,0 +1,50 @@ + + + WinExe + net8.0-windows10.0.19041.0 + 10.0.17763.0 + Sample.Client.Win + app.manifest + x86;x64;ARM64 + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + diff --git a/Sample.Client.Win/app.manifest b/Sample.Client.Win/app.manifest new file mode 100644 index 0000000..0bef63d --- /dev/null +++ b/Sample.Client.Win/app.manifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/Sample.Client/Models.cs b/Sample.Client/Models.cs new file mode 100644 index 0000000..8f37564 --- /dev/null +++ b/Sample.Client/Models.cs @@ -0,0 +1,29 @@ +using System.Net; + +namespace Sample.Common.Models +{ + public enum RemoteCommands + { + Command1, + Command2 + } + + public class RemoteParameter + { + public string? SerializedData { get; set; } + public RemoteCommands Command { get; set; } + } + + public class RomeRemoteSystem(object nativeObject) + { + public object NativeObject { get; } = nativeObject; + + public string DisplayName { get; set; } + public string Id { get; set; } + public string Status { get; set; } + public string Kind { get; set; } + public IPEndPoint EndPoint { get; set; } + + public string DisplayMember => $"{DisplayName} - {Id}"; + } +} diff --git a/Sample.Client/Sample.Common.csproj b/Sample.Client/Sample.Common.csproj new file mode 100644 index 0000000..215cd12 --- /dev/null +++ b/Sample.Client/Sample.Common.csproj @@ -0,0 +1,14 @@ + + + + Library + net8.0 + enable + enable + + + + + + + From f8cbbbcc65f96676149a0a0274401494a6fa0585 Mon Sep 17 00:00:00 2001 From: Guillaume DEMICHELI Date: Fri, 24 Nov 2023 18:11:44 +0100 Subject: [PATCH 12/13] Update README.md --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44e2917..e0ac842 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,20 @@ Under the hood the library uses BlueZ. The provided API aims to preserve the obj ## Examples + + + + + + + + + + + +
Client / Server demo (Windows - Linux)
Client running WinUI app using PLUGIN.BLE nuget packageServer running Linux (raspberry PI 3) (connected via SSH on WSL)
+ + ### Advertisement ```csharp using (var serverContext = new ServerContext()) @@ -30,7 +44,8 @@ using (var serverContext = new ServerContext()) using (var serverContext = new ServerContext()) { var gattServiceDescription = new GattServiceDescription - { + {![demo](https://github.com/CarteKiwi/BluetoothLE.Server.Linux/assets/12309245/f8dd1bb2-f6af-4bbe-bcbe-50257fd29622) + UUID = "12345678-1234-5678-1234-56789abcdef0", Primary = true }; @@ -38,7 +53,11 @@ using (var serverContext = new ServerContext()) var gattCharacteristicDescription = new GattCharacteristicDescription { UUID = "12345678-1234-5678-1234-56789abcdef1", - Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify + Flags = CharacteristicFla + +https://github.com/CarteKiwi/BluetoothLE.Server.Linux/assets/12309245/bb4ebdf7-d298-494a-b33c-72248d4db187 + +gs.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify }; var gattDescriptorDescription = new GattDescriptorDescription { From 7d537c4a1337c0d9dbadc7deee4f9a98effa8769 Mon Sep 17 00:00:00 2001 From: Guillaume DEMICHELI Date: Fri, 24 Nov 2023 23:04:11 +0100 Subject: [PATCH 13/13] Update README.md --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e0ac842..4fb6c43 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,7 @@ using (var serverContext = new ServerContext()) using (var serverContext = new ServerContext()) { var gattServiceDescription = new GattServiceDescription - {![demo](https://github.com/CarteKiwi/BluetoothLE.Server.Linux/assets/12309245/f8dd1bb2-f6af-4bbe-bcbe-50257fd29622) - + { UUID = "12345678-1234-5678-1234-56789abcdef0", Primary = true }; @@ -53,18 +52,16 @@ using (var serverContext = new ServerContext()) var gattCharacteristicDescription = new GattCharacteristicDescription { UUID = "12345678-1234-5678-1234-56789abcdef1", - Flags = CharacteristicFla - -https://github.com/CarteKiwi/BluetoothLE.Server.Linux/assets/12309245/bb4ebdf7-d298-494a-b33c-72248d4db187 - -gs.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify + Flags = CharacteristicFlags.Read | CharacteristicFlags.Write | CharacteristicFlags.WritableAuxiliaries | CharacteristicFlags.Notify }; + var gattDescriptorDescription = new GattDescriptorDescription { Value = new[] {(byte) 't'}, UUID = "12345678-1234-5678-1234-56789abcdef2", Flags = new[] {"read", "write"} }; + var gab = new GattApplicationBuilder(); gab .AddService(gattServiceDescription)