1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# Imports 17# ----------------------------------------------------------------------------- 18import asyncio 19import sys 20import os 21import logging 22 23from bumble.core import UUID 24from bumble.device import Device 25from bumble.transport import open_transport_or_link 26from bumble.rfcomm import Server 27from bumble.utils import AsyncRunner 28from bumble.rfcomm import make_service_sdp_records 29 30 31# ----------------------------------------------------------------------------- 32def sdp_records(channel, uuid): 33 service_record_handle = 0x00010001 34 return { 35 service_record_handle: make_service_sdp_records( 36 service_record_handle, channel, UUID(uuid) 37 ) 38 } 39 40 41# ----------------------------------------------------------------------------- 42def on_rfcomm_session(rfcomm_session, tcp_server): 43 print('*** RFComm session connected', rfcomm_session) 44 tcp_server.attach_session(rfcomm_session) 45 46 47# ----------------------------------------------------------------------------- 48class TcpServerProtocol(asyncio.Protocol): 49 def __init__(self, server): 50 self.server = server 51 52 def connection_made(self, transport): 53 peer_name = transport.get_extra_info('peer_name') 54 print(f'<<< TCP Server: connection from {peer_name}') 55 if self.server: 56 self.server.tcp_transport = transport 57 else: 58 transport.close() 59 60 def connection_lost(self, exc): 61 print('<<< TCP Server: connection lost') 62 if self.server: 63 self.server.tcp_transport = None 64 65 def data_received(self, data): 66 print(f'<<< TCP Server: data received: {len(data)} bytes - {data.hex()}') 67 if self.server: 68 self.server.tcp_data_received(data) 69 70 71# ----------------------------------------------------------------------------- 72class TcpServer: 73 def __init__(self, port): 74 self.rfcomm_session = None 75 self.tcp_transport = None 76 AsyncRunner.spawn(self.run(port)) 77 78 def attach_session(self, rfcomm_session): 79 if self.rfcomm_session: 80 self.rfcomm_session.sink = None 81 82 self.rfcomm_session = rfcomm_session 83 rfcomm_session.sink = self.rfcomm_data_received 84 85 def rfcomm_data_received(self, data): 86 print(f'<<< RFCOMM Data: {data.hex()}') 87 if self.tcp_transport: 88 self.tcp_transport.write(data) 89 else: 90 print('!!! no TCP connection, dropping data') 91 92 def tcp_data_received(self, data): 93 if self.rfcomm_session: 94 self.rfcomm_session.write(data) 95 else: 96 print('!!! no RFComm session, dropping data') 97 98 async def run(self, port): 99 print(f'$$$ Starting TCP server on port {port}') 100 101 server = await asyncio.get_running_loop().create_server( 102 lambda: TcpServerProtocol(self), '127.0.0.1', port 103 ) 104 105 async with server: 106 await server.serve_forever() 107 108 109# ----------------------------------------------------------------------------- 110async def main() -> None: 111 if len(sys.argv) < 4: 112 print( 113 'Usage: run_rfcomm_server.py <device-config> <transport-spec> ' 114 '<tcp-port> [<uuid>]' 115 ) 116 print('example: run_rfcomm_server.py classic2.json usb:0 8888') 117 return 118 119 tcp_port = int(sys.argv[3]) 120 121 if len(sys.argv) >= 5: 122 uuid = sys.argv[4] 123 else: 124 uuid = 'E6D55659-C8B4-4B85-96BB-B1143AF6D3AE' 125 126 print('<<< connecting to HCI...') 127 async with await open_transport_or_link(sys.argv[2]) as hci_transport: 128 print('<<< connected') 129 130 # Create a device 131 device = Device.from_config_file_with_hci( 132 sys.argv[1], hci_transport.source, hci_transport.sink 133 ) 134 device.classic_enabled = True 135 136 # Create a TCP server 137 tcp_server = TcpServer(tcp_port) 138 139 # Create and register an RFComm server 140 rfcomm_server = Server(device) 141 142 # Listen for incoming DLC connections 143 channel_number = rfcomm_server.listen( 144 lambda session: on_rfcomm_session(session, tcp_server) 145 ) 146 print(f'### Listening for RFComm connections on channel {channel_number}') 147 148 # Setup the SDP to advertise this channel 149 device.sdp_service_records = sdp_records(channel_number, uuid) 150 151 # Start the controller 152 await device.power_on() 153 154 # Start being discoverable and connectable 155 await device.set_discoverable(True) 156 await device.set_connectable(True) 157 158 await hci_transport.source.wait_for_termination() 159 160 161# ----------------------------------------------------------------------------- 162logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper()) 163asyncio.run(main()) 164