Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions examples/discover_adapters_peripherals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.start_scan()
.await
.expect("Can't scan BLE adapter for connected devices...");
time::sleep(Duration::from_secs(2)).await;
time::sleep(Duration::from_secs(10)).await;
let peripherals = adapter.peripherals().await?;
if peripherals.is_empty() {
eprintln!("->>> BLE peripheral devices were not found, sorry. Exiting...");
Expand Down Expand Up @@ -53,12 +53,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
"Now connected ({:?}) to peripheral {:?}...",
is_connected, &local_name
);
let chars = peripheral.discover_characteristics().await?;
if is_connected {
println!("Discover peripheral {:?} characteristics...", &local_name);
for characteristic in chars.into_iter() {
println!("{:?}", characteristic);
peripheral.discover_services().await?;
println!("Discover peripheral {:?} services...", &local_name);
for service in peripheral.services() {
println!(
"Service UUID {}, primary: {}",
service.uuid, service.primary
);
for characteristic in service.characteristics {
println!(" {:?}", characteristic);
}
}
if is_connected {
println!("Disconnecting from peripheral {:?}...", &local_name);
peripheral
.disconnect()
Expand Down
4 changes: 2 additions & 2 deletions examples/lights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
// connect to the device
light.connect().await?;

// discover characteristics
light.discover_characteristics().await?;
// discover services and characteristics
light.discover_services().await?;

// find the characteristic we want
let chars = light.characteristics();
Expand Down
6 changes: 3 additions & 3 deletions examples/subscribe_notify_characteristic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
"Now connected ({:?}) to peripheral {:?}.",
is_connected, &local_name
);
let chars = peripheral.discover_characteristics().await?;
println!("Discover peripheral {:?} services...", local_name);
peripheral.discover_services().await?;
if is_connected {
println!("Discover peripheral {:?} characteristics...", local_name);
for characteristic in chars.into_iter() {
for characteristic in peripheral.characteristics() {
println!("Checking characteristic {:?}", characteristic);
// Subscribe to notifications from the characteristic with the selected
// UUID.
Expand Down
31 changes: 27 additions & 4 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ impl Default for CharPropFlags {
}
}

/// A GATT service. Services are groups of characteristics, which may be standard or
/// device-specific.
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
pub struct Service {
/// The UUID for this service.
pub uuid: Uuid,
/// Whether this is a primary service.
pub primary: bool,
/// The characteristics of this service.
pub characteristics: BTreeSet<Characteristic>,
}

/// A Bluetooth characteristic. Characteristics are the main way you will interact with other
/// bluetooth devices. Characteristics are identified by a UUID which may be standardized
/// (like 0x2803, which identifies a characteristic for reading heart rate measurements) but more
Expand All @@ -124,6 +136,8 @@ impl Default for CharPropFlags {
pub struct Characteristic {
/// The UUID for this characteristic. This uniquely identifies its behavior.
pub uuid: Uuid,
/// The UUID of the service this characteristic belongs to.
pub service_uuid: Uuid,
/// The set of properties for this characteristic, which indicate what functionality it
/// supports. If you attempt an operation that is not supported by the characteristics (for
/// example setting notify on one without the NOTIFY flag), that operation will fail.
Expand Down Expand Up @@ -186,9 +200,18 @@ pub trait Peripheral: Send + Sync + Clone + Debug {
/// as additional advertising reports are received.
async fn properties(&self) -> Result<Option<PeripheralProperties>>;

/// The set of services we've discovered for this device. This will be empty until
/// `discover_services` is called.
fn services(&self) -> BTreeSet<Service>;

/// The set of characteristics we've discovered for this device. This will be empty until
/// `discover_characteristics` is called.
fn characteristics(&self) -> BTreeSet<Characteristic>;
/// `discover_services` is called.
fn characteristics(&self) -> BTreeSet<Characteristic> {
self.services()
.iter()
.flat_map(|service| service.characteristics.clone().into_iter())
.collect()
}

/// Returns true iff we are currently connected to the device.
async fn is_connected(&self) -> Result<bool>;
Expand All @@ -201,8 +224,8 @@ pub trait Peripheral: Send + Sync + Clone + Debug {
/// Terminates a connection to the device.
async fn disconnect(&self) -> Result<()>;

/// Discovers all characteristics for the device.
async fn discover_characteristics(&self) -> Result<Vec<Characteristic>>;
/// Discovers all services for the device, including their characteristics.
async fn discover_services(&self) -> Result<()>;

/// Write some data to the characteristic. Returns an error if the write couldn't be sent or (in
/// the case of a write-with-response) if the device returns an error.
Expand Down
122 changes: 86 additions & 36 deletions src/bluez/peripheral.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
use async_trait::async_trait;
use bluez_async::{
BluetoothEvent, BluetoothSession, CharacteristicEvent, CharacteristicFlags, CharacteristicInfo,
DeviceId, DeviceInfo, MacAddress, WriteOptions,
BluetoothEvent, BluetoothSession, CharacteristicEvent, CharacteristicFlags, CharacteristicId,
CharacteristicInfo, DeviceId, DeviceInfo, MacAddress, ServiceInfo, WriteOptions,
};
use futures::future::ready;
use futures::stream::{Stream, StreamExt};
use std::collections::BTreeSet;
use std::collections::{BTreeSet, HashMap};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use uuid::Uuid;

use crate::api::{
self, AddressType, BDAddr, CharPropFlags, Characteristic, PeripheralProperties,
self, AddressType, BDAddr, CharPropFlags, Characteristic, PeripheralProperties, Service,
ValueNotification, WriteType,
};
use crate::{Error, Result};

#[derive(Clone, Debug)]
struct ServiceInternal {
info: ServiceInfo,
characteristics: HashMap<Uuid, CharacteristicInfo>,
}

/// Implementation of [api::Peripheral](crate::api::Peripheral).
#[derive(Clone, Debug)]
pub struct Peripheral {
session: BluetoothSession,
device: DeviceId,
mac_address: BDAddr,
characteristics: Arc<Mutex<Vec<CharacteristicInfo>>>,
services: Arc<Mutex<HashMap<Uuid, ServiceInternal>>>,
}

impl Peripheral {
Expand All @@ -30,15 +37,25 @@ impl Peripheral {
session,
device: device.id,
mac_address: (&device.mac_address).into(),
characteristics: Arc::new(Mutex::new(vec![])),
services: Arc::new(Mutex::new(HashMap::new())),
}
}

fn characteristic_info(&self, characteristic: &Characteristic) -> Result<CharacteristicInfo> {
let characteristics = self.characteristics.lock().unwrap();
characteristics
.iter()
.find(|info| info.uuid == characteristic.uuid)
let services = self.services.lock().unwrap();
services
.get(&characteristic.service_uuid)
.ok_or_else(|| {
Error::Other(
format!(
"Service with UUID {} not found.",
characteristic.service_uuid
)
.into(),
)
})?
.characteristics
.get(&characteristic.uuid)
.cloned()
.ok_or_else(|| {
Error::Other(
Expand Down Expand Up @@ -76,9 +93,13 @@ impl api::Peripheral for Peripheral {
}))
}

fn characteristics(&self) -> BTreeSet<Characteristic> {
let characteristics = &*self.characteristics.lock().unwrap();
characteristics.iter().map(Characteristic::from).collect()
fn services(&self) -> BTreeSet<Service> {
self.services
.lock()
.unwrap()
.values()
.map(|service| service.into())
.collect()
}

async fn is_connected(&self) -> Result<bool> {
Expand All @@ -96,15 +117,24 @@ impl api::Peripheral for Peripheral {
Ok(())
}

async fn discover_characteristics(&self) -> Result<Vec<Characteristic>> {
let mut characteristics = vec![];
async fn discover_services(&self) -> Result<()> {
let mut services_internal = HashMap::new();
let services = self.session.get_services(&self.device).await?;
for service in services {
characteristics.extend(self.session.get_characteristics(&service.id).await?);
let characteristics = self.session.get_characteristics(&service.id).await?;
services_internal.insert(
service.uuid,
ServiceInternal {
info: service,
characteristics: characteristics
.into_iter()
.map(|characteristic| (characteristic.uuid, characteristic))
.collect(),
},
);
}
let converted = characteristics.iter().map(Characteristic::from).collect();
*self.characteristics.lock().unwrap() = characteristics;
Ok(converted)
*self.services.lock().unwrap() = services_internal;
Ok(())
}

async fn write(
Expand Down Expand Up @@ -145,38 +175,45 @@ impl api::Peripheral for Peripheral {
async fn notifications(&self) -> Result<Pin<Box<dyn Stream<Item = ValueNotification> + Send>>> {
let device_id = self.device.clone();
let events = self.session.device_event_stream(&device_id).await?;
let characteristics = self.characteristics.clone();
let services = self.services.clone();
Ok(Box::pin(events.filter_map(move |event| {
ready(value_notification(
event,
&device_id,
characteristics.clone(),
))
ready(value_notification(event, &device_id, services.clone()))
})))
}
}

fn value_notification(
event: BluetoothEvent,
device_id: &DeviceId,
characteristics: Arc<Mutex<Vec<CharacteristicInfo>>>,
services: Arc<Mutex<HashMap<Uuid, ServiceInternal>>>,
) -> Option<ValueNotification> {
match event {
BluetoothEvent::Characteristic {
id,
event: CharacteristicEvent::Value { value },
} if id.service().device() == *device_id => {
let characteristics = characteristics.lock().unwrap();
let uuid = characteristics
.iter()
.find(|characteristic| characteristic.id == id)?
.uuid;
let services = services.lock().unwrap();
let uuid = find_characteristic_by_id(&services, id)?.uuid;
Some(ValueNotification { uuid, value })
}
_ => None,
}
}

fn find_characteristic_by_id(
services: &HashMap<Uuid, ServiceInternal>,
characteristic_id: CharacteristicId,
) -> Option<&CharacteristicInfo> {
for service in services.values() {
for characteristic in service.characteristics.values() {
if characteristic.id == characteristic_id {
return Some(characteristic);
}
}
}
None
}

impl From<WriteType> for bluez_async::WriteType {
fn from(write_type: WriteType) -> Self {
match write_type {
Expand All @@ -201,11 +238,24 @@ impl From<bluez_async::AddressType> for AddressType {
}
}

impl From<&CharacteristicInfo> for Characteristic {
fn from(characteristic: &CharacteristicInfo) -> Self {
Characteristic {
uuid: characteristic.uuid,
properties: characteristic.flags.into(),
fn make_characteristic(info: &CharacteristicInfo, service_uuid: Uuid) -> Characteristic {
Characteristic {
uuid: info.uuid,
properties: info.flags.into(),
service_uuid,
}
}

impl From<&ServiceInternal> for Service {
fn from(service: &ServiceInternal) -> Self {
Service {
uuid: service.info.uuid,
primary: service.info.primary,
characteristics: service
.characteristics
.iter()
.map(|(_, characteristic)| make_characteristic(characteristic, service.info.uuid))
.collect(),
}
}
}
Expand Down
Loading