pub mod backend;
pub mod types;
pub use types::{CentralConfig, CentralEvent, DisconnectCause, ScanFilter, ScanMode, WriteType};
use crate::error::{BlewError, BlewResult};
use crate::gatt::service::GattService;
use crate::l2cap::{L2capChannel, types::Psm};
use crate::platform::PlatformCentral;
use crate::types::{BleDevice, DeviceId};
use crate::util::event_stream::EventStream;
use backend::CentralBackend;
use uuid::Uuid;
pub struct Central<B: CentralBackend = PlatformCentral> {
pub(crate) backend: B,
}
impl Central {
pub async fn with_config(config: CentralConfig) -> BlewResult<Self> {
let backend = PlatformCentral::with_config(config).await?;
Ok(Self { backend })
}
#[cfg(target_os = "android")]
pub async fn refresh(&self, device_id: &DeviceId) -> BlewResult<()> {
self.backend.refresh(device_id).await
}
}
impl<B: CentralBackend> Central<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 start_scan(&self, filter: ScanFilter) -> BlewResult<()> {
self.backend.start_scan(filter).await
}
pub async fn stop_scan(&self) -> BlewResult<()> {
self.backend.stop_scan().await
}
pub async fn discovered_devices(&self) -> BlewResult<Vec<BleDevice>> {
self.backend.discovered_devices().await
}
pub async fn connect(&self, device_id: &DeviceId) -> BlewResult<()> {
self.backend.connect(device_id).await
}
pub async fn disconnect(&self, device_id: &DeviceId) -> BlewResult<()> {
self.backend.disconnect(device_id).await
}
pub async fn discover_services(&self, device_id: &DeviceId) -> BlewResult<Vec<GattService>> {
self.backend.discover_services(device_id).await
}
pub async fn read_characteristic(
&self,
device_id: &DeviceId,
char_uuid: Uuid,
) -> BlewResult<Vec<u8>> {
self.backend.read_characteristic(device_id, char_uuid).await
}
pub async fn write_characteristic(
&self,
device_id: &DeviceId,
char_uuid: Uuid,
value: Vec<u8>,
write_type: WriteType,
) -> BlewResult<()> {
self.backend
.write_characteristic(device_id, char_uuid, value, write_type)
.await
}
pub async fn subscribe_characteristic(
&self,
device_id: &DeviceId,
char_uuid: Uuid,
) -> BlewResult<()> {
self.backend
.subscribe_characteristic(device_id, char_uuid)
.await
}
pub async fn unsubscribe_characteristic(
&self,
device_id: &DeviceId,
char_uuid: Uuid,
) -> BlewResult<()> {
self.backend
.unsubscribe_characteristic(device_id, char_uuid)
.await
}
pub async fn mtu(&self, device_id: &DeviceId) -> u16 {
self.backend.mtu(device_id).await
}
pub async fn open_l2cap_channel(
&self,
device_id: &DeviceId,
psm: Psm,
) -> BlewResult<L2capChannel> {
self.backend.open_l2cap_channel(device_id, psm).await
}
pub fn events(&self) -> EventStream<CentralEvent, B::EventStream> {
EventStream::new(self.backend.events())
}
#[cfg(not(target_os = "android"))]
#[allow(clippy::unused_async)]
pub async fn refresh(&self, _device_id: &DeviceId) -> BlewResult<()> {
Err(BlewError::NotSupported)
}
pub async fn wait_ready(&self, timeout: std::time::Duration) -> BlewResult<()> {
if self.backend.is_powered().await.unwrap_or(false) {
return Ok(());
}
let mut events = self.events();
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::Internal("adapter event stream closed".into())),
Ok(Some(CentralEvent::AdapterStateChanged { powered: true })) => return Ok(()),
Ok(Some(_)) => {}
}
}
}
}