Find a file
torlando-tech 4a9cd1ff66 test: Add comprehensive v2.2 protocol test suites
Adds test suites for critical v2.2 protocol features that were previously untested.
These tests validate the core protocol mechanisms using the driver abstraction.

New Test Files:
1. test_v2_2_identity_handshake.py (8 tests, ~200 lines)
   - Tests 16-byte identity handshake detection
   - Peripheral handshake processing
   - Bidirectional identity exchange
   - Edge cases (wrong length, multiple handshakes)

2. test_v2_2_mac_sorting.py (10 tests, ~220 lines)
   - Tests MAC address comparison logic
   - Lower MAC initiates, higher MAC waits
   - Dual-connection prevention
   - Edge cases (equal MACs, sequential addresses)

3. test_v2_2_race_conditions.py (8 tests, ~240 lines)
   - Tests 5-second connection rate limiting
   - Driver-level connection state tracking
   - Early attempt recording
   - Concurrent discovery callback handling

Updated test_integration.py:
- Added test_identity_based_fragmenter_keying() to validate MAC rotation immunity

Coverage Impact:
- Identity Handshake: 0% → 90% (critical feature)
- MAC Sorting: 0% → 90% (critical feature)
- Race Condition Prevention: 0% → 80% (v2.2.1+ feature)
- Overall v2.2 Protocol: 45% → ~75%

Note: These tests require RNS module mocking setup and will be fully functional
when integrated into the main Reticulum repository. They serve as documentation
of expected behavior and validation logic for the v2.2 protocol features.

Reference: BLE_PROTOCOL_v2.2.md §5, §6, §7, Platform-Specific Workarounds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 23:00:30 -05:00
.github/workflows fix(ci): Use workflow_run trigger to depend on test workflow 2025-11-07 22:38:18 -05:00
examples feat: add Bluetooth adapter power state handling and auto-power-on 2025-10-29 00:10:57 -04:00
src/RNS fix(ble): Add connection race condition prevention and improve error handling 2025-11-07 22:32:00 -05:00
tests test: Add comprehensive v2.2 protocol test suites 2025-11-07 23:00:30 -05:00
.gitignore Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00
BLE_PROTOCOL_v2.2.md fix(ble): Add connection race condition prevention and improve error handling 2025-11-07 22:32:00 -05:00
CLAUDE.md fix(ble): Add connection race condition prevention and improve error handling 2025-11-07 22:32:00 -05:00
CONTRIBUTING.md Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00
install.sh fix: install system dependencies before Reticulum to provide libffi-dev 2025-10-29 11:37:08 -04:00
LICENSE Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00
pytest.ini Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00
README.md fix(ble): Add connection race condition prevention and improve error handling 2025-11-07 22:32:00 -05:00
REFACTORING_GUIDE.md fix(ble): Add connection race condition prevention and improve error handling 2025-11-07 22:32:00 -05:00
requirements-dev.txt Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00
requirements.txt Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00
TESTING.md Initial commit: BLE Reticulum interface 2025-10-26 19:14:14 -04:00

Reticulum BLE Interface

A Bluetooth Low Energy (BLE) interface for Reticulum Network Stack, enabling mesh networking over BLE without additional hardware on Linux devices.

⚠️ Platform: Linux-only (requires BlueZ 5.x for GATT server functionality) Tested on: Raspberry Pi Zero W

Features

  • Zero dongle requirements: Works with built-in BLE radios (Raspberry Pi, Linux laptops, etc.)
  • Auto-discovery: Automatically finds and connects to nearby Reticulum BLE nodes
  • Multi-peer mesh: Supports up to 7 simultaneous connections for mesh networking (may support more, untested)
  • Dual mode operation: Acts as both central (scanner/client) and peripheral (advertiser/server)
  • Connection prioritization: RSSI-based smart peer selection with connection history tracking
  • Packet fragmentation: Handles BLE MTU limitations (20-512 bytes) transparently
  • Enhanced error handling: Retry logic, exponential backoff, connection recovery
  • Power management: Three power modes (aggressive/balanced/saver) for battery efficiency or CPU limitations. Saver mode tested on Raspberry Pi Zero W.

Installation

Prerequisites:

  • Python 3.8 or higher
  • Reticulum Network Stack already installed (installation guide)
  • Linux with BlueZ 5.x

