diff --git a/micropython/bluetooth/aioble/aioble/device.py b/micropython/bluetooth/aioble/aioble/device.py index 93819bc1e..feece62ea 100644 --- a/micropython/bluetooth/aioble/aioble/device.py +++ b/micropython/bluetooth/aioble/aioble/device.py @@ -273,7 +273,7 @@ def timeout(self, timeout_ms): async def exchange_mtu(self, mtu=None, timeout_ms=1000): if not self.is_connected(): - raise ValueError("Not connected") + raise DeviceDisconnectedError if mtu: ble.config(mtu=mtu) diff --git a/micropython/bluetooth/aioble/aioble/l2cap.py b/micropython/bluetooth/aioble/aioble/l2cap.py index 397b7ceb6..02cb74c35 100644 --- a/micropython/bluetooth/aioble/aioble/l2cap.py +++ b/micropython/bluetooth/aioble/aioble/l2cap.py @@ -4,9 +4,10 @@ from micropython import const import asyncio +import errno from .core import ble, log_error, register_irq_handler -from .device import DeviceConnection +from .device import DeviceConnection, DeviceDisconnectedError _IRQ_L2CAP_ACCEPT = const(22) @@ -63,7 +64,7 @@ def _l2cap_shutdown(): # The channel was disconnected during a send/recvinto/flush. -class L2CAPDisconnectedError(Exception): +class L2CAPDisconnectedError(DeviceDisconnectedError): pass @@ -75,7 +76,7 @@ class L2CAPConnectionError(Exception): class L2CAPChannel: def __init__(self, connection): if not connection.is_connected(): - raise ValueError("Not connected") + raise L2CAPDisconnectedError if connection._l2cap_channel: raise ValueError("Already has channel") @@ -118,10 +119,17 @@ async def recvinto(self, buf, timeout_ms=None): self._assert_connected() # Extract up to len(buf) bytes from the channel buffer. - n = ble.l2cap_recvinto(self._connection._conn_handle, self._cid, buf) + try: + n = ble.l2cap_recvinto(self._connection._conn_handle, self._cid, buf) - # Check if there's still remaining data in the channel buffers. - self._data_ready = ble.l2cap_recvinto(self._connection._conn_handle, self._cid, None) > 0 + # Check if there's still remaining data in the channel buffers. + self._data_ready = ( + ble.l2cap_recvinto(self._connection._conn_handle, self._cid, None) > 0 + ) + except OSError as e: + if e.errno == errno.EINVAL: + raise L2CAPDisconnectedError() + raise return n @@ -141,11 +149,16 @@ async def send(self, buf, timeout_ms=None, chunk_size=None): await self.flush(timeout_ms) # l2cap_send returns True if you can send immediately. self._assert_connected() - self._stalled = not ble.l2cap_send( - self._connection._conn_handle, - self._cid, - mv[offset : offset + chunk_size], - ) + try: + self._stalled = not ble.l2cap_send( + self._connection._conn_handle, + self._cid, + mv[offset : offset + chunk_size], + ) + except OSError as e: + if e.errno == errno.EINVAL: + raise L2CAPDisconnectedError() + raise offset += chunk_size async def flush(self, timeout_ms=None): @@ -161,7 +174,14 @@ async def disconnect(self, timeout_ms=1000): return # Wait for the cid to be cleared by the disconnect IRQ. - ble.l2cap_disconnect(self._connection._conn_handle, self._cid) + try: + ble.l2cap_disconnect(self._connection._conn_handle, self._cid) + except OSError as e: + if e.errno == errno.EINVAL: + # Channel already closed by peer — treat as disconnected. + self._cid = None + return + raise await self.disconnected(timeout_ms) async def disconnected(self, timeout_ms=1000): diff --git a/micropython/bluetooth/aioble/aioble/server.py b/micropython/bluetooth/aioble/aioble/server.py index 5d5d7399b..392381923 100644 --- a/micropython/bluetooth/aioble/aioble/server.py +++ b/micropython/bluetooth/aioble/aioble/server.py @@ -15,7 +15,7 @@ register_irq_handler, GattError, ) -from .device import DeviceConnection, DeviceTimeout +from .device import DeviceConnection, DeviceDisconnectedError, DeviceTimeout _registered_characteristics = {} @@ -263,7 +263,7 @@ async def indicate(self, connection, data=None, timeout_ms=1000): if self._indicate_connection is not None: raise ValueError("In progress") if not connection.is_connected(): - raise ValueError("Not connected") + raise DeviceDisconnectedError self._indicate_connection = connection self._indicate_status = None