Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

406 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Bluetooth.Core & Bluetooth.Maui

Icon

CI .NET NuGet NuGet Downloads GitHub Release License

A cross-platform .NET MAUI Bluetooth Low Energy (BLE) library providing a clean, unified API for Android, iOS/MacCatalyst, and Windows platforms.

Features

  • πŸ” BLE Scanning - Discover nearby devices with customizable filtering
  • πŸ”— Connection Management - Robust connect/disconnect with auto-reconnect support
  • πŸ“‘ GATT Operations - Full support for services, characteristics, and descriptors
  • πŸ“Š Read/Write/Notify - Read values, write data, and subscribe to notifications
  • 🎯 Cross-Platform - Consistent API across all major platforms
  • πŸ”„ Modern Async - Async/await throughout with cancellation token support
  • πŸ’‰ DI-First - Built for MAUI dependency injection
  • 🧩 Options Pattern - Flexible configuration via options objects
  • πŸ“ Well-Documented - Comprehensive XML docs and examples

Platform Support

Platform Scanning Connection GATT Operations Broadcasting
Android βœ… βœ… βœ… βœ…
iOS βœ… βœ… βœ… βœ…
MacCatalyst βœ… βœ… βœ… βœ…
Windows βœ… βœ… βœ… βœ…

Note: Broadcasting (advertising and hosting local GATT services/characteristics/descriptors) is implemented on Android, Apple, and Windows. One known Windows limitation: IBluetoothConnectedDevice.DisconnectAsync() throws NotSupportedException on Windows, because the WinRT GATT server API has no direct way to force-disconnect a subscribed central β€” it drops on its own once the central stops interacting or the app stops advertising.

Installation

dotnet add package Bluetooth.Maui

Or via NuGet Package Manager:

<PackageReference Include="Bluetooth.Maui" Version="1.0.0" />

Quick Start

1. Register Services in MauiProgram.cs

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        builder
            .UseMauiApp<App>()
            .UseMauiCommunityToolkit();

        // Register Bluetooth services
        builder.Services.AddBluetoothServices();

        return builder.Build();
    }
}

2. Inject and Use IBluetoothScanner

public class ScannerViewModel
{
    private readonly IBluetoothScanner _scanner;

    public ScannerViewModel(IBluetoothScanner scanner)
    {
        _scanner = scanner;

        // Subscribe to device discovery
        _scanner.DeviceListChanged += OnDeviceListChanged;
    }

    public async Task StartScanningAsync()
    {
        var options = new ScanningOptions
        {
            // Optional: Configure scanning behavior
        };

        await _scanner.StartScanningAsync(options);
    }

    private void OnDeviceListChanged(object? sender, DeviceListChangedEventArgs e)
    {
        foreach (var device in _scanner.Devices)
        {
            Console.WriteLine($"Found: {device.Name} ({device.Id})");
            Console.WriteLine($"  RSSI: {device.SignalStrengthDbm} dBm");
        }
    }
}

Usage Guide

Scanning for Devices

// Start scanning
await _scanner.StartScanningAsync();

// Access discovered devices
var devices = _scanner.Devices;

// Stop scanning when done
await _scanner.StopScanningAsync();

Connecting to a Device

// Get a device from the scanner
var device = _scanner.Devices.FirstOrDefault(d => d.Name == "MyDevice");

if (device != null)
{
    // Connect with options
    var connectionOptions = new ConnectionOptions
    {
        // Platform-specific connection parameters
    };

    await device.ConnectAsync(connectionOptions);

    // Check connection status
    Console.WriteLine($"Connected: {device.IsConnected}");
}

Service Discovery - Simplified API

The new exploration APIs use a single, flexible method with optional configuration:

// Simple exploration (defaults: services only, caching enabled)
await device.ExploreServicesAsync();

// Explore services AND characteristics
await device.ExploreServicesAsync(ServiceExplorationOptions.WithCharacteristics);

// Full exploration (services + characteristics + descriptors)
await device.ExploreServicesAsync(ServiceExplorationOptions.Full);

// Force re-exploration (ignore cache)
await device.ExploreServicesAsync(new ServiceExplorationOptions
{
    UseCache = false
});

// Filter by service UUID
await device.ExploreServicesAsync(new ServiceExplorationOptions
{
    ServiceUuidFilter = uuid => uuid == myServiceUuid
});

Getting Services and Characteristics

// Get a specific service by UUID
var service = device.GetService(serviceGuid);

// Or use a filter
var service = device.GetService(s => s.Id == serviceGuid);

// Explore characteristics (simple)
await service.ExploreCharacteristicsAsync();

// Explore characteristics AND descriptors
await service.ExploreCharacteristicsAsync(CharacteristicExplorationOptions.Full);

// Get a characteristic
var characteristic = service.GetCharacteristic(characteristicGuid);

Reading and Writing

// Read a characteristic value
var value = await characteristic.ReadValueAsync();
Console.WriteLine($"Value: {BitConverter.ToString(value.ToArray())}");

// Write a value
byte[] data = new byte[] { 0x01, 0x02, 0x03 };
await characteristic.WriteValueAsync(data);

// Check capabilities
if (characteristic.CanRead)
{
    // Safe to read
}

if (characteristic.CanWrite)
{
    // Safe to write
}

Subscribing to Notifications

// Subscribe to value changes
characteristic.ValueUpdated += (sender, args) =>
{
    Console.WriteLine($"New value: {BitConverter.ToString(args.NewValue.ToArray())}");
    Console.WriteLine($"Old value: {BitConverter.ToString(args.OldValue.ToArray())}");
};

// Start listening
await characteristic.StartListeningAsync();

// Check listening state
Console.WriteLine($"Listening: {characteristic.IsListening}");

// Stop listening when done
await characteristic.StopListeningAsync();

Working with Descriptors

// Explore descriptors
await characteristic.ExploreDescriptorsAsync();

// Get a specific descriptor
var descriptor = characteristic.GetDescriptor(descriptorGuid);

// Read descriptor value
var value = await descriptor.ReadValueAsync();

// Write descriptor value
await descriptor.WriteValueAsync(new byte[] { 0x01, 0x00 });

Advanced Features

Connection Priority (Android)

Optimize connection parameters for your use case:

// High priority: Fast data transfer, low latency (11.25-15ms)
await device.RequestConnectionPriorityAsync(BluetoothConnectionPriority.High);

// Balanced: Moderate performance and power (30-50ms)
await device.RequestConnectionPriorityAsync(BluetoothConnectionPriority.Balanced);

// Low power: Battery optimization, higher latency (100-125ms)
await device.RequestConnectionPriorityAsync(BluetoothConnectionPriority.LowPower);

Note: iOS, macOS, and Windows manage connection parameters automatically. This API is a no-op on those platforms.

Timeout and Cancellation

All async operations support timeouts and cancellation:

using var cts = new CancellationTokenSource();

try
{
    await device.ConnectAsync(
        connectionOptions: new ConnectionOptions(),
        timeout: TimeSpan.FromSeconds(10),
        cancellationToken: cts.Token
    );
}
catch (TimeoutException)
{
    Console.WriteLine("Connection timed out");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation was cancelled");
}

Event-Driven Architecture

Subscribe to events for reactive programming:

// Scanner events
_scanner.RunningStateChanged += (s, e) => Console.WriteLine($"Scanning: {_scanner.IsRunning}");
_scanner.DeviceListChanged += OnDeviceListChanged;

// Device events
device.ConnectionStateChanged += (s, e) => Console.WriteLine($"State: {device.ConnectionState}");
device.Connected += (s, e) => Console.WriteLine("Connected");
device.Disconnected += (s, e) => Console.WriteLine("Disconnected");
device.UnexpectedDisconnection += (s, e) => Console.WriteLine($"Lost connection: {e.Exception}");

// Service events
service.CharacteristicListChanged += OnCharacteristicsChanged;

// Characteristic events
characteristic.ValueUpdated += OnValueUpdated;

Caching and Performance

The exploration APIs use intelligent caching by default:

