trouble_host/
central.rs

1//! Functionality for the BLE central role.
2use bt_hci::cmd::le::{LeAddDeviceToFilterAcceptList, LeClearFilterAcceptList, LeCreateConn, LeExtCreateConn};
3use bt_hci::controller::{Controller, ControllerCmdAsync, ControllerCmdSync};
4use bt_hci::param::{AddrKind, BdAddr, InitiatingPhy, LeConnRole, PhyParams};
5use embassy_futures::select::{select, Either};
6
7use crate::connection::{ConnectConfig, Connection, PhySet};
8use crate::{bt_hci_duration, BleHostError, Error, PacketPool, Stack};
9
10/// A type implementing the BLE central role.
11pub struct Central<'stack, C, P: PacketPool> {
12    pub(crate) stack: &'stack Stack<'stack, C, P>,
13}
14
15impl<'stack, C: Controller, P: PacketPool> Central<'stack, C, P> {
16    pub(crate) fn new(stack: &'stack Stack<'stack, C, P>) -> Self {
17        Self { stack }
18    }
19
20    /// Attempt to create a connection with the provided config.
21    pub async fn connect(&mut self, config: &ConnectConfig<'_>) -> Result<Connection<'stack, P>, BleHostError<C::Error>>
22    where
23        C: ControllerCmdSync<LeClearFilterAcceptList>
24            + ControllerCmdSync<LeAddDeviceToFilterAcceptList>
25            + ControllerCmdAsync<LeCreateConn>,
26    {
27        if config.scan_config.filter_accept_list.is_empty() {
28            return Err(Error::ConfigFilterAcceptListIsEmpty.into());
29        }
30
31        let host = &self.stack.host;
32        let _drop = crate::host::OnDrop::new(|| {
33            host.connect_command_state.cancel(true);
34        });
35        host.connect_command_state.request().await;
36
37        self.set_accept_filter(config.scan_config.filter_accept_list).await?;
38
39        host.async_command(LeCreateConn::new(
40            bt_hci_duration(config.scan_config.interval),
41            bt_hci_duration(config.scan_config.window),
42            true,
43            AddrKind::PUBLIC,
44            BdAddr::default(),
45            host.address.map(|a| a.kind).unwrap_or(AddrKind::PUBLIC),
46            bt_hci_duration(config.connect_params.min_connection_interval),
47            bt_hci_duration(config.connect_params.max_connection_interval),
48            config.connect_params.max_latency,
49            bt_hci_duration(config.connect_params.supervision_timeout),
50            bt_hci_duration(config.connect_params.min_event_length),
51            bt_hci_duration(config.connect_params.max_event_length),
52        ))
53        .await?;
54        match select(
55            host.connections
56                .accept(LeConnRole::Central, config.scan_config.filter_accept_list),
57            host.connect_command_state.wait_idle(),
58        )
59        .await
60        {
61            Either::First(conn) => {
62                _drop.defuse();
63                host.connect_command_state.done();
64                Ok(conn)
65            }
66            Either::Second(_) => Err(Error::Timeout.into()),
67        }
68    }
69
70    /// Attempt to create a connection with the provided config.
71    pub async fn connect_ext(
72        &mut self,
73        config: &ConnectConfig<'_>,
74    ) -> Result<Connection<'stack, P>, BleHostError<C::Error>>
75    where
76        C: ControllerCmdSync<LeClearFilterAcceptList>
77            + ControllerCmdSync<LeAddDeviceToFilterAcceptList>
78            + ControllerCmdAsync<LeExtCreateConn>,
79    {
80        if config.scan_config.filter_accept_list.is_empty() {
81            return Err(Error::ConfigFilterAcceptListIsEmpty.into());
82        }
83
84        let host = &self.stack.host;
85        // Ensure no other connect ongoing.
86        let _drop = crate::host::OnDrop::new(|| {
87            host.connect_command_state.cancel(true);
88        });
89        host.connect_command_state.request().await;
90
91        self.set_accept_filter(config.scan_config.filter_accept_list).await?;
92
93        let initiating = InitiatingPhy {
94            scan_interval: bt_hci_duration(config.scan_config.interval),
95            scan_window: bt_hci_duration(config.scan_config.window),
96            conn_interval_min: bt_hci_duration(config.connect_params.min_connection_interval),
97            conn_interval_max: bt_hci_duration(config.connect_params.max_connection_interval),
98            max_latency: config.connect_params.max_latency,
99            supervision_timeout: bt_hci_duration(config.connect_params.supervision_timeout),
100            min_ce_len: bt_hci_duration(config.connect_params.min_event_length),
101            max_ce_len: bt_hci_duration(config.connect_params.max_event_length),
102        };
103        let phy_params = create_phy_params(initiating, config.scan_config.phys);
104
105        host.async_command(LeExtCreateConn::new(
106            true,
107            host.address.map(|a| a.kind).unwrap_or(AddrKind::PUBLIC),
108            AddrKind::PUBLIC,
109            BdAddr::default(),
110            phy_params,
111        ))
112        .await?;
113
114        match select(
115            host.connections
116                .accept(LeConnRole::Central, config.scan_config.filter_accept_list),
117            host.connect_command_state.wait_idle(),
118        )
119        .await
120        {
121            Either::First(conn) => {
122                _drop.defuse();
123                host.connect_command_state.done();
124                Ok(conn)
125            }
126            Either::Second(_) => Err(Error::Timeout.into()),
127        }
128    }
129
130    pub(crate) async fn set_accept_filter(
131        &mut self,
132        filter_accept_list: &[(AddrKind, &BdAddr)],
133    ) -> Result<(), BleHostError<C::Error>>
134    where
135        C: ControllerCmdSync<LeClearFilterAcceptList> + ControllerCmdSync<LeAddDeviceToFilterAcceptList>,
136    {
137        let host = &self.stack.host;
138        host.command(LeClearFilterAcceptList::new()).await?;
139        for entry in filter_accept_list {
140            host.command(LeAddDeviceToFilterAcceptList::new(entry.0, *entry.1))
141                .await?;
142        }
143        Ok(())
144    }
145}
146
147pub(crate) fn create_phy_params<P: Copy>(phy: P, phys: PhySet) -> PhyParams<P> {
148    let phy_params: PhyParams<P> = PhyParams {
149        le_1m_phy: match phys {
150            PhySet::M1 | PhySet::M1M2 | PhySet::M1Coded | PhySet::M1M2Coded => Some(phy),
151            _ => None,
152        },
153        le_2m_phy: match phys {
154            PhySet::M2 | PhySet::M1M2 | PhySet::M2Coded | PhySet::M1M2Coded => Some(phy),
155            _ => None,
156        },
157        le_coded_phy: match phys {
158            PhySet::M2Coded | PhySet::Coded | PhySet::M1Coded | PhySet::M1M2Coded => Some(phy),
159            _ => None,
160        },
161    };
162    phy_params
163}