1use core::iter::FusedIterator;
2
3use super::{param, param_slice, BdAddr, ConnHandle, Duration, RemainingBytes};
4use crate::{ByteAlignedValue, FixedSizeValue, FromHciBytes, FromHciBytesError, WriteHci};
5
6param!(struct AddrKind(u8));
7
8#[allow(missing_docs)]
9impl AddrKind {
10 pub const PUBLIC: AddrKind = AddrKind(0);
11 pub const RANDOM: AddrKind = AddrKind(1);
12 pub const RESOLVABLE_PRIVATE_OR_PUBLIC: AddrKind = AddrKind(2);
13 pub const RESOLVABLE_PRIVATE_OR_RANDOM: AddrKind = AddrKind(3);
14 pub const ANONYMOUS_ADV: AddrKind = AddrKind(0xff);
15
16 pub const fn new(v: u8) -> Self {
18 Self(v)
19 }
20
21 pub fn as_raw(&self) -> u8 {
23 self.0
24 }
25}
26
27unsafe impl ByteAlignedValue for AddrKind {}
28
29impl<'de> crate::FromHciBytes<'de> for &'de AddrKind {
30 #[inline(always)]
31 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
32 <AddrKind as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
33 }
34}
35
36param! {
37 bitfield AdvChannelMap[1] {
38 (0, is_channel_37_enabled, enable_channel_37);
39 (1, is_channel_38_enabled, enable_channel_38);
40 (2, is_channel_39_enabled, enable_channel_39);
41 }
42}
43
44#[allow(missing_docs)]
45impl AdvChannelMap {
46 pub const ALL: AdvChannelMap = AdvChannelMap(0x07);
47 pub const CHANNEL_37: AdvChannelMap = AdvChannelMap(0x01);
48 pub const CHANNEL_38: AdvChannelMap = AdvChannelMap(0x02);
49 pub const CHANNEL_39: AdvChannelMap = AdvChannelMap(0x04);
50}
51
52param!(struct ChannelMap([u8; 5]));
53
54impl ChannelMap {
55 pub fn new() -> Self {
57 Self([0xff, 0xff, 0xff, 0xff, 0x1f])
58 }
59
60 pub fn is_channel_bad(&self, channel: u8) -> bool {
62 let byte = usize::from(channel / 8);
63 let bit = channel % 8;
64 (self.0[byte] & (1 << bit)) == 0
65 }
66
67 pub fn set_channel_bad(&mut self, channel: u8, bad: bool) {
69 let byte = usize::from(channel / 8);
70 let bit = channel % 8;
71 self.0[byte] = (self.0[byte] & !(1 << bit)) | (u8::from(!bad) << bit);
72 }
73}
74
75unsafe impl ByteAlignedValue for ChannelMap {}
76
77impl<'de> crate::FromHciBytes<'de> for &'de ChannelMap {
78 #[inline(always)]
79 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
80 <ChannelMap as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
81 }
82}
83
84param! {
85 #[derive(Default)]
86 enum AdvKind {
87 #[default]
88 AdvInd = 0,
89 AdvDirectIndHigh = 1,
90 AdvScanInd = 2,
91 AdvNonconnInd = 3,
92 AdvDirectIndLow = 4,
93 }
94}
95
96param! {
97 #[derive(Default)]
98 enum AdvFilterPolicy {
99 #[default]
100 Unfiltered = 0,
101 FilterScan = 1,
102 FilterConn = 2,
103 FilterConnAndScan = 3,
104 }
105}
106
107param! {
108 #[derive(Default)]
109 enum LeScanKind {
110 #[default]
111 Passive = 0,
112 Active = 1,
113 }
114}
115
116param! {
117 #[derive(Default)]
118 enum ScanningFilterPolicy {
119 #[default]
120 BasicUnfiltered = 0,
121 BasicFiltered = 1,
122 ExtUnfiltered = 2,
123 ExtFiltered = 3,
124 }
125}
126
127param! {
128 #[derive(Default)]
129 enum PhyKind {
130 #[default]
131 Le1M = 1,
132 Le2M = 2,
133 LeCoded = 3,
134 LeCodedS2 = 4,
135 }
136}
137
138param! {
139 bitfield SpacingTypes[2] {
140 (0, has_t_ifs_acl_cp, set_t_ifs_acl_cp);
141 (1, has_t_ifs_acl_pc, set_t_ifs_acl_pc);
142 (2, has_t_mces, set_t_mces);
143 (3, has_t_ifs_cis, set_t_ifs_cis);
144 (4, has_t_mss_cis, set_t_mss_cis);
145 }
146}
147
148param! {
149 enum FrameSpaceInitiator {
150 LocalHostInitiated = 0x00,
151 LocalControllerInitiated = 0x01,
152 PeerInitiated = 0x02,
153 }
154}
155
156param! {
157 bitfield AllPhys[1] {
158 (0, has_no_tx_phy_preference, set_has_no_tx_phy_preference);
159 (1, has_no_rx_phy_preference, set_has_no_rx_phy_preference);
160 }
161}
162
163param! {
164 bitfield PhyMask[1] {
165 (0, has_le_1m_phy, set_le_1m_phy);
166 (1, has_le_2m_phy, set_le_2m_phy);
167 (2, has_le_coded_phy, set_le_coded_phy);
168 }
169}
170
171#[derive(Default)]
173#[repr(u16, align(1))]
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
175#[cfg_attr(feature = "defmt", derive(defmt::Format))]
176#[allow(missing_docs)]
177pub enum PhyOptions {
178 #[default]
179 NoPreferredCoding = 0,
180 S2CodingPreferred = 1,
181 S8CodingPreferred = 2,
182}
183
184unsafe impl FixedSizeValue for PhyOptions {
185 #[inline(always)]
186 fn is_valid(data: &[u8]) -> bool {
187 data[0] == 0 || data[0] == 1 || data[0] == 2
188 }
189}
190
191unsafe impl ByteAlignedValue for PhyOptions {}
192
193impl<'de> FromHciBytes<'de> for &'de PhyOptions {
194 #[inline(always)]
195 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> {
196 <PhyOptions as ByteAlignedValue>::ref_from_hci_bytes(data)
197 }
198}
199
200#[derive(Default)]
202#[repr(u8)]
203#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
204#[cfg_attr(feature = "defmt", derive(defmt::Format))]
205#[allow(missing_docs)]
206pub enum AdvPhyOptions {
207 #[default]
208 NoPreferredCoding = 0,
209 S2CodingPreferred = 1,
210 S8CodingPreferred = 2,
211 S2CodingRequired = 3,
212 S8CodingRequired = 4,
213}
214
215unsafe impl FixedSizeValue for AdvPhyOptions {
216 #[inline(always)]
217 fn is_valid(data: &[u8]) -> bool {
218 data[0] == 0 || data[0] == 1 || data[0] == 2 || data[0] == 3 || data[0] == 4
219 }
220}
221
222unsafe impl ByteAlignedValue for AdvPhyOptions {}
223
224impl<'de> FromHciBytes<'de> for &'de AdvPhyOptions {
225 #[inline(always)]
226 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> {
227 <AdvPhyOptions as ByteAlignedValue>::ref_from_hci_bytes(data)
228 }
229}
230
231param! {
232 struct ScanningPhy {
233 active_scan: bool,
234 scan_interval: Duration<625>,
235 scan_window: Duration<625>,
236 }
237}
238
239param! {
240 struct InitiatingPhy {
241 scan_interval: Duration<625>,
242 scan_window: Duration<625>,
243 conn_interval_min: Duration<1_250>,
244 conn_interval_max: Duration<1_250>,
245 max_latency: u16,
246 supervision_timeout: Duration<10_000>,
247 min_ce_len: Duration<625>,
248 max_ce_len: Duration<625>,
249 }
250}
251
252param! {
253 struct ConnIntervalGroup {
254 min: Duration<125>,
255 max: Duration<125>,
256 stride: Duration<125>,
257 }
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
262#[cfg_attr(feature = "defmt", derive(defmt::Format))]
263pub struct PhyParams<T> {
264 pub le_1m_phy: Option<T>,
266 pub le_2m_phy: Option<T>,
268 pub le_coded_phy: Option<T>,
270}
271
272impl<T> PhyParams<T> {
273 pub fn scanning_phys(&self) -> PhyMask {
275 PhyMask::new()
276 .set_le_1m_phy(self.le_1m_phy.is_some())
277 .set_le_2m_phy(self.le_2m_phy.is_some())
278 .set_le_coded_phy(self.le_coded_phy.is_some())
279 }
280}
281
282impl<T: WriteHci> WriteHci for PhyParams<T> {
283 #[inline(always)]
284 fn size(&self) -> usize {
285 1 + self.le_1m_phy.size() + self.le_2m_phy.size() + self.le_coded_phy.size()
286 }
287
288 #[inline(always)]
289 fn write_hci<W: embedded_io::Write>(&self, mut writer: W) -> Result<(), W::Error> {
290 self.scanning_phys().write_hci(&mut writer)?;
291 self.le_1m_phy.write_hci(&mut writer)?;
292 self.le_2m_phy.write_hci(&mut writer)?;
293 self.le_coded_phy.write_hci(&mut writer)?;
294 Ok(())
295 }
296
297 #[inline(always)]
298 async fn write_hci_async<W: ::embedded_io_async::Write>(&self, mut writer: W) -> Result<(), W::Error> {
299 self.scanning_phys().write_hci_async(&mut writer).await?;
300 self.le_1m_phy.write_hci_async(&mut writer).await?;
301 self.le_2m_phy.write_hci_async(&mut writer).await?;
302 self.le_coded_phy.write_hci_async(&mut writer).await?;
303 Ok(())
304 }
305}
306
307param!(struct AdvHandle(u8));
308
309impl AdvHandle {
310 pub const fn new(v: u8) -> Self {
312 Self(v)
313 }
314
315 pub fn as_raw(&self) -> u8 {
317 self.0
318 }
319}
320
321unsafe impl ByteAlignedValue for AdvHandle {}
322
323impl<'de> crate::FromHciBytes<'de> for &'de AdvHandle {
324 #[inline(always)]
325 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
326 <AdvHandle as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
327 }
328}
329
330param! {
331 bitfield AdvEventProps[2] {
332 (0, connectable_adv, set_connectable_adv);
333 (1, scannable_adv, set_scannable_adv);
334 (2, directed_adv, set_directed_adv);
335 (3, high_duty_cycle_directed_connectable_adv, set_high_duty_cycle_directed_connectable_adv);
336 (4, legacy_adv, set_legacy_adv);
337 (5, anonymous_adv, set_anonymous_adv);
338 (6, include_tx_power, set_include_tx_power);
339 }
340}
341
342param! {
343 #[derive(Default)]
344 enum Operation {
345 #[default]
346 IntermediateFragment = 0,
347 FirstFragment = 1,
348 LastFragment = 2,
349 Complete = 3,
350 Unchanged = 4,
351 }
352}
353
354param! {
355 struct AdvSet {
356 adv_handle: AdvHandle,
357 duration: Duration<10_000>,
358 max_ext_adv_events: u8,
359 }
360}
361
362param_slice!(&'a [AdvSet]);
363
364param! {
365 bitfield PeriodicAdvProps[2] {
366 (6, is_tx_power_included, include_tx_power);
367 }
368}
369
370param! {
371 #[derive(Default)]
372 enum FilterDuplicates {
373 #[default]
374 Disabled = 0,
375 Enabled = 1,
376 EnabledPerScanPeriod = 2,
377 }
378}
379
380param! {
381 bitfield LePeriodicAdvCreateSyncOptions[1] {
382 (0, is_using_periodic_adv_list, use_periodic_adv_list);
383 (1, is_reporting_initially_disabled, disable_initial_reporting);
384 (2, is_duplicate_filtering_enabled, enable_duplicate_filtering);
385 }
386}
387
388param! {
389 bitfield CteMask[1] {
390 (0, is_aoa_cte, set_aoa_cte);
391 (1, is_aod_1us_cte, set_aod_1us_cte);
392 (2, is_aod_2us_cte, set_aod_2us_cte);
393 (3, is_type_3_cte, set_type_3_cte);
394 (4, is_non_cte, set_non_cte);
395 }
396}
397
398param!(struct SyncHandle(u16));
399
400param!(struct BigHandle(u8));
401
402param! {
403 #[derive(Default)]
404 enum PrivacyMode {
405 #[default]
406 Network = 0,
407 Device = 1,
408 }
409}
410
411param! {
412 #[derive(Default)]
413 enum CteKind {
414 #[default]
415 AoA = 0,
416 AoD1Us = 1,
417 AoD2Us = 2,
418 NoCte = 0xff,
419 }
420}
421
422param! {
423 bitfield SwitchingSamplingRates[1] {
424 (0, is_1us_aod_tx, set_1us_aod_tx);
425 (1, is_1us_aod_rx, set_1us_aod_rx);
426 (2, is_1us_aoa_rx, set_1us_aoa_rx);
427 }
428}
429
430param! {
431 bitfield LePeriodicAdvReceiveEnable[1] {
432 (0, is_reporting, set_reporting);
433 (1, is_duplicate_filtering, set_duplicate_filtering);
434 }
435}
436
437param! {
438 #[derive(Default)]
439 enum LePeriodicAdvSyncTransferMode {
440 #[default]
441 NoSync = 0,
442 SyncRx = 1,
443 SyncRxReport = 2,
444 SyncRxReportFilterDuplicates = 3,
445 }
446}
447
448param!(struct LeHostFeature(u8));
449
450#[allow(missing_docs)]
451impl LeHostFeature {
452 pub const CONN_ISO_STREAM: LeHostFeature = LeHostFeature(32);
453 pub const CONN_SUBRATING: LeHostFeature = LeHostFeature(38);
454 pub const ADV_CODING_SELECTION: LeHostFeature = LeHostFeature(41);
455 pub const CHANNEL_SOUNDING: LeHostFeature = LeHostFeature(47);
456}
457
458unsafe impl ByteAlignedValue for LeHostFeature {}
459
460impl<'de> FromHciBytes<'de> for &'de LeHostFeature {
461 #[inline(always)]
462 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> {
463 <LeHostFeature as ByteAlignedValue>::ref_from_hci_bytes(data)
464 }
465}
466
467param!(struct LeHostFeatureV2(u16));
468
469#[allow(missing_docs)]
470impl LeHostFeatureV2 {
471 pub const CONN_ISO_STREAM: LeHostFeatureV2 = LeHostFeatureV2(32);
472 pub const CONN_SUBRATING: LeHostFeatureV2 = LeHostFeatureV2(38);
473 pub const ADV_CODING_SELECTION: LeHostFeatureV2 = LeHostFeatureV2(41);
474 pub const CHANNEL_SOUNDING: LeHostFeatureV2 = LeHostFeatureV2(47);
475 pub const SHORTER_CONNECTION_INTERVALS: LeHostFeatureV2 = LeHostFeatureV2(73);
476}
477
478impl From<LeHostFeature> for LeHostFeatureV2 {
479 fn from(value: LeHostFeature) -> Self {
480 LeHostFeatureV2(value.0.into())
481 }
482}
483
484param! {
485 bitfield LeDataRelatedAddrChangeReasons[1] {
486 (0, change_on_adv_data_change, set_change_addr_on_adv_data_changes);
487 (1, change_on_scan_response_data_change, set_change_addr_on_scan_response_data_changes);
488 }
489}
490
491param! {
492 #[derive(Default)]
493 enum LeConnRole {
494 #[default]
495 Central = 0,
496 Peripheral = 1,
497 }
498}
499
500param! {
501 #[derive(Default)]
502 enum ClockAccuracy {
503 #[default]
504 Ppm500 = 0,
505 Ppm250 = 1,
506 Ppm150 = 2,
507 Ppm100 = 3,
508 Ppm75 = 4,
509 Ppm50 = 5,
510 Ppm30 = 6,
511 Ppm20 = 7,
512 }
513}
514
515param! {
516 struct LeAdvertisingReportParam<'a> {
517 event_type: u8,
518 addr_kind: AddrKind,
519 addr: BdAddr,
520 data: &'a [u8],
521 rssi: i8,
522 }
523}
524
525param_slice! {
526 [LeDirectedAdvertisingReportParam; 16] {
527 event_type[0]: u8,
528 addr_kind[1]: AddrKind,
529 addr[2]: BdAddr,
530 direct_addr_kind[8]: AddrKind,
531 direct_addr[9]: BdAddr,
532 rssi[15]: i8,
533 }
534}
535
536param_slice! {
537 [LeIQSample; 2] {
538 i_sample[0]: i8,
539 q_sample[1]: i8,
540 }
541}
542
543param_slice! {
544 [BisConnHandle; 2] {
545 handle[0]: ConnHandle,
546 }
547}
548
549param! {
550 #[derive(Default)]
551 enum DataStatus {
552 #[default]
553 Complete = 0,
554 Incomplete = 1,
555 Failed = 0xff,
556 }
557}
558
559param! {
560 #[derive(Default)]
561 enum PacketStatus {
562 #[default]
563 CrcCorrect = 0,
564 CrcIncorrectUsedLength = 1,
565 CrcIncorrectUsedOther = 2,
566 InsufficientResources = 0xff,
567 }
568}
569
570param! {
571 #[derive(Default)]
572 enum TxStatus {
573 #[default]
574 Transmitted = 0,
575 NotTransmitted = 1,
576 }
577}
578
579param! {
580 #[derive(Default)]
581 enum ZoneEntered {
582 #[default]
583 Low = 0,
584 Middle = 1,
585 High = 2,
586 }
587}
588
589param! {
590 #[derive(Default)]
591 enum LeTxPowerReportingReason {
592 #[default]
593 LocalTxPowerChanged = 0,
594 RemoteTxPowerChanged = 1,
595 LeReadRemoteTxPowerLevelCompleted = 2,
596 }
597}
598
599param! {
600 #[derive(Default)]
601 enum LeAdvEventKind {
602 #[default]
603 AdvInd = 0,
604 AdvDirectInd = 1,
605 AdvScanInd = 2,
606 AdvNonconnInd = 3,
607 ScanRsp = 4,
608 }
609}
610
611param! {
612 bitfield LeExtAdvEventKind[2] {
613 (0, connectable, set_connectable);
614 (1, scannable, set_scannable);
615 (2, directed, set_directed);
616 (3, scan_response, set_scan_response);
617 (4, legacy, set_legacy);
618 }
619}
620
621#[allow(missing_docs)]
623pub enum LeExtAdvDataStatus {
624 Complete,
625 IncompleteMoreExpected,
626 IncompleteTruncated,
627 Reserved,
628}
629
630impl LeExtAdvEventKind {
631 pub fn data_status(&self) -> LeExtAdvDataStatus {
633 let data_status = (self.0[0] >> 5) & 0x03;
634 match data_status {
635 0 => LeExtAdvDataStatus::Complete,
636 1 => LeExtAdvDataStatus::IncompleteMoreExpected,
637 2 => LeExtAdvDataStatus::IncompleteTruncated,
638 _ => LeExtAdvDataStatus::Reserved,
639 }
640 }
641
642 pub fn set_data_status(mut self, status: LeExtAdvDataStatus) -> Self {
644 let value = match status {
645 LeExtAdvDataStatus::Complete => 0,
646 LeExtAdvDataStatus::IncompleteMoreExpected => 1,
647 LeExtAdvDataStatus::IncompleteTruncated => 2,
648 LeExtAdvDataStatus::Reserved => 3,
649 };
650 self.0[0] &= !(0x03 << 5);
651 self.0[0] |= value << 5;
652 self
653 }
654}
655
656param! {
657 struct LeAdvReport<'a> {
658 event_kind: LeAdvEventKind,
659 addr_kind: AddrKind,
660 addr: BdAddr,
661 data: &'a [u8],
662 rssi: i8,
663 }
664}
665
666param! {
667 struct LeAdvReports<'a> {
668 num_reports: u8,
669 bytes: RemainingBytes<'a>,
670 }
671}
672
673impl LeAdvReports<'_> {
674 pub fn is_empty(&self) -> bool {
676 self.num_reports == 0
677 }
678
679 pub fn len(&self) -> usize {
681 usize::from(self.num_reports)
682 }
683
684 pub fn iter(&self) -> LeAdvReportsIter<'_> {
686 LeAdvReportsIter {
687 len: self.len(),
688 bytes: &self.bytes,
689 }
690 }
691}
692
693pub struct LeAdvReportsIter<'a> {
695 len: usize,
696 bytes: &'a [u8],
697}
698
699impl<'a> Iterator for LeAdvReportsIter<'a> {
700 type Item = Result<LeAdvReport<'a>, FromHciBytesError>;
701
702 fn next(&mut self) -> Option<Self::Item> {
703 if self.len == 0 {
704 None
705 } else {
706 match LeAdvReport::from_hci_bytes(self.bytes) {
707 Ok((report, rest)) => {
708 self.bytes = rest;
709 self.len -= 1;
710 Some(Ok(report))
711 }
712 Err(err) => {
713 self.len = 0;
714 Some(Err(err))
715 }
716 }
717 }
718 }
719
720 fn size_hint(&self) -> (usize, Option<usize>) {
721 (self.len, Some(self.len))
722 }
723}
724
725impl ExactSizeIterator for LeAdvReportsIter<'_> {
726 fn len(&self) -> usize {
727 self.len
728 }
729}
730
731impl FusedIterator for LeAdvReportsIter<'_> {}
732
733#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
734#[cfg_attr(feature = "defmt", derive(defmt::Format))]
735#[allow(missing_docs)]
736pub struct LeExtAdvReport<'a> {
737 pub event_kind: LeExtAdvEventKind,
738 pub addr_kind: AddrKind,
739 pub addr: BdAddr,
740 pub primary_adv_phy: PhyKind,
741 pub secondary_adv_phy: Option<PhyKind>,
742 pub adv_sid: u8,
743 pub tx_power: i8,
744 pub rssi: i8,
745 pub adv_interval: Duration<1_250>,
746 pub direct_addr_kind: AddrKind,
747 pub direct_addr: BdAddr,
748 pub data: &'a [u8],
749}
750
751impl WriteHci for LeExtAdvReport<'_> {
752 #[inline(always)]
753 fn size(&self) -> usize {
754 WriteHci::size(&self.event_kind)
755 + WriteHci::size(&self.addr_kind)
756 + WriteHci::size(&self.addr)
757 + WriteHci::size(&self.primary_adv_phy)
758 + 1 + WriteHci::size(&self.adv_sid)
760 + WriteHci::size(&self.tx_power)
761 + WriteHci::size(&self.rssi)
762 + WriteHci::size(&self.adv_interval)
763 + WriteHci::size(&self.direct_addr_kind)
764 + WriteHci::size(&self.direct_addr)
765 + WriteHci::size(&self.data)
766 }
767 #[inline(always)]
768 fn write_hci<W: ::embedded_io::Write>(&self, mut writer: W) -> Result<(), W::Error> {
769 self.event_kind.write_hci(&mut writer)?;
770 self.addr_kind.write_hci(&mut writer)?;
771 self.addr.write_hci(&mut writer)?;
772 self.primary_adv_phy.write_hci(&mut writer)?;
773 match self.secondary_adv_phy {
774 None => 0u8.write_hci(&mut writer)?,
775 Some(val) => val.write_hci(&mut writer)?,
776 };
777 self.adv_sid.write_hci(&mut writer)?;
778 self.tx_power.write_hci(&mut writer)?;
779 self.rssi.write_hci(&mut writer)?;
780 self.adv_interval.write_hci(&mut writer)?;
781 self.direct_addr_kind.write_hci(&mut writer)?;
782 self.direct_addr.write_hci(&mut writer)?;
783 self.data.write_hci(&mut writer)?;
784 Ok(())
785 }
786 #[inline(always)]
787 async fn write_hci_async<W: ::embedded_io_async::Write>(&self, mut writer: W) -> Result<(), W::Error> {
788 self.event_kind.write_hci_async(&mut writer).await?;
789 self.addr_kind.write_hci_async(&mut writer).await?;
790 self.addr.write_hci_async(&mut writer).await?;
791 self.primary_adv_phy.write_hci_async(&mut writer).await?;
792 match self.secondary_adv_phy {
793 None => 0u8.write_hci_async(&mut writer).await?,
794 Some(val) => val.write_hci_async(&mut writer).await?,
795 };
796 self.adv_sid.write_hci_async(&mut writer).await?;
797 self.tx_power.write_hci_async(&mut writer).await?;
798 self.rssi.write_hci_async(&mut writer).await?;
799 self.adv_interval.write_hci_async(&mut writer).await?;
800 self.direct_addr_kind.write_hci_async(&mut writer).await?;
801 self.direct_addr.write_hci_async(&mut writer).await?;
802 self.data.write_hci_async(&mut writer).await?;
803 Ok(())
804 }
805}
806
807impl<'de> crate::FromHciBytes<'de> for LeExtAdvReport<'de> {
808 #[allow(unused_variables)]
809 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
810 let (event_kind, data) = <LeExtAdvEventKind as crate::FromHciBytes>::from_hci_bytes(data)?;
811 let (addr_kind, data) = <AddrKind as crate::FromHciBytes>::from_hci_bytes(data)?;
812 let (addr, data) = <BdAddr as crate::FromHciBytes>::from_hci_bytes(data)?;
813 let (primary_adv_phy, data) = <PhyKind as crate::FromHciBytes>::from_hci_bytes(data)?;
814 let (secondary_adv_phy, data) = if data[0] == 0 {
815 (None, &data[1..])
816 } else {
817 let (ret, rest) = <PhyKind as crate::FromHciBytes>::from_hci_bytes(data)?;
818 (Some(ret), rest)
819 };
820 let (adv_sid, data) = <u8 as crate::FromHciBytes>::from_hci_bytes(data)?;
821 let (tx_power, data) = <i8 as crate::FromHciBytes>::from_hci_bytes(data)?;
822 let (rssi, data) = <i8 as crate::FromHciBytes>::from_hci_bytes(data)?;
823 let (adv_interval, data) = <Duration<1_250> as crate::FromHciBytes>::from_hci_bytes(data)?;
824 let (direct_addr_kind, data) = <AddrKind as crate::FromHciBytes>::from_hci_bytes(data)?;
825 let (direct_addr, data) = <BdAddr as crate::FromHciBytes>::from_hci_bytes(data)?;
826 let (data, rest) = <&'de [u8] as crate::FromHciBytes>::from_hci_bytes(data)?;
827 Ok((
828 Self {
829 event_kind,
830 addr_kind,
831 addr,
832 primary_adv_phy,
833 secondary_adv_phy,
834 adv_sid,
835 tx_power,
836 rssi,
837 adv_interval,
838 direct_addr_kind,
839 direct_addr,
840 data,
841 },
842 rest,
843 ))
844 }
845}
846
847param! {
848 struct LeExtAdvReports<'a> {
849 num_reports: u8,
850 bytes: RemainingBytes<'a>,
851 }
852}
853
854impl LeExtAdvReports<'_> {
855 pub fn is_empty(&self) -> bool {
857 self.num_reports == 0
858 }
859
860 pub fn len(&self) -> usize {
862 usize::from(self.num_reports)
863 }
864
865 pub fn iter(&self) -> LeExtAdvReportsIter<'_> {
867 LeExtAdvReportsIter {
868 len: self.len(),
869 bytes: &self.bytes,
870 }
871 }
872}
873
874pub struct LeExtAdvReportsIter<'a> {
876 len: usize,
877 bytes: &'a [u8],
878}
879
880impl<'a> Iterator for LeExtAdvReportsIter<'a> {
881 type Item = Result<LeExtAdvReport<'a>, FromHciBytesError>;
882
883 fn next(&mut self) -> Option<Self::Item> {
884 if self.len == 0 {
885 None
886 } else {
887 match LeExtAdvReport::from_hci_bytes(self.bytes) {
888 Ok((report, rest)) => {
889 self.bytes = rest;
890 self.len -= 1;
891 Some(Ok(report))
892 }
893 Err(err) => {
894 self.len = 0;
895 Some(Err(err))
896 }
897 }
898 }
899 }
900
901 fn size_hint(&self) -> (usize, Option<usize>) {
902 (self.len, Some(self.len))
903 }
904}
905
906impl ExactSizeIterator for LeExtAdvReportsIter<'_> {
907 fn len(&self) -> usize {
908 self.len
909 }
910}
911
912impl FusedIterator for LeExtAdvReportsIter<'_> {}
913
914param! {
915 struct LePeriodicAdvSubeventData<'a> {
916 subevent: u8,
917 response_slot_start: u8,
918 response_slot_count: u8,
919 subevent_data: &'a [u8],
920 }
921}
922
923impl<'a, 'b: 'a> WriteHci for &'a [LePeriodicAdvSubeventData<'b>] {
924 #[inline(always)]
925 fn size(&self) -> usize {
926 1 + self.iter().map(WriteHci::size).sum::<usize>()
927 }
928 #[inline(always)]
929 fn write_hci<W: ::embedded_io::Write>(&self, mut writer: W) -> Result<(), W::Error> {
930 writer.write_all(&[self.len() as u8])?;
931 for x in self.iter() {
932 <LePeriodicAdvSubeventData as WriteHci>::write_hci(x, &mut writer)?;
933 }
934 Ok(())
935 }
936 #[inline(always)]
937 async fn write_hci_async<W: ::embedded_io_async::Write>(&self, mut writer: W) -> Result<(), W::Error> {
938 writer.write_all(&[self.len() as u8]).await?;
939 for x in self.iter() {
940 <LePeriodicAdvSubeventData as WriteHci>::write_hci_async(x, &mut writer).await?;
941 }
942 Ok(())
943 }
944}
945
946#[allow(missing_docs)]
947fn read_n<T: ByteAlignedValue>(data: &[u8], n: usize) -> Result<(&[T], &[u8]), FromHciBytesError> {
948 let size = n * core::mem::size_of::<T>();
949 if data.len() < size {
950 return Err(FromHciBytesError::InvalidSize);
951 }
952 let (bytes, rest) = data.split_at(size);
953 let slice = unsafe { core::slice::from_raw_parts(bytes.as_ptr() as *const T, n) };
954 Ok((slice, rest))
955}
956
957param! {
958 struct LePeriodicAdvertisingResponseReport<'a> {
959 tx_power: i8,
960 rssi: i8,
961 cte_type: CteKind,
962 response_slot: u8,
963 data_status: DataStatus,
964 data_length: u8,
965 data: &'a [u8],
966 }
967}
968
969#[derive(Debug, Clone, Hash)]
971#[cfg_attr(feature = "defmt", derive(defmt::Format))]
972pub struct LePeriodicAdvertisingResponseReports<'a> {
973 num_responses: u8,
974 tx_power: &'a [i8],
975 rssi: &'a [i8],
976 cte_type: &'a [CteKind],
977 response_slot: &'a [u8],
978 data_status: &'a [DataStatus],
979 data_length: &'a [u8],
980 data: &'a [u8],
981}
982
983impl<'a> LePeriodicAdvertisingResponseReports<'a> {
984 pub fn is_empty(&self) -> bool {
986 self.num_responses == 0
987 }
988
989 pub fn len(&self) -> usize {
991 usize::from(self.num_responses)
992 }
993
994 pub fn get(&self, index: usize) -> Option<LePeriodicAdvertisingResponseReport<'a>> {
996 if index >= self.len() {
997 return None;
998 }
999 let data_offset: usize = self.data_length[..index].iter().map(|&l| l as usize).sum();
1000 let data_len = self.data_length[index] as usize;
1001 Some(LePeriodicAdvertisingResponseReport {
1002 tx_power: self.tx_power[index],
1003 rssi: self.rssi[index],
1004 cte_type: self.cte_type[index],
1005 response_slot: self.response_slot[index],
1006 data_status: self.data_status[index],
1007 data_length: self.data_length[index],
1008 data: &self.data[data_offset..data_offset + data_len],
1009 })
1010 }
1011
1012 pub fn iter(&self) -> LePeriodicAdvertisingResponseReportsIter<'_> {
1014 LePeriodicAdvertisingResponseReportsIter {
1015 reports: self,
1016 index: 0,
1017 }
1018 }
1019}
1020
1021impl<'de> FromHciBytes<'de> for LePeriodicAdvertisingResponseReports<'de> {
1022 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> {
1023 let (num_responses, data) = u8::from_hci_bytes(data)?;
1024 let n = num_responses as usize;
1025
1026 let (tx_power, data) = read_n::<i8>(data, n)?;
1027 let (rssi, data) = read_n::<i8>(data, n)?;
1028 let (cte_type, data) = read_n::<CteKind>(data, n)?;
1029 let (response_slot, data) = read_n::<u8>(data, n)?;
1030 let (data_status, data) = read_n::<DataStatus>(data, n)?;
1031 let (data_length, data) = read_n::<u8>(data, n)?;
1032
1033 Ok((
1034 Self {
1035 num_responses,
1036 tx_power,
1037 rssi,
1038 cte_type,
1039 response_slot,
1040 data_status,
1041 data_length,
1042 data,
1043 },
1044 &[],
1045 ))
1046 }
1047}
1048
1049pub struct LePeriodicAdvertisingResponseReportsIter<'a> {
1051 reports: &'a LePeriodicAdvertisingResponseReports<'a>,
1052 index: usize,
1053}
1054
1055impl<'a> Iterator for LePeriodicAdvertisingResponseReportsIter<'a> {
1056 type Item = LePeriodicAdvertisingResponseReport<'a>;
1057
1058 fn next(&mut self) -> Option<Self::Item> {
1059 let entry = self.reports.get(self.index)?;
1060 self.index += 1;
1061 Some(entry)
1062 }
1063
1064 fn size_hint(&self) -> (usize, Option<usize>) {
1065 let remaining = self.reports.len() - self.index;
1066 (remaining, Some(remaining))
1067 }
1068}
1069
1070impl ExactSizeIterator for LePeriodicAdvertisingResponseReportsIter<'_> {
1071 fn len(&self) -> usize {
1072 self.reports.len() - self.index
1073 }
1074}
1075
1076impl FusedIterator for LePeriodicAdvertisingResponseReportsIter<'_> {}
1077
1078param! {
1079 struct LeCsSubeventStepEntry<'a> {
1080 step_mode: u8,
1081 step_channel: u8,
1082 step_data_length: u8,
1083 step_data: &'a [u8],
1084 }
1085}
1086
1087#[derive(Debug, Clone, Hash)]
1095#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1096pub struct LeCsSubeventStepData<'a> {
1097 num_steps_reported: u8,
1098 step_mode: &'a [u8],
1099 step_channel: &'a [u8],
1100 step_data_length: &'a [u8],
1101 step_data: &'a [u8],
1102}
1103
1104impl<'a> LeCsSubeventStepData<'a> {
1105 pub fn is_empty(&self) -> bool {
1107 self.num_steps_reported == 0
1108 }
1109
1110 pub fn len(&self) -> usize {
1112 usize::from(self.num_steps_reported)
1113 }
1114
1115 pub fn get(&self, index: usize) -> Option<LeCsSubeventStepEntry<'a>> {
1117 if index >= self.len() {
1118 return None;
1119 }
1120 let data_offset: usize = self.step_data_length[..index].iter().map(|&l| l as usize).sum();
1121 let data_len = self.step_data_length[index] as usize;
1122 Some(LeCsSubeventStepEntry {
1123 step_mode: self.step_mode[index],
1124 step_channel: self.step_channel[index],
1125 step_data_length: self.step_data_length[index],
1126 step_data: &self.step_data[data_offset..data_offset + data_len],
1127 })
1128 }
1129
1130 pub fn iter(&self) -> LeCsSubeventStepDataIter<'_> {
1132 LeCsSubeventStepDataIter { data: self, index: 0 }
1133 }
1134}
1135
1136impl<'de> FromHciBytes<'de> for LeCsSubeventStepData<'de> {
1137 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), FromHciBytesError> {
1138 let (num_steps_reported, data) = u8::from_hci_bytes(data)?;
1139 let n = num_steps_reported as usize;
1140
1141 let (step_mode, data) = read_n::<u8>(data, n)?;
1142 let (step_channel, data) = read_n::<u8>(data, n)?;
1143 let (step_data_length, data) = read_n::<u8>(data, n)?;
1144
1145 Ok((
1146 Self {
1147 num_steps_reported,
1148 step_mode,
1149 step_channel,
1150 step_data_length,
1151 step_data: data,
1152 },
1153 &[],
1154 ))
1155 }
1156}
1157
1158pub struct LeCsSubeventStepDataIter<'a> {
1160 data: &'a LeCsSubeventStepData<'a>,
1161 index: usize,
1162}
1163
1164impl<'a> Iterator for LeCsSubeventStepDataIter<'a> {
1165 type Item = LeCsSubeventStepEntry<'a>;
1166
1167 fn next(&mut self) -> Option<Self::Item> {
1168 let entry = self.data.get(self.index)?;
1169 self.index += 1;
1170 Some(entry)
1171 }
1172
1173 fn size_hint(&self) -> (usize, Option<usize>) {
1174 let remaining = self.data.len() - self.index;
1175 (remaining, Some(remaining))
1176 }
1177}
1178
1179impl ExactSizeIterator for LeCsSubeventStepDataIter<'_> {
1180 fn len(&self) -> usize {
1181 self.data.len() - self.index
1182 }
1183}
1184
1185impl FusedIterator for LeCsSubeventStepDataIter<'_> {}
1186
1187param! {
1188 #[derive(Default)]
1189 enum DoneStatus {
1190 #[default]
1191 Complete = 0,
1192 Partial = 1,
1193 Aborted = 0xf,
1194 }
1195}
1196
1197#[repr(u8)]
1199#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1200#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1201pub enum ProcedureAbortReason {
1202 #[default]
1203 NoAbort = 0x0,
1205 HostRequest = 0x1,
1207 FilteredChannelMap = 0x2,
1209 ChannelMapInstantPassed = 0x3,
1211 Unspecified = 0xf,
1213}
1214
1215impl From<u8> for ProcedureAbortReason {
1216 fn from(v: u8) -> Self {
1217 match v {
1218 0x0 => Self::NoAbort,
1219 0x1 => Self::HostRequest,
1220 0x2 => Self::FilteredChannelMap,
1221 0x3 => Self::ChannelMapInstantPassed,
1222 _ => Self::Unspecified,
1223 }
1224 }
1225}
1226
1227#[repr(u8)]
1229#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1230#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1231pub enum SubeventAbortReason {
1232 #[default]
1233 NoAbort = 0x0,
1235 HostRequest = 0x1,
1237 NoCsSync = 0x2,
1239 SchedulingConflict = 0x3,
1241 Unspecified = 0xf,
1243}
1244
1245impl From<u8> for SubeventAbortReason {
1246 fn from(v: u8) -> Self {
1247 match v {
1248 0x0 => Self::NoAbort,
1249 0x1 => Self::HostRequest,
1250 0x2 => Self::NoCsSync,
1251 0x3 => Self::SchedulingConflict,
1252 _ => Self::Unspecified,
1253 }
1254 }
1255}
1256
1257#[repr(transparent)]
1262#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1263#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1264pub struct PackedAbortReasons(u8);
1265
1266impl PackedAbortReasons {
1267 pub fn procedure_reason(&self) -> ProcedureAbortReason {
1269 ProcedureAbortReason::from(self.0 & 0x0f)
1270 }
1271
1272 pub fn subevent_reason(&self) -> SubeventAbortReason {
1274 SubeventAbortReason::from((self.0 >> 4) & 0x0f)
1275 }
1276}
1277
1278unsafe impl FixedSizeValue for PackedAbortReasons {
1279 fn is_valid(_data: &[u8]) -> bool {
1280 true
1281 }
1282}
1283
1284#[repr(transparent)]
1286#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1287#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1288pub struct FrequencyCompensation(u16);
1289
1290impl FrequencyCompensation {
1291 pub fn as_raw(&self) -> u16 {
1293 self.0
1294 }
1295
1296 pub fn is_available(&self) -> bool {
1298 self.0 != 0xC000
1299 }
1300
1301 pub fn as_ppm_x100(&self) -> Option<i16> {
1303 if !self.is_available() {
1304 return None;
1305 }
1306 let val = self.0 & 0x7FFF;
1307 Some(((val << 1) as i16) >> 1)
1308 }
1309}
1310
1311unsafe impl FixedSizeValue for FrequencyCompensation {
1312 fn is_valid(_data: &[u8]) -> bool {
1313 true
1314 }
1315}
1316
1317param!(
1322 struct CigId(u8)
1324);
1325
1326#[allow(missing_docs)]
1327impl CigId {
1328 pub const fn new(v: u8) -> Self {
1330 Self(v)
1331 }
1332
1333 pub fn as_raw(&self) -> u8 {
1335 self.0
1336 }
1337}
1338
1339unsafe impl ByteAlignedValue for CigId {}
1340
1341impl<'de> crate::FromHciBytes<'de> for &'de CigId {
1342 #[inline(always)]
1343 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
1344 <CigId as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
1345 }
1346}
1347
1348param!(
1349 struct CisId(u8)
1351);
1352
1353#[allow(missing_docs)]
1354impl CisId {
1355 pub const fn new(v: u8) -> Self {
1357 Self(v)
1358 }
1359
1360 pub fn as_raw(&self) -> u8 {
1362 self.0
1363 }
1364}
1365
1366unsafe impl ByteAlignedValue for CisId {}
1367
1368impl<'de> crate::FromHciBytes<'de> for &'de CisId {
1369 #[inline(always)]
1370 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
1371 <CisId as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
1372 }
1373}
1374
1375param! {
1376 struct CisConfig {
1378 cis_id: CisId,
1379 max_sdu_c_to_p: u16,
1380 max_sdu_p_to_c: u16,
1381 phy_c_to_p: PhyMask,
1382 phy_p_to_c: PhyMask,
1383 rtn_c_to_p: u8,
1384 rtn_p_to_c: u8,
1385 }
1386}
1387
1388param_slice!(&'a [CisConfig]);
1389
1390param! {
1391 struct CisConfigTest {
1393 cis_id: CisId,
1394 max_sdu_c_to_p: u16,
1395 max_sdu_p_to_c: u16,
1396 max_pdu_c_to_p: u16,
1397 max_pdu_p_to_c: u16,
1398 phy_c_to_p: PhyMask,
1399 phy_p_to_c: PhyMask,
1400 bn_c_to_p: u8,
1401 bn_p_to_c: u8,
1402 }
1403}
1404
1405param_slice!(&'a [CisConfigTest]);
1406
1407param! {
1408 struct CisConnConfig {
1410 cis_handle: ConnHandle,
1411 acl_handle: ConnHandle,
1412 }
1413}
1414
1415param_slice!(&'a [CisConnConfig]);
1416
1417param! {
1418 #[derive(Default)]
1420 enum DataPathDirection {
1421 #[default]
1422 Input = 0,
1423 Output = 1,
1424 }
1425}
1426
1427param!(
1428 struct DataPathId(u8)
1434);
1435
1436#[allow(missing_docs)]
1437impl DataPathId {
1438 pub const HCI: DataPathId = DataPathId(0x00);
1440 pub const AUDIO_TEST_MODE: DataPathId = DataPathId(0xFF);
1442
1443 pub const fn new(v: u8) -> Self {
1445 Self(v)
1446 }
1447
1448 pub fn as_raw(&self) -> u8 {
1450 self.0
1451 }
1452}
1453
1454unsafe impl ByteAlignedValue for DataPathId {}
1455
1456impl<'de> crate::FromHciBytes<'de> for &'de DataPathId {
1457 #[inline(always)]
1458 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
1459 <DataPathId as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
1460 }
1461}
1462
1463param! {
1464 #[derive(Default)]
1466 struct CodecId {
1467 coding_format: u8,
1468 company_id: u16,
1469 vendor_specific_codec_id: u16,
1470 }
1471}
1472
1473param! {
1474 #[derive(Default)]
1476 enum LogicalTransportType {
1477 #[default]
1478 BrEdrAcl = 0,
1479 BrEdrSco = 1,
1480 LeCis = 2,
1481 LeBis = 3,
1482 }
1483}
1484
1485param!(
1486 struct BroadcastCode([u8; 16])
1488);
1489
1490#[allow(missing_docs)]
1491impl BroadcastCode {
1492 pub const fn new(v: [u8; 16]) -> Self {
1494 Self(v)
1495 }
1496
1497 pub fn raw(&self) -> &[u8] {
1499 &self.0[..]
1500 }
1501}
1502
1503unsafe impl ByteAlignedValue for BroadcastCode {}
1504
1505impl<'de> crate::FromHciBytes<'de> for &'de BroadcastCode {
1506 #[inline(always)]
1507 fn from_hci_bytes(data: &'de [u8]) -> Result<(Self, &'de [u8]), crate::FromHciBytesError> {
1508 <BroadcastCode as crate::ByteAlignedValue>::ref_from_hci_bytes(data)
1509 }
1510}
1511
1512param! {
1513 #[derive(Default)]
1515 enum EncryptionMode {
1516 #[default]
1517 Unencrypted = 0,
1518 Encrypted = 1,
1519 }
1520}
1521
1522param! {
1523 #[derive(Default)]
1525 enum Packing {
1526 #[default]
1527 Sequential = 0,
1528 Interleaved = 1,
1529 }
1530}
1531
1532param! {
1533 #[derive(Default)]
1535 enum Framing {
1536 #[default]
1537 Unframed = 0,
1538 Framed = 1,
1539 }
1540}
1541
1542param! {
1543 #[derive(Default)]
1545 enum PayloadType {
1546 #[default]
1547 ZeroLength = 0,
1548 VariableLength = 1,
1549 MaximumLength = 2,
1550 }
1551}
1552
1553#[cfg(test)]
1554mod tests {
1555 use super::*;
1556
1557 #[test]
1558 fn test_ext_adv_event_kind() {
1559 let k = LeExtAdvEventKind::new().set_connectable(true);
1560 assert_eq!(k.0[0], 0b0000001);
1561 let k = k.set_data_status(LeExtAdvDataStatus::Complete);
1562 assert_eq!(k.0[0], 0b0000001);
1563 let k = k.set_data_status(LeExtAdvDataStatus::IncompleteMoreExpected);
1564 assert_eq!(k.0[0], 0b0100001);
1565 let k = k.set_data_status(LeExtAdvDataStatus::IncompleteTruncated);
1566 assert_eq!(k.0[0], 0b1000001);
1567 }
1568
1569 #[test]
1570 fn test_channel_map_new() {
1571 let m = ChannelMap::new();
1572 for chan in 0..37 {
1573 assert!(!m.is_channel_bad(chan));
1574 }
1575
1576 for chan in 37..40 {
1577 assert!(m.is_channel_bad(chan));
1578 }
1579 }
1580
1581 #[test]
1582 fn test_frequency_compensation_positive() {
1583 let fc = FrequencyCompensation(0x2710);
1584 assert!(fc.is_available());
1585 assert_eq!(fc.as_ppm_x100(), Some(10000));
1586 assert_eq!(fc.as_raw(), 0x2710);
1587 }
1588
1589 #[test]
1590 fn test_frequency_compensation_negative() {
1591 let fc = FrequencyCompensation(0x58F0);
1592 assert!(fc.is_available());
1593 assert_eq!(fc.as_ppm_x100(), Some(-10000));
1594 assert_eq!(fc.as_raw(), 0x58F0);
1595 }
1596
1597 #[test]
1598 fn test_frequency_compensation_not_available() {
1599 let fc = FrequencyCompensation(0xC000);
1600 assert!(!fc.is_available());
1601 assert_eq!(fc.as_ppm_x100(), None);
1602 assert_eq!(fc.as_raw(), 0xC000);
1603 }
1604}