use bluez_generated::{
OrgBluezAdapter1Properties, OrgBluezDevice1Properties, OrgBluezGattCharacteristic1Properties,
ORG_BLUEZ_ADAPTER1_NAME, ORG_BLUEZ_DEVICE1_NAME, ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME,
};
use dbus::message::{MatchRule, SignalArgs};
use dbus::nonblock::stdintf::org_freedesktop_dbus::{
ObjectManagerInterfacesAdded, PropertiesPropertiesChanged,
};
use dbus::{Message, Path};
use std::collections::HashMap;
use super::device::convert_manufacturer_data;
use super::{AdapterId, CharacteristicId, DeviceId};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BluetoothEvent {
Adapter {
id: AdapterId,
event: AdapterEvent,
},
Device {
id: DeviceId,
event: DeviceEvent,
},
Characteristic {
id: CharacteristicId,
event: CharacteristicEvent,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AdapterEvent {
Powered { powered: bool },
Discovering { discovering: bool },
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DeviceEvent {
Discovered,
Connected { connected: bool },
RSSI { rssi: i16 },
ManufacturerData {
manufacturer_data: HashMap<u16, Vec<u8>>,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum CharacteristicEvent {
Value { value: Vec<u8> },
}
impl BluetoothEvent {
pub(crate) fn match_rules(object: Option<impl Into<Path<'static>>>) -> Vec<MatchRule<'static>> {
let bus_name = "org.bluez".into();
let mut match_rules = vec![];
if object.is_none() {
let match_rule =
ObjectManagerInterfacesAdded::match_rule(Some(&bus_name), None).static_clone();
match_rules.push(match_rule);
}
let object_path = object.map(|o| o.into());
let mut match_rule =
PropertiesPropertiesChanged::match_rule(Some(&bus_name), object_path.as_ref())
.static_clone();
match_rule.path_is_namespace = true;
match_rules.push(match_rule);
match_rules
}
pub(crate) fn message_to_events(message: Message) -> Vec<BluetoothEvent> {
if let Some(properties_changed) = PropertiesPropertiesChanged::from_message(&message) {
let object_path = message.path().unwrap().into_static();
Self::properties_changed_to_events(object_path, properties_changed)
} else if let Some(interfaces_added) = ObjectManagerInterfacesAdded::from_message(&message)
{
Self::interfaces_added_to_events(interfaces_added)
} else {
log::info!("Unexpected message: {:?}", message);
vec![]
}
}
fn interfaces_added_to_events(
interfaces_added: ObjectManagerInterfacesAdded,
) -> Vec<BluetoothEvent> {
log::trace!("InterfacesAdded: {:?}", interfaces_added);
let mut events = vec![];
let object_path = interfaces_added.object;
if let Some(_device) =
OrgBluezDevice1Properties::from_interfaces(&interfaces_added.interfaces)
{
let id = DeviceId { object_path };
events.push(BluetoothEvent::Device {
id,
event: DeviceEvent::Discovered,
})
}
events
}
fn properties_changed_to_events(
object_path: Path<'static>,
properties_changed: PropertiesPropertiesChanged,
) -> Vec<BluetoothEvent> {
log::trace!(
"PropertiesChanged for {}: {:?}",
object_path,
properties_changed
);
let mut events = vec![];
let changed_properties = &properties_changed.changed_properties;
match properties_changed.interface_name.as_ref() {
ORG_BLUEZ_ADAPTER1_NAME => {
let id = AdapterId { object_path };
let adapter = OrgBluezAdapter1Properties(changed_properties);
if let Some(powered) = adapter.powered() {
events.push(BluetoothEvent::Adapter {
id: id.clone(),
event: AdapterEvent::Powered { powered },
})
}
if let Some(discovering) = adapter.discovering() {
events.push(BluetoothEvent::Adapter {
id,
event: AdapterEvent::Discovering { discovering },
});
}
}
ORG_BLUEZ_DEVICE1_NAME => {
let id = DeviceId { object_path };
let device = OrgBluezDevice1Properties(changed_properties);
if let Some(connected) = device.connected() {
events.push(BluetoothEvent::Device {
id: id.clone(),
event: DeviceEvent::Connected { connected },
});
}
if let Some(rssi) = device.rssi() {
events.push(BluetoothEvent::Device {
id: id.clone(),
event: DeviceEvent::RSSI { rssi },
});
}
if let Some(manufacturer_data) = device.manufacturer_data() {
events.push(BluetoothEvent::Device {
id,
event: DeviceEvent::ManufacturerData {
manufacturer_data: convert_manufacturer_data(manufacturer_data),
},
})
}
}
ORG_BLUEZ_GATT_CHARACTERISTIC1_NAME => {
let id = CharacteristicId { object_path };
let characteristic = OrgBluezGattCharacteristic1Properties(changed_properties);
if let Some(value) = characteristic.value() {
events.push(BluetoothEvent::Characteristic {
id,
event: CharacteristicEvent::Value {
value: value.to_owned(),
},
})
}
}
_ => {}
}
events
}
}
#[cfg(test)]
mod tests {
use super::super::ServiceId;
use dbus::arg::{RefArg, Variant};
use super::*;
#[test]
fn adapter_powered() {
let message = adapter_powered_message("/org/bluez/hci0", true);
let id = AdapterId::new("/org/bluez/hci0");
assert_eq!(
BluetoothEvent::message_to_events(message),
vec![BluetoothEvent::Adapter {
id,
event: AdapterEvent::Powered { powered: true }
}]
)
}
#[test]
fn device_rssi() {
let rssi = 42;
let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", rssi);
let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(
BluetoothEvent::message_to_events(message),
vec![BluetoothEvent::Device {
id,
event: DeviceEvent::RSSI { rssi }
}]
)
}
#[test]
fn device_manufacturer_data() {
let mut manufacturer_data = HashMap::new();
manufacturer_data.insert(42, vec![1u8, 2, 3]);
let message = device_manufacturer_data_message(
"/org/bluez/hci0/dev_11_22_33_44_55_66",
manufacturer_data.clone(),
);
let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(
BluetoothEvent::message_to_events(message),
vec![BluetoothEvent::Device {
id,
event: DeviceEvent::ManufacturerData { manufacturer_data }
}]
)
}
#[test]
fn characteristic_value() {
let value: Vec<u8> = vec![1, 2, 3];
let message = characteristic_value_message(
"/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
&value,
);
let id =
CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034");
assert_eq!(
BluetoothEvent::message_to_events(message),
vec![BluetoothEvent::Characteristic {
id,
event: CharacteristicEvent::Value { value }
}]
)
}
#[test]
fn device_discovered() {
let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(
BluetoothEvent::message_to_events(message),
vec![BluetoothEvent::Device {
id,
event: DeviceEvent::Discovered
}]
)
}
#[test]
fn match_rules_all() {
let match_rules = BluetoothEvent::match_rules(None::<DeviceId>);
let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
let message = adapter_powered_message("/org/bluez/hci0", true);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
let message = characteristic_value_message(
"/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
&vec![1, 2, 3],
);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
}
#[test]
fn match_rules_device() {
let id = DeviceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66");
let match_rules = BluetoothEvent::match_rules(Some(id));
let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = adapter_powered_message("/org/bluez/hci0", true);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
let message = characteristic_value_message(
"/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
&vec![1, 2, 3],
);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
}
#[test]
fn match_rules_service() {
let id = ServiceId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0012");
let match_rules = BluetoothEvent::match_rules(Some(id));
let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = adapter_powered_message("/org/bluez/hci0", true);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = characteristic_value_message(
"/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
&vec![1, 2, 3],
);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
}
#[test]
fn match_rules_characteristic() {
let id =
CharacteristicId::new("/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034");
let match_rules = BluetoothEvent::match_rules(Some(id));
let message = new_device_message("/org/bluez/hci0/dev_11_22_33_44_55_66");
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = adapter_powered_message("/org/bluez/hci0", true);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = device_rssi_message("/org/bluez/hci0/dev_11_22_33_44_55_66", 42);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), false);
let message = characteristic_value_message(
"/org/bluez/hci0/dev_11_22_33_44_55_66/service0012/char0034",
&vec![1, 2, 3],
);
assert_eq!(match_rules.iter().any(|rule| rule.matches(&message)), true);
}
fn new_device_message(device_path: &'static str) -> Message {
let properties = HashMap::new();
let mut interfaces = HashMap::new();
interfaces.insert("org.bluez.Device1".to_string(), properties);
let interfaces_added = ObjectManagerInterfacesAdded {
object: device_path.into(),
interfaces,
};
interfaces_added.to_emit_message(&"/".into())
}
fn adapter_powered_message(adapter_path: &'static str, powered: bool) -> Message {
let mut changed_properties: HashMap<String, Variant<Box<dyn RefArg>>> = HashMap::new();
changed_properties.insert("Powered".to_string(), Variant(Box::new(powered)));
let properties_changed = PropertiesPropertiesChanged {
interface_name: "org.bluez.Adapter1".to_string(),
changed_properties,
invalidated_properties: vec![],
};
properties_changed.to_emit_message(&adapter_path.into())
}
fn device_rssi_message(device_path: &'static str, rssi: i16) -> Message {
let mut changed_properties: HashMap<String, Variant<Box<dyn RefArg>>> = HashMap::new();
changed_properties.insert("RSSI".to_string(), Variant(Box::new(rssi)));
let properties_changed = PropertiesPropertiesChanged {
interface_name: "org.bluez.Device1".to_string(),
changed_properties,
invalidated_properties: vec![],
};
properties_changed.to_emit_message(&device_path.into())
}
fn device_manufacturer_data_message(
device_path: &'static str,
manufacturer_data: HashMap<u16, Vec<u8>>,
) -> Message {
let manufacturer_data: HashMap<_, _> = manufacturer_data
.into_iter()
.map::<(u16, Variant<Box<dyn RefArg>>), _>(|(k, v)| (k, Variant(Box::new(v))))
.collect();
let mut changed_properties: HashMap<String, Variant<Box<dyn RefArg>>> = HashMap::new();
changed_properties.insert(
"ManufacturerData".to_string(),
Variant(Box::new(manufacturer_data)),
);
let properties_changed = PropertiesPropertiesChanged {
interface_name: "org.bluez.Device1".to_string(),
changed_properties,
invalidated_properties: vec![],
};
properties_changed.to_emit_message(&device_path.into())
}
fn characteristic_value_message(characteristic_path: &'static str, value: &[u8]) -> Message {
let mut changed_properties: HashMap<String, Variant<Box<dyn RefArg>>> = HashMap::new();
changed_properties.insert("Value".to_string(), Variant(Box::new(value.to_owned())));
let properties_changed = PropertiesPropertiesChanged {
interface_name: "org.bluez.GattCharacteristic1".to_string(),
changed_properties,
invalidated_properties: vec![],
};
properties_changed.to_emit_message(&characteristic_path.into())
}
}