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::{bt_hci_duration, bt_hci_ext_duration, 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            bt_hci_duration(params.interval_min),
68            bt_hci_duration(params.interval_max),
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: bt_hci_duration(params.timeout.unwrap_or(embassy_time::Duration::from_micros(0))),
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                bt_hci_ext_duration(params.interval_min),
192                bt_hci_ext_duration(params.interval_max),
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 = bt_hci_duration(set.params.timeout.unwrap_or(embassy_time::Duration::from_micros(0)));
232            handles[i].max_ext_adv_events = set.params.max_events.unwrap_or(0);
233        }
234
235        trace!("[host] enabling extended advertising");
236        host.advertise_state.start(handles);
237        host.command(LeSetExtAdvEnable::new(true, handles)).await?;
238        drop.defuse();
239        Ok(Advertiser {
240            stack: self.stack,
241            extended: true,
242            done: false,
243        })
244    }
245
246    /// Update the extended advertisment adv_data and/or scan_data for multiple
247    /// advertising sets. Does not change any other advertising parameters. If
248    /// no advertising is active, this will not produce any observable effect.
249    /// This is typically useful when implementing a BLE beacon that only
250    /// broadcasts advertisement data and does not accept any connections.
251    pub async fn update_adv_data_ext<'k>(
252        &mut self,
253        sets: &[AdvertisementSet<'k>],
254        handles: &mut [AdvSet],
255    ) -> Result<(), BleHostError<C::Error>>
256    where
257        C: for<'t> ControllerCmdSync<LeSetExtAdvData<'t>> + for<'t> ControllerCmdSync<LeSetExtScanResponseData<'t>>,
258    {
259        assert_eq!(sets.len(), handles.len());
260        let host = &self.stack.host;
261        for (i, set) in sets.iter().enumerate() {
262            let handle = handles[i].adv_handle;
263            let data: RawAdvertisement<'k> = set.data.into();
264            if !data.adv_data.is_empty() {
265                host.command(LeSetExtAdvData::new(
266                    handle,
267                    Operation::Complete,
268                    set.params.fragment,
269                    data.adv_data,
270                ))
271                .await?;
272            }
273            if !data.scan_data.is_empty() {
274                host.command(LeSetExtScanResponseData::new(
275                    handle,
276                    Operation::Complete,
277                    set.params.fragment,
278                    data.scan_data,
279                ))
280                .await?;
281            }
282        }
283        Ok(())
284    }
285
286    /// Accept any pending available connection.
287    ///
288    /// Accepts the next pending connection if there are any.
289    pub fn try_accept(&mut self) -> Option<Connection<'d, P>> {
290        if let Poll::Ready(conn) = self
291            .stack
292            .host
293            .connections
294            .poll_accept(LeConnRole::Peripheral, &[], None)
295        {
296            Some(conn)
297        } else {
298            None
299        }
300    }
301}
302
303/// Handle to an active advertiser which can accept connections.
304pub struct Advertiser<'d, C, P: PacketPool> {
305    stack: &'d Stack<'d, C, P>,
306    extended: bool,
307    done: bool,
308}
309
310impl<'d, C: Controller, P: PacketPool> Advertiser<'d, C, P> {
311    /// Accept the next peripheral connection for this advertiser.
312    ///
313    /// Returns Error::Timeout if advertiser stopped.
314    pub async fn accept(mut self) -> Result<Connection<'d, P>, Error> {
315        let result = match select(
316            self.stack.host.connections.accept(LeConnRole::Peripheral, &[]),
317            self.stack.host.advertise_state.wait(),
318        )
319        .await
320        {
321            Either::First(conn) => Ok(conn),
322            Either::Second(_) => Err(Error::Timeout),
323        };
324        self.done = true;
325        result
326    }
327}
328
329impl<C, P: PacketPool> Drop for Advertiser<'_, C, P> {
330    fn drop(&mut self) {
331        if !self.done {
332            self.stack.host.advertise_command_state.cancel(self.extended);
333        } else {
334            self.stack.host.advertise_command_state.canceled();
335        }
336    }
337}