Add Bluetooth 6.3 CS Enhancements (Inline PCT) support - #953
Conversation
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.
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.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
2138970 to
15d8c07
Compare
Annotate the CS Read Local Supported Capabilities result as the union of the V2 and 6.0 return-parameter types so both the V2 read and the fallback send_sync_command type-check, and read the renamed rtt_random field in-branch (rtt_random_payload_n on V2, rtt_random_sequence_n on 6.0) where the concrete type is known. No behavior change.
| hci.HCI_LE_CS_Read_Local_Supported_Capabilities_V2_ReturnParameters | ||
| | hci.HCI_LE_CS_Read_Local_Supported_Capabilities_ReturnParameters | ||
| ) | ||
| try: |
There was a problem hiding this comment.
shouldn't we check command supported to decide which command to send?
There was a problem hiding this comment.
You're right, and my premise for the try/except was wrong. On the controller here (nRF54LM20A, NCS v3.4.0) octet 49 is 0x0c — bit 2 set, supports_command() returns True. Gated on it in 9413603; V2 is still selected, and forcing the check false falls back to the 6.0 read.
There was a problem hiding this comment.
Oh, I missed the explanation in PR description, but nice to catch that.
| # HCI event landed. | ||
| self._on_cs_remote_supported_capabilities_impl(event) | ||
|
|
||
| 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( |
There was a problem hiding this comment.
if isinstance(event, V2):
...
else:
...
There was a problem hiding this comment.
Done in 9413603 — moved the isinstance branch into ChannelSoundingCapabilities.from_hci() so power_on() shares it.
| # 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)) |
There was a problem hiding this comment.
should not have default value for HCI events
Review feedback: HCI packet fields should not carry default values. The trailing byte of HCI_LE_CS_Create_Config is a required parameter like every other field in the class, so declare it that way. field(default=...) appeared exactly once in hci.py — this line — so removing it also restores the file's consistency. Device.create_cs_config already passes cs_enhancements_1 explicitly and is the only construction site in the tree, so no caller changes.
Addresses three review points on the CS bring-up path: 1. Use host.supports_command() to choose between the 6.0 and V2 caps reads instead of blind-calling V2 and catching UNKNOWN_HCI_COMMAND. The try/except was justified by a claim that Nordic hci_uart firmware implements the V2 command without setting octet 49 bit 2 in its LE Supported Commands reply. That does not reproduce: on an nRF54LM20A running hci_uart from nRF Connect SDK v3.4.0, octet 49 reads 0x0c, bit 2 is set, and supports_command() returns True. The gate now matches the supports_command() check used for HCI_LE_SET_HOST_FEATURE_COMMAND a few lines above. 2. Annotate _on_cs_remote_supported_capabilities_impl's event parameter with the V1 | V2 union rather than leaving it bare. 3. Replace the getattr() field probing with an explicit isinstance() branch. The isinstance branch lives in a new ChannelSoundingCapabilities.from_hci() classmethod that accepts all four HCI payload shapes (local V1/V2 return parameters, remote V1/V2 events). Those shapes share the 6.0 field names and differ only in the rtt_random_payload_n rename plus the IPT tail, so one branch covers them and the 23-argument constructor is no longer repeated at each call site. Net 94 deletions against 85 insertions. Verified on hardware: the V2 path is still selected and reports t_ip2_ipt_times_supported=0x7e, t_sw_ipt_time_supported=0x0a, subfeatures_supported=0x12, cs_ipt_reflector_supported=True — identical to the values this PR originally recorded. Forcing supports_command() to report the V2 command as unsupported sends the 6.0 read and leaves both IPT timing fields at zero.
01b00fc to
9413603
Compare
Summary
Adds Bluetooth 6.3 Channel Sounding Enhancements support to Bumble — specifically the Inline Phase Correction Term transfer (IPT) feature. Two commits:
hci:— pure additive HCI plumbing (opcodes, event codes, dataclasses, IntFlag enums, supports-commands mask, host dispatch handler). One breaking rename insideHCI_LE_CS_Create_Config_Command: the trailingreservedbyte becomescs_enhancements_1(see notes).device:— wires the plumbing intoDevice.power_on()(prefer V2 caps read, fall back to 6.0) andcreate_cs_config()(newcs_enhancements_1kwarg, default 0).Net change: +185 / -7 across three files (
hci.py,device.py,host.py).What's implemented
HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2_COMMAND0x20A5— new opcode; return-params appendt_ip2_ipt_times_supported(2 B) +t_sw_ipt_time_supported(1 B).HCI_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES_V2_COMMAND0x20A6— opcode constant only (no in-tree caller yet).HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_V2_EVENT0x38— 6.3 variant of0x2Cwith the same V2 tail.HCI_LE_CS_Read_Local_Supported_Capabilities_V2_Command/_V2_ReturnParametersHCI_LE_CS_Read_Remote_Supported_Capabilities_Complete_V2_EventCsSubfeature.CS_IPT_REFLECTOR1 << 4— bit insubfeatures_supportedthat advertises IPT reflector capability.CsEnhancements1.INLINE_PCT0x01— bit 0 of the last byte ofHCI_LE_CS_Create_Configthat enables IPT on this configuration.HCI_LE_CS_Create_Config_Command.reserved→cs_enhancements_1Device.create_cs_config(..., cs_enhancements_1=0)Device.power_on()CS bring-upUNKNOWN_HCI_COMMAND.ChannelSoundingCapabilitiest_ip2_ipt_times_supported,t_sw_ipt_time_supported,cs_ipt_reflector_supported.The
reserved→cs_enhancements_1renameThe trailing byte of
HCI_LE_CS_Create_Configwas reserved in 6.0. Bluetooth 6.3 §7.8.137 assigns bit 0 as CS Enhancements 1 — Inline PCT enable; this is the field Nordic exposes ascs_enhancements_1inzephyr/include/zephyr/bluetooth/hci_types.h:2986andbt_conn_le_cs_config::cs_enhancements_1inconn.h:851-855.Impact on downstream callers: the field is now named
cs_enhancements_1rather thanreserved. Callers that were passingreserved=0x00explicitly must update tocs_enhancements_1=0x00(or drop the kwarg entirely — default is0). No known in-tree caller does this —Device.create_cs_configis patched in the second commit.Design notes
try/exceptV2 fallback instead ofsupports_commandgate: some vendorhci_uartfirmwares implement the V2 command without setting the corresponding bit in the LE Supported Commands reply (octet 49 bit 2). A blind V2 call with fallback onUNKNOWN_HCI_COMMANDhandles both correctly-advertising and misadvertising controllers without vendor-specific probing.host.pyemits a distinctcs_remote_supported_capabilities_v2event anddevice.pyroutes both through a single_on_cs_remote_supported_capabilities_implbuilder. Existing listeners oncs_remote_supported_capabilitiescontinue to fire for 6.0 events unchanged.ChannelSoundingCapabilitiesdefault values: the three new fields all default to0/False, keeping any existing positional constructor call working.Testing
Verified end-to-end against Nordic hardware:
hci_uartfrom nRF Connect SDK v3.4.0 (withCONFIG_BT_CTLR_EXTENDED_FEAT_SET=y), attached as Bumble's HCI transport over USB serial.channel_sounding/ipt_reflectorsample from nRF Connect SDK v3.4.0.Observed:
t_ip2_ipt_times_supported = 0x7E(bits 1–6 set → 10/20/30/40/50/60 μs),t_sw_ipt_time_supported = 0x0A(10 μs),subfeatures_supported = 0x12(bit 4 → CS_IPT_REFLECTOR set).HCI_LE_CS_Create_Configwithcs_enhancements_1 = 0x01completed withstatus: SUCCESS.HCI_LE_CS_Config_Complete_Eventechoedcs_enhancements_1: 1back.HCI_LE_CS_Procedure_Enable_Complete_Eventreturnedstate: ENABLED.HCI_LE_CS_Subevent_Result_Events streamed withprocedure_counterincrementing normally.On a purely 6.0 controller (verified by simulating the
UNKNOWN_HCI_COMMANDreply againstV2_Command), everything falls back to the pre-existing 6.0 path andcs_ipt_reflector_supportedstaysFalse.Spec references
cs_enhancements_1bit 0 = Inline PCT enable.Follow-ups (not in this PR)
HCI_LE_CS_Write_Cached_Remote_Supported_Capabilities_V2_Commanddataclass (opcode constant is defined; body not wired). Uncommon path in practice.cs_enhancements_1remain reserved).