mod adapter;
mod bleuuid;
mod characteristic;
mod descriptor;
mod device;
mod events;
mod introspect;
mod macaddress;
mod messagestream;
mod modalias;
mod serde_path;
mod service;
pub use self::adapter::{AdapterId, AdapterInfo};
pub use self::bleuuid::{uuid_from_u16, uuid_from_u32, BleUuid};
pub use self::characteristic::{CharacteristicFlags, CharacteristicId, CharacteristicInfo};
pub use self::descriptor::{DescriptorId, DescriptorInfo};
pub use self::device::{AddressType, DeviceId, DeviceInfo};
pub use self::events::{AdapterEvent, BluetoothEvent, CharacteristicEvent, DeviceEvent};
use self::introspect::IntrospectParse;
pub use self::macaddress::{MacAddress, ParseMacAddressError};
use self::messagestream::MessageStream;
pub use self::modalias::{Modalias, ParseModaliasError};
pub use self::service::{ServiceId, ServiceInfo};
use bluez_generated::{
OrgBluezAdapter1, OrgBluezAdapter1Properties, OrgBluezDevice1, OrgBluezDevice1Properties,
OrgBluezGattCharacteristic1, OrgBluezGattDescriptor1, OrgBluezGattService1,
ORG_BLUEZ_ADAPTER1_NAME, ORG_BLUEZ_DEVICE1_NAME,
};
use dbus::arg::{PropMap, Variant};
use dbus::nonblock::stdintf::org_freedesktop_dbus::{Introspectable, ObjectManager, Properties};
use dbus::nonblock::{Proxy, SyncConnection};
use dbus::Path;
use dbus_tokio::connection::IOResourceError;
use futures::stream::{self, select_all, StreamExt};
use futures::{FutureExt, Stream};
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::{self, Debug, Display, Formatter};
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tokio::task::JoinError;
use tokio::time::timeout;
use uuid::Uuid;
const DBUS_METHOD_CALL_TIMEOUT: Duration = Duration::from_secs(30);
const DBUS_METHOD_CALL_MAX_TIMEOUT: Duration = Duration::from_secs(i32::MAX as u64);
const SERVICE_DISCOVERY_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Error)]
pub enum BluetoothError {
#[error("No Bluetooth adapters found.")]
NoBluetoothAdapters,
#[error(transparent)]
DbusError(#[from] dbus::Error),
#[error("Error parsing XML for introspection: {0}")]
XmlParseError(#[from] serde_xml_rs::Error),
#[error("Service or characteristic UUID {uuid} not found.")]
UuidNotFound { uuid: Uuid },
#[error("Error parsing UUID string: {0}")]
UuidParseError(#[from] uuid::Error),
#[error("Invalid characteristic flag {0:?}")]
FlagParseError(String),
#[error("Invalid address type {0}")]
AddressTypeParseError(String),
#[error("Required property {0} missing.")]
RequiredPropertyMissing(&'static str),
#[error("Service discovery timed out")]
ServiceDiscoveryTimedOut,
#[error(transparent)]
MacAddressParseError(#[from] ParseMacAddressError),
#[error(transparent)]
ModaliasParseError(#[from] ParseModaliasError),
}
#[derive(Debug, Error)]
pub enum SpawnError {
#[error("D-Bus connection lost: {0}")]
DbusConnectionLost(#[source] IOResourceError),
#[error("Task failed: {0}")]
Join(#[from] JoinError),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Transport {
Auto,
BrEdr,
Le,
}
impl Transport {
fn as_str(&self) -> &'static str {
match self {
Self::Auto => "auto",
Self::BrEdr => "bredr",
Self::Le => "le",
}
}
}
impl Display for Transport {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DiscoveryFilter {
pub service_uuids: Vec<Uuid>,
pub rssi_threshold: Option<i16>,
pub pathloss_threshold: Option<u16>,
pub transport: Option<Transport>,
pub duplicate_data: Option<bool>,
pub discoverable: Option<bool>,
pub pattern: Option<String>,
}
impl From<&DiscoveryFilter> for PropMap {
fn from(filter: &DiscoveryFilter) -> Self {
let mut map: PropMap = HashMap::new();
if !filter.service_uuids.is_empty() {
let uuids: Vec<String> = filter.service_uuids.iter().map(Uuid::to_string).collect();
map.insert("UUIDs".to_string(), Variant(Box::new(uuids)));
}
if let Some(rssi_threshold) = filter.rssi_threshold {
map.insert("RSSI".to_string(), Variant(Box::new(rssi_threshold)));
}
if let Some(pathloss_threshold) = filter.pathloss_threshold {
map.insert(
"Pathloss".to_string(),
Variant(Box::new(pathloss_threshold)),
);
}
if let Some(transport) = filter.transport {
map.insert(
"Transport".to_string(),
Variant(Box::new(transport.to_string())),
);
}
if let Some(duplicate_data) = filter.duplicate_data {
map.insert(
"DuplicateData".to_string(),
Variant(Box::new(duplicate_data)),
);
}
if let Some(discoverable) = filter.discoverable {
map.insert("Discoverable".to_string(), Variant(Box::new(discoverable)));
}
if let Some(pattern) = &filter.pattern {
map.insert("Pattern".to_string(), Variant(Box::new(pattern.to_owned())));
}
map
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WriteType {
WithResponse,
WithoutResponse,
Reliable,
}
impl WriteType {
fn as_str(&self) -> &'static str {
match self {
Self::WithResponse => "request",
Self::WithoutResponse => "command",
Self::Reliable => "reliable",
}
}
}
impl Display for WriteType {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct WriteOptions {
pub offset: usize,
pub write_type: Option<WriteType>,
}
impl From<WriteOptions> for PropMap {
fn from(options: WriteOptions) -> Self {
let mut map: PropMap = HashMap::new();
if options.offset != 0 {
map.insert(
"offset".to_string(),
Variant(Box::new(options.offset as u64)),
);
}
if let Some(write_type) = options.write_type {
map.insert(
"type".to_string(),
Variant(Box::new(write_type.to_string())),
);
}
map
}
}
#[derive(Clone)]
pub struct BluetoothSession {
connection: Arc<SyncConnection>,
}
impl Debug for BluetoothSession {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "BluetoothSession")
}
}
impl BluetoothSession {
pub async fn new(
) -> Result<(impl Future<Output = Result<(), SpawnError>>, Self), BluetoothError> {
let (dbus_resource, connection) = dbus_tokio::connection::new_system_sync()?;
connection.set_signal_match_mode(true);
let dbus_handle = tokio::spawn(async {
let err = dbus_resource.await;
Err(SpawnError::DbusConnectionLost(err))
});
Ok((dbus_handle.map(|res| res?), BluetoothSession { connection }))
}
pub async fn start_discovery(&self) -> Result<(), BluetoothError> {
self.start_discovery_with_filter(&DiscoveryFilter::default())
.await
}
pub async fn start_discovery_on_adapter(
&self,
adapter: &AdapterId,
) -> Result<(), BluetoothError> {
self.start_discovery_on_adapter_with_filter(adapter, &DiscoveryFilter::default())
.await
}
pub async fn start_discovery_with_filter(
&self,
discovery_filter: &DiscoveryFilter,
) -> Result<(), BluetoothError> {
let adapters = self.get_adapters().await?;
if adapters.is_empty() {
return Err(BluetoothError::NoBluetoothAdapters);
}
for adapter in adapters {
log::trace!("Starting discovery on adapter {}", adapter.id);
self.start_discovery_on_adapter_with_filter(&adapter.id, discovery_filter)
.await
.unwrap_or_else(|err| log::error!("starting discovery failed {:?}", err));
}
Ok(())
}
pub async fn start_discovery_on_adapter_with_filter(
&self,
adapter_id: &AdapterId,
discovery_filter: &DiscoveryFilter,
) -> Result<(), BluetoothError> {
let adapter = self.adapter(adapter_id);
adapter.set_powered(true).await?;
adapter
.set_discovery_filter(discovery_filter.into())
.await?;
adapter.start_discovery().await?;
Ok(())
}
pub async fn stop_discovery(&self) -> Result<(), BluetoothError> {
let adapters = self.get_adapters().await?;
if adapters.is_empty() {
return Err(BluetoothError::NoBluetoothAdapters);
}
for adapter in adapters {
self.stop_discovery_on_adapter(&adapter.id).await?;
}
Ok(())
}
pub async fn stop_discovery_on_adapter(
&self,
adapter_id: &AdapterId,
) -> Result<(), BluetoothError> {
let adapter = self.adapter(adapter_id);
adapter.stop_discovery().await?;
Ok(())
}
pub async fn get_adapters(&self) -> Result<Vec<AdapterInfo>, BluetoothError> {
let bluez_root = Proxy::new(
"org.bluez",
"/",
DBUS_METHOD_CALL_TIMEOUT,
self.connection.clone(),
);
let tree = bluez_root.get_managed_objects().await?;
Ok(tree
.into_iter()
.filter_map(|(object_path, interfaces)| {
let adapter_properties = OrgBluezAdapter1Properties::from_interfaces(&interfaces)?;
AdapterInfo::from_properties(AdapterId { object_path }, adapter_properties).ok()
})
.collect())
}
pub async fn get_devices(&self) -> Result<Vec<DeviceInfo>, BluetoothError> {
let bluez_root = Proxy::new(
"org.bluez",
"/",
DBUS_METHOD_CALL_TIMEOUT,
self.connection.clone(),
);
let tree = bluez_root.get_managed_objects().await?;
let devices = tree
.into_iter()
.filter_map(|(object_path, interfaces)| {
let device_properties = OrgBluezDevice1Properties::from_interfaces(&interfaces)?;
DeviceInfo::from_properties(DeviceId { object_path }, device_properties).ok()
})
.collect();
Ok(devices)
}
pub async fn get_devices_on_adapter(
&self,
adapter: &AdapterId,
) -> Result<Vec<DeviceInfo>, BluetoothError> {
let devices = self.get_devices().await?;
Ok(devices
.into_iter()
.filter(|device| device.id.adapter() == *adapter)
.collect())
}
pub async fn get_services(
&self,
device: &DeviceId,
) -> Result<Vec<ServiceInfo>, BluetoothError> {
let device_node = self
.device(device, DBUS_METHOD_CALL_TIMEOUT)
.introspect_parse()
.await?;
let mut services = vec![];
for subnode in device_node.nodes {
let subnode_name = subnode.name.as_ref().unwrap();
if subnode_name.starts_with("service") {
let service_id = ServiceId {
object_path: format!("{}/{}", device.object_path, subnode_name).into(),
};
let service = self.service(&service_id);
let uuid = Uuid::parse_str(&service.uuid().await?)?;
let primary = service.primary().await?;
services.push(ServiceInfo {
id: service_id,
uuid,
primary,
});
}
}
Ok(services)
}
pub async fn get_characteristics(
&self,
service: &ServiceId,
) -> Result<Vec<CharacteristicInfo>, BluetoothError> {
let service_node = self.service(service).introspect_parse().await?;
let mut characteristics = vec![];
for subnode in service_node.nodes {
let subnode_name = subnode.name.as_ref().unwrap();
if subnode_name.starts_with("char") {
let characteristic_id = CharacteristicId {
object_path: format!("{}/{}", service.object_path, subnode_name).into(),
};
let characteristic = self.characteristic(&characteristic_id);
let uuid = Uuid::parse_str(&characteristic.uuid().await?)?;
let flags = characteristic.flags().await?;
characteristics.push(CharacteristicInfo {
id: characteristic_id,
uuid,
flags: flags.try_into()?,
});
}
}
Ok(characteristics)
}
pub async fn get_descriptors(
&self,
characteristic: &CharacteristicId,
) -> Result<Vec<DescriptorInfo>, BluetoothError> {
let characteristic_node = self
.characteristic(characteristic)
.introspect_parse()
.await?;
let mut descriptors = vec![];
for subnode in characteristic_node.nodes {
let subnode_name = subnode.name.as_ref().unwrap();
if subnode_name.starts_with("desc") {
let descriptor_id = DescriptorId {
object_path: format!("{}/{}", characteristic.object_path, subnode_name).into(),
};
let uuid = Uuid::parse_str(&self.descriptor(&descriptor_id).uuid().await?)?;
descriptors.push(DescriptorInfo {
id: descriptor_id,
uuid,
});
}
}
Ok(descriptors)
}
pub async fn get_service_by_uuid(
&self,
device: &DeviceId,
uuid: Uuid,
) -> Result<ServiceInfo, BluetoothError> {
let services = self.get_services(device).await?;
services
.into_iter()
.find(|service_info| service_info.uuid == uuid)
.ok_or(BluetoothError::UuidNotFound { uuid })
}
pub async fn get_characteristic_by_uuid(
&self,
service: &ServiceId,
uuid: Uuid,
) -> Result<CharacteristicInfo, BluetoothError> {
let characteristics = self.get_characteristics(service).await?;
characteristics
.into_iter()
.find(|characteristic_info| characteristic_info.uuid == uuid)
.ok_or(BluetoothError::UuidNotFound { uuid })
}
pub async fn get_service_characteristic_by_uuid(
&self,
device: &DeviceId,
service_uuid: Uuid,
characteristic_uuid: Uuid,
) -> Result<CharacteristicInfo, BluetoothError> {
let service = self.get_service_by_uuid(device, service_uuid).await?;
self.get_characteristic_by_uuid(&service.id, characteristic_uuid)
.await
}
pub async fn get_device_info(&self, id: &DeviceId) -> Result<DeviceInfo, BluetoothError> {
let device = self.device(id, DBUS_METHOD_CALL_TIMEOUT);
let properties = device.get_all(ORG_BLUEZ_DEVICE1_NAME).await?;
DeviceInfo::from_properties(id.to_owned(), OrgBluezDevice1Properties(&properties))
}
pub async fn get_adapter_info(&self, id: &AdapterId) -> Result<AdapterInfo, BluetoothError> {
let adapter = self.adapter(id);
let properties = adapter.get_all(ORG_BLUEZ_ADAPTER1_NAME).await?;
AdapterInfo::from_properties(id.to_owned(), OrgBluezAdapter1Properties(&properties))
}
pub async fn get_service_info(&self, id: &ServiceId) -> Result<ServiceInfo, BluetoothError> {
let service = self.service(id);
let uuid = Uuid::parse_str(&service.uuid().await?)?;
let primary = service.primary().await?;
Ok(ServiceInfo {
id: id.to_owned(),
uuid,
primary,
})
}
pub async fn get_characteristic_info(
&self,
id: &CharacteristicId,
) -> Result<CharacteristicInfo, BluetoothError> {
let characteristic = self.characteristic(id);
let uuid = Uuid::parse_str(&characteristic.uuid().await?)?;
let flags = characteristic.flags().await?;
Ok(CharacteristicInfo {
id: id.to_owned(),
uuid,
flags: flags.try_into()?,
})
}
pub async fn get_descriptor_info(
&self,
id: &DescriptorId,
) -> Result<DescriptorInfo, BluetoothError> {
let uuid = Uuid::parse_str(&self.descriptor(id).uuid().await?)?;
Ok(DescriptorInfo {
id: id.to_owned(),
uuid,
})
}
fn adapter(&self, id: &AdapterId) -> impl OrgBluezAdapter1 + Introspectable + Properties {
Proxy::new(
"org.bluez",
id.object_path.to_owned(),
DBUS_METHOD_CALL_TIMEOUT,
self.connection.clone(),
)
}
fn device(
&self,
id: &DeviceId,
timeout: Duration,
) -> impl OrgBluezDevice1 + Introspectable + Properties {
let timeout = timeout.min(DBUS_METHOD_CALL_MAX_TIMEOUT);
Proxy::new(
"org.bluez",
id.object_path.to_owned(),
timeout,
self.connection.clone(),
)
}
fn service(&self, id: &ServiceId) -> impl OrgBluezGattService1 + Introspectable + Properties {
Proxy::new(
"org.bluez",
id.object_path.to_owned(),
DBUS_METHOD_CALL_TIMEOUT,
self.connection.clone(),
)
}
fn characteristic(
&self,
id: &CharacteristicId,
) -> impl OrgBluezGattCharacteristic1 + Introspectable + Properties {
Proxy::new(
"org.bluez",
id.object_path.to_owned(),
DBUS_METHOD_CALL_TIMEOUT,
self.connection.clone(),
)
}
fn descriptor(
&self,
id: &DescriptorId,
) -> impl OrgBluezGattDescriptor1 + Introspectable + Properties {
Proxy::new(
"org.bluez",
id.object_path.to_owned(),
DBUS_METHOD_CALL_TIMEOUT,
self.connection.clone(),
)
}
async fn await_service_discovery(&self, device_id: &DeviceId) -> Result<(), BluetoothError> {
let mut events = self.device_event_stream(device_id).await?;
if self
.device(device_id, DBUS_METHOD_CALL_TIMEOUT)
.services_resolved()
.await?
{
log::info!("Services already resolved.");
return Ok(());
}
timeout(SERVICE_DISCOVERY_TIMEOUT, async {
while let Some(event) = events.next().await {
if matches!(event, BluetoothEvent::Device {
id,
event: DeviceEvent::ServicesResolved,
} if device_id == &id)
{
return Ok(());
}
}
Err(BluetoothError::ServiceDiscoveryTimedOut)
})
.await
.unwrap_or(Err(BluetoothError::ServiceDiscoveryTimedOut))
}
pub async fn connect(&self, id: &DeviceId) -> Result<(), BluetoothError> {
self.connect_with_timeout(id, DBUS_METHOD_CALL_TIMEOUT)
.await
}
pub async fn connect_with_timeout(
&self,
id: &DeviceId,
timeout: Duration,
) -> Result<(), BluetoothError> {
self.device(id, timeout).connect().await?;
self.await_service_discovery(id).await
}
pub async fn disconnect(&self, id: &DeviceId) -> Result<(), BluetoothError> {
Ok(self
.device(id, DBUS_METHOD_CALL_TIMEOUT)
.disconnect()
.await?)
}
pub async fn read_characteristic_value(
&self,
id: &CharacteristicId,
) -> Result<Vec<u8>, BluetoothError> {
self.read_characteristic_value_with_offset(id, 0).await
}
pub async fn read_characteristic_value_with_offset(
&self,
id: &CharacteristicId,
offset: usize,
) -> Result<Vec<u8>, BluetoothError> {
let characteristic = self.characteristic(id);
Ok(characteristic.read_value(offset_to_propmap(offset)).await?)
}
pub async fn write_characteristic_value(
&self,
id: &CharacteristicId,
value: impl Into<Vec<u8>>,
) -> Result<(), BluetoothError> {
self.write_characteristic_value_with_options(id, value, WriteOptions::default())
.await
}
pub async fn write_characteristic_value_with_options(
&self,
id: &CharacteristicId,
value: impl Into<Vec<u8>>,
options: WriteOptions,
) -> Result<(), BluetoothError> {
let characteristic = self.characteristic(id);
Ok(characteristic
.write_value(value.into(), options.into())
.await?)
}
pub async fn read_descriptor_value(
&self,
id: &DescriptorId,
) -> Result<Vec<u8>, BluetoothError> {
self.read_descriptor_value_with_offset(id, 0).await
}
pub async fn read_descriptor_value_with_offset(
&self,
id: &DescriptorId,
offset: usize,
) -> Result<Vec<u8>, BluetoothError> {
let descriptor = self.descriptor(id);
Ok(descriptor.read_value(offset_to_propmap(offset)).await?)
}
pub async fn write_descriptor_value(
&self,
id: &DescriptorId,
value: impl Into<Vec<u8>>,
) -> Result<(), BluetoothError> {
self.write_descriptor_value_with_offset(id, value, 0).await
}
pub async fn write_descriptor_value_with_offset(
&self,
id: &DescriptorId,
value: impl Into<Vec<u8>>,
offset: usize,
) -> Result<(), BluetoothError> {
let descriptor = self.descriptor(id);
Ok(descriptor
.write_value(value.into(), offset_to_propmap(offset))
.await?)
}
pub async fn start_notify(&self, id: &CharacteristicId) -> Result<(), BluetoothError> {
let characteristic = self.characteristic(id);
characteristic.start_notify().await?;
Ok(())
}
pub async fn stop_notify(&self, id: &CharacteristicId) -> Result<(), BluetoothError> {
let characteristic = self.characteristic(id);
characteristic.stop_notify().await?;
Ok(())
}
pub async fn event_stream(&self) -> Result<impl Stream<Item = BluetoothEvent>, BluetoothError> {
self.filtered_event_stream(None::<&DeviceId>, true).await
}
pub async fn adapter_event_stream(
&self,
adapter: &AdapterId,
) -> Result<impl Stream<Item = BluetoothEvent>, BluetoothError> {
self.filtered_event_stream(Some(adapter), true).await
}
pub async fn device_event_stream(
&self,
device: &DeviceId,
) -> Result<impl Stream<Item = BluetoothEvent>, BluetoothError> {
self.filtered_event_stream(Some(device), false).await
}
pub async fn characteristic_event_stream(
&self,
characteristic: &CharacteristicId,
) -> Result<impl Stream<Item = BluetoothEvent>, BluetoothError> {
self.filtered_event_stream(Some(characteristic), false)
.await
}
async fn filtered_event_stream(
&self,
object: Option<&(impl Into<Path<'static>> + Clone)>,
device_discovery: bool,
) -> Result<impl Stream<Item = BluetoothEvent>, BluetoothError> {
let mut message_streams = vec![];
for match_rule in BluetoothEvent::match_rules(object.cloned(), device_discovery) {
let msg_match = self.connection.add_match(match_rule).await?;
message_streams.push(MessageStream::new(msg_match, self.connection.clone()));
}
Ok(select_all(message_streams)
.flat_map(|message| stream::iter(BluetoothEvent::message_to_events(message))))
}
}
fn offset_to_propmap(offset: usize) -> PropMap {
let mut map: PropMap = HashMap::new();
if offset != 0 {
map.insert("offset".to_string(), Variant(Box::new(offset as u64)));
}
map
}