use crate::bpv7::EndpointId;
use crate::cla::peer::ClaPeer;
use crate::cla::ConvergenceLayer;
use crate::consts::ble::{ADV_NAME, NOTIFY_CHAR_UUID, WRITE_CHAR_UUID};
use async_trait::async_trait;
use btleplug::api::{Central, Manager as _, Peripheral as _, WriteType};
use btleplug::platform::Manager;
use futures::stream::StreamExt;
use std::time::Duration;
use tokio::time;
use uuid::Uuid;
#[derive(Clone)]
pub struct BlePeer {
pub peer_id: EndpointId,
pub device_name: String,
pub connection_info: Option<BleConnectionInfo>,
}
#[derive(Clone, Debug)]
pub struct BleConnectionInfo {
pub device_name: String,
pub mac_address: String,
pub rssi: Option<i16>,
pub tx_power: Option<i16>,
pub services: Vec<Uuid>,
pub is_connectable: bool,
}
impl BleConnectionInfo {
pub fn new(device_name: String, mac_address: String) -> Self {
Self {
device_name,
mac_address,
rssi: None,
tx_power: None,
services: Vec::new(),
is_connectable: true,
}
}
pub fn display_info(&self) {
println!("📱 BLE Device Found:");
println!(" Name: {}", self.device_name);
println!(" MAC Address: {}", self.mac_address);
if let Some(rssi) = self.rssi {
println!(" RSSI: {} dBm", rssi);
}
if let Some(tx_power) = self.tx_power {
println!(" TX Power: {} dBm", tx_power);
}
println!(" Connectable: {}", self.is_connectable);
if !self.services.is_empty() {
println!(" Services: {:?}", self.services);
}
println!();
}
}
impl BlePeer {
pub fn new(peer_id: EndpointId, device_name: String) -> Self {
Self {
peer_id,
device_name,
connection_info: None,
}
}
pub fn with_connection_info(mut self, info: BleConnectionInfo) -> Self {
self.connection_info = Some(info);
self
}
pub fn get_connection_info(&self) -> Option<&BleConnectionInfo> {
self.connection_info.as_ref()
}
}
async fn ble_discover_device(device_name: &str) -> anyhow::Result<Option<BleConnectionInfo>> {
let manager = Manager::new().await?;
let adapter = manager
.adapters()
.await?
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("No BLE adapter found"))?;
println!("🔍 Scanning for BLE device: {}", device_name);
adapter.start_scan(Default::default()).await?;
time::sleep(Duration::from_secs(2)).await;
let peripherals = adapter.peripherals().await?;
for peripheral in peripherals {
if let Ok(Some(props)) = peripheral.properties().await {
if let Some(name) = &props.local_name {
if name.contains(device_name) {
let mut connection_info =
BleConnectionInfo::new(name.clone(), props.address.to_string());
if let Some(rssi) = props.rssi {
connection_info.rssi = Some(rssi);
}
if let Some(tx_power) = props.tx_power_level {
connection_info.tx_power = Some(tx_power);
}
connection_info.is_connectable = true;
return Ok(Some(connection_info));
}
}
}
}
Ok(None)
}
async fn ble_connect_device(info: &BleConnectionInfo) -> anyhow::Result<()> {
let manager = Manager::new().await?;
let adapter = manager
.adapters()
.await?
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("No BLE adapter found"))?;
let peripherals = adapter.peripherals().await?;
for peripheral in peripherals {
if let Ok(Some(props)) = peripheral.properties().await {
if let Some(name) = &props.local_name {
if name == &info.device_name && props.address.to_string() == info.mac_address {
peripheral.connect().await?;
peripheral.discover_services().await?;
println!(
"Connected to BLE device: {} ({})",
info.device_name, info.mac_address
);
return Ok(());
}
}
}
}
Err(anyhow::anyhow!(
"Device not found for connection: {}",
info.device_name
))
}
#[async_trait]
impl ConvergenceLayer for BlePeer {
fn address(&self) -> String {
self.device_name.clone()
}
async fn activate(&self) -> anyhow::Result<()> {
if let Some(connection_info) = ble_discover_device(&self.device_name).await? {
println!("✅ BLE device found and connection info retrieved:");
connection_info.display_info();
ble_connect_device(&connection_info).await?;
Ok(())
} else {
Err(anyhow::anyhow!(
"BLE device not found: {}",
self.device_name
))
}
}
}
#[async_trait]
impl ClaPeer for BlePeer {
fn get_peer_endpoint_id(&self) -> EndpointId {
self.peer_id.clone()
}
async fn is_reachable(&self) -> bool {
ble_discover_device(&self.device_name)
.await
.unwrap_or(None)
.is_some()
}
fn get_cla_type(&self) -> &str {
"ble"
}
fn get_connection_address(&self) -> String {
if let Some(info) = &self.connection_info {
format!("{} ({})", info.device_name, info.mac_address)
} else {
self.device_name.clone()
}
}
fn clone_box(&self) -> Box<dyn ClaPeer> {
Box::new(self.clone())
}
async fn activate(&self) -> anyhow::Result<()> {
<Self as ConvergenceLayer>::activate(self).await
}
}
pub struct BleClaClient {
pub device_name: String,
pub connection_info: Option<BleConnectionInfo>,
}
impl BleClaClient {
pub fn new(device_name: String) -> Self {
Self {
device_name,
connection_info: None,
}
}
pub async fn scan_and_store_info(&mut self) -> anyhow::Result<bool> {
if let Some(info) = ble_discover_device(&self.device_name).await? {
self.connection_info = Some(info.clone());
println!("✅ Device found and connection info stored:");
info.display_info();
Ok(true)
} else {
println!("❌ Device not found: {}", self.device_name);
Ok(false)
}
}
pub fn get_connection_info(&self) -> Option<&BleConnectionInfo> {
self.connection_info.as_ref()
}
pub fn display_stored_info(&self) {
if let Some(info) = &self.connection_info {
println!("📱 Stored BLE Connection Info:");
info.display_info();
} else {
println!("❌ No connection information stored. Run scan_and_store_info() first.");
}
}
}
#[tokio::main]
async fn _main() -> anyhow::Result<()> {
let manager = Manager::new().await?;
let adapter = manager
.adapters()
.await?
.into_iter()
.next()
.expect("No BLE adapter");
println!("Scanning for peripherals...");
adapter.start_scan(Default::default()).await?;
time::sleep(Duration::from_secs(3)).await;
let peripherals = adapter.peripherals().await?;
let mut maybe_peripheral = None;
for p in peripherals {
if let Ok(Some(props)) = p.properties().await {
if let Some(name) = props.local_name {
if name.contains(ADV_NAME) {
maybe_peripheral = Some(p);
break;
}
}
}
}
let peripheral = match maybe_peripheral {
Some(p) => p,
None => {
println!("No target peripheral found.");
return Ok(());
}
};
peripheral.connect().await?;
peripheral.discover_services().await?;
println!("Connected to peripheral.");
let chars = peripheral.characteristics();
let write_char = chars
.iter()
.find(|c| c.uuid == Uuid::parse_str(WRITE_CHAR_UUID).unwrap())
.expect("Write char not found");
let notify_char = chars
.iter()
.find(|c| c.uuid == Uuid::parse_str(NOTIFY_CHAR_UUID).unwrap())
.expect("Notify char not found");
peripheral.subscribe(notify_char).await?;
let bundle_data = b"HelloBundle".to_vec();
peripheral
.write(write_char, &bundle_data, WriteType::WithResponse)
.await?;
println!("Sent bundle.");
let mut notification_stream = peripheral.notifications().await?;
println!("Waiting for ACK...");
if let Some(data) = notification_stream.next().await {
println!(
"Received notify: {:?}",
String::from_utf8_lossy(&data.value)
);
}
peripheral.disconnect().await?;
println!("Disconnected.");
Ok(())
}