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

Group characteristics by service #202

Merged
merged 9 commits into from
Sep 2, 2021
Merged
18 changes: 12 additions & 6 deletions examples/discover_adapters_peripherals.rs
Original file line number Diff line number Diff line change
@@ -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...");
@@ -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()
4 changes: 2 additions & 2 deletions examples/lights.rs
Original file line number Diff line number Diff line change
@@ -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();
6 changes: 3 additions & 3 deletions examples/subscribe_notify_characteristic.rs
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 27 additions & 4 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -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
@@ -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.
@@ -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>;
@@ -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.
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 {
@@ -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(
@@ -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> {
@@ -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(
@@ -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 {
@@ -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(),
}
}
}
323 changes: 222 additions & 101 deletions src/corebluetooth/central_delegate.rs

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions src/corebluetooth/framework.rs
Original file line number Diff line number Diff line change
@@ -383,12 +383,9 @@ pub mod cb {

// CBService : CBAttribute

// pub fn service_isprimary(cbservice: *mut Object) -> BOOL {
// unsafe {
// let isprimary: BOOL = msg_send![cbservice, isPrimary];
// isprimary
// }
// }
pub fn service_isprimary(cbservice: *mut Object) -> BOOL {
unsafe { msg_send![cbservice, isPrimary] }
}

pub fn service_includedservices(cbservice: *mut Object) -> *mut Object /* NSArray<CBService*>* */
{
@@ -414,6 +411,10 @@ pub mod cb {
unsafe { msg_send![cbcharacteristic, properties] }
}

pub fn characteristic_service(cbcharacteristic: *mut Object) -> *mut Object /* CBService* */ {
unsafe { msg_send![cbcharacteristic, service] }
}

// CBCharacteristicProperties = NSUInteger from CBCharacteristic.h

pub const CHARACTERISTICPROPERTY_BROADCAST: c_uint = 0x01; // CBCharacteristicPropertyBroadcast
495 changes: 306 additions & 189 deletions src/corebluetooth/internal.rs

Large diffs are not rendered by default.

84 changes: 44 additions & 40 deletions src/corebluetooth/peripheral.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ use super::{
};
use crate::{
api::{
self, BDAddr, CentralEvent, CharPropFlags, Characteristic, PeripheralProperties,
self, BDAddr, CentralEvent, CharPropFlags, Characteristic, PeripheralProperties, Service,
ValueNotification, WriteType,
},
common::{adapter_manager::AdapterManager, util::notifications_stream_from_broadcast_receiver},
@@ -45,7 +45,7 @@ struct Shared {
notifications_channel: broadcast::Sender<ValueNotification>,
manager: AdapterManager<Peripheral>,
uuid: Uuid,
characteristics: Mutex<BTreeSet<Characteristic>>,
services: Mutex<BTreeSet<Service>>,
properties: Mutex<PeripheralProperties>,
message_sender: Sender<CoreBluetoothMessage>,
// We're not actually holding a peripheral object here, that's held out in
@@ -81,7 +81,7 @@ impl Peripheral {
let shared = Arc::new(Shared {
properties,
manager,
characteristics: Mutex::new(BTreeSet::new()),
services: Mutex::new(BTreeSet::new()),
notifications_channel,
uuid,
message_sender,
@@ -160,7 +160,7 @@ impl Debug for Peripheral {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Peripheral")
.field("uuid", &self.shared.uuid)
.field("characteristics", &self.shared.characteristics)
.field("services", &self.shared.services)
.field("properties", &self.shared.properties)
.field("message_sender", &self.shared.message_sender)
.finish()
@@ -179,19 +179,19 @@ impl api::Peripheral for Peripheral {
Ok(Some(self.shared.properties.lock().unwrap().clone()))
}

fn characteristics(&self) -> BTreeSet<Characteristic> {
self.shared.characteristics.lock().unwrap().clone()
fn services(&self) -> BTreeSet<Service> {
self.shared.services.lock().unwrap().clone()
}

async fn is_connected(&self) -> Result<bool> {
let fut = CoreBluetoothReplyFuture::default();
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::IsConnected(
self.shared.uuid,
fut.get_state_clone(),
))
.send(CoreBluetoothMessage::IsConnected {
peripheral_uuid: self.shared.uuid,
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::State(state) => match state {
@@ -207,14 +207,14 @@ impl api::Peripheral for Peripheral {
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::ConnectDevice(
self.shared.uuid,
fut.get_state_clone(),
))
.send(CoreBluetoothMessage::ConnectDevice {
peripheral_uuid: self.shared.uuid,
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::Connected(chars) => {
*(self.shared.characteristics.lock().unwrap()) = chars;
CoreBluetoothReply::Connected(services) => {
*(self.shared.services.lock().unwrap()) = services;
self.shared.manager.emit(CentralEvent::DeviceConnected(
// TODO: look at moving/copying address out of properties so we don't have to
// take a lock here! (the address for the peripheral won't ever change)
@@ -232,9 +232,9 @@ impl api::Peripheral for Peripheral {
Ok(())
}

async fn discover_characteristics(&self) -> Result<Vec<Characteristic>> {
let characteristics = self.shared.characteristics.lock().unwrap().clone();
Ok(characteristics.into_iter().collect())
async fn discover_services(&self) -> Result<()> {
// TODO: Actually discover on this, rather than on connection
Ok(())
}

async fn write(
@@ -257,13 +257,14 @@ impl api::Peripheral for Peripheral {
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::WriteValue(
self.shared.uuid,
characteristic.uuid,
Vec::from(data),
.send(CoreBluetoothMessage::WriteValue {
peripheral_uuid: self.shared.uuid,
service_uuid: characteristic.service_uuid,
characteristic_uuid: characteristic.uuid,
data: Vec::from(data),
write_type,
fut.get_state_clone(),
))
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::Ok => {}
@@ -277,11 +278,12 @@ impl api::Peripheral for Peripheral {
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::ReadValue(
self.shared.uuid,
characteristic.uuid,
fut.get_state_clone(),
))
.send(CoreBluetoothMessage::ReadValue {
peripheral_uuid: self.shared.uuid,
service_uuid: characteristic.service_uuid,
characteristic_uuid: characteristic.uuid,
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::ReadResult(chars) => Ok(chars),
@@ -296,11 +298,12 @@ impl api::Peripheral for Peripheral {
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::Subscribe(
self.shared.uuid,
characteristic.uuid,
fut.get_state_clone(),
))
.send(CoreBluetoothMessage::Subscribe {
peripheral_uuid: self.shared.uuid,
service_uuid: characteristic.service_uuid,
characteristic_uuid: characteristic.uuid,
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::Ok => trace!("subscribed!"),
@@ -314,11 +317,12 @@ impl api::Peripheral for Peripheral {
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::Unsubscribe(
self.shared.uuid,
characteristic.uuid,
fut.get_state_clone(),
))
.send(CoreBluetoothMessage::Unsubscribe {
peripheral_uuid: self.shared.uuid,
service_uuid: characteristic.service_uuid,
characteristic_uuid: characteristic.uuid,
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::Ok => {}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -51,8 +51,8 @@
//! // 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();
15 changes: 12 additions & 3 deletions src/winrtble/ble/characteristic.rs
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ use bindings::Windows::Devices::Bluetooth::GenericAttributeProfile::{
use bindings::Windows::Foundation::{EventRegistrationToken, TypedEventHandler};
use bindings::Windows::Storage::Streams::{DataReader, DataWriter};
use log::{debug, trace};
use uuid::Uuid;

pub type NotifiyEventHandler = Box<dyn Fn(Vec<u8>) + Send>;

@@ -161,11 +162,19 @@ impl BLECharacteristic {
}
}

pub fn to_characteristic(&self) -> Characteristic {
let uuid = utils::to_uuid(&self.characteristic.Uuid().unwrap());
pub fn uuid(&self) -> Uuid {
utils::to_uuid(&self.characteristic.Uuid().unwrap())
}

pub fn to_characteristic(&self, service_uuid: Uuid) -> Characteristic {
let uuid = self.uuid();
let properties =
utils::to_char_props(&self.characteristic.CharacteristicProperties().unwrap());
Characteristic { uuid, properties }
Characteristic {
uuid,
service_uuid,
properties,
}
}
}

20 changes: 4 additions & 16 deletions src/winrtble/ble/device.rs
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ use bindings::Windows::Devices::Bluetooth::GenericAttributeProfile::{
};
use bindings::Windows::Devices::Bluetooth::{BluetoothConnectionStatus, BluetoothLEDevice};
use bindings::Windows::Foundation::{EventRegistrationToken, TypedEventHandler};
use log::{debug, error, trace};
use log::{debug, trace};

pub type ConnectedEventHandler = Box<dyn Fn(bool) + Send>;

@@ -71,8 +71,7 @@ impl BLEDevice {
utils::to_error(status)
}

async fn get_characteristics(
&self,
pub async fn get_characteristics(
service: &GattDeviceService,
) -> std::result::Result<Vec<GattCharacteristic>, windows::Error> {
let async_result = service.GetCharacteristicsAsync()?.await?;
@@ -87,12 +86,11 @@ impl BLEDevice {
}
}

pub async fn discover_characteristics(&self) -> Result<Vec<GattCharacteristic>> {
pub async fn discover_services(&self) -> Result<Vec<GattDeviceService>> {
let winrt_error = |e| Error::Other(format!("{:?}", e).into());
let service_result = self.get_gatt_services().await?;
let status = service_result.Status().map_err(winrt_error)?;
if status == GattCommunicationStatus::Success {
let mut characteristics = Vec::new();
// We need to convert the IVectorView to a Vec, because IVectorView is not Send and so
// can't be help past the await point below.
let services: Vec<_> = service_result
@@ -101,17 +99,7 @@ impl BLEDevice {
.into_iter()
.collect();
debug!("services {:?}", services.len());
for service in &services {
match self.get_characteristics(&service).await {
Ok(mut service_characteristics) => {
characteristics.append(&mut service_characteristics);
}
Err(e) => {
error!("get_characteristics_async {:?}", e);
}
}
}
return Ok(characteristics);
return Ok(services);
}
Ok(Vec::new())
}
1 change: 1 addition & 0 deletions src/winrtble/ble/mod.rs
Original file line number Diff line number Diff line change
@@ -13,4 +13,5 @@

pub mod characteristic;
pub mod device;
pub mod service;
pub mod watcher;
25 changes: 25 additions & 0 deletions src/winrtble/ble/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use super::characteristic::BLECharacteristic;
use crate::api::Service;
use std::collections::HashMap;
use uuid::Uuid;

#[derive(Debug)]
pub struct BLEService {
pub uuid: Uuid,
pub characteristics: HashMap<Uuid, BLECharacteristic>,
}

impl BLEService {
pub fn to_service(&self) -> Service {
let characteristics = self
.characteristics
.values()
.map(|ble_characteristic| ble_characteristic.to_characteristic(self.uuid))
.collect();
Service {
uuid: self.uuid,
primary: true,
characteristics,
}
}
}
141 changes: 83 additions & 58 deletions src/winrtble/peripheral.rs
Original file line number Diff line number Diff line change
@@ -13,20 +13,21 @@

use super::{
advertisement_data_type, bindings, ble::characteristic::BLECharacteristic,
ble::device::BLEDevice, utils,
ble::device::BLEDevice, ble::service::BLEService, utils,
};
use crate::{
api::{
bleuuid::{uuid_from_u16, uuid_from_u32},
AddressType, BDAddr, CentralEvent, Characteristic, Peripheral as ApiPeripheral,
PeripheralProperties, ValueNotification, WriteType,
PeripheralProperties, Service, ValueNotification, WriteType,
},
common::{adapter_manager::AdapterManager, util::notifications_stream_from_broadcast_receiver},
Error, Result,
};
use async_trait::async_trait;
use dashmap::DashMap;
use futures::stream::Stream;
use log::error;
use std::{
collections::{BTreeSet, HashMap, HashSet},
convert::TryInto,
@@ -52,7 +53,7 @@ struct Shared {
adapter: AdapterManager<Peripheral>,
address: BDAddr,
connected: AtomicBool,
ble_characteristics: DashMap<Uuid, BLECharacteristic>,
ble_services: DashMap<Uuid, BLEService>,
notifications_channel: broadcast::Sender<ValueNotification>,

// Mutable, advertised, state...
@@ -74,7 +75,7 @@ impl Peripheral {
device: tokio::sync::Mutex::new(None),
address: address,
connected: AtomicBool::new(false),
ble_characteristics: DashMap::new(),
ble_services: DashMap::new(),
notifications_channel: broadcast_sender,
address_type: RwLock::new(None),
local_name: RwLock::new(None),
@@ -298,8 +299,8 @@ impl Debug for Peripheral {
let properties = self.derive_properties();
write!(
f,
"{} properties: {:?}, characteristics: {:?} {}",
self.shared.address, properties, self.shared.ble_characteristics, connected
"{} properties: {:?}, services: {:?} {}",
self.shared.address, properties, self.shared.ble_services, connected
)
}
}
@@ -317,13 +318,11 @@ impl ApiPeripheral for Peripheral {
Ok(Some(self.derive_properties()))
}

/// The set of characteristics we've discovered for this device. This will be empty until
/// `discover_characteristics` is called.
fn characteristics(&self) -> BTreeSet<Characteristic> {
fn services(&self) -> BTreeSet<Service> {
self.shared
.ble_characteristics
.ble_services
.iter()
.map(|item| item.value().to_characteristic())
.map(|item| item.value().to_service())
.collect()
}

@@ -372,21 +371,35 @@ impl ApiPeripheral for Peripheral {
}

/// Discovers all characteristics for the device. This is a synchronous operation.
async fn discover_characteristics(&self) -> Result<Vec<Characteristic>> {
async fn discover_services(&self) -> Result<()> {
let device = self.shared.device.lock().await;
if let Some(ref device) = *device {
let mut characteristics_result = vec![];
let characteristics = device.discover_characteristics().await?;
for gatt_characteristic in characteristics {
let ble_characteristic = BLECharacteristic::new(gatt_characteristic);
let characteristic = ble_characteristic.to_characteristic();
self.shared
.ble_characteristics
.entry(characteristic.uuid.clone())
.or_insert_with(|| ble_characteristic);
characteristics_result.push(characteristic);
let gatt_services = device.discover_services().await?;
for service in &gatt_services {
let uuid = utils::to_uuid(&service.Uuid().unwrap());
match BLEDevice::get_characteristics(&service).await {
Ok(characteristics) => {
let characteristics = characteristics
.into_iter()
.map(|gatt_characteristic| {
let characteristic = BLECharacteristic::new(gatt_characteristic);
(characteristic.uuid(), characteristic)
})
.collect();
self.shared.ble_services.insert(
uuid,
BLEService {
uuid,
characteristics,
},
);
}
Err(e) => {
error!("get_characteristics_async {:?}", e);
}
}
}
return Ok(characteristics_result);
return Ok(());
}
Err(Error::NotConnected)
}
@@ -399,58 +412,70 @@ impl ApiPeripheral for Peripheral {
data: &[u8],
write_type: WriteType,
) -> Result<()> {
if let Some(ble_characteristic) = self.shared.ble_characteristics.get(&characteristic.uuid)
{
ble_characteristic.write_value(data, write_type).await
} else {
Err(Error::NotSupported("write".into()))
}
let ble_service = &*self
.shared
.ble_services
.get(&characteristic.service_uuid)
.ok_or_else(|| Error::NotSupported("Service not found for write".into()))?;
let ble_characteristic = ble_service
.characteristics
.get(&characteristic.uuid)
.ok_or_else(|| Error::NotSupported("Characteristic not found for write".into()))?;
ble_characteristic.write_value(data, write_type).await
}

/// Enables either notify or indicate (depending on support) for the specified characteristic.
/// This is a synchronous call.
async fn subscribe(&self, characteristic: &Characteristic) -> Result<()> {
if let Some(mut ble_characteristic) = self
let ble_service = &mut *self
.shared
.ble_characteristics
.ble_services
.get_mut(&characteristic.service_uuid)
.ok_or_else(|| Error::NotSupported("Service not found for subscribe".into()))?;
let ble_characteristic = ble_service
.characteristics
.get_mut(&characteristic.uuid)
{
let notifications_sender = self.shared.notifications_channel.clone();
let uuid = characteristic.uuid;
ble_characteristic
.subscribe(Box::new(move |value| {
let notification = ValueNotification { uuid: uuid, value };
// Note: we ignore send errors here which may happen while there are no
// receivers...
let _ = notifications_sender.send(notification);
}))
.await
} else {
Err(Error::NotSupported("subscribe".into()))
}
.ok_or_else(|| Error::NotSupported("Characteristic not found for subscribe".into()))?;
let notifications_sender = self.shared.notifications_channel.clone();
let uuid = characteristic.uuid;
ble_characteristic
.subscribe(Box::new(move |value| {
let notification = ValueNotification { uuid: uuid, value };
// Note: we ignore send errors here which may happen while there are no
// receivers...
let _ = notifications_sender.send(notification);
}))
.await
}

/// Disables either notify or indicate (depending on support) for the specified characteristic.
/// This is a synchronous call.
async fn unsubscribe(&self, characteristic: &Characteristic) -> Result<()> {
if let Some(mut ble_characteristic) = self
let ble_service = &mut *self
.shared
.ble_characteristics
.ble_services
.get_mut(&characteristic.service_uuid)
.ok_or_else(|| Error::NotSupported("Service not found for unsubscribe".into()))?;
let ble_characteristic = ble_service
.characteristics
.get_mut(&characteristic.uuid)
{
ble_characteristic.unsubscribe().await
} else {
Err(Error::NotSupported("unsubscribe".into()))
}
.ok_or_else(|| {
Error::NotSupported("Characteristic not found for unsubscribe".into())
})?;
ble_characteristic.unsubscribe().await
}

async fn read(&self, characteristic: &Characteristic) -> Result<Vec<u8>> {
if let Some(ble_characteristic) = self.shared.ble_characteristics.get(&characteristic.uuid)
{
ble_characteristic.read_value().await
} else {
Err(Error::NotSupported("read".into()))
}
let ble_service = &*self
.shared
.ble_services
.get(&characteristic.service_uuid)
.ok_or_else(|| Error::NotSupported("Service not found for read".into()))?;
let ble_characteristic = ble_service
.characteristics
.get(&characteristic.uuid)
.ok_or_else(|| Error::NotSupported("Characteristic not found for read".into()))?;
ble_characteristic.read_value().await
}

async fn notifications(&self) -> Result<Pin<Box<dyn Stream<Item = ValueNotification> + Send>>> {