pub mod backend;
pub mod types;
pub use types::{
AdvertisingConfig, PeripheralConfig, PeripheralRequest, PeripheralStateEvent, ReadResponder,
WriteResponder,
};
use crate::error::{BlewError, BlewResult};
use crate::gatt::service::GattService;
use crate::l2cap::{L2capChannel, types::Psm};
use crate::platform::PlatformPeripheral;
use crate::types::DeviceId;
use crate::util::event_stream::EventStream;
use backend::PeripheralBackend;
use uuid::Uuid;
impl Peripheral {
pub async fn with_config(config: PeripheralConfig) -> BlewResult<Self> {
let backend = PlatformPeripheral::with_config(config).await?;
Ok(Self { backend })
}
}
pub struct Peripheral<B: PeripheralBackend = PlatformPeripheral> {
pub(crate) backend: B,
}
impl<B: PeripheralBackend> Peripheral<B> {
pub async fn new() -> BlewResult<Self> {
Ok(Self {
backend: B::new().await?,
})
}
pub async fn is_powered(&self) -> BlewResult<bool> {
self.backend.is_powered().await
}
pub async fn add_service(&self, service: &GattService) -> BlewResult<()> {
self.backend.add_service(service).await
}
pub async fn start_advertising(&self, config: &AdvertisingConfig) -> BlewResult<()> {
self.backend.start_advertising(config).await
}
pub async fn stop_advertising(&self) -> BlewResult<()> {
self.backend.stop_advertising().await
}
pub async fn notify_characteristic(
&self,
device_id: &DeviceId,
char_uuid: Uuid,
value: Vec<u8>,
) -> BlewResult<()> {
self.backend
.notify_characteristic(device_id, char_uuid, value)
.await
}
pub async fn l2cap_listener(
&self,
) -> BlewResult<(
Psm,
impl futures_core::Stream<Item = BlewResult<(DeviceId, L2capChannel)>> + Send + 'static,
)> {
self.backend.l2cap_listener().await
}
pub fn state_events(&self) -> EventStream<PeripheralStateEvent, B::StateEvents> {
EventStream::new(self.backend.state_events())
}
pub fn take_requests(&self) -> Option<EventStream<PeripheralRequest, B::Requests>> {
self.backend.take_requests().map(EventStream::new)
}
pub async fn wait_ready(&self, timeout: std::time::Duration) -> BlewResult<()> {
let mut events = self.state_events();
if self.backend.is_powered().await.unwrap_or(false) {
return Ok(());
}
let deadline = tokio::time::Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err(BlewError::Timeout);
}
match tokio::time::timeout(remaining, tokio_stream::StreamExt::next(&mut events)).await
{
Err(_) => return Err(BlewError::Timeout),
Ok(None) => return Err(BlewError::StreamClosed),
Ok(Some(PeripheralStateEvent::AdapterStateChanged { powered: true })) => {
return Ok(());
}
Ok(Some(_)) => {}
}
}
}
}
#[cfg(target_vendor = "apple")]
impl Peripheral {
#[must_use]
pub fn take_restored(&self) -> Option<Vec<Uuid>> {
self.backend.take_restored()
}
}
#[cfg(all(test, feature = "testing"))]
mod take_requests_tests {
#[tokio::test]
async fn second_take_returns_none() {
let p = crate::testing::MockPeripheral::new_powered();
assert!(p.take_requests().is_some());
assert!(p.take_requests().is_none());
}
}