BLE UART Service - Host#

Verify Bluetooth is available and working. This has been tested on a Raspberry Pi 4. If you get an error, your host may not have Bluetooth or it is not configured.

!bluetoothctl scan on
[CHG] Controller 00:1A:7D:DA:71:03 UUIDs: 00001801-0000-1000-8000-00805f9b34fb
[CHG] Controller 00:1A:7D:DA:71:03 UUIDs: 00001800-0000-1000-8000-00805f9b34fb
[CHG] Controller 00:1A:7D:DA:71:03 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Controller 00:1A:7D:DA:71:03 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
AdvertisementMonitor path registered

Python bluetooth scanner:

import asyncio
from bleak import BleakScanner

async def main():
    devices = await BleakScanner.discover()
    for d in devices:
        print(d)

await main()
Hide code cell output
58:CB:B8:62:D0:A8: 58-CB-B8-62-D0-A8
55:0B:A1:83:26:06: 55-0B-A1-83-26-06
A4:C1:38:A7:84:1C: xiaoxiang BMS
5F:32:62:FB:7C:A8: 5F-32-62-FB-7C-A8
7B:7F:9D:27:52:F9: 7B-7F-9D-27-52-F9
CF:0C:19:82:5E:00: BLEsmart_000001100F0C19825E00
06:C3:85:00:0F:03: 06-C3-85-00-0F-03
61:7E:F9:93:21:A5: 61-7E-F9-93-21-A5
4E:9B:4E:06:86:18: 4E-9B-4E-06-86-18
44:52:69:94:29:88: 44-52-69-94-29-88
E7:B5:0C:39:8B:F2: E7-B5-0C-39-8B-F2
4A:C7:BA:B4:8B:AC: 4A-C7-BA-B4-8B-AC
D3:68:59:DF:03:82: D3-68-59-DF-03-82
F5:78:D2:09:01:DE: BMV
E6:44:73:09:EE:AF: E6-44-73-09-EE-AF
75:72:B8:35:12:17: 75-72-B8-35-12-17

UART Service - CPython#

import asyncio
import sys

from bleak import BleakScanner, BleakClient
from bleak.backends.scanner import AdvertisementData
from bleak.backends.device import BLEDevice

# https://github.com/hbldh/bleak/blob/develop/examples/uart_service.py

class BLE_UART:
    
    UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
    UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
    UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

    # All BLE devices have MTU of at least 23. Subtracting 3 bytes overhead, we can
    # safely send 20 bytes at a time to any device supporting this service.
    UART_SAFE_SIZE = 20
    
    def __init__(self, peripheral_name='mpy-uart'):
        self._peripheral_name = peripheral_name
        self._rx_queue = asyncio.Queue()
        
    async def read(self):
        msg = await self._rx_queue.get()
        return msg
    
    async def write(self, msg):
        if isinstance(msg, str):
            msg = msg.encode()
        await self._client.write_gatt_char(self.UART_RX_CHAR_UUID, msg)
        
    async def connect(self):
        self._discovery_queue = asyncio.Queue()
        device = None
        print(f"scanning for {self._peripheral_name}")
        async with BleakScanner(detection_callback=self._find_uart_device):
            device: BLEDevice = await self._discovery_queue.get()
        print(f"connecting to {self._peripheral_name} ...", end="")
        client = self._client = BleakClient(device, disconnected_callback=self._handle_disconnect)
        await client.connect()
        await client.start_notify(self.UART_TX_CHAR_UUID, self._rx_handler)    
        print(f" connected")
        
    async def disconnect(self):
        await self._client.disconnect()
    
    async def __aenter__(self):
        return self
    
    async def __aexit__(self, *args):
        await self.disconnect()
        
    def _rx_handler(self, _: int, data: bytearray):
        self._rx_queue.put_nowait(data)
    
    def _find_uart_device(self, device: BLEDevice, adv: AdvertisementData):
        # called whenever a device is detected during discovery
        # ignore all but target device
        if device.name == self._peripheral_name:
            self._discovery_queue.put_nowait(device)
        
    def _handle_disconnect(self, _: BleakClient):
        self._rx_queue.put_nowait(None)

Receive messages from mpy-uart:

async with BLE_UART() as uart:
    await uart.connect()

    for i in range(3):
        msg = await uart.read()
        msg = msg.decode()
        print("received", msg)
        await uart.write(f"echo {msg}")
scanning for mpy-uart
connecting to mpy-uart ... connected
received hello # 1 from esp32
received hello # 2 from esp32
received hello # 3 from esp32