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