DSoft.System.BluetoothLe 4.1.2610.102

dotnet add package DSoft.System.BluetoothLe --version 4.1.2610.102
                    
NuGet\Install-Package DSoft.System.BluetoothLe -Version 4.1.2610.102
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DSoft.System.BluetoothLe" Version="4.1.2610.102" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DSoft.System.BluetoothLe" Version="4.1.2610.102" />
                    
Directory.Packages.props
<PackageReference Include="DSoft.System.BluetoothLe" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DSoft.System.BluetoothLe --version 4.1.2610.102
                    
#r "nuget: DSoft.System.BluetoothLe, 4.1.2610.102"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DSoft.System.BluetoothLe@4.1.2610.102
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DSoft.System.BluetoothLe&version=4.1.2610.102
                    
Install as a Cake Addin
#tool nuget:?package=DSoft.System.BluetoothLe&version=4.1.2610.102
                    
Install as a Cake Tool

DSoft.System.BluetoothLe

DSoft.System.BluetoothLe is a cross-platform Bluetooth Low Energy library for modern .NET. It provides one API for scanning, connecting, discovering GATT services/characteristics, reading, writing, and receiving characteristic updates across mobile and desktop targets.

The library started as a fork/repackage of Plugin.BLE and has been migrated from Xamarin targets to .NET platform targets.

Supported Targets

Target Minimum OS
net10.0-android Android API 21
net10.0-ios iOS 15.0
net10.0-maccatalyst Mac Catalyst 15.0
net10.0-macos macOS 15.0
net10.0-tvos tvOS 15.0
net10.0-windows10.0.19041.0 Windows 10 1809
net481 Windows 10 1809
net10.0 / netstandard2.0 API surface only; platform Bluetooth calls throw on unsupported platforms

Install

Reference the package from your app project:

<PackageReference Include="DSoft.System.BluetoothLe" Version="4.1.*" />

When working from source, reference the project:

<ProjectReference Include="..\DSoft.System.BluetoothLe\DSoft.System.BluetoothLe.csproj" />

Use the library from the System.BluetoothLe namespace:

using System.BluetoothLe;
using System.BluetoothLe.EventArgs;

Platform Setup

Your app must request the operating-system permissions needed for Bluetooth. The library does not replace runtime permission prompts or app manifest entries.

Android

Add Bluetooth permissions to your Android manifest. For Android 12/API 31 and later, apps normally need BLUETOOTH_SCAN and BLUETOOTH_CONNECT. Older Android versions commonly require BLUETOOTH, BLUETOOTH_ADMIN, and location permission for scanning.

Example manifest entries:

<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />

Request dangerous permissions at runtime before scanning or connecting.

iOS, macOS, Mac Catalyst, tvOS

Add Bluetooth usage descriptions to your app's Info.plist where required by the platform:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to nearby BLE devices.</string>

For iOS background BLE scenarios, also configure the appropriate background modes in your app.

Windows

Windows support uses the Windows Runtime Bluetooth APIs. The .NET for Windows and .NET Framework implementations share the same implementation and require Windows 10 1809 or later with a Bluetooth LE-capable adapter.

Packaged Windows apps should declare the Bluetooth capability in the app manifest. Desktop WPF apps should still handle unavailable Bluetooth hardware/radio states at runtime.

Quick Start

Get the current platform implementation:

var bluetooth = BluetoothLE.Current;

// Await readiness rather than testing IsOn. A radio that has only just been brought up has not settled and
// reports BluetoothState.Unknown, so an immediate IsOn check fails against a perfectly good adapter.
var state = await bluetooth.WaitForStateAsync(BluetoothState.On, cancellationToken);

var adapter = bluetooth.Adapter;

If you would rather report the real state to the user than wait for a particular one, await WaitForAvailabilityAsync(), which completes as soon as the radio settles into any determinate state - including Off and Unauthorized.

Scan for devices:

var adapter = BluetoothLE.Current.Adapter;

adapter.DeviceDiscovered += (sender, args) =>
{
    Console.WriteLine($"Found {args.Device.NameOrId} ({args.Device.Id}) RSSI {args.Device.Rssi}");
};

adapter.ScanTimeout = 10000; // milliseconds
adapter.ScanMode = ScanMode.LowLatency;

await adapter.StartScanningForDevicesAsync(cancellationToken: cancellationToken);

