Class BluetoothScanner
Unified Bluetooth scanner facade providing cross-platform inheritance and extension points.
public class BluetoothScanner : IBluetoothScanner, IAsyncDisposable
- Inheritance
-
BluetoothScanner
- Implements
- Inherited Members
Remarks
This class wraps platform-specific scanner implementations and provides a unified API with virtual extension points for client customization.
Client Extensibility: Inherit from this class to add custom scanning logic:
public class MyCustomScanner : BluetoothScanner
{
public MyCustomScanner(...) : base(...) { }
protected override void OnAdvertisementReceived(IBluetoothAdvertisement advertisement)
{
// Custom filtering/processing
if (advertisement.DeviceName?.StartsWith("MyDevice") == true)
{
// Handle specific devices
}
base.OnAdvertisementReceived(advertisement);
}
}
Constructors
BluetoothScanner(IBluetoothAdapter, IBluetoothRssiToSignalStrengthConverter, ITicker, IBluetoothNameProvider?, ILoggerFactory?)
Initializes a new instance of the BluetoothScanner class.
[ActivatorUtilitiesConstructor]
public BluetoothScanner(IBluetoothAdapter adapter, IBluetoothRssiToSignalStrengthConverter rssiToSignalStrengthConverter, ITicker ticker, IBluetoothNameProvider? nameProvider = null, ILoggerFactory? loggerFactory = null)
Parameters
adapterIBluetoothAdapterThe Bluetooth adapter associated with this scanner.
rssiToSignalStrengthConverterIBluetoothRssiToSignalStrengthConverterThe converter for RSSI to signal strength.
tickerITickerThe ticker for scheduling periodic refresh tasks.
nameProviderIBluetoothNameProviderOptional provider for Bluetooth device names.
loggerFactoryILoggerFactoryOptional logger factory for creating loggers.
Properties
AdvertisementFilter
Gets or sets the advertisement filter used to determine which Bluetooth advertisements should be processed.
public Func<IBluetoothAdvertisement, bool>? AdvertisementFilter { get; set; }
Property Value
Remarks
When null (default), all advertisements are accepted.
When set, only advertisements where the filter returns true are processed.
DeviceWrapper
Gets or sets an optional function that wraps a newly created device before it is added to the device list.
public Func<IBluetoothRemoteDevice, IBluetoothAdvertisement, IBluetoothRemoteDevice?>? DeviceWrapper { get; set; }
Property Value
Examples
scanner.DeviceWrapper = (device, advertisement) =>
advertisement.Manufacturer == Manufacturer.Laerdal_Medical_AS
? new LaerdalDevice(device, advertisement)
: null;
Remarks
Called once per new device, after the platform has created the raw IBluetoothRemoteDevice. Return a richer subtype (e.g. a product-specific device class) or null to keep the original device unchanged.
This follows the same pattern as AdvertisementFilter: set it once on the scanner instance, and the scanner calls it automatically for every new device.
IsRunning
Gets a value indicating whether the Bluetooth activity is actively running.
public bool IsRunning { get; }
Property Value
IsStarting
Gets a value indicating whether the Bluetooth activity is starting.
public bool IsStarting { get; }
Property Value
IsStopping
Gets a value indicating whether the Scanner is stopping.
public bool IsStopping { get; }
Property Value
PlatformScanner
Gets the underlying platform-specific scanner implementation.
public IBluetoothScanner PlatformScanner { get; }
Property Value
Remarks
Clients can access platform-specific APIs by casting this property using conditional compilation.
Methods
CleanRestartScanningAsync(Func<IBluetoothAdvertisement, bool>?, ScanningOptions?, PermissionOptions?, TimeSpan?, CancellationToken)
Stops the scanner, discards every device in the scanner's registry, then starts scanning again.
public Task CleanRestartScanningAsync(Func<IBluetoothAdvertisement, bool>? newAdvertisementFilter = null, ScanningOptions? scanningOptions = null, PermissionOptions? permissionOptions = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
newAdvertisementFilterFunc<IBluetoothAdvertisement, bool>An optional replacement for AdvertisementFilter, applied while the scanner is stopped so it is already in effect when scanning resumes. When null the current filter is left untouched.
scanningOptionsScanningOptionsThe options to restart with. When null the options of the scan session being restarted are reused, falling back to defaults if the scanner was not running.
permissionOptionsPermissionOptionsThe options for requesting permissions. If null, default options will be used.
timeoutTimeSpan?The timeout applied to the stop and the start leg individually. Does not bound the registry-clear step in between.
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- Task
A task that represents the asynchronous clean restart operation.
Remarks
Unlike StopScanningAsync(TimeSpan?, CancellationToken) followed by StartScanningAsync(ScanningOptions?, PermissionOptions?, TimeSpan?, CancellationToken), this also drops the device registry, so devices discovered before the restart are gone: every device is disconnected, removed and disposed. Hold on to device identifiers rather than IBluetoothRemoteDevice instances across a clean restart, and re-acquire the instance afterwards with WaitForDeviceToAppearAsync(string, TimeSpan?, CancellationToken).
The intended use case is a device that changes identity while the scanner is running - most notably a device rebooting into or out of firmware-update mode, where a stale registry entry and an in-flight scan session otherwise prevent it from being rediscovered under its new advertisement.
Restarting a scanner that is already stopped is valid: the stop leg is skipped and the registry is still dropped.
Exceptions
- ScannerFailedToStopException
Thrown when the scanner fails to stop.
- ScannerFailedToStartException
Thrown when the scanner fails to start again.
- TimeoutException
Thrown when either leg of the operation times out.
- OperationCanceledException
Thrown when the operation is cancelled.
ClearDeviceAsync(IBluetoothRemoteDevice?)
Clears resources associated with a specific Bluetooth device.
public ValueTask ClearDeviceAsync(IBluetoothRemoteDevice? device)
Parameters
deviceIBluetoothRemoteDeviceThe device to clear.
Returns
ClearDeviceAsync(string)
Clears resources associated with a Bluetooth device by its ID.
public ValueTask ClearDeviceAsync(string deviceId)
Parameters
deviceIdstringThe ID of the device to clear.
Returns
ClearDevicesAsync(IEnumerable<IBluetoothRemoteDevice>?)
Clears resources associated with Bluetooth devices. If no devices are specified, it clears all devices.
public ValueTask ClearDevicesAsync(IEnumerable<IBluetoothRemoteDevice>? devices = null)
Parameters
devicesIEnumerable<IBluetoothRemoteDevice>The devices to clear, or null to clear all devices.
Returns
DisposeAsync()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
public ValueTask DisposeAsync()
Returns
- ValueTask
A task that represents the asynchronous dispose operation.
GetClosestDeviceOrDefault(Func<IBluetoothRemoteDevice, bool>?)
Returns the closest Bluetooth device currently available.
public IBluetoothRemoteDevice? GetClosestDeviceOrDefault(Func<IBluetoothRemoteDevice, bool>? filter = null)
Parameters
filterFunc<IBluetoothRemoteDevice, bool>An optional function to filter devices. Defaults to null for all devices.
Returns
- IBluetoothRemoteDevice
The closest IBluetoothRemoteDevice, or null if none are found.
GetDevice(Func<IBluetoothRemoteDevice, bool>)
Gets the device that matches the specified filter.
public IBluetoothRemoteDevice GetDevice(Func<IBluetoothRemoteDevice, bool> filter)
Parameters
filterFunc<IBluetoothRemoteDevice, bool>The filter to apply to the devices.
Returns
- IBluetoothRemoteDevice
The device that matches the filter.
Exceptions
- DeviceNotFoundException
Thrown if no device matches the specified filter.
- MultipleDevicesFoundException
Thrown if multiple devices match the specified filter.
GetDevice(string)
Gets the device with the specified ID.
public IBluetoothRemoteDevice GetDevice(string id)
Parameters
idstringThe ID of the device to get.
Returns
- IBluetoothRemoteDevice
The device with the specified ID.
Exceptions
- DeviceNotFoundException
Thrown if no device matches the specified ID.
- MultipleDevicesFoundException
Thrown if multiple devices match the specified ID.
GetDeviceOrDefault(Func<IBluetoothRemoteDevice, bool>)
Gets the device that matches the specified filter.
public IBluetoothRemoteDevice? GetDeviceOrDefault(Func<IBluetoothRemoteDevice, bool> filter)
Parameters
filterFunc<IBluetoothRemoteDevice, bool>The filter to apply to the devices.
Returns
- IBluetoothRemoteDevice
The device that matches the filter, or null if no such device exists.
Exceptions
- MultipleDevicesFoundException
Thrown if multiple devices match the specified filter.
GetDeviceOrDefault(string)
Gets the device with the specified ID.
public IBluetoothRemoteDevice? GetDeviceOrDefault(string id)
Parameters
idstringThe ID of the device to get.
Returns
- IBluetoothRemoteDevice
The device with the specified ID, or null if no such device exists.
Exceptions
- MultipleDevicesFoundException
Thrown if multiple devices match the specified ID.
GetDevices(Func<IBluetoothRemoteDevice, bool>?)
Returns all Bluetooth devices that match the specified filter.
public IReadOnlyList<IBluetoothRemoteDevice> GetDevices(Func<IBluetoothRemoteDevice, bool>? filter = null)
Parameters
filterFunc<IBluetoothRemoteDevice, bool>An optional function to filter devices. Defaults to null for all devices.
Returns
- IReadOnlyList<IBluetoothRemoteDevice>
A read-only snapshot of devices at the time of the call. This collection is immutable and will not be modified if devices are added or removed after the call returns. To get updated results, call this method again or subscribe to DeviceListChanged event.
HasDevice(Func<IBluetoothRemoteDevice, bool>)
Checks if a device that matches the specified filter exists.
public bool HasDevice(Func<IBluetoothRemoteDevice, bool> filter)
Parameters
filterFunc<IBluetoothRemoteDevice, bool>The filter to apply to the devices.
Returns
- bool
True if a device that matches the filter exists, false otherwise.
HasDevice(string)
Checks if a device with the specified ID exists.
public bool HasDevice(string id)
Parameters
idstringThe ID of the device to check for.
Returns
- bool
True if a device with the specified ID exists, false otherwise.
HasScannerPermissionsAsync()
Checks if the application has the necessary scanner permissions.
public ValueTask<bool> HasScannerPermissionsAsync()
Returns
Remarks
This is a read-only check. It does not trigger any permission requests.
Platform-specific behavior:
- Android: Checks BLUETOOTH_SCAN (API 31+) or location permissions (older)
- iOS/macOS: Checks Bluetooth Always permission
- Windows: Checks adapter availability and radio state
OnAdvertisementReceived(IBluetoothAdvertisement)
Virtual method called when an advertisement is received. Override this in client projects to add custom advertisement processing logic.
protected virtual void OnAdvertisementReceived(IBluetoothAdvertisement advertisement)
Parameters
advertisementIBluetoothAdvertisementThe received Bluetooth advertisement.
Examples
protected override void OnAdvertisementReceived(IBluetoothAdvertisement advertisement)
{
if (advertisement.DeviceName?.StartsWith("MyDevice") == true)
{
Logger?.LogInformation("Found target device: {Name}", advertisement.DeviceName);
}
base.OnAdvertisementReceived(advertisement);
}
Remarks
This method is called on the platform's native thread/dispatcher. Use this to filter, transform, or react to advertisements before they're processed by the base scanner logic.
Example Use Cases:
- Filter advertisements by manufacturer data
- Parse custom advertisement payloads
- Log specific device discoveries
- Trigger custom events or notifications
OnScanStartingAsync(ScanningOptions?, CancellationToken)
Virtual method called before scanning starts. Override this in client projects to add custom pre-scan logic.
protected virtual ValueTask OnScanStartingAsync(ScanningOptions? options, CancellationToken cancellationToken)
Parameters
optionsScanningOptionsThe scanning options being used.
cancellationTokenCancellationTokenCancellation token for the operation.
Returns
- ValueTask
A task representing the asynchronous operation.
Remarks
This method is called after permissions are checked but before the native platform scanner starts. Use this to perform setup, validation, or logging.
Example Use Cases:
- Configure platform-specific scan settings
- Initialize custom state or caches
- Validate scanning prerequisites
- Start performance monitoring
OnScanStoppedAsync(CancellationToken)
Virtual method called after scanning stops. Override this in client projects to add custom post-scan cleanup logic.
protected virtual ValueTask OnScanStoppedAsync(CancellationToken cancellationToken)
Parameters
cancellationTokenCancellationTokenCancellation token for the operation.
Returns
- ValueTask
A task representing the asynchronous operation.
Remarks
This method is called after the native platform scanner has stopped. Use this to perform cleanup, logging, or final processing.
Example Use Cases:
- Flush cached data
- Log scan statistics
- Clean up resources
- Stop performance monitoring
RequestScannerPermissionsAsync(bool, CancellationToken)
Requests the necessary scanner permissions from the user.
public ValueTask RequestScannerPermissionsAsync(bool requireBackgroundLocation = false, CancellationToken cancellationToken = default)
Parameters
requireBackgroundLocationboolAndroid-only: whether to request background location permission (API 29-30).
cancellationTokenCancellationTokenCancellation token to cancel the permission request operation.
Returns
- ValueTask
Task that completes when permissions are requested.
Remarks
Platform-specific behavior:
- Android: Shows system permission dialog; can be requested multiple times
- iOS/macOS: Shows permission dialog on first call; subsequent denials require Settings
- Windows: Checks adapter state and requests radio access if needed
Exceptions
- BluetoothPermissionException
Thrown when permission request fails or is denied. Check InnerException for platform-specific details (COMException, SecurityException, etc.).
StartScanningAsync(ScanningOptions?, PermissionOptions?, TimeSpan?, CancellationToken)
Asynchronously starts the Bluetooth activity with an optional timeout.
public Task StartScanningAsync(ScanningOptions? scanningOptions = null, PermissionOptions? permissionOptions = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
scanningOptionsScanningOptionsThe options for starting the Bluetooth activity. If null, default options will be used.
permissionOptionsPermissionOptionsThe options for requesting permissions. If null, default options will be used.
timeoutTimeSpan?The timeout for this operation
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- Task
A task that represents the asynchronous start operation.
Remarks
Ensures that the Bluetooth activity is initialized and ready for use.
Exceptions
- ScannerIsAlreadyStartedException
Thrown when the scanner is already running.
- ScannerFailedToStartException
Thrown when the scanner fails to start.
- ScannerUnexpectedStartException
Thrown when an unexpected error occurs during start.
- TimeoutException
Thrown when the operation times out.
- OperationCanceledException
Thrown when the operation is cancelled.
StartScanningIfNeededAsync(ScanningOptions?, PermissionOptions?, TimeSpan?, CancellationToken)
Asynchronously starts the Bluetooth activity if it is not already running, with an optional timeout.
public ValueTask StartScanningIfNeededAsync(ScanningOptions? scanningOptions = null, PermissionOptions? permissionOptions = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
scanningOptionsScanningOptionsThe options for starting the Bluetooth activity. If null, default options will be used.
permissionOptionsPermissionOptionsThe options for requesting permissions. If null, default options will be used.
timeoutTimeSpan?The timeout for this operation
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- ValueTask
A task that represents the asynchronous start operation.
Remarks
Checks if the Bluetooth activity is already running before attempting to start it.
StopScanningAsync(TimeSpan?, CancellationToken)
Asynchronously stops the Scanner with an optional timeout.
public Task StopScanningAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
timeoutTimeSpan?The timeout for this operation
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- Task
A task that represents the asynchronous stop operation.
Remarks
Ensures that the Scanner and its resources are safely released.
Exceptions
- ScannerIsAlreadyStoppedException
Thrown when the scanner is already stopped.
- ScannerFailedToStopException
Thrown when the scanner fails to stop.
- ScannerUnexpectedStopException
Thrown when an unexpected error occurs during stop.
- TimeoutException
Thrown when the operation times out.
- OperationCanceledException
Thrown when the operation is cancelled.
StopScanningIfNeededAsync(TimeSpan?, CancellationToken)
Asynchronously stops the Scanner if it is running, with an optional timeout.
public ValueTask StopScanningIfNeededAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
timeoutTimeSpan?The timeout for this operation
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- ValueTask
A task that represents the asynchronous stop operation.
Remarks
Checks if the Scanner is running before attempting to stop it.
WaitForDeviceToAppearAsync(Func<IBluetoothRemoteDevice, bool>?, TimeSpan?, CancellationToken)
Waits for the first Bluetooth device that matches the specified filter to appear.
public ValueTask<IBluetoothRemoteDevice> WaitForDeviceToAppearAsync(Func<IBluetoothRemoteDevice, bool>? filter = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
filterFunc<IBluetoothRemoteDevice, bool>A function to filter devices. Should return true for matching devices.
timeoutTimeSpan?The timeout for this operation
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- ValueTask<IBluetoothRemoteDevice>
The IBluetoothRemoteDevice that matches the filter when it appears.
WaitForDeviceToAppearAsync(string, TimeSpan?, CancellationToken)
Waits for a Bluetooth device with the specified ID to appear or returns it if already available.
public ValueTask<IBluetoothRemoteDevice> WaitForDeviceToAppearAsync(string id, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
Parameters
idstringThe ID of the device to wait for.
timeoutTimeSpan?The timeout for this operation
cancellationTokenCancellationTokenA cancellation token to cancel this operation.
Returns
- ValueTask<IBluetoothRemoteDevice>
The IBluetoothRemoteDevice when it appears.
Events
AdvertisementReceived
Event triggered when a Bluetooth advertisement is received.
public event EventHandler<AdvertisementReceivedEventArgs>? AdvertisementReceived
Event Type
DeviceListChanged
Event triggered when the list of available devices changes.
public event EventHandler<DeviceListChangedEventArgs>? DeviceListChanged
Event Type
DevicesAdded
Event triggered when devices are added.
public event EventHandler<DevicesAddedEventArgs>? DevicesAdded
Event Type
DevicesRemoved
Event triggered when devices are removed.
public event EventHandler<DevicesRemovedEventArgs>? DevicesRemoved
Event Type
RunningStateChanged
Occurs when the running state of the Bluetooth activity changes.
public event EventHandler? RunningStateChanged
Event Type
Started
Occurs when the Bluetooth activity has started.
public event EventHandler? Started
Event Type
Starting
Occurs when the Bluetooth activity is starting.
public event EventHandler? Starting
Event Type
Stopped
Occurs when the Scanner has stopped.
public event EventHandler? Stopped
Event Type
Stopping
Occurs when the Scanner is stopping.
public event EventHandler? Stopping