1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
28pub enum BlePhy {
29 #[default]
34 Le1M,
35
36 Le2M,
41
42 LeCodedS2,
47
48 LeCodedS8,
53}
54
55impl BlePhy {
56 pub fn data_rate_bps(&self) -> u32 {
58 match self {
59 BlePhy::Le1M => 1_000_000,
60 BlePhy::Le2M => 2_000_000,
61 BlePhy::LeCodedS2 => 500_000,
62 BlePhy::LeCodedS8 => 125_000,
63 }
64 }
65
66 pub fn data_rate_kbps(&self) -> u32 {
68 self.data_rate_bps() / 1000
69 }
70
71 pub fn typical_range_m(&self) -> u16 {
73 match self {
74 BlePhy::Le1M => 100,
75 BlePhy::Le2M => 50,
76 BlePhy::LeCodedS2 => 200,
77 BlePhy::LeCodedS8 => 400,
78 }
79 }
80
81 pub fn typical_latency_ms(&self) -> u16 {
83 match self {
84 BlePhy::Le1M => 30,
85 BlePhy::Le2M => 20,
86 BlePhy::LeCodedS2 => 50,
87 BlePhy::LeCodedS8 => 100,
88 }
89 }
90
91 pub fn is_coded(&self) -> bool {
93 matches!(self, BlePhy::LeCodedS2 | BlePhy::LeCodedS8)
94 }
95
96 pub fn requires_ble5(&self) -> bool {
98 !matches!(self, BlePhy::Le1M)
99 }
100
101 pub fn coding_scheme(&self) -> Option<u8> {
103 match self {
104 BlePhy::LeCodedS2 => Some(2),
105 BlePhy::LeCodedS8 => Some(8),
106 _ => None,
107 }
108 }
109
110 pub fn name(&self) -> &'static str {
112 match self {
113 BlePhy::Le1M => "LE 1M",
114 BlePhy::Le2M => "LE 2M",
115 BlePhy::LeCodedS2 => "LE Coded S=2",
116 BlePhy::LeCodedS8 => "LE Coded S=8",
117 }
118 }
119
120 pub fn transmit_time_us(&self, bytes: usize) -> u64 {
122 let bits = (bytes + 10) * 8; let rate = self.data_rate_bps() as u64;
125 (bits as u64 * 1_000_000) / rate
126 }
127
128 pub fn relative_power(&self) -> f32 {
130 match self {
131 BlePhy::Le1M => 1.0,
132 BlePhy::Le2M => 0.8, BlePhy::LeCodedS2 => 1.5, BlePhy::LeCodedS8 => 2.0, }
136 }
137}
138
139impl core::fmt::Display for BlePhy {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 write!(f, "{}", self.name())
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
147pub struct PhyCapabilities {
148 pub le_2m: bool,
150 pub le_coded: bool,
152}
153
154impl PhyCapabilities {
155 pub fn le_1m_only() -> Self {
157 Self {
158 le_2m: false,
159 le_coded: false,
160 }
161 }
162
163 pub fn ble5_full() -> Self {
165 Self {
166 le_2m: true,
167 le_coded: true,
168 }
169 }
170
171 pub fn ble5_no_coded() -> Self {
173 Self {
174 le_2m: true,
175 le_coded: false,
176 }
177 }
178
179 pub fn supports(&self, phy: BlePhy) -> bool {
181 match phy {
182 BlePhy::Le1M => true, BlePhy::Le2M => self.le_2m,
184 BlePhy::LeCodedS2 | BlePhy::LeCodedS8 => self.le_coded,
185 }
186 }
187
188 pub fn best_for_range(&self) -> BlePhy {
190 if self.le_coded {
191 BlePhy::LeCodedS8
192 } else {
193 BlePhy::Le1M
194 }
195 }
196
197 pub fn best_for_throughput(&self) -> BlePhy {
199 if self.le_2m {
200 BlePhy::Le2M
201 } else {
202 BlePhy::Le1M
203 }
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub struct PhyPreference {
210 pub tx: BlePhy,
212 pub rx: BlePhy,
214}
215
216impl Default for PhyPreference {
217 fn default() -> Self {
218 Self {
219 tx: BlePhy::Le1M,
220 rx: BlePhy::Le1M,
221 }
222 }
223}
224
225impl PhyPreference {
226 pub fn symmetric(phy: BlePhy) -> Self {
228 Self { tx: phy, rx: phy }
229 }
230
231 pub fn is_symmetric(&self) -> bool {
233 self.tx == self.rx
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_phy_default() {
243 assert_eq!(BlePhy::default(), BlePhy::Le1M);
244 }
245
246 #[test]
247 fn test_phy_data_rates() {
248 assert_eq!(BlePhy::Le1M.data_rate_kbps(), 1000);
249 assert_eq!(BlePhy::Le2M.data_rate_kbps(), 2000);
250 assert_eq!(BlePhy::LeCodedS2.data_rate_kbps(), 500);
251 assert_eq!(BlePhy::LeCodedS8.data_rate_kbps(), 125);
252 }
253
254 #[test]
255 fn test_phy_ranges() {
256 assert_eq!(BlePhy::Le1M.typical_range_m(), 100);
257 assert_eq!(BlePhy::Le2M.typical_range_m(), 50);
258 assert_eq!(BlePhy::LeCodedS2.typical_range_m(), 200);
259 assert_eq!(BlePhy::LeCodedS8.typical_range_m(), 400);
260 }
261
262 #[test]
263 fn test_phy_is_coded() {
264 assert!(!BlePhy::Le1M.is_coded());
265 assert!(!BlePhy::Le2M.is_coded());
266 assert!(BlePhy::LeCodedS2.is_coded());
267 assert!(BlePhy::LeCodedS8.is_coded());
268 }
269
270 #[test]
271 fn test_phy_requires_ble5() {
272 assert!(!BlePhy::Le1M.requires_ble5());
273 assert!(BlePhy::Le2M.requires_ble5());
274 assert!(BlePhy::LeCodedS2.requires_ble5());
275 assert!(BlePhy::LeCodedS8.requires_ble5());
276 }
277
278 #[test]
279 fn test_phy_coding_scheme() {
280 assert_eq!(BlePhy::Le1M.coding_scheme(), None);
281 assert_eq!(BlePhy::Le2M.coding_scheme(), None);
282 assert_eq!(BlePhy::LeCodedS2.coding_scheme(), Some(2));
283 assert_eq!(BlePhy::LeCodedS8.coding_scheme(), Some(8));
284 }
285
286 #[test]
287 fn test_phy_display() {
288 assert_eq!(format!("{}", BlePhy::Le1M), "LE 1M");
289 assert_eq!(format!("{}", BlePhy::LeCodedS8), "LE Coded S=8");
290 }
291
292 #[test]
293 fn test_phy_transmit_time() {
294 let time_1m = BlePhy::Le1M.transmit_time_us(100);
296 let time_2m = BlePhy::Le2M.transmit_time_us(100);
297
298 assert!(time_2m < time_1m);
300 }
301
302 #[test]
303 fn test_phy_capabilities_default() {
304 let caps = PhyCapabilities::default();
305 assert!(!caps.le_2m);
306 assert!(!caps.le_coded);
307 assert!(caps.supports(BlePhy::Le1M));
308 assert!(!caps.supports(BlePhy::Le2M));
309 }
310
311 #[test]
312 fn test_phy_capabilities_ble5() {
313 let caps = PhyCapabilities::ble5_full();
314 assert!(caps.supports(BlePhy::Le1M));
315 assert!(caps.supports(BlePhy::Le2M));
316 assert!(caps.supports(BlePhy::LeCodedS2));
317 assert!(caps.supports(BlePhy::LeCodedS8));
318 }
319
320 #[test]
321 fn test_phy_capabilities_best_for_range() {
322 let caps = PhyCapabilities::ble5_full();
323 assert_eq!(caps.best_for_range(), BlePhy::LeCodedS8);
324
325 let caps_no_coded = PhyCapabilities::ble5_no_coded();
326 assert_eq!(caps_no_coded.best_for_range(), BlePhy::Le1M);
327 }
328
329 #[test]
330 fn test_phy_capabilities_best_for_throughput() {
331 let caps = PhyCapabilities::ble5_full();
332 assert_eq!(caps.best_for_throughput(), BlePhy::Le2M);
333
334 let caps_basic = PhyCapabilities::le_1m_only();
335 assert_eq!(caps_basic.best_for_throughput(), BlePhy::Le1M);
336 }
337
338 #[test]
339 fn test_phy_preference_symmetric() {
340 let pref = PhyPreference::symmetric(BlePhy::LeCodedS8);
341 assert_eq!(pref.tx, BlePhy::LeCodedS8);
342 assert_eq!(pref.rx, BlePhy::LeCodedS8);
343 assert!(pref.is_symmetric());
344 }
345
346 #[test]
347 fn test_phy_preference_asymmetric() {
348 let pref = PhyPreference {
349 tx: BlePhy::Le2M,
350 rx: BlePhy::LeCodedS2,
351 };
352 assert!(!pref.is_symmetric());
353 }
354
355 #[test]
356 fn test_relative_power() {
357 assert!(BlePhy::Le2M.relative_power() < BlePhy::Le1M.relative_power());
358 assert!(BlePhy::LeCodedS8.relative_power() > BlePhy::Le1M.relative_power());
359 }
360}