use anyhow::{Context, Error, Result};
use derive_new::new;
use futures::{prelude::*, stream::BoxStream};
use std::{
convert::{TryFrom, TryInto},
fmt::{self, Display},
time::Duration,
};
#[macro_export]
macro_rules! uuid {
($hex:literal) => {
$crate::ble::Uuid(hex_literal::hex!($hex))
};
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, new)]
pub struct Uuid(pub [u8; 16]);
impl Display for Uuid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let b = self.0;
write!(f, "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15])
}
}
pub type ValueStream = BoxStream<'static, (Uuid, Vec<u8>)>;
pub type MessageStream<T> = BoxStream<'static, Result<T>>;
pub type Peripheral = Box<dyn PeripheralOps + Send>;
pub type Searcher = Box<dyn SearchOps + Send>;
#[async_trait::async_trait]
pub trait SearchOps {
async fn search(&mut self, uuid: &Uuid, timeout: Duration) -> Result<Vec<Peripheral>>;
}
#[async_trait::async_trait]
pub trait PeripheralOps {
fn id(&self) -> &str;
fn rssi(&self) -> i32;
async fn connect(&mut self) -> Result<()>;
async fn disconnect(&mut self) -> Result<()>;
async fn read(&mut self, uuid: &Uuid) -> Result<()>;
async fn write(&mut self, uuid: &Uuid, value: &[u8], with_resp: bool) -> Result<()>;
fn subscribe(&mut self) -> Result<ValueStream>;
}
#[async_trait::async_trait]
pub trait PeripheralOpsExt: PeripheralOps {
async fn write_msg<T>(&mut self, value: T, with_resp: bool) -> Result<()>
where
T: TryInto<(Uuid, Vec<u8>), Error = Error> + Send,
{
let (uuid, value): (Uuid, Vec<u8>) =
value.try_into().context(format!("Couldn't pack message"))?;
self.write(&uuid, &value, with_resp).await?;
Ok(())
}
fn subscribe_msg<T>(&mut self) -> Result<MessageStream<T>>
where
T: TryFrom<(Uuid, Vec<u8>), Error = Error> + Send,
{
Ok(self
.subscribe()?
.map(|(uuid, value)| {
(uuid.clone(), value).try_into().context(format!(
"Couldn't unpack message from characteristic {}",
uuid
))
})
.boxed())
}
}
#[async_trait::async_trait]
impl<T> PeripheralOps for Box<T>
where
T: PeripheralOps + ?Sized + Send,
{
fn id(&self) -> &str {
(**self).id()
}
fn rssi(&self) -> i32 {
(**self).rssi()
}
async fn connect(&mut self) -> Result<()> {
(**self).connect().await
}
async fn disconnect(&mut self) -> Result<()> {
(**self).disconnect().await
}
async fn read(&mut self, uuid: &Uuid) -> Result<()> {
(**self).read(uuid).await
}
async fn write(&mut self, uuid: &Uuid, value: &[u8], with_resp: bool) -> Result<()> {
(**self).write(uuid, value, with_resp).await
}
fn subscribe(&mut self) -> Result<ValueStream> {
(**self).subscribe()
}
}
impl<T> PeripheralOpsExt for T where T: PeripheralOps {}
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "windows")]
mod windows;
pub fn searcher() -> Searcher {
#[cfg(target_os = "linux")]
use linux::searcher as s;
#[cfg(target_os = "macos")]
use macos::searcher as s;
#[cfg(target_os = "windows")]
use windows::searcher as s;
s()
}