diff --git a/examples/discover_adapters_peripherals.rs b/examples/discover_adapters_peripherals.rs index d520427d..5126599f 100644 --- a/examples/discover_adapters_peripherals.rs +++ b/examples/discover_adapters_peripherals.rs @@ -24,7 +24,7 @@ async fn main() -> Result<(), Box> { .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> { "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() diff --git a/examples/lights.rs b/examples/lights.rs index 8086ee44..76e08f59 100644 --- a/examples/lights.rs +++ b/examples/lights.rs @@ -54,8 +54,8 @@ async fn main() -> Result<(), Box> { // 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(); diff --git a/examples/subscribe_notify_characteristic.rs b/examples/subscribe_notify_characteristic.rs index fe9bf2bc..8d8cb602 100644 --- a/examples/subscribe_notify_characteristic.rs +++ b/examples/subscribe_notify_characteristic.rs @@ -62,10 +62,10 @@ async fn main() -> Result<(), Box> { "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. diff --git a/src/api/mod.rs b/src/api/mod.rs index a953105e..b0e5e98c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -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, +} + /// 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>; + /// The set of services we've discovered for this device. This will be empty until + /// `discover_services` is called. + fn services(&self) -> BTreeSet; + /// The set of characteristics we've discovered for this device. This will be empty until - /// `discover_characteristics` is called. - fn characteristics(&self) -> BTreeSet; + /// `discover_services` is called. + fn characteristics(&self) -> BTreeSet { + 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; @@ -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>; + /// 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. diff --git a/src/bluez/peripheral.rs b/src/bluez/peripheral.rs index 3cea0a4c..443fa059 100644 --- a/src/bluez/peripheral.rs +++ b/src/bluez/peripheral.rs @@ -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, +} + /// Implementation of [api::Peripheral](crate::api::Peripheral). #[derive(Clone, Debug)] pub struct Peripheral { session: BluetoothSession, device: DeviceId, mac_address: BDAddr, - characteristics: Arc>>, + services: Arc>>, } 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 { - 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 { - let characteristics = &*self.characteristics.lock().unwrap(); - characteristics.iter().map(Characteristic::from).collect() + fn services(&self) -> BTreeSet { + self.services + .lock() + .unwrap() + .values() + .map(|service| service.into()) + .collect() } async fn is_connected(&self) -> Result { @@ -96,15 +117,24 @@ impl api::Peripheral for Peripheral { Ok(()) } - async fn discover_characteristics(&self) -> Result> { - 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,13 +175,9 @@ impl api::Peripheral for Peripheral { async fn notifications(&self) -> Result + 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())) }))) } } @@ -159,24 +185,35 @@ impl api::Peripheral for Peripheral { fn value_notification( event: BluetoothEvent, device_id: &DeviceId, - characteristics: Arc>>, + services: Arc>>, ) -> Option { 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, + 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 for bluez_async::WriteType { fn from(write_type: WriteType) -> Self { match write_type { @@ -201,11 +238,24 @@ impl From 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(), } } } diff --git a/src/corebluetooth/central_delegate.rs b/src/corebluetooth/central_delegate.rs index 8065541c..484be8e3 100644 --- a/src/corebluetooth/central_delegate.rs +++ b/src/corebluetooth/central_delegate.rs @@ -45,21 +45,61 @@ use uuid::Uuid; pub enum CentralDelegateEvent { DidUpdateState, - DiscoveredPeripheral(StrongPtr), - // Peripheral UUID, HashMap Service Uuid to StrongPtr - DiscoveredServices(Uuid, HashMap), - ManufacturerData(Uuid, u16, Vec), - ServiceData(Uuid, HashMap>), - Services(Uuid, Vec), + DiscoveredPeripheral { + cbperipheral: StrongPtr, + }, + DiscoveredServices { + peripheral_uuid: Uuid, + /// Service UUID to CBService + services: HashMap, + }, + ManufacturerData { + peripheral_uuid: Uuid, + manufacturer_id: u16, + data: Vec, + }, + ServiceData { + peripheral_uuid: Uuid, + service_data: HashMap>, + }, + Services { + peripheral_uuid: Uuid, + service_uuids: Vec, + }, // DiscoveredIncludedServices(Uuid, HashMap), - // Peripheral UUID, HashMap Characteristic Uuid to StrongPtr - DiscoveredCharacteristics(Uuid, HashMap), - ConnectedDevice(Uuid), - DisconnectedDevice(Uuid), - CharacteristicSubscribed(Uuid, Uuid), - CharacteristicUnsubscribed(Uuid, Uuid), - CharacteristicNotified(Uuid, Uuid, Vec), - CharacteristicWritten(Uuid, Uuid), + DiscoveredCharacteristics { + peripheral_uuid: Uuid, + service_uuid: Uuid, + /// Characteristic UUID to CBCharacteristic + characteristics: HashMap, + }, + ConnectedDevice { + peripheral_uuid: Uuid, + }, + DisconnectedDevice { + peripheral_uuid: Uuid, + }, + CharacteristicSubscribed { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + }, + CharacteristicUnsubscribed { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + }, + CharacteristicNotified { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + data: Vec, + }, + CharacteristicWritten { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + }, // TODO Deal with descriptors at some point, but not a huge worry at the moment. // DiscoveredDescriptors(String, ) } @@ -68,62 +108,106 @@ impl Debug for CentralDelegateEvent { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { CentralDelegateEvent::DidUpdateState => f.debug_tuple("DidUpdateState").finish(), - CentralDelegateEvent::DiscoveredPeripheral(p) => f - .debug_tuple("CentralDelegateEvent") - .field(p.deref()) + CentralDelegateEvent::DiscoveredPeripheral { cbperipheral } => f + .debug_struct("CentralDelegateEvent") + .field("cbperipheral", cbperipheral.deref()) .finish(), - CentralDelegateEvent::DiscoveredServices(uuid, services) => f - .debug_tuple("DiscoveredServices") - .field(uuid) - .field(&services.keys().collect::>()) + CentralDelegateEvent::DiscoveredServices { + peripheral_uuid, + services, + } => f + .debug_struct("DiscoveredServices") + .field("peripheral_uuid", peripheral_uuid) + .field("services", &services.keys().collect::>()) .finish(), - CentralDelegateEvent::DiscoveredCharacteristics(uuid, characteristics) => f - .debug_tuple("DiscoveredCharacteristics") - .field(uuid) - .field(&characteristics.keys().collect::>()) + CentralDelegateEvent::DiscoveredCharacteristics { + peripheral_uuid, + service_uuid, + characteristics, + } => f + .debug_struct("DiscoveredCharacteristics") + .field("peripheral_uuid", peripheral_uuid) + .field("service_uuid", service_uuid) + .field( + "characteristics", + &characteristics.keys().collect::>(), + ) .finish(), - CentralDelegateEvent::ConnectedDevice(uuid) => { - f.debug_tuple("ConnectedDevice").field(uuid).finish() - } - CentralDelegateEvent::DisconnectedDevice(uuid) => { - f.debug_tuple("DisconnectedDevice").field(uuid).finish() - } - CentralDelegateEvent::CharacteristicSubscribed(uuid1, uuid2) => f - .debug_tuple("CharacteristicSubscribed") - .field(uuid1) - .field(uuid2) + CentralDelegateEvent::ConnectedDevice { peripheral_uuid } => f + .debug_struct("ConnectedDevice") + .field("peripheral_uuid", peripheral_uuid) + .finish(), + CentralDelegateEvent::DisconnectedDevice { peripheral_uuid } => f + .debug_struct("DisconnectedDevice") + .field("peripheral_uuid", peripheral_uuid) .finish(), - CentralDelegateEvent::CharacteristicUnsubscribed(uuid1, uuid2) => f - .debug_tuple("CharacteristicUnsubscribed") - .field(uuid1) - .field(uuid2) + CentralDelegateEvent::CharacteristicSubscribed { + peripheral_uuid, + service_uuid, + characteristic_uuid, + } => f + .debug_struct("CharacteristicSubscribed") + .field("peripheral_uuid", peripheral_uuid) + .field("service_uuid", service_uuid) + .field("characteristic_uuid", characteristic_uuid) .finish(), - CentralDelegateEvent::CharacteristicNotified(uuid1, uuid2, vec) => f - .debug_tuple("CharacteristicNotified") - .field(uuid1) - .field(uuid2) - .field(vec) + CentralDelegateEvent::CharacteristicUnsubscribed { + peripheral_uuid, + service_uuid, + characteristic_uuid, + } => f + .debug_struct("CharacteristicUnsubscribed") + .field("peripheral_uuid", peripheral_uuid) + .field("service_uuid", service_uuid) + .field("characteristic_uuid", characteristic_uuid) .finish(), - CentralDelegateEvent::CharacteristicWritten(uuid1, uuid2) => f - .debug_tuple("CharacteristicWritten") - .field(uuid1) - .field(uuid2) + CentralDelegateEvent::CharacteristicNotified { + peripheral_uuid, + service_uuid, + characteristic_uuid, + data, + } => f + .debug_struct("CharacteristicNotified") + .field("peripheral_uuid", peripheral_uuid) + .field("service_uuid", service_uuid) + .field("characteristic_uuid", characteristic_uuid) + .field("data", data) .finish(), - CentralDelegateEvent::ManufacturerData(uuid, manufacturer_id, manufacturer_data) => f - .debug_tuple("ManufacturerData") - .field(uuid) - .field(manufacturer_id) - .field(manufacturer_data) + CentralDelegateEvent::CharacteristicWritten { + peripheral_uuid, + service_uuid, + characteristic_uuid, + } => f + .debug_struct("CharacteristicWritten") + .field("service_uuid", service_uuid) + .field("peripheral_uuid", peripheral_uuid) + .field("characteristic_uuid", characteristic_uuid) .finish(), - CentralDelegateEvent::ServiceData(uuid, service_data) => f - .debug_tuple("ServiceData") - .field(uuid) - .field(service_data) + CentralDelegateEvent::ManufacturerData { + peripheral_uuid, + manufacturer_id, + data, + } => f + .debug_struct("ManufacturerData") + .field("peripheral_uuid", peripheral_uuid) + .field("manufacturer_id", manufacturer_id) + .field("data", data) .finish(), - CentralDelegateEvent::Services(uuid, services) => f - .debug_tuple("Services") - .field(uuid) - .field(services) + CentralDelegateEvent::ServiceData { + peripheral_uuid, + service_data, + } => f + .debug_struct("ServiceData") + .field("peripheral_uuid", peripheral_uuid) + .field("service_data", service_data) + .finish(), + CentralDelegateEvent::Services { + peripheral_uuid, + service_uuids, + } => f + .debug_struct("Services") + .field("peripheral_uuid", peripheral_uuid) + .field("service_uuids", service_uuids) .finish(), } } @@ -306,8 +390,11 @@ pub mod CentralDelegate { ); cb::peripheral_setdelegate(peripheral, delegate); cb::peripheral_discoverservices(peripheral); - let uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); - send_delegate_event(delegate, CentralDelegateEvent::ConnectedDevice(uuid)); + let peripheral_uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + send_delegate_event( + delegate, + CentralDelegateEvent::ConnectedDevice { peripheral_uuid }, + ); } extern "C" fn delegate_centralmanager_diddisconnectperipheral_error( @@ -321,8 +408,11 @@ pub mod CentralDelegate { "delegate_centralmanager_diddisconnectperipheral_error {}", peripheral_debug(peripheral) ); - let uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); - send_delegate_event(delegate, CentralDelegateEvent::DisconnectedDevice(uuid)); + let peripheral_uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + send_delegate_event( + delegate, + CentralDelegateEvent::DisconnectedDevice { peripheral_uuid }, + ); } // extern fn delegate_centralmanager_didfailtoconnectperipheral_error(_delegate: &mut Object, _cmd: Sel, _central: *mut Object, _peripheral: *mut Object, _error: *mut Object) { @@ -342,16 +432,15 @@ pub mod CentralDelegate { peripheral_debug(peripheral) ); - let held_peripheral; - unsafe { - held_peripheral = StrongPtr::retain(peripheral); - } + let held_peripheral = unsafe { StrongPtr::retain(peripheral) }; send_delegate_event( delegate, - CentralDelegateEvent::DiscoveredPeripheral(held_peripheral), + CentralDelegateEvent::DiscoveredPeripheral { + cbperipheral: held_peripheral, + }, ); - let puuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + let peripheral_uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); let manufacturer_data = ns::dictionary_objectforkey(adv_data, unsafe { cb::ADVERTISEMENT_DATA_MANUFACTURER_DATA_KEY @@ -366,11 +455,11 @@ pub mod CentralDelegate { send_delegate_event( delegate, - CentralDelegateEvent::ManufacturerData( - puuid, - u16::from_le_bytes(manufacturer_id.try_into().unwrap()), - Vec::from(manufacturer_data), - ), + CentralDelegateEvent::ManufacturerData { + peripheral_uuid, + manufacturer_id: u16::from_le_bytes(manufacturer_id.try_into().unwrap()), + data: Vec::from(manufacturer_data), + }, ); } } @@ -388,7 +477,13 @@ pub mod CentralDelegate { result.insert(cbuuid_to_uuid(uuid), data); } - send_delegate_event(delegate, CentralDelegateEvent::ServiceData(puuid, result)); + send_delegate_event( + delegate, + CentralDelegateEvent::ServiceData { + peripheral_uuid, + service_data: result, + }, + ); } let services = ns::dictionary_objectforkey(adv_data, unsafe { @@ -396,14 +491,19 @@ pub mod CentralDelegate { }); if services != nil { // services: [CBUUID] - let mut result = Vec::new(); + let mut service_uuids = Vec::new(); for i in 0..ns::array_count(services) { let uuid = ns::array_objectatindex(services, i); - - result.push(cbuuid_to_uuid(uuid)); + service_uuids.push(cbuuid_to_uuid(uuid)); } - send_delegate_event(delegate, CentralDelegateEvent::Services(puuid, result)); + send_delegate_event( + delegate, + CentralDelegateEvent::Services { + peripheral_uuid, + service_uuids, + }, + ); } } @@ -443,10 +543,13 @@ pub mod CentralDelegate { } service_map.insert(uuid, held_service); } - let puuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + let peripheral_uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); send_delegate_event( delegate, - CentralDelegateEvent::DiscoveredServices(puuid, service_map), + CentralDelegateEvent::DiscoveredServices { + peripheral_uuid, + services: service_map, + }, ); } } @@ -487,7 +590,7 @@ pub mod CentralDelegate { localized_description(error) ); if error == nil { - let mut char_map = HashMap::new(); + let mut characteristics = HashMap::new(); let chars = cb::service_characteristics(service); for i in 0..ns::array_count(chars) { let c = ns::array_objectatindex(chars, i); @@ -495,16 +598,18 @@ pub mod CentralDelegate { // cb::peripheral_discoverdescriptorsforcharacteristic(peripheral, c); // Create the map entry we'll need to export. let uuid = cbuuid_to_uuid(cb::attribute_uuid(c)); - let held_char; - unsafe { - held_char = StrongPtr::retain(c); - } - char_map.insert(uuid, held_char); + let held_char = unsafe { StrongPtr::retain(c) }; + characteristics.insert(uuid, held_char); } - let puuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + let peripheral_uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + let service_uuid = cbuuid_to_uuid(cb::attribute_uuid(service)); send_delegate_event( delegate, - CentralDelegateEvent::DiscoveredCharacteristics(puuid, char_map), + CentralDelegateEvent::DiscoveredCharacteristics { + peripheral_uuid, + service_uuid, + characteristics, + }, ); } } @@ -523,12 +628,15 @@ pub mod CentralDelegate { localized_description(error) ); if error == nil { - let v = get_characteristic_value(characteristic); - let puuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); - let characteristic_uuid = cbuuid_to_uuid(cb::attribute_uuid(characteristic)); + let service = cb::characteristic_service(characteristic); send_delegate_event( delegate, - CentralDelegateEvent::CharacteristicNotified(puuid, characteristic_uuid, v), + CentralDelegateEvent::CharacteristicNotified { + peripheral_uuid: nsuuid_to_uuid(cb::peer_identifier(peripheral)), + service_uuid: cbuuid_to_uuid(cb::attribute_uuid(service)), + characteristic_uuid: cbuuid_to_uuid(cb::attribute_uuid(characteristic)), + data: get_characteristic_value(characteristic), + }, ); // Notify BluetoothGATTCharacteristic::read_value that read was successful. } @@ -548,11 +656,14 @@ pub mod CentralDelegate { localized_description(error) ); if error == nil { - let puuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); - let characteristic_uuid = cbuuid_to_uuid(cb::attribute_uuid(characteristic)); + let service = cb::characteristic_service(characteristic); send_delegate_event( delegate, - CentralDelegateEvent::CharacteristicWritten(puuid, characteristic_uuid), + CentralDelegateEvent::CharacteristicWritten { + peripheral_uuid: nsuuid_to_uuid(cb::peer_identifier(peripheral)), + service_uuid: cbuuid_to_uuid(cb::attribute_uuid(service)), + characteristic_uuid: cbuuid_to_uuid(cb::attribute_uuid(characteristic)), + }, ); } } @@ -566,17 +677,27 @@ pub mod CentralDelegate { ) { trace!("delegate_peripheral_didupdatenotificationstateforcharacteristic_error"); // TODO check for error here - let puuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + let peripheral_uuid = nsuuid_to_uuid(cb::peer_identifier(peripheral)); + let service = cb::characteristic_service(characteristic); + let service_uuid = cbuuid_to_uuid(cb::attribute_uuid(service)); let characteristic_uuid = cbuuid_to_uuid(cb::attribute_uuid(characteristic)); if cb::characteristic_isnotifying(characteristic) == objc::runtime::YES { send_delegate_event( delegate, - CentralDelegateEvent::CharacteristicSubscribed(puuid, characteristic_uuid), + CentralDelegateEvent::CharacteristicSubscribed { + peripheral_uuid, + service_uuid, + characteristic_uuid, + }, ); } else { send_delegate_event( delegate, - CentralDelegateEvent::CharacteristicUnsubscribed(puuid, characteristic_uuid), + CentralDelegateEvent::CharacteristicUnsubscribed { + peripheral_uuid, + service_uuid, + characteristic_uuid, + }, ); } } diff --git a/src/corebluetooth/framework.rs b/src/corebluetooth/framework.rs index ecb8c9a2..ad9671b3 100644 --- a/src/corebluetooth/framework.rs +++ b/src/corebluetooth/framework.rs @@ -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* */ { @@ -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 diff --git a/src/corebluetooth/internal.rs b/src/corebluetooth/internal.rs index 0a2013ef..9f45c4bf 100644 --- a/src/corebluetooth/internal.rs +++ b/src/corebluetooth/internal.rs @@ -17,7 +17,7 @@ use super::{ future::{BtlePlugFuture, BtlePlugFutureStateShared}, utils::{core_bluetooth::cbuuid_to_uuid, nsstring::nsstring_to_string, nsuuid_to_uuid}, }; -use crate::api::{CharPropFlags, Characteristic, WriteType}; +use crate::api::{CharPropFlags, Characteristic, Service, WriteType}; use crate::Error; use futures::channel::mpsc::{self, Receiver, Sender}; use futures::select; @@ -109,7 +109,7 @@ impl CBCharacteristic { #[derive(Clone, Debug)] pub enum CoreBluetoothReply { ReadResult(Vec), - Connected(BTreeSet), + Connected(BTreeSet), State(CBPeripheralState), Ok, Err(String), @@ -127,27 +127,32 @@ pub enum CBPeripheralEvent { pub type CoreBluetoothReplyStateShared = BtlePlugFutureStateShared; pub type CoreBluetoothReplyFuture = BtlePlugFuture; +struct ServiceInternal { + cbservice: StrongPtr, + characteristics: HashMap, +} + struct CBPeripheral { pub peripheral: StrongPtr, - services: HashMap, - pub characteristics: HashMap, + services: HashMap, pub event_sender: Sender, pub connected_future_state: Option, - characteristic_update_count: u32, } impl Debug for CBPeripheral { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_struct("CBPeripheral") .field("peripheral", self.peripheral.deref()) - .field("services", &self.services.keys().collect::>()) - .field("characteristics", &self.characteristics) - .field("event_sender", &self.event_sender) - .field("connected_future_state", &self.connected_future_state) .field( - "characteristic_update_count", - &self.characteristic_update_count, + "services", + &self + .services + .iter() + .map(|(service_uuid, service)| (service_uuid, service.characteristics.len())) + .collect::>(), ) + .field("event_sender", &self.event_sender) + .field("connected_future_state", &self.connected_future_state) .finish() } } @@ -157,22 +162,28 @@ impl CBPeripheral { Self { peripheral, services: HashMap::new(), - characteristics: HashMap::new(), event_sender, connected_future_state: None, - characteristic_update_count: 0, } } - pub fn set_services(&mut self, services: HashMap) { - self.services = services; - } + pub fn set_characteristics( + &mut self, + service_uuid: Uuid, + characteristics: HashMap, + ) { + let characteristics = characteristics + .into_iter() + .map(|(characteristic_uuid, characteristic)| { + (characteristic_uuid, CBCharacteristic::new(characteristic)) + }) + .collect(); + let service = self + .services + .get_mut(&service_uuid) + .expect("Got characteristics for a service we don't know about"); + service.characteristics = characteristics; - pub fn set_characteristics(&mut self, characteristics: HashMap) { - for (c_uuid, c_obj) in characteristics { - self.characteristics - .insert(c_uuid, CBCharacteristic::new(c_obj)); - } // It's time for QUESTIONABLE ASSUMPTIONS. // // For sake of being lazy, we don't want to fire device connection until @@ -181,26 +192,37 @@ impl CBPeripheral { // service map. Once that's done, we're filled out enough and can send // back a Connected reply to the waiting future with all of the // characteristic info in it. - self.characteristic_update_count += 1; - if self.characteristic_update_count == (self.services.len() as u32) { + if !self + .services + .values() + .any(|service| service.characteristics.is_empty()) + { if self.connected_future_state.is_none() { panic!("We should still have a future at this point!"); } - let mut char_set = BTreeSet::new(); - for (&uuid, c) in &self.characteristics { - let char = Characteristic { - uuid, - properties: c.properties, - }; - trace!("{:?}", char.uuid); - char_set.insert(char); - } + let services = self + .services + .iter() + .map(|(&service_uuid, service)| Service { + uuid: service_uuid, + primary: cb::service_isprimary(*service.cbservice) != objc::runtime::NO, + characteristics: service + .characteristics + .iter() + .map(|(&characteristic_uuid, characteristic)| Characteristic { + uuid: characteristic_uuid, + service_uuid, + properties: characteristic.properties, + }) + .collect(), + }) + .collect(); self.connected_future_state .take() .unwrap() .lock() .unwrap() - .set_reply(CoreBluetoothReply::Connected(char_set)); + .set_reply(CoreBluetoothReply::Connected(services)); } } } @@ -238,23 +260,44 @@ impl Debug for CoreBluetoothInternal { pub enum CoreBluetoothMessage { StartScanning, StopScanning, - ConnectDevice(Uuid, CoreBluetoothReplyStateShared), - DisconnectDevice(Uuid, CoreBluetoothReplyStateShared), - // device uuid, characteristic uuid, future - ReadValue(Uuid, Uuid, CoreBluetoothReplyStateShared), - // device uuid, characteristic uuid, data, kind, future - WriteValue( - Uuid, - Uuid, - Vec, - WriteType, - CoreBluetoothReplyStateShared, - ), - // device uuid, characteristic uuid, future - Subscribe(Uuid, Uuid, CoreBluetoothReplyStateShared), - // device uuid, characteristic uuid, future - Unsubscribe(Uuid, Uuid, CoreBluetoothReplyStateShared), - IsConnected(Uuid, CoreBluetoothReplyStateShared), + ConnectDevice { + peripheral_uuid: Uuid, + future: CoreBluetoothReplyStateShared, + }, + DisconnectDevice { + peripheral_uuid: Uuid, + future: CoreBluetoothReplyStateShared, + }, + ReadValue { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + future: CoreBluetoothReplyStateShared, + }, + WriteValue { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + data: Vec, + write_type: WriteType, + future: CoreBluetoothReplyStateShared, + }, + Subscribe { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + future: CoreBluetoothReplyStateShared, + }, + Unsubscribe { + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + future: CoreBluetoothReplyStateShared, + }, + IsConnected { + peripheral_uuid: Uuid, + future: CoreBluetoothReplyStateShared, + }, } #[derive(Debug)] @@ -388,21 +431,38 @@ impl CoreBluetoothInternal { trace!("{}", id); } if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - p.set_services(service_map); + let services = service_map + .into_iter() + .map(|(service_uuid, cbservice)| { + ( + service_uuid, + ServiceInternal { + cbservice, + characteristics: HashMap::new(), + }, + ) + }) + .collect(); + p.services = services; } } fn on_discovered_characteristics( &mut self, peripheral_uuid: Uuid, - char_map: HashMap, + service_uuid: Uuid, + characteristics: HashMap, ) { - trace!("Found chars!"); - for id in char_map.keys() { + trace!( + "Found characteristics for peripheral {} service {}:", + peripheral_uuid, + service_uuid + ); + for id in characteristics.keys() { trace!("{}", id); } if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - p.set_characteristics(char_map); + p.set_characteristics(service_uuid, characteristics); } } @@ -418,68 +478,102 @@ impl CoreBluetoothInternal { .await; } - fn on_characteristic_subscribed(&mut self, peripheral_uuid: Uuid, characteristic_uuid: Uuid) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Got subscribed event!"); - let state = c.subscribe_future_state.pop_back().unwrap(); - state.lock().unwrap().set_reply(CoreBluetoothReply::Ok); - } + /// Get the CBCharacteristic for the given characteristic of the given peripheral, if it exists. + fn get_characteristic( + &mut self, + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + ) -> Option<&mut CBCharacteristic> { + self.peripherals + .get_mut(&peripheral_uuid)? + .services + .get_mut(&service_uuid)? + .characteristics + .get_mut(&characteristic_uuid) + } + + fn on_characteristic_subscribed( + &mut self, + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + ) { + if let Some(characteristic) = + self.get_characteristic(peripheral_uuid, service_uuid, characteristic_uuid) + { + trace!("Got subscribed event!"); + let state = characteristic.subscribe_future_state.pop_back().unwrap(); + state.lock().unwrap().set_reply(CoreBluetoothReply::Ok); } } - fn on_characteristic_unsubscribed(&mut self, peripheral_uuid: Uuid, characteristic_uuid: Uuid) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Got unsubscribed event!"); - let state = c.unsubscribe_future_state.pop_back().unwrap(); - state.lock().unwrap().set_reply(CoreBluetoothReply::Ok); - } + fn on_characteristic_unsubscribed( + &mut self, + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + ) { + if let Some(characteristic) = + self.get_characteristic(peripheral_uuid, service_uuid, characteristic_uuid) + { + trace!("Got unsubscribed event!"); + let state = characteristic.unsubscribe_future_state.pop_back().unwrap(); + state.lock().unwrap().set_reply(CoreBluetoothReply::Ok); } } async fn on_characteristic_read( &mut self, peripheral_uuid: Uuid, + service_uuid: Uuid, characteristic_uuid: Uuid, data: Vec, ) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Got read event!"); - - let mut data_clone = Vec::new(); - for byte in data.iter() { - data_clone.push(*byte); - } - // Reads and notifications both return the same callback. If - // we're trying to do a read, we'll have a future we can - // fulfill. Otherwise, just treat the returned value as a - // notification and use the event system. - if !c.read_future_state.is_empty() { - let state = c.read_future_state.pop_back().unwrap(); - state - .lock() - .unwrap() - .set_reply(CoreBluetoothReply::ReadResult(data_clone)); - } else if let Err(e) = p - .event_sender - .send(CBPeripheralEvent::Notification(characteristic_uuid, data)) - .await + if let Some(peripheral) = self.peripherals.get_mut(&peripheral_uuid) { + if let Some(service) = peripheral.services.get_mut(&service_uuid) { + if let Some(characteristic) = service.characteristics.get_mut(&characteristic_uuid) { - error!("Error sending notification event: {}", e); + trace!("Got read event!"); + + let mut data_clone = Vec::new(); + for byte in data.iter() { + data_clone.push(*byte); + } + // Reads and notifications both return the same callback. If + // we're trying to do a read, we'll have a future we can + // fulfill. Otherwise, just treat the returned value as a + // notification and use the event system. + if !characteristic.read_future_state.is_empty() { + let state = characteristic.read_future_state.pop_back().unwrap(); + state + .lock() + .unwrap() + .set_reply(CoreBluetoothReply::ReadResult(data_clone)); + } else if let Err(e) = peripheral + .event_sender + .send(CBPeripheralEvent::Notification(characteristic_uuid, data)) + .await + { + error!("Error sending notification event: {}", e); + } } } } } - fn on_characteristic_written(&mut self, peripheral_uuid: Uuid, characteristic_uuid: Uuid) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Got written event!"); - let state = c.write_future_state.pop_back().unwrap(); - state.lock().unwrap().set_reply(CoreBluetoothReply::Ok); - } + fn on_characteristic_written( + &mut self, + peripheral_uuid: Uuid, + service_uuid: Uuid, + characteristic_uuid: Uuid, + ) { + if let Some(characteristic) = + self.get_characteristic(peripheral_uuid, service_uuid, characteristic_uuid) + { + trace!("Got written event!"); + let state = characteristic.write_future_state.pop_back().unwrap(); + state.lock().unwrap().set_reply(CoreBluetoothReply::Ok); } } @@ -505,29 +599,33 @@ impl CoreBluetoothInternal { fn write_value( &mut self, peripheral_uuid: Uuid, + service_uuid: Uuid, characteristic_uuid: Uuid, data: Vec, kind: WriteType, fut: CoreBluetoothReplyStateShared, ) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Writing value! With kind {:?}", kind); - cb::peripheral_writevalue_forcharacteristic( - *p.peripheral, - ns::data(data.as_ptr(), data.len() as c_uint), - *c.characteristic, - match kind { - WriteType::WithResponse => 0, - WriteType::WithoutResponse => 1, - }, - ); - // WriteWithoutResponse does not call the corebluetooth - // callback, it just always succeeds silently. - if kind == WriteType::WithoutResponse { - fut.lock().unwrap().set_reply(CoreBluetoothReply::Ok); - } else { - c.write_future_state.push_front(fut); + if let Some(peripheral) = self.peripherals.get_mut(&peripheral_uuid) { + if let Some(service) = peripheral.services.get_mut(&service_uuid) { + if let Some(characteristic) = service.characteristics.get_mut(&characteristic_uuid) + { + trace!("Writing value! With kind {:?}", kind); + cb::peripheral_writevalue_forcharacteristic( + *peripheral.peripheral, + ns::data(data.as_ptr(), data.len() as c_uint), + *characteristic.characteristic, + match kind { + WriteType::WithResponse => 0, + WriteType::WithoutResponse => 1, + }, + ); + // WriteWithoutResponse does not call the corebluetooth + // callback, it just always succeeds silently. + if kind == WriteType::WithoutResponse { + fut.lock().unwrap().set_reply(CoreBluetoothReply::Ok); + } else { + characteristic.write_future_state.push_front(fut); + } } } } @@ -536,14 +634,21 @@ impl CoreBluetoothInternal { fn read_value( &mut self, peripheral_uuid: Uuid, + service_uuid: Uuid, characteristic_uuid: Uuid, fut: CoreBluetoothReplyStateShared, ) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Reading value!"); - cb::peripheral_readvalue_forcharacteristic(*p.peripheral, *c.characteristic); - c.read_future_state.push_front(fut); + if let Some(peripheral) = self.peripherals.get_mut(&peripheral_uuid) { + if let Some(service) = peripheral.services.get_mut(&service_uuid) { + if let Some(characteristic) = service.characteristics.get_mut(&characteristic_uuid) + { + trace!("Reading value!"); + cb::peripheral_readvalue_forcharacteristic( + *peripheral.peripheral, + *characteristic.characteristic, + ); + characteristic.read_future_state.push_front(fut); + } } } } @@ -551,18 +656,22 @@ impl CoreBluetoothInternal { fn subscribe( &mut self, peripheral_uuid: Uuid, + service_uuid: Uuid, characteristic_uuid: Uuid, fut: CoreBluetoothReplyStateShared, ) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Setting subscribe!"); - cb::peripheral_setnotifyvalue_forcharacteristic( - *p.peripheral, - objc::runtime::YES, - *c.characteristic, - ); - c.subscribe_future_state.push_front(fut); + if let Some(peripheral) = self.peripherals.get_mut(&peripheral_uuid) { + if let Some(service) = peripheral.services.get_mut(&service_uuid) { + if let Some(characteristic) = service.characteristics.get_mut(&characteristic_uuid) + { + trace!("Setting subscribe!"); + cb::peripheral_setnotifyvalue_forcharacteristic( + *peripheral.peripheral, + objc::runtime::YES, + *characteristic.characteristic, + ); + characteristic.subscribe_future_state.push_front(fut); + } } } } @@ -570,18 +679,22 @@ impl CoreBluetoothInternal { fn unsubscribe( &mut self, peripheral_uuid: Uuid, + service_uuid: Uuid, characteristic_uuid: Uuid, fut: CoreBluetoothReplyStateShared, ) { - if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) { - if let Some(c) = p.characteristics.get_mut(&characteristic_uuid) { - trace!("Setting subscribe!"); - cb::peripheral_setnotifyvalue_forcharacteristic( - *p.peripheral, - objc::runtime::NO, - *c.characteristic, - ); - c.unsubscribe_future_state.push_front(fut); + if let Some(peripheral) = self.peripherals.get_mut(&peripheral_uuid) { + if let Some(service) = peripheral.services.get_mut(&service_uuid) { + if let Some(characteristic) = service.characteristics.get_mut(&characteristic_uuid) + { + trace!("Setting subscribe!"); + cb::peripheral_setnotifyvalue_forcharacteristic( + *peripheral.peripheral, + objc::runtime::NO, + *characteristic.characteristic, + ); + characteristic.unsubscribe_future_state.push_front(fut); + } } } } @@ -599,46 +712,50 @@ impl CoreBluetoothInternal { CentralDelegateEvent::DidUpdateState => { self.dispatch_event(CoreBluetoothEvent::AdapterConnected).await } - CentralDelegateEvent::DiscoveredPeripheral(peripheral) => { - self.on_discovered_peripheral(peripheral).await + CentralDelegateEvent::DiscoveredPeripheral{cbperipheral} => { + self.on_discovered_peripheral(cbperipheral).await } - CentralDelegateEvent::DiscoveredServices(peripheral_id, service_map) => { - self.on_discovered_services(peripheral_id, service_map) + CentralDelegateEvent::DiscoveredServices{peripheral_uuid, services} => { + self.on_discovered_services(peripheral_uuid, services) } - CentralDelegateEvent::DiscoveredCharacteristics(peripheral_id, char_map) => { - self.on_discovered_characteristics(peripheral_id, char_map) + CentralDelegateEvent::DiscoveredCharacteristics{peripheral_uuid, service_uuid, characteristics} => { + self.on_discovered_characteristics(peripheral_uuid, service_uuid, characteristics) } - CentralDelegateEvent::ConnectedDevice(peripheral_id) => { - self.on_peripheral_connect(peripheral_id) + CentralDelegateEvent::ConnectedDevice{peripheral_uuid} => { + self.on_peripheral_connect(peripheral_uuid) } - CentralDelegateEvent::DisconnectedDevice(peripheral_id) => { - self.on_peripheral_disconnect(peripheral_id).await + CentralDelegateEvent::DisconnectedDevice{peripheral_uuid} => { + self.on_peripheral_disconnect(peripheral_uuid).await } - CentralDelegateEvent::CharacteristicSubscribed( - peripheral_id, - characteristic_id, - ) => self.on_characteristic_subscribed(peripheral_id, characteristic_id), - CentralDelegateEvent::CharacteristicUnsubscribed( - peripheral_id, - characteristic_id, - ) => self.on_characteristic_unsubscribed(peripheral_id, characteristic_id), - CentralDelegateEvent::CharacteristicNotified( - peripheral_id, - characteristic_id, + CentralDelegateEvent::CharacteristicSubscribed{ + peripheral_uuid, + service_uuid, + characteristic_uuid, + } => self.on_characteristic_subscribed(peripheral_uuid, service_uuid, characteristic_uuid), + CentralDelegateEvent::CharacteristicUnsubscribed{ + peripheral_uuid, + service_uuid, + characteristic_uuid, + } => self.on_characteristic_unsubscribed(peripheral_uuid, service_uuid,characteristic_uuid), + CentralDelegateEvent::CharacteristicNotified{ + peripheral_uuid, + service_uuid, + characteristic_uuid, data, - ) => self.on_characteristic_read(peripheral_id, characteristic_id, data).await, - CentralDelegateEvent::CharacteristicWritten( - peripheral_id, - characteristic_id, - ) => self.on_characteristic_written(peripheral_id, characteristic_id), - CentralDelegateEvent::ManufacturerData(peripheral_id, manufacturer_id, manufacturer_data) => { - self.on_manufacturer_data(peripheral_id, manufacturer_id, manufacturer_data).await + } => self.on_characteristic_read(peripheral_uuid, service_uuid,characteristic_uuid, data).await, + CentralDelegateEvent::CharacteristicWritten{ + peripheral_uuid, + service_uuid, + characteristic_uuid, + } => self.on_characteristic_written(peripheral_uuid, service_uuid, characteristic_uuid), + CentralDelegateEvent::ManufacturerData{peripheral_uuid, manufacturer_id, data} => { + self.on_manufacturer_data(peripheral_uuid, manufacturer_id, data).await }, - CentralDelegateEvent::ServiceData(peripheral_id, service_data) => { - self.on_service_data(peripheral_id, service_data).await + CentralDelegateEvent::ServiceData{peripheral_uuid, service_data} => { + self.on_service_data(peripheral_uuid, service_data).await }, - CentralDelegateEvent::Services(peripheral_id, services) => { - self.on_services(peripheral_id, services).await + CentralDelegateEvent::Services{peripheral_uuid, service_uuids} => { + self.on_services(peripheral_uuid, service_uuids).await }, }; } @@ -647,29 +764,29 @@ impl CoreBluetoothInternal { match adapter_msg { CoreBluetoothMessage::StartScanning => self.start_discovery(), CoreBluetoothMessage::StopScanning => self.stop_discovery(), - CoreBluetoothMessage::ConnectDevice(peripheral_uuid, fut) => { + CoreBluetoothMessage::ConnectDevice{peripheral_uuid, future} => { trace!("got connectdevice msg!"); - self.connect_peripheral(peripheral_uuid, fut); + self.connect_peripheral(peripheral_uuid, future); } - CoreBluetoothMessage::DisconnectDevice(_peripheral_uuid, _fut) => {} - CoreBluetoothMessage::ReadValue(peripheral_uuid, char_uuid, fut) => { - self.read_value(peripheral_uuid, char_uuid, fut) + CoreBluetoothMessage::DisconnectDevice{peripheral_uuid:_, future:_} => {} + CoreBluetoothMessage::ReadValue{peripheral_uuid, service_uuid,characteristic_uuid, future} => { + self.read_value(peripheral_uuid, service_uuid,characteristic_uuid, future) } - CoreBluetoothMessage::WriteValue( - peripheral_uuid, - char_uuid, + CoreBluetoothMessage::WriteValue{ + peripheral_uuid,service_uuid, + characteristic_uuid, data, - kind, - fut, - ) => self.write_value(peripheral_uuid, char_uuid, data, kind, fut), - CoreBluetoothMessage::Subscribe(peripheral_uuid, char_uuid, fut) => { - self.subscribe(peripheral_uuid, char_uuid, fut) + write_type, + future, + } => self.write_value(peripheral_uuid, service_uuid,characteristic_uuid, data, write_type, future), + CoreBluetoothMessage::Subscribe{peripheral_uuid, service_uuid,characteristic_uuid, future} => { + self.subscribe(peripheral_uuid, service_uuid,characteristic_uuid, future) } - CoreBluetoothMessage::Unsubscribe(peripheral_uuid, char_uuid, fut) => { - self.unsubscribe(peripheral_uuid, char_uuid, fut) + CoreBluetoothMessage::Unsubscribe{peripheral_uuid, service_uuid,characteristic_uuid, future} => { + self.unsubscribe(peripheral_uuid, service_uuid,characteristic_uuid, future) } - CoreBluetoothMessage::IsConnected(peripheral_uuid, fut) => { - self.is_connected(peripheral_uuid, fut); + CoreBluetoothMessage::IsConnected{peripheral_uuid, future} => { + self.is_connected(peripheral_uuid, future); } }; } diff --git a/src/corebluetooth/peripheral.rs b/src/corebluetooth/peripheral.rs index e4bc25a0..97c2f2d0 100644 --- a/src/corebluetooth/peripheral.rs +++ b/src/corebluetooth/peripheral.rs @@ -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, manager: AdapterManager, uuid: Uuid, - characteristics: Mutex>, + services: Mutex>, properties: Mutex, message_sender: Sender, // 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,8 +179,8 @@ impl api::Peripheral for Peripheral { Ok(Some(self.shared.properties.lock().unwrap().clone())) } - fn characteristics(&self) -> BTreeSet { - self.shared.characteristics.lock().unwrap().clone() + fn services(&self) -> BTreeSet { + self.shared.services.lock().unwrap().clone() } async fn is_connected(&self) -> Result { @@ -188,10 +188,10 @@ impl api::Peripheral for Peripheral { 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> { - 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 => {} diff --git a/src/lib.rs b/src/lib.rs index 9f7cac88..6c7b4d61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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(); diff --git a/src/winrtble/ble/characteristic.rs b/src/winrtble/ble/characteristic.rs index 0ffda583..e23ae2d6 100644 --- a/src/winrtble/ble/characteristic.rs +++ b/src/winrtble/ble/characteristic.rs @@ -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) + 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, + } } } diff --git a/src/winrtble/ble/device.rs b/src/winrtble/ble/device.rs index 2b417705..cb09a335 100644 --- a/src/winrtble/ble/device.rs +++ b/src/winrtble/ble/device.rs @@ -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; @@ -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, windows::Error> { let async_result = service.GetCharacteristicsAsync()?.await?; @@ -87,12 +86,11 @@ impl BLEDevice { } } - pub async fn discover_characteristics(&self) -> Result> { + pub async fn discover_services(&self) -> Result> { 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()) } diff --git a/src/winrtble/ble/mod.rs b/src/winrtble/ble/mod.rs index 0f20ac1b..c1e3656d 100644 --- a/src/winrtble/ble/mod.rs +++ b/src/winrtble/ble/mod.rs @@ -13,4 +13,5 @@ pub mod characteristic; pub mod device; +pub mod service; pub mod watcher; diff --git a/src/winrtble/ble/service.rs b/src/winrtble/ble/service.rs new file mode 100644 index 00000000..c38b4053 --- /dev/null +++ b/src/winrtble/ble/service.rs @@ -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, +} + +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, + } + } +} diff --git a/src/winrtble/peripheral.rs b/src/winrtble/peripheral.rs index 2590ae17..44cc9e57 100644 --- a/src/winrtble/peripheral.rs +++ b/src/winrtble/peripheral.rs @@ -13,13 +13,13 @@ 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, @@ -27,6 +27,7 @@ use crate::{ 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, address: BDAddr, connected: AtomicBool, - ble_characteristics: DashMap, + ble_services: DashMap, notifications_channel: broadcast::Sender, // 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 { + fn services(&self) -> BTreeSet { 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> { + 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> { - 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 + Send>>> {