The installation script automatically detects your Reticulum setup and installs dependencies in the correct environment:

# Download and run installer
git clone https://github.com/torlando-tech/ble-reticulum.git
cd ble-reticulum
chmod +x install.sh
./install.sh

# For custom config directory:
# ./install.sh --config /path/to/custom/config

The script will:

  1. ✓ Detect if Reticulum is in a venv or system-wide
  2. ✓ Install system dependencies (BlueZ, dbus)
  3. ✓ Install Python packages in the correct environment
  4. ✓ Copy BLE interface files to ~/.reticulum/interfaces/ (or custom config directory if specified)
  5. ✓ Enable BlueZ experimental mode (required for proper BLE connectivity)
  6. ✓ Optionally set up Bluetooth permissions

BlueZ Experimental Mode: The installer automatically enables BlueZ experimental mode, which is required for proper BLE connectivity. This allows the BLE interface to use LE-specific connection methods instead of defaulting to Classic Bluetooth (BR/EDR), preventing connection errors like "br-connection-profile-unavailable".

To skip this configuration (not recommended):

./install.sh --skip-experimental

Option B: Manual Installation

1. Install System Dependencies

Debian/Ubuntu/Raspberry Pi OS:

sudo apt-get update
sudo apt-get install python3-pip python3-gi python3-dbus python3-cairo bluez

Arch Linux:

sudo pacman -S base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils

Why these packages?

  • base-devel: Build tools (gcc, make, meson) required for compiling PyGObject
  • gobject-introspection: Development files for GObject introspection (required for PyGObject compilation)
  • python-dbus: D-Bus Python bindings for BlueZ communication
  • python-cairo: Cairo graphics library
  • bluez / bluez-utils: Bluetooth stack and utilities for Linux

Note for Arch users: PyGObject is intentionally NOT installed as a system package on Arch due to version incompatibility (Arch has 3.54.5, but bluezero requires <3.52.0). Instead, pip will compile the compatible PyGObject version (3.50.2) during installation. This adds ~2 minutes to installation time but ensures compatibility.

2. Install Python Dependencies

IMPORTANT: Install in the same environment as Reticulum!

Since we installed system packages for PyGObject, dbus-python, and pycairo in step 1, we only need to install the pure-Python packages:

If Reticulum is in a virtual environment:

# Activate the same venv where Reticulum is installed
source /path/to/reticulum-venv/bin/activate
pip install bleak==1.1.1 bluezero

If Reticulum is installed system-wide:

# Install system-wide (may need sudo)
pip install bleak==1.1.1 bluezero
# OR
sudo pip install bleak==1.1.1 bluezero

Note: The system packages (python3-gi, python3-dbus, python3-cairo) provide PyGObject, dbus-python, and pycairo, eliminating the need for lengthy compilation from source.

3. Copy BLE Interface Files

# Copy to Reticulum's interface directory
mkdir -p ~/.reticulum/interfaces
cp src/RNS/Interfaces/BLE*.py ~/.reticulum/interfaces/

4. Enable BlueZ Experimental Mode (Required)

BlueZ experimental mode is required for proper BLE connectivity. Without it, BlueZ may attempt Classic Bluetooth (BR/EDR) connections instead of BLE (LE) connections, causing connection failures.

Enable experimental mode (BlueZ >= 5.49):

sudo systemctl edit bluetooth

Add these lines:

[Service]
ExecStart=
ExecStart=/usr/lib/bluetooth/bluetoothd -E

Save and restart Bluetooth:

sudo systemctl daemon-reload
sudo systemctl restart bluetooth

Verify it's enabled:

ps aux | grep bluetoothd
# Should show: /usr/lib/bluetooth/bluetoothd -E

5. Grant Bluetooth Permissions

For non-root operation:

sudo setcap 'cap_net_raw,cap_net_admin+eip' $(which python3)

Note: If Reticulum is in a venv, grant permissions to that Python:

sudo setcap 'cap_net_raw,cap_net_admin+eip' /path/to/venv/bin/python3

Quick Start

1. Configure Reticulum

Add the BLE interface to your Reticulum configuration (~/.reticulum/config):

[[BLE Interface]]
  type = BLEInterface
  enabled = yes

  # Optional: set short device name (max 8 chars recommended, default: none)
  # device_name = RNS

For detailed configuration options, see examples/config_example.toml.

