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
//! BLE beacon support, without dealing with Link-Layer stuff.
use crate::link::advertising::{Header, Pdu, PduBuf};
use crate::link::filter::{self, AddressFilter, ScanFilter};
use crate::link::{
ad_structure::AdStructure, Cmd, DeviceAddress, NextUpdate, RadioCmd, Transmitter,
};
use crate::phy::AdvertisingChannel;
use crate::time::{Duration, Instant};
use crate::{bytes::*, Error};
/// A BLE beacon.
///
/// FIXME: This has to randomly offset the broadcast interval
pub struct Beacon {
pdu: PduBuf,
}
impl Beacon {
/// Creates a new beacon that will broadcast a packet on all advertisement
/// channels.
///
/// # Parameters
///
/// * **`addr`**: Address of the beacon device.
/// * **`data`**: Data to broadcast. This must fit within a single PDU.
///
/// # Errors
///
/// If `data` doesn't fit in a single PDU, an error will be returned.
pub fn new(addr: DeviceAddress, data: &[AdStructure<'_>]) -> Result<Self, Error> {
let pdu = PduBuf::beacon(addr, data)?;
Ok(Self { pdu })
}
/// Broadcasts the beacon data using `tx`.
///
/// This will broadcast once on every advertising channel.
pub fn broadcast<T: Transmitter>(&self, tx: &mut T) {
// The spec says that we have to broadcast on all 3 channels in sequence, so that the total
// time of this broadcast ("advertising event") is <10ms.
// The transmitter retains the old transmit buffer, but it might be used for other purposes
// (like sending multiple beacon payloads), so we always copy our payload into the tx
// buffer here.
let payload = self.pdu.payload();
let buf = tx.tx_payload_buf();
buf[..payload.len()].copy_from_slice(payload);
for channel in AdvertisingChannel::iter_all() {
tx.transmit_advertising(self.pdu.header(), channel);
}
}
}
/// Callback for the [`BeaconScanner`].
pub trait ScanCallback {
/// Called when a beacon is received and has passed the configured device address filter.
///
/// # Parameters
///
/// * **`adv_addr`**: Address of the device sending the beacon.
/// * **`adv_data`**: Advertising data structures attached to the beacon.
fn beacon<'a, I>(&mut self, adv_addr: DeviceAddress, adv_data: I)
where
I: Iterator<Item = AdStructure<'a>>;
}
/// A passive scanner for non-connectable beacon advertisements.
pub struct BeaconScanner<C: ScanCallback, F: AddressFilter> {
cb: C,
filter: ScanFilter<F>,
interval: Duration,
channel: AdvertisingChannel,
}
impl<C: ScanCallback> BeaconScanner<C, filter::AllowAll> {
/// Creates a `BeaconScanner` that will report beacons from any device.
pub fn new(callback: C) -> Self {
Self::with_filter(callback, filter::AllowAll)
}
}
impl<C: ScanCallback, F: AddressFilter> BeaconScanner<C, F> {
/// Creates a `BeaconScanner` with a custom device filter.
pub fn with_filter(callback: C, scan_filter: F) -> Self {
Self {
cb: callback,
filter: ScanFilter::new(scan_filter),
interval: Duration::from_micros(0),
channel: AdvertisingChannel::first(),
}
}
/// Configures the `BeaconScanner` and returns a `Cmd` to apply to the radio.
///
/// The `next_update` field of the returned `Cmd` specifies when to call `timer_update` the next
/// time. The timer used for this does not have to be very accurate, it is only used to switch
/// to the next advertising channel after `interval` elapses.
pub fn configure(&mut self, now: Instant, interval: Duration) -> Cmd {
self.interval = interval;
self.channel = AdvertisingChannel::first();
Cmd {
// Switch channels
next_update: NextUpdate::At(now + self.interval),
radio: RadioCmd::ListenAdvertising {
channel: self.channel,
},
queued_work: false,
}
}
/// Updates the `BeaconScanner` after the configured timer has fired.
///
/// This switches to the next advertising channel and will listen there.
pub fn timer_update(&mut self, now: Instant) -> Cmd {
self.channel = self.channel.cycle();
Cmd {
// Switch channels
next_update: NextUpdate::At(now + self.interval),
radio: RadioCmd::ListenAdvertising {
channel: self.channel,
},
queued_work: false,
}
}
/// Processes a received advertising channel packet.
///
/// This should be called whenever the radio receives a packet on the configured advertising
/// channel.
pub fn process_adv_packet(&mut self, header: Header, payload: &[u8], crc_ok: bool) -> Cmd {
if crc_ok && header.type_().is_beacon() {
// Partially decode to get the device ID and run it through the filter
if let Ok(pdu) = Pdu::from_header_and_payload(header, &mut ByteReader::new(payload)) {
if self.filter.should_scan(*pdu.sender()) {
let ad = pdu.advertising_data().unwrap();
self.cb.beacon(*pdu.sender(), ad);
}
}
}
Cmd {
next_update: NextUpdate::Keep,
radio: RadioCmd::ListenAdvertising {
channel: self.channel,
},
queued_work: false,
}
}
}