一键导入
shiny-bluetoothle
Shiny BluetoothLE client/central operations for scanning, connecting, and communicating with BLE peripherals
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Shiny BluetoothLE client/central operations for scanning, connecting, and communicating with BLE peripherals
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Guide for implementing Firebase Cloud Messaging push notifications in .NET MAUI apps using Shiny.Push.FirebaseMessaging on iOS and Android.
Generate code using Shiny.DocumentDb.Firestore.Mobile, an on-device (native) Firebase Firestore provider for Shiny.DocumentDb on iOS and Android — offline-first persistence, real-time snapshot listeners, LINQ queries, and managed Firebase Auth identity.
Generate code using Shiny.Extensions.Push, a server-side push notification dispatch library for .NET with a provider-agnostic core and transports for APNs (.p8/ES256), FCM (HTTP v1), Web Push (VAPID + RFC 8291) and WNS (Windows App SDK / Entra auth), plus Shiny.DocumentDb persistence, structured targeting, topics, interceptors, dead-token pruning, multi-app keyed registration, runtime static/dynamic (multi-tenant) configuration via IPushConfigurationProvider, and System.Diagnostics.Metrics + tracing — fully AOT/trim-safe.
Generate code using Shiny.Speech for cross-platform speech-to-text, text-to-speech, audio capture, and audio playback with pluggable cloud providers
Generate code for Shiny.AiConversation - a centralized AI service library for .NET MAUI apps with chat client abstraction, wake word detection, speech-to-text/text-to-speech, acknowledgement modes (None/AudioBlip/LessWordy/Full), persistent message store, optional AI chat history lookup tool, and configurable sound effects
Generate code using Shiny Aspire integrations — Orleans ADO.NET hosting and Gluetun VPN container routing
| name | shiny-bluetoothle |
| description | Shiny BluetoothLE client/central operations for scanning, connecting, and communicating with BLE peripherals |
| auto_invoke | true |
| triggers | ["bluetooth","ble","bluetoothle","bluetooth le","bluetooth low energy","peripheral","gatt","characteristic","scan ble","ble scan","ble connect","IBleManager","IPeripheral","managed scan","ble notification","ble write","ble read","ble descriptor","advertisement","L2CAP","L2Cap","L2CapChannel","ICanL2Cap","OpenL2CapChannel","PSM"] |
Use this skill when the user needs to:
Do NOT use this skill for BLE hosting/peripheral mode (advertising, GATT server). That is a separate library (Shiny.BluetoothLE.Hosting).
Shiny.BluetoothLE (Android, iOS/macOS, Windows), Shiny.BluetoothLE.Linux (Linux via BlueZ), Shiny.BluetoothLE.Blazor (Blazor WebAssembly via Web Bluetooth API)Shiny.BluetoothLEShiny.BluetoothLE.ManagedThe Blazor implementation is built on the browser's Web Bluetooth API and inherits its limitations:
http://localhost required. The API is unavailable on plain http://.chrome://flags/#enable-web-bluetooth (or edge://flags, etc.) → Enabled → restart. Linux also needs experimental-web-platform-features on and BlueZ 5.43+.internet://flags → Web Bluetooth.Register in your MauiProgram.cs or host builder:
// Basic registration
services.AddBluetoothLE();
// With a delegate for background events (adapter state changes, peripheral connections)
services.AddBluetoothLE<MyBleDelegate>();
// iOS/macOS only - with Apple-specific configuration
services.AddBluetoothLE<MyBleDelegate>(new AppleBleConfiguration(
ShowPowerAlert: true,
RestoreIdentifier: "my-ble-app"
));
The delegate class:
public class MyBleDelegate : BleDelegate
{
public override Task OnAdapterStateChanged(AccessState state)
{
// Handle adapter state changes (foreground or background)
return Task.CompletedTask;
}
public override Task OnPeripheralStateChanged(IPeripheral peripheral)
{
// Handle peripheral connection state changes (foreground or background)
return Task.CompletedTask;
}
}
Add the BLE permissions to Platforms/Android/AndroidManifest.xml. Critical: on Android 12+ (API 31+) Shiny requests only BLUETOOTH_SCAN / BLUETOOTH_CONNECT at runtime — it does NOT request ACCESS_FINE_LOCATION. If you declare BLUETOOTH_SCAN without the neverForLocation flag, Android silently withholds all scan results unless fine location is also granted, so scans appear to return nothing. Unless your app actually derives physical location from BLE, always add neverForLocation:
<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11 and below -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
If you DO use BLE to infer location, omit neverForLocation and also request/grant ACCESS_FINE_LOCATION at runtime.
Scans discover both legacy and Bluetooth 5 extended advertisements automatically (when the chipset supports extended advertising); legacy advertisements that virtually all peripherals send are always included. To force a legacy-only scan, use new AndroidScanConfig(IncludeExtendedAdvertisements: false).
When generating BLE client code, follow these conventions:
Always request access before scanning: Call IBleManager.RequestAccess() or RequestAccessAsync() and verify AccessState.Available before starting a scan.
Use reactive (IObservable) APIs as the primary pattern: The library is built on System.Reactive. Use the Async extension methods only when you need Task-based patterns.
Dispose scan subscriptions: Only one scan can be active at a time. Always dispose the scan subscription or call StopScan() when done.
Use string-based UUIDs for services and characteristics: The API uses string UUIDs throughout (e.g., "180D" or "0000180d-0000-1000-8000-00805f9b34fb").
Prefer ConnectAsync for simple connection flows: It handles waiting for the connected state and has a default 30-second timeout.
Always call CancelConnection() or DisconnectAsync() when done: Connections are not automatically cleaned up.
Use IManagedScan for UI-bound scanning: It provides an INotifyReadOnlyCollection that works with MVVM bindings and handles peripheral deduplication, buffering, and stale removal.
Feature detection via interface checks: Optional capabilities (MTU request, pairing, reliable transactions) use feature interfaces. Always use the Try* or Can* extension methods rather than casting directly.
Handle BleException and BleOperationException: GATT operations can throw these. BleOperationException includes a GattStatusCode.
Connection auto-reconnect: ConnectionConfig.AutoConnect = true (default) enables automatic reconnection. Set to false for faster initial connections.
Some platforms support L2CAP Connection-Oriented Channels for streaming data without going through GATT. This is exposed as an optional capability — ICanL2Cap — on the platform Peripheral types.
using Shiny.BluetoothLE;
if (peripheral.IsL2CapAvailable())
{
// Backend supports L2CAP
}
// Safe variant — returns an empty observable on unsupported platforms
peripheral
.TryOpenL2CapChannel(psm: 0x0083, secure: false)
.Subscribe(channel => { /* ... */ });
// Direct access when the cast succeeds
if (peripheral is ICanL2Cap l2cap)
{
l2cap.OpenL2CapChannel(psm: 0x0083, secure: false).Subscribe(channel =>
{
// channel.Psm — the PSM the channel was opened on
// channel.Identifier — the remote peer identifier
// channel.DataReceived — IObservable<byte[]> of incoming bytes
// channel.Write(bytes) — IObservable<Unit> that completes when bytes are queued
});
}
L2CapChannel implements IDisposable — dispose it to close the underlying streams (Apple) or socket (Android).
using System.Reactive.Threading.Tasks;
channel.DataReceived.Subscribe(
payload => Console.WriteLine($"<- {payload.Length} bytes"),
ex => Console.WriteLine($"Channel error: {ex.Message}"),
() => Console.WriteLine("Remote closed the channel")
);
await channel.Write(payload).ToTask();
DataReceived is hot, emits right-sized byte arrays per read, completes on remote close, and surfaces I/O errors via OnError.
CBPeripheral.OpenL2CapChannel. The secure flag is ignored — security is set by how the peripheral published the channel.BluetoothDevice.CreateL2capChannel / CreateInsecureL2capChannel. Requires API 29+. Throws InvalidOperationException on older versions.IsL2CapAvailable() returns false).L2CapChannelExtensions.SendFile(...) streams a file over the channel with progress metrics (throughput, percent-complete, estimated time remaining) that match Shiny.Net.Http.TransferProgress:
using Shiny.BluetoothLE;
await channel.SendFile(
"/path/to/file.bin",
bufferSize: 4096,
onProgress: p => Console.WriteLine(
$"{p.PercentComplete:P0} ({p.BytesTransferred}/{p.BytesToTransfer}) " +
$"{p.BytesPerSecond / 1024} KB/s, ETA {p.EstimatedTimeRemaining}"
),
cancellationToken: ct
);
Stream overload exists for non-file sources. Pass totalBytes to enable percent / ETA; pass null and IsDeterministic will be false, PercentComplete returns -1, EstimatedTimeRemaining returns TimeSpan.Zero.IPeripheral: Both Shiny.BluetoothLE and Shiny.BluetoothLE.Hosting define an IPeripheral interface. If both packages are referenced, do NOT add Shiny.BluetoothLE.Hosting as a global using. Use file-level using or FQN (Shiny.BluetoothLE.IPeripheral) to disambiguate.DeviceInfo: Shiny.BluetoothLE has a DeviceInfo class that conflicts with Microsoft.Maui.Devices.DeviceInfo in MAUI apps. Use FQN when needed.ScanConfig with ServiceUuids to filter scans, especially on iOS where background scanning requires a service UUID filter.AndroidScanConfig for scan mode and batching options.AndroidConnectionConfig for connection priority settings.CharacteristicProperties before attempting read/write/notify operations using the convenience extensions (CanRead(), CanWrite(), CanNotify(), etc.).WriteCharacteristicBlob() for writing large data streams that exceed MTU size.NotifyCharacteristic() for real-time data streaming from a peripheral -- it handles subscription lifecycle and auto-reconnection.WhenConnected() and WhenDisconnected() convenience extensions for cleaner connection state handling.