From 65e6d683553c3b2dcc748015a1106943162b5764 Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Tue, 15 Aug 2023 13:33:09 -0700 Subject: [PATCH 1/3] add tcp server --- examples/run_rfcomm_server.py | 109 +++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/examples/run_rfcomm_server.py b/examples/run_rfcomm_server.py index 71feca9..a27c67e 100644 --- a/examples/run_rfcomm_server.py +++ b/examples/run_rfcomm_server.py @@ -33,10 +33,11 @@ from bumble.sdp import ( SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, ) +from bumble.utils import AsyncRunner # ----------------------------------------------------------------------------- -def sdp_records(channel): +def sdp_records(channel, uuid): return { 0x00010001: [ ServiceAttribute( @@ -49,9 +50,7 @@ def sdp_records(channel): ), ServiceAttribute( SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - DataElement.sequence( - [DataElement.uuid(UUID('E6D55659-C8B4-4B85-96BB-B1143AF6D3AE'))] - ), + DataElement.sequence([DataElement.uuid(UUID(uuid))]), ), ServiceAttribute( SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, @@ -72,31 +71,90 @@ def sdp_records(channel): # ----------------------------------------------------------------------------- -def on_dlc(dlc): - print('*** DLC connected', dlc) - dlc.sink = lambda data: on_rfcomm_data_received(dlc, data) +def on_rfcomm_session(rfcomm_session, tcp_server): + print('*** RFComm session connected', rfcomm_session) + tcp_server.attach_session(rfcomm_session) # ----------------------------------------------------------------------------- -def on_rfcomm_data_received(dlc, data): - print(f'<<< Data received: {data.hex()}') - try: - message = data.decode('utf-8') - print(f'<<< Message = {message}') - except Exception: - pass +class TcpServerProtocol(asyncio.Protocol): + def __init__(self, server): + self.server = server - # Echo everything back - dlc.write(data) + def connection_made(self, transport): + peer_name = transport.get_extra_info('peer_name') + print(f'<<< TCP Server: connection from {peer_name}') + if self.server: + self.server.tcp_transport = transport + else: + transport.close() + + def connection_lost(self, exc): + print('<<< TCP Server: connection lost') + if self.server: + self.server.tcp_transport = None + + def data_received(self, data): + print(f'<<< TCP Server: data received: {len(data)} bytes - {data.hex()}') + if self.server: + self.server.tcp_data_received(data) + + +# ----------------------------------------------------------------------------- +class TcpServer: + def __init__(self, port): + self.rfcomm_session = None + self.tcp_transport = None + AsyncRunner.spawn(self.run(port)) + + def attach_session(self, rfcomm_session): + if self.rfcomm_session: + self.rfcomm_session.sink = None + + self.rfcomm_session = rfcomm_session + rfcomm_session.sink = self.rfcomm_data_received + + def rfcomm_data_received(self, data): + print(f'<<< RFCOMM Data: {data.hex()}') + if self.tcp_transport: + self.tcp_transport.write(data) + else: + print('!!! no TCP connection, dropping data') + + def tcp_data_received(self, data): + if self.rfcomm_session: + self.rfcomm_session.write(data) + else: + print('!!! no RFComm session, dropping data') + + async def run(self, port): + print(f'$$$ Starting TCP server on port {port}') + + server = await asyncio.get_running_loop().create_server( + lambda: TcpServerProtocol(self), '127.0.0.1', port + ) + + async with server: + await server.serve_forever() # ----------------------------------------------------------------------------- async def main(): - if len(sys.argv) < 3: - print('Usage: run_rfcomm_server.py ') - print('example: run_rfcomm_server.py classic2.json usb:04b4:f901') + if len(sys.argv) < 4: + print( + 'Usage: run_rfcomm_server.py ' + ' []' + ) + print('example: run_rfcomm_server.py classic2.json usb:0 8888') return + tcp_port = int(sys.argv[3]) + + if len(sys.argv) >= 5: + uuid = sys.argv[4] + else: + uuid = 'E6D55659-C8B4-4B85-96BB-B1143AF6D3AE' + print('<<< connecting to HCI...') async with await open_transport_or_link(sys.argv[2]) as (hci_source, hci_sink): print('<<< connected') @@ -105,15 +163,20 @@ async def main(): device = Device.from_config_file_with_hci(sys.argv[1], hci_source, hci_sink) device.classic_enabled = True - # Create and register a server + # Create a TCP server + tcp_server = TcpServer(tcp_port) + + # Create and register an RFComm server rfcomm_server = Server(device) # Listen for incoming DLC connections - channel_number = rfcomm_server.listen(on_dlc) - print(f'### Listening for connection on channel {channel_number}') + channel_number = rfcomm_server.listen( + lambda session: on_rfcomm_session(session, tcp_server) + ) + print(f'### Listening for RFComm connections on channel {channel_number}') # Setup the SDP to advertise this channel - device.sdp_service_records = sdp_records(channel_number) + device.sdp_service_records = sdp_records(channel_number, uuid) # Start the controller await device.power_on() From 1ea12b1bf7644b6f4d68c52f48bd0b89502a7f5d Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Wed, 6 Sep 2023 16:33:47 -0700 Subject: [PATCH 2/3] rebase --- bumble/rfcomm.py | 60 +++++++++++++++++++++++++++++++++-- examples/run_rfcomm_server.py | 43 +++---------------------- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/bumble/rfcomm.py b/bumble/rfcomm.py index c67ea56..5920eb0 100644 --- a/bumble/rfcomm.py +++ b/bumble/rfcomm.py @@ -20,13 +20,29 @@ from __future__ import annotations import logging import asyncio import enum +from typing import Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING from pyee import EventEmitter -from typing import Optional, Tuple, Callable, Dict, Union, TYPE_CHECKING from . import core, l2cap from .colors import color -from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError +from .core import ( + UUID, + BT_RFCOMM_PROTOCOL_ID, + BT_BR_EDR_TRANSPORT, + BT_L2CAP_PROTOCOL_ID, + InvalidStateError, + ProtocolError, +) +from .sdp import ( + SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, + SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, + SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, + SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, + SDP_PUBLIC_BROWSE_ROOT, + DataElement, + ServiceAttribute, +) if TYPE_CHECKING: from bumble.device import Device, Connection @@ -111,6 +127,46 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30 # fmt: on +# ----------------------------------------------------------------------------- +def make_service_sdp_records( + service_record_handle: int, channel: int, uuid: UUID = None +) -> List[ServiceAttribute]: + records = [ + ServiceAttribute( + SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, + DataElement.unsigned_integer_32(service_record_handle), + ), + ServiceAttribute( + SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, + DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]), + ), + ServiceAttribute( + SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, + DataElement.sequence( + [ + DataElement.sequence([DataElement.uuid(BT_L2CAP_PROTOCOL_ID)]), + DataElement.sequence( + [ + DataElement.uuid(BT_RFCOMM_PROTOCOL_ID), + DataElement.unsigned_integer_8(channel), + ] + ), + ] + ), + ), + ] + + if uuid: + records.append( + ServiceAttribute( + SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, + DataElement.sequence([DataElement.uuid(UUID(uuid))]), + ) + ) + + return records + + # ----------------------------------------------------------------------------- def compute_fcs(buffer: bytes) -> int: result = 0xFF diff --git a/examples/run_rfcomm_server.py b/examples/run_rfcomm_server.py index a27c67e..0e7e72e 100644 --- a/examples/run_rfcomm_server.py +++ b/examples/run_rfcomm_server.py @@ -22,51 +22,18 @@ import logging from bumble.device import Device from bumble.transport import open_transport_or_link -from bumble.core import BT_L2CAP_PROTOCOL_ID, BT_RFCOMM_PROTOCOL_ID, UUID from bumble.rfcomm import Server -from bumble.sdp import ( - DataElement, - ServiceAttribute, - SDP_PUBLIC_BROWSE_ROOT, - SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, - SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, - SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, -) from bumble.utils import AsyncRunner +from bumble.rfcomm import make_service_sdp_records # ----------------------------------------------------------------------------- def sdp_records(channel, uuid): + service_record_handle = 0x00010001 return { - 0x00010001: [ - ServiceAttribute( - SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, - DataElement.unsigned_integer_32(0x00010001), - ), - ServiceAttribute( - SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID, - DataElement.sequence([DataElement.uuid(SDP_PUBLIC_BROWSE_ROOT)]), - ), - ServiceAttribute( - SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - DataElement.sequence([DataElement.uuid(UUID(uuid))]), - ), - ServiceAttribute( - SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID, - DataElement.sequence( - [ - DataElement.sequence([DataElement.uuid(BT_L2CAP_PROTOCOL_ID)]), - DataElement.sequence( - [ - DataElement.uuid(BT_RFCOMM_PROTOCOL_ID), - DataElement.unsigned_integer_8(channel), - ] - ), - ] - ), - ), - ] + service_record_handle: make_service_sdp_records( + service_record_handle, channel, uuid + ) } From 8be9f4cb0e6bbecd563091752cd4863319ecb47f Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Wed, 6 Sep 2023 16:48:08 -0700 Subject: [PATCH 3/3] add doc and fix types --- bumble/rfcomm.py | 8 ++++++-- examples/run_rfcomm_server.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bumble/rfcomm.py b/bumble/rfcomm.py index 5920eb0..02c18fa 100644 --- a/bumble/rfcomm.py +++ b/bumble/rfcomm.py @@ -129,8 +129,12 @@ RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30 # ----------------------------------------------------------------------------- def make_service_sdp_records( - service_record_handle: int, channel: int, uuid: UUID = None + service_record_handle: int, channel: int, uuid: Optional[UUID] = None ) -> List[ServiceAttribute]: + """ + Create SDP records for an RFComm service given a channel number and an + optional UUID. A Service Class Attribute is included only if the UUID is not None. + """ records = [ ServiceAttribute( SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID, @@ -160,7 +164,7 @@ def make_service_sdp_records( records.append( ServiceAttribute( SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID, - DataElement.sequence([DataElement.uuid(UUID(uuid))]), + DataElement.sequence([DataElement.uuid(uuid)]), ) ) diff --git a/examples/run_rfcomm_server.py b/examples/run_rfcomm_server.py index 0e7e72e..41915a4 100644 --- a/examples/run_rfcomm_server.py +++ b/examples/run_rfcomm_server.py @@ -20,6 +20,7 @@ import sys import os import logging +from bumble.core import UUID from bumble.device import Device from bumble.transport import open_transport_or_link from bumble.rfcomm import Server @@ -32,7 +33,7 @@ def sdp_records(channel, uuid): service_record_handle = 0x00010001 return { service_record_handle: make_service_sdp_records( - service_record_handle, channel, uuid + service_record_handle, channel, UUID(uuid) ) }