Skip to content

Commit

Permalink
Adding BtLE Advertisements
Browse files Browse the repository at this point in the history
  • Loading branch information
RossComputerGuy committed May 26, 2024
1 parent 0cdadb0 commit 255ae99
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 7 deletions.
68 changes: 68 additions & 0 deletions example/advertisement.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:dbus/dbus.dart';
import 'package:bluez/bluez.dart';

class ExampleAdvert extends BlueZAdvertisement {
ExampleAdvert()
: super(DBusObjectPath('/org/bluez/example/advertisement0099'));

@override
Future<void> release() async {}

@override
String get type => 'broadcast';

@override
List<String> get serviceUuids => [];

@override
Map<BlueZUUID, DBusValue> get serviceData => {};

@override
bool get includeTxPower => false;

@override
Map<BlueZManufacturerId, DBusValue> get manufacturerData => {
BlueZManufacturerId(0x004c): DBusArray.int16([
0x02,
0x15,
0xE2,
0xC5,
0x6D,
0xB5,
0xDF,
0xFB,
0x48,
0xD2,
0xB0,
0x60,
0xD0,
0xF5,
0xA7,
0x10,
0x96,
0xE0,
0x00,
0x01,
0x00,
0x02,
0x0c
]),
};

@override
List<String> get solicitUuids => [];
}

void main() async {
var client = BlueZClient();
await client.connect();

var adapter = client.adapters.first;
var advertMngr = adapter.advertisingManager;

var advert = ExampleAdvert();

await advertMngr.registerAdvertisement(advert);

while (true) {}
}
186 changes: 179 additions & 7 deletions lib/src/bluez_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,13 @@ final _bluezAddressTypeMap = <String, BlueZAddressType>{
class BlueZAdapter {
final String _adapterInterfaceName = 'org.bluez.Adapter1';

final BlueZClient _client;
final _BlueZObject _object;

BlueZAdapter(this._object);
BlueZAdapter(this._client, this._object);

BlueZAdvertisingManager get advertisingManager =>
BlueZAdvertisingManager(_client, _object);

/// Stream of property names as their values change.
Stream<List<String>> get propertiesChanged {
Expand Down Expand Up @@ -340,6 +344,174 @@ class BlueZAdapter {
.toList();
}

/// BtLE Advertising Manager
class BlueZAdvertisingManager {
final String _advertInterfaceName = 'org.bluez.LEAdvertisingManager1';

final BlueZClient _client;
final _BlueZObject _object;

BlueZAdvertisingManager(this._client, this._object);

Future<void> registerAdvertisement(BlueZAdvertisement advert) async {
await _client._bus.registerObject(advert);

await _object.callMethod(_advertInterfaceName, 'RegisterAdvertisement',
[advert.path, DBusDict.stringVariant({})],
replySignature: DBusSignature(''));
}
}

/// BtLE Advertisement instance
abstract class BlueZAdvertisement extends DBusObject {
final String _advertInterfaceName = 'org.bluez.LEAdvertisement1';

BlueZAdvertisement(DBusObjectPath path) : super(path);

Future<void> release();

Map<BlueZManufacturerId, DBusValue> get manufacturerData;
String get type;
List<String> get serviceUuids;
Map<BlueZUUID, DBusValue> get serviceData;
bool get includeTxPower;
List<String> get solicitUuids;

@override
Future<DBusMethodResponse> handleMethodCall(DBusMethodCall methodCall) async {
if (methodCall.interface == _advertInterfaceName) {
if (methodCall.name == 'Release') {
if (methodCall.values.isNotEmpty) {
return DBusMethodErrorResponse.invalidArgs();
}
await release();
return DBusMethodSuccessResponse();
} else {
return DBusMethodErrorResponse.unknownMethod();
}
} else {
return DBusMethodErrorResponse.unknownInterface();
}
}

@override
Future<DBusMethodResponse> getProperty(String interface, String name) async {
if (interface == _advertInterfaceName) {
if (name == 'ManufacturerData') {
return DBusMethodSuccessResponse([
DBusDict(
DBusSignature('q'),
DBusSignature('v'),
manufacturerData.map((id, value) =>
MapEntry(DBusUint16(id.id), DBusVariant(value))))
]);
}

if (name == 'Type') {
return DBusMethodSuccessResponse([DBusString(type)]);
}

if (name == 'ServiceUUIDs') {
return DBusMethodSuccessResponse([DBusArray.string(serviceUuids)]);
}

if (name == 'ServiceData') {
return DBusMethodSuccessResponse([
DBusDict.stringVariant(serviceData
.map((uuid, value) => MapEntry(uuid.toString(), value)))
]);
}

if (name == 'IncludeTxPower') {
return DBusMethodSuccessResponse([DBusBoolean(includeTxPower)]);
}

if (name == 'SolicitUUIDs') {
return DBusMethodSuccessResponse([DBusArray.string(solicitUuids)]);
}

return DBusMethodErrorResponse.unknownProperty();
} else {
return DBusMethodErrorResponse.unknownInterface();
}
}

@override
Future<DBusMethodResponse> getAllProperties(String interface) async {
var properties = <String, DBusValue>{};
if (interface == _advertInterfaceName) {
properties['Type'] =
(await getProperty(_advertInterfaceName, 'Type')).returnValues[0];
properties['ServiceUUIDs'] =
(await getProperty(_advertInterfaceName, 'ServiceUUIDs'))
.returnValues[0];
properties['ServiceData'] =
(await getProperty(_advertInterfaceName, 'ServiceData'))
.returnValues[0];
properties['IncludeTxPower'] =
(await getProperty(_advertInterfaceName, 'IncludeTxPower'))
.returnValues[0];
properties['ManufacturerData'] =
(await getProperty(_advertInterfaceName, 'ManufacturerData'))
.returnValues[0];
properties['SolicitUUIDs'] =
(await getProperty(_advertInterfaceName, 'SolicitUUIDs'))
.returnValues[0];
}
return DBusMethodSuccessResponse([DBusDict.stringVariant(properties)]);
}

@override
Future<DBusMethodResponse> setProperty(
String interface, String name, DBusValue value) async {
if (interface == _advertInterfaceName) {
if (name == 'Type') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'ServiceUUIDs') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'ServiceData') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'IncludeTxPower') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'ManufacturerData') {
return DBusMethodErrorResponse.propertyReadOnly();
} else if (name == 'SolicitUUIDs') {
return DBusMethodErrorResponse.propertyReadOnly();
} else {
return DBusMethodErrorResponse.unknownProperty();
}
} else {
return DBusMethodErrorResponse.unknownProperty();
}
}

