Service
Overview
A Service represents a collection of related functionality on a BLE device. Services group together characteristics that work together to provide a specific feature. For example, a Heart Rate Service contains characteristics for measuring heart rate, body sensor location, and heart rate control.
Interface: IBluetoothRemoteService
What Does It Do?
A Service allows you to:
- Explore and access characteristics within the service
- Organize related device functionality
- Identify standard or custom services by UUID
- Navigate the GATT hierarchy
GATT Hierarchy Position
Device
└── Service ◄── You are here
└── Characteristic
└── Descriptor
A service sits between the device and its characteristics, acting as a logical grouping.
Standard Services
The Bluetooth SIG defines many standard services. Here are some common ones:
| Service Name | UUID | Purpose |
|---|---|---|
| Battery Service | 0000180F-0000-1000-8000-00805F9B34FB |
Battery level information |
| Heart Rate | 0000180D-0000-1000-8000-00805F9B34FB |
Heart rate measurements |
| Device Information | 0000180A-0000-1000-8000-00805F9B34FB |
Manufacturer, model, firmware version |
| Current Time | 00001805-0000-1000-8000-00805F9B34FB |
Time synchronization |
| Blood Pressure | 00001810-0000-1000-8000-00805F9B34FB |
Blood pressure measurements |
Custom Services: Devices can also define custom services with their own UUIDs for proprietary features.
Getting Started
1. Get a Service
After connecting to a device and exploring services:
await device.ConnectAsync();
await device.ExploreServicesAsync();
// Get service by UUID
var batteryServiceUuid = Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB");
var service = device.GetService(batteryServiceUuid);
// Get service by name
var service = device.GetService(s => s.Name == "Battery Service");
// Safe retrieval
var service = device.GetServiceOrDefault(batteryServiceUuid);
if (service == null)
{
Console.WriteLine("Battery service not found");
return;
}
2. Explore Characteristics
Before accessing characteristics, you need to discover them:
// Simple exploration (characteristics only)
await service.ExploreCharacteristicsAsync();
// Include descriptors
await service.ExploreCharacteristicsAsync(
CharacteristicExplorationOptions.Full
);
// Custom options
await service.ExploreCharacteristicsAsync(new CharacteristicExplorationOptions
{
ExploreDescriptors = true,
UseCache = false, // Force re-exploration
CharacteristicUuidFilter = uuid => uuid == myCharUuid
});
3. Access Characteristics
Once explored, retrieve characteristics:
// Get by UUID
var batteryLevelUuid = Guid.Parse("00002A19-0000-1000-8000-00805F9B34FB");
var characteristic = service.GetCharacteristic(batteryLevelUuid);
// Get by filter
var characteristic = service.GetCharacteristic(c => c.CanRead);
// Get all
var allCharacteristics = service.GetCharacteristics();
// Safe retrieval
var characteristic = service.GetCharacteristicOrDefault(batteryLevelUuid);
Service Properties
Basic Properties
// Service UUID
Guid id = service.Id;
// Service name (human-readable)
string name = service.Name; // e.g., "Battery Service" or "Unknown Service"
// Parent device
IBluetoothRemoteDevice device = service.Device;
Example
Console.WriteLine($"Service: {service.Name}");
Console.WriteLine($"UUID: {service.Id}");
Console.WriteLine($"Device: {service.Device}");
Characteristic Exploration
Exploration Options
// Option 1: Default (characteristics only, with caching)
await service.ExploreCharacteristicsAsync();
// Option 2: Include descriptors
await service.ExploreCharacteristicsAsync(
CharacteristicExplorationOptions.Full
);
// Option 3: Custom configuration
await service.ExploreCharacteristicsAsync(new CharacteristicExplorationOptions
{
ExploreDescriptors = true, // Include descriptors
UseCache = false, // Force re-exploration
CharacteristicUuidFilter = uuid => // Only specific characteristics
uuid == myCharacteristicUuid
});
Caching Behavior
By default, exploration results are cached:
// First call: explores the device
await service.ExploreCharacteristicsAsync();
// Second call: returns immediately (uses cache)
await service.ExploreCharacteristicsAsync();
// Force re-exploration
await service.ExploreCharacteristicsAsync(new CharacteristicExplorationOptions
{
UseCache = false
});
Characteristic List Management
Retrieve Characteristics
// Get single characteristic
var characteristic = service.GetCharacteristic(characteristicUuid);
// Get with filter
var writableChar = service.GetCharacteristic(c => c.CanWrite);
// Get all characteristics
var allChars = service.GetCharacteristics();
// Get with custom filter
var readableChars = service.GetCharacteristics(c => c.CanRead);
Check for Characteristics
// Check if characteristic exists
bool hasChar = service.HasCharacteristic(characteristicUuid);
// Check with filter
bool hasWritable = service.HasCharacteristic(c => c.CanWrite);
Clear Characteristics
// Clear all characteristics and stop notifications
await service.ClearCharacteristicsAsync();
This is useful when you want to force a complete re-discovery.
Events
Monitor changes to the characteristic list:
// Any change to the list
service.CharacteristicListChanged += (s, args) =>
{
Console.WriteLine($"Added: {args.AddedItems?.Count() ?? 0}, Removed: {args.RemovedItems?.Count() ?? 0}");
};
// Characteristics added
service.CharacteristicsAdded += (s, args) =>
{
foreach (var characteristic in args.Items)
{
Console.WriteLine($"Added: {characteristic.Name}");
}
};
// Characteristics removed
service.CharacteristicsRemoved += (s, args) =>
{
foreach (var characteristic in args.Items)
{
Console.WriteLine($"Removed: {characteristic.Name}");
}
};
State Properties
Check the service's current state:
// Is currently exploring characteristics?
bool isExploring = service.IsExploringCharacteristics;
Common Patterns
Complete Service Exploration
async Task ExploreServiceAsync(IBluetoothRemoteService service)
{
Console.WriteLine($"Exploring: {service.Name}");
// Explore characteristics and descriptors
await service.ExploreCharacteristicsAsync(
CharacteristicExplorationOptions.Full
);
// List all characteristics
var characteristics = service.GetCharacteristics();
Console.WriteLine($"Found {characteristics.Count} characteristics:");
foreach (var characteristic in characteristics)
{
Console.WriteLine($" - {characteristic.Name} ({characteristic.Id})");
Console.WriteLine($" Can Read: {characteristic.CanRead}");
Console.WriteLine($" Can Write: {characteristic.CanWrite}");
Console.WriteLine($" Can Listen: {characteristic.CanListen}");
}
}
Find Specific Characteristic
async Task<IBluetoothRemoteCharacteristic> FindCharacteristicAsync(
IBluetoothRemoteService service,
Guid characteristicUuid)
{
// Ensure characteristics are explored
await service.ExploreCharacteristicsAsync();
// Try to get the characteristic
var characteristic = service.GetCharacteristicOrDefault(characteristicUuid);
if (characteristic == null)
{
throw new InvalidOperationException(
$"Characteristic {characteristicUuid} not found in service {service.Name}"
);
}
return characteristic;
}
Read All Readable Characteristics
async Task ReadAllCharacteristicsAsync(IBluetoothRemoteService service)
{
await service.ExploreCharacteristicsAsync();
var readableChars = service.GetCharacteristics(c => c.CanRead);
foreach (var characteristic in readableChars)
{
try
{
var value = await characteristic.ReadValueAsync();
Console.WriteLine($"{characteristic.Name}: {BitConverter.ToString(value.ToArray())}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to read {characteristic.Name}: {ex.Message}");
}
}
}
Battery Service Example
async Task ReadBatteryLevelAsync(IBluetoothRemoteDevice device)
{
// Get Battery Service
var batteryServiceUuid = Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB");
var service = device.GetServiceOrDefault(batteryServiceUuid);
if (service == null)
{
Console.WriteLine("Device does not have a battery service");
return;
}
// Explore characteristics
await service.ExploreCharacteristicsAsync();
// Get Battery Level characteristic
var batteryLevelUuid = Guid.Parse("00002A19-0000-1000-8000-00805F9B34FB");
var characteristic = service.GetCharacteristic(batteryLevelUuid);
// Read battery level
var value = await characteristic.ReadValueAsync();
int batteryLevel = value.Span[0]; // First byte is the percentage
Console.WriteLine($"Battery Level: {batteryLevel}%");
}
Device Information Service Example
async Task ReadDeviceInfoAsync(IBluetoothRemoteDevice device)
{
var deviceInfoUuid = Guid.Parse("0000180A-0000-1000-8000-00805F9B34FB");
var service = device.GetService(deviceInfoUuid);
await service.ExploreCharacteristicsAsync();
// Manufacturer Name
var mfgNameChar = service.GetCharacteristicOrDefault(
Guid.Parse("00002A29-0000-1000-8000-00805F9B34FB")
);
if (mfgNameChar != null)
{
var value = await mfgNameChar.ReadValueAsync();
string manufacturer = Encoding.UTF8.GetString(value.ToArray());
Console.WriteLine($"Manufacturer: {manufacturer}");
}
// Model Number
var modelChar = service.GetCharacteristicOrDefault(
Guid.Parse("00002A24-0000-1000-8000-00805F9B34FB")
);
if (modelChar != null)
{
var value = await modelChar.ReadValueAsync();
string model = Encoding.UTF8.GetString(value.ToArray());
Console.WriteLine($"Model: {model}");
}
// Firmware Revision
var fwChar = service.GetCharacteristicOrDefault(
Guid.Parse("00002A26-0000-1000-8000-00805F9B34FB")
);
if (fwChar != null)
{
var value = await fwChar.ReadValueAsync();
string firmware = Encoding.UTF8.GetString(value.ToArray());
Console.WriteLine($"Firmware: {firmware}");
}
}
Best Practices
Always Explore First: Call
ExploreCharacteristicsAsync()before accessing characteristicsawait service.ExploreCharacteristicsAsync(); var characteristic = service.GetCharacteristic(uuid);Use Appropriate Exploration Depth: Only explore what you need
- Need characteristics only? Use default options
- Need descriptors too? Use
CharacteristicExplorationOptions.Full
Handle Missing Characteristics: Not all devices implement all characteristics
var characteristic = service.GetCharacteristicOrDefault(uuid); if (characteristic != null) { // Use characteristic }Cache Exploration Results: The default
UseCache = trueavoids redundant explorationsCheck Characteristic Capabilities: Before reading/writing, check
CanRead/CanWriteif (characteristic.CanRead) { var value = await characteristic.ReadValueAsync(); }Graceful Error Handling: Operations can fail
try { await service.ExploreCharacteristicsAsync(); } catch (DeviceNotConnectedException) { Console.WriteLine("Device disconnected during exploration"); } catch (TimeoutException) { Console.WriteLine("Exploration timed out"); }
Troubleshooting
Characteristics Not Found
- Ensure you called
ExploreCharacteristicsAsync()first - Check if the device actually implements the characteristic
- Try
UseCache = falseto force re-exploration - Verify the device is still connected
Exploration Fails
- Check device connection:
service.Device.IsConnected - Ensure sufficient timeout
- Some devices require authentication before exposing all characteristics
- Check if device firmware is up to date
Unknown Service Name
If service.Name returns "Unknown Service", it means:
- The service uses a custom UUID (not a standard Bluetooth SIG service)
- You're working with a proprietary service
You can still work with it using its UUID.
Performance Issues
- Only explore characteristics when needed
- Use
UseCache = true(default) to avoid repeated explorations - Don't explore descriptors unless you need them
- Use UUID filters to limit exploration scope
Related Topics
- Device - Managing device connections
- Characteristic - Reading and writing data
- Descriptor - Characteristic metadata