summaryrefslogtreecommitdiff
path: root/nrf-softdevice/src/ble/peripheral.rs
blob: 65243f75c7a510623a8a8799cee000fe85290864 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
//! Bluetooth Peripheral operations. Peripheral devices emit advertisements, and optionally accept connections from Central devices.

use core::{mem, ptr};

use crate::ble::*;
use crate::util::{get_union_field, OnDrop, Portal};
use crate::{raw, RawError, Softdevice};

struct RawAdvertisement<'a> {
    kind: u8,
    adv_data: Option<&'a [u8]>,
    scan_data: Option<&'a [u8]>,
}

/// Connectable advertisement types, which can accept connections from interested Central devices.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConnectableAdvertisement<'a> {
    ScannableUndirected {
        adv_data: &'a [u8],
        scan_data: &'a [u8],
    },
    NonscannableDirected {
        scan_data: &'a [u8],
    },
    NonscannableDirectedHighDuty {
        scan_data: &'a [u8],
    },
    #[cfg(any(feature = "s132", feature = "s140"))]
    ExtendedNonscannableUndirected {
        adv_data: &'a [u8],
    },
    #[cfg(any(feature = "s132", feature = "s140"))]
    ExtendedNonscannableDirected {
        adv_data: &'a [u8],
    },
}

impl<'a> From<ConnectableAdvertisement<'a>> for RawAdvertisement<'a> {
    fn from(val: ConnectableAdvertisement<'a>) -> RawAdvertisement<'a> {
        match val {
            ConnectableAdvertisement::ScannableUndirected { adv_data, scan_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED as u8,
                adv_data: Some(adv_data),
                scan_data: Some(scan_data),
            },
            ConnectableAdvertisement::NonscannableDirected { scan_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED as u8,
                adv_data: None,
                scan_data: Some(scan_data),
            },
            ConnectableAdvertisement::NonscannableDirectedHighDuty { scan_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_CONNECTABLE_NONSCANNABLE_DIRECTED_HIGH_DUTY_CYCLE as u8,
                adv_data: None,
                scan_data: Some(scan_data),
            },
            #[cfg(any(feature = "s132", feature = "s140"))]
            ConnectableAdvertisement::ExtendedNonscannableUndirected { adv_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED as u8,
                adv_data: Some(adv_data),
                scan_data: None,
            },
            #[cfg(any(feature = "s132", feature = "s140"))]
            ConnectableAdvertisement::ExtendedNonscannableDirected { adv_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_DIRECTED as u8,
                adv_data: Some(adv_data),
                scan_data: None,
            },
        }
    }
}

/// Non-Connectable advertisement types. They cannot accept connections, they can be
/// only used to broadcast information in the air.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NonconnectableAdvertisement<'a> {
    ScannableUndirected {
        adv_data: &'a [u8],
        scan_data: &'a [u8],
    },
    NonscannableUndirected {
        adv_data: &'a [u8],
    },
    #[cfg(any(feature = "s132", feature = "s140"))]
    ExtendedScannableUndirected {
        adv_data: &'a [u8],
        scan_data: &'a [u8],
    },
    #[cfg(any(feature = "s132", feature = "s140"))]
    ExtendedScannableDirected {
        adv_data: &'a [u8],
        scan_data: &'a [u8],
    },
    #[cfg(any(feature = "s132", feature = "s140"))]
    ExtendedNonscannableUndirected {
        adv_data: &'a [u8],
    },
    #[cfg(any(feature = "s132", feature = "s140"))]
    ExtendedNonscannableDirected {
        adv_data: &'a [u8],
    },
}

