fix(ble): Pass peer identity via callback to eliminate redundant read
## Problem The central device was timing out when trying to read the identity characteristic from peripheral devices, causing connection failures: ``` ERROR: Error reading characteristic ...28e6 from B8:27:EB:10:28:CD: TimeoutError ``` Root cause: The driver already reads the identity during connection setup (line 806 in _connect_to_peer), but then BLEInterface tried to read it AGAIN in _device_connected_callback. The second read consistently timed out, likely due to BlueZ/D-Bus caching issues or characteristic state. ## Solution Changed the `on_device_connected` callback signature to pass the peer identity directly, following the established pattern of other callbacks like `on_data_received(address, data)` and `on_mtu_negotiated(address, mtu)`. ### Changes 1. **Driver Interface** (bluetooth_driver.py) - Updated callback: `on_device_connected(str, Optional[bytes])` - Identity is None for peripheral connections (arrives via handshake) 2. **PeerConnection** (linux_bluetooth_driver.py) - Added `peer_identity: Optional[bytes]` field - Store identity read during connection setup 3. **Connection Flow** (linux_bluetooth_driver.py) - Central: Pass identity to callback after reading it - Peripheral: Pass None (identity comes later via handshake) 4. **BLEInterface** (BLEInterface.py) - Updated callback signature to accept peer_identity parameter - Removed buggy `read_characteristic()` call - Use passed identity directly for central connections - Added typing.Optional import ## Benefits - ✅ Eliminates redundant GATT read operation - ✅ Fixes timeout bug for central connections - ✅ More efficient: reuses identity already read by driver - ✅ Cleaner architecture: follows callback pattern consistency - ✅ Explicit about identity availability by connection role ## Testing Tested on Raspberry Pi Zero W devices with BlueZ 5.82: - Central connections now receive identity immediately - Peripheral connections correctly wait for handshake - No more timeout errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6ab71641c8
commit
424af588f4
3 changed files with 31 additions and 39 deletions
|
|
@ -41,6 +41,7 @@ import threading
|
|||
import time
|
||||
import asyncio
|
||||
from collections import deque
|
||||
from typing import Optional
|
||||
|
||||
# Add interface directory to path for importing other BLE modules
|
||||
# This is needed when loaded as external interface
|
||||
|
|
@ -647,54 +648,43 @@ class BLEInterface(Interface):
|
|||
except Exception as e:
|
||||
RNS.log(f"{self} failed to initiate connection to {device.name}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
def _device_connected_callback(self, address: str):
|
||||
def _device_connected_callback(self, address: str, peer_identity: Optional[bytes]):
|
||||
"""
|
||||
Driver callback: Handle successful device connection.
|
||||
|
||||
Called when driver has established a connection. We read the identity
|
||||
characteristic and prepare to receive data.
|
||||
Called when driver has established a connection. For central connections,
|
||||
the peer_identity is provided. For peripheral connections, identity will
|
||||
arrive later via handshake.
|
||||
|
||||
Args:
|
||||
address: MAC address of connected peer
|
||||
peer_identity: 16-byte identity hash (None for peripheral connections)
|
||||
"""
|
||||
# Check connection role to determine identity exchange method
|
||||
role = self.driver.get_peer_role(address)
|
||||
|
||||
if role == "central":
|
||||
# We are the central, we must read the peer's identity
|
||||
RNS.log(f"{self} connected to {address} as CENTRAL, reading identity...", RNS.LOG_INFO)
|
||||
try:
|
||||
identity_bytes = self.driver.read_characteristic(
|
||||
address,
|
||||
BLEInterface.CHARACTERISTIC_IDENTITY_UUID
|
||||
)
|
||||
if peer_identity is not None:
|
||||
# Central mode: identity provided by driver
|
||||
if len(peer_identity) == 16:
|
||||
identity_hash = self._compute_identity_hash(peer_identity)
|
||||
|
||||
if identity_bytes and len(identity_bytes) == 16:
|
||||
peer_identity = bytes(identity_bytes)
|
||||
identity_hash = self._compute_identity_hash(peer_identity)
|
||||
# Store identity mappings
|
||||
self.address_to_identity[address] = peer_identity
|
||||
self.identity_to_address[identity_hash] = address
|
||||
|
||||
# Store identity mappings
|
||||
self.address_to_identity[address] = peer_identity
|
||||
self.identity_to_address[identity_hash] = address
|
||||
|
||||
RNS.log(f"{self} received peer identity from {address}: {identity_hash}", RNS.LOG_INFO)
|
||||
self._record_connection_success(address)
|
||||
else:
|
||||
RNS.log(f"{self} invalid identity from {address}, disconnecting", RNS.LOG_WARNING)
|
||||
self.driver.disconnect(address)
|
||||
self._record_connection_failure(address)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"{self} failed to read identity from {address}: {e}", RNS.LOG_ERROR)
|
||||
RNS.log(f"{self} connected to {address} as CENTRAL, received identity: {identity_hash}", RNS.LOG_INFO)
|
||||
self._record_connection_success(address)
|
||||
else:
|
||||
RNS.log(f"{self} invalid identity from {address} (wrong length), disconnecting", RNS.LOG_WARNING)
|
||||
self.driver.disconnect(address)
|
||||
self._record_connection_failure(address)
|
||||
|
||||
elif role == "peripheral":
|
||||
# We are the peripheral, we must wait for the central to send its identity
|
||||
# Peripheral mode: identity will arrive via handshake
|
||||
RNS.log(f"{self} connected to {address} as PERIPHERAL, waiting for identity handshake...", RNS.LOG_INFO)
|
||||
# The identity will be received in `handle_peripheral_data` or `_data_received_callback`
|
||||
# No action is needed here.
|
||||
pass
|
||||
# The identity will be received in `_data_received_callback`
|
||||
|
||||
else:
|
||||
RNS.log(f"{self} connected to {address}, but role is unknown. Disconnecting.", RNS.LOG_WARNING)
|
||||
RNS.log(f"{self} connected to {address}, but identity not provided and role is {role}. Disconnecting.", RNS.LOG_WARNING)
|
||||
self.driver.disconnect(address)
|
||||
|
||||
def _mtu_negotiated_callback(self, address: str, mtu: int):
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class BLEDriverInterface(ABC):
|
|||
# implement and assign these callbacks to receive events from the driver.
|
||||
|
||||
on_device_discovered: Optional[Callable[[BLEDevice], None]] = None
|
||||
on_device_connected: Optional[Callable[[str], None]] = None # address (MTU reported separately)
|
||||
on_device_connected: Optional[Callable[[str, Optional[bytes]], None]] = None # address, peer_identity (None for peripheral role)
|
||||
on_device_disconnected: Optional[Callable[[str], None]] = None # address
|
||||
on_data_received: Optional[Callable[[str, bytes], None]] = None # address, data
|
||||
on_mtu_negotiated: Optional[Callable[[str, int], None]] = None # address, mtu
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ class PeerConnection:
|
|||
mtu: int = 23 # Negotiated MTU
|
||||
connection_type: str = "unknown" # "central" or "peripheral"
|
||||
connected_at: float = 0.0
|
||||
peer_identity: Optional[bytes] = None # 16-byte identity hash
|
||||
|
||||
|
||||
class LinuxBluetoothDriver(BLEDriverInterface):
|
||||
|
|
@ -822,7 +823,8 @@ class LinuxBluetoothDriver(BLEDriverInterface):
|
|||
client=client,
|
||||
mtu=mtu,
|
||||
connection_type="central",
|
||||
connected_at=time.time()
|
||||
connected_at=time.time(),
|
||||
peer_identity=peer_identity
|
||||
)
|
||||
|
||||
with self._peers_lock:
|
||||
|
|
@ -846,10 +848,10 @@ class LinuxBluetoothDriver(BLEDriverInterface):
|
|||
except Exception as e:
|
||||
self._log(f"Failed to send identity handshake: {e}", "WARNING")
|
||||
|
||||
# Notify callback
|
||||
# Notify callback with peer identity
|
||||
if self.on_device_connected:
|
||||
try:
|
||||
self.on_device_connected(address)
|
||||
self.on_device_connected(address, peer_identity)
|
||||
except Exception as e:
|
||||
self._log(f"Error in device connected callback: {e}", "ERROR")
|
||||
|
||||
|
|
@ -1511,10 +1513,10 @@ class BluezeroGATTServer:
|
|||
|
||||
self._log(f"Central connected: {central_address} (MTU: {effective_mtu})")
|
||||
|
||||
# Notify callback
|
||||
# Notify callback (identity not available yet for peripheral connections)
|
||||
if self.driver.on_device_connected:
|
||||
try:
|
||||
self.driver.on_device_connected(central_address)
|
||||
self.driver.on_device_connected(central_address, None)
|
||||
except Exception as e:
|
||||
self._log(f"Error in device connected callback: {e}", "ERROR")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue