Peripheral
Overview
Section titled “Overview”IPeripheral represents a BLE device discovered during scanning. It provides connection management and GATT operations.
| Property | Type | Description |
|---|---|---|
Uuid |
string |
Unique identifier for this peripheral |
Name |
string? |
Local name (may be null) |
Mtu |
int |
Usable payload per GATT operation - the negotiated ATT MTU minus the 3-byte ATT header. Starts at 20 |
Status |
ConnectionState |
Current connection state |
Connecting
Section titled “Connecting”IPeripheral peripheral; // from scan result
// Fire and forget - connects when in rangeperipheral.Connect();
// Async - waits for connection to establishawait peripheral.ConnectAsync(cancelToken: cts.Token, timeout: TimeSpan.FromSeconds(10));Auto Connect
Section titled “Auto Connect”AutoConnect (on by default) keeps the peripheral connected for you - the connection is re-established when the
peripheral comes back into range, is power-cycled, or the user toggles Bluetooth off and on, without you having to
watch WhenDisconnected() and call Connect() again.
peripheral.Connect(new ConnectionConfig(AutoConnect: true));
// opt out - faster initial connection, but you own reconnectingperipheral.Connect(new ConnectionConfig(AutoConnect: false));Setting false speeds up the initial connection (the OS connects to the peripheral it can see right now instead of
arming a background connect), at the cost of reconnecting yourself.
An explicit CancelConnection() is final - it tears the auto-reconnect down, so a deliberate disconnect never
reconnects behind your back. Call Connect() again to arm it once more.
Bluetooth turned off and on
Section titled “Bluetooth turned off and on”Applies to iOS, Mac Catalyst, macOS, and Android. Users toggle Bluetooth in Settings - often because something is
misbehaving - and neither OS reports the resulting drop per peripheral. CoreBluetooth does not call
didDisconnectPeripheral when the adapter powers down, and several Android devices deliver no GATT connection-state
callback either. Shiny watches the adapter itself and treats a power-down as a disconnect:
- Every peripheral that was
ConnectedorConnectinggets the full teardown a real disconnect gets - notifiers cleared, in-flight operations broken so the operation queue is not left holding its lock, and on Android the GATT client closed and service discovery re-armed. WhenStatusChanged()emitsDisconnected, so the status stream agrees withIPeripheral.Status(which reads the platform live and reportedDisconnectedall along).- When the adapter comes back, every peripheral connected with
AutoConnect: trueis reconnected.
Connects issued while the adapter is off are parked, not dropped. ConnectPeripheral below PoweredOn on Apple
and ConnectGatt with the adapter off on Android are silent no-ops that never report back, so Shiny holds the request
and replays it when the adapter returns - your own Connect() from OnAdapterStateChanged included. A
CancelConnection() discards anything parked, as you would expect.
Starting a scan while a peripheral is waiting to reconnect is safe: Scan() prunes the manager’s peripheral cache, but
it no longer evicts a peripheral with an armed auto-reconnect or a parked connect. Scanning as a fallback while the link
is down - a natural thing to do - will not quietly cancel the reconnect.
Disconnecting
Section titled “Disconnecting”peripheral.CancelConnection();
// or asyncawait peripheral.DisconnectAsync();Always call CancelConnection() when you’re done with a peripheral. Not doing so will leave the connection open and drain the device battery.
Monitoring Connection Status
Section titled “Monitoring Connection Status”peripheral .WhenStatusChanged() .Subscribe(state => { // ConnectionState: Connecting, Connected, Disconnecting, Disconnected });
// Convenience extensionsperipheral.WhenConnected().Subscribe(p => { /* connected */ });peripheral.WhenDisconnected().Subscribe(p => { /* disconnected */ });Connection Failures
Section titled “Connection Failures”peripheral .WhenConnectionFailed() .Subscribe(ex => { // BleException with details about the failure });MTU Negotiation
Section titled “MTU Negotiation”The ATT MTU (Maximum Transmission Unit) determines how much data fits in a single GATT operation. Every BLE link starts at the spec minimum of 23 bytes, of which 3 are the ATT header - so 20 bytes of payload.
// Check if ATT MTU requests are supportedif (peripheral.CanRequestMtu()){ // 512 is the requested ATT MTU; the result is the usable payload var payloadSize = await peripheral.TryRequestMtuAsync(512); Console.WriteLine($"Usable payload: {payloadSize} bytes"); // 509 when 512 is granted}
// Fragment to this value as-isforeach (var chunk in data.Chunk(peripheral.Mtu)) await peripheral.WriteCharacteristicAsync(serviceUuid, charUuid, chunk);Need the ATT MTU itself - to hand to a peer protocol that negotiates its own framing, for example? Add the header back:
var attMtu = peripheral.Mtu + BleConstants.AttHeaderSize;Pairing
Section titled “Pairing”// Check if pairing is availableif (peripheral.IsPairingRequestsAvailable()){ var result = await peripheral .TryPairingRequest() .ToTask();
if (result == true) Console.WriteLine("Paired successfully");}
// Check current pairing statusvar status = peripheral.TryGetPairingStatus();// PairingState: NotPaired, PairedReading RSSI
Section titled “Reading RSSI”var rssi = await peripheral.ReadRssiAsync();Console.WriteLine($"RSSI: {rssi} dBm");

