feat(windows): add native GATT support - #20
Conversation
02fb37c to
9901010
Compare
9901010 to
c67338f
Compare
Three defects in the native Windows GATT binding, found while testing a peripheral on Windows 11: * Advertising reported a spurious failure on every start. The AdvertisementStatusChanged handler treated any Aborted status as fatal, but Windows transiently reports Aborted with BluetoothError::Success while the provider re-tunes its advertisement during startup (the observed transition is Created -> Aborted -> Started). The success code 0 was then stringified into "Advertising GATT service failed with Bluetooth error 0", so consumers saw advertisingStart fire twice, the second time with a phantom error. Only surface a genuine failure. * Reserved descriptors broke setServices. Only 2901/2902 were recognised, and only by raw string compare, so the long-form UUID was missed entirely and 2904 fell through to CreateDescriptorAsync - which Windows rejects with "The provided descriptor uuid is reserved and will be automatically published by the system", failing the whole call. This broke the battery-service example. Resolve the 16-bit SIG alias from the GUID so short and long forms both match, skip the system-managed descriptors (2900/2902/2903), and forward 2904 through GattLocalCharacteristicParameters.PresentationFormats so its declared value is preserved rather than dropped. * The advertised local name was silently discarded. mName was assigned and never read: GattServiceProviderAdvertisingParameters exposes no local name, and Windows always advertises the system Bluetooth name. Emit a warning once per session so callers know to discover the peripheral by service UUID instead of by the name they passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
setAddressAsync resolved unconditionally: the addressChange callback parameter shadowed the requested address, so `address === address` was always true and a mismatch could never be detected. The else branch also left the rejection timer running and could reject after an earlier settle. Name the callback parameter, clear the timer before branching, and compare case-insensitively since address formatting varies between bindings. Also relay the binding's new warning event, so notices such as the Windows local-name limitation reach consumers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GattServiceProviderAdvertisingParameters selects between legacy and
extended advertising by whether a secondary PHY is requested. The binding
never set one, so Windows was always asked for legacy advertising with no
way to change that.
Expose it as an opt-in: withBindings('win', { extendedAdvertising: true }),
or BLENO_WIN_EXTENDED_ADV=1 for use with the stock examples. Setting it
requests the uncoded 1M PHY as secondary PHY, which switches Windows to
extended advertising.
The default stays legacy, matching current behaviour: extended
advertisements are invisible to Bluetooth 4.x-only scanners, so this is
not safe to enable for everyone. The call is guarded with try_as on
IGattServiceProviderAdvertisingParameters3 and degrades to legacy with a
warning on Windows releases that predate the secondary PHY parameters.
Note this was written to test whether a Bluetooth 5 controller that never
put a legacy advertisement on air would advertise in extended mode. It did
not: on a Realtek dongle (USB\VID_0BDA&PID_A729) running the Microsoft
inbox driver, advertising is accepted and AdvertisementStatus reports
Started in both modes while nothing is transmitted. A CSR8510 (BT 4.0) on
the same machine works in legacy mode. The option is kept because the
capability is legitimate, not because it fixes that controller.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Tested this branch on Windows 11 (build 26200, Node 22.22) against two USB Bluetooth adapters. The native binding builds cleanly and works: a peripheral powers on, advertises, publishes services and serves GATT. Pushed four fixes found along the way, and one hardware finding that is worth documenting but is not a bleno bug. Fixes1. Every advertising start reported a phantom failure (
|
| Check | Result |
|---|---|
BluetoothAdapter.IsPeripheralRoleSupported |
True |
Raw WinRT GattServiceProvider (no bleno) |
AdvertisementStatus: Started, sustained |
| bleno | advertisingStart: success, setServices: success |
| Legacy advertising | not visible |
| Extended advertising (1M secondary PHY) | not visible |
Adapters involved:
USB\VID_0BDA&PID_A729— Realtek, Microsoft generic inbox driver, sold as "Bluetooth 5.3". Never transmits, in either advertising mode.USB\VID_0A12&PID_0001— CSR8510 (Bluetooth 4.0). Works.
Reproduced with raw WinRT and no bleno in the process, so this is below the binding: Windows accepts the peripheral role, reports Started, and nothing reaches the air. Nothing bleno can do about it, but it is a nasty failure mode because every available API says success. If anyone has this working on a Realtek BT5 dongle under Windows, I would be glad to hear which driver.
Practical note for anyone testing on Windows: scan by service UUID, not by name — the local name is never advertised (see fix 3).
What changed
win32; the raw HCI backend remains available withwithBindings('hci').withBindings('win')selection, handle-specific disconnects, platform requirements, supported features, and native API limitations.Why
Windows previously defaulted to raw HCI access, which requires a dedicated adapter and WinUSB driver. The native binding uses the normal Windows Bluetooth stack and exposes bleno's peripheral APIs through WinRT.
Impact
Windows users with an adapter that supports the Bluetooth LE peripheral role can use the system Bluetooth driver. Raw EIR and iBeacon advertising, application-controlled local names, and peer RSSI reads are not available through this binding; these limitations are documented.
Validation
npm pack --dry-runincludes the Windows sources.