with one click
shiny-ble-hosting
// Generate code using Shiny.BluetoothLE.Hosting, a BLE peripheral hosting library for .NET with GATT server, advertising, and managed characteristic patterns
// Generate code using Shiny.BluetoothLE.Hosting, a BLE peripheral hosting library for .NET with GATT server, advertising, and managed characteristic patterns
Shiny BluetoothLE client/central operations for scanning, connecting, and communicating with BLE peripherals
Background job scheduling and execution for .NET MAUI (iOS/Android native OS schedulers) and in-process jobs for plain .NET, Linux, macOS, and Blazor WASM using Shiny.Jobs
Guide for generating code that uses Shiny.NET HTTP Transfers for background uploads and downloads on iOS/Android, Windows, Linux, macOS, and Blazor WASM (Service Worker Background Sync)
GPS tracking, geofence monitoring, and motion activity recognition for .NET MAUI, iOS, and Android using Shiny.Locations
Core infrastructure, hosting, DI, key-value stores, lifecycle hooks, and platform abstractions for Shiny on .NET MAUI, iOS, and Android
Guide for implementing push notifications in .NET MAUI apps using Shiny.Push (native FCM/APNs) and Shiny.Push.AzureNotificationHubs
| name | shiny-ble-hosting |
| description | Generate code using Shiny.BluetoothLE.Hosting, a BLE peripheral hosting library for .NET with GATT server, advertising, and managed characteristic patterns |
| auto_invoke | true |
| triggers | ["ble hosting","ble peripheral","ble advertise","ble advertising","gatt server","gatt service","gatt characteristic","ble host","bluetooth hosting","bluetooth peripheral","bluetooth advertise","IBleHostingManager","IGattService","IGattServiceBuilder","IGattCharacteristic","IGattCharacteristicBuilder","BleGattCharacteristic","BleGattCharacteristicAttribute","AddBluetoothLeHosting","AddBleHostedCharacteristic","StartAdvertising","StopAdvertising","AdvertiseBeacon","AdvertisementOptions","CharacteristicSubscription","GattResult","GattState","WriteRequest","ReadRequest","WriteOptions","NotificationOptions","IPeripheral","Shiny.BluetoothLE.Hosting","AttachRegisteredServices","DetachRegisteredServices","ibeacon advertise","ble notify","ble indicate","ble read characteristic","ble write characteristic","L2CAP","L2Cap","L2CapChannel","L2CapInstance","OpenL2Cap","PSM"] |
You are an expert in Shiny.BluetoothLE.Hosting, a .NET library for turning a mobile device into a BLE peripheral. It provides a GATT server, BLE advertising, iBeacon broadcasting, and a managed characteristic pattern using dependency injection and attribute-based registration.
Invoke this skill when the user wants to:
BleGattCharacteristic pattern with DI registrationShiny.BluetoothLE.HostingShiny.BluetoothLE.Hosting, Shiny.BluetoothLE.Hosting.ManagedShiny.Core, Shiny.BluetoothLE.CommonThe library has two usage patterns:
IBleHostingManager, call AddService with a builder lambda to configure characteristics inlineBleGattCharacteristic subclasses decorated with [BleGattCharacteristic], register them via AddBleHostedCharacteristic<T>(), and attach/detach them at runtimedotnet add package Shiny.BluetoothLE.Hosting
Imperative (no managed characteristics):
builder.Services.AddBluetoothLeHosting();
Managed pattern (preferred for structured services):
builder.Services.AddBleHostedCharacteristic<MyReadCharacteristic>();
builder.Services.AddBleHostedCharacteristic<MyWriteCharacteristic>();
// AddBluetoothLeHosting() is called automatically by AddBleHostedCharacteristic
When generating code for Shiny.BluetoothLE.Hosting projects, follow these conventions:
Always request access before advertising or adding services:
var access = await hostingManager.RequestAccess();
if (access != AccessState.Available)
{
// Handle denied/disabled/not supported
return;
}
Use the builder pattern to add services and characteristics inline:
var service = await hostingManager.AddService("12345678-1234-1234-1234-123456789abc", true, sb =>
{
sb.AddCharacteristic("12345678-1234-1234-1234-123456789ab1", cb =>
{
cb.SetRead(request =>
{
var data = System.Text.Encoding.UTF8.GetBytes("Hello");
return Task.FromResult(GattResult.Success(data));
});
cb.SetWrite(request =>
{
var received = request.Data;
if (request.IsReplyNeeded)
request.Respond(GattState.Success);
return Task.CompletedTask;
}, WriteOptions.Write);
cb.SetNotification(sub =>
{
// sub.IsSubscribing tells you if subscribing or unsubscribing
// sub.Peripheral is the central device
return Task.CompletedTask;
}, NotificationOptions.Notify);
});
});
Create a class that extends BleGattCharacteristic and decorate it with [BleGattCharacteristic]:
[BleGattCharacteristic("12345678-1234-1234-1234-123456789abc", "12345678-1234-1234-1234-123456789ab1")]
public class MyCharacteristic : BleGattCharacteristic
{
public override Task OnStart()
{
// Called when the service is attached
return Task.CompletedTask;
}
public override void OnStop()
{
// Called when the service is detached
}
public override Task<GattResult> OnRead(ReadRequest request)
{
var data = System.Text.Encoding.UTF8.GetBytes("Hello");
return Task.FromResult(GattResult.Success(data));
}
public override Task OnWrite(WriteRequest request)
{
var received = request.Data;
if (request.IsReplyNeeded)
request.Respond(GattState.Success);
return Task.CompletedTask;
}
public override Task OnSubscriptionChanged(IPeripheral peripheral, bool subscribed)
{
// React to central subscribing/unsubscribing
return Task.CompletedTask;
}
}
Register in DI:
builder.Services.AddBleHostedCharacteristic<MyCharacteristic>();
Attach at runtime:
await hostingManager.AttachRegisteredServices();
await hostingManager.StartAdvertising(new AdvertisementOptions("MyDevice", "12345678-1234-1234-1234-123456789abc"));
// Advertise with local name and service UUIDs
await hostingManager.StartAdvertising(new AdvertisementOptions(
LocalName: "MyDevice",
ServiceUuids: "12345678-1234-1234-1234-123456789abc"
));
// Advertise with defaults (no name, no service UUIDs)
await hostingManager.StartAdvertising();
// Stop advertising
hostingManager.StopAdvertising();
await hostingManager.AdvertiseBeacon(
uuid: Guid.Parse("12345678-1234-1234-1234-123456789abc"),
major: 1,
minor: 100,
txpower: -59
);
// From an IGattCharacteristic reference
var data = System.Text.Encoding.UTF8.GetBytes("Updated value");
// Notify all subscribed centrals
await characteristic.Notify(data);
// Notify specific centrals
await characteristic.Notify(data, specificPeripheral1, specificPeripheral2);
In the managed pattern, override Request instead of OnWrite to receive a write, process it, and automatically respond via notification:
[BleGattCharacteristic("service-uuid", "char-uuid")]
public class MyRequestCharacteristic : BleGattCharacteristic
{
public override Task<GattResult> Request(WriteRequest request)
{
// Process incoming data
var received = System.Text.Encoding.UTF8.GetString(request.Data);
// Return result -- this is sent back as a notification to the requesting central
var response = System.Text.Encoding.UTF8.GetBytes($"Echo: {received}");
return Task.FromResult(GattResult.Success(response));
}
}
Note: Request and OnWrite cannot both be overridden on the same characteristic.
When WriteRequest.IsReplyNeeded is true, you must call Respond:
cb.SetWrite(request =>
{
try
{
// Process data
if (request.IsReplyNeeded)
request.Respond(GattState.Success);
}
catch
{
if (request.IsReplyNeeded)
request.Respond(GattState.Failure);
}
return Task.CompletedTask;
}, WriteOptions.Write);
Publish an L2CAP PSM that centrals can connect to for streaming data without going through GATT. OpenL2Cap returns an L2CapInstance representing the listener; the onOpen callback fires for every accepted central connection. Each L2CapChannel is itself an IDisposable — dispose it to close that specific central's channel; dispose the L2CapInstance to stop accepting new connections and release the PSM.
using System.Reactive.Threading.Tasks;
using Shiny.BluetoothLE;
using Shiny.BluetoothLE.Hosting;
var instance = await hostingManager.OpenL2Cap(
secure: false,
onOpen: channel =>
{
Console.WriteLine($"Central {channel.Identifier} connected on PSM {channel.Psm}");
channel.DataReceived.Subscribe(
async payload =>
{
// Echo back
await channel.Write(payload).ToTask();
},
ex => Console.WriteLine($"Channel error: {ex.Message}"),
() => channel.Dispose()
);
}
);
Console.WriteLine($"Listening on PSM {instance.Psm}");
// Later, when shutting down:
instance.Dispose();
The platform-assigned PSM is on instance.Psm — advertise it to centrals out-of-band (typically through a GATT characteristic exposed by your service).
Platform notes:
CBPeripheralManager.PublishL2CapChannel(encryptionRequired). The secure flag maps to encryption-required.BluetoothAdapter.ListenUsing[Insecure]L2capChannel. Requires API 29+ — throws InvalidOperationException on older versions.L2CapChannelExtensions.SendFile(...) streams a file over a connected channel with progress metrics (throughput, percent-complete, ETA) matching the Shiny.Net.Http.TransferProgress shape. Useful for pushing large blobs to a connected central:
using Shiny.BluetoothLE;
using var instance = await hostingManager.OpenL2Cap(secure: false, onOpen: async channel =>
{
await channel.SendFile(
"/path/to/firmware.bin",
bufferSize: 4096,
onProgress: p => Console.WriteLine(
$"{p.PercentComplete:P0} {p.BytesPerSecond / 1024} KB/s ETA {p.EstimatedTimeRemaining}"
)
);
channel.Dispose();
});
A Stream overload is available for non-file sources; pass totalBytes to enable percent / ETA computation.
BleHosting/{Name}Characteristic.csFeatures/{Feature}/{Name}Characteristic.csIPeripheral: Both Shiny.BluetoothLE (client) and Shiny.BluetoothLE.Hosting define an IPeripheral interface with different members. If both packages are referenced in the same project, do NOT add both namespaces as global usings. Use file-level using directives or FQN (Shiny.BluetoothLE.Hosting.IPeripheral) to disambiguate.RequestAccess() and check the result before any hosting operationsBleGattCharacteristic subclasses are easier to test and maintainxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) or short 16-bit UUIDs for standard Bluetooth SIG servicesWriteRequest.IsReplyNeeded and call Respond with the appropriate GattStateGattResult.Error(GattState.Failure) in read handlers when an error occursStopAdvertising() and ClearServices() when doneBleGattCharacteristic instances to the GATT serverOnStop on each characteristicStartAdvertising if already advertisingFor detailed API signatures and examples, see:
reference/api-reference.md - Full API surface, interfaces, enums, records, and usage examples