From 3cbf42cf43343610cc0aa0ce3381464b1e50a807 Mon Sep 17 00:00:00 2001 From: Joe Bakalor Date: Thu, 9 Jul 2026 17:01:25 -0500 Subject: [PATCH 1/3] hci: add Bluetooth 6.3 CS Enhancements plumbing (Inline PCT) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the HCI-layer definitions for the Bluetooth 6.3 Channel Sounding Enhancements feature (Inline Phase Correction Term transfer, "IPT"), none of which are wired to any existing behavior — this commit is purely additive so it can land independently of any policy change. New in bumble/hci.py: - HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2_COMMAND (opcode 0x20A5) with matching HCI_LE_CS_Read_Local_Supported_Capabilities_V2_Command class + _V2_ReturnParameters that append `t_ip2_ipt_times_supported` (uint16) and `t_sw_ipt_time_supported` (uint8) after the 6.0 field list. - HCI_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES_V2_COMMAND opcode constant (0x20A6) for symmetry (no class yet — no in-tree caller). - HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_V2_EVENT subevent code (0x38) + matching event class carrying the same V2 tail. Emitted (instead of the 6.0 event 0x2C) when the LE Extended Feature Set has been negotiated on the link. - CsSubfeature IntFlag with the `CS_IPT_REFLECTOR = 1 << 4` bit — the subfeatures_supported flag advertised by a controller that can act as an IPT reflector. - CsEnhancements1 IntFlag with `INLINE_PCT = 0x01` — the value used in the last byte of HCI_LE_CS_Create_Config to enable IPT per- configuration. - Rename the trailing `reserved` byte of HCI_LE_CS_Create_Config_Command to `cs_enhancements_1` and drop it from the "pre-6.3 rsvd" comment. This is the field name Nordic uses in zephyr/bluetooth/hci_types.h:2986 and mirrors the semantics of `bt_conn_le_cs_config::cs_enhancements_1` in conn.h:851. - Add the V2 read command to HCI_SUPPORTED_COMMANDS_MASKS at octet 49 bit 2 per the spec. Callers can now feature-detect V2 via Host.supports_command(). bumble/host.py: - Add on_hci_le_cs_read_remote_supported_capabilities_complete_v2_event handler that emits `cs_remote_supported_capabilities_v2` so device.py can pick the right payload shape (see the follow-up commit). Verified end-to-end against a Nordic nRF54L15 (nRF Connect SDK v3.4.0) running as an IPT initiator against Nordic's channel_sounding/ipt_reflector sample: V2 caps read returned `t_ip2_ipt_times_supported = 0x7e`, `t_sw_ipt_time_supported = 0x0a`, `subfeatures_supported = 0x12` (CS_IPT_REFLECTOR bit set). Create_Config with `cs_enhancements_1 = 0x01` completed with status SUCCESS and CS procedures streamed subevent results. --- bumble/hci.py | 112 ++++++++++++++++++++++++++++++++++++++++++++++++- bumble/host.py | 8 ++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/bumble/hci.py b/bumble/hci.py index 30ea1daa5..3ab679392 100644 --- a/bumble/hci.py +++ b/bumble/hci.py @@ -380,6 +380,7 @@ class SpecificationVersion(SpecableEnum): HCI_LE_FRAME_SPACE_UPDATE_COMPLETE_EVENT = 0x35 HCI_LE_UTP_RECEIVE_EVENT = 0x36 HCI_LE_CONNECTION_RATE_CHANGE_EVENT = 0x37 +HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_V2_EVENT = 0x38 # HCI Command @@ -709,6 +710,9 @@ class SpecificationVersion(SpecableEnum): HCI_LE_CS_PROCEDURE_ENABLE_COMMAND = hci_command_op_code(0x08, 0x0094) HCI_LE_CS_TEST_COMMAND = hci_command_op_code(0x08, 0x0095) HCI_LE_CS_TEST_END_COMMAND = hci_command_op_code(0x08, 0x0096) +# Bluetooth 6.3 CS Enhancements (Inline PCT) — opcodes 0x20A5 / 0x20A6. +HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2_COMMAND = hci_command_op_code(0x08, 0x00A5) +HCI_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES_V2_COMMAND = hci_command_op_code(0x08, 0x00A6) HCI_LE_SET_HOST_FEATURE_V2_COMMAND = hci_command_op_code(0x08, 0x0097) HCI_LE_ADD_DEVICE_TO_MONITORED_ADVERTISERS_LIST_COMMAND = hci_command_op_code(0x08, 0x0098) HCI_LE_REMOVE_DEVICE_FROM_MONITORED_ADVERTISERS_LIST_COMMAND = hci_command_op_code(0x08, 0x0099) @@ -973,6 +977,20 @@ class CsSubeventAbortReason(SpecableEnum): SCHEDULING_CONFLICT_OR_LIMITED_RESOURCES = 0x03 UNSPECIFIED = 0x0F + +# Bluetooth 6.3 CS Enhancements — bits inside the 2-byte `subfeatures_supported` +# field of the CS Read (Local|Remote) Supported Capabilities V2 reply. Mirrors +# BT_HCI_LE_CS_SUBFEATURE_* masks from Nordic hci_types.h:3934. +class CsSubfeature(enum.IntFlag): + CS_IPT_REFLECTOR = 1 << 4 # Inline PCT capable as reflector + + +# Values for the `cs_enhancements_1` byte of HCI_LE_CS_Create_Config command. +# Bit 0 enables Inline PCT transfer on the CS configuration; other bits +# reserved. Mirrors Zephyr conn.h:851-855 semantics. +class CsEnhancements1(enum.IntFlag): + INLINE_PCT = 0x01 + class Role(SpecableEnum): CENTRAL = 0 PERIPHERAL = 1 @@ -1188,6 +1206,10 @@ class AddressType(SpecableEnum): HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_COMMAND : 1 << (20*8+5), HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMMAND : 1 << (20*8+6), HCI_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES_COMMAND : 1 << (20*8+7), + # Bluetooth 6.3 LE Extended Feature Set — CS V2 read of local supported + # capabilities. Per Nordic hci_types.h:2609: BT_CMD_TEST(cmd, 49, 2) + # → octet 49, bit 2 of the LE Supported Commands bitmap. + HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2_COMMAND : 1 << (49*8+2), HCI_SET_EVENT_MASK_PAGE_2_COMMAND : 1 << (22*8+2), HCI_READ_FLOW_CONTROL_MODE_COMMAND : 1 << (23*8+0), HCI_WRITE_FLOW_CONTROL_MODE_COMMAND : 1 << (23*8+1), @@ -5968,6 +5990,54 @@ class HCI_LE_CS_Read_Local_Supported_Capabilities_Command( ''' +# ----------------------------------------------------------------------------- +# Bluetooth 6.3 LE Extended Feature Set — CS Enhancements (V2 variant). +# Adds Inline PCT (IPT) timings after tx_snr_capability. Opcode 0x20A5. +# See Nordic hci_types.h §2607-2660 for the reference struct. +# ----------------------------------------------------------------------------- +@dataclasses.dataclass +class HCI_LE_CS_Read_Local_Supported_Capabilities_V2_ReturnParameters( + HCI_StatusReturnParameters +): + num_config_supported: int = field(metadata=metadata(1)) + max_consecutive_procedures_supported: int = field(metadata=metadata(2)) + num_antennas_supported: int = field(metadata=metadata(1)) + max_antenna_paths_supported: int = field(metadata=metadata(1)) + roles_supported: int = field(metadata=metadata(1)) + modes_supported: int = field(metadata=metadata(1)) + rtt_capability: int = field(metadata=metadata(1)) + rtt_aa_only_n: int = field(metadata=metadata(1)) + rtt_sounding_n: int = field(metadata=metadata(1)) + rtt_random_payload_n: int = field(metadata=metadata(1)) + nadm_sounding_capability: int = field(metadata=metadata(2)) + nadm_random_capability: int = field(metadata=metadata(2)) + cs_sync_phys_supported: int = field(metadata=metadata(CS_SYNC_PHY_SUPPORTED_SPEC)) + subfeatures_supported: int = field(metadata=metadata(2)) + t_ip1_times_supported: int = field(metadata=metadata(2)) + t_ip2_times_supported: int = field(metadata=metadata(2)) + t_fcs_times_supported: int = field(metadata=metadata(2)) + t_pm_times_supported: int = field(metadata=metadata(2)) + t_sw_time_supported: int = field(metadata=metadata(1)) + tx_snr_capability: int = field(metadata=metadata(CS_SNR_SPEC)) + # V2 tail. + t_ip2_ipt_times_supported: int = field(metadata=metadata(2)) + t_sw_ipt_time_supported: int = field(metadata=metadata(1)) + + +@HCI_SyncCommand.sync_command( + HCI_LE_CS_Read_Local_Supported_Capabilities_V2_ReturnParameters +) +@dataclasses.dataclass +class HCI_LE_CS_Read_Local_Supported_Capabilities_V2_Command( + HCI_SyncCommand[HCI_LE_CS_Read_Local_Supported_Capabilities_V2_ReturnParameters] +): + ''' + See Bluetooth spec @ 7.8.161 LE CS Read Local Supported Capabilities V2 command + (opcode 0x20A5). Introduced with LE Extended Feature Set / CS Enhancements + to report Inline PCT (IPT) timing capabilities. + ''' + + # ----------------------------------------------------------------------------- @HCI_Command.command @dataclasses.dataclass @@ -6097,7 +6167,10 @@ class Ch3cShape(SpecableEnum): channel_selection_type: int = field(metadata=ChannelSelectionType.type_metadata(1)) ch3c_shape: int = field(metadata=Ch3cShape.type_metadata(1)) ch3c_jump: int = field(metadata=metadata(1)) - reserved: int = field(metadata=metadata(1)) + # Bluetooth 6.3 CS Enhancements — last byte of the command was called + # "reserved" pre-6.3. Bit 0 (`CsEnhancements1.INLINE_PCT`) enables Inline + # PCT transfer on this CS configuration. Set to zero for 6.0 behavior. + cs_enhancements_1: int = field(default=0, metadata=metadata(1)) # ----------------------------------------------------------------------------- @@ -7220,6 +7293,43 @@ class HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_Event(HCI_LE_Meta_Ev tx_snr_capability: int = field(metadata=metadata(CS_SNR_SPEC)) +# ----------------------------------------------------------------------------- +@HCI_LE_Meta_Event.event +@dataclasses.dataclass +class HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_V2_Event(HCI_LE_Meta_Event): + ''' + See Bluetooth spec @ 7.7.65.65 LE CS Read Remote Supported Capabilities + Complete V2 event (subevent 0x38). Emitted (instead of the 6.0 variant + 0x2C) when the LE Extended Feature Set has been negotiated on the link. + Adds T_IP2_IPT and T_SW_IPT timings for Inline PCT. + ''' + + status: int = field(metadata=metadata(STATUS_SPEC)) + connection_handle: int = field(metadata=metadata(2)) + num_config_supported: int = field(metadata=metadata(1)) + max_consecutive_procedures_supported: int = field(metadata=metadata(2)) + num_antennas_supported: int = field(metadata=metadata(1)) + max_antenna_paths_supported: int = field(metadata=metadata(1)) + roles_supported: int = field(metadata=metadata(1)) + modes_supported: int = field(metadata=metadata(1)) + rtt_capability: int = field(metadata=metadata(1)) + rtt_aa_only_n: int = field(metadata=metadata(1)) + rtt_sounding_n: int = field(metadata=metadata(1)) + rtt_random_payload_n: int = field(metadata=metadata(1)) + nadm_sounding_capability: int = field(metadata=metadata(2)) + nadm_random_capability: int = field(metadata=metadata(2)) + cs_sync_phys_supported: int = field(metadata=metadata(CS_SYNC_PHY_SUPPORTED_SPEC)) + subfeatures_supported: int = field(metadata=metadata(2)) + t_ip1_times_supported: int = field(metadata=metadata(2)) + t_ip2_times_supported: int = field(metadata=metadata(2)) + t_fcs_times_supported: int = field(metadata=metadata(2)) + t_pm_times_supported: int = field(metadata=metadata(2)) + t_sw_time_supported: int = field(metadata=metadata(1)) + tx_snr_capability: int = field(metadata=metadata(CS_SNR_SPEC)) + t_ip2_ipt_times_supported: int = field(metadata=metadata(2)) + t_sw_ipt_time_supported: int = field(metadata=metadata(1)) + + # ----------------------------------------------------------------------------- @HCI_LE_Meta_Event.event @dataclasses.dataclass diff --git a/bumble/host.py b/bumble/host.py index 992e042e3..501712122 100644 --- a/bumble/host.py +++ b/bumble/host.py @@ -2009,6 +2009,14 @@ def on_hci_le_cs_read_remote_supported_capabilities_complete_event( ): self.emit('cs_remote_supported_capabilities', event) + def on_hci_le_cs_read_remote_supported_capabilities_complete_v2_event( + self, + event: hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_V2_Event, + ): + # Bluetooth 6.3 variant (subevent 0x38) — dispatched separately so + # device.py handlers can pick the right payload shape. + self.emit('cs_remote_supported_capabilities_v2', event) + def on_hci_le_cs_security_enable_complete_event( self, event: hci.HCI_LE_CS_Security_Enable_Complete_Event ): From 8a5ac6ea2a58729dcb4be9d9796477bdd68cbd1b Mon Sep 17 00:00:00 2001 From: Joe Bakalor Date: Thu, 9 Jul 2026 17:01:43 -0500 Subject: [PATCH 2/3] device: consume Bluetooth 6.3 CS Enhancements (V2 caps + IPT enable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the HCI additions from the previous commit into Device: - ChannelSoundingCapabilities gains three trailing fields: `t_ip2_ipt_times_supported`, `t_sw_ipt_time_supported`, and the derived boolean `cs_ipt_reflector_supported` (from bit 4 of `subfeatures_supported`, per CsSubfeature.CS_IPT_REFLECTOR). Defaults keep the constructor backwards-compatible with any call site that still passes only the 6.0 field set positionally. - Device.power_on()'s CS bring-up now issues the V2 caps read (opcode 0x20A5) preferentially, falling back to the 6.0 read on UNKNOWN_HCI_COMMAND. Using try/except rather than supports_command() is deliberate: some hci_uart controllers implement the V2 command without setting octet 49 bit 2 in their LE Supported Commands reply, and this flow handles both cases without needing a per-vendor probe. The V2 dataclass renamed rtt_random_sequence_n → rtt_random_payload_n (same semantic) so the constructor unpacks either. - `on_cs_remote_supported_capabilities` is split into an outer V1/V2 dispatch pair and an inner _impl builder so both event shapes feed the same ChannelSoundingCapabilities instantiation. Same rtt_random field renaming applies. The new `cs_ipt_reflector_supported` bit propagates onto every emitted capability record regardless of which event fired. - create_cs_config() gains a `cs_enhancements_1: int = 0` keyword parameter (default 0 preserves the 6.0 behavior) and passes it through to HCI_LE_CS_Create_Config_Command. The old `reserved=0x00` call-site keyword was replaced by the rename in the previous commit; existing callers that did not set the reserved field remain unaffected. Verified against the same nRF54L15 rig described in the previous commit: caps report `cs_ipt_reflector_supported=True`, and a `create_cs_config(..., cs_enhancements_1=CsEnhancements1.INLINE_PCT)` call runs a full CS procedure with IPT enabled end-to-end. --- bumble/device.py | 72 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/bumble/device.py b/bumble/device.py index 7eb5dc15a..9bffeb425 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -1189,6 +1189,15 @@ class ChannelSoundingCapabilities: t_pm_times_supported: int t_sw_time_supported: int tx_snr_capability: int + # Bluetooth 6.3 [v2] additions from LE CS Read Local Supported + # Capabilities V2 (opcode 0x20A5). Populated when the local controller + # supports the V2 command; zero otherwise. `cs_ipt_reflector_supported` + # is a derived boolean from `subfeatures_supported` bit 4 (the + # CS_IPT_REFLECTOR subfeature mask per Bluetooth Core Spec / Nordic + # hci_types.h:3934-3935). + t_ip2_ipt_times_supported: int = 0 + t_sw_ipt_time_supported: int = 0 + cs_ipt_reflector_supported: bool = False # ----------------------------------------------------------------------------- @@ -2961,9 +2970,30 @@ async def power_on(self) -> None: bit_value=1, ) ) - result = await self.send_sync_command( - hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command() - ) + # Prefer the Bluetooth 6.3 V2 caps read (opcode 0x20A5) which + # appends T_IP2_IPT_TIMES and T_SW_IPT_TIME after tx_snr_capability. + # supports_command() would be the clean gate, but Nordic hci_uart + # firmware doesn't always set the LE Supported Commands bit for + # V2 even when the command works — so we blind-call V2 and + # transparently fall back to 6.0 on UNKNOWN_HCI_COMMAND. That + # keeps 6.0-only controllers working unchanged while extracting + # IPT timings whenever the controller actually implements V2. + use_v2 = True + try: + result = await self.send_sync_command( + hci.HCI_LE_CS_Read_Local_Supported_Capabilities_V2_Command() + ) + except hci.HCI_Error as e: + if e.error_code != hci.HCI_UNKNOWN_HCI_COMMAND_ERROR: + raise + use_v2 = False + result = await self.send_sync_command( + hci.HCI_LE_CS_Read_Local_Supported_Capabilities_Command() + ) + # The V2 dataclass renamed rtt_random_sequence_n → rtt_random_payload_n + # (same semantic). Cover both. + rtt_random = (getattr(result, 'rtt_random_payload_n', None) + if use_v2 else result.rtt_random_sequence_n) self.cs_capabilities = ChannelSoundingCapabilities( num_config_supported=result.num_config_supported, max_consecutive_procedures_supported=result.max_consecutive_procedures_supported, @@ -2974,7 +3004,7 @@ async def power_on(self) -> None: rtt_capability=result.rtt_capability, rtt_aa_only_n=result.rtt_aa_only_n, rtt_sounding_n=result.rtt_sounding_n, - rtt_random_sequence_n=result.rtt_random_sequence_n, + rtt_random_sequence_n=rtt_random, nadm_sounding_capability=result.nadm_sounding_capability, nadm_random_capability=result.nadm_random_capability, cs_sync_phys_supported=result.cs_sync_phys_supported, @@ -2985,6 +3015,11 @@ async def power_on(self) -> None: t_pm_times_supported=result.t_pm_times_supported, t_sw_time_supported=result.t_sw_time_supported, tx_snr_capability=result.tx_snr_capability, + t_ip2_ipt_times_supported=getattr(result, 't_ip2_ipt_times_supported', 0), + t_sw_ipt_time_supported=getattr(result, 't_sw_ipt_time_supported', 0), + cs_ipt_reflector_supported=bool( + result.subfeatures_supported & hci.CsSubfeature.CS_IPT_REFLECTOR + ), ) if ( @@ -5448,6 +5483,7 @@ async def create_cs_config( channel_selection_type: int = hci.HCI_LE_CS_Create_Config_Command.ChannelSelectionType.ALGO_3B, ch3c_shape: int = hci.HCI_LE_CS_Create_Config_Command.Ch3cShape.HAT, ch3c_jump: int = 0x03, + cs_enhancements_1: int = 0x00, ) -> ChannelSoundingConfig: complete_future: asyncio.Future[ChannelSoundingConfig] = ( asyncio.get_running_loop().create_future() @@ -5493,7 +5529,7 @@ async def create_cs_config( channel_selection_type=channel_selection_type, ch3c_shape=ch3c_shape, ch3c_jump=ch3c_jump, - reserved=0x00, + cs_enhancements_1=cs_enhancements_1, ) ) return await complete_future @@ -6826,6 +6862,19 @@ def on_connection_data_length_change( def on_cs_remote_supported_capabilities( self, event: hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_Event ): + self._on_cs_remote_supported_capabilities_impl(event) + + @host_event_handler + def on_cs_remote_supported_capabilities_v2( + self, + event: hci.HCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_V2_Event, + ): + # 6.3 variant with T_IP2_IPT_Times and T_SW_IPT_Time appended. + # Fed through the same builder so downstream code doesn't care which + # HCI event landed. + self._on_cs_remote_supported_capabilities_impl(event) + + def _on_cs_remote_supported_capabilities_impl(self, event) -> None: if not (connection := self.lookup_connection(event.connection_handle)): return @@ -6835,6 +6884,12 @@ def on_cs_remote_supported_capabilities( ) return + # V1 event carries rtt_random_sequence_n; V2 renamed the field to + # rtt_random_payload_n (same semantic per spec 6.3). + rtt_random = getattr( + event, 'rtt_random_payload_n', + getattr(event, 'rtt_random_sequence_n', 0), + ) capabilities = ChannelSoundingCapabilities( num_config_supported=event.num_config_supported, max_consecutive_procedures_supported=event.max_consecutive_procedures_supported, @@ -6845,7 +6900,7 @@ def on_cs_remote_supported_capabilities( rtt_capability=event.rtt_capability, rtt_aa_only_n=event.rtt_aa_only_n, rtt_sounding_n=event.rtt_sounding_n, - rtt_random_sequence_n=event.rtt_random_sequence_n, + rtt_random_sequence_n=rtt_random, nadm_sounding_capability=event.nadm_sounding_capability, nadm_random_capability=event.nadm_random_capability, cs_sync_phys_supported=event.cs_sync_phys_supported, @@ -6856,6 +6911,11 @@ def on_cs_remote_supported_capabilities( t_pm_times_supported=event.t_pm_times_supported, t_sw_time_supported=event.t_sw_time_supported, tx_snr_capability=event.tx_snr_capability, + t_ip2_ipt_times_supported=getattr(event, 't_ip2_ipt_times_supported', 0), + t_sw_ipt_time_supported=getattr(event, 't_sw_ipt_time_supported', 0), + cs_ipt_reflector_supported=bool( + event.subfeatures_supported & hci.CsSubfeature.CS_IPT_REFLECTOR + ), ) connection.emit(connection.EVENT_CHANNEL_SOUNDING_CAPABILITIES, capabilities) From 15d8c07e46e4f24f9f8c64150e8435f5d9b78612 Mon Sep 17 00:00:00 2001 From: JoeBakalor Date: Wed, 15 Jul 2026 19:25:30 -0500 Subject: [PATCH 3/3] device: apply project formatter (black) --- bumble/device.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/bumble/device.py b/bumble/device.py index 9bffeb425..e2f0604d1 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -2992,8 +2992,11 @@ async def power_on(self) -> None: ) # The V2 dataclass renamed rtt_random_sequence_n → rtt_random_payload_n # (same semantic). Cover both. - rtt_random = (getattr(result, 'rtt_random_payload_n', None) - if use_v2 else result.rtt_random_sequence_n) + rtt_random = ( + getattr(result, 'rtt_random_payload_n', None) + if use_v2 + else result.rtt_random_sequence_n + ) self.cs_capabilities = ChannelSoundingCapabilities( num_config_supported=result.num_config_supported, max_consecutive_procedures_supported=result.max_consecutive_procedures_supported, @@ -3015,8 +3018,12 @@ async def power_on(self) -> None: t_pm_times_supported=result.t_pm_times_supported, t_sw_time_supported=result.t_sw_time_supported, tx_snr_capability=result.tx_snr_capability, - t_ip2_ipt_times_supported=getattr(result, 't_ip2_ipt_times_supported', 0), - t_sw_ipt_time_supported=getattr(result, 't_sw_ipt_time_supported', 0), + t_ip2_ipt_times_supported=getattr( + result, 't_ip2_ipt_times_supported', 0 + ), + t_sw_ipt_time_supported=getattr( + result, 't_sw_ipt_time_supported', 0 + ), cs_ipt_reflector_supported=bool( result.subfeatures_supported & hci.CsSubfeature.CS_IPT_REFLECTOR ), @@ -6887,7 +6894,8 @@ def _on_cs_remote_supported_capabilities_impl(self, event) -> None: # V1 event carries rtt_random_sequence_n; V2 renamed the field to # rtt_random_payload_n (same semantic per spec 6.3). rtt_random = getattr( - event, 'rtt_random_payload_n', + event, + 'rtt_random_payload_n', getattr(event, 'rtt_random_sequence_n', 0), ) capabilities = ChannelSoundingCapabilities(