Skip to content

Repository files navigation

bluez_native

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.

Features

  • 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 / StopNotify via org.bluez.GattCharacteristic1
  • ReadValue / WriteValue for characteristics and descriptors

Platform Support

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

Getting Started

1. Install system dependencies

Ubuntu/Debian:

sudo apt-get install cmake ninja-build clang libsystemd-dev pkg-config

Fedora:

sudo dnf install cmake ninja-build clang systemd-devel pkgconf-pkg-config

2. Add the package

dependencies:
  bluez_native: ^0.1.0

3. Clone with submodules and build the native library

git 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 --parallel

4. Run a Dart example

Set BLUEZ_NC_LIB to the built shared library, then run:

export BLUEZ_NC_LIB=$PWD/build/libbluez_nc.so
dart run example/scan_devices.dart

5. Run the Flutter example

cd example/flutter_ble_scanner
flutter pub get
BLUEZ_NC_LIB=$PWD/../../build/libbluez_nc.so flutter run

Quick Start

import '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();
}

API Surface

BlueZClient

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

BlueZAdapter

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

BlueZDevice

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

BlueZGattCharacteristic

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

BlueZGattDescriptor

Method Description
readValue() Read descriptor value
writeValue(data) Write descriptor value

Characteristic Notifications

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();

Migration from canonical/bluez.dart

// 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.value emits List<int> directly (not DBusValue variants)
  • device.propertiesChanged emits List<String> (changed property names)
  • Errors are BlueZOperationException (not DBusMethodResponseException)
  • No dbus package dependency required

Examples

Troubleshooting

org.bluez.Error.NotReady — Resource Not Ready

The Bluetooth adapter is not powered on. Check its status and power it on:

bluetoothctl show          # look for "Powered: yes/no"
bluetoothctl power on

Or programmatically:

if (!adapter.powered) {
  await adapter.setPowered(true);
}

org.bluez.Error.Failed when setting adapter properties

This is usually caused by rfkill blocking the adapter or insufficient permissions.

Check rfkill status:

rfkill list bluetooth

If it shows Soft blocked: yes, unblock it:

sudo rfkill unblock bluetooth

If 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.dart

BlueZServiceUnavailableException — BlueZ service is not available

The BlueZ daemon is not running:

sudo systemctl start bluetooth
sudo systemctl enable bluetooth   # start on boot

Verify the Bluetooth stack is working

systemctl status bluetooth        # daemon running?
rfkill list bluetooth             # not blocked?
bluetoothctl show                 # adapter visible and powered?

License

Apache 2.0 — see LICENSE.

About

High-performance BlueZ Bluetooth client for Linux in Dart/Flutter — zero-copy GATT notifications via native_comms + sdbus-cpp. Drop-in replacement for bluez.dart.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages