#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BlePhy {
#[default]
Le1M,
Le2M,
LeCodedS2,
LeCodedS8,
}
impl BlePhy {
pub fn data_rate_bps(&self) -> u32 {
match self {
BlePhy::Le1M => 1_000_000,
BlePhy::Le2M => 2_000_000,
BlePhy::LeCodedS2 => 500_000,
BlePhy::LeCodedS8 => 125_000,
}
}
pub fn data_rate_kbps(&self) -> u32 {
self.data_rate_bps() / 1000
}
pub fn typical_range_m(&self) -> u16 {
match self {
BlePhy::Le1M => 100,
BlePhy::Le2M => 50,
BlePhy::LeCodedS2 => 200,
BlePhy::LeCodedS8 => 400,
}
}
pub fn typical_latency_ms(&self) -> u16 {
match self {
BlePhy::Le1M => 30,
BlePhy::Le2M => 20,
BlePhy::LeCodedS2 => 50,
BlePhy::LeCodedS8 => 100,
}
}
pub fn is_coded(&self) -> bool {
matches!(self, BlePhy::LeCodedS2 | BlePhy::LeCodedS8)
}
pub fn requires_ble5(&self) -> bool {
!matches!(self, BlePhy::Le1M)
}
pub fn coding_scheme(&self) -> Option<u8> {
match self {
BlePhy::LeCodedS2 => Some(2),
BlePhy::LeCodedS8 => Some(8),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
BlePhy::Le1M => "LE 1M",
BlePhy::Le2M => "LE 2M",
BlePhy::LeCodedS2 => "LE Coded S=2",
BlePhy::LeCodedS8 => "LE Coded S=8",
}
}
pub fn transmit_time_us(&self, bytes: usize) -> u64 {
let bits = (bytes + 10) * 8; let rate = self.data_rate_bps() as u64;
(bits as u64 * 1_000_000) / rate
}
pub fn relative_power(&self) -> f32 {
match self {
BlePhy::Le1M => 1.0,
BlePhy::Le2M => 0.8, BlePhy::LeCodedS2 => 1.5, BlePhy::LeCodedS8 => 2.0, }
}
}
impl core::fmt::Display for BlePhy {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PhyCapabilities {
pub le_2m: bool,
pub le_coded: bool,
}
impl PhyCapabilities {
pub fn le_1m_only() -> Self {
Self {
le_2m: false,
le_coded: false,
}
}
pub fn ble5_full() -> Self {
Self {
le_2m: true,
le_coded: true,
}
}
pub fn ble5_no_coded() -> Self {
Self {
le_2m: true,
le_coded: false,
}
}
pub fn supports(&self, phy: BlePhy) -> bool {
match phy {
BlePhy::Le1M => true, BlePhy::Le2M => self.le_2m,
BlePhy::LeCodedS2 | BlePhy::LeCodedS8 => self.le_coded,
}
}
pub fn best_for_range(&self) -> BlePhy {
if self.le_coded {
BlePhy::LeCodedS8
} else {
BlePhy::Le1M
}
}
pub fn best_for_throughput(&self) -> BlePhy {
if self.le_2m {
BlePhy::Le2M
} else {
BlePhy::Le1M
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PhyPreference {
pub tx: BlePhy,
pub rx: BlePhy,
}
impl Default for PhyPreference {
fn default() -> Self {
Self {
tx: BlePhy::Le1M,
rx: BlePhy::Le1M,
}
}
}
impl PhyPreference {
pub fn symmetric(phy: BlePhy) -> Self {
Self { tx: phy, rx: phy }
}
pub fn is_symmetric(&self) -> bool {
self.tx == self.rx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phy_default() {
assert_eq!(BlePhy::default(), BlePhy::Le1M);
}
#[test]
fn test_phy_data_rates() {
assert_eq!(BlePhy::Le1M.data_rate_kbps(), 1000);
assert_eq!(BlePhy::Le2M.data_rate_kbps(), 2000);
assert_eq!(BlePhy::LeCodedS2.data_rate_kbps(), 500);
assert_eq!(BlePhy::LeCodedS8.data_rate_kbps(), 125);
}
#[test]
fn test_phy_ranges() {
assert_eq!(BlePhy::Le1M.typical_range_m(), 100);
assert_eq!(BlePhy::Le2M.typical_range_m(), 50);
assert_eq!(BlePhy::LeCodedS2.typical_range_m(), 200);
assert_eq!(BlePhy::LeCodedS8.typical_range_m(), 400);
}
#[test]
fn test_phy_is_coded() {
assert!(!BlePhy::Le1M.is_coded());
assert!(!BlePhy::Le2M.is_coded());
assert!(BlePhy::LeCodedS2.is_coded());
assert!(BlePhy::LeCodedS8.is_coded());
}
#[test]
fn test_phy_requires_ble5() {
assert!(!BlePhy::Le1M.requires_ble5());
assert!(BlePhy::Le2M.requires_ble5());
assert!(BlePhy::LeCodedS2.requires_ble5());
assert!(BlePhy::LeCodedS8.requires_ble5());
}
#[test]
fn test_phy_coding_scheme() {
assert_eq!(BlePhy::Le1M.coding_scheme(), None);
assert_eq!(BlePhy::Le2M.coding_scheme(), None);
assert_eq!(BlePhy::LeCodedS2.coding_scheme(), Some(2));
assert_eq!(BlePhy::LeCodedS8.coding_scheme(), Some(8));
}
#[test]
fn test_phy_display() {
assert_eq!(format!("{}", BlePhy::Le1M), "LE 1M");
assert_eq!(format!("{}", BlePhy::LeCodedS8), "LE Coded S=8");
}
#[test]
fn test_phy_transmit_time() {
let time_1m = BlePhy::Le1M.transmit_time_us(100);
let time_2m = BlePhy::Le2M.transmit_time_us(100);
assert!(time_2m < time_1m);
}
#[test]
fn test_phy_capabilities_default() {
let caps = PhyCapabilities::default();
assert!(!caps.le_2m);
assert!(!caps.le_coded);
assert!(caps.supports(BlePhy::Le1M));
assert!(!caps.supports(BlePhy::Le2M));
}
#[test]
fn test_phy_capabilities_ble5() {
let caps = PhyCapabilities::ble5_full();
assert!(caps.supports(BlePhy::Le1M));
assert!(caps.supports(BlePhy::Le2M));
assert!(caps.supports(BlePhy::LeCodedS2));
assert!(caps.supports(BlePhy::LeCodedS8));
}
#[test]
fn test_phy_capabilities_best_for_range() {
let caps = PhyCapabilities::ble5_full();
assert_eq!(caps.best_for_range(), BlePhy::LeCodedS8);
let caps_no_coded = PhyCapabilities::ble5_no_coded();
assert_eq!(caps_no_coded.best_for_range(), BlePhy::Le1M);
}
#[test]
fn test_phy_capabilities_best_for_throughput() {
let caps = PhyCapabilities::ble5_full();
assert_eq!(caps.best_for_throughput(), BlePhy::Le2M);
let caps_basic = PhyCapabilities::le_1m_only();
assert_eq!(caps_basic.best_for_throughput(), BlePhy::Le1M);
}
#[test]
fn test_phy_preference_symmetric() {
let pref = PhyPreference::symmetric(BlePhy::LeCodedS8);
assert_eq!(pref.tx, BlePhy::LeCodedS8);
assert_eq!(pref.rx, BlePhy::LeCodedS8);
assert!(pref.is_symmetric());
}
#[test]
fn test_phy_preference_asymmetric() {
let pref = PhyPreference {
tx: BlePhy::Le2M,
rx: BlePhy::LeCodedS2,
};
assert!(!pref.is_symmetric());
}
#[test]
fn test_relative_power() {
assert!(BlePhy::Le2M.relative_power() < BlePhy::Le1M.relative_power());
assert!(BlePhy::LeCodedS8.relative_power() > BlePhy::Le1M.relative_power());
}
}