#![allow(clippy::let_unit_value)]
use futures_core::Stream;
use futures_lite::StreamExt;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_core_bluetooth::{CBPeripheral, CBPeripheralState, CBService, CBUUID};
use objc2_foundation::{NSArray, NSData};
use super::delegates::{PeripheralDelegate, PeripheralEvent};
use super::dispatch::Dispatched;
#[cfg(feature = "l2cap")]
use super::l2cap_channel::{L2capChannelReader, L2capChannelWriter};
use crate::device::ServicesChanged;
use crate::error::ErrorKind;
use crate::pairing::PairingAgent;
use crate::{BluetoothUuidExt, Device, DeviceId, Error, Result, Service, Uuid};
#[derive(Clone)]
pub struct DeviceImpl {
pub(super) peripheral: Dispatched<CBPeripheral>,
delegate: Retained<PeripheralDelegate>,
}
impl PartialEq for DeviceImpl {
fn eq(&self, other: &Self) -> bool {
self.peripheral == other.peripheral
}
}
impl Eq for DeviceImpl {}
impl std::hash::Hash for DeviceImpl {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.peripheral.hash(state)
}
}
impl std::fmt::Debug for DeviceImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Device").field(&self.peripheral).finish()
}
}
impl std::fmt::Display for DeviceImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name().as_deref().unwrap_or("(Unknown)"))
}
}
impl Device {
pub(super) fn new(peripheral: Retained<CBPeripheral>) -> Self {
let delegate = unsafe { peripheral.delegate() }.unwrap_or_else(|| {
let delegate = ProtocolObject::from_retained(PeripheralDelegate::new());
unsafe { peripheral.setDelegate(Some(&delegate)) }
delegate
});
let delegate = delegate.downcast().unwrap();
Device(DeviceImpl {
peripheral: unsafe { Dispatched::new(peripheral) },
delegate,
})
}
}
impl DeviceImpl {
pub fn id(&self) -> DeviceId {
let uuid = self
.peripheral
.dispatch(|peripheral| unsafe { Uuid::from_bluetooth_bytes(&peripheral.identifier().as_bytes()[..]) });
super::DeviceId(uuid)
}
pub fn name(&self) -> Result<String> {
self.peripheral
.dispatch(|peripheral| match unsafe { peripheral.name() } {
Some(name) => Ok(name.to_string()),
None => Err(ErrorKind::NotFound.into()),
})
}
pub async fn name_async(&self) -> Result<String> {
self.name()
}
pub async fn is_connected(&self) -> bool {
self.peripheral.dispatch(|peripheral| unsafe { peripheral.state() }) == CBPeripheralState::Connected
}
pub async fn is_paired(&self) -> Result<bool> {
Err(ErrorKind::NotSupported.into())
}
pub async fn pair(&self) -> Result<()> {
Ok(())
}
pub async fn pair_with_agent<T: PairingAgent>(&self, _agent: &T) -> Result<()> {
Ok(())
}
pub async fn unpair(&self) -> Result<()> {
Err(ErrorKind::NotSupported.into())
}
pub async fn discover_services(&self) -> Result<Vec<Service>> {
self.discover_services_inner(None).await
}
pub async fn discover_services_with_uuid(&self, uuid: Uuid) -> Result<Vec<Service>> {
let services = self.discover_services_inner(Some(uuid)).await?;
Ok(services.into_iter().filter(|x| x.uuid() == uuid).collect())
}
async fn discover_services_inner(&self, uuid: Option<Uuid>) -> Result<Vec<Service>> {
let mut receiver = self.delegate.sender().new_receiver();
if !self.is_connected().await {
return Err(ErrorKind::NotConnected.into());
}
self.peripheral.dispatch(|peripheral| {
let uuids = uuid.map(|uuid| unsafe {
NSArray::from_retained_slice(&[CBUUID::UUIDWithData(&NSData::with_bytes(uuid.as_bluetooth_bytes()))])
});
unsafe { peripheral.discoverServices(uuids.as_deref()) };
});
loop {
match receiver.recv().await.map_err(Error::from_recv_error)? {
PeripheralEvent::DiscoveredServices { error: None } => break,
PeripheralEvent::DiscoveredServices { error: Some(err) } => {
return Err(Error::from_nserror(err));
}
PeripheralEvent::Disconnected { error } => {
return Err(Error::from_kind_and_nserror(ErrorKind::NotConnected, error));
}
_ => (),
}
}
self.services_inner()
}
pub async fn services(&self) -> Result<Vec<Service>> {
match self.services_inner() {
Ok(services) => Ok(services),
Err(_) => self.discover_services().await,
}
}
fn services_inner(&self) -> Result<Vec<Service>> {
self.peripheral.dispatch(|peripheral| {
unsafe { peripheral.services() }
.map(|s| s.iter().map(|x| Service::new(x, self.delegate.clone())).collect())
.ok_or_else(|| Error::new(ErrorKind::NotReady, None, "no services have been discovered"))
})
}
pub async fn service_changed_indications(
&self,
) -> Result<impl Stream<Item = Result<ServicesChanged>> + Send + Unpin + '_> {
let receiver = self.delegate.sender().new_receiver();
if !self.is_connected().await {
return Err(ErrorKind::NotConnected.into());
}
Ok(receiver.filter_map(|ev| match ev {
PeripheralEvent::ServicesChanged { invalidated_services } => {
Some(Ok(ServicesChanged(ServicesChangedImpl(invalidated_services))))
}
PeripheralEvent::Disconnected { error } => {
Some(Err(Error::from_kind_and_nserror(ErrorKind::NotConnected, error)))
}
_ => None,
}))
}
pub async fn rssi(&self) -> Result<i16> {
let mut receiver = self.delegate.sender().new_receiver();
self.peripheral.dispatch(|peripheral| unsafe { peripheral.readRSSI() });
loop {
match receiver.recv().await {
Ok(PeripheralEvent::ReadRssi { rssi, error: None }) => return Ok(rssi),
Ok(PeripheralEvent::ReadRssi { error: Some(err), .. }) => return Err(Error::from_nserror(err)),
Err(err) => return Err(Error::from_recv_error(err)),
_ => (),
}
}
}
#[cfg(feature = "l2cap")]
pub async fn open_l2cap_channel(
&self,
psm: u16,
_secure: bool,
) -> Result<(L2capChannelReader, L2capChannelWriter)> {
use tracing::{debug, info};
let mut receiver = self.delegate.sender().new_receiver();
if !self.is_connected().await {
return Err(ErrorKind::NotConnected.into());
}
info!("starting open_l2cap_channel on {}", psm);
self.peripheral
.dispatch(|peripheral| unsafe { peripheral.openL2CAPChannel(psm) });
let l2capchannel;
loop {
match receiver.recv().await.map_err(Error::from_recv_error)? {
PeripheralEvent::L2CAPChannelOpened { channel, error: None } => {
l2capchannel = channel;
break;
}
PeripheralEvent::L2CAPChannelOpened { channel: _, error } => {
return Err(Error::from_nserror(error.unwrap()));
}
PeripheralEvent::Disconnected { error } => {
return Err(Error::from_kind_and_nserror(ErrorKind::NotConnected, error));
}
o => {
info!("Other event: {:?}", o);
}
}
}
debug!("open_l2cap_channel success {:?}", self.peripheral);
let reader = L2capChannelReader::new(l2capchannel.clone());
let writer = L2capChannelWriter::new(l2capchannel);
Ok((reader, writer))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServicesChangedImpl(Vec<Dispatched<CBService>>);
impl ServicesChangedImpl {
pub fn was_invalidated(&self, service: &Service) -> bool {
self.0.contains(&service.0.inner)
}
}