// First call: Queries the device
await device.ExploreServicesAsync();  // UseCache = true (default)

// Subsequent calls: Returns cached results instantly
await device.ExploreServicesAsync();  // Cached, no device query

// Force refresh: Ignore cache
await device.ExploreServicesAsync(new ServiceExplorationOptions
{
    UseCache = false  // Forces device query
});

Cleanup and Disposal

Proper cleanup ensures resources are released:

// Clear services (stops notifications, clears cache)
await device.ClearServicesAsync();

// Clear specific service characteristics
await service.ClearCharacteristicsAsync();

// Clear characteristic descriptors
await characteristic.ClearDescriptorsAsync();

// Disconnect and dispose device
await device.DisconnectAsync();
await device.DisposeAsync();  // Implements IAsyncDisposable

Platform-Specific Setup

Android

Add Bluetooth permissions to AndroidManifest.xml:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
                 android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

iOS / MacCatalyst

Add Bluetooth usage description to Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth to scan for BLE devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs Bluetooth to scan for BLE devices</string>

Windows

Add Bluetooth capability to Package.appxmanifest:

<Capabilities>
  <DeviceCapability Name="bluetooth" />
</Capabilities>

Architecture

The solution is layered so that scanning (central role) and broadcasting (peripheral role) stay parallel, independent concerns all the way from the interfaces down to the platform implementations. Bluetooth.Maui is the only package published to NuGet β€” everything else is an internal building block pulled in transitively.

flowchart TB
    subgraph SAMPLES["Sample apps"]
        direction LR
        SAMPLE_SCAN["Sample.Scanner"]
        SAMPLE_BCAST["Sample.Broadcaster"]
    end

    MAUI["πŸ“¦ Bluetooth.Maui<br/>facade + DI composition root<br/>AddBluetoothServices()<br/>β€” the only NuGet-published package β€”"]

    subgraph PLATFORMS["Platform implementations β€” exactly one wired in per TargetFramework"]
        direction LR
        DROID["Platforms.Droid<br/>AndroidBluetooth*"]
        APPLE["Platforms.Apple<br/>AppleBluetooth*"]
        WIN["Platforms.Win<br/>WindowsBluetooth*"]
        NETCORE["Platforms.DotNetCore<br/>(stub β€” throws PlatformNotSupportedException)"]
    end

    subgraph CORE_LAYER["Core layer β€” shared base implementations"]
        direction LR
        CORE_SCAN["Bluetooth.Core.Scanning<br/>BaseBluetoothScanner..."]
        CORE_BCAST["Bluetooth.Core.Broadcasting<br/>BaseBluetoothBroadcaster..."]
        CORE["Bluetooth.Core<br/>BaseBluetoothAdapter"]
    end

    subgraph ABSTRACTIONS["Abstractions layer β€” platform-agnostic contracts"]
        direction LR
        ABS_SCAN["Bluetooth.Abstractions.Scanning<br/>IBluetoothScanner, IBluetoothRemoteDevice/Service/Characteristic/Descriptor"]
        ABS_BCAST["Bluetooth.Abstractions.Broadcasting<br/>IBluetoothBroadcaster, IBluetoothLocalService/Characteristic/Descriptor"]
        ABS["Bluetooth.Abstractions<br/>IBluetoothAdapter"]
    end

    SAMPLE_SCAN --> MAUI
    SAMPLE_BCAST --> MAUI

    MAUI --> CORE_SCAN
    MAUI --> CORE_BCAST
    MAUI -. "TFM-conditional, pick exactly 1" .-> DROID
    MAUI -. "TFM-conditional, pick exactly 1" .-> APPLE
    MAUI -. "TFM-conditional, pick exactly 1" .-> WIN
    MAUI -. "TFM-conditional, pick exactly 1" .-> NETCORE

    DROID --> CORE_SCAN
    DROID --> CORE_BCAST
    APPLE --> CORE_SCAN
    APPLE --> CORE_BCAST
    WIN --> CORE_SCAN
    WIN --> CORE_BCAST
    NETCORE --> CORE_SCAN
    NETCORE --> CORE_BCAST

    CORE_SCAN --> CORE
    CORE_SCAN --> ABS_SCAN
    CORE_BCAST --> CORE
    CORE_BCAST --> ABS_BCAST
    CORE --> ABS

    ABS_SCAN --> ABS
    ABS_BCAST --> ABS
