From 7d816b0c6c72132cdb9855e4f2f0234b7611ae56 Mon Sep 17 00:00:00 2001 From: uier Date: Tue, 23 Jun 2026 13:39:43 +0000 Subject: [PATCH 1/2] CIG: Fix CIG parameters for unidirectional CIS When setting up a CIG for a unidirectional CIS (e.g., Central-to-Peripheral only), the unused direction's max SDU size is set to 0. However, the other parameters (retransmission count and PHY) defaulted to non-zero values. This caused controllers to reject the configuration during CIG setup or CIS establishment with error 0x30 (Parameter Out Of Mandatory Range). This change resets the retransmission count and PHY to 0 for any direction where max SDU is 0. A test_cis_parameters_unidirectional test is included with this change. --- bumble/device.py | 8 ++++++++ tests/device_test.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/bumble/device.py b/bumble/device.py index e90dbf01a..907061601 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -1575,6 +1575,14 @@ class CisParameters: rtn_c_to_p: int = DEVICE_DEFAULT_ISO_CIS_RTN # Number of C->P retransmissions rtn_p_to_c: int = DEVICE_DEFAULT_ISO_CIS_RTN # Number of P->C retransmissions + def __post_init__(self) -> None: + if self.max_sdu_c_to_p == 0: + self.rtn_c_to_p = 0 + self.phy_c_to_p = hci.PhyBit(0) + if self.max_sdu_p_to_c == 0: + self.rtn_p_to_c = 0 + self.phy_p_to_c = hci.PhyBit(0) + cig_id: int cis_parameters: list[CisParameters] sdu_interval_c_to_p: int # C->P SDU interval, in microseconds diff --git a/tests/device_test.py b/tests/device_test.py index a45c43dd3..3efe4db1e 100644 --- a/tests/device_test.py +++ b/tests/device_test.py @@ -595,6 +595,25 @@ def on_hci_le_cis_established_event(host, event): await asyncio.wait_for(cis_create_task, _TIMEOUT) +# ----------------------------------------------------------------------------- +def test_cis_parameters_unidirectional(): + # Test C2P unidirectional (P to C not used) + cis_c2p = CigParameters.CisParameters(cis_id=1, max_sdu_p_to_c=0) + assert cis_c2p.max_sdu_c_to_p != 0 + assert cis_c2p.rtn_c_to_p != 0 + assert cis_c2p.phy_c_to_p != hci.PhyBit(0) + assert cis_c2p.rtn_p_to_c == 0 + assert cis_c2p.phy_p_to_c == hci.PhyBit(0) + + # Test P2C unidirectional (C to P not used) + cis_p2c = CigParameters.CisParameters(cis_id=2, max_sdu_c_to_p=0) + assert cis_p2c.max_sdu_p_to_c != 0 + assert cis_p2c.rtn_p_to_c != 0 + assert cis_p2c.phy_p_to_c != hci.PhyBit(0) + assert cis_p2c.rtn_c_to_p == 0 + assert cis_p2c.phy_c_to_p == hci.PhyBit(0) + + # ----------------------------------------------------------------------------- @pytest.mark.asyncio async def test_enter_and_exit_sniff_mode(): From 9f2fdf53090b6bc5599bdcb76bc5d97403ea014c Mon Sep 17 00:00:00 2001 From: uier Date: Wed, 1 Jul 2026 05:55:56 +0000 Subject: [PATCH 2/2] Add explanatory comments in post init method Clarify why we reset retransmission count and PHY to 0 when the SDU size is 0 for a direction. --- bumble/device.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bumble/device.py b/bumble/device.py index 907061601..f3b431c5e 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -1576,6 +1576,10 @@ class CisParameters: rtn_p_to_c: int = DEVICE_DEFAULT_ISO_CIS_RTN # Number of P->C retransmissions def __post_init__(self) -> None: + # For unidirectional CIS (e.g., Central-to-Peripheral only), the unused direction's + # SDU size is 0. If SDU size is 0, the corresponding retransmission count and PHY + # must also be set to 0. Otherwise, some controllers will reject the parameters + # with error 0x30 (Parameter Out Of Mandatory Range). if self.max_sdu_c_to_p == 0: self.rtn_c_to_p = 0 self.phy_c_to_p = hci.PhyBit(0)