DSoft.System.BluetoothLe
4.0.2609.21
dotnet add package DSoft.System.BluetoothLe --version 4.0.2609.21
NuGet\Install-Package DSoft.System.BluetoothLe -Version 4.0.2609.21
<PackageReference Include="DSoft.System.BluetoothLe" Version="4.0.2609.21" />
<PackageVersion Include="DSoft.System.BluetoothLe" Version="4.0.2609.21" />
<PackageReference Include="DSoft.System.BluetoothLe" />
paket add DSoft.System.BluetoothLe --version 4.0.2609.21
#r "nuget: DSoft.System.BluetoothLe, 4.0.2609.21"
#:package DSoft.System.BluetoothLe@4.0.2609.21
#addin nuget:?package=DSoft.System.BluetoothLe&version=4.0.2609.21
#tool nuget:?package=DSoft.System.BluetoothLe&version=4.0.2609.21
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.0.*" />
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.
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.UnavailableandBluetoothState.Off. net10.0andnetstandard2.0builds 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.TraceImplementationreceives every diagnostic message the library emits. It writes toSystem.Diagnostics.Traceby default; assign your own delegate to bridge it to your logging framework, or set it tonullto silence the library.
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.
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 | Versions 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. |
-
.NETFramework 4.8.1
- Microsoft.Windows.SDK.Contracts (>= 10.0.18362.2007)
-
.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.0.2609.21 | 74 | 9/2/2026 |
| 3.0.2607.92 | 411 | 7/9/2026 |
| 2.0.2110.291 | 2,893 | 10/30/2021 |
| 2.0.2109.301 | 586 | 9/30/2021 |
| 2.0.2108.171 | 600 | 8/17/2021 |
| 2.0.2107.141 | 618 | 7/14/2021 |
| 2.0.2107.132 | 601 | 7/13/2021 |
| 2.0.2107.131 | 582 | 7/13/2021 |
| 2.0.2106.104 | 708 | 6/15/2021 |
| 2.0.2010.51-prerelease | 648 | 10/5/2020 |
| 2.0.2010.21-prerelease | 616 | 10/2/2020 |
| 2.0.2009.301-prerelease | 545 | 9/30/2020 |
| 2.0.2006.222-prerelease | 577 | 6/22/2020 |
| 2.0.2006.191-prerelease | 626 | 6/19/2020 |
| 2.0.2006.181-prerelease | 602 | 6/18/2020 |
| 2.0.2006.152-prerelease | 635 | 6/15/2020 |
| 1.0.2006.52 | 833 | 6/5/2020 |
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.