Starting a scan while one is already running throws InvalidOperationException, and a scan the platform refuses to start (radio off, permission denied, unsupported) throws AdapterScanException carrying a ScanFailureReason rather than completing empty after the full timeout.

Scan for devices that advertise a service:

var heartRateService = Guid.Parse("0000180d-0000-1000-8000-00805f9b34fb");

await adapter.StartScanningForDevicesAsync(
    serviceUuids: new[] { heartRateService },
    deviceFilter: device => !string.IsNullOrWhiteSpace(device.Name),
    allowDuplicatesKey: false,
    cancellationToken: cancellationToken);

Connect to a discovered device:

var device = adapter.DiscoveredDevices.FirstOrDefault();

if (device == null)
{
    throw new InvalidOperationException("No BLE device was discovered.");
}

await adapter.ConnectToDeviceAsync(device);

Connect to a known device by id:

var knownDeviceId = Guid.Parse("00000000-0000-0000-0000-000000000000");
var device = await adapter.ConnectToKnownDeviceAsync(knownDeviceId);

Discover services and characteristics:

var services = await device.GetServicesAsync();

foreach (var service in services)
{
    Console.WriteLine($"{service.Name}: {service.Id}");

    var characteristics = await service.GetCharacteristicsAsync();

    foreach (var characteristic in characteristics)
    {
        Console.WriteLine($"  {characteristic.Name}: {characteristic.Id} ({characteristic.Properties})");
    }
}

Read and write a characteristic:

var serviceId = Guid.Parse("0000180d-0000-1000-8000-00805f9b34fb");
var characteristicId = Guid.Parse("00002a37-0000-1000-8000-00805f9b34fb");

var service = await device.GetServiceAsync(serviceId);
var characteristic = await service.GetCharacteristicAsync(characteristicId);

if (characteristic.CanRead)
{
    byte[] value = await characteristic.ReadAsync();
}

if (characteristic.CanWrite)
{
    await characteristic.WriteAsync(new byte[] { 0x01, 0x02, 0x03 });
}

Subscribe to characteristic updates:

characteristic.ValueUpdated += (sender, args) =>
{
    var value = args.Characteristic.Value;
    Console.WriteLine(BitConverter.ToString(value));
};

await characteristic.StartUpdatesAsync();

// Later:
await characteristic.StopUpdatesAsync();

Disconnect:

await adapter.DisconnectDeviceAsync(device);

Useful API Surface

  • BluetoothLE.Current: singleton entry point for the current platform.
  • BluetoothLE.State, IsAvailable, IsOn: current Bluetooth state.
  • BluetoothLE.StateChanged: Bluetooth state notifications.
  • Adapter.StartScanningForDevicesAsync: scan for BLE devices.
  • Adapter.DeviceDiscovered: raised the first time a device is discovered during a scan.
  • Adapter.DeviceAdvertised: raised for matching advertisements.
  • Adapter.ConnectToDeviceAsync: connect to a discovered device.
  • Adapter.ConnectToKnownDeviceAsync: connect directly by known platform device id.
  • Device.GetServicesAsync: discover GATT services.
  • Service.GetCharacteristicsAsync: discover GATT characteristics.
  • Characteristic.ReadAsync, WriteAsync, StartUpdatesAsync, StopUpdatesAsync: interact with characteristic values.
  • Descriptor.ReadAsync, WriteAsync: interact with descriptors.
  • Device.BondState: the device's bond (pairing) state, as the platform reports it.
  • Adapter.DeviceBondStateChanged: raised on Android when a device's bond state changes.
  • IGattStatusException.GattStatus, GattFailure: the GATT status on a failed read or write.
  • Device.CreateBondAsync, RemoveBondAsync: request or remove a bond on Android.

Bonding

A peripheral that requires an encrypted link only answers a central it is bonded (paired) with. What an app can see of that bond depends on the platform.

Platform Device.BondState Adapter.DeviceBondStateChanged
Android NotBonded, Bonding or Bonded, read from the system on every call Raised on the main thread for every bond change the system broadcasts, including changes made in the system Bluetooth settings
iOS, macOS, Mac Catalyst, tvOS Always Unknown Never raised
Windows Bonded or NotBonded, from the device's pairing information Never raised
net10.0 / netstandard2.0 Always Unknown Never raised

