From b5a9c87973bcef1ce34b06770b76297e74562f27 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Mon, 11 May 2026 08:44:51 -0400 Subject: [PATCH 1/3] Rename WaitForPropertyValueInternalAsync to WaitForPropertyValueAsync and make public --- .../Extensions/WatchableExtensions.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs b/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs index 8dd2a81..5887802 100644 --- a/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs +++ b/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs @@ -17,7 +17,7 @@ public static class WatchableExtensions /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IAdapter1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for AdvertisingManager's Property and specified value to resolve. /// Type of value. @@ -28,7 +28,7 @@ public static Task WaitForPropertyValueAsync(this IAdapter1 obj, string prope /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this ILEAdvertisingManager1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for Device's Property and specified value to resolve. /// Type of value. @@ -39,7 +39,7 @@ public static Task WaitForPropertyValueAsync(this ILEAdvertisingManager1 obj, /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IDevice1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for Battery's Property and specified value to resolve. /// Type of value. @@ -50,7 +50,7 @@ public static Task WaitForPropertyValueAsync(this IDevice1 obj, string proper /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IBattery1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /* /// Wait for GattService's Property and specified value to resolve. @@ -62,7 +62,7 @@ public static Task WaitForPropertyValueAsync(this IBattery1 obj, string prope /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IGattService1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for GattCharacteristic's Property and specified value to resolve. /// Type of value. @@ -73,7 +73,7 @@ public static Task WaitForPropertyValueAsync(this IGattService1 obj, string p /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IGattCharacteristic1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for GattDescriptor's Property and specified value to resolve. /// Type of value. @@ -84,7 +84,7 @@ public static Task WaitForPropertyValueAsync(this IGattCharacteristic1 obj, s /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IGattDescriptor1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); */ /// Wait for MediaControl's Property and specified value to resolve. @@ -96,7 +96,7 @@ public static Task WaitForPropertyValueAsync(this IGattDescriptor1 obj, strin /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IMediaControl1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// /// Wait for watchable objects property and specified value to resolve. @@ -110,10 +110,13 @@ public static Task WaitForPropertyValueAsync(this IMediaControl1 obj, string /// TimeSpan to wait for. /// Task or exception. /// On timeout a is thrown. - private static async Task WaitForPropertyValueInternalAsync( + public static async Task WaitForPropertyValueAsync( Func> getAsync, - Func, Task> watchPropertiesAsync, - string propertyName, T value, TimeSpan timeout) + Func, + Task> watchPropertiesAsync, + string propertyName, + T value, + TimeSpan timeout) { // TODO: Change to Task versus throwing an error. var (watchTask, watcher) = WaitForPropertyValueInternal(watchPropertiesAsync, propertyName, value); From 050a5ce579fa048003cf5e2b83e12df565403094 Mon Sep 17 00:00:00 2001 From: Valentin Kindschi Date: Tue, 21 Jul 2026 12:46:41 +0200 Subject: [PATCH 2/3] feat(Device): Add Device RSSIChanged and generic PropertyChanged events --- src/Linux.Bluetooth/Device.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Linux.Bluetooth/Device.cs b/src/Linux.Bluetooth/Device.cs index cfd93b1..334b470 100644 --- a/src/Linux.Bluetooth/Device.cs +++ b/src/Linux.Bluetooth/Device.cs @@ -16,6 +16,7 @@ public class Device : IDevice1, IDisposable { private const string DeviceConnected = "Connected"; private const string DeviceServicesResolved = "ServicesResolved"; + private const string DeviceRSSI = "RSSI"; private IDevice1 _proxy; private IDisposable _propertyWatcher; @@ -64,6 +65,16 @@ public event DeviceEventHandlerAsync Connected public event DeviceEventHandlerAsync Disconnected; + /// Raised when the device's RSSI property changes, carrying the new value in dBm. + public event EventHandler? RSSIChanged; + + /// + /// Raised for every Device1 property change, carrying the raw . Covers + /// properties without a dedicated typed event (e.g. ManufacturerData, TxPower). Note that changes also + /// covered by a typed event (Connected, ServicesResolved, RSSI) are delivered on both. + /// + public event EventHandler? PropertyChanged; + public event DeviceEventHandlerAsync ServicesResolved { add @@ -244,8 +255,16 @@ private void OnPropertyChanges(PropertyChanges changes) OnResolved?.Invoke(this, new BlueZEventArgs()); break; + + case DeviceRSSI: + if (pair.Value is short sRSSI) + RSSIChanged?.Invoke(this, sRSSI); + + break; } } + + PropertyChanged?.Invoke(this, changes); } } } From 31bda974269091eaa2d559cfe704a314dee619ee Mon Sep 17 00:00:00 2001 From: Valentin Kindschi Date: Thu, 6 Aug 2026 11:34:36 +0200 Subject: [PATCH 3/3] fix(Device): Contain exceptions in the property-changed handler --- src/Linux.Bluetooth/Device.cs | 44 +++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Linux.Bluetooth/Device.cs b/src/Linux.Bluetooth/Device.cs index 334b470..e9de128 100644 --- a/src/Linux.Bluetooth/Device.cs +++ b/src/Linux.Bluetooth/Device.cs @@ -238,33 +238,41 @@ private async void FireEventIfPropertyAlreadyTrueAsync(DeviceEventHandlerAsync h private void OnPropertyChanges(PropertyChanges changes) { - foreach (var pair in changes.Changed) + // Runs on the DBus receive loop: consumer throws must not escape. + try { - switch (pair.Key) + foreach (var pair in changes.Changed) { - case DeviceConnected: - if (true.Equals(pair.Value)) - OnConnected?.Invoke(this, new BlueZEventArgs()); - else - Disconnected?.Invoke(this, new BlueZEventArgs()); + switch (pair.Key) + { + case DeviceConnected: + if (true.Equals(pair.Value)) + OnConnected?.Invoke(this, new BlueZEventArgs()); + else + Disconnected?.Invoke(this, new BlueZEventArgs()); - break; + break; - case DeviceServicesResolved: - if (true.Equals(pair.Value)) - OnResolved?.Invoke(this, new BlueZEventArgs()); + case DeviceServicesResolved: + if (true.Equals(pair.Value)) + OnResolved?.Invoke(this, new BlueZEventArgs()); - break; + break; - case DeviceRSSI: - if (pair.Value is short sRSSI) - RSSIChanged?.Invoke(this, sRSSI); + case DeviceRSSI: + if (pair.Value is short sRSSI) + RSSIChanged?.Invoke(this, sRSSI); - break; + break; + } } - } - PropertyChanged?.Invoke(this, changes); + PropertyChanged?.Invoke(this, changes); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Device property handler threw: {ex.Message}"); + } } } }