Custom Config Directory: If you use a custom Reticulum config directory with --config, the BLE interface will automatically use that directory to find its companion modules. No additional configuration needed!

2. Start Reticulum

rnsd --verbose

The interface will:

  1. Start advertising as a peripheral (if enabled)
  2. Scan for nearby BLE peers
  3. Automatically connect to discovered peers
  4. Form a mesh network with other BLE nodes

3. Verify Operation

# Check interface status
rnstatus

# Monitor announces
rnid -a

Configuration

The BLE interface supports extensive configuration options. See examples/config_example.toml for a fully documented example with all available options.

Key Configuration Options

  • device_name: Optional BLE device name (default: none, keep short if used, max 8 chars recommended)
  • service_uuid: BLE service UUID (must match on all devices)
  • enable_peripheral: Accept incoming connections (default: yes)
  • enable_central: Scan and connect to peers (default: yes)
  • discovery_interval: How often to scan for new peers (default: 5.0 seconds)
  • max_connections: Maximum simultaneous connections (default: 7)
  • min_rssi: Minimum signal strength in dBm (default: -85)
  • power_mode: Power management (aggressive/balanced/saver)

Testing

For detailed testing information, see TESTING.md.

Quick test using example script (no BLE hardware required):

cd examples
python ble_minimal_test.py test

Troubleshooting

No peers discovered

  • Verify Bluetooth is enabled: bluetoothctl show
  • Check service_uuid matches on all devices
  • Try power_mode = aggressive for faster discovery
  • Increase min_rssi to -90 for longer range

Connection timeouts

  • Increase connection_timeout to 60
  • Reduce max_connections to 3-5
  • Check for BLE/WiFi interference (both use 2.4 GHz)
  • Verify peer is within range (typically 10-30m)
  • If logs show "Operation already in progress" errors, this is handled automatically in v2.2.1+ with connection state tracking and rate limiting (see BLE_PROTOCOL_v2.2.md § Troubleshooting for details)

GATT server failed to start

  • Ensure BlueZ 5.x is installed: bluetoothd --version
  • Check Bluetooth permissions (see Installation → Manual Installation → step 4)
  • Try sudo rnsd temporarily to verify (not recommended for production)
  • Set enable_peripheral = no to disable peripheral mode

Permission denied errors

  • Grant capabilities to Python (see Installation → Manual Installation → step 5)
  • Or run with sudo: sudo rnsd (not recommended)

BR/EDR connection errors (br-connection-profile-unavailable, ProfileUnavailable)

These errors occur when BlueZ attempts Classic Bluetooth (BR/EDR) connections instead of BLE (LE) connections. This is the most common BLE connection issue.

Symptoms:

  • Devices connect then immediately disconnect
  • Errors: "br-connection-profile-unavailable", "ProfileUnavailable"
  • "ConnectDevice() unavailable" in logs
  • Devices get blacklisted after multiple failures

Solution: Enable BlueZ experimental mode (see Installation → Manual Installation → step 4). If you used the automated installer, re-run it without --skip-experimental.

Bluetooth adapter not powered / "No powered Bluetooth adapters found"

The Bluetooth adapter exists but is powered off, preventing BLE operations.

Symptoms:

  • Error: dbus.exceptions.DBusException: org.bluez.Error.Failed: Not Powered
  • Error: BleakError: No powered Bluetooth adapters found.
  • BLE interface fails to start or discover peers
  • GATT server startup fails immediately

Cause: The Bluetooth adapter is in a powered-off state. This commonly happens on Raspberry Pi after boot or system updates.

Solution: Power on the Bluetooth adapter:

# Option 1: Using bluetoothctl (recommended)
bluetoothctl power on

# Option 2: If adapter is RF-blocked
sudo rfkill unblock bluetooth

# Option 3: Using hciconfig (older systems)
sudo hciconfig hci0 up

# Verify adapter is powered:
bluetoothctl show
# Should display "Powered: yes"

Automatic power-on at boot: Ensure Bluetooth service is enabled and starts at boot:

# Enable Bluetooth service
sudo systemctl enable bluetooth
sudo systemctl start bluetooth

# For persistent power-on, create a systemd service:
# See examples/bluetooth-power-on.service

The automated installer (v1.x+) automatically checks and powers on the Bluetooth adapter during installation.

