Device
Overview
A Device (also called a Remote Device) represents a Bluetooth Low Energy peripheral that you've discovered through scanning. It provides methods to connect, disconnect, and explore the device's services and characteristics.
Interface: IBluetoothRemoteDevice
What Does It Do?
The Device interface allows you to:
- Connect and disconnect from BLE devices
- Explore the device's GATT hierarchy (services, characteristics, descriptors)
- Monitor connection state changes
- Access device properties (name, address, signal strength, battery level)
- Configure connection parameters
GATT Hierarchy
Understanding the GATT (Generic Attribute Profile) structure is key to working with BLE devices:
Device
└── Service (e.g., Heart Rate Service)
└── Characteristic (e.g., Heart Rate Measurement)
└── Descriptor (e.g., Client Characteristic Configuration)
Each level serves a purpose:
- Device: The physical hardware
- Service: A collection of related functionality
- Characteristic: A specific data value you can read/write/listen to
- Descriptor: Configuration or metadata for a characteristic
Basic Workflow
┌──────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐
│ Discover │─────▶│ Connect │─────▶│ Explore │─────▶│ Access │
│ Device │ │ │ │ Services │ │ Data │
└──────────┘ └─────────┘ └──────────┘ └────────────┘
│
▼
┌──────────────┐
│ Disconnect │
└──────────────┘
Getting Started
1. Discover a Device
First, use the Scanner to find your device:
await scanner.StartScanningAsync();
IBluetoothRemoteDevice device = await scanner.WaitForDeviceToAppearAsync(
d => d.Name == "MyDevice",
timeout: TimeSpan.FromSeconds(10));
await scanner.StopScanningAsync();
2. Connect to the Device
// Simple connection
await device.ConnectAsync();
// With options
await device.ConnectAsync(new ConnectionOptions
{
WaitForAdvertisementBeforeConnecting = true
});
// Safe variant - only connects if not already connected
await device.ConnectIfNeededAsync();
3. Explore Services
Before you can access data, you need to discover what the device offers:
// Discover only services
await device.ExploreServicesAsync();
// Discover services AND characteristics
await device.ExploreServicesAsync(ServiceExplorationOptions.WithCharacteristics);
// Full discovery (services + characteristics + descriptors)
await device.ExploreServicesAsync(ServiceExplorationOptions.Full);
4. Access Services
Once explored, retrieve services:
// Get a specific service by UUID
var heartRateServiceUuid = Guid.Parse("0000180D-0000-1000-8000-00805F9B34FB");
var service = device.GetService(heartRateServiceUuid);
// Get service by filter
var service = device.GetService(s => s.Name.Contains("Battery"));
// Get all services
var allServices = device.GetServices();
// Safe retrieval (returns null if not found)
var service = device.GetServiceOrDefault(heartRateServiceUuid);
5. Disconnect
await device.DisconnectAsync();
// Safe variant - only disconnects if connected
await device.DisconnectIfNeededAsync();
Connection States
Monitor the device's connection state:
// Check current state
bool isConnected = device.IsConnected;
bool isConnecting = device.IsConnecting;
bool isDisconnecting = device.IsDisconnecting;
// Listen for state changes
device.Connected += (s, e) =>
Console.WriteLine("Device connected!");
device.Disconnected += (s, e) =>
Console.WriteLine("Device disconnected!");
device.ConnectionStateChanged += (s, args) =>
Console.WriteLine($"State: {args.NewState}");
// Wait for a specific state
await device.WaitForIsConnectedAsync(isConnected: true);
Exploration Options
The ServiceExplorationOptions class controls how deeply you explore the device:
Exploration Depth
// Option 1: Using predefined options
await device.ExploreServicesAsync(ServiceExplorationOptions.ServicesOnly);
await device.ExploreServicesAsync(ServiceExplorationOptions.WithCharacteristics);
await device.ExploreServicesAsync(ServiceExplorationOptions.Full);
// Option 2: Custom configuration
await device.ExploreServicesAsync(new ServiceExplorationOptions
{
Depth = ExplorationDepth.Characteristics,
UseCache = false, // Force re-exploration
ServiceUuidFilter = uuid => uuid == myServiceUuid // Only explore specific service
});
Depth Levels:
ExplorationDepth.ServicesOnly: Discover services onlyExplorationDepth.Characteristics: Discover services and characteristicsExplorationDepth.Descriptors: Full discovery (services + characteristics + descriptors)
Caching
By default, exploration results are cached:
// First call: actually explores the device
await device.ExploreServicesAsync();
// Second call: returns immediately (uses cache)
await device.ExploreServicesAsync();
// Force re-exploration
await device.ExploreServicesAsync(new ServiceExplorationOptions
{
UseCache = false
});
Filtering
Limit exploration to specific services:
var batteryServiceUuid = Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB");
await device.ExploreServicesAsync(new ServiceExplorationOptions
{
Depth = ExplorationDepth.Characteristics,
ServiceUuidFilter = uuid => uuid == batteryServiceUuid
});
Connection Options
Customize connection behavior:
var options = new ConnectionOptions
{
// Wait for device to advertise before connecting
WaitForAdvertisementBeforeConnecting = true,
// Configure retry behavior
ConnectionRetry = new RetryOptions
{
MaxRetries = 3,
DelayBetweenRetries = TimeSpan.FromMilliseconds(200)
},
// Platform-specific options
Android = new AndroidConnectionOptions
{
AutoConnect = false,
ConnectionPriority = BluetoothConnectionPriority.High,
TransportType = BluetoothTransportType.Le
}
};
await device.ConnectAsync(options);
Connection Priority (Android)
On Android, you can adjust connection parameters for different use cases:
// After connecting, change priority
await device.RequestConnectionPriorityAsync(
BluetoothConnectionPriority.High // Low latency for fast transfers
);
Priority Modes:
Balanced: Default, reasonable performance (30-50ms interval)High: Low latency, high power (11-15ms interval) - for gaming, real-time dataLowPower: Battery optimization (100-125ms interval) - for infrequent updates
Service List Management
Work with the list of discovered services:
// Check if service exists
bool hasService = device.HasService(serviceUuid);
// Get multiple services
var services = device.GetServices(s => s.Name.StartsWith("Custom"));
// Clear all services (useful before re-exploration)
await device.ClearServicesAsync();
// Listen for service changes
device.ServiceListChanged += (s, args) =>
{
Console.WriteLine($"Total services: {device.GetServices().Count}");
};
device.ServicesAdded += (s, args) =>
{
foreach (var service in args.Items)
Console.WriteLine($"Added: {service.Name}");
};
device.ServicesRemoved += (s, args) =>
{
foreach (var service in args.Items)
Console.WriteLine($"Removed: {service.Name}");
};
Unexpected Disconnections
Handle unexpected disconnections gracefully:
device.UnexpectedDisconnection += (s, args) =>
{
Console.WriteLine("Device disconnected unexpectedly!");
Console.WriteLine($"Reason: {args.Reason}");
// Attempt reconnection
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
await device.ConnectIfNeededAsync();
});
};
// Ignore the next unexpected disconnection
// (useful when you expect the device to disconnect)
device.IgnoreNextUnexpectedDisconnection = true;
await device.DisconnectAsync();
Common Patterns
Complete Connection Flow
async Task ConnectAndExploreAsync(IBluetoothRemoteDevice device)
{
try
{
// Connect
await device.ConnectAsync();
Console.WriteLine("Connected!");
// Full exploration
await device.ExploreServicesAsync(ServiceExplorationOptions.Full);
Console.WriteLine($"Found {device.GetServices().Count} services");
// List all services
foreach (var service in device.GetServices())
{
Console.WriteLine($"Service: {service.Name} ({service.Id})");
foreach (var characteristic in service.GetCharacteristics())
{
Console.WriteLine($" Characteristic: {characteristic.Name}");
Console.WriteLine($" Can Read: {characteristic.CanRead}");
Console.WriteLine($" Can Write: {characteristic.CanWrite}");
Console.WriteLine($" Can Listen: {characteristic.CanListen}");
}
}
}
catch (TimeoutException)
{
Console.WriteLine("Connection timed out");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
Find and Connect to Specific Device
// scanner is an IBluetoothScanner injected via DI into the containing class's constructor
async Task<IBluetoothRemoteDevice> FindAndConnectAsync(IBluetoothScanner scanner, string deviceName)
{
await scanner.StartScanningAsync();
// Waits for a matching device, or throws TimeoutException after 10s
var device = await scanner.WaitForDeviceToAppearAsync(
d => d.Name == deviceName,
timeout: TimeSpan.FromSeconds(10));
await scanner.StopScanningAsync();
// Connect to device
await device.ConnectAsync();
return device;
}
Connection with Retry
async Task RobustConnectAsync(IBluetoothRemoteDevice device, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
await device.ConnectAsync();
Console.WriteLine("Connected successfully!");
return;
}
catch (Exception ex)
{
Console.WriteLine($"Attempt {i + 1} failed: {ex.Message}");
if (i < maxRetries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(1));
}
else
{
throw;
}
}
}
}
Best Practices
Always Disconnect: Release resources when done
try { await device.ConnectAsync(); // Work with device... } finally { await device.DisconnectIfNeededAsync(); }Explore Before Accessing: Always call
ExploreServicesAsync()before trying to access servicesUse Appropriate Exploration Depth: Only explore what you need
- Need services only? Use
ServicesOnly - Need characteristics? Use
WithCharacteristics - Need everything? Use
Full
- Need services only? Use
Handle Connection Failures: BLE connections can be unreliable
try { await device.ConnectAsync(); } catch (TimeoutException) { // Retry or inform user }Monitor Connection State: Listen to
Connected/DisconnectedeventsDispose Properly: Device implements
IAsyncDisposableawait using var device = await scanner.WaitForDeviceToAppearAsync(d => d.Name == "MyDevice"); await device.ConnectAsync(); // Use device... // Automatically disconnected and disposed
Troubleshooting
Connection Fails
- Ensure device is in range and powered on
- Check Bluetooth permissions
- Try increasing the connection timeout
- Enable connection retry in
ConnectionOptions - On Android, ensure location services are enabled
Services Not Found
- Call
ExploreServicesAsync()after connecting - Use appropriate exploration depth
- Check if the device actually offers the service
- Try
UseCache = falseto force re-exploration
Unexpected Disconnections
- Implement reconnection logic in
UnexpectedDisconnectionevent - Check signal strength (device may be out of range)
- Verify device battery isn't depleted
- Some devices disconnect after a period of inactivity
Slow Connection
- Remove
WaitForAdvertisementBeforeConnectingoption - On Android, use
Highconnection priority for faster transfers - Reduce exploration depth if you don't need all data
Related Topics
- Scanner - Discover devices
- Service - Access device services
- Characteristic - Read/write device data
- Advertisement - Device advertisement data