Coverage for src / bluetooth_sig / types / advertising / features.py: 75%
106 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-18 11:17 +0000
1"""LE Supported Features (Core Spec Vol 6, Part B, Section 4.6)."""
3from __future__ import annotations
5from enum import IntFlag
7import msgspec
10class LEFeatureBits(IntFlag):
11 """LE Supported Features bit definitions (Core Spec Vol 6, Part B, Section 4.6).
13 Byte 0 features (bits 0-7).
14 """
16 # Byte 0 features
17 LE_ENCRYPTION = 0x0001
18 CONNECTION_PARAMETERS_REQUEST = 0x0002
19 EXTENDED_REJECT_INDICATION = 0x0004
20 PERIPHERAL_INITIATED_FEATURES_EXCHANGE = 0x0008
21 LE_PING = 0x0010
22 LE_DATA_PACKET_LENGTH_EXTENSION = 0x0020
23 LL_PRIVACY = 0x0040
24 EXTENDED_SCANNER_FILTER_POLICIES = 0x0080
26 # Byte 1 features (bits 8-15)
27 LE_2M_PHY = 0x0100
28 STABLE_MODULATION_INDEX_TX = 0x0200
29 STABLE_MODULATION_INDEX_RX = 0x0400
30 LE_CODED_PHY = 0x0800
31 LE_EXTENDED_ADVERTISING = 0x1000
32 LE_PERIODIC_ADVERTISING = 0x2000
33 CHANNEL_SELECTION_ALGORITHM_2 = 0x4000
34 LE_POWER_CLASS_1 = 0x8000
36 # Byte 2 features (bits 16-23)
37 MIN_NUMBER_OF_USED_CHANNELS = 0x010000
38 CONNECTION_CTE_REQUEST = 0x020000
39 CONNECTION_CTE_RESPONSE = 0x040000
40 CONNECTIONLESS_CTE_TX = 0x080000
41 CONNECTIONLESS_CTE_RX = 0x100000
42 ANTENNA_SWITCHING_TX = 0x200000
43 ANTENNA_SWITCHING_RX = 0x400000
44 RECEIVING_CTE = 0x800000
47class LEFeatures(msgspec.Struct, kw_only=True): # pylint: disable=too-many-public-methods # One property per LE feature bit
48 """LE Supported Features bit field (Core Spec Vol 6, Part B, Section 4.6).
50 Attributes:
51 raw_value: Raw feature bit field bytes (up to 8 bytes)
52 """
54 raw_value: bytes
56 def _get_features_int(self) -> int:
57 """Convert raw bytes to integer for bit checking."""
58 return int.from_bytes(self.raw_value, byteorder="little") if self.raw_value else 0
60 def _has_feature(self, feature: LEFeatureBits) -> bool:
61 """Check if a specific feature bit is set."""
62 return bool(self._get_features_int() & feature)
64 @property
65 def le_encryption(self) -> bool:
66 """LE Encryption supported."""
67 return self._has_feature(LEFeatureBits.LE_ENCRYPTION)
69 @property
70 def connection_parameters_request(self) -> bool:
71 """Connection Parameters Request Procedure."""
72 return self._has_feature(LEFeatureBits.CONNECTION_PARAMETERS_REQUEST)
74 @property
75 def extended_reject_indication(self) -> bool:
76 """Extended Reject Indication."""
77 return self._has_feature(LEFeatureBits.EXTENDED_REJECT_INDICATION)
79 @property
80 def peripheral_initiated_features_exchange(self) -> bool:
81 """Peripheral-initiated Features Exchange."""
82 return self._has_feature(LEFeatureBits.PERIPHERAL_INITIATED_FEATURES_EXCHANGE)
84 @property
85 def le_ping(self) -> bool:
86 """LE Ping."""
87 return self._has_feature(LEFeatureBits.LE_PING)
89 @property
90 def le_data_packet_length_extension(self) -> bool:
91 """LE Data Packet Length Extension."""
92 return self._has_feature(LEFeatureBits.LE_DATA_PACKET_LENGTH_EXTENSION)
94 @property
95 def ll_privacy(self) -> bool:
96 """LL Privacy."""
97 return self._has_feature(LEFeatureBits.LL_PRIVACY)
99 @property
100 def extended_scanner_filter_policies(self) -> bool:
101 """Extended Scanner Filter Policies."""
102 return self._has_feature(LEFeatureBits.EXTENDED_SCANNER_FILTER_POLICIES)
104 @property
105 def le_2m_phy(self) -> bool:
106 """LE 2M PHY."""
107 return self._has_feature(LEFeatureBits.LE_2M_PHY)
109 @property
110 def stable_modulation_index_tx(self) -> bool:
111 """Stable Modulation Index - Transmitter."""
112 return self._has_feature(LEFeatureBits.STABLE_MODULATION_INDEX_TX)
114 @property
115 def stable_modulation_index_rx(self) -> bool:
116 """Stable Modulation Index - Receiver."""
117 return self._has_feature(LEFeatureBits.STABLE_MODULATION_INDEX_RX)
119 @property
120 def le_coded_phy(self) -> bool:
121 """LE Coded PHY."""
122 return self._has_feature(LEFeatureBits.LE_CODED_PHY)
124 @property
125 def le_extended_advertising(self) -> bool:
126 """LE Extended Advertising."""
127 return self._has_feature(LEFeatureBits.LE_EXTENDED_ADVERTISING)
129 @property
130 def le_periodic_advertising(self) -> bool:
131 """LE Periodic Advertising."""
132 return self._has_feature(LEFeatureBits.LE_PERIODIC_ADVERTISING)
134 @property
135 def channel_selection_algorithm_2(self) -> bool:
136 """Channel Selection Algorithm #2."""
137 return self._has_feature(LEFeatureBits.CHANNEL_SELECTION_ALGORITHM_2)
139 @property
140 def le_power_class_1(self) -> bool:
141 """LE Power Class 1."""
142 return self._has_feature(LEFeatureBits.LE_POWER_CLASS_1)
144 @property
145 def min_number_of_used_channels(self) -> bool:
146 """Minimum Number of Used Channels Procedure."""
147 return self._has_feature(LEFeatureBits.MIN_NUMBER_OF_USED_CHANNELS)
149 @property
150 def connection_cte_request(self) -> bool:
151 """Connection CTE Request."""
152 return self._has_feature(LEFeatureBits.CONNECTION_CTE_REQUEST)
154 @property
155 def connection_cte_response(self) -> bool:
156 """Connection CTE Response."""
157 return self._has_feature(LEFeatureBits.CONNECTION_CTE_RESPONSE)
159 @property
160 def connectionless_cte_tx(self) -> bool:
161 """Connectionless CTE Transmitter."""
162 return self._has_feature(LEFeatureBits.CONNECTIONLESS_CTE_TX)
164 @property
165 def connectionless_cte_rx(self) -> bool:
166 """Connectionless CTE Receiver."""
167 return self._has_feature(LEFeatureBits.CONNECTIONLESS_CTE_RX)
169 @property
170 def antenna_switching_tx(self) -> bool:
171 """Antenna Switching During CTE Transmission."""
172 return self._has_feature(LEFeatureBits.ANTENNA_SWITCHING_TX)
174 @property
175 def antenna_switching_rx(self) -> bool:
176 """Antenna Switching During CTE Reception."""
177 return self._has_feature(LEFeatureBits.ANTENNA_SWITCHING_RX)
179 @property
180 def receiving_cte(self) -> bool:
181 """Receiving Constant Tone Extensions."""
182 return self._has_feature(LEFeatureBits.RECEIVING_CTE)