impl<'a> From<NonconnectableAdvertisement<'a>> for RawAdvertisement<'a> {
    fn from(val: NonconnectableAdvertisement<'a>) -> RawAdvertisement<'a> {
        match val {
            NonconnectableAdvertisement::ScannableUndirected { adv_data, scan_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_NONCONNECTABLE_SCANNABLE_UNDIRECTED as _,
                adv_data: Some(adv_data),
                scan_data: Some(scan_data),
            },
            NonconnectableAdvertisement::NonscannableUndirected { adv_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED as _,
                adv_data: Some(adv_data),
                scan_data: None,
            },
            #[cfg(any(feature = "s132", feature = "s140"))]
            NonconnectableAdvertisement::ExtendedScannableUndirected { adv_data, scan_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_UNDIRECTED as _,
                adv_data: Some(adv_data),
                scan_data: Some(scan_data),
            },
            #[cfg(any(feature = "s132", feature = "s140"))]
            NonconnectableAdvertisement::ExtendedScannableDirected { adv_data, scan_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_SCANNABLE_DIRECTED as _,
                adv_data: Some(adv_data),
                scan_data: Some(scan_data),
            },
            #[cfg(any(feature = "s132", feature = "s140"))]
            NonconnectableAdvertisement::ExtendedNonscannableUndirected { adv_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED as _,
                adv_data: Some(adv_data),
                scan_data: None,
            },
            #[cfg(any(feature = "s132", feature = "s140"))]
            NonconnectableAdvertisement::ExtendedNonscannableDirected { adv_data } => RawAdvertisement {
                kind: raw::BLE_GAP_ADV_TYPE_EXTENDED_NONCONNECTABLE_NONSCANNABLE_DIRECTED as _,
                adv_data: Some(adv_data),
                scan_data: None,
            },
        }
    }
}

/// Error for [`advertise_start`]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AdvertiseError {
    Timeout,
    NoFreeConn,
    Raw(RawError),
}

impl From<RawError> for AdvertiseError {
    fn from(err: RawError) -> Self {
        AdvertiseError::Raw(err)
    }
}

static mut ADV_HANDLE: u8 = raw::BLE_GAP_ADV_SET_HANDLE_NOT_SET as u8;
pub(crate) static ADV_PORTAL: Portal<*const raw::ble_evt_t> = Portal::new();

fn start_adv(adv: RawAdvertisement<'_>, config: &Config) -> Result<(), AdvertiseError> {
    let mut adv_params: raw::ble_gap_adv_params_t = unsafe { mem::zeroed() };
    adv_params.properties.type_ = adv.kind;
    adv_params.primary_phy = config.primary_phy as u8;
    adv_params.secondary_phy = config.secondary_phy as u8;
    adv_params.duration = config.timeout.map(|t| t.max(1)).unwrap_or(0);
    adv_params.max_adv_evts = config.max_events.map(|t| t.max(1)).unwrap_or(0);
    adv_params.interval = config.interval;

    let map_data = |data: Option<&[u8]>| {
        if let Some(data) = data {
            assert!(data.len() < u16::MAX as usize);
            raw::ble_data_t {
                p_data: data.as_ptr() as _,
                len: data.len() as u16,
            }
        } else {
            raw::ble_data_t {
                p_data: ptr::null_mut(),
                len: 0,
            }
        }
    };

    let datas = raw::ble_gap_adv_data_t {
        adv_data: map_data(adv.adv_data),
        scan_rsp_data: map_data(adv.scan_data),
    };

    let ret = unsafe { raw::sd_ble_gap_adv_set_configure(&mut ADV_HANDLE as _, &datas as _, &adv_params as _) };
    RawError::convert(ret).map_err(|err| {
        warn!("sd_ble_gap_adv_set_configure err {:?}", err);
        err
    })?;

    let ret = unsafe {
        raw::sd_ble_gap_tx_power_set(
            raw::BLE_GAP_TX_POWER_ROLES_BLE_GAP_TX_POWER_ROLE_ADV as _,
            ADV_HANDLE as _,
            config.tx_power as i8,
        )
    };
    RawError::convert(ret).map_err(|err| {
        warn!("sd_ble_gap_tx_power_set err {:?}", err);
        err
    })?;

    let ret = unsafe { raw::sd_ble_gap_adv_start(ADV_HANDLE, 1 as u8) };
    RawError::convert(ret).map_err(|err| {
        warn!("sd_ble_gap_adv_start err {:?}", err);
        err
    })?;

    Ok(())
}

