Refactor LE emulation with LL and Air Interface

This commit is contained in:
Josh Wu
2025-11-27 21:40:43 +08:00
parent d2a4c2a8e4
commit a84f0279b1
8 changed files with 731 additions and 656 deletions

View File

@@ -19,9 +19,12 @@ import asyncio
# Imports
# -----------------------------------------------------------------------------
import logging
from typing import Optional
from typing import TYPE_CHECKING, Optional
from bumble import controller, core, hci, lmp
from bumble import core, hci, ll, lmp
if TYPE_CHECKING:
from bumble import controller
# -----------------------------------------------------------------------------
# Logging
@@ -29,11 +32,6 @@ from bumble import controller, core, hci, lmp
logger = logging.getLogger(__name__)
# -----------------------------------------------------------------------------
# Utils
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# TODO: add more support for various LL exchanges
# (see Vol 6, Part B - 2.4 DATA CHANNEL PDU)
@@ -47,7 +45,6 @@ class LocalLink:
def __init__(self):
self.controllers = set()
self.pending_connection = None
self.pending_classic_connection = None
############################################################
@@ -61,14 +58,10 @@ class LocalLink:
def remove_controller(self, controller: controller.Controller):
self.controllers.remove(controller)
def find_controller(self, address: hci.Address) -> controller.Controller | None:
def find_le_controller(self, address: hci.Address) -> controller.Controller | None:
for controller in self.controllers:
if controller.random_address == address:
return controller
if controller.public_address == address:
return controller
for advertising_set in controller.advertising_sets.values():
if advertising_set.random_address == address:
for connection in controller.le_connections.values():
if connection.self_address == address:
return controller
return None
@@ -80,9 +73,6 @@ class LocalLink:
return controller
return None
def get_pending_connection(self):
return self.pending_connection
############################################################
# LE handlers
############################################################
@@ -90,23 +80,6 @@ class LocalLink:
def on_address_changed(self, controller):
pass
def send_advertising_data(self, sender_address: hci.Address, data: bytes):
# Send the advertising data to all controllers, except the sender
for controller in self.controllers:
if controller.random_address != sender_address:
controller.on_link_advertising_data(sender_address, data)
def send_extended_advertising_data(
self, sender_address: hci.Address, data: bytes, properties: int = 0
):
# Send the advertising data to all controllers, except the sender
sender_controller = self.find_controller(sender_address)
for controller in self.controllers:
if controller != sender_controller:
controller.on_link_extended_advertising_data(
sender_address, data, properties
)
def send_acl_data(
self,
sender_controller: controller.Controller,
@@ -116,7 +89,7 @@ class LocalLink:
):
# Send the data to the first controller with a matching address
if transport == core.PhysicalTransport.LE:
destination_controller = self.find_controller(destination_address)
destination_controller = self.find_le_controller(destination_address)
source_address = sender_controller.random_address
elif transport == core.PhysicalTransport.BR_EDR:
destination_controller = self.find_classic_controller(destination_address)
@@ -131,157 +104,30 @@ class LocalLink:
)
)
def on_connection_complete(self) -> None:
# Check that we expect this call
if not self.pending_connection:
logger.warning('on_connection_complete with no pending connection')
return
central_address, le_create_connection_command = self.pending_connection
self.pending_connection = None
# Find the controller that initiated the connection
if not (central_controller := self.find_controller(central_address)):
logger.warning('!!! Initiating controller not found')
return
# Connect to the first controller with a matching address
if peripheral_controller := self.find_controller(
le_create_connection_command.peer_address
):
central_controller.on_link_peripheral_connection_complete(
le_create_connection_command, hci.HCI_SUCCESS
)
peripheral_controller.on_link_central_connected(
central_address, le_create_connection_command.peer_address
)
return
# No peripheral found
central_controller.on_link_peripheral_connection_complete(
le_create_connection_command, hci.HCI_CONNECTION_ACCEPT_TIMEOUT_ERROR
)
def connect(
def send_advertising_pdu(
self,
central_address: hci.Address,
le_create_connection_command: (
hci.HCI_LE_Create_Connection_Command
| hci.HCI_LE_Extended_Create_Connection_Command
),
sender_controller: controller.Controller,
packet: ll.AdvertisingPdu,
):
logger.debug(
f'$$$ CONNECTION {central_address} -> '
f'{le_create_connection_command.peer_address}'
)
self.pending_connection = (central_address, le_create_connection_command)
asyncio.get_running_loop().call_soon(self.on_connection_complete)
loop = asyncio.get_running_loop()
for c in self.controllers:
if c != sender_controller:
loop.call_soon(c.on_ll_advertising_pdu, packet)
def on_disconnection_complete(
def send_ll_control_pdu(
self,
initiating_address: hci.Address,
target_address: hci.Address,
disconnect_command: hci.HCI_Disconnect_Command,
sender_address: hci.Address,
receiver_address: hci.Address,
packet: ll.ControlPdu,
):
# Find the controller that initiated the disconnection
if not (initiating_controller := self.find_controller(initiating_address)):
logger.warning('!!! Initiating controller not found')
return
# Disconnect from the first controller with a matching address
if target_controller := self.find_controller(target_address):
target_controller.on_link_disconnected(
initiating_address, disconnect_command.reason
if not (receiver_controller := self.find_le_controller(receiver_address)):
raise core.InvalidArgumentError(
f"Unable to find controller for address {receiver_address}"
)
initiating_controller.on_link_disconnection_complete(
disconnect_command, hci.HCI_SUCCESS
)
def disconnect(
self,
initiating_address: hci.Address,
target_address: hci.Address,
disconnect_command: hci.HCI_Disconnect_Command,
):
logger.debug(
f'$$$ DISCONNECTION {initiating_address} -> '
f'{target_address}: reason = {disconnect_command.reason}'
)
asyncio.get_running_loop().call_soon(
lambda: self.on_disconnection_complete(
initiating_address, target_address, disconnect_command
)
lambda: receiver_controller.on_ll_control_pdu(sender_address, packet)
)
def on_connection_encrypted(
self,
central_address: hci.Address,
peripheral_address: hci.Address,
rand: bytes,
ediv: int,
ltk: bytes,
):
logger.debug(f'*** ENCRYPTION {central_address} -> {peripheral_address}')
if central_controller := self.find_controller(central_address):
central_controller.on_link_encrypted(peripheral_address, rand, ediv, ltk)
if peripheral_controller := self.find_controller(peripheral_address):
peripheral_controller.on_link_encrypted(central_address, rand, ediv, ltk)
def create_cis(
self,
central_controller: controller.Controller,
peripheral_address: hci.Address,
cig_id: int,
cis_id: int,
) -> None:
logger.debug(
f'$$$ CIS Request {central_controller.random_address} -> {peripheral_address}'
)
if peripheral_controller := self.find_controller(peripheral_address):
asyncio.get_running_loop().call_soon(
peripheral_controller.on_link_cis_request,
central_controller.random_address,
cig_id,
cis_id,
)
def accept_cis(
self,
peripheral_controller: controller.Controller,
central_address: hci.Address,
cig_id: int,
cis_id: int,
) -> None:
logger.debug(
f'$$$ CIS Accept {peripheral_controller.random_address} -> {central_address}'
)
if central_controller := self.find_controller(central_address):
loop = asyncio.get_running_loop()
loop.call_soon(central_controller.on_link_cis_established, cig_id, cis_id)
loop.call_soon(
peripheral_controller.on_link_cis_established, cig_id, cis_id
)
def disconnect_cis(
self,
initiator_controller: controller.Controller,
peer_address: hci.Address,
cig_id: int,
cis_id: int,
) -> None:
logger.debug(
f'$$$ CIS Disconnect {initiator_controller.random_address} -> {peer_address}'
)
if peer_controller := self.find_controller(peer_address):
loop = asyncio.get_running_loop()
loop.call_soon(
initiator_controller.on_link_cis_disconnected, cig_id, cis_id
)
loop.call_soon(peer_controller.on_link_cis_disconnected, cig_id, cis_id)
############################################################
# Classic handlers
############################################################