Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ pub enum CentralEvent {
DeviceUpdated(PeripheralId),
DeviceConnected(PeripheralId),
DeviceDisconnected(PeripheralId),
/// Only emitted on the corebluetooth subsystem
DeviceServicesModified(PeripheralId),
/// Emitted when a Manufacturer Data advertisement has been received from a device
ManufacturerDataAdvertisement {
id: PeripheralId,
Expand Down
28 changes: 27 additions & 1 deletion src/corebluetooth/central_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub enum CentralDelegateEvent {
service_uuids: Vec<Uuid>,
rssi: i16,
},
ServicesModified {
peripheral_uuid: Uuid,
},
// DiscoveredIncludedServices(Uuid, HashMap<Uuid, Retained<CBService>>),
DiscoveredCharacteristics {
peripheral_uuid: Uuid,
Expand Down Expand Up @@ -265,6 +268,10 @@ impl Debug for CentralDelegateEvent {
.field("service_uuids", service_uuids)
.field("rssi", rssi)
.finish(),
CentralDelegateEvent::ServicesModified { peripheral_uuid } => f
.debug_struct("ServicesModified")
.field("peripheral_uuid", peripheral_uuid)
.finish(),
CentralDelegateEvent::DescriptorNotified {
peripheral_uuid,
service_uuid,
Expand Down Expand Up @@ -335,7 +342,6 @@ declare_class!(
peripheral_debug(peripheral)
);
unsafe { peripheral.setDelegate(Some(ProtocolObject::from_ref(self))) };
unsafe { peripheral.discoverServices(None) }
let peripheral_uuid = nsuuid_to_uuid(unsafe { &peripheral.identifier() });
self.send_event(CentralDelegateEvent::ConnectedDevice { peripheral_uuid });
}
Expand Down Expand Up @@ -721,6 +727,26 @@ declare_class!(
});
}
}

#[method(peripheral:didModifyServices:)]
fn delegate_peripheral_didmodifyservices(
&self,
peripheral: &CBPeripheral,
_invalidated_services: &NSArray<CBService>,
) {
trace!(
"delegate_peripheral_didmodifyservices {}",
peripheral_debug(peripheral),
);
// This is a corebluetooth-only event that makes peripheral services unusable until discovery has been performed again.
// https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate/peripheral(_:didmodifyservices:)?language=objc
// Trigger the removal of internal corebluetooth peripheral discovered services. It is also expected that
// discover_services() will be performed again on the peripheral at the API level as soon as is practical.
// NOTE: the list of modified services does not appear to be particularly useful; a full service rediscovery is needed.
self.send_event(CentralDelegateEvent::ServicesModified {
peripheral_uuid: nsuuid_to_uuid(unsafe { &peripheral.identifier() }),
});
}
}
);

Expand Down
71 changes: 63 additions & 8 deletions src/corebluetooth/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ impl CharacteristicInternal {
pub enum CoreBluetoothReply {
AdapterState(CBManagerState),
ReadResult(Vec<u8>),
Connected(BTreeSet<Service>),
Connected,
ServicesDiscovered(BTreeSet<Service>),
State(CBPeripheralState),
Ok,
Err(String),
Expand All @@ -161,6 +162,7 @@ pub enum PeripheralEventInternal {
ManufacturerData(u16, Vec<u8>, i16),
ServiceData(HashMap<Uuid, Vec<u8>>, i16),
Services(Vec<Uuid>, i16),
ServicesModified,
}

pub type CoreBluetoothReplyStateShared = BtlePlugFutureStateShared<CoreBluetoothReply>;
Expand All @@ -178,6 +180,7 @@ struct PeripheralInternal {
pub event_sender: Sender<PeripheralEventInternal>,
pub disconnected_future_state: Option<CoreBluetoothReplyStateShared>,
pub connected_future_state: Option<CoreBluetoothReplyStateShared>,
pub services_discovered_future_state: Option<CoreBluetoothReplyStateShared>,
}

impl Debug for PeripheralInternal {
Expand All @@ -194,6 +197,10 @@ impl Debug for PeripheralInternal {
)
.field("event_sender", &self.event_sender)
.field("connected_future_state", &self.connected_future_state)
.field(
"services_discovered_future_state",
&self.services_discovered_future_state,
)
.finish()
}
}
Expand All @@ -209,6 +216,7 @@ impl PeripheralInternal {
event_sender,
connected_future_state: None,
disconnected_future_state: None,
services_discovered_future_state: None,
}
}

Expand Down Expand Up @@ -285,7 +293,7 @@ impl PeripheralInternal {
// back a Connected reply to the waiting future with all of the
// characteristic info in it.
if !self.services.values().any(|service| !service.discovered) {
if self.connected_future_state.is_none() {
if self.services_discovered_future_state.is_none() {
panic!("We should still have a future at this point!");
}
let services = self
Expand Down Expand Up @@ -317,12 +325,12 @@ impl PeripheralInternal {
.collect(),
})
.collect();
self.connected_future_state
self.services_discovered_future_state
.take()
.unwrap()
.lock()
.unwrap()
.set_reply(CoreBluetoothReply::Connected(services));
.set_reply(CoreBluetoothReply::ServicesDiscovered(services));
}
}

Expand Down Expand Up @@ -452,6 +460,10 @@ pub enum CoreBluetoothMessage {
data: Vec<u8>,
future: CoreBluetoothReplyStateShared,
},
DiscoverServices {
peripheral_uuid: Uuid,
future: CoreBluetoothReplyStateShared,
},
}

