fix a few timescale adjustments

This commit is contained in:
Gilles Boccon-Gibod
2025-05-03 12:00:34 -07:00
parent d3bd5a759f
commit fbd03ed4a5
5 changed files with 127 additions and 54 deletions

View File

@@ -121,9 +121,9 @@ def print_connection(connection):
params.append( params.append(
'Parameters=' 'Parameters='
f'{connection.parameters.connection_interval * 1.25:.2f}/' f'{connection.parameters.connection_interval:.2f}/'
f'{connection.parameters.peripheral_latency}/' f'{connection.parameters.peripheral_latency}/'
f'{connection.parameters.supervision_timeout * 10} ' f'{connection.parameters.supervision_timeout:.2f} '
) )
params.append(f'MTU={connection.att_mtu}') params.append(f'MTU={connection.att_mtu}')

View File

@@ -335,9 +335,9 @@ class ConsoleApp:
elif self.connected_peer: elif self.connected_peer:
connection = self.connected_peer.connection connection = self.connected_peer.connection
connection_parameters = ( connection_parameters = (
f'{connection.parameters.connection_interval}/' f'{connection.parameters.connection_interval:.2f}/'
f'{connection.parameters.peripheral_latency}/' f'{connection.parameters.peripheral_latency}/'
f'{connection.parameters.supervision_timeout}' f'{connection.parameters.supervision_timeout:.2f}'
) )
if self.connection_phy is not None: if self.connection_phy is not None:
phy_state = ( phy_state = (

View File

@@ -61,7 +61,6 @@ from bumble.core import (
BaseBumbleError, BaseBumbleError,
ConnectionParameterUpdateError, ConnectionParameterUpdateError,
CommandTimeoutError, CommandTimeoutError,
ConnectionParameters,
ConnectionPHY, ConnectionPHY,
InvalidArgumentError, InvalidArgumentError,
InvalidOperationError, InvalidOperationError,
@@ -484,7 +483,7 @@ class BIGInfoAdvertisement:
sid: int sid: int
num_bis: int num_bis: int
nse: int nse: int
iso_interval: int iso_interval: float
bn: int bn: int
pto: int pto: int
irc: int irc: int
@@ -502,7 +501,7 @@ class BIGInfoAdvertisement:
sid, sid,
report.num_bis, report.num_bis,
report.nse, report.nse,
report.iso_interval, report.iso_interval * 1.25,
report.bn, report.bn,
report.pto, report.pto,
report.irc, report.irc,
@@ -529,8 +528,8 @@ class AdvertisingParameters:
advertising_event_properties: AdvertisingEventProperties = field( advertising_event_properties: AdvertisingEventProperties = field(
default_factory=AdvertisingEventProperties default_factory=AdvertisingEventProperties
) )
primary_advertising_interval_min: int = DEVICE_DEFAULT_ADVERTISING_INTERVAL primary_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
primary_advertising_interval_max: int = DEVICE_DEFAULT_ADVERTISING_INTERVAL primary_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
primary_advertising_channel_map: ( primary_advertising_channel_map: (
hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap hci.HCI_LE_Set_Extended_Advertising_Parameters_Command.ChannelMap
) = ( ) = (
@@ -554,8 +553,8 @@ class AdvertisingParameters:
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@dataclass @dataclass
class PeriodicAdvertisingParameters: class PeriodicAdvertisingParameters:
periodic_advertising_interval_min: int = DEVICE_DEFAULT_ADVERTISING_INTERVAL periodic_advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
periodic_advertising_interval_max: int = DEVICE_DEFAULT_ADVERTISING_INTERVAL periodic_advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
periodic_advertising_properties: ( periodic_advertising_properties: (
hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command.Properties
) = field( ) = field(
@@ -685,8 +684,12 @@ class AdvertisingSet(utils.EventEmitter):
await self.device.send_command( await self.device.send_command(
hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command( hci.HCI_LE_Set_Periodic_Advertising_Parameters_Command(
advertising_handle=self.advertising_handle, advertising_handle=self.advertising_handle,
periodic_advertising_interval_min=advertising_parameters.periodic_advertising_interval_min, periodic_advertising_interval_min=int(
periodic_advertising_interval_max=advertising_parameters.periodic_advertising_interval_max, advertising_parameters.periodic_advertising_interval_min / 1.25
),
periodic_advertising_interval_max=int(
advertising_parameters.periodic_advertising_interval_max / 1.25
),
periodic_advertising_properties=advertising_parameters.periodic_advertising_properties, periodic_advertising_properties=advertising_parameters.periodic_advertising_properties,
), ),
check_result=True, check_result=True,
@@ -826,7 +829,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
filter_duplicates: bool filter_duplicates: bool
status: int status: int
advertiser_phy: int advertiser_phy: int
periodic_advertising_interval: int periodic_advertising_interval: float # Advertising interval, in milliseconds
advertiser_clock_accuracy: int advertiser_clock_accuracy: int
EVENT_STATE_CHANGE = "state_change" EVENT_STATE_CHANGE = "state_change"
@@ -950,7 +953,7 @@ class PeriodicAdvertisingSync(utils.EventEmitter):
if status == hci.HCI_SUCCESS: if status == hci.HCI_SUCCESS:
self.sync_handle = sync_handle self.sync_handle = sync_handle
self.advertiser_phy = advertiser_phy self.advertiser_phy = advertiser_phy
self.periodic_advertising_interval = periodic_advertising_interval self.periodic_advertising_interval = periodic_advertising_interval * 1.25
self.advertiser_clock_accuracy = advertiser_clock_accuracy self.advertiser_clock_accuracy = advertiser_clock_accuracy
self.state = self.State.ESTABLISHED self.state = self.State.ESTABLISHED
self.emit(self.EVENT_ESTABLISHMENT) self.emit(self.EVENT_ESTABLISHMENT)
@@ -1055,7 +1058,7 @@ class Big(utils.EventEmitter):
pto: int = 0 pto: int = 0
irc: int = 0 irc: int = 0
max_pdu: int = 0 max_pdu: int = 0
iso_interval: int = 0 iso_interval: float = 0.0
bis_links: Sequence[BisLink] = () bis_links: Sequence[BisLink] = ()
def __post_init__(self) -> None: def __post_init__(self) -> None:
@@ -1116,7 +1119,7 @@ class BigSync(utils.EventEmitter):
pto: int = 0 pto: int = 0
irc: int = 0 irc: int = 0
max_pdu: int = 0 max_pdu: int = 0
iso_interval: int = 0 iso_interval: float = 0.0
bis_links: Sequence[BisLink] = () bis_links: Sequence[BisLink] = ()
def __post_init__(self) -> None: def __post_init__(self) -> None:
@@ -1197,11 +1200,11 @@ class ChannelSoundingProcedure:
selected_tx_power: int selected_tx_power: int
subevent_len: int subevent_len: int
subevents_per_event: int subevents_per_event: int
subevent_interval: int subevent_interval: float # milliseconds.
event_interval: int event_interval: int
procedure_interval: int procedure_interval: int
procedure_count: int procedure_count: int
max_procedure_len: int max_procedure_len: float # milliseconds.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -1226,9 +1229,8 @@ class Peer:
def __init__(self, connection: Connection) -> None: def __init__(self, connection: Connection) -> None:
self.connection = connection self.connection = connection
# Create a GATT client for the connection # Shortcut to the connection's GATT client
self.gatt_client = gatt_client.Client(connection) self.gatt_client = connection.gatt_client
connection.gatt_client = self.gatt_client
@property @property
def services(self) -> list[gatt_client.ServiceProxy]: def services(self) -> list[gatt_client.ServiceProxy]:
@@ -1586,7 +1588,7 @@ class Connection(utils.CompositeEventEmitter):
encryption: int encryption: int
authenticated: bool authenticated: bool
sc: bool sc: bool
link_key_type: int link_key_type: Optional[int]
gatt_client: gatt_client.Client gatt_client: gatt_client.Client
pairing_peer_io_capability: Optional[int] pairing_peer_io_capability: Optional[int]
pairing_peer_authentication_requirements: Optional[int] pairing_peer_authentication_requirements: Optional[int]
@@ -1656,17 +1658,23 @@ class Connection(utils.CompositeEventEmitter):
def on_connection_encryption_key_refresh(self): def on_connection_encryption_key_refresh(self):
pass pass
@dataclass
class Parameters:
connection_interval: float # Connection interval, in milliseconds. [LE only]
peripheral_latency: int # Peripheral latency, in number of intervals. [LE only]
supervision_timeout: float # Supervision timeout, in milliseconds.
def __init__( def __init__(
self, self,
device, device: Device,
handle, handle: int,
transport, transport: core.PhysicalTransport,
self_address, self_address: hci.Address,
self_resolvable_address, self_resolvable_address: Optional[hci.Address],
peer_address, peer_address: hci.Address,
peer_resolvable_address, peer_resolvable_address: Optional[hci.Address],
role, role: hci.Role,
parameters, parameters: Parameters,
): ):
super().__init__() super().__init__()
self.device = device self.device = device
@@ -1685,7 +1693,7 @@ class Connection(utils.CompositeEventEmitter):
self.link_key_type = None self.link_key_type = None
self.att_mtu = ATT_DEFAULT_MTU self.att_mtu = ATT_DEFAULT_MTU
self.data_length = DEVICE_DEFAULT_DATA_LENGTH self.data_length = DEVICE_DEFAULT_DATA_LENGTH
self.gatt_client = None # Per-connection client self.gatt_client = gatt_client.Client(self) # Per-connection client
self.gatt_server = ( self.gatt_server = (
device.gatt_server device.gatt_server
) # By default, use the device's shared server ) # By default, use the device's shared server
@@ -1812,12 +1820,22 @@ class Connection(utils.CompositeEventEmitter):
async def update_parameters( async def update_parameters(
self, self,
connection_interval_min, connection_interval_min: float,
connection_interval_max, connection_interval_max: float,
max_latency, max_latency: int,
supervision_timeout, supervision_timeout: float,
use_l2cap=False, use_l2cap=False,
): ) -> None:
"""
Request an update of the connection parameters.
Args:
connection_interval_min: Minimum interval, in milliseconds.
connection_interval_max: Maximum interval, in milliseconds.
max_latency: Latency, in number of intervals.
supervision_timeout: Timeout, in milliseconds.
use_l2cap: Request the update via L2CAP.
"""
return await self.device.update_connection_parameters( return await self.device.update_connection_parameters(
self, self,
connection_interval_min, connection_interval_min,
@@ -1904,8 +1922,8 @@ class DeviceConfiguration:
address: hci.Address = hci.Address(DEVICE_DEFAULT_ADDRESS) address: hci.Address = hci.Address(DEVICE_DEFAULT_ADDRESS)
class_of_device: int = DEVICE_DEFAULT_CLASS_OF_DEVICE class_of_device: int = DEVICE_DEFAULT_CLASS_OF_DEVICE
scan_response_data: bytes = DEVICE_DEFAULT_SCAN_RESPONSE_DATA scan_response_data: bytes = DEVICE_DEFAULT_SCAN_RESPONSE_DATA
advertising_interval_min: int = DEVICE_DEFAULT_ADVERTISING_INTERVAL advertising_interval_min: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
advertising_interval_max: int = DEVICE_DEFAULT_ADVERTISING_INTERVAL advertising_interval_max: float = DEVICE_DEFAULT_ADVERTISING_INTERVAL
le_enabled: bool = True le_enabled: bool = True
le_simultaneous_enabled: bool = False le_simultaneous_enabled: bool = False
le_privacy_enabled: bool = False le_privacy_enabled: bool = False
@@ -2824,8 +2842,8 @@ class Device(utils.CompositeEventEmitter):
auto_restart: bool = False, auto_restart: bool = False,
advertising_data: Optional[bytes] = None, advertising_data: Optional[bytes] = None,
scan_response_data: Optional[bytes] = None, scan_response_data: Optional[bytes] = None,
advertising_interval_min: Optional[int] = None, advertising_interval_min: Optional[float] = None,
advertising_interval_max: Optional[int] = None, advertising_interval_max: Optional[float] = None,
) -> None: ) -> None:
"""Start legacy advertising. """Start legacy advertising.
@@ -3980,20 +3998,39 @@ class Device(utils.CompositeEventEmitter):
async def update_connection_parameters( async def update_connection_parameters(
self, self,
connection, connection: Connection,
connection_interval_min, connection_interval_min: float,
connection_interval_max, connection_interval_max: float,
max_latency, max_latency: int,
supervision_timeout, supervision_timeout: float,
min_ce_length=0, min_ce_length: float = 0.0,
max_ce_length=0, max_ce_length: float = 0.0,
use_l2cap=False, use_l2cap: bool = False,
) -> None: ) -> None:
''' '''
Request an update of the connection parameters.
Args:
connection: The connection to update
connection_interval_min: Minimum interval, in milliseconds.
connection_interval_max: Maximum interval, in milliseconds.
max_latency: Latency, in number of intervals.
supervision_timeout: Timeout, in milliseconds.
min_ce_length: Minimum connection event length, in milliseconds.
max_ce_length: Maximum connection event length, in milliseconds.
use_l2cap: Request the update via L2CAP.
NOTE: the name of the parameters may look odd, but it just follows the names NOTE: the name of the parameters may look odd, but it just follows the names
used in the Bluetooth spec. used in the Bluetooth spec.
''' '''
# Convert the input parameters
connection_interval_min = int(connection_interval_min / 1.25)
connection_interval_max = int(connection_interval_max / 1.25)
supervision_timeout = int(supervision_timeout / 10)
min_ce_length = int(min_ce_length / 0.625)
max_ce_length = int(max_ce_length / 0.625)
if use_l2cap: if use_l2cap:
if connection.role != hci.Role.PERIPHERAL: if connection.role != hci.Role.PERIPHERAL:
raise InvalidStateError( raise InvalidStateError(
@@ -4011,6 +4048,8 @@ class Device(utils.CompositeEventEmitter):
if l2cap_result != l2cap.L2CAP_CONNECTION_PARAMETERS_ACCEPTED_RESULT: if l2cap_result != l2cap.L2CAP_CONNECTION_PARAMETERS_ACCEPTED_RESULT:
raise ConnectionParameterUpdateError(l2cap_result) raise ConnectionParameterUpdateError(l2cap_result)
return
result = await self.send_command( result = await self.send_command(
hci.HCI_LE_Connection_Update_Command( hci.HCI_LE_Connection_Update_Command(
connection_handle=connection.handle, connection_handle=connection.handle,
@@ -5208,7 +5247,7 @@ class Device(utils.CompositeEventEmitter):
big.pto = pto big.pto = pto
big.irc = irc big.irc = irc
big.max_pdu = max_pdu big.max_pdu = max_pdu
big.iso_interval = iso_interval big.iso_interval = iso_interval * 1.25
big.state = Big.State.ACTIVE big.state = Big.State.ACTIVE
for bis_link in big.bis_links: for bis_link in big.bis_links:
@@ -5257,7 +5296,7 @@ class Device(utils.CompositeEventEmitter):
big_sync.pto = pto big_sync.pto = pto
big_sync.irc = irc big_sync.irc = irc
big_sync.max_pdu = max_pdu big_sync.max_pdu = max_pdu
big_sync.iso_interval = iso_interval big_sync.iso_interval = iso_interval * 1.25
big_sync.bis_links = [ big_sync.bis_links = [
BisLink(handle=handle, big=big_sync) for handle in bis_handles BisLink(handle=handle, big=big_sync) for handle in bis_handles
] ]
@@ -5314,7 +5353,7 @@ class Device(utils.CompositeEventEmitter):
self_resolvable_address: Optional[hci.Address], self_resolvable_address: Optional[hci.Address],
peer_resolvable_address: Optional[hci.Address], peer_resolvable_address: Optional[hci.Address],
role: hci.Role, role: hci.Role,
connection_parameters: ConnectionParameters, connection_parameters: Optional[core.ConnectionParameters],
) -> None: ) -> None:
# Convert all-zeros addresses into None. # Convert all-zeros addresses into None.
if self_resolvable_address == hci.Address.ANY_RANDOM: if self_resolvable_address == hci.Address.ANY_RANDOM:
@@ -5345,6 +5384,8 @@ class Device(utils.CompositeEventEmitter):
return return
assert connection_parameters is not None
if peer_resolvable_address is None: if peer_resolvable_address is None:
# Resolve the peer address if we can # Resolve the peer address if we can
if self.address_resolver: if self.address_resolver:
@@ -5400,7 +5441,11 @@ class Device(utils.CompositeEventEmitter):
peer_address, peer_address,
peer_resolvable_address, peer_resolvable_address,
role, role,
connection_parameters, Connection.Parameters(
connection_parameters.connection_interval * 1.25,
connection_parameters.peripheral_latency,
connection_parameters.supervision_timeout * 10.0,
),
) )
self.connections[connection_handle] = connection self.connections[connection_handle] = connection

View File

@@ -5971,6 +5971,33 @@ class HCI_LE_Enhanced_Connection_Complete_Event(HCI_LE_Meta_Event):
''' '''
# -----------------------------------------------------------------------------
@HCI_LE_Meta_Event.event(
[
('status', STATUS_SPEC),
('connection_handle', 2),
(
'role',
{'size': 1, 'mapper': lambda x: 'CENTRAL' if x == 0 else 'PERIPHERAL'},
),
('peer_address_type', Address.ADDRESS_TYPE_SPEC),
('peer_address', Address.parse_address_preceded_by_type),
('local_resolvable_private_address', Address.parse_random_address),
('peer_resolvable_private_address', Address.parse_random_address),
('connection_interval', 2),
('peripheral_latency', 2),
('supervision_timeout', 2),
('central_clock_accuracy', 1),
('advertising_handle', 1),
('sync_handle', 2),
]
)
class HCI_LE_Enhanced_Connection_Complete_V2_Event(HCI_LE_Meta_Event):
'''
See Bluetooth spec @ 7.7.65.10 LE Enhanced Connection Complete Event
'''
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@HCI_LE_Meta_Event.event( @HCI_LE_Meta_Event.event(
[ [

View File

@@ -456,6 +456,7 @@ class Host(utils.EventEmitter):
hci.HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT, hci.HCI_LE_READ_LOCAL_P_256_PUBLIC_KEY_COMPLETE_EVENT,
hci.HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT, hci.HCI_LE_GENERATE_DHKEY_COMPLETE_EVENT,
hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT, hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_EVENT,
hci.HCI_LE_ENHANCED_CONNECTION_COMPLETE_V2_EVENT,
hci.HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT, hci.HCI_LE_DIRECTED_ADVERTISING_REPORT_EVENT,
hci.HCI_LE_PHY_UPDATE_COMPLETE_EVENT, hci.HCI_LE_PHY_UPDATE_COMPLETE_EVENT,
hci.HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT, hci.HCI_LE_EXTENDED_ADVERTISING_REPORT_EVENT,