fix(ble): Resolve connection role and startup errors

This commit addresses two critical issues that prevented the BLE
interface from functioning correctly after the driver abstraction
refactor.

1.  **Fix `exec()` Startup Error:**
    The interface failed to load via `rnsd` due to a `KeyError: '__name__'`
    caused by using relative imports (`from . import ...`). The `exec()`
    environment used by Reticulum does not preserve package context,
    breaking these imports. This is fixed by reverting to absolute
    imports (`from bluetooth_driver import ...`) which work correctly
    with the existing `sys.path` manipulation logic.

2.  **Fix Connection Role Logic:**
    Connections were failing because the interface would always attempt
    to read the peer's identity, even when acting as the peripheral.
    This caused a `Can only read characteristics in central mode` error.

    The fix introduces role-aware logic into the connection callback:
    - A `get_peer_role()` method was added to the driver interface.
    - `BLEInterface` now checks the role on connection.
    - If central, it reads the identity characteristic.
    - If peripheral, it waits for the identity handshake packet,
      preventing the invalid operation.
This commit is contained in:
torlando-tech 2025-11-03 23:43:30 -05:00
commit f3cafedb60
3 changed files with 53 additions and 22 deletions

View file

@ -646,37 +646,48 @@ class BLEInterface(Interface):
Called when driver has established a connection. We read the identity
characteristic and prepare to receive data.
"""
RNS.log(f"{self} connected to {address}, reading identity...", RNS.LOG_INFO)
# Check connection role to determine identity exchange method
role = self.driver.get_peer_role(address)
# Read identity characteristic
try:
identity_bytes = self.driver.read_characteristic(
address,
BLEInterface.CHARACTERISTIC_IDENTITY_UUID
)
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 identity_bytes and len(identity_bytes) == 16:
peer_identity = bytes(identity_bytes)
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)
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)
# Record successful connection
self._record_connection_success(address)
else:
RNS.log(f"{self} invalid identity from {address}, disconnecting", RNS.LOG_WARNING)
except Exception as e:
RNS.log(f"{self} failed to read identity from {address}: {e}", RNS.LOG_ERROR)
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)
elif role == "peripheral":
# We are the peripheral, we must wait for the central to send its identity
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
else:
RNS.log(f"{self} connected to {address}, but role is unknown. Disconnecting.", RNS.LOG_WARNING)
self.driver.disconnect(address)
self._record_connection_failure(address)
def _mtu_negotiated_callback(self, address: str, mtu: int):
"""

View file

@ -181,6 +181,19 @@ class BLEDriverInterface(ABC):
"""
pass
@abstractmethod
def get_peer_role(self, address: str) -> Optional[str]:
"""
Returns the connection role for a connected peer.
Args:
address: The MAC address of the peer.
Returns:
A string ('central' or 'peripheral') or None if not connected.
"""
pass
@abstractmethod
def set_service_discovery_delay(self, seconds: float):
"""

View file

@ -1079,6 +1079,13 @@ class LinuxBluetoothDriver(BLEDriverInterface):
"""Return local Bluetooth adapter MAC address."""
return self.local_address or "00:00:00:00:00:00"
def get_peer_role(self, address: str) -> Optional[str]:
"""Return the connection role ('central' or 'peripheral') for a peer."""
with self._peers_lock:
if address in self._peers:
return self._peers[address].connection_type
return None
def set_service_discovery_delay(self, seconds: float):
"""Set delay between connection and service discovery."""
self.service_discovery_delay = seconds