@override
List<DBusIntrospectInterface> introspect() {
return [
DBusIntrospectInterface(
_advertInterfaceName,
methods: [
DBusIntrospectMethod('Release'),
],
properties: [
DBusIntrospectProperty('Type', DBusSignature('s'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('ServiceUUIDs', DBusSignature('as'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('ServiceData', DBusSignature('a{sv}'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('IncludeTxPower', DBusSignature('b'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('ManufacturerData', DBusSignature('a{qv}'),
access: DBusPropertyAccess.read),
DBusIntrospectProperty('SolicitUUIDs', DBusSignature('as'),
access: DBusPropertyAccess.read),
],
),
];
}
}

/// A GATT service running on a BlueZ device.
class BlueZGattService {
final String _serviceInterfaceName = 'org.bluez.GattService1';
Expand Down Expand Up @@ -1292,7 +1464,7 @@ class BlueZClient {

/// Creates a new BlueZ client. If [bus] is provided connect to the given D-Bus server.
BlueZClient({DBusClient? bus})
: _bus = bus ?? DBusClient.system(),
: _bus = bus ?? DBusClient.system(introspectable: true),
_closeBus = bus == null {
_root = DBusRemoteObjectManager(_bus,
name: 'org.bluez', path: DBusObjectPath('/'));
Expand All @@ -1317,7 +1489,7 @@ class BlueZClient {
_bus, signal.changedPath, signal.interfacesAndProperties);
_objects[signal.changedPath] = object;
if (_isAdapter(object)) {
_adapterAddedStreamController.add(BlueZAdapter(object));
_adapterAddedStreamController.add(BlueZAdapter(this, object));
} else if (_isDevice(object)) {
_deviceAddedStreamController.add(BlueZDevice(this, object));
}
Expand All @@ -1334,7 +1506,7 @@ class BlueZClient {
}

if (signal.interfaces.contains('org.bluez.Adapter1')) {
_adapterRemovedStreamController.add(BlueZAdapter(object));
_adapterRemovedStreamController.add(BlueZAdapter(this, object));
} else if (signal.interfaces.contains('org.bluez.Device1')) {
_deviceRemovedStreamController.add(BlueZDevice(this, object));
}
Expand All @@ -1358,7 +1530,7 @@ class BlueZClient {
// Report initial adapters and devices.
for (var object in _objects.values) {
if (_isAdapter(object)) {
_adapterAddedStreamController.add(BlueZAdapter(object));
_adapterAddedStreamController.add(BlueZAdapter(this, object));
} else if (_isDevice(object)) {
_deviceAddedStreamController.add(BlueZDevice(this, object));
}
Expand All @@ -1371,7 +1543,7 @@ class BlueZClient {
var adapters = <BlueZAdapter>[];
for (var object in _objects.values) {
if (_isAdapter(object)) {
adapters.add(BlueZAdapter(object));
adapters.add(BlueZAdapter(this, object));
}
}
return adapters;
Expand Down Expand Up @@ -1466,7 +1638,7 @@ class BlueZClient {
if (object == null) {
return null;
}
return BlueZAdapter(object);
return BlueZAdapter(this, object);
}

bool _isAdapter(_BlueZObject object) {
Expand Down

0 comments on commit 255ae99

Please sign in to comment.