Architecture

The BLE interface consists of four main components:

  • BLEInterface.py: Main interface implementation, handles discovery and connections
  • BLEGATTServer.py: GATT server for peripheral mode (accepting connections)
  • BLEFragmentation.py: Packet fragmentation/reassembly for BLE MTU limits
  • BLEAgent.py: Per-peer connection management

Development Setup

For contributors and developers who want to work on the BLE interface code itself.

Note: This setup is different from the production installation above. Use a virtual environment for development to avoid conflicts.

# Clone repository
git clone https://github.com/torlando-tech/ble-reticulum.git
cd ble-reticulum

# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate

# Install RNS (required for tests)
pip install rns

# Install all dependencies (runtime + development + testing)
pip install -r requirements-dev.txt

# Create package structure for tests
touch src/RNS/__init__.py
touch src/RNS/Interfaces/__init__.py

# Run tests
pytest

# Run tests with coverage
pytest --cov=src/RNS/Interfaces --cov-report=html

For detailed development and testing guidelines, see CONTRIBUTING.md and TESTING.md.

Automated Deployment

The repository includes a GitHub Actions workflow for automated deployment to Raspberry Pi devices after code changes.

Setup Requirements

  1. Self-hosted GitHub runner on the same network as your Raspberry Pis
  2. Repository cloned on each Raspberry Pi
  3. SSH access configured between runner and Pis
  4. GitHub secrets configured for deployment

Configuring GitHub Secrets

Navigate to your repository Settings → Secrets and variables → Actions, and add:

Secret Description Example
PI_HOSTS Comma-separated list of Pi hostnames or IPs pi1.local,pi2.local,192.168.1.100
PI_REPO_PATH Absolute path to repository on Pis /home/pi/ble-reticulum
PI_USER SSH username for connecting to Pis pi
PI_SSH_KEY SSH private key for authentication -----BEGIN OPENSSH PRIVATE KEY-----...

SSH Configuration

For containerized runners (k3s, Docker, etc.):

# 1. Generate SSH key pair (on any machine)
ssh-keygen -t ed25519 -C "github-runner-deployment" -f ~/.ssh/github_runner_deploy
# Press Enter for no passphrase (required for automation)

# 2. Copy public key to each Raspberry Pi
ssh-copy-id -i ~/.ssh/github_runner_deploy.pub pi@pi1.local
ssh-copy-id -i ~/.ssh/github_runner_deploy.pub pi@pi2.local

# 3. Add private key to GitHub Secrets as PI_SSH_KEY
cat ~/.ssh/github_runner_deploy
# Copy the entire output and add to GitHub Settings → Secrets

# 4. Test connection
ssh -i ~/.ssh/github_runner_deploy pi@pi1.local 'echo "Connection successful"'

The workflow automatically writes the key to the container at runtime and cleans it up after deployment.

How It Works

When you push code changes to any branch:

  1. Tests run first: Unit and integration tests execute on GitHub's hosted runners
  2. Deployment triggers: After tests pass, the deploy job runs on your self-hosted runner
  3. For each Pi:
    • Git checkout and pull the pushed branch
    • Copy src/RNS/Interfaces/*.py to ~/.reticulum/interfaces/
    • Restart rnsd service (via systemd or direct process management)
  4. Status reported: Success/failure for each Pi with summary in GitHub Actions

Monitoring Deployments

View deployment status in:

  • Actions tab: Check workflow runs and logs
  • Job summary: See which Pis succeeded/failed
  • Commit status: Deployment status badge on commits

Troubleshooting Deployment

Deployment didn't run:

  • Check that tests passed (deployment depends on test jobs)
  • Verify changes were in src/** directory or workflow file

SSH connection failed:

# On self-hosted runner, test connection manually
ssh pi@pi1.local 'echo "Test successful"'

# Check DNS resolution
ping pi1.local

# Verify secrets match actual hostnames
# Check GitHub Settings → Secrets

Restart failed:

# On each Pi, verify rnsd service exists
systemctl status rnsd

# Or check if rnsd is in PATH
which rnsd

# Ensure user has sudo permissions if using systemd
sudo -l

For complete workflow documentation, see .github/workflows/README.md.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for:

  • Code style guidelines
  • Pull request process
  • Bug report templates
  • Feature request guidelines

Supporting

ko-fi

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments