mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-07-28 10:58:13 +00:00
Merge pull request #77 from mwolter805/fix/transport-symmetry
fix: symmetric disconnect signaling, serial timeout, BLE callback, oversize-frame recovery
This commit is contained in:
@@ -124,9 +124,12 @@ class BLEConnection:
|
||||
await self.client.pair()
|
||||
logger.info("BLE pairing successful")
|
||||
except Exception as e:
|
||||
logger.warning(f"BLE pairing failed: {e}")
|
||||
# Don't fail the connection if pairing fails, as the device
|
||||
# might already be paired or not require pairing
|
||||
logger.error(f"BLE pairing failed: {e}")
|
||||
# A failed pairing leaves the transport in a half-usable
|
||||
# state — re-raise so the caller gets a clean failure
|
||||
# instead of a silently degraded connection.
|
||||
await self.client.disconnect()
|
||||
raise
|
||||
|
||||
except BleakDeviceNotFoundError:
|
||||
return None
|
||||
@@ -162,6 +165,17 @@ class BLEConnection:
|
||||
self.client = self._user_provided_client
|
||||
self.device = self._user_provided_device
|
||||
|
||||
# Re-register disconnect callback on the reset client so subsequent
|
||||
# disconnects after a reconnect cycle are still detected.
|
||||
if self.client is not None and hasattr(self.client, 'set_disconnected_callback'):
|
||||
try:
|
||||
self.client.set_disconnected_callback(self.handle_disconnect)
|
||||
except Exception:
|
||||
# set_disconnected_callback may not be available on all bleak
|
||||
# versions; the next connect() call will re-create the client
|
||||
# with the callback anyway.
|
||||
pass
|
||||
|
||||
if self._disconnect_callback:
|
||||
self._spawn_background(self._disconnect_callback("ble_disconnect"))
|
||||
|
||||
@@ -179,11 +193,19 @@ class BLEConnection:
|
||||
async def send(self, data):
|
||||
if not self.client:
|
||||
logger.error("Client is not connected")
|
||||
if self._disconnect_callback:
|
||||
await self._disconnect_callback("ble_transport_lost")
|
||||
return False
|
||||
if not self.rx_char:
|
||||
logger.error("RX characteristic not found")
|
||||
return False
|
||||
await self.client.write_gatt_char(self.rx_char, bytes(data), response=True)
|
||||
try:
|
||||
await self.client.write_gatt_char(self.rx_char, bytes(data), response=True)
|
||||
except Exception as exc:
|
||||
logger.warning(f"BLE write failed: {exc}")
|
||||
if self._disconnect_callback:
|
||||
await self._disconnect_callback(f"ble_write_failed: {exc}")
|
||||
return False
|
||||
|
||||
async def disconnect(self):
|
||||
"""Disconnect from the BLE device."""
|
||||
|
||||
@@ -60,12 +60,16 @@ class SerialConnection:
|
||||
def resume_writing(self):
|
||||
logger.debug("resume writing")
|
||||
|
||||
async def connect(self):
|
||||
async def connect(self, timeout: float = 10.0):
|
||||
"""
|
||||
Connects to the device
|
||||
Connects to the device.
|
||||
|
||||
Args:
|
||||
timeout: Maximum seconds to wait for connection_made callback.
|
||||
Defaults to 10.0. Raises asyncio.TimeoutError on expiry.
|
||||
"""
|
||||
self._connected_event.clear()
|
||||
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
await serial_asyncio.create_serial_connection(
|
||||
loop,
|
||||
@@ -74,7 +78,7 @@ class SerialConnection:
|
||||
baudrate=self.baudrate,
|
||||
)
|
||||
|
||||
await self._connected_event.wait()
|
||||
await asyncio.wait_for(self._connected_event.wait(), timeout=timeout)
|
||||
logger.info("Serial Connection started")
|
||||
return self.port
|
||||
|
||||
@@ -110,7 +114,7 @@ class SerialConnection:
|
||||
self.frame_expected_size = 0
|
||||
if len(data) > 0: # rerun handle_rx on remaining data
|
||||
self.handle_rx(data)
|
||||
return
|
||||
return # nothing left to process after reset
|
||||
|
||||
upbound = self.frame_expected_size - len(self.inframe)
|
||||
if len(data) < upbound:
|
||||
@@ -133,11 +137,18 @@ class SerialConnection:
|
||||
async def send(self, data):
|
||||
if not self.transport:
|
||||
logger.error("Transport not connected, cannot send data")
|
||||
if self._disconnect_callback:
|
||||
await self._disconnect_callback("serial_transport_lost")
|
||||
return
|
||||
size = len(data)
|
||||
pkt = b"\x3c" + size.to_bytes(2, byteorder="little") + data
|
||||
logger.debug(f"sending pkt : {pkt}")
|
||||
self.transport.write(pkt)
|
||||
try:
|
||||
self.transport.write(pkt)
|
||||
except OSError as exc:
|
||||
logger.warning(f"Serial write failed: {exc}")
|
||||
if self._disconnect_callback:
|
||||
await self._disconnect_callback(f"serial_write_failed: {exc}")
|
||||
|
||||
async def disconnect(self):
|
||||
"""Close the serial connection."""
|
||||
|
||||
@@ -46,7 +46,6 @@ class TCPConnection:
|
||||
|
||||
def data_received(self, data):
|
||||
logger.debug("data received")
|
||||
self.cx._receive_count += 1
|
||||
self.cx.handle_rx(data)
|
||||
|
||||
def error_received(self, exc):
|
||||
@@ -101,7 +100,7 @@ class TCPConnection:
|
||||
self.frame_expected_size = 0
|
||||
if len(data) > 0: # rerun handle_rx on remaining data
|
||||
self.handle_rx(data)
|
||||
return
|
||||
return # nothing left to process after reset
|
||||
|
||||
upbound = self.frame_expected_size - len(self.inframe)
|
||||
if len(data) < upbound :
|
||||
@@ -111,6 +110,10 @@ class TCPConnection:
|
||||
|
||||
self.inframe = self.inframe + data[0:upbound]
|
||||
data = data[upbound:]
|
||||
# Increment per completed MeshCore frame, not per TCP segment (N04).
|
||||
# The threshold heuristic in send() compares _send_count vs
|
||||
# _receive_count — counting per-segment skews it under fragmentation.
|
||||
self._receive_count += 1
|
||||
if self.reader is not None:
|
||||
# feed meshcore reader
|
||||
self._spawn_background(self.reader.handle_rx(self.inframe))
|
||||
@@ -142,7 +145,12 @@ class TCPConnection:
|
||||
size = len(data)
|
||||
pkt = b"\x3c" + size.to_bytes(2, byteorder="little") + data
|
||||
logger.debug(f"sending pkt : {pkt}")
|
||||
self.transport.write(pkt)
|
||||
try:
|
||||
self.transport.write(pkt)
|
||||
except (OSError, ConnectionResetError) as exc:
|
||||
logger.warning(f"TCP write failed: {exc}")
|
||||
if self._disconnect_callback:
|
||||
await self._disconnect_callback(f"tcp_write_failed: {exc}")
|
||||
|
||||
async def disconnect(self):
|
||||
"""Close the TCP connection."""
|
||||
|
||||
Reference in New Issue
Block a user