From d2e47b79d9a7fbc3b543fe5783e9a7fc5d629676 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Thu, 30 Apr 2026 17:07:28 +0200 Subject: [PATCH] Apply advertising TX power via Zephyr vendor-specific command for Nordic SDC. --- src/auracast/multicast.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index b423f5a..b962492 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -49,6 +49,7 @@ import bumble.transport import bumble.utils from bumble.device import Host, AdvertisingChannelMap from bumble.audio import io as audio_io +from bumble.vendor.zephyr.hci import HCI_Write_Tx_Power_Level_Command from auracast import auracast_config from auracast.utils.read_lc3_file import read_lc3_file @@ -537,12 +538,46 @@ async def init_broadcast( ) bigs[f'big{i}']['advertising_set'] = advertising_set logging.info( - 'Advertising TX power: requested=%+d dBm, controller selected=%+d dBm (handle=%d)', + 'Advertising TX power (LE_Set_Ext_Adv_Params): requested=%+d dBm, controller selected=%+d dBm (handle=%d)', global_config.advertising_tx_power, getattr(advertising_set, 'selected_tx_power', 0), i, ) + # The Nordic SoftDevice Controller does not honor the per-set + # advertising_tx_power passed in HCI_LE_Set_Extended_Advertising_Parameters + # (it returns the compile-time CONFIG_BT_CTLR_TX_PWR_* value regardless). + # Apply the requested level via the Zephyr Vendor-Specific HCI command + # Write_Tx_Power_Level (opcode 0xFC0E), which the SDC honors per + # advertising handle. The SDC clamps the value to the nearest supported + # hardware step (max bounded by CONFIG_BT_CTLR_TX_PWR_PLUS_8). + try: + adv_handle = getattr(advertising_set, 'advertising_handle', i) + response = await device.send_command( + HCI_Write_Tx_Power_Level_Command( + handle_type=HCI_Write_Tx_Power_Level_Command.TX_POWER_HANDLE_TYPE_ADV, + connection_handle=adv_handle, + tx_power_level=global_config.advertising_tx_power, + ) + ) + rp = getattr(response, 'return_parameters', None) + status = getattr(rp, 'status', 0xFF) if rp is not None else 0xFF + selected = getattr(rp, 'selected_tx_power_level', None) if rp is not None else None + if status == 0 and selected is not None: + logging.info( + 'Advertising TX power (VS Write_Tx_Power_Level): requested=%+d dBm, controller selected=%+d dBm (handle=%d)', + global_config.advertising_tx_power, + selected, + adv_handle, + ) + else: + logging.warning( + 'VS Write_Tx_Power_Level failed: status=0x%02X handle=%d requested=%+d dBm', + status, adv_handle, global_config.advertising_tx_power, + ) + except Exception as e: + logging.warning('VS Write_Tx_Power_Level not supported by controller: %s', e) + logging.info('Start Periodic Advertising') await advertising_set.start_periodic()