/// Perform connectable advertising, returning the connection that's established as a result.
pub async fn advertise(
    _sd: &Softdevice,
    adv: NonconnectableAdvertisement<'_>,
    config: &Config,
) -> Result<(), AdvertiseError> {
    let d = OnDrop::new(|| {
        let ret = unsafe { raw::sd_ble_gap_adv_stop(ADV_HANDLE) };
        if let Err(_e) = RawError::convert(ret) {
            warn!("sd_ble_gap_adv_stop: {:?}", _e);
        }
    });

    start_adv(adv.into(), config)?;

    // The advertising data needs to be kept alive for the entire duration of the advertising procedure.
    let res = ADV_PORTAL
        .wait_once(|ble_evt| unsafe {
            match (*ble_evt).header.evt_id as u32 {
                raw::BLE_GAP_EVTS_BLE_GAP_EVT_TIMEOUT => Err(AdvertiseError::Timeout),
                raw::BLE_GAP_EVTS_BLE_GAP_EVT_ADV_SET_TERMINATED => Err(AdvertiseError::Timeout),
                e => panic!("unexpected event {}", e),
            }
        })
        .await;

    d.defuse();
    res
}

/// Perform connectable advertising, returning the connection that's established as a result.
pub async fn advertise_connectable(
    _sd: &Softdevice,
    adv: ConnectableAdvertisement<'_>,
    config: &Config,
) -> Result<Connection, AdvertiseError> {
    let d = OnDrop::new(|| {
        let ret = unsafe { raw::sd_ble_gap_adv_stop(ADV_HANDLE) };
        if let Err(_e) = RawError::convert(ret) {
            warn!("sd_ble_gap_adv_stop: {:?}", _e);
        }
    });

    start_adv(adv.into(), config)?;

    // The advertising data needs to be kept alive for the entire duration of the advertising procedure.
    let res = ADV_PORTAL
        .wait_once(|ble_evt| unsafe {
            match (*ble_evt).header.evt_id as u32 {
                raw::BLE_GAP_EVTS_BLE_GAP_EVT_CONNECTED => {
                    let gap_evt = get_union_field(ble_evt, &(*ble_evt).evt.gap_evt);
                    let params = &gap_evt.params.connected;
                    let conn_handle = gap_evt.conn_handle;
                    let role = Role::from_raw(params.role);
                    let peer_address = Address::from_raw(params.peer_addr);
                    let conn_params = params.conn_params;
                    debug!("connected role={:?} peer_addr={:?}", role, peer_address);

                    match Connection::new(conn_handle, role, peer_address, conn_params) {
                        Ok(conn) => {
                            #[cfg(any(feature = "s113", feature = "s132", feature = "s140"))]
                            gap::do_data_length_update(conn_handle, ptr::null());

                            Ok(conn)
                        }
                        Err(_) => {
                            raw::sd_ble_gap_disconnect(
                                conn_handle,
                                raw::BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION as _,
                            );
                            Err(AdvertiseError::NoFreeConn)
                        }
                    }
                }
                raw::BLE_GAP_EVTS_BLE_GAP_EVT_TIMEOUT => Err(AdvertiseError::Timeout),
                raw::BLE_GAP_EVTS_BLE_GAP_EVT_ADV_SET_TERMINATED => Err(AdvertiseError::Timeout),
                e => panic!("unexpected event {}", e),
            }
        })
        .await;

    d.defuse();
    res
}

#[derive(Copy, Clone)]
pub struct Config {
    pub primary_phy: Phy,
    pub secondary_phy: Phy,
    pub tx_power: TxPower,

    /// Timeout duration, in 10ms units
    pub timeout: Option<u16>,
    pub max_events: Option<u8>,

    /// Advertising interval, in 0.625us units
    pub interval: u32,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            primary_phy: Phy::M1,
            secondary_phy: Phy::M1,
            tx_power: TxPower::ZerodBm,
            timeout: None,
            max_events: None,
            interval: 400, // 250ms
        }
    }
}