Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use typed interfaces for getting D-Bus properties #1

Merged
merged 5 commits into from Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Expand Up @@ -50,7 +50,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 @@ -67,6 +67,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 @@ -170,13 +173,17 @@ 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 == BLUEZ_INTERFACE_CHARACTERISTIC)
.any(|s| s == ORG_BLUEZ_GATT_SERVICE1_NAME)
{
} else if args
.interfaces
.iter()
.any(|s| s == ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME)
{
}

Expand Down Expand Up @@ -227,24 +234,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 @@ -284,49 +303,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 @@ -335,33 +343,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 @@ -428,12 +429,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,
},
Error, Result,
};
Expand Down Expand Up @@ -129,7 +130,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 @@ -238,17 +239,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 @@ -271,14 +268,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 @@ -303,43 +298,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 @@ -349,10 +325,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"));