BluetoothLE Hubs
Getting Started
Section titled “Getting Started”| GitHub | |
| Shared + Generator | |
| Host | |
| Client |
BluetoothLE Hubs brings the SignalR programming model to Bluetooth LE. One device hosts a hub, and nearby devices discover it, connect, and call it through a strongly typed proxy. The host pushes events back to all clients, one client, everyone else, or a group. No server, no network, no Wi-Fi: just phones in the same room.
It’s built on BluetoothLE (client) and BluetoothLE Hosting (host). The chunking, correlation, timeouts and reconnection rules are handled for you, and the code is generated at compile time, so it is AOT- and trim-safe with no reflection.
Features
Section titled “Features”- One interface describes the whole hub. Its methods are client → host calls (
Task,Task<T>,IAsyncEnumerable<T>) and its events are host → client pushes. - Source generated: the client proxy, hub dispatcher and typed push methods, with compile-time diagnostics for mistakes.
- Hubs:
Context,Clients(All / Others / Caller / Client / Group…),Groups,OnConnectedAsync/OnDisconnectedAsyncandContext.Abort(). - Outside a hub:
IHubContext<THub>to push, disconnect clients, or start and stop a single hub. - Calls: streaming with cancellation that reaches the host, timeouts, and remote exceptions.
- Large messages are chunked to the negotiated MTU automatically.
- File transfers over L2CAP with progress (avatars, logs, photos…).
- Several hubs per device, and several hub clients share one BLE connection.
-
Install the packages. The host app needs
Host, the client app needsClient, and an app that can play either role installs both. The shared package (with the source generator) comes along automatically.Terminal window dotnet add package Shiny.BluetoothLE.Hubs.Hostdotnet add package Shiny.BluetoothLE.Hubs.Client -
Describe the hub in an interface that both sides can see:
using Shiny.BluetoothLE.Hubs;[BleHubClient]public interface IChatHub{Task<bool> Send(string message); // client -> hostevent Action<string, string> Received; // host -> clients (who, message)}[JsonSerializable(typeof(string))][JsonSerializable(typeof(bool))]public partial class ChatJsonContext : JsonSerializerContext; -
Implement it on the host:
public class ChatHub : BleHub<IChatHub>{public async Task<bool> Send(string message){await this.Clients.All.Received(this.Context.Client.Name ?? "?", message);return true;}} -
Register everything in
MauiProgram:Shiny.Json.AddContext(ChatJsonContext.Default);// hostbuilder.Services.AddBluetoothLeHosting();builder.Services.AddBleHub<ChatHub>(ServiceUuid, ChatCharacteristicUuid);// clientbuilder.Services.AddBluetoothLE();builder.Services.AddBleHubClient<IChatHub>(ServiceUuid, ChatCharacteristicUuid); -
Start hosting on one device:
await hubContext.Start(); // IHubContext<ChatHub> - or IBleHubHost.Start() for every hub -
Connect and call it from another:
// IBleHubClient<IChatHub> clientclient.Hub.Received += (who, message) => MainThread.BeginInvokeOnMainThread(() => Messages.Add($"{who}: {message}"));var host = await client.Discover().FirstAsync();await client.Connect(host, new BleHubConnectOptions("Allan"));await client.Hub.Send("hello!");
Platform Setup
Section titled “Platform Setup”Android (Platforms/Android/AndroidManifest.xml):
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /><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" />iOS (Platforms/iOS/Info.plist):
<key>NSBluetoothAlwaysUsageDescription</key><string>Bluetooth is used to talk to nearby devices</string>| Feature | iOS | macOS / Mac Catalyst | Android | Windows |
|---|---|---|---|---|
| Host a hub | Foreground | Yes | Yes | — (can’t host) |
| Connect as a client | Yes | Yes | Yes | Yes |
| File transfers | Yes | Yes | API 29+ | — |
Sample
Section titled “Sample”The Tic Tac Toe sample is a .NET MAUI app for iOS and Android.
- One phone hosts and plays X. The next phone to join plays O, and anyone after that spectates.
- Moves are hub calls, and board updates and emotes are pushes.
- Avatars are uploaded and downloaded over L2CAP.
- Spectators sit in a group the host can remove in one go.
- Contracts & Source Generator: what a hub interface may contain and what gets generated
- Hosting Hubs:
BleHub<T>,IHubContext<THub>, groups, start/stop - Connecting Clients: discovery, calls, streams, events, failures
- File Transfers: L2CAP uploads and downloads
- How It Works: GATT layout, wire protocol, limits and best practices
Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skillsStep 2 — Install the plugin:
claude plugin install shiny@shinyOne plugin installs all 39 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 39 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.


