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