Classic Bluetooth | Getting Started
Classic Bluetooth (BR/EDR) for the devices Bluetooth LE never replaced: SPP serial adapters, receipt
and label printers, barcode scanners, OBD dongles and a long tail of industrial and medical
equipment. One IBluetoothManager is both the client and the server. It finds devices, pairs with
them, and opens or accepts RFCOMM connections that you read and write as an ordinary
System.IO.Stream.
| GitHub | |
| Downloads |
What each platform can do
Section titled “What each platform can do”| Operation | Android | Windows | macOS | Linux |
|---|---|---|---|---|
| Paired devices | ✅ | ✅ | ✅ | ✅ |
| Discovery (inquiry) | ✅ | ✅ | ✅ | ✅ |
| Pair | ✅ | ✅ | ✅ | ✅ (needs a BlueZ agent for PIN / confirmation) |
| Connect (RFCOMM client) | ✅ | ✅ | ✅ | ✅ |
| Listen (RFCOMM server) | ✅ | ✅ | ✅ | ✅ |
| Make discoverable | ✅ (system dialog) | ❌ | ❌ | ✅ |
IBluetoothManager.Capabilities reports the same thing at runtime. An operation the platform does
not support throws BluetoothNotSupportedException.
Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skillsStep 2 — Install the plugin:
claude plugin install shiny@shinyOne plugin installs all 48 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.
Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skillsStep 2 — Install the plugin:
copilot plugin install shiny@shinyOne plugin installs all 48 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.
dotnet add package Shiny.BluetoothOn Linux, add the BlueZ-backed package as well:
dotnet add package Shiny.Bluetooth.Linuxusing Shiny;
builder.Services.AddBluetooth(); // Android, Windows, macOSservices.AddLinuxBluetooth(); // LinuxA MAUI app that also targets iOS has to leave the package out of the iOS and Mac Catalyst builds:
<PackageReference Include="Shiny.Bluetooth" Condition="!$(TargetFramework.Contains('-ios')) AND !$(TargetFramework.Contains('maccatalyst'))" />#if ANDROID || WINDOWSbuilder.Services.AddBluetooth();#endifThen ask for access. This requests the Android permissions or raises the macOS privacy prompt. It
returns Disabled when access is granted but the radio is off:
var access = await bluetooth.RequestAccess();if (access != AccessState.Available) return;See Platform Setup for the manifest entries and entitlements.
Finding devices
Section titled “Finding devices”var devices = new List<IBluetoothDevice>(await bluetooth.GetPairedDevices());
await foreach (var result in bluetooth.Discover(ct)){ Console.WriteLine($"{result.Device.Name} {result.Device.Address} {result.Rssi} dBm"); if (!devices.Contains(result.Device)) devices.Add(result.Device);}Discover runs an inquiry for about 12 seconds and yields each address once. On Android and macOS
that is the OS’s own inquiry cycle. Windows and Linux would otherwise scan until stopped, so they get
the same window (IBluetoothManager.InquiryDuration). Cancel the token or break out of the loop to end
it sooner. Finish the inquiry before connecting: an inquiry in progress slows connections on the
same radio to a crawl.
Each BluetoothDiscoveryResult holds the Device and the Rssi (signal strength in dBm) it was heard
at. The signal strength describes that sighting, not the device, so it lives on the result. Devices
from GetPairedDevices() have none.
An IBluetoothDevice carries:
| Property | |
|---|---|
Address |
Always AA:BB:CC:DD:EE:FF. GetDevice also accepts aa-bb-…, AABBCC… and the other platform forms. Two devices are equal when their addresses are. |
Name |
The friendly name, or null if the OS has not resolved it yet |
IsPaired |
Whether this machine is bonded with it, as read and kept current by Pair() |
ClassOfDevice / MajorDeviceClass |
The raw Class of Device, and its major class decoded (Phone, Imaging, AudioVideo, …) |
Pair() / Connect(serviceUuid) |
See below and Connections |
You don’t need to discover a device you already know the address of:
var device = await bluetooth.GetDevice("00:1A:7D:DA:71:13");GetDevice fills in the name, pairing state and class from whatever the OS already knows about the
address. On Linux, BlueZ must already know the device (paired, or seen by
Discover) before you can connect.
Pairing
Section titled “Pairing”if (!device.IsPaired && !await device.Pair(ct)) return; // declined, wrong PIN, or timed outThe OS shows its own confirmation or PIN prompt where the pairing method needs one. Pair returns
true when the device ends up paired, including when it already was. A secure Connect pairs on the
way in anyway, so call Pair when you want the prompt to come at a predictable point in your UI.
On Linux, a device that asks for a PIN or a confirmation needs a BlueZ agent to answer it. A desktop
session already has one. On a headless device, pair once with bluetoothctl (agent on,
default-agent, pair <address>). Shiny marks a device it paired as trusted, so it can connect to
your server later without BlueZ asking an agent to authorize it.
- Connections: connect to a service, host one, and work with the stream
- Platform Setup: permissions, entitlements and platform limits


