use objc2::rc::Retained;
use objc2_core_bluetooth::{CBPeripheralState, CBService, CBUUID};
use objc2_foundation::{NSArray, NSData};
use super::delegates::{PeripheralDelegate, PeripheralEvent};
use super::dispatch::Dispatched;
use crate::error::ErrorKind;
use crate::{BluetoothUuidExt, Characteristic, Error, Result, Service, Uuid};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceImpl {
pub(super) inner: Dispatched<CBService>,
delegate: Retained<PeripheralDelegate>,
}
impl Service {
pub(super) fn new(service: Retained<CBService>, delegate: Retained<PeripheralDelegate>) -> Self {
Service(ServiceImpl {
inner: unsafe { Dispatched::new(service) },
delegate,
})
}
}
impl ServiceImpl {
pub fn uuid(&self) -> Uuid {
self.inner
.dispatch(|service| unsafe { Uuid::from_bluetooth_bytes(service.UUID().data().as_bytes_unchecked()) })
}
pub async fn uuid_async(&self) -> Result<Uuid> {
Ok(self.uuid())
}
pub async fn is_primary(&self) -> Result<bool> {
self.inner.dispatch(|service| unsafe { Ok(service.isPrimary()) })
}
pub async fn discover_characteristics(&self) -> Result<Vec<Characteristic>> {
self.discover_characteristics_inner(None).await
}
pub async fn discover_characteristics_with_uuid(&self, uuid: Uuid) -> Result<Vec<Characteristic>> {
let characteristics = self.discover_characteristics_inner(Some(uuid)).await?;
Ok(characteristics.into_iter().filter(|x| x.uuid() == uuid).collect())
}
async fn discover_characteristics_inner(&self, uuid: Option<Uuid>) -> Result<Vec<Characteristic>> {
let mut receiver = self.delegate.sender().new_receiver();
self.inner.dispatch(|service| {
let uuids = uuid.map(|uuid| unsafe {
NSArray::from_retained_slice(&[CBUUID::UUIDWithData(&NSData::with_bytes(uuid.as_bluetooth_bytes()))])
});
let peripheral = unsafe {
service
.peripheral()
.ok_or(Error::new(ErrorKind::NotFound, None, "peripheral not found"))?
};
if unsafe { peripheral.state() } != CBPeripheralState::Connected {
return Err(Error::from(ErrorKind::NotConnected));
}
unsafe { peripheral.discoverCharacteristics_forService(uuids.as_deref(), service) };
Ok(())
})?;
loop {
match receiver.recv().await.map_err(Error::from_recv_error)? {
PeripheralEvent::DiscoveredCharacteristics { service, error } if service == self.inner => match error {
Some(err) => return Err(Error::from_nserror(err)),
None => break,
},
PeripheralEvent::Disconnected { error } => {
return Err(Error::from_kind_and_nserror(ErrorKind::NotConnected, error));
}
PeripheralEvent::ServicesChanged { invalidated_services }
if invalidated_services.contains(&self.inner) =>
{
return Err(ErrorKind::ServiceChanged.into());
}
_ => (),
}
}
self.characteristics_inner()
}
pub async fn characteristics(&self) -> Result<Vec<Characteristic>> {
match self.characteristics_inner() {
Ok(characteristics) => Ok(characteristics),
Err(_) => self.discover_characteristics().await,
}
}
fn characteristics_inner(&self) -> Result<Vec<Characteristic>> {
self.inner.dispatch(|service| {
unsafe { service.characteristics() }
.map(|s| {
s.iter()
.map(|x| Characteristic::new(x, self.delegate.clone()))
.collect()
})
.ok_or_else(|| Error::new(ErrorKind::NotReady, None, "no characteristics have been discovered"))
})
}
pub async fn discover_included_services(&self) -> Result<Vec<Service>> {
self.discover_included_services_inner(None).await
}
pub async fn discover_included_services_with_uuid(&self, uuid: Uuid) -> Result<Vec<Service>> {
let services = self.discover_included_services_inner(Some(uuid)).await?;
Ok(services.into_iter().filter(|x| x.uuid() == uuid).collect())
}
async fn discover_included_services_inner(&self, uuid: Option<Uuid>) -> Result<Vec<Service>> {
let mut receiver = self.delegate.sender().new_receiver();
self.inner.dispatch(|service| {
let uuids = uuid.map(|uuid| unsafe {
NSArray::from_retained_slice(&[CBUUID::UUIDWithData(&NSData::with_bytes(uuid.as_bluetooth_bytes()))])
});
let peripheral = unsafe {
service
.peripheral()
.ok_or(Error::new(ErrorKind::NotFound, None, "peripheral not found"))?
};
if unsafe { peripheral.state() } != CBPeripheralState::Connected {
return Err(Error::from(ErrorKind::NotConnected));
}
unsafe { peripheral.discoverIncludedServices_forService(uuids.as_deref(), service) };
Ok(())
})?;
loop {
match receiver.recv().await.map_err(Error::from_recv_error)? {
PeripheralEvent::DiscoveredIncludedServices { service, error } if service == self.inner => {
match error {
Some(err) => return Err(Error::from_nserror(err)),
None => break,
}
}
PeripheralEvent::Disconnected { error } => {
return Err(Error::from_kind_and_nserror(ErrorKind::NotConnected, error));
}
PeripheralEvent::ServicesChanged { invalidated_services }
if invalidated_services.contains(&self.inner) =>
{
return Err(ErrorKind::ServiceChanged.into());
}
_ => (),
}
}
self.included_services_inner()
}
pub async fn included_services(&self) -> Result<Vec<Service>> {
match self.included_services_inner() {
Ok(services) => Ok(services),
Err(_) => self.discover_included_services().await,
}
}
fn included_services_inner(&self) -> Result<Vec<Service>> {
self.inner.dispatch(|service| {
unsafe { service.includedServices() }
.map(|s| s.iter().map(|x| Service::new(x, self.delegate.clone())).collect())
.ok_or_else(|| Error::new(ErrorKind::NotReady, None, "no included services have been discovered"))
})
}
}