You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

108 lines
3.4 KiB

"""
# Select a BLE radio on the PC for for use by bluetoothctl (apparently it can only use one at a time).
sudo btmgmt info
bluetoothctl select 11:22:33:44:55:66
# Readies the device for pairing.
bluetoothctl connect 77:88:99:AA:BB:CC
# Actually pairs. Will prompt for pin.
bluetoothctl pair 77:88:99:AA:BB:CC
# Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again.
bluetoothctl trust 77:88:99:AA:BB:CC
# Check whether paired and connected.
bluetoothctl devices
bluetoothctl info 77:88:99:AA:BB:CC
# Disconnect bluez from the device. Sometimes bleak needs this to own the connection.
bluetoothctl disconnect 77:88:99:AA:BB:CC
"""
import asyncio
from bleak import BleakClient
import logging
logger = logging.getLogger(__name__)
# Meshtastic BLE UUIDs
TO_RADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROM_RADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
FROM_NUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
class BLETransport:
def __init__(self, address, adapter=None):
self.address = address
self.adapter = adapter
self.client = None
self._in_q = asyncio.Queue()
self._drain_lock = asyncio.Lock()
self._error = None
async def _drain(self):
if self.client is None:
return
async with self._drain_lock:
try:
while True:
data = await self.client.read_gatt_char(FROM_RADIO_UUID)
if not data:
break
await self._in_q.put(data)
except Exception as e:
self._error = e
logger.error("ble drain error: %s", e)
try:
self._in_q.put_nowait(None)
except Exception:
pass
async def start(self):
# Bleak expects the address as the first positional arg
# Meshtastic devices typically use random address type on BLE.
# Reset state to avoid stale sentinels/data from prior sessions
self._in_q = asyncio.Queue()
self._error = None
client = BleakClient(self.address, adapter=self.adapter, address_type="random")
await client.connect()
self.client = client
logger.debug("BLETransport.start: connected to %s (adapter=%s)", self.address, self.adapter)
def from_num_handler(_sender, _data):
asyncio.create_task(self._drain())
await client.start_notify(FROM_NUM_UUID, from_num_handler)
async def send(self, payload):
if self.client is None:
return
try:
await self.client.write_gatt_char(TO_RADIO_UUID, payload, response=True)
asyncio.create_task(self._drain())
except Exception as e:
self._error = e
logger.error("ble send error: %s", e)
try:
self._in_q.put_nowait(None)
except Exception:
pass
async def recv(self):
item = await self._in_q.get()
if item is None:
raise ConnectionError(self._error or "ble transport error")
return item
async def close(self):
if self.client is None:
return
try:
try:
await self.client.stop_notify(FROM_NUM_UUID)
except Exception:
pass
await self.client.disconnect()
finally:
self.client = None