High-performance BlueZ Bluetooth client for Linux, built on sdbus-cpp. Zero-copy characteristic notifications via Dart_PostCObject_DL.
Drop-in API replacement for canonical/bluez.dart
with lower latency.
- Full BlueZ D-Bus API surface: adapters, devices, GATT services, characteristics, and descriptors
- Zero-copy characteristic notifications — bytes arrive directly from the
sdbus-cpp event loop thread via
kExternalTypedData - API compatible with canonical/bluez.dart for straightforward migration
- Discovery filter support (transport, RSSI threshold, UUIDs)
StartNotify/StopNotifyviaorg.bluez.GattCharacteristic1ReadValue/WriteValuefor characteristics and descriptors
| Platform | Scan | Connect | GATT Read/Write | Notifications |
|---|---|---|---|---|
| Linux (BlueZ >= 5.50) | Yes | Yes | Yes | Yes (zero-copy) |
| macOS | No | No | No | No |
| Windows | No | No | No | No |
Ubuntu/Debian:
sudo apt-get install cmake ninja-build clang libsystemd-dev pkg-configFedora:
sudo dnf install cmake ninja-build clang systemd-devel pkgconf-pkg-configdependencies:
bluez_native: ^0.1.0git clone --recurse-submodules https://github.com/jwinarske/bluez_native.git
cd bluez_native
cmake -B build native/ -GNinja -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallelSet BLUEZ_NC_LIB to the built shared library, then run:
export BLUEZ_NC_LIB=$PWD/build/libbluez_nc.so
dart run example/scan_devices.dartcd example/flutter_ble_scanner
flutter pub get
BLUEZ_NC_LIB=$PWD/../../build/libbluez_nc.so flutter runimport 'package:bluez_native/bluez_native.dart';
Future<void> main() async {
final client = BlueZClient();
await client.connect();
// Enumerate adapters
for (final adapter in client.adapters) {
print('${adapter.address} (${adapter.name})');
await adapter.startDiscovery();
}
// Watch for new devices
client.deviceAdded.listen((device) {
print('Found: ${device.address} RSSI: ${device.rssi}');
});
// After 10 seconds...
await client.close();
}Top-level entry point. Call connect() to establish a D-Bus connection
and snapshot the BlueZ object tree.
| Property / Method | Description |
|---|---|
adapters |
All known HCI adapters |
devices |
All known devices |
deviceAdded |
Stream of newly discovered devices |
deviceRemoved |
Stream of removed devices |
adapterChanged |
Stream of adapter property changes |
connect() |
Connect to BlueZ system bus |
close() |
Release all resources |
| Method | Description |
|---|---|
setPowered() |
Power the adapter on or off |
startDiscovery() |
Begin scanning |
stopDiscovery() |
Stop scanning |
setDiscoveryFilter() |
Set transport, RSSI, UUIDs filter |
removeDevice() |
Remove a cached device |
| Property / Method | Description |
|---|---|
address, name, rssi, paired, connected |
Device state |
connect() / disconnect() |
Connection management |
pair() / cancelPairing() |
Pairing |
waitForServicesResolved() |
Wait for GATT discovery |
gattServices |
Discovered GATT services |
propertiesChanged |
Stream of changed property names |
manufacturerData |
BLE advertisement data |
| Property / Method | Description |
|---|---|
uuid, flags, mtu |
Characteristic metadata |
readValue() |
Read from the device |
writeValue(data) |
Write to the device |
startNotify() |
Subscribe to value notifications |
stopNotify() |
Unsubscribe |
value |
Stream<List<int>> of notification bytes |
| Method | Description |
|---|---|
readValue() |
Read descriptor value |
writeValue(data) |
Write descriptor value |
The notification path is zero-copy from the D-Bus signal to Dart:
BlueZ PropertiesChanged (sdbus event loop thread)
-> signal handler
-> Dart_PostCObject_DL (kExternalTypedData)
-> Dart ReceivePort
-> Stream<List<int>> listener
await char.startNotify();
await for (final bytes in char.value) {
print('Received: $bytes');
}
await char.stopNotify();// Before (canonical/bluez.dart)
import 'package:bluez/bluez.dart';
final client = BlueZClient();
await client.connect();
// After (bluez_native) — same API
import 'package:bluez_native/bluez_native.dart';
final client = BlueZClient();
await client.connect();Key differences:
characteristic.valueemitsList<int>directly (notDBusValuevariants)device.propertiesChangedemitsList<String>(changed property names)- Errors are
BlueZOperationException(notDBusMethodResponseException) - No
dbuspackage dependency required
scan_devices.dart— scan and print discovered devicesconnect_device.dart— connect and enumerate servicesread_characteristic.dart— read a GATT valuewrite_characteristic.dart— write hex bytes to a characteristicread_descriptor.dart— read all descriptors on a characteristicnotify_characteristic.dart— subscribe to notificationsnotify_regression.dart— assert subscription state (notifyingbefore/after), not just that bytes arrivepair_device.dart— pair with a devicedevice_properties.dart— monitor live property changes (RSSI, connection state)flutter_ble_scanner/— Flutter app with scan, connect, and GATT UI
The Bluetooth adapter is not powered on. Check its status and power it on:
bluetoothctl show # look for "Powered: yes/no"
bluetoothctl power onOr programmatically:
if (!adapter.powered) {
await adapter.setPowered(true);
}This is usually caused by rfkill blocking the adapter or insufficient permissions.
Check rfkill status:
rfkill list bluetoothIf it shows Soft blocked: yes, unblock it:
sudo rfkill unblock bluetoothIf rfkill is not the issue, the process may lack permission to change adapter properties. Run with elevated privileges:
sudo BLUEZ_NC_LIB=$PWD/build/libbluez_nc.so dart run example/scan_devices.dartThe BlueZ daemon is not running:
sudo systemctl start bluetooth
sudo systemctl enable bluetooth # start on bootsystemctl status bluetooth # daemon running?
rfkill list bluetooth # not blocked?
bluetoothctl show # adapter visible and powered?Apache 2.0 — see LICENSE.