use crate::{
data_manipulation::{crc24_ble, reverse_bits, whiten},
services::BlePayload,
};
use embedded_hal::{
delay::DelayNs,
digital::{ErrorKind as OutputPinError, OutputPin},
spi::{ErrorKind as SpiError, SpiDevice},
};
use rf24::{
radio::{
prelude::{EsbChannel, EsbPaLevel, EsbRadio},
Nrf24Error, RadioConfig, RF24,
},
CrcLength, PaLevel,
};
pub const BLE_CHANNEL: [u8; 3] = [2, 26, 80];
pub struct BleChannels;
impl BleChannels {
pub fn index_of(channel: u8) -> Option<usize> {
for (index, ch) in BLE_CHANNEL.iter().enumerate() {
if *ch == channel {
return Some(index);
}
}
None
}
pub fn increment(current: u8) -> Option<u8> {
if let Some(index) = Self::index_of(current) {
if index < (BLE_CHANNEL.len() - 1) {
return Some(BLE_CHANNEL[index + 1]);
} else {
return Some(BLE_CHANNEL[0]);
}
}
None
}
}
const BLE_ADDRESS: [u8; 4] = [0x71, 0x91, 0x7d, 0x6b];
pub fn ble_config() -> RadioConfig {
RadioConfig::default()
.with_channel(BLE_CHANNEL[0])
.with_crc_length(CrcLength::Disabled)
.with_auto_ack(0)
.with_auto_retries(0, 0)
.with_address_length(4)
.with_rx_address(1, &BLE_ADDRESS)
.with_tx_address(&BLE_ADDRESS)
}
pub struct FakeBle {
pub(crate) name: [u8; 12],
pub show_pa_level: bool,
pub mac_address: [u8; 6],
}
impl Default for FakeBle {
fn default() -> Self {
Self::new()
}
}
impl FakeBle {
const DEVICE_FLAGS: u8 = 0x42;
const PROFILE_FLAGS: [u8; 3] = [2, 1, 5];
pub fn new() -> Self {
let mut mac_address = [0u8; 6];
mac_address.copy_from_slice(b"nRF24L");
Self {
name: [0u8; 12],
show_pa_level: false,
mac_address,
}
}
pub fn set_name(&mut self, name: &str) {
let len = name.len().min(10);
self.name[2..len + 2].copy_from_slice(&name.as_bytes()[0..len]);
self.name[0] = len as u8 + 1;
self.name[1] = 0x08;
}
pub fn get_name(&self, name: &mut [u8]) -> u8 {
let len = self.name[0];
if len > 1 {
let len = (len as usize - 1).min(name.len());
name[0..len].copy_from_slice(&self.name[2..len + 2]);
return len as u8;
}
0
}
pub fn len_available(&self, hypothetical: &[u8]) -> i8 {
let mut result = 18 - hypothetical.len() as i8;
let name_len = self.name[0];
if name_len > 1 {
result -= name_len as i8 + 1;
}
if self.show_pa_level {
result -= 3;
}
result
}
pub fn hop_channel<SPI, DO, DELAY>(
&self,
radio: &mut RF24<SPI, DO, DELAY>,
) -> Result<(), Nrf24Error<SpiError, OutputPinError>>
where
SPI: SpiDevice,
DO: OutputPin,
DELAY: DelayNs,
{
let channel = radio.get_channel()?;
if let Some(channel) = BleChannels::increment(channel) {
radio.set_channel(channel)?;
}
Ok(())
}
pub fn make_payload(
&self,
buf: &[u8],
pa_level: Option<PaLevel>,
channel: u8,
) -> Option<[u8; 32]> {
let mut payload_length = buf.len() + 9;
let mut tx_queue = [0; 32];
tx_queue[0] = Self::DEVICE_FLAGS;
tx_queue[2..8].copy_from_slice(&self.mac_address);
tx_queue[8..11].copy_from_slice(&Self::PROFILE_FLAGS);
let mut offset = 11;
if let Some(pa_level) = pa_level {
let pa_level: i8 = match pa_level {
rf24::PaLevel::Min => -18,
rf24::PaLevel::Low => -12,
rf24::PaLevel::High => -6,
rf24::PaLevel::Max => 0,
};
payload_length += 3;
offset += 3;
tx_queue[11..offset].copy_from_slice(&[2, 0x0A, pa_level as u8]);
}
if self.name[0] > 1 {
let len = self.name[0] as usize + 1;
payload_length += len;
tx_queue[offset..offset + len].copy_from_slice(&self.name[0..len]);
offset += len;
}
if payload_length > BlePayload::MAX_BLE_PAYLOAD_SIZE as usize {
return None;
}
tx_queue[1] = payload_length as u8;
for byte in buf {
tx_queue[offset] = *byte;
offset += 1;
}
let crc = crc24_ble(&tx_queue[0..offset]);
tx_queue[offset..offset + 3].copy_from_slice(&crc);
offset += 3;
let coefficient = (BleChannels::index_of(channel).unwrap_or_default() + 37) | 0x40;
whiten(&mut tx_queue[0..offset], coefficient as u8);
reverse_bits(&mut tx_queue[0..offset]);
Some(tx_queue)
}
pub fn send<SPI, DO, DELAY>(
&self,
radio: &mut RF24<SPI, DO, DELAY>,
buf: &[u8],
) -> Result<bool, Nrf24Error<SpiError, OutputPinError>>
where
SPI: SpiDevice,
DO: OutputPin,
DELAY: DelayNs,
{
if let Some(tx_queue) = self.make_payload(
buf,
if self.show_pa_level {
Some(radio.get_pa_level()?)
} else {
None
},
radio.get_channel()?,
) {
return radio.send(&tx_queue, false);
}
Ok(false)
}
pub fn read<SPI, DO, DELAY>(
&self,
radio: &mut RF24<SPI, DO, DELAY>,
) -> Result<Option<BlePayload>, Nrf24Error<SpiError, OutputPinError>>
where
SPI: SpiDevice,
DO: OutputPin,
DELAY: DelayNs,
{
let mut buf = [0u8; 32];
radio.read(&mut buf, Some(32))?;
let channel = radio.get_channel()?;
Ok(BlePayload::from_bytes(&mut buf, channel))
}
}
#[cfg(test)]
mod test {
extern crate std;
use super::{ble_config, FakeBle, BLE_ADDRESS, BLE_CHANNEL};
use crate::{spi_test_expects, test::mk_radio};
use embedded_hal_mock::eh1::{
digital::{State, Transaction as PinTransaction},
spi::Transaction as SpiTransaction,
};
use rf24::{CrcLength, PaLevel};
use std::vec;
#[test]
fn name() {
let mut ble = FakeBle::default();
let mut expected = [0u8; 10];
assert_eq!(0, ble.get_name(&mut expected));
ble.set_name("nRF24L");
assert_eq!(6, ble.get_name(&mut expected));
assert!(expected.starts_with(b"nRF24L"));
assert_eq!(ble.len_available(b""), 10);
}
#[test]
fn mac() {
let mut mac = [0u8; 6];
mac.copy_from_slice(b"nRF24L");
let mut ble = FakeBle::default();
ble.mac_address.copy_from_slice(&mac);
assert_eq!(ble.len_available(b""), 18);
}
#[test]
fn pa_level() {
let mut ble = FakeBle::default();
assert_eq!(ble.len_available(b""), 18);
ble.show_pa_level = true;
assert_eq!(ble.len_available(b""), 15);
}
#[test]
fn config() {
let config = ble_config();
assert_eq!(config.channel(), BLE_CHANNEL[0]);
assert_eq!(config.crc_length(), CrcLength::Disabled);
assert_eq!(config.auto_ack(), 0);
assert_eq!(config.auto_retry_count(), 0);
assert_eq!(config.auto_retry_delay(), 0);
assert_eq!(config.address_length(), 4);
let mut address = [0u8; 4];
config.tx_address(&mut address);
assert_eq!(address, BLE_ADDRESS);
config.rx_address(1, &mut address);
assert_eq!(address, BLE_ADDRESS);
for pipe in 0..5 {
let enabled = config.is_rx_pipe_enabled(pipe);
assert_eq!(enabled, pipe == 1);
}
}
const RF_CH: u8 = 5;
const W_REGISTER: u8 = 0x20;
#[test]
fn channel_hop() {
let expectations = spi_test_expects![
(vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[0]]),
(vec![RF_CH | W_REGISTER, BLE_CHANNEL[1]], vec![0xEu8, 0]),
(vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[1]]),
(vec![RF_CH | W_REGISTER, BLE_CHANNEL[2]], vec![0xEu8, 0]),
(vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[2]]),
(vec![RF_CH | W_REGISTER, BLE_CHANNEL[0]], vec![0xEu8, 0]),
(vec![RF_CH, 0], vec![0xEu8, 0]),
];
let mocks = mk_radio(&[], &expectations);
let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
let ble = FakeBle::default();
for _ in 0..4 {
ble.hop_channel(&mut radio).unwrap();
}
spi.done();
ce_pin.done();
}
const R_RX_PAYLOAD: u8 = 0x61;
const STATUS: u8 = 7;
const MASK_RX_DR: u8 = 1 << 6;
#[test]
fn read() {
let ble = FakeBle::default();
let channel = BLE_CHANNEL[0];
let payload = ble.make_payload(&[], None, channel).unwrap();
let mut buf = [0; 33];
buf[1..].copy_from_slice(&payload);
buf[0] = 0xE;
let mut expected = [0; 33];
expected[0] = R_RX_PAYLOAD;
let spi_expectations = spi_test_expects![
(expected.to_vec(), buf.to_vec()),
(vec![STATUS | W_REGISTER, MASK_RX_DR], vec![0xEu8, 0]),
(vec![RF_CH, 0], vec![0xEu8, BLE_CHANNEL[0]]),
];
let mocks = mk_radio(&[], &spi_expectations);
let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
let ble_payload = ble.read(&mut radio).unwrap().unwrap();
assert_eq!(&ble.mac_address, &ble_payload.mac_address);
spi.done();
ce_pin.done();
}
const MASK_TX_DS: u8 = 1 << 5;
const MASK_MAX_RT: u8 = 1 << 4;
const W_TX_PAYLOAD: u8 = 0xA0;
const FLUSH_TX: u8 = 0xE1;
const NOP: u8 = 0xFF;
const RF_SETUP: u8 = 0x06;
fn send_ce_expects() -> vec::Vec<PinTransaction> {
vec![
PinTransaction::set(State::Low),
PinTransaction::set(State::High),
]
}
fn send_spi_expects(
ble: &FakeBle,
pa_level: Option<PaLevel>,
big_buf: bool,
) -> vec::Vec<SpiTransaction<u8>> {
let channel = BLE_CHANNEL[0];
let payload = ble.make_payload(&[], pa_level, channel).unwrap();
let mut buf = [0; 33];
buf[0] = 0xE;
let mut expected = [0; 33];
expected[0] = W_TX_PAYLOAD;
expected[1..].copy_from_slice(&payload);
let mut expectations = vec![];
let bin_pa_level = pa_level.map(|lvl| match lvl {
PaLevel::High => 4,
_ => 6,
});
if let Some(lvl) = bin_pa_level {
expectations
.append(&mut spi_test_expects![(vec![RF_SETUP, 0], vec![0xEu8, lvl]),].to_vec());
}
expectations.append(
&mut spi_test_expects![(
vec![RF_CH, bin_pa_level.unwrap_or_default()],
vec![0xEu8, BLE_CHANNEL[0]]
),]
.to_vec(),
);
if !big_buf {
expectations.append(
&mut spi_test_expects![
(vec![FLUSH_TX], vec![0xEu8]),
(
vec![STATUS | W_REGISTER, MASK_TX_DS | MASK_MAX_RT],
vec![0xEu8, 0]
),
(expected.to_vec(), buf.to_vec()),
(vec![NOP], vec![0xE | MASK_TX_DS]),
]
.to_vec(),
);
}
expectations
}
#[test]
fn send() {
let ble = FakeBle::default();
let spi_expectations = send_spi_expects(&ble, None, false);
let ce_expectations = send_ce_expects();
let mocks = mk_radio(&ce_expectations, &spi_expectations);
let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
assert!(ble.send(&mut radio, &[]).unwrap());
spi.done();
ce_pin.done();
}
#[test]
fn send_pa_level() {
let mut ble = FakeBle::new();
ble.show_pa_level = true;
let spi_expectations = send_spi_expects(&ble, Some(PaLevel::Max), false);
let ce_expectations = send_ce_expects();
let mocks = mk_radio(&ce_expectations, &spi_expectations);
let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
assert!(ble.send(&mut radio, &[]).unwrap());
spi.done();
ce_pin.done();
}
#[test]
fn send_big_buf() {
let mut ble = FakeBle::new();
ble.show_pa_level = true;
let spi_expectations = send_spi_expects(&ble, Some(PaLevel::High), true);
let ce_expectations = [];
let mocks = mk_radio(&ce_expectations, &spi_expectations);
let (mut radio, mut spi, mut ce_pin) = (mocks.0, mocks.1, mocks.2);
assert!(!ble.send(&mut radio, &[0u8; 20]).unwrap());
spi.done();
ce_pin.done();
}
}