Loading

Notes:

  • Arrows point from a project to the project(s) it depends on; transitive references (e.g. every project's implicit dependency on Bluetooth.Abstractions) are omitted for readability.
  • Bluetooth.Maui doesn't compile against all four platform projects at once β€” MSBuild conditions (IsForAndroid / IsForAppleStuff / IsForWindows / IsForPlainNetX) select exactly one per TargetFramework. At runtime, AddBluetoothServices() then registers the Bluetooth.Maui facade classes (BluetoothScanner/BluetoothBroadcaster) as the actual IBluetoothScanner/IBluetoothBroadcaster implementations, wrapping the platform implementation in front of consumers (see Docs/Architecture/ADR/0001-facade-overrides-platform-registrations.md).
  • Bluetooth.Linux exists as an empty placeholder folder for a future native Linux target; it has no project file yet and isn't part of the current build graph, so it's omitted above.
  • For deeper diagrams (class hierarchies, the DI registration tree, the async TaskCompletionSource coordination flow, etc.), see Docs/ARCHITECTURE_DIAGRAMS.md.

Core Interfaces

Scanning

  • IBluetoothScanner - Device discovery and scanning control
  • IBluetoothRemoteDevice - Remote device representation and connection
  • IBluetoothRemoteService - GATT service on a remote device
  • IBluetoothRemoteCharacteristic - GATT characteristic with read/write/notify
  • IBluetoothRemoteDescriptor - GATT descriptor

Broadcasting

  • IBluetoothBroadcaster - Peripheral/advertising mode
  • IBluetoothLocalService - Local GATT service
  • IBluetoothLocalCharacteristic - Local characteristic for broadcasting
  • IBluetoothConnectedDevice - Connected central device

Exception Handling

Comprehensive exception hierarchy for error handling:

try
{
    await device.ConnectAsync(connectionOptions);
}
catch (DeviceNotConnectedException ex)
{
    // Device is not connected when operation requires it
}
catch (ServiceNotFoundException ex)
{
    // Requested service not found on device
}
catch (CharacteristicNotFoundException ex)
{
    // Requested characteristic not found in service
}
catch (TimeoutException ex)
{
    // Operation timed out
}
catch (OperationCanceledException ex)
{
    // Operation was cancelled
}
catch (BluetoothException ex)
{
    // Base exception for all Bluetooth errors
}

API Design Principles

  1. Async First - All I/O operations are async with cancellation support
  2. Options Pattern - Flexible configuration via options objects
  3. Caching - Intelligent caching enabled by default for performance
  4. Events - Event-driven architecture for reactive patterns
  5. IAsyncDisposable - Proper resource cleanup with async disposal
  6. Immutability - ReadOnlyMemory for value types
  7. Platform Parity - Consistent API across all platforms

Contributing

Contributions are welcome! Please:

  1. Ensure all public APIs have XML documentation
  2. Follow the existing code style and patterns
  3. Add unit tests for new features
  4. Update the README for API changes

Requirements

  • .NET 10.0 or higher
  • .NET MAUI application
  • Platform-specific Bluetooth permissions (see setup section)

License

MIT License - Copyright (c) 2025 Laerdal Medical

See LICENSE.md for details.

Support

For issues, feature requests, or questions:

Changelog

Recent Changes

v1.0.0 (Current)

  • βœ… Windows platform implementation complete
  • βœ… Simplified exploration APIs (single method with options)
  • βœ… Modern DI registration with AddBluetoothServices()
  • βœ… Comprehensive XML documentation
  • βœ… Options pattern for all configuration

Built with ❀️ by Laerdal Medical

About

A cross-platform .NET MAUI BLE library providing a unified API for scanning, connecting and communicating with Bluetooth Low Energy devices on Android, iOS and Windows

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

8 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages