pub use blec;
pub use blec::models;
pub use blec::{Error, Handler, Result};
use blec::models::{BleDevice, ScanFilter, WriteType};
use blec::OnDisconnectHandler;
use dioxus::core::spawn_forever;
use dioxus::prelude::*;
use std::sync::atomic::Ordering;
use tokio::sync::mpsc;
use uuid::Uuid;
#[cfg(target_os = "android")]
#[allow(dead_code)]
mod android {
#[manganis::ffi("android")]
extern "Kotlin" {
pub type Blec;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InitState {
Pending,
Ready,
Failed(String),
}
#[derive(Clone, Copy)]
pub struct Ble {
init: Signal<InitState, SyncStorage>,
scanning: Signal<bool, SyncStorage>,
devices: Signal<Vec<BleDevice>, SyncStorage>,
connected: Signal<Option<BleDevice>, SyncStorage>,
}
pub fn use_ble() -> Ble {
use_root_context(|| {
let ble = Ble {
init: Signal::new_maybe_sync_in_scope(InitState::Pending, ScopeId::ROOT),
scanning: Signal::new_maybe_sync_in_scope(false, ScopeId::ROOT),
devices: Signal::new_maybe_sync_in_scope(Vec::new(), ScopeId::ROOT),
connected: Signal::new_maybe_sync_in_scope(None, ScopeId::ROOT),
};
spawn_forever(ble.start());
ble
})
}
impl Ble {
async fn start(mut self) {
let handler = match blec::init().await {
Ok(handler) => handler,
Err(e) => {
tracing::error!("blec::init failed: {e}");
self.init.set(InitState::Failed(e.to_string()));
return;
}
};
let (scan_tx, mut scan_rx) = mpsc::channel(8);
handler.set_scanning_update_channel(scan_tx).await;
let mut scanning = self.scanning;
spawn_forever(async move {
while let Some(state) = scan_rx.recv().await {
scanning.set(state);
}
});
let (conn_tx, mut conn_rx) = mpsc::channel(8);
handler.set_connection_update_channel(conn_tx).await;
let mut connected = self.connected;
spawn_forever(async move {
while let Some(is_connected) = conn_rx.recv().await {
if is_connected {
if let Ok(device) = handler.connected_device().await {
connected.set(Some(device));
}
} else {
connected.set(None);
}
}
});
self.init.set(InitState::Ready);
}
pub fn init_state(&self) -> InitState {
self.init.cloned()
}
pub fn is_ready(&self) -> bool {
*self.init.read() == InitState::Ready
}
pub fn scanning(&self) -> bool {
*self.scanning.read()
}
pub fn devices(&self) -> Vec<BleDevice> {
self.devices.cloned()
}
pub fn connected(&self) -> Option<BleDevice> {
self.connected.cloned()
}
pub fn is_connected(&self) -> bool {
self.connected.read().is_some()
}
pub fn handler(&self) -> Result<&'static Handler> {
blec::get_handler()
}
pub async fn check_permissions(self, ask_if_denied: bool) -> Result<bool> {
blec::check_permissions(ask_if_denied).await
}
pub async fn scan(mut self, timeout_ms: u64, filter: ScanFilter) -> Result<()> {
let handler = blec::get_handler()?;
self.devices.set(Vec::new());
let (tx, mut rx) = mpsc::channel(8);
let mut devices = self.devices;
spawn_forever(async move {
while let Some(found) = rx.recv().await {
devices.set(found);
}
});
handler
.discover(Some(tx), timeout_ms, filter, allow_ibeacons())
.await
}
pub async fn stop_scan(self) -> Result<()> {
blec::get_handler()?.stop_scan().await
}
pub async fn connect(mut self, address: &str) -> Result<()> {
let handler = blec::get_handler()?;
let mut connected = self.connected;
let on_disconnect = OnDisconnectHandler::from_sync(move || connected.set(None));
handler
.connect(address, on_disconnect, allow_ibeacons())
.await?;
self.connected.set(handler.connected_device().await.ok());
Ok(())
}
pub async fn disconnect(mut self) -> Result<()> {
let result = blec::get_handler()?.disconnect().await;
self.connected.set(None);
result
}
pub async fn write(
self,
characteristic: Uuid,
service: Option<Uuid>,
data: &[u8],
write_type: WriteType,
) -> Result<()> {
blec::get_handler()?
.send_data(characteristic, service, data, write_type)
.await
}
pub async fn read(self, characteristic: Uuid, service: Option<Uuid>) -> Result<Vec<u8>> {
blec::get_handler()?
.recv_data(characteristic, service)
.await
}
}
fn allow_ibeacons() -> bool {
blec::ALLOW_IBEACONS.load(Ordering::Relaxed)
}
pub fn use_ble_notifications(
characteristic: Uuid,
service: Option<Uuid>,
) -> ReadSignal<Option<Vec<u8>>, SyncStorage> {
let ble = use_ble();
let value = use_signal_sync(|| None::<Vec<u8>>);
let connected_address = use_memo(move || {
ble.connected
.read()
.as_ref()
.map(|device| device.address.clone())
});
use_resource(move || {
let address = connected_address();
async move {
if address.is_none() {
return;
}
let Ok(handler) = blec::get_handler() else {
return;
};
let subscribed = handler
.subscribe(characteristic, service, move |data: Vec<u8>| {
let mut value = value;
value.set(Some(data));
})
.await;
if let Err(e) = subscribed {
tracing::warn!("could not subscribe to {characteristic}: {e}");
}
}
});
use_drop(move || {
if !ble.is_connected() {
return;
}
spawn_forever(async move {
if let Ok(handler) = blec::get_handler() {
let _ = handler.unsubscribe(characteristic).await;
}
});
});
value.into()
}