1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::error::Error;

use async_trait::async_trait;
use bluest::pairing::{IoCapability, PairingAgent, PairingRejected, Passkey};
use bluest::{btuuid, Adapter, Device};
use futures_lite::StreamExt;
use tracing::info;
use tracing::metadata::LevelFilter;

struct StdioPairingAgent;

#[async_trait]
impl PairingAgent for StdioPairingAgent {
    /// The input/output capabilities of this agent
    fn io_capability(&self) -> IoCapability {
        IoCapability::KeyboardDisplay
    }

    async fn confirm(&self, device: &Device) -> Result<(), PairingRejected> {
        tokio::task::block_in_place(move || {
            println!("Do you want to pair with {:?}? (Y/n)", device.name().unwrap());
            let mut buf = String::new();
            std::io::stdin()
                .read_line(&mut buf)
                .map_err(|_| PairingRejected::default())?;
            let response = buf.trim();
            if response.is_empty() || response == "y" || response == "Y" {
                Ok(())
            } else {
                Err(PairingRejected::default())
            }
        })
    }

    async fn confirm_passkey(&self, device: &Device, passkey: Passkey) -> Result<(), PairingRejected> {
        tokio::task::block_in_place(move || {
            println!(
                "Is the passkey \"{}\" displayed on {:?}? (Y/n)",
                passkey,
                device.name().unwrap()
            );
            let mut buf = String::new();
            std::io::stdin()
                .read_line(&mut buf)
                .map_err(|_| PairingRejected::default())?;
            let response = buf.trim();
            if response.is_empty() || response == "y" || response == "Y" {
                Ok(())
            } else {
                Err(PairingRejected::default())
            }
        })
    }

    async fn request_passkey(&self, device: &Device) -> Result<Passkey, PairingRejected> {
        tokio::task::block_in_place(move || {
            println!("Please enter the 6-digit passkey for {:?}: ", device.name().unwrap());
            let mut buf = String::new();
            std::io::stdin()
                .read_line(&mut buf)
                .map_err(|_| PairingRejected::default())?;
            buf.trim().parse().map_err(|_| PairingRejected::default())
        })
    }

    fn display_passkey(&self, device: &Device, passkey: Passkey) {
        println!("The passkey is \"{}\" for {:?}.", passkey, device.name().unwrap());
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    use tracing_subscriber::prelude::*;
    use tracing_subscriber::{fmt, EnvFilter};

    tracing_subscriber::registry()
        .with(fmt::layer())
        .with(
            EnvFilter::builder()
                .with_default_directive(LevelFilter::INFO.into())
                .from_env_lossy(),
        )
        .init();

    let adapter = Adapter::default().await.ok_or("Bluetooth adapter not found")?;
    adapter.wait_available().await?;

    let discovered_device = {
        info!("starting scan");
        let mut scan = adapter.scan(&[btuuid::services::HUMAN_INTERFACE_DEVICE]).await?;
        info!("scan started");
        scan.next().await.ok_or("scan terminated")?
    };

    info!("{:?} {:?}", discovered_device.rssi, discovered_device.adv_data);
    let device = discovered_device.device;

    adapter.connect_device(&device).await?;
    info!("connected!");

    device.pair_with_agent(&StdioPairingAgent).await?;
    info!("paired!");

    adapter.disconnect_device(&device).await?;
    info!("disconnected!");

    Ok(())
}