#[derive(Debug)]
Expand Down Expand Up @@ -566,6 +578,23 @@ impl CoreBluetoothInternal {
}
}

async fn on_services_modified(&mut self, peripheral_uuid: Uuid) {
trace!(
"Peripheral modified services and must be rediscovered! {:?}",
peripheral_uuid
);
if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) {
p.services.clear();
if let Err(e) = p
.event_sender
.send(PeripheralEventInternal::ServicesModified)
.await
{
error!("Error sending notification event: {}", e);
}
}
}

async fn on_discovered_peripheral(
&mut self,
peripheral: Retained<CBPeripheral>,
Expand Down Expand Up @@ -673,9 +702,20 @@ impl CoreBluetoothInternal {
}
}

fn on_peripheral_connect(&mut self, _peripheral_uuid: Uuid) {
// Don't actually do anything here. The peripheral will fire the future
// itself when it receives all of its service/characteristic info.
fn on_peripheral_connect(&mut self, peripheral_uuid: Uuid) {
if self.peripherals.contains_key(&peripheral_uuid) {
let peripheral = self
.peripherals
.get_mut(&peripheral_uuid)
.expect("If we're here we should have an ID");
peripheral
.connected_future_state
.take()
.unwrap()
.lock()
.unwrap()
.set_reply(CoreBluetoothReply::Connected);
}
}

fn on_peripheral_connection_failed(
Expand Down Expand Up @@ -1085,6 +1125,15 @@ impl CoreBluetoothInternal {
}
}

fn discover_services(&mut self, peripheral_uuid: Uuid, fut: CoreBluetoothReplyStateShared) {
if let Some(p) = self.peripherals.get_mut(&peripheral_uuid) {
trace!("Discovering services!");
p.services_discovered_future_state = Some(fut);
// This will trigger the delegate_peripheral_diddiscoverservices in central_delegate.rs
unsafe { p.peripheral.discoverServices(None) };
}
}

async fn wait_for_message(&mut self) {
select! {
delegate_msg = self.delegate_receiver.select_next_some() => {
Expand All @@ -1108,7 +1157,7 @@ impl CoreBluetoothInternal {
self.on_discovered_characteristic_descriptors(peripheral_uuid, service_uuid, characteristic_uuid, descriptors)
}
CentralDelegateEvent::ConnectedDevice{peripheral_uuid} => {
self.on_peripheral_connect(peripheral_uuid)
self.on_peripheral_connect(peripheral_uuid)
},
CentralDelegateEvent::ConnectionFailed{peripheral_uuid, error_description} => {
self.on_peripheral_connection_failed(peripheral_uuid, error_description)
Expand Down Expand Up @@ -1146,6 +1195,9 @@ impl CoreBluetoothInternal {
CentralDelegateEvent::Services{peripheral_uuid, service_uuids, rssi} => {
self.on_services(peripheral_uuid, service_uuids, rssi).await
},
CentralDelegateEvent::ServicesModified{peripheral_uuid} => {
self.on_services_modified(peripheral_uuid).await
},
CentralDelegateEvent::DescriptorNotified{
peripheral_uuid,
service_uuid,
Expand Down Expand Up @@ -1205,6 +1257,9 @@ impl CoreBluetoothInternal {
data,
future,
} => self.write_descriptor_value(peripheral_uuid, service_uuid, characteristic_uuid, descriptor_uuid, data, future),
CoreBluetoothMessage::DiscoverServices{peripheral_uuid, future} => {
self.discover_services(peripheral_uuid, future);
}
};
}
}
Expand Down
26 changes: 22 additions & 4 deletions src/corebluetooth/peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ impl Peripheral {
services,
});
}
Some(PeripheralEventInternal::ServicesModified) => {
shared.services.lock().unwrap().clear();
shared.emit_event(CentralEvent::DeviceServicesModified(shared.uuid.into()));
}
Some(PeripheralEventInternal::Disconnected) => (),
None => {
info!("Event receiver died, breaking out of corebluetooth device loop.");
Expand Down Expand Up @@ -251,8 +255,7 @@ impl api::Peripheral for Peripheral {
})
.await?;
match fut.await {
CoreBluetoothReply::Connected(services) => {
*(self.shared.services.lock().map_err(Into::<Error>::into)?) = services;
CoreBluetoothReply::Connected => {
self.shared
.emit_event(CentralEvent::DeviceConnected(self.shared.uuid.into()));
}
Expand Down Expand Up @@ -285,8 +288,23 @@ impl api::Peripheral for Peripheral {
}

async fn discover_services(&self) -> Result<()> {
// TODO: Actually discover on this, rather than on connection
Ok(())
let fut = CoreBluetoothReplyFuture::default();
self.shared
.message_sender
.to_owned()
.send(CoreBluetoothMessage::DiscoverServices {
peripheral_uuid: self.shared.uuid,
future: fut.get_state_clone(),
})
.await?;
match fut.await {
CoreBluetoothReply::ServicesDiscovered(services) => {
*(self.shared.services.lock().unwrap()) = services.clone();
return Ok(());
}
CoreBluetoothReply::Err(msg) => return Err(Error::RuntimeError(msg)),
_ => panic!("Shouldn't get anything but discovered or err!"),
}
}

async fn write(
Expand Down
Loading