Major architectural refactoring to separate high-level Reticulum protocol
logic from platform-specific Bluetooth operations. This enables code sharing
between pure Python and Android (Columba) implementations, improves
testability, and creates a clean boundary for future platform support.
ARCHITECTURE CHANGES:
1. **Driver Abstraction Layer**
- Created BLEDriverInterface (bluetooth_driver.py) defining the contract
for all platform-specific BLE drivers
- Abstraction includes 18 methods + 6 callbacks for complete BLE lifecycle
- Enhanced BLEDevice dataclass with service_uuids and manufacturer_data
- Added on_mtu_negotiated callback for delayed MTU reporting
- Added on_error callback for consistent platform error reporting
2. **Linux Driver Implementation**
- Created LinuxBluetoothDriver (linux_bluetooth_driver.py, 1534 lines)
- Moved ALL bleak/bluezero/D-Bus code from BLEInterface
- Preserves 5 critical platform workarounds:
* BlueZ ServicesResolved race condition patch
* D-Bus LE-only connection (ConnectDevice)
* BLE Agent registration for Just Works pairing
* MTU negotiation with 3-method fallback
* Service discovery delay for bluezero timing
- Role-aware send() automatically chooses GATT write vs notification
- Dedicated asyncio event loop management in separate thread
- Configuration via constructor (no Reticulum dependencies)
3. **Refactored BLEInterface**
- Removed 801 lines (32.3% reduction: 2479 → 1678 lines)
- Removed all platform-specific imports (bleak, bluezero, dbus_fast)
- Removed 9 async methods (moved to driver)
- Driver dependency injection via constructor
- Implemented 6 driver callbacks for event handling
- PRESERVED high-level logic:
* Peer scoring algorithm (RSSI + history + recency)
* Connection blacklist with exponential backoff
* MAC-based connection direction (prevents dual connections)
* Fragmentation/reassembly orchestration (identity-based keying)
* Interface spawning per peer
4. **Simplified BLEPeerInterface**
- Removed connection_type, client, mtu parameters
- Deleted _send_via_central() and _send_via_peripheral() methods
- Single send path via driver.send() (driver handles role routing)
- 77 lines removed from peer interface class
5. **Mock Driver for Testing**
- Created MockBLEDriver (tests/mock_ble_driver.py)
- Complete BLEDriverInterface implementation without hardware
- Bidirectional communication via link_drivers()
- Enables unit testing of BLEInterface logic (fragmentation, reassembly,
peer lifecycle, blacklist management)
CRITICAL FIXES:
1. **Restored Periodic Cleanup Task** (CRITICAL: prevents memory leaks)
- Converted from async (driver-owned loop) to threading.Timer
- Runs every 30 seconds to clean stale reassembly buffers
- Essential for long-running instances (Pi Zero with 512MB RAM)
- Properly cancelled in detach() for clean shutdown
2. **Fixed Naming Consistency**
- Renamed processOutgoing → process_outgoing (snake_case)
FILES MODIFIED:
- src/RNS/Interfaces/BLEInterface.py (refactored, -801 lines)
FILES ADDED:
- bluetooth_driver.py (driver abstraction interface)
- linux_bluetooth_driver.py (Linux/BlueZ implementation, 1534 lines)
- tests/mock_ble_driver.py (mock driver for unit tests)
- REFACTORING_GUIDE.md (comprehensive refactoring documentation)
- BLE_PROTOCOL_v2.2.md (protocol specification)
- tests/test_refactor_suite.py (initial test suite)
BENEFITS:
1. **Testability** - Mock driver enables hardware-free unit testing
2. **Portability** - Easy to create Android/Windows/macOS drivers
3. **Maintainability** - Platform quirks isolated in single driver file
4. **Code Sharing** - High-level logic shared across all platforms
5. **Clean Architecture** - Clear separation of concerns
TESTING REQUIRED:
- Tier 1 (Unit): Test with MockBLEDriver (fragmentation, reassembly, lifecycle)
- Tier 2 (Integration): Test on Raspberry Pi hardware (scanning, connecting,
dual mode, MTU negotiation, identity exchange)
- Tier 3 (Regression): Full Reticulum stack (announces, LXMF, multi-hop)
- Tier 4 (Edge Cases): MAC rotation, identity handshake, reconnection,
reassembly timeout, discovery cache pruning
BACKWARD COMPATIBILITY:
- Configuration: Fully backward compatible (same config parameters)
- Protocol: No changes to BLE wire protocol (v2.2)
- Interface API: Unchanged for Reticulum Transport integration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
|
|
import pytest
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
|
|
# Add the project root to the Python path
|
|
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
sys.path.insert(0, project_root)
|
|
|
|
from src.RNS.Interfaces.BLEInterface import BLEInterface
|
|
|
|
class MockReticulum:
|
|
def __init__(self):
|
|
self.transport_enabled = False
|
|
self.is_connected_to_shared_instance = False
|
|
|
|
def register_interface(self, interface):
|
|
pass
|
|
|
|
class MockOwner:
|
|
def __init__(self):
|
|
self.reticulum = MockReticulum()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_two_device_communication():
|
|
"""
|
|
Tests a basic two-device communication scenario where one device acts as a
|
|
peripheral and the other as a central.
|
|
"""
|
|
# Create mock owner and configuration for the peripheral device
|
|
peripheral_owner = MockOwner()
|
|
peripheral_config = {
|
|
'name': 'PeripheralInterface',
|
|
'enable_central': False,
|
|
'enable_peripheral': True,
|
|
'device_name': 'TestPeripheral',
|
|
}
|
|
|
|
# Create mock owner and configuration for the central device
|
|
central_owner = MockOwner()
|
|
central_config = {
|
|
'name': 'CentralInterface',
|
|
'enable_central': True,
|
|
'enable_peripheral': False,
|
|
}
|
|
|
|
# Create the peripheral and central interfaces
|
|
peripheral_interface = BLEInterface(peripheral_owner, peripheral_config)
|
|
central_interface = BLEInterface(central_owner, central_config)
|
|
|
|
# Allow some time for the interfaces to start and for discovery to happen
|
|
await asyncio.sleep(10)
|
|
|
|
# Check that the central has discovered and connected to the peripheral
|
|
assert len(central_interface.peers) > 0, "Central did not connect to any peers"
|
|
|
|
# TODO: Add assertions to verify data exchange
|
|
|
|
# Clean up
|
|
await peripheral_interface.stop()
|
|
await central_interface.stop()
|