Broadcaster
Overview
The Broadcaster (also called Peripheral or GATT Server) allows your device to act as a BLE server. Instead of scanning for and connecting to other devices, your app can advertise services and accept connections from other devices (called Central or Client devices).
Interface: IBluetoothBroadcaster
What Does It Do?
The Broadcaster allows you to:
- Advertise your device's presence to nearby scanners
- Host GATT services that clients can discover and interact with
- Accept connections from central devices
- Provide data to connected clients
- Notify clients when data changes
Use Cases
Broadcasting is useful for:
- IoT Sensors: Temperature sensor that broadcasts readings
- Fitness Devices: Heart rate monitor that sends data to phone
- Smart Home: Light bulb that accepts control commands
- Beacons: Proximity beacons for location services
- Peer-to-Peer: Direct communication between mobile devices
Basic Workflow
┌───────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐
│ Request │─────▶│ Create │─────▶│ Start │─────▶│ Handle │
│Permission │ │ Services │ │Broadcasting│ │ Clients │
└───────────┘ └────────────┘ └────────────┘ └──────────┘
Getting Started
1. Request Permissions
// broadcaster is an IBluetoothBroadcaster injected via DI into the containing class's constructor
// Check permissions
bool hasPermission = await broadcaster.HasBroadcasterPermissionsAsync();
if (!hasPermission)
{
// Request permissions
await broadcaster.RequestBroadcasterPermissionsAsync();
}
Platform Notes:
- Android: Requests
BLUETOOTH_ADVERTISEpermission (API 31+) - iOS/macOS: Requests Bluetooth Always + Peripheral permissions
- Windows: Checks adapter and peripheral role support
2. Create a Service
// Create a simple service, then add a characteristic to it
var service = await broadcaster.CreateServiceAsync(
Guid.Parse("12345678-1234-1234-1234-123456789abc"),
name: "My Service",
isPrimary: true);
var characteristic = await service.CreateCharacteristicAsync(
Guid.Parse("12345678-1234-1234-1234-123456789abd"),
BluetoothCharacteristicProperties.Read | BluetoothCharacteristicProperties.Notify,
BluetoothCharacteristicPermissions.Read,
name: "My Characteristic");
// Set the initial value
await characteristic.UpdateValueAsync(new byte[] { 0x00 }, notifyClients: false);
3. Start Broadcasting
await broadcaster.StartBroadcastingAsync(new BroadcastingOptions
{
LocalDeviceName = "MyDevice",
IncludeDeviceName = true
});
Console.WriteLine("Broadcasting started!");
4. Handle Client Connections
// Client connections/disconnections are reported as batches, not one event per device
broadcaster.ClientDevicesAdded += (s, args) =>
{
foreach (var client in args.Items)
Console.WriteLine($"Client connected: {client.Name}");
};
broadcaster.ClientDevicesRemoved += (s, args) =>
{
foreach (var client in args.Items)
Console.WriteLine($"Client disconnected: {client.Name}");
};
5. Stop Broadcasting
await broadcaster.StopBroadcastingAsync();
Service Management
Create Services
// Create the service, then its characteristic
var service = await broadcaster.CreateServiceAsync(
Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB"), // Battery Service
isPrimary: true);
var characteristic = await service.CreateCharacteristicAsync(
Guid.Parse("00002A19-0000-1000-8000-00805F9B34FB"), // Battery Level
BluetoothCharacteristicProperties.Read | BluetoothCharacteristicProperties.Notify,
BluetoothCharacteristicPermissions.Read);
await characteristic.UpdateValueAsync(new byte[] { 100 }, notifyClients: false); // 100% battery
Get Services
// Get specific service
var service = broadcaster.GetService(serviceUuid);
// Get all services
var allServices = broadcaster.GetServices();
// Safe retrieval
var service = broadcaster.GetServiceOrDefault(serviceUuid);
if (service != null)
{
// Use service
}
// Check if service exists
bool hasService = broadcaster.HasService(serviceUuid);
Remove Services
// Remove specific service
await broadcaster.RemoveServiceAsync(serviceUuid);
// Remove by reference
await broadcaster.RemoveServiceAsync(service);
// Remove all services
await broadcaster.RemoveAllServicesAsync();
Broadcasting Options
Customize your broadcast behavior:
var options = new BroadcastingOptions
{
// Device name shown to scanners
LocalDeviceName = "MyDevice",
IncludeDeviceName = true,
// Service UUIDs to advertise
AdvertisedServiceUuids = new[]
{
Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB")
}
};
await broadcaster.StartBroadcastingAsync(options);
State Management
Monitor the broadcaster's state:
// Check current state
bool isRunning = broadcaster.IsRunning;
bool isStarting = broadcaster.IsStarting;
bool isStopping = broadcaster.IsStopping;
// Listen for state changes
broadcaster.Starting += (s, e) =>
Console.WriteLine("Broadcaster starting...");
broadcaster.Started += (s, e) =>
Console.WriteLine("Broadcaster started!");
broadcaster.Stopping += (s, e) =>
Console.WriteLine("Broadcaster stopping...");
broadcaster.Stopped += (s, e) =>
Console.WriteLine("Broadcaster stopped!");
broadcaster.RunningStateChanged += (s, e) =>
Console.WriteLine($"Running: {broadcaster.IsRunning}");
Client Device Management
Track connected clients:
// Get all connected clients
var clients = broadcaster.GetClientDevices();
// Get specific client
var client = broadcaster.GetClientDevice(clientId);
// Check if client is connected
bool hasClient = broadcaster.HasClientDevice(clientId);
// Listen for connection events (fired as batches)
broadcaster.ClientDevicesAdded += (s, args) =>
{
foreach (var client in args.Items)
Console.WriteLine($"Client {client.Id} connected");
};
broadcaster.ClientDevicesRemoved += (s, args) =>
{
foreach (var client in args.Items)
Console.WriteLine($"Client {client.Id} disconnected");
};
broadcaster.ClientDeviceListChanged += (s, args) =>
{
Console.WriteLine($"Total clients: {broadcaster.GetClientDevices().Count}");
};
Common Patterns
Simple Battery Service
async Task CreateBatteryServiceAsync(IBluetoothBroadcaster broadcaster)
{
// Create battery service and characteristic
var batteryServiceId = Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB");
var service = await broadcaster.CreateServiceAsync(batteryServiceId, isPrimary: true);
var characteristic = await service.CreateCharacteristicAsync(
Guid.Parse("00002A19-0000-1000-8000-00805F9B34FB"),
BluetoothCharacteristicProperties.Read | BluetoothCharacteristicProperties.Notify,
BluetoothCharacteristicPermissions.Read);
await characteristic.UpdateValueAsync(new byte[] { 100 }, notifyClients: false);
// Start broadcasting
await broadcaster.StartBroadcastingAsync(new BroadcastingOptions
{
LocalDeviceName = "Battery Monitor",
IncludeDeviceName = true,
AdvertisedServiceUuids = new[] { batteryServiceId }
});
Console.WriteLine("Battery service is broadcasting");
}
Update Characteristic Value
async Task UpdateBatteryLevelAsync(int level)
{
var service = broadcaster.GetService(batteryServiceUuid);
var characteristic = service.GetCharacteristic(batteryLevelUuid);
// Update value and notify subscribers
await characteristic.UpdateValueAsync(
new byte[] { (byte)level },
notifyClients: true
);
Console.WriteLine($"Battery level updated to {level}%");
}
Temperature Sensor
async Task CreateTemperatureSensorAsync(IBluetoothBroadcaster broadcaster)
{
var service = await broadcaster.CreateServiceAsync(
Guid.Parse("00001809-0000-1000-8000-00805F9B34FB"), // Health Thermometer
isPrimary: true);
var characteristic = await service.CreateCharacteristicAsync(
Guid.Parse("00002A1C-0000-1000-8000-00805F9B34FB"),
BluetoothCharacteristicProperties.Indicate,
BluetoothCharacteristicPermissions.Read);
await characteristic.UpdateValueAsync(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 }, notifyClients: false);
await broadcaster.StartBroadcastingAsync(new BroadcastingOptions
{
LocalDeviceName = "Temperature Sensor",
IncludeDeviceName = true
});
// Simulate temperature readings
while (broadcaster.IsRunning)
{
// Read temperature (simulated)
float temperature = 36.5f + Random.Shared.NextSingle() * 2;
// Format according to Health Thermometer spec
var tempBytes = FormatTemperature(temperature);
await characteristic.UpdateValueAsync(tempBytes, notifyClients: true);
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
byte[] FormatTemperature(float celsius)
{
// Flags: Celsius, no timestamp, no temperature type
byte flags = 0x00;
// Temperature in IEEE-11073 float format
int tempValue = (int)(celsius * 10);
byte[] temp = BitConverter.GetBytes(tempValue);
return new byte[] { flags, temp[0], temp[1], temp[2], temp[3] };
}
Handle Client Requests
// Track which clients are subscribed
var subscribedClients = new HashSet<string>();
characteristic.ClientSubscribed += (s, args) =>
{
subscribedClients.Add(args.ClientId);
Console.WriteLine($"Client {args.ClientId} subscribed to notifications");
};
characteristic.ClientUnsubscribed += (s, args) =>
{
subscribedClients.Remove(args.ClientId);
Console.WriteLine($"Client {args.ClientId} unsubscribed");
};
// Only notify subscribed clients
if (subscribedClients.Count > 0)
{
await characteristic.UpdateValueAsync(newValue, notifyClients: true);
}
Advanced Features
Dynamic Service Updates
var newServiceId = Guid.Parse("0000180D-0000-1000-8000-00805F9B34FB");
// Remove old service
await broadcaster.RemoveServiceAsync(oldServiceUuid);
// Add new service
var service = await broadcaster.CreateServiceAsync(newServiceId);
// There's no in-place "update options while running" API — restart broadcasting
// with the new set of advertised service UUIDs instead.
await broadcaster.StopBroadcastingIfNeededAsync();
await broadcaster.StartBroadcastingAsync(new BroadcastingOptions
{
AdvertisedServiceUuids = new[] { newServiceId }
});
Multiple Services
var batteryServiceId = Guid.Parse("0000180F-0000-1000-8000-00805F9B34FB");
var heartRateServiceId = Guid.Parse("0000180D-0000-1000-8000-00805F9B34FB");
var customServiceId = Guid.Parse("12345678-1234-1234-1234-123456789abc");
// Create multiple services
var batteryService = await broadcaster.CreateServiceAsync(batteryServiceId);
var heartRateService = await broadcaster.CreateServiceAsync(heartRateServiceId);
var customService = await broadcaster.CreateServiceAsync(customServiceId);
// Advertise all services
await broadcaster.StartBroadcastingAsync(new BroadcastingOptions
{
AdvertisedServiceUuids = new[]
{
batteryServiceId,
heartRateServiceId,
customServiceId
}
});
Best Practices
Request Permissions Early: Check and request broadcaster permissions before creating services
if (!await broadcaster.HasBroadcasterPermissionsAsync()) await broadcaster.RequestBroadcasterPermissionsAsync();Advertise Primary Services: Only advertise your main services to save advertising space
new BroadcastingOptions { AdvertisedServiceUuids = new[] { primaryServiceUuid } }Clean Up Resources: Stop broadcasting and remove services when done
try { await broadcaster.StartBroadcastingAsync(); // Use broadcaster... } finally { await broadcaster.StopBroadcastingIfNeededAsync(); await broadcaster.RemoveAllServicesAsync(); }Handle Client Disconnections: Track connected clients and clean up state
broadcaster.ClientDevicesRemoved += (s, args) => { foreach (var client in args.Items) { // Clean up client-specific state } };Use Standard Services: When possible, use Bluetooth SIG standard services and characteristics
Notify Efficiently: Only notify when values actually change
if (newValue != oldValue) await characteristic.UpdateValueAsync(newValue, notifyClients: true);
Platform Support
Broadcasting support varies by platform:
| Platform | Support | Notes |
|---|---|---|
| Android | Full | Requires Android 5.0+ (API 21+) |
| iOS | Full | Background mode requires capabilities |
| macOS | Full | Peripheral mode supported |
| Windows | Full | GATT server/peripheral role is fully implemented; a narrow set of operations (e.g. force-disconnecting a subscribed client) throw NotSupportedException due to Windows API limits |
Check support before implementing:
try
{
await broadcaster.StartBroadcastingAsync();
}
catch (PlatformNotSupportedException ex)
{
Console.WriteLine("Broadcasting not supported on this device");
}
Troubleshooting
Broadcasting Won't Start
- Check permissions:
await broadcaster.HasBroadcasterPermissionsAsync() - Ensure Bluetooth adapter supports peripheral role
- On Windows, check if device supports BLE peripheral mode
- Verify no other app is using peripheral mode
Clients Can't Connect
- Verify services are created before broadcasting
- Ensure device name is set (
LocalDeviceName+IncludeDeviceName) - Check platform-specific connection limits
Notifications Not Sent
- Verify characteristic has
NotifyorIndicateproperty - Check that client subscribed to notifications
- Ensure
notifyClients: truewhen updating value
Services Not Visible
- Advertise service UUIDs via
BroadcastingOptions.AdvertisedServiceUuids - Use standard UUIDs when possible
- Some platforms limit advertising data size
Related Topics
- Local Service - Managing hosted services
- Local Characteristic - Providing data to clients
- Connected Device - Tracking connected clients
- Advertisement - Understanding advertisements