Shiny.BluetoothLE.Hubs
1.0.0-alpha-0004-gbc068ff52e
Prefix Reserved
dotnet add package Shiny.BluetoothLE.Hubs --version 1.0.0-alpha-0004-gbc068ff52e
NuGet\Install-Package Shiny.BluetoothLE.Hubs -Version 1.0.0-alpha-0004-gbc068ff52e
<PackageReference Include="Shiny.BluetoothLE.Hubs" Version="1.0.0-alpha-0004-gbc068ff52e" />
<PackageVersion Include="Shiny.BluetoothLE.Hubs" Version="1.0.0-alpha-0004-gbc068ff52e" />
<PackageReference Include="Shiny.BluetoothLE.Hubs" />
paket add Shiny.BluetoothLE.Hubs --version 1.0.0-alpha-0004-gbc068ff52e
#r "nuget: Shiny.BluetoothLE.Hubs, 1.0.0-alpha-0004-gbc068ff52e"
#:package Shiny.BluetoothLE.Hubs@1.0.0-alpha-0004-gbc068ff52e
#addin nuget:?package=Shiny.BluetoothLE.Hubs&version=1.0.0-alpha-0004-gbc068ff52e&prerelease
#tool nuget:?package=Shiny.BluetoothLE.Hubs&version=1.0.0-alpha-0004-gbc068ff52e&prerelease
Shiny.BluetoothLE.Hubs
Shiny.BluetoothLE.Hubs gives you SignalR-style hubs over Bluetooth LE. One device hosts a hub, and nearby devices discover it, connect, and call it through a source-generated, strongly typed proxy. The host pushes events back to everyone, to some clients, or to groups. Files move over L2CAP, a faster direct channel between the devices.
It's built on Shiny.BluetoothLE and Shiny.BluetoothLE.Hosting, and it is AOT- and trim-safe with no reflection.
// shared contract
[BleHubClient]
public interface IGameHub
{
Task<MoveResult> MakeMove(int cell); // client -> host
IAsyncEnumerable<int> Countdown(int from, CancellationToken ct);
event Action<GameState> StateChanged; // host -> clients
}
// host
public class GameHub(GameEngine engine) : BleHub<IGameHub>
{
public async Task<MoveResult> MakeMove(int cell)
{
// Context.Client, Context.Abort(), Groups, OnConnectedAsync/OnDisconnectedAsync
await Clients.All.StateChanged(engine.Snapshot()); // generated typed push
return new MoveResult(true, null);
}
...
}
// client - GameHubClient is generated
client.Hub.StateChanged += state => ...;
await client.Connect(host, new BleHubConnectOptions("Allan"));
var result = await client.Hub.MakeMove(4);
await client.UploadFile(path, "avatar.jpg"); // L2CAP
Packages
| Project | Purpose |
|---|---|
Shiny.BluetoothLE.Hubs |
Wire protocol (framing, chunking, reassembly), argument codec, serializer, [BleHubClient]. Ships the source generator |
Shiny.BluetoothLE.Hubs.Host |
BleHub<T>, IBleHubHost, IHubContext<THub>, groups, L2CAP file server |
Shiny.BluetoothLE.Hubs.Client |
BleHubClient (base of the generated proxies), discovery, shared connections, file upload and download |
Setup
Json.AddContext(MyJsonContext.Default); // hub arguments/results are AOT-safe JSON by default
// host
services.AddBluetoothLeHosting();
services.AddBleHub<GameHub>(ServiceUuid, CharacteristicUuid, o => o.MaxClients = 6);
services.ConfigureBleHubHost(o => o.EnableFileTransfers(Path.Combine(FileSystem.AppDataDirectory, "files")));
await serviceProvider.GetRequiredService<IBleHubHost>().Start();
// client
services.AddBluetoothLE();
services.AddBleHubClient<IGameHub>(ServiceUuid, CharacteristicUuid); // inject IBleHubClient<IGameHub>, GameHubClient or IGameHub
- Contract: hub methods return
Task,Task<T>orIAsyncEnumerable<T>. A trailingCancellationTokenis passed through to the host. Events areAction/Action<T1..T4>. Compile errors are reported asSBH001–SBH006. - Hubs: a new hub instance runs, in its own DI scope, for every call, like SignalR. The hub may take the contract's
CancellationTokenor leave it out. - Outside a hub: inject
IHubContext<GameHub>and callcontext.Clients.All.StateChanged(state).Clientsis a generated C# 14 extension property. - Start/stop:
IBleHubHost.Start()/Stop()start or stop every hub.IHubContext<THub>.Start()/Stop(reason)control one hub. A stopped hub disconnects its clients and refuses new ones.- A GATT service shared by several hubs stays up while any of them is running.
- The advertisement follows the running hubs.
- Disconnect is cooperative, because iOS peripherals can't drop a central.
Context.Abort()andIHubContext.Disconnect()ask the client to leave. The client library does so and raisesDisconnectedwith the reason. - Multiple hubs: each hub needs its own characteristic. Sharing one service UUID is recommended so the advertisement holds only one 128-bit UUID. Hub clients on the same device share one BLE connection.
See PLAN.md for the wire protocol, the design decisions and the roadmap.
Sample: Tic Tac Toe
samples/TicTacToe is a .NET MAUI app for iOS and Android that uses Shiny.Maui.Shell.
- One phone taps Host a game and plays X.
- The next phone to tap Join a game plays O. Later phones join the
spectatorsgroup. - Moves are hub calls, and board updates and emotes are hub pushes.
- Avatars are uploaded and downloaded over L2CAP.
BLE needs two physical devices: simulators and emulators have no usable Bluetooth.
dotnet build samples/TicTacToe -f net10.0-android -t:Run
dotnet build samples/TicTacToe -f net10.0-ios -t:Run
Tests
dotnet test tests/Shiny.BluetoothLE.Hubs.Tests
The tests run the generated hub dispatcher and the generated client proxy against each other through an in-memory "radio", plus diagnostic tests for the generator. No hardware is needed.
Documentation and release notes: https://shinylib.net/blehubs/
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Shiny.Extensions.Serialization (>= 5.3.1)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Shiny.BluetoothLE.Hubs:
| Package | Downloads |
|---|---|
|
Shiny.BluetoothLE.Hubs.Client
Shiny.BluetoothLE.Hubs client - discovers BLE hub hosts and calls them through generated, strongly typed proxies |
|
|
Shiny.BluetoothLE.Hubs.Host
Shiny.BluetoothLE.Hubs host - SignalR style hubs served over a BLE GATT server, plus L2CAP file transfers |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-alpha-0004-gbc068ff52e | 28 | 10/3/2026 |
| 1.0.0-alpha-0004 | 27 | 10/3/2026 |