trouble_host/
peripheral.rs

1//! Functionality for the BLE peripheral role.
2use core::task::Poll;
3
4use bt_hci::cmd::le::{
5    LeClearAdvSets, LeReadNumberOfSupportedAdvSets, LeSetAdvData, LeSetAdvEnable, LeSetAdvParams,
6    LeSetAdvSetRandomAddr, LeSetExtAdvData, LeSetExtAdvEnable, LeSetExtAdvParams, LeSetExtScanResponseData,
7    LeSetScanResponseData,
8};
9use bt_hci::controller::{Controller, ControllerCmdSync};
10use bt_hci::param::{AddrKind, AdvChannelMap, AdvHandle, AdvKind, AdvSet, BdAddr, LeConnRole, Operation};
11use embassy_futures::select::{select, Either};
12
13use crate::advertise::{Advertisement, AdvertisementParameters, AdvertisementSet, RawAdvertisement};
14use crate::connection::Connection;
15use crate::{Address, BleHostError, Error, PacketPool, Stack};
16
17/// Type which implements the BLE peripheral role.
18pub struct Peripheral<'d, C, P: PacketPool> {
19    stack: &'d Stack<'d, C, P>,
20}
21
22impl<'d, C: Controller, P: PacketPool> Peripheral<'d, C, P> {
23    pub(crate) fn new(stack: &'d Stack<'d, C, P>) -> Self {
24        Self { stack }
25    }
26
27    /// Start advertising with the provided parameters and return a handle to accept connections.
28    pub async fn advertise<'k>(
29        &mut self,
30        params: &AdvertisementParameters,
31        data: Advertisement<'k>,
32    ) -> Result<Advertiser<'d, C, P>, BleHostError<C::Error>>
33    where
34        C: for<'t> ControllerCmdSync<LeSetAdvData>
35            + ControllerCmdSync<LeSetAdvParams>
36            + for<'t> ControllerCmdSync<LeSetAdvEnable>
37            + for<'t> ControllerCmdSync<LeSetScanResponseData>,
38    {
39        let host = &self.stack.host;
40
41        // Ensure no other advertise ongoing.
42        let drop = crate::host::OnDrop::new(|| {
43            host.advertise_command_state.cancel(false);
44        });
45        host.advertise_command_state.request().await;
46
47        // Clear current advertising terminations
48        host.advertise_state.reset();
49
50        let data: RawAdvertisement = data.into();
51        if !data.props.legacy_adv() {
52            return Err(Error::ExtendedAdvertisingNotSupported.into());
53        }
54
55        let kind = match (data.props.connectable_adv(), data.props.scannable_adv()) {
56            (true, true) => AdvKind::AdvInd,
57            (true, false) => AdvKind::AdvDirectIndLow,
58            (false, true) => AdvKind::AdvScanInd,
59            (false, false) => AdvKind::AdvNonconnInd,
60        };
61        let peer = data.peer.unwrap_or(Address {
62            kind: AddrKind::PUBLIC,
63            addr: BdAddr::default(),
64        });
65
66        host.command(LeSetAdvParams::new(
67            params.interval_min.into(),
68            params.interval_max.into(),
69            kind,
70            host.address.map(|a| a.kind).unwrap_or(AddrKind::PUBLIC),
71            peer.kind,
72            peer.addr,
73            params.channel_map.unwrap_or(AdvChannelMap::ALL),
74            params.filter_policy,
75        ))
76        .await?;
77
78        if !data.adv_data.is_empty() {
79            let mut buf = [0; 31];
80            let to_copy = data.adv_data.len().min(buf.len());
81            buf[..to_copy].copy_from_slice(&data.adv_data[..to_copy]);
82            host.command(LeSetAdvData::new(to_copy as u8, buf)).await?;
83        }
84
85        if !data.scan_data.is_empty() {
86            let mut buf = [0; 31];
87            let to_copy = data.scan_data.len().min(buf.len());
88            buf[..to_copy].copy_from_slice(&data.scan_data[..to_copy]);
89            host.command(LeSetScanResponseData::new(to_copy as u8, buf)).await?;
90        }
91
92        let advset: [AdvSet; 1] = [AdvSet {
93            adv_handle: AdvHandle::new(0),
94            duration: params.timeout.unwrap_or(embassy_time::Duration::from_micros(0)).into(),
95            max_ext_adv_events: 0,
96        }];
97
98        trace!("[host] enabling advertising");
99        host.advertise_state.start(&advset[..]);
100        host.command(LeSetAdvEnable::new(true)).await?;
101        drop.defuse();
102        Ok(Advertiser {
103            stack: self.stack,
104            extended: false,
105            done: false,
106        })
107    }
108
109    /// Update the advertisment adv_data and/or scan_data. Does not change any
110    /// other advertising parameters. If no advertising is active, this will not
111    /// produce any observable effect. This is typically useful when
112    /// implementing a BLE beacon that only broadcasts advertisement data and
113    /// does not accept any connections.
114    pub async fn update_adv_data<'k>(&mut self, data: Advertisement<'k>) -> Result<(), BleHostError<C::Error>>
115    where
116        C: for<'t> ControllerCmdSync<LeSetAdvData> + for<'t> ControllerCmdSync<LeSetScanResponseData>,
117    {
118        let host = &self.stack.host;
119        let data: RawAdvertisement = data.into();
120        if !data.props.legacy_adv() {
121            return Err(Error::ExtendedAdvertisingNotSupported.into());
122        }
123        if !data.adv_data.is_empty() {
124            let mut buf = [0; 31];
125            let to_copy = data.adv_data.len().min(buf.len());
126            buf[..to_copy].copy_from_slice(&data.adv_data[..to_copy]);
127            host.command(LeSetAdvData::new(to_copy as u8, buf)).await?;
128        }
129        if !data.scan_data.is_empty() {
130            let mut buf = [0; 31];
131            let to_copy = data.scan_data.len().min(buf.len());
132            buf[..to_copy].copy_from_slice(&data.scan_data[..to_copy]);
133            host.command(LeSetScanResponseData::new(to_copy as u8, buf)).await?;
134        }
135        Ok(())
136    }
137
138    /// Starts sending BLE advertisements according to the provided config.
139    ///
140    /// The handles are required to provide the storage while advertising, and
141    /// can be created by calling AdvertisementSet::handles(sets).
142    ///
143    /// Advertisements are stopped when a connection is made against this host,
144    /// in which case a handle for the connection is returned.
145    ///
146    /// Returns a handle to accept connections.
147    pub async fn advertise_ext<'k>(
148        &mut self,
149        sets: &[AdvertisementSet<'k>],
150        handles: &mut [AdvSet],
151    ) -> Result<Advertiser<'d, C, P>, BleHostError<C::Error>>
152    where
153        C: for<'t> ControllerCmdSync<LeSetExtAdvData<'t>>
154            + ControllerCmdSync<LeClearAdvSets>
155            + ControllerCmdSync<LeSetExtAdvParams>
156            + ControllerCmdSync<LeSetAdvSetRandomAddr>
157            + ControllerCmdSync<LeReadNumberOfSupportedAdvSets>
158            + for<'t> ControllerCmdSync<LeSetExtAdvEnable<'t>>
159            + for<'t> ControllerCmdSync<LeSetExtScanResponseData<'t>>,
160    {
161        assert_eq!(sets.len(), handles.len());
162        let host = &self.stack.host;
163        // Check host supports the required advertisement sets
164        {
165            let result = host.command(LeReadNumberOfSupportedAdvSets::new()).await?;
166            if result < sets.len() as u8 || host.advertise_state.len() < sets.len() {
167                return Err(Error::InsufficientSpace.into());
168            }
169        }
170
171        // Ensure no other advertise ongoing.
172        let drop = crate::host::OnDrop::new(|| {
173            host.advertise_command_state.cancel(true);
174        });
175        host.advertise_command_state.request().await;
176
177        // Clear current advertising terminations
178        host.advertise_state.reset();
179
180        for (i, set) in sets.iter().enumerate() {
181            let handle = AdvHandle::new(i as u8);
182            let data: RawAdvertisement<'k> = set.data.into();
183            let params = set.params;
184            let peer = data.peer.unwrap_or(Address {
185                kind: AddrKind::PUBLIC,
186                addr: BdAddr::default(),
187            });
188            host.command(LeSetExtAdvParams::new(
189                handle,
190                data.props,
191                params.interval_min.into(),
192                params.interval_max.into(),
193                params.channel_map.unwrap_or(AdvChannelMap::ALL),
194                host.address.map(|a| a.kind).unwrap_or(AddrKind::PUBLIC),
195                peer.kind,
196                peer.addr,
197                params.filter_policy,
198                params.tx_power as i8,
199                params.primary_phy,
200                0,
201                params.secondary_phy,
202                0,
203                false,
204            ))
205            .await?;
206
207            if let Some(address) = host.address.as_ref() {
208                host.command(LeSetAdvSetRandomAddr::new(handle, address.addr)).await?;
209            }
210
211            if !data.adv_data.is_empty() {
212                host.command(LeSetExtAdvData::new(
213                    handle,
214                    Operation::Complete,
215                    params.fragment,
216                    data.adv_data,
217                ))
218                .await?;
219            }
220
221            if !data.scan_data.is_empty() {
222                host.command(LeSetExtScanResponseData::new(
223                    handle,
224                    Operation::Complete,
225                    params.fragment,
226                    data.scan_data,
227                ))
228                .await?;
229            }
230            handles[i].adv_handle = handle;
231            handles[i].duration = set
232                .params
233                .timeout
234                .unwrap_or(embassy_time::Duration::from_micros(0))
235                .into();
236            handles[i].max_ext_adv_events = set.params.max_events.unwrap_or(0);
237        }
238
239        trace!("[host] enabling extended advertising");
240        host.advertise_state.start(handles);
241        host.command(LeSetExtAdvEnable::new(true, handles)).await?;
242        drop.defuse();
243        Ok(Advertiser {
244            stack: self.stack,
245            extended: true,
246            done: false,
247        })
248    }
249
250    /// Update the extended advertisment adv_data and/or scan_data for multiple
251    /// advertising sets. Does not change any other advertising parameters. If
252    /// no advertising is active, this will not produce any observable effect.
253    /// This is typically useful when implementing a BLE beacon that only
254    /// broadcasts advertisement data and does not accept any connections.
255    pub async fn update_adv_data_ext<'k>(
256        &mut self,
257        sets: &[AdvertisementSet<'k>],
258        handles: &mut [AdvSet],
259    ) -> Result<(), BleHostError<C::Error>>
260    where
261        C: for<'t> ControllerCmdSync<LeSetExtAdvData<'t>> + for<'t> ControllerCmdSync<LeSetExtScanResponseData<'t>>,
262    {
263        assert_eq!(sets.len(), handles.len());
264        let host = &self.stack.host;
265        for (i, set) in sets.iter().enumerate() {
266            let handle = handles[i].adv_handle;
267            let data: RawAdvertisement<'k> = set.data.into();
268            if !data.adv_data.is_empty() {
269                host.command(LeSetExtAdvData::new(
270                    handle,
271                    Operation::Complete,
272                    set.params.fragment,
273                    data.adv_data,
274                ))
275                .await?;
276            }
277            if !data.scan_data.is_empty() {
278                host.command(LeSetExtScanResponseData::new(
279                    handle,
280                    Operation::Complete,
281                    set.params.fragment,
282                    data.scan_data,
283                ))
284                .await?;
285            }
286        }
287        Ok(())
288    }
289
290    /// Accept any pending available connection.
291    ///
292    /// Accepts the next pending connection if there are any.
293    pub fn try_accept(&mut self) -> Option<Connection<'d, P>> {
294        if let Poll::Ready(conn) = self
295            .stack
296            .host
297            .connections
298            .poll_accept(LeConnRole::Peripheral, &[], None)
299        {
300            Some(conn)
301        } else {
302            None
303        }
304    }
305}
306
307/// Handle to an active advertiser which can accept connections.
308pub struct Advertiser<'d, C, P: PacketPool> {
309    stack: &'d Stack<'d, C, P>,
310    extended: bool,
311    done: bool,
312}
313
314impl<'d, C: Controller, P: PacketPool> Advertiser<'d, C, P> {
315    /// Accept the next peripheral connection for this advertiser.
316    ///
317    /// Returns Error::Timeout if advertiser stopped.
318    pub async fn accept(mut self) -> Result<Connection<'d, P>, Error> {
319        let result = match select(
320            self.stack.host.connections.accept(LeConnRole::Peripheral, &[]),
321            self.stack.host.advertise_state.wait(),
322        )
323        .await
324        {
325            Either::First(conn) => Ok(conn),
326            Either::Second(_) => Err(Error::Timeout),
327        };
328        self.done = true;
329        result
330    }
331}
332
333impl<C, P: PacketPool> Drop for Advertiser<'_, C, P> {
334    fn drop(&mut self) {
335        if !self.done {
336            self.stack.host.advertise_command_state.cancel(self.extended);
337        } else {
338            self.stack.host.advertise_command_state.canceled();
339        }
340    }
341}