use crate::{
data_manipulation::{crc24_ble, reverse_bits, whiten},
BleChannels,
};
#[cfg(feature = "std")]
extern crate std;
const TEMPERATURE_UUID: u16 = 0x1809;
const BATTERY_UUID: u16 = 0x180F;
const EDDYSTONE_UUID: u16 = 0xFEAA;
pub mod prelude {
pub(super) trait FromBuffer {
fn from_buffer(buf: &[u8]) -> Self;
}
pub trait AsBuffer {
fn buffer(&self) -> &[u8];
}
pub trait ServiceData<T> {
fn set_data(&mut self, value: T);
fn data(&self) -> T;
}
}
#[derive(Debug, Clone, Copy)]
pub struct TemperatureService {
buf: [u8; 8],
}
impl Default for TemperatureService {
fn default() -> Self {
Self::new()
}
}
impl TemperatureService {
pub fn new() -> Self {
let mut data = [0u8; 8];
data[0] = 7; data[1] = 0x16; data[2..4].copy_from_slice(&TEMPERATURE_UUID.to_le_bytes());
data[7] = 0xFE;
Self { buf: data }
}
}
impl prelude::ServiceData<f32> for TemperatureService {
fn set_data(&mut self, value: f32) {
let buf = ((value * 100.0) as u32 & 0xFFFFFF).to_le_bytes();
self.buf[4..7].copy_from_slice(&buf[0..3]);
}
fn data(&self) -> f32 {
let mut buf = [0u8; 4];
buf[0..3].copy_from_slice(&self.buf[4..7]);
let value = u32::from_le_bytes(buf);
value as f32 / 100.0
}
}
impl prelude::AsBuffer for TemperatureService {
fn buffer(&self) -> &[u8] {
&self.buf
}
}
impl prelude::FromBuffer for TemperatureService {
fn from_buffer(buf: &[u8]) -> Self {
let mut self_buf = [0u8; 8];
self_buf.copy_from_slice(&buf[0..buf.len().min(8)]);
Self { buf: self_buf }
}
}
#[derive(Debug, Clone, Copy)]
pub struct BatteryService {
buf: [u8; 5],
}
impl Default for BatteryService {
fn default() -> Self {
Self::new()
}
}
impl BatteryService {
pub fn new() -> Self {
let mut data = [0u8; 5];
data[0] = 4; data[1] = 0x16; data[2..4].copy_from_slice(&BATTERY_UUID.to_le_bytes());
Self { buf: data }
}
}
impl prelude::ServiceData<u8> for BatteryService {
fn set_data(&mut self, value: u8) {
self.buf[4] = value;
}
fn data(&self) -> u8 {
self.buf[4]
}
}
impl prelude::AsBuffer for BatteryService {
fn buffer(&self) -> &[u8] {
&self.buf
}
}
impl prelude::FromBuffer for BatteryService {
fn from_buffer(buf: &[u8]) -> Self {
let mut self_buf = [0u8; 5];
self_buf.copy_from_slice(&buf[0..buf.len().min(5)]);
Self { buf: self_buf }
}
}
#[derive(Debug, Clone, Copy)]
pub struct UrlService {
buf: [u8; 18],
}
impl Default for UrlService {
fn default() -> Self {
Self::new()
}
}
impl UrlService {
const CODEX_PREFIX: [&str; 4] = ["http://www.", "https://www.", "http://", "https://"];
const CODEX_SUFFIX: [&str; 14] = [
".com/", ".org/", ".edu/", ".net/", ".info/", ".biz/", ".gov/", ".com", ".org", ".edu",
".net", ".info", ".biz", ".gov",
];
pub fn new() -> Self {
let mut data = [0u8; 18];
data[1] = 0x16; data[2..4].copy_from_slice(&EDDYSTONE_UUID.to_le_bytes());
data[4] = 0x10; data[5] = -25i8 as u8;
Self { buf: data }
}
pub fn set_pa_level(&mut self, level: i8) {
self.buf[5] = level as u8;
}
pub fn pa_level(&self) -> i8 {
self.buf[5] as i8
}
pub fn set_data(&mut self, value: &str) {
let mut index = 6; let max_len = self.buf.len();
let mut pos = 0; let len = value.len();
for (j, pre) in Self::CODEX_PREFIX.iter().enumerate() {
if value[0..len].starts_with(*pre) {
self.buf[index] = j as u8;
pos += pre.len();
index += 1;
break;
}
}
for (i, ch) in value.char_indices() {
if index >= max_len {
break;
}
if i < pos {
continue;
}
for (j, post) in Self::CODEX_SUFFIX.iter().enumerate() {
if value[i..len].starts_with(*post) {
self.buf[index] = j as u8;
pos += post.len();
index += 1;
break;
}
}
if i < pos {
continue;
}
self.buf[index] = ch as u8;
index += 1;
pos += 1;
}
self.buf[0] = index as u8 - 1;
}
#[cfg(feature = "std")]
pub fn data(&self) -> std::string::String {
let mut result = std::string::String::new();
let mut index = 0; let max_len = self.buf[0] - 5;
for (j, pre) in Self::CODEX_PREFIX.iter().enumerate() {
if j as u8 == self.buf[6] {
result.push_str(pre);
index += 1;
break;
}
}
for (i, byte) in self.buf[6..6 + max_len as usize].iter().enumerate() {
if index > i {
continue;
}
for (j, post) in Self::CODEX_SUFFIX.iter().enumerate() {
if j as u8 == *byte {
result.push_str(post);
index += 1;
break;
}
}
if index > i {
continue;
}
result.push(*byte as char);
index += 1;
}
result
}
}
impl prelude::AsBuffer for UrlService {
fn buffer(&self) -> &[u8] {
let len = self.buf[0] + 1;
&self.buf[0..len as usize]
}
}
impl prelude::FromBuffer for UrlService {
fn from_buffer(buf: &[u8]) -> Self {
let max_len = buf.len().min(18);
let mut self_buf = [0u8; 18];
self_buf[0..max_len].copy_from_slice(&buf[0..max_len]);
Self { buf: self_buf }
}
}
pub struct BlePayload {
pub mac_address: [u8; 6],
pub short_name: Option<[u8; 10]>,
pub tx_power: Option<i8>,
pub battery_charge: Option<BatteryService>,
pub url: Option<UrlService>,
pub temperature: Option<TemperatureService>,
}
impl BlePayload {
pub(crate) const MAX_BLE_PAYLOAD_SIZE: u8 = 27;
pub fn from_bytes(buf: &mut [u8], channel: u8) -> Option<Self> {
use prelude::FromBuffer;
reverse_bits(buf);
let coefficient = (BleChannels::index_of(channel).unwrap_or_default() as u8 + 37) | 0x40;
whiten(buf, coefficient);
let len = buf[1];
if len > Self::MAX_BLE_PAYLOAD_SIZE {
return None;
}
let len = len as usize + 2;
let mut crc = [0u8; 3];
crc.copy_from_slice(&buf[len..len + 3]);
let expected = crc24_ble(&buf[0..len]);
if crc != expected {
return None;
}
let mut mac_address = [0u8; 6];
mac_address.copy_from_slice(&buf[2..8]);
let mut tx_power = None;
let mut short_name = None;
let mut battery_charge = None;
let mut temperature = None;
let mut url = None;
let mut index = 8_usize;
while index < len {
let chunk_len = (buf[index] - 1) as usize;
let chunk_type = buf[index + 1];
let start = index + 2;
let end = index + chunk_len + 2;
match chunk_type {
0x08 | 0x09 => {
let mut name = [0u8; 10];
let name_len = (end - start).min(10);
name[0..name_len].copy_from_slice(&buf[start..start + name_len]);
short_name = Some(name);
}
0x0A => {
tx_power = Some(buf[start] as i8);
}
0x16 => {
let mut tmp = [0u8; 2];
tmp.copy_from_slice(&buf[start..start + 2]);
let service_id = u16::from_le_bytes(tmp);
match service_id {
BATTERY_UUID => {
let batt = BatteryService::from_buffer(&buf[index..end]);
battery_charge = Some(batt);
}
TEMPERATURE_UUID => {
let temp = TemperatureService::from_buffer(&buf[index..end]);
temperature = Some(temp);
}
EDDYSTONE_UUID => {
let eddystone = UrlService::from_buffer(&buf[index..end]);
url = Some(eddystone);
}
_ => {}
}
}
_ => {
}
}
index = end;
}
Some(Self {
mac_address,
short_name,
tx_power,
battery_charge,
url,
temperature,
})
}
}
#[cfg(test)]
mod test {
use rf24::PaLevel;
use super::{
prelude::{AsBuffer, ServiceData},
BatteryService, BlePayload, TemperatureService, UrlService,
};
use crate::data_manipulation::{reverse_bits, whiten};
use crate::{BleChannels, FakeBle, BLE_CHANNEL};
#[test]
fn battery_service() {
let mut battery = BatteryService::default();
battery.set_data(85);
assert_eq!(battery.data(), 85);
assert_eq!([0x04, 0x16, 0x0F, 0x18, 0x55], *battery.buffer());
}
#[test]
fn temperature_service() {
let mut temp = TemperatureService::default();
temp.set_data(45.5);
assert_eq!(temp.data(), 45.5);
assert_eq!(
[0x07, 0x16, 0x09, 0x18, 0xC6, 0x11, 0x00, 0xFE],
*temp.buffer()
);
}
#[test]
fn url_service() {
let mut url = UrlService::default();
url.set_data("https://www.foo.com/bar/bazz");
url.set_pa_level(-20);
assert_eq!(url.pa_level(), -20);
assert_eq!(
[
0x11, 0x16, 0xAA, 0xFE, 0x10, 0xEC, 0x01, 0x66, 0x6F, 0x6F, 0x00, 0x62, 0x61, 0x72,
0x2F, 0x62, 0x61, 0x7A
],
*(url.buffer())
);
}
#[test]
fn rx_battery() {
let mut service = BatteryService::default();
service.set_data(85);
let mut ble = FakeBle::default();
ble.set_name("nRF24L01");
let channel = BLE_CHANNEL[0];
let mut payload = ble
.make_payload(service.buffer(), Some(PaLevel::Low), channel)
.unwrap();
let ble_payload = BlePayload::from_bytes(&mut payload, channel).unwrap();
assert_eq!(&ble.mac_address, &ble_payload.mac_address);
assert_eq!(&ble_payload.short_name.unwrap(), &ble.name[2..]);
assert_eq!(ble_payload.tx_power.unwrap(), -12);
assert_eq!(
ble_payload.battery_charge.unwrap().buffer(),
service.buffer()
);
}
#[test]
fn rx_temperature() {
let mut service = TemperatureService::default();
service.set_data(45.5);
let ble = FakeBle::default();
let channel = BLE_CHANNEL[0];
let mut payload = ble.make_payload(service.buffer(), None, channel).unwrap();
let ble_payload = BlePayload::from_bytes(&mut payload, channel).unwrap();
assert_eq!(&ble.mac_address, &ble_payload.mac_address);
assert_eq!(ble_payload.temperature.unwrap().buffer(), service.buffer());
}
#[test]
fn rx_url() {
let mut service = UrlService::default();
service.set_data("https://www.google.com");
let buffer = service.buffer();
let ble = FakeBle::default();
let channel = BLE_CHANNEL[0];
let mut payload = ble.make_payload(buffer, None, channel).unwrap();
let ble_payload = BlePayload::from_bytes(&mut payload, channel).unwrap();
assert_eq!(&ble.mac_address, &ble_payload.mac_address);
for (i, byte) in ble_payload.url.unwrap().buffer().iter().enumerate() {
assert_eq!(buffer[i], *byte);
}
}
#[test]
fn rx_too_big() {
let channel = BLE_CHANNEL[0];
let coefficient = (BleChannels::index_of(channel).unwrap() as u8 + 37) | 0x40;
let mut payload = [0u8; 32];
payload[1] = 29;
whiten(&mut payload, coefficient);
reverse_bits(&mut payload);
assert!(BlePayload::from_bytes(&mut payload, channel).is_none());
}
#[test]
fn rx_bad_crc() {
let ble = FakeBle::default();
let channel = BLE_CHANNEL[0];
let coefficient = (BleChannels::index_of(channel).unwrap() as u8 + 37) | 0x40;
let mut payload = ble.make_payload(&[17u8; 18], None, channel).unwrap();
reverse_bits(&mut payload[29..32]);
assert!(BlePayload::from_bytes(&mut payload, coefficient).is_none());
}
#[test]
fn rx_unsupported_service() {
let buffer = [4u8, 0x16, 0xFF, 0x0F, 0xFF];
let ble = FakeBle::default();
let channel = BLE_CHANNEL[0];
let mut payload = ble
.make_payload(&buffer, Some(PaLevel::Min), channel)
.unwrap();
let ble_payload = BlePayload::from_bytes(
&mut payload,
(BleChannels::index_of(channel).unwrap() as u8 + 37) | 0x40,
)
.unwrap();
assert_eq!(&ble.mac_address, &ble_payload.mac_address);
}
}