Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration!How!?

BluetoothLE Releases

FixiOS
A disconnect that races an in-flight GATT operation no longer permanently deadlocks the BLE operation queue. CoreBluetooth delegate callbacks are the only thing that completes a queued operation, and a dead peripheral never fires them again — so an operation parked on one (most visibly a WriteCharacteristic(withResponse: false) waiting on canSendWriteWithoutResponse flow control) held the operation lock forever, blocking every subsequent operation across all peripherals until the app was restarted. Every queued Apple operation — service/characteristic/descriptor discovery, reads, both write modes, and ReadRssi — now aborts with a BleException when the peripheral disconnects mid-operation, releasing the lock so reconnect and retry work normally. Applies to iOS, Mac Catalyst, and macOS.
FixAndroid
The same disconnect-mid-operation deadlock is fixed on Android. Once the link drops, the BluetoothGatt client is closed and its callbacks never fire again, so a parked read, write-with-response, descriptor read/write (including the CCCD write that re-arms notifications on reconnect), service discovery, or ReadRssi held the operation lock indefinitely. These now abort with a BleException on disconnect — including the explicit disconnect path, where Gatt.Close() suppresses the framework’s own state-change callback. Write-without-response was never affected on Android; it does not wait on flow control.
FixAndroid
NotifyCharacteristic no longer drops the first notification(s) for peripherals that stream data the instant the subscription is enabled. The notification listener is now wired up before the CCCD descriptor write turns the peripheral on (it was previously hooked only after the write was acknowledged, leaving a window where the earliest OnCharacteristicChanged callbacks were lost).
FixiOS
NotifyCharacteristic now subscribes to characteristic updates before calling SetNotifyValue(true), closing the same notify-before-listen race on Apple platforms (iOS, Mac Catalyst, macOS).
FixAndroid
Scanning now discovers devices again. A previous change forced setLegacy(false) alone on Android 8+, which on most chipsets suppresses the legacy advertisements that virtually all BLE peripherals send. Scans now report both legacy AND Bluetooth 5 extended advertisements automatically, by pairing setLegacy(false) with all-PHY scanning and only enabling it when the chipset reports IsLeExtendedAdvertisingSupported (otherwise falling back to a legacy scan). Force a legacy-only scan with new AndroidScanConfig(IncludeExtendedAdvertisements: false).
FixAndroid
RequestAccess() no longer crash the app ~5 seconds after the OS permission dialog appears. The internal 5-second guard was incorrectly timing the user’s interaction with the dialog; it now only bounds the wait for an Activity to become available, so the user may take as long as they like to respond.
Feature
L2CapChannelExtensions.SendFile(...) — new file-transfer helper on top of an open L2CapChannel with HTTP-transfer-style progress metrics (bytes-per-second, percent-complete, estimated time remaining). Overloads accept either a file path (length auto-detected) or an arbitrary Stream with an optional totalBytes. Progress callbacks fire ~every 2s plus a final 100% emission on completion. The new Shiny.BluetoothLE.TransferProgress record mirrors Shiny.Net.Http.TransferProgress so consumers have an identical mental model across HTTP and L2CAP transfers. Lives in Shiny.BluetoothLE.Common, shared with the hosting library.
Feature
L2CAP CoC central-role support shipped via the optional ICanL2Cap capability on IPeripheral. Call peripheral.OpenL2CapChannel(psm, secure) (or the safe TryOpenL2CapChannel(...) extension on the base IPeripheral) to open a streaming channel to a peripheral that has published a PSM. Supported on iOS, Mac Catalyst, macOS (CoreBluetooth CBPeripheral.OpenL2CapChannel), Android API 29+ (BluetoothDevice.CreateL2capChannel / CreateInsecureL2capChannel), and Linux (BlueZ — raw AF_BLUETOOTH / BTPROTO_L2CAP / SOCK_SEQPACKET socket; LE dynamic PSMs ≥ 0x80 do not need CAP_NET_RAW). On Apple platforms the secure flag is ignored — security is determined by how the peripheral published the channel. On Linux the flag toggles BT_SECURITY_LOW/MEDIUM via setsockopt(SOL_BLUETOOTH, BT_SECURITY).
Enhancement
The public L2CapChannel record moved into Shiny.BluetoothLE.Common (namespace Shiny.BluetoothLE) so both central and hosting libraries share one type. The record now implements IDisposable with an optional OnDispose hook for closing streams and disposing sockets.
FixAndroid
Shiny.BluetoothLE.Extensions.ListenForData(BluetoothSocket) now reads from socket.InputStream (was incorrectly reading OutputStream) and emits a right-sized copy of each chunk instead of the full 8 KB buffer. The observable now completes on EOF and surfaces read errors via OnError.
FixiOS
Shiny.BluetoothLE.Extensions.ListenForData(NSInputStream) now drains all bytes available per HasBytesAvailable event, emits a right-sized copy per read (was leaking the full 8 KB shared buffer to every subscriber), and completes on NSStreamEvent.EndEncountered.
Feature
macOS support added via CoreBluetooth (central role) - Shiny.BluetoothLE
Feature
Linux support added via BlueZ / D-Bus (central role) - new Shiny.BluetoothLE.Linux package
Feature
Blazor WebAssembly (Web) support added via the browser Web Bluetooth API - new Shiny.BluetoothLE.Blazor package. Central role only; scans require a user gesture, HTTPS, and a Chromium-based browser
FixWindows
Fix BLE state cleanup after disconnect and reconnect - properly releases GATT resources and disposes stale peripherals
FixAndroid
Fix the classic “status 133 after a few reconnects” trap — Connect() now closes any prior BluetoothGatt before opening a new client, so reconnect loops no longer leak GATT clients into Android’s per-app limit.
FixAndroid
Connection state and connection-failure observables are now replay-safe (BehaviorSubject / time-windowed ReplaySubject), so subscribers that hook up immediately after calling Connect() no longer miss the resulting state change or failure.
FixAndroid
OnConnectionStateChange now dispatches subscriber notifications off the single-threaded GATT binder callback thread, removing a class of deadlocks where awaiting subscribers blocked further BLE callbacks.
FixAndroid
Notifier teardown re-resolves the characteristic against the current BluetoothGatt instead of a captured (possibly closed) reference, preventing spurious status 133 errors on the next operation after a disconnect/reconnect cycle.
FixAndroid
Starting a scan no longer evicts peripherals that are currently in the Connecting state.
FixiOS
IBleManager.GetKnownPeripheral(uuid) now calls CoreBluetooth’s RetrievePeripheralsWithIdentifiers, so callers can reconnect to a previously-paired device by UUID after a process restart without first running a scan.
FixiOS
IBleManager.GetConnectedPeripherals() now seeds from RetrieveConnectedPeripherals so devices connected by other apps or restored sessions are visible.
FixiOS
Auto-reconnect now issues CancelPeripheralConnection before each retry (iOS otherwise holds the previous pending connection slot) and additionally retries on FailedToConnectPeripheral, fixing cold-start failures that previously never retried because no Disconnected event was emitted.
FixWindows
Service and characteristic lookups on the hot path now use BluetoothCacheMode.Uncached, so a reconnect always re-discovers fresh GattDeviceService / GattCharacteristic handles instead of silently operating on dead, OS-cached ones.
FixWindows
Peripheral connections now acquire a GattSession with MaintainConnection = true, so the OS keeps the LE link up across idle periods and automatically re-establishes it when a device returns in range — eliminating “the connection dropped while idle” symptoms.
FixWindows
IPeripheral references now survive a disconnect/reconnect cycle. Previously the manager replaced the wrapper instance on every reconnect, leaving any caller-held reference dead with “Device is disposed” errors. The wrapper now refreshes its underlying BluetoothLEDevice in place.
FixWindows
A transient ConnectionStatus = Disconnected dip immediately after a successful service discovery no longer cancels the connect attempt — the connect now succeeds, and a real subsequent disconnect is handled by the normal ConnectionStatusChanged path.
Feature
Windows support added (No Background Support at this time)
Enhancement
ManagedScanResult is now passed with the full advertisement data in case user needs access to native internals
EnhancementAndroid
Improved manufacturer data parsing in ad data
FixAndroid
BLE Delegate now reports proper status changes for enabled
Fix
ManagedScan now uses thread safe BindingList
FixAndroid
BLE scan now disables legacy scanning for new android versions
Fix
More thread safetying for ManagedScan
FixAndroid
Ensure peripheral cleanup matches iOS
FixAndroid
BLE Delegate was not responding with Available when adapter was reenabled
FixAndroid
Disable legacy scanner on newer Android versions
FixAndroid
ManagedScanResult now has a property for the raw advertisement data
FixAndroid
Additional thread safety on managed scan events
FixAndroid
IBleDelegate now reports adapter state properly
FixAndroid
BleDelegate does not fire for disconnected event on Android
FixAndroid
Unsubscribing from a connection may be temporarily unstable if sub/unsub is performed rapidly
FixAndroid
Reduce logging severity for characteristic events
FixiOS
IsScanning flag was not being set
Fix
Characteristic extension (GetAllCharacteristics) was only returning characteristics from last service
Fix
Characteristic async extension signature fixes
Enhancement
BLE manager now allows you to check current permissions without requesting
EnhancementAndroid
RequestAccess(bool connect) now allows you to additionally request access to GATT connections (defaults to true). This allows Shiny to use Android API 31 properly. It will always ask for scan permissions.
BREAKINGAndroid
Adapter control is no longer support through the Shiny API, but you do have raw access to the native adapter if needed
BREAKING
Managed scan now require you to set scan configuration values in Start instead of the constructor & property setters
BREAKING
The API has been simplified and no longer requires you to maintain (and refresh) instances of services/characteristics/descriptors
BREAKING
Managed peripheral is now gone. This functionality is now built into the main API.
BREAKINGEnhancementAndroid
Android MTU requests are moved to the IPeripheral.Connect(AndroidConnectionConfig)