diff --git a/ble_serial/bluetooth/ble_interface.py b/ble_serial/bluetooth/ble_client.py similarity index 100% rename from ble_serial/bluetooth/ble_interface.py rename to ble_serial/bluetooth/ble_client.py diff --git a/ble_serial/bluetooth/ble_server.py b/ble_serial/bluetooth/ble_server.py new file mode 100644 index 0000000..8722e05 --- /dev/null +++ b/ble_serial/bluetooth/ble_server.py @@ -0,0 +1,111 @@ +from bless import BlessServer, BlessGATTCharacteristic +from bless import GATTAttributePermissions, GATTCharacteristicProperties + +import logging, asyncio +from typing import Optional + +class BLE_server(): + def __init__(self): + self._send_queue = asyncio.Queue() + self.data_read_done = asyncio.Event() + + self.server = BlessServer(name='BLE Serial Server') # loop=asyncio.get_event_loop()) + self.server.read_request_func = self.handle_incoming_read + self.server.write_request_func = self.handle_incoming_write + + async def start(self, addr_str: str, addr_type: str, adapter: str, timeout: float): + logging.info(f'Trying to start with {addr_str}') + #TODO: obtain adapter address + success = await self.server.start(timeout=timeout) + logging.info(f'Server startup {"successful" if success else "failed!"}') + + + async def setup_chars(self, write_uuid: str, read_uuid: str, mode: str): + self.read_enabled = 'r' in mode + self.write_enabled = 'w' in mode + + service_uuid = "0000ffe0-0000-1000-8000-00805f9b34fb" + await self.server.add_new_service(service_uuid) + self.service = self.server.get_service(service_uuid) + logging.debug(self.service) + + # TODO: setup depending on mode + # if self.write_enabled: + # self.write_char = self.find_char(write_uuid, ['write', 'write-without-response']) + # else: + # logging.info('Writing disabled, skipping write UUID detection') + + # if self.read_enabled: + # self.read_char = self.find_char(read_uuid, ['notify', 'indicate']) + # await self.dev.start_notify(self.read_char, self.handle_notify) + # else: + # logging.info('Reading disabled, skipping read UUID detection') + + write_uuid = "0000ffe1-0000-1000-8000-00805f9b34fb" + char_flags = GATTCharacteristicProperties.write | GATTCharacteristicProperties.write_without_response + permissions = GATTAttributePermissions.readable | GATTAttributePermissions.writeable + await self.server.add_new_characteristic(service_uuid, write_uuid, + char_flags, None, permissions) + + self.write_char = self.server.get_characteristic(write_uuid) + logging.debug(self.write_char) + + read_uuid = "0000ffe2-0000-1000-8000-00805f9b34fb" + char_flags = GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify + permissions = GATTAttributePermissions.readable | GATTAttributePermissions.writeable + await self.server.add_new_characteristic(service_uuid, read_uuid, + char_flags, None, permissions) + + self.read_char = self.server.get_characteristic(read_uuid) + logging.debug(self.read_char) + + self.data_read_done.set() + + def handle_incoming_read(self, char: BlessGATTCharacteristic) -> bytearray: + logging.debug('Client read data') + if self.read_char != char: + logging.warning('Read request received on wrong characteristic') + return None + self.data_read_done.set() + return self.read_char.value + + def queue_send(self, data: bytes): + self._send_queue.put_nowait(data) + + async def send_loop(self): + assert hasattr(self, '_cb'), 'Callback must be set before receive loop!' + while True: + data = await self._send_queue.get() + if data == None: + break # Let future end on shutdown + if not self.read_enabled: + logging.warning(f'Ignoring unexpected read data: {data}') + continue + logging.debug(f'Offering read {data}') + # Wait for current data to get read, then overwrite + # await self.data_read_done.wait() + self.read_char.value = data + # Mark as ready to read + self.data_read_done.clear() + self.server.update_value(self.service.uuid, self.read_char.uuid) + await server.stop() + + def stop_loop(self): + logging.info('Stopping Bluetooth event loop') + self._send_queue.put_nowait(None) + + async def disconnect(self): + if hasattr(self, 'server'): + await self.server.stop() + logging.info('Bluetooth server stopped') + + def set_receiver(self, callback): + self._cb = callback + logging.info('Receiver set up') + + def handle_incoming_write(self, char: BlessGATTCharacteristic, data: bytes): + logging.debug(f'Received write from {char}: {data}') + if not self.write_enabled: + logging.warning(f'Got unexpected write data, dropping: {data}') + return + self._cb(data) diff --git a/ble_serial/log/console_log.py b/ble_serial/log/console_log.py index 07706b5..98ce13f 100644 --- a/ble_serial/log/console_log.py +++ b/ble_serial/log/console_log.py @@ -1,7 +1,7 @@ import logging, coloredlogs def setup_logger(verbosity: int): - bleak_logger = logging.getLogger('bleak') + bleak_logger = logging.getLogger('bless') bleak_logger.level = logging.DEBUG if verbosity > 1 else logging.INFO level_colors = { diff --git a/ble_serial/main.py b/ble_serial/main.py index 86dd9b8..788a7ae 100644 --- a/ble_serial/main.py +++ b/ble_serial/main.py @@ -2,7 +2,7 @@ from bleak.exc import BleakError from ble_serial import platform_uart as UART from ble_serial.ports.tcp_socket import TCP_Socket -from ble_serial.bluetooth.ble_interface import BLE_interface +from ble_serial.bluetooth.ble_server import BLE_server from ble_serial.log.fs_log import FS_log, Direction from ble_serial.log.console_log import setup_logger from ble_serial import cli @@ -30,7 +30,7 @@ async def _run(self): else: self.uart = UART(args.port, loop, args.mtu) - self.bt = BLE_interface(args.adapter, args.service_uuid) + self.bt = BLE_server() if args.filename: self.log = FS_log(args.filename, args.binlog) @@ -41,8 +41,8 @@ async def _run(self): self.uart.set_receiver(self.bt.queue_send) self.uart.start() - await self.bt.connect(args.device, args.addr_type, args.timeout) await self.bt.setup_chars(args.write_uuid, args.read_uuid, args.mode) + await self.bt.start(args.device, args.addr_type, args.adapter, args.timeout) logging.info('Running main loop!') main_tasks = { diff --git a/setup.py b/setup.py index 00c255a..84a3d53 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,9 @@ ], python_requires='>=3.8', install_requires=REQUIRES, + extras_require={ + "server": 'bless >= 0.2.4', + }, entry_points={ 'console_scripts': [ 'ble-scan=ble_serial.scan.main:launch',