Skip to content

Commit

Permalink
Merge branch 'feature/bluez-dbus' of git://github.com/qwandor/btleplu…
Browse files Browse the repository at this point in the history
…g into feature/bluez-dbus-qwandor
  • Loading branch information
NZSmartie committed Feb 2, 2021
2 parents 167238a + 1048bf8 commit 838d220
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 121 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Expand Up @@ -45,7 +45,7 @@ version = "^0.6"
features = ["windows-devices", "windows-storage"]

[target.'cfg(target_os = "linux")'.dependencies]
dbus = "0.9"
dbus = "0.9.1"

[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7"
Expand All @@ -62,6 +62,6 @@ rand = "0.8.2"
# The build script uses `dbus-codegen` to generate BlueZ's DBus traits for use in the linux library.
# The `build.rs` file does nothing on other platforms.
[build-dependencies.dbus-codegen]
version = "0.9"
version = "0.9.1"
# Disable the default feature which requires the DBus C API to be installed.
default-features = false
1 change: 1 addition & 0 deletions build.rs
Expand Up @@ -13,6 +13,7 @@ mod build {
let options = dbus_codegen::GenOpts {
methodtype: None,
genericvariant: true,
propnewtype: true,
..dbus_codegen::GenOpts::default()
};

Expand Down
150 changes: 80 additions & 70 deletions src/bluez/adapter/mod.rs
Expand Up @@ -16,12 +16,15 @@
mod peripheral;

use super::{
bluez_dbus::adapter::OrgBluezAdapter1, BLUEZ_DEST, BLUEZ_INTERFACE_CHARACTERISTIC,
BLUEZ_INTERFACE_DEVICE, BLUEZ_INTERFACE_SERVICE, DEFAULT_TIMEOUT,
bluez_dbus::adapter::OrgBluezAdapter1, bluez_dbus::device::OrgBluezDevice1Properties,
bluez_dbus::device::ORG_BLUEZ_DEVICE1_NAME,
bluez_dbus::gatt_characteristic::OrgBluezGattCharacteristic1Properties,
bluez_dbus::gatt_characteristic::ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME,
bluez_dbus::gatt_service::ORG_BLUEZ_GATT_SERVICE1_NAME, BLUEZ_DEST, DEFAULT_TIMEOUT,
};
use dashmap::DashMap;
use dbus::{
arg::{RefArg, Variant},
arg::RefArg,
blocking::{Proxy, SyncConnection},
channel::Token,
message::SignalArgs,
Expand Down Expand Up @@ -175,14 +178,18 @@ impl Adapter {
trace!("Received 'InterfacesRemoved' signal");
let path = args.object;

if args.interfaces.iter().any(|s| s == BLUEZ_INTERFACE_DEVICE) {
if args.interfaces.iter().any(|s| s == ORG_BLUEZ_DEVICE1_NAME) {
adapter.remove_device(&path).unwrap();
} else if args.interfaces.iter().any(|s| s == BLUEZ_INTERFACE_SERVICE) {
} else if args
.interfaces
.iter()
.any(|s| s == ORG_BLUEZ_GATT_SERVICE1_NAME)
{
// Ignore Services that get removed, the BTLEPlug API doesn't support that
} else if args
.interfaces
.iter()
.any(|s| s == BLUEZ_INTERFACE_CHARACTERISTIC)
.any(|s| s == ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME)
{
// Ignore Characteristics that get removed, the BTLEPlug API doesn't support that
}
Expand Down Expand Up @@ -240,24 +247,36 @@ impl Adapter {
// first, objects that implement org.bluez.Device1,
adapter_objects
.clone()
.filter_map(|(p, i)| i.get(BLUEZ_INTERFACE_DEVICE).map(|d| (p, d)))
.map(|(path, device)| Ok(self.add_device(path.as_str().unwrap(), device)?))
.filter_map(|(p, i)| i.get(ORG_BLUEZ_DEVICE1_NAME).map(|d| (p, d)))
.map(|(path, device)| {
Ok(self.add_device(path.as_str().unwrap(), OrgBluezDevice1Properties(device))?)
})
.collect::<Result<()>>()?;

trace!("Fetching known peripheral services");
// then, objects that implement org.bluez.GattService1 as they depend on devices being known first
adapter_objects
.clone()
.filter_map(|(p, i)| i.get(BLUEZ_INTERFACE_SERVICE).map(|a| (p, a)))
.map(|(path, attribute)| Ok(self.add_attribute(path.as_str().unwrap(), attribute)?))
.filter_map(|(p, i)| i.get(ORG_BLUEZ_GATT_SERVICE1_NAME).map(|a| (p, a)))
.map(|(path, attribute)| {
Ok(self.add_attribute(
path.as_str().unwrap(),
OrgBluezGattCharacteristic1Properties(attribute),
)?)
})
.collect::<Result<()>>()?;

trace!("Fetching known peripheral characteristics");
// then, objects that implement org.bluez.GattService1 as they depend on devices being known first
// then, objects that implement org.bluez.GattCharacteristic1 as they depend on devices being known first
adapter_objects
.clone()
.filter_map(|(p, i)| i.get(BLUEZ_INTERFACE_CHARACTERISTIC).map(|a| (p, a)))
.map(|(path, attribute)| Ok(self.add_attribute(path.as_str().unwrap(), attribute)?))
.filter_map(|(p, i)| i.get(ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME).map(|a| (p, a)))
.map(|(path, attribute)| {
Ok(self.add_attribute(
path.as_str().unwrap(),
OrgBluezGattCharacteristic1Properties(attribute),
)?)
})
.collect::<Result<()>>()?;

// TODO: Descriptors are nested behind characteristics, and their UUID may be used more than once.
Expand Down Expand Up @@ -297,49 +316,38 @@ impl Adapter {
}

/// Helper function to add a org.bluez.Device1 object to the adapter manager
fn add_device(
&self,
path: &str,
device: &::std::collections::HashMap<String, Variant<Box<dyn RefArg + 'static>>>,
) -> Result<()> {
if let Some(address) = device.get("Address") {
if let Some(address) = address.as_str() {
let address: BDAddr = address.parse()?;
// Ignore devices that are blocked, else they'll make this lirbary a bit harder to manage
// TODO: Should we allow blocked devices to be "discovered"?
if device
.get("Blocked")
.map_or(false, |b| b.0.as_u64().map_or(false, |b| b > 0))
fn add_device(&self, path: &str, device: OrgBluezDevice1Properties) -> Result<()> {
if let Some(address) = device.address() {
let address: BDAddr = address.parse()?;
// Ignore devices that are blocked, else they'll make this library a bit harder to manage
// TODO: Should we allow blocked devices to be "discovered"?
if device.blocked().unwrap_or(false) {
info!("Skipping blocked device \"{:?}\"", address);
return Ok(());
}
let peripheral = self.manager.peripheral(address).unwrap_or_else(|| {
Peripheral::new(self.manager.clone(), self.connection.clone(), path, address)
});
peripheral.update_properties(device);
if !self.manager.has_peripheral(&address) {
info!(
"Adding discovered peripheral \"{}\" on \"{}\"",
address, self.path
);
{
info!("Skipping blocked device \"{:?}\"", address);
return Ok(());
}
let peripheral = self.manager.peripheral(address).unwrap_or_else(|| {
Peripheral::new(self.manager.clone(), self.connection.clone(), path, address)
});
peripheral.update_properties(&device);
if !self.manager.has_peripheral(&address) {
info!(
"Adding discovered peripheral \"{}\" on \"{}\"",
address, self.path
);
{
let listener = self.listener.lock();
peripheral.listen(&listener)?;
// TODO: cal peripheral.stop_listening(...) when the peripheral is removed.
}
self.manager.add_peripheral(address, peripheral);
self.manager.emit(CentralEvent::DeviceDiscovered(address));
} else {
info!("Updating peripheral \"{}\"", address);
self.manager.update_peripheral(address, peripheral);
self.manager.emit(CentralEvent::DeviceUpdated(address));
let listener = self.listener.lock();
peripheral.listen(&listener)?;
// TODO: cal peripheral.stop_listening(...) when the peripheral is removed.
}
self.manager.add_peripheral(address, peripheral);
self.manager.emit(CentralEvent::DeviceDiscovered(address));
} else {
error!("Could not parse Bluetooth address");
info!("Updating peripheral \"{}\"", address);
self.manager.update_peripheral(address, peripheral);
self.manager.emit(CentralEvent::DeviceUpdated(address));
}
} else {
error!("Could not retrieve 'Address' from DBus 'InterfaceAdded' message with interface '{}'", BLUEZ_INTERFACE_DEVICE);
error!("Could not retrieve 'Address' from DBus 'InterfaceAdded' message with interface '{}'", ORG_BLUEZ_DEVICE1_NAME);
}

Ok(())
Expand All @@ -348,33 +356,26 @@ impl Adapter {
fn add_attribute(
&self,
path: &str,
characteristic: &::std::collections::HashMap<String, Variant<Box<dyn RefArg + 'static>>>,
characteristic: OrgBluezGattCharacteristic1Properties,
) -> Result<()> {
// Convert "/org/bluez/hciXX/dev_XX_XX_XX_XX_XX_XX/serviceXX" into "XX:XX:XX:XX:XX:XX"
if let Some(device_id) = path.strip_prefix(format!("{}/dev_", self.path).as_str()) {
let device_id: BDAddr = device_id[..17].replace("_", ":").parse()?;

if let Some(device) = self.manager.peripheral(device_id) {
trace!("Adding characteristic \"{}\" on \"{:?}\"", path, device_id);
let uuid: UUID = characteristic
.get("UUID")
.unwrap()
.as_str()
.unwrap()
.parse()?;
let flags = if let Some(flags) = characteristic.get("Flags") {
flags
.0
.as_iter()
.unwrap()
.map(|s| s.as_str().unwrap().parse::<CharPropFlags>())
.fold(Ok(CharPropFlags::new()), |a, f| {
let uuid: UUID = characteristic.uuid().unwrap().parse()?;
let flags = if let Some(flags) = characteristic.flags() {
flags.iter().map(|s| s.parse::<CharPropFlags>()).fold(
Ok(CharPropFlags::new()),
|a, f| {
if f.is_ok() {
Ok(f.unwrap() | a.unwrap())
} else {
f
}
})?
},
)?
} else {
CharPropFlags::new()
};
Expand Down Expand Up @@ -445,12 +446,21 @@ impl Central<Peripheral> for Adapter {
trace!("Received 'InterfacesAdded' signal");
let path = args.object;

if let Some(device) = args.interfaces.get(BLUEZ_INTERFACE_DEVICE) {
if let Some(device) =
OrgBluezDevice1Properties::from_interfaces(&args.interfaces)
{
adapter.add_device(&path, device).unwrap();
} else if let Some(service) = args.interfaces.get(BLUEZ_INTERFACE_SERVICE) {
adapter.add_attribute(&path, service).unwrap();
} else if let Some(service) =
args.interfaces.get(ORG_BLUEZ_GATT_SERVICE1_NAME)
{
adapter
.add_attribute(
&path,
OrgBluezGattCharacteristic1Properties(service),
)
.unwrap();
} else if let Some(characteristic) =
args.interfaces.get(BLUEZ_INTERFACE_CHARACTERISTIC)
OrgBluezGattCharacteristic1Properties::from_interfaces(&args.interfaces)
{
adapter.add_attribute(&path, characteristic).unwrap();
}
Expand Down
62 changes: 19 additions & 43 deletions src/bluez/adapter/peripheral.rs
Expand Up @@ -12,7 +12,7 @@
// Copyright (c) 2014 The Rust Project Developers

use dbus::{
arg::{RefArg, Variant},
arg::{cast, RefArg},
blocking::{stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged, Proxy, SyncConnection},
channel::Token,
message::{Message, SignalArgs},
Expand All @@ -28,7 +28,8 @@ use crate::{
RequestCallback, ValueNotification, UUID,
},
bluez::{
bluez_dbus::device::OrgBluezDevice1, AttributeType, Handle, BLUEZ_DEST, DEFAULT_TIMEOUT,
bluez_dbus::device::OrgBluezDevice1, bluez_dbus::device::OrgBluezDevice1Properties,
AttributeType, Handle, BLUEZ_DEST, DEFAULT_TIMEOUT,
},
common::util::invoke_handlers,
Error, Result,
Expand Down Expand Up @@ -124,7 +125,7 @@ impl Peripheral {
);
}
} else {
self.update_properties(&args.changed_properties);
self.update_properties(OrgBluezDevice1Properties(&args.changed_properties));
if !args.invalidated_properties.is_empty() {
warn!(
"TODO: Got some properties to invalidate\n\t{:?}",
Expand Down Expand Up @@ -233,17 +234,13 @@ impl Peripheral {
Ok(())
}

pub fn update_properties(
&self,
args: &::std::collections::HashMap<String, Variant<Box<dyn RefArg + 'static>>>,
) {
pub fn update_properties(&self, args: OrgBluezDevice1Properties) {
trace!("Updating peripheral properties");
let mut properties = self.properties.lock().unwrap();

properties.discovery_count += 1;

if let Some(connected) = args.get("Connected") {
let connected = connected.0.as_u64().unwrap() > 0;
if let Some(connected) = args.connected() {
debug!(
"Updating \"{}\" connected to \"{:?}\"",
self.address, connected
Expand All @@ -266,14 +263,12 @@ impl Peripheral {
cvar.notify_all();
}

if let Some(name) = args.get("Name") {
let name = name.as_str().map(|s| s.to_string());
if let Some(name) = args.name() {
debug!("Updating \"{}\" local name to \"{:?}\"", self.address, name);
properties.local_name = name;
properties.local_name = Some(name.to_owned());
}

if let Some(services_resolved) = args.get("ServicesResolved") {
let services_resolved = services_resolved.0.as_u64().unwrap() > 0;
if let Some(services_resolved) = args.services_resolved() {
if services_resolved {
// Need to prase and figure out handle ranges for all discovered characteristics.
self.build_characteristic_ranges().unwrap();
Expand All @@ -298,43 +293,24 @@ impl Peripheral {

// As of writing this: ManufacturerData returns a 'Variant({<manufacturer_id>: Variant([<manufacturer_data>])})'.
// This Variant wrapped dictionary and array is difficult to navigate. So uh.. trust me, this works on my machine™.
if let Some(manufacturer_data) = args.get("ManufacturerData") {
if let Some(manufacturer_data) = args.manufacturer_data() {
debug!(
"Updating \"{}\" manufacturer data \"{:?}\"",
self.address, manufacturer_data
);
let mut result = Vec::<u8>::new();
// dbus-rs doesn't really have a dictionary API... so need to iterate two at a time and make a key-value pair.
if let Some(mut iter) = manufacturer_data.0.as_iter() {
loop {
if let (Some(id), Some(data)) = (iter.next(), iter.next()) {
// This API is terrible.. why can't I just get an array out, why is it all wrapped in a Variant?
let data: Vec<u8> = data
.as_iter() // 🎶 The Variant is connected to the
.unwrap() // Array type!
.next() // The Array type is connected to the
.unwrap() // Array of integers!
.as_iter() // Lets convert the
.unwrap() // integers to a
.map(|b| b.as_u64().unwrap() as u8) // array of bytes...
.collect(); // I got too lazy to make it rhyme... 🎶

result.put_u16_le(id.as_u64().map(|v| v as u16).unwrap());
result.extend(data);
} else {
break;
}
for (manufacturer_id, data) in manufacturer_data {
if let Some(data) = cast::<Vec<u8>>(&data.0) {
result.put_u16_le(*manufacturer_id);
result.extend(data);
}
}
// 🎉
properties.manufacturer_data = Some(result);
}

if let Some(address_type) = args.get("AddressType") {
let address_type = address_type
.as_str()
.map(|address_type| AddressType::from_str(address_type).unwrap_or_default())
.unwrap_or_default();
if let Some(address_type) = args.address_type() {
let address_type = AddressType::from_str(address_type).unwrap_or_default();

debug!(
"Updating \"{}\" address type \"{:?}\"",
Expand All @@ -344,10 +320,10 @@ impl Peripheral {
properties.address_type = address_type;
}

if let Some(rssi) = args.get("RSSI") {
let rssi = rssi.as_i64().map(|rssi| rssi as i8);
if let Some(rssi) = args.rssi() {
let rssi = rssi as i8;
debug!("Updating \"{}\" RSSI \"{:?}\"", self.address, rssi);
properties.tx_power_level = rssi;
properties.tx_power_level = Some(rssi);
}

self.adapter.emit(CentralEvent::DeviceUpdated(self.address));
Expand Down
1 change: 1 addition & 0 deletions src/bluez/bluez_dbus/adapter.rs
@@ -1,4 +1,5 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bluez_dbus/adapter.rs"));
1 change: 1 addition & 0 deletions src/bluez/bluez_dbus/device.rs
@@ -1,4 +1,5 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bluez_dbus/device.rs"));

0 comments on commit 838d220

Please sign in to comment.