Apple platforms. CoreBluetooth gives an app no way to read or request a bond. The operating system pairs on demand: the first time the app reads, writes or subscribes to a characteristic the peripheral protects, iOS shows the pairing prompt itself, and once the user accepts the link is encrypted for that and later operations. If the user declines, or the peripheral has lost its keys (for example after a factory reset), the operation fails, and the user has to remove the device in Settings > Bluetooth before pairing again. Design the app around attempting the protected operation and handling its failure, not around checking the bond first.

Android. On Android 12 and later, reading the bond state and receiving bond changes both need the BLUETOOTH_CONNECT permission. Without it BondState reports Unknown and the event is not raised. Android, like Apple platforms, normally starts bonding by itself the first time a protected characteristic is accessed; DeviceBondStateChanged reports the outcome.

Requesting and removing a bond (Android). Device.CreateBondAsync starts bonding, showing the system pairing prompt where the peripheral asks for one, and completes with true once the device is bonded or false if bonding fails or is refused. Use it for an explicit "Pair" action instead of waiting for the first protected operation. Device.RemoveBondAsync forgets a stale bond, for example after the peripheral has been factory reset. Android has no public API for removal, so it is best effort: it calls a hidden platform method that newer Android versions may refuse, and returns false when it cannot remove the bond. In that case, send the user to the system Bluetooth settings. Both methods return false on Apple platforms and Windows. A cancellation token stops the wait, but not bonding the system has already started.

if (device.BondState != DeviceBondState.Bonded && !await device.CreateBondAsync(cancellationToken))
{
    // Not bonded: tell the user to pair, or to forget the device in Settings and try again.
}

Recognising a security failure. A peripheral that requires a bonded, encrypted link refuses an unbonded central with ATT error Insufficient Authentication (0x05) or Insufficient Encryption (0x0F). The read and write exceptions (CharacteristicReadException, CharacteristicWriteException, DescriptorReadException and DescriptorWriteException) carry that status in GattStatus, classified in GattFailure, on Android, Apple platforms and Windows. All four implement IGattStatusException, so one catch covers them:

try
{
    await characteristic.WriteAsync(data, cancellationToken);
}
catch (BleException ex) when (ex is IGattStatusException { GattFailure: GattFailure.InsufficientAuthentication or GattFailure.InsufficientEncryption })
{
    // Ask the user to pair, or to forget the device in Settings and pair again.
}

Subscribing with StartUpdatesAsync fails as a DescriptorWriteException on Android, where it writes the configuration descriptor itself, and as a CharacteristicReadException on Apple platforms. GattStatus is null, and GattFailure is None, when no GATT status was reported: the operation could not start, the device disconnected, or on Apple platforms the error was not an ATT error. NativeStatus and the exception messages are unchanged.

BondState never throws: it reports Unknown wherever the state cannot be read, including for a stand-in device and after the device has been disposed.

Notes

  • BLE device identifiers are platform-specific. Persist known device ids only for the same platform/device context.
  • Scanning and connecting require OS permissions and Bluetooth hardware. Always handle BluetoothState.Unavailable and BluetoothState.Off.
  • net10.0 and netstandard2.0 builds keep the shared API available, but platform Bluetooth operations require a supported platform target.
  • Some Android APIs used by the implementation are marked obsolete by newer SDK analyzers (CA1422). Migrating to the API 33 GATT overloads rewrites the notification delivery path, so it is deliberately deferred until it can be tested against real hardware; the behaviour is correct today.
  • Every event this library raises is raised on a native callback thread - the Android GATT callback thread or the CoreBluetooth delegate queue - and is not marshalled to your UI thread. Marshal in your handler.
  • Trace.TraceImplementation receives every diagnostic message the library emits. It writes to System.Diagnostics.Trace by default; assign your own delegate to bridge it to your logging framework, or set it to null to silence the library.
  • Device and Service can be derived outside the library to build a stand-in peripheral where no radio is present, for a device picker or for unit tests of code that takes a Device. Derive from Device and call protected Device(Guid id, string name, int rssi = 0); derive from Service and call protected Service(Device device). A stand-in has no adapter or native peripheral, so members that reach the platform fail as they do on a target without Bluetooth support. In 4.0 this constructor replaces the parameterless protected Device() that 3.x exposed, and Device.Id is no longer assignable from outside the library - pass the identity to the constructor instead.
  • Upgrading from 3.x: AdvertisementRecordType.UuidsIncomple16Bit was renamed to UuidsIncomplete16Bit in 4.0, a spelling correction that breaks compilation of any code that read that record type. Rename the reference; the value (0x02) is unchanged.

