Skip to main content

trouble_host/
peripheral.rs

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