Building From Source

Restore and build the active solution with the .NET 10 SDK:

dotnet restore DSoft.System.BluetoothLe.slnx
dotnet build DSoft.System.BluetoothLe.slnx --no-restore

net10.0-windows10.0.19041.0 and net481 are only built when the host is Windows; on macOS and Linux the remaining seven target frameworks build and the two Windows ones are skipped.

Continuous integration

GitHub Actions (.github/workflows/):

  • ci.yml: runs on pull requests to main and development. Builds Release for every target framework. Publishes nothing.
  • release.yml: runs on every push to main (changes only to Markdown or workflow files are skipped; run it by hand to test a workflow change). Builds Release as 4.1.yyMM.<run number + 100> plus RELEASE_SUFFIX (empty for a stable version; set it to -prerelease in the workflow to publish a prerelease), uploads the package as the drop artifact and pushes it to nuget.org with Trusted Publishing (OIDC, NUGET_USER secret, nuget environment), then tags the commit v<version> and creates a GitHub release with the package attached. AssemblyVersion is not stamped by CI: it stays pinned in the project file (see "Strong naming").

Strong naming

The assembly is strong-named with DSoft.snk, which is committed to this repository on purpose. A strong name is an identity, not a signature: it lets the runtime tell this assembly apart from another of the same name, and it is what net481 binding needs. It is not a guarantee of origin, and publishing the key does not weaken anything that was ever guaranteed. Verify the package through NuGet, not through the strong name.

Relationship To Plugin.BLE

This project keeps the broad shape of Plugin.BLE but uses:

  • Namespace: System.BluetoothLe
  • Entry point: BluetoothLE.Current
  • Multi-targeted partial classes instead of the original base-class layout

Existing Plugin.BLE concepts map closely to this library, but code should be updated to the names above.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-android36.0 is compatible.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-ios26.0 is compatible.  net10.0-maccatalyst was computed.  net10.0-maccatalyst26.0 is compatible.  net10.0-macos was computed.  net10.0-macos26.0 is compatible.  net10.0-tvos was computed.  net10.0-tvos26.0 is compatible.  net10.0-windows was computed.  net10.0-windows10.0.19041 is compatible. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.8.1

  • .NETStandard 2.0

    • No dependencies.
  • net10.0

    • No dependencies.
  • net10.0-android36.0

    • No dependencies.
  • net10.0-ios26.0

    • No dependencies.
  • net10.0-maccatalyst26.0

    • No dependencies.
  • net10.0-macos26.0

    • No dependencies.
  • net10.0-tvos26.0

    • No dependencies.
  • net10.0-windows10.0.19041

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.1.2610.102 0 10/18/2026
4.0.2610.101 68 10/14/2026
4.0.2610.91 109 10/9/2026
4.0.2609.21 146 9/2/2026
3.0.2607.92 439 7/9/2026
2.0.2110.291 2,906 10/30/2021
2.0.2109.301 605 9/30/2021
2.0.2108.171 614 8/17/2021
2.0.2107.141 633 7/14/2021
2.0.2107.132 614 7/13/2021
2.0.2107.131 598 7/13/2021
2.0.2106.104 723 6/15/2021
2.0.2010.51-prerelease 665 10/5/2020
2.0.2010.21-prerelease 629 10/2/2020
2.0.2009.301-prerelease 556 9/30/2020
2.0.2006.222-prerelease 590 6/22/2020
2.0.2006.191-prerelease 636 6/19/2020
2.0.2006.181-prerelease 613 6/18/2020
2.0.2006.152-prerelease 648 6/15/2020
Loading failed

4.1 - adds bonding and the GATT status on failures. Device.BondState reports a device's bond (pairing) state, and on Android
           Adapter.DeviceBondStateChanged is raised when it changes. DeviceBondState gains Unknown, declared last
           so existing values are unchanged, which Apple platforms always report because CoreBluetooth does not
           expose bonding. BondStatusBroadcastReceiver is now registered by the Android adapter.
           Device.CreateBondAsync and Device.RemoveBondAsync request and remove a bond on Android; removal is
           best effort, through a hidden platform method, and reports false where Android refuses it. Both
           return false on Apple platforms and Windows.
           CharacteristicReadException, CharacteristicWriteException, DescriptorReadException and
           DescriptorWriteException carry the GATT status in GattStatus, classified as GattFailure, so an app can
           recognise Insufficient Authentication (0x05) and Insufficient Encryption (0x0F) on Android, Apple
           platforms and Windows. All four implement IGattStatusException. NativeStatus and messages are unchanged.

           4.0.1 - restores the stand-in seam that 4.0 removed. Device and Service can again be derived outside
           the library: protected Device(Guid id, string name, int rssi = 0) replaces the parameterless
           protected Device() that 3.x exposed, and Service(Device) is protected again. Device.Id stays
           settable only by the library; a stand-in passes its identity to the constructor instead.

           Also recorded from 4.0, where the release notes omitted it: AdvertisementRecordType.UuidsIncomple16Bit
           was renamed to UuidsIncomplete16Bit. A deliberate spelling correction, but a compile-breaking rename
           for a 3.x consumer that read that record type.

           Also on Windows: an advertisement is resolved to a device with the address type the advertisement
           carries, and a device glyph that cannot be loaded - or a host with no WPF Application - no longer
           raises an unobserved exception from inside the scan callback.

           4.0 - modernisation release. Contains breaking changes.

           This release corrects defects carried over from the Xamarin port and reshapes the public API.

           Fixed: a failed scan start no longer wedges the adapter into a permanent "already scanning" state;
           connection failures are no longer lost as unhandled async void exceptions; CancellationToken is now
           honoured throughout - service discovery, characteristic and descriptor reads and writes, notifications,
           MTU and RSSI - where it was previously accepted and discarded; on Android, a pending read or write is
           now cancelled when the link drops for any reason, a characteristic read is no longer completed by an
           unrelated notification, and a BluetoothGattCallback is no longer allocated for every advertisement
           received; malformed advertisements no longer crash the process from inside a native scan callback;
           built-in tracing, which had never been wired up on any platform, now works.

           Changed deliberately: starting a scan while one is running throws InvalidOperationException instead of
           silently doing nothing, and StopScanningForDevicesAsync now genuinely waits for the scan to stop; a scan
           that cannot start (adapter off, permission denied, unsupported) faults with AdapterScanException and
           raises ScanFailed instead of completing empty after the full timeout; devices arriving in the tail of a
           stopping scan are no longer reported; exceptions thrown by consumer event handlers are logged and
           swallowed instead of terminating the app; task continuations now resume on the thread pool rather than
           inline on the CoreBluetooth or Android GATT callback thread; Characteristic.WriteAsync returns Task and
           throws CharacteristicWriteException instead of returning an unexplained false on two platforms and
           throwing on the third; ConnectToKnownDeviceAsync reuses an already-discovered device instead of minting
           a duplicate (on Android, a duplicate BluetoothGatt); Device.Dispose() no longer starts an unawaited
           disconnect - disconnect explicitly first; on Android, 32-bit service-data advertisement records are no
           longer byte-reversed and incomplete 32-bit UUID lists now are, and a denied BLUETOOTH_SCAN /
           BLUETOOTH_CONNECT permission is reported as BluetoothState.Unauthorized; Device.State on iOS, macOS,
           Mac Catalyst and tvOS reports the new DeviceState.Disconnecting while a disconnect is in progress,
           where it previously reported Disconnected - Android and Windows still move straight to Disconnected,
           so test for Connected rather than for Disconnected if you need portable behaviour.

           Removed: BluetoothLE.UseOldFindCharacteristicMode, Adapter.ConnectedDeviceRegistry (use ConnectedDevices
           / TryGetConnectedDevice), the fork's PlatformNotSupportedException (the BCL type is thrown instead),
           BleCommandQueue, LEStream, the unreachable Android API-18 scan path, and several unused parameterless
           constructors.

           Added: BluetoothLE.WaitForStateAsync - await Bluetooth readiness instead of polling IsOn / IsAvailable,
           which on Apple can bail out on a working adapter that has not yet settled. Every exception the library
           raises deliberately now derives from BleException, so one catch clause covers them all.

           The Windows (net10.0-windows10.0.19041.0) and net481 targets compile cleanly, but were not run: the
           work was done on macOS, where those two targets build only by overriding TargetFrameworks with
           EnableWindowsTargeting. Their behaviour should be smoke-tested on a Windows agent before release.