Merge pull request #8 from torlando-tech/fix/issue-3-bluez-experimental-mode

Enable BlueZ experimental mode by default to fix BLE connection issues (fixes #3)
This commit is contained in:
torlando-tech 2025-10-29 00:09:05 -04:00 committed by GitHub
commit 82bd61d157
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 430 additions and 44 deletions

View file

@ -43,7 +43,15 @@ The script will:
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. ✓ Optionally set up Bluetooth permissions
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):
```bash
./install.sh --skip-experimental
```
### Option B: Manual Installation
@ -100,7 +108,35 @@ mkdir -p ~/.reticulum/interfaces
cp src/RNS/Interfaces/BLE*.py ~/.reticulum/interfaces/
```
#### 4. Grant Bluetooth Permissions
#### 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):
```bash
sudo systemctl edit bluetooth
```
Add these lines:
```
[Service]
ExecStart=
ExecStart=/usr/lib/bluetooth/bluetoothd -E
```
Save and restart Bluetooth:
```bash
sudo systemctl daemon-reload
sudo systemctl restart bluetooth
```
Verify it's enabled:
```bash
ps aux | grep bluetoothd
# Should show: /usr/lib/bluetooth/bluetoothd -E
```
#### 5. Grant Bluetooth Permissions
For non-root operation:
```bash
@ -199,9 +235,21 @@ python ble_minimal_test.py test
- Set `enable_peripheral = no` to disable peripheral mode
### Permission denied errors
- Grant capabilities to Python (see Installation → Manual Installation → step 4)
- 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`.
## Architecture
The BLE interface consists of four main components:

View file

@ -253,6 +253,19 @@ power_mode = balanced
# - Restart Bluetooth service (Linux: sudo systemctl restart bluetooth)
# - Check device_name is not too long (max ~20 characters)
#
# 6. BR/EDR connection errors (br-connection-profile-unavailable, ProfileUnavailable):
# IMPORTANT: This is the most common BLE connection issue!
# - Symptoms: Devices connect then immediately disconnect, "ConnectDevice() unavailable" in logs
# - Cause: BlueZ attempting Classic Bluetooth (BR/EDR) instead of BLE (LE) connections
# - Solution: Enable BlueZ experimental mode (required for proper BLE connectivity)
# Linux: sudo systemctl edit bluetooth
# Add: [Service]
# ExecStart=
# ExecStart=/usr/lib/bluetooth/bluetoothd -E
# Then: sudo systemctl daemon-reload && sudo systemctl restart bluetooth
# - If you used install.sh, it should have enabled this automatically
# - Verify: ps aux | grep bluetoothd (should show -E flag)
#
# For more troubleshooting, see README.md
# ============================================================================

View file

@ -51,19 +51,33 @@ pip_install() {
# Parse command line arguments
CUSTOM_CONFIG_DIR=""
SKIP_BLUEZ_EXPERIMENTAL=false
SKIP_BT_PERMISSIONS=false
while [[ $# -gt 0 ]]; do
case $1 in
--config)
CUSTOM_CONFIG_DIR="$2"
shift 2
;;
--skip-experimental)
SKIP_BLUEZ_EXPERIMENTAL=true
shift
;;
--skip-bt-permissions)
SKIP_BT_PERMISSIONS=true
shift
;;
-h|--help)
echo "Usage: $0 [--config CONFIG_DIR]"
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --config CONFIG_DIR Install to custom Reticulum config directory"
echo " (default: ~/.reticulum)"
echo " -h, --help Show this help message"
echo " --config CONFIG_DIR Install to custom Reticulum config directory"
echo " (default: ~/.reticulum)"
echo " --skip-experimental Skip enabling BlueZ experimental mode"
echo " WARNING: May cause BLE connection failures"
echo " --skip-bt-permissions Skip granting Bluetooth permissions"
echo " (you will need to run rnsd with sudo)"
echo " -h, --help Show this help message"
exit 0
;;
*)
@ -151,6 +165,11 @@ RNS_VENV=""
RNS_PYTHON=""
INSTALL_MODE=""
# Add user's local bin to PATH if it exists (common pip install location)
if [ -d "$HOME/.local/bin" ]; then
export PATH="$HOME/.local/bin:$PATH"
fi
# Check if rnsd is available
if command -v rnsd &> /dev/null; then
print_success "Found rnsd command"
@ -190,16 +209,50 @@ else
# Install Reticulum using our pip helper function
pip_install rns
# Verify installation
# Add user's local bin to PATH (pip may install there)
export PATH="$HOME/.local/bin:$PATH"
# Verify installation (check multiple locations and Python import)
if command -v rnsd &> /dev/null; then
print_success "Reticulum installed successfully"
INSTALL_MODE="system"
RNS_PYTHON="python3"
elif [ -f "$HOME/.local/bin/rnsd" ]; then
print_success "Reticulum installed successfully (user installation)"
print_info "rnsd location: $HOME/.local/bin/rnsd"
INSTALL_MODE="system"
RNS_PYTHON="python3"
# Ensure it's executable
chmod +x "$HOME/.local/bin/rnsd" 2>/dev/null || true
# Automatically add ~/.local/bin to PATH if not already there
if [ -f "$HOME/.bashrc" ]; then
if ! grep -q '.local/bin' "$HOME/.bashrc" 2>/dev/null; then
print_info "Adding ~/.local/bin to PATH in ~/.bashrc..."
echo '' >> "$HOME/.bashrc"
echo '# Added by Reticulum BLE installer' >> "$HOME/.bashrc"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc"
print_success "Added ~/.local/bin to PATH in ~/.bashrc"
print_warning "Reload your shell to use rnsd command:"
echo " source ~/.bashrc"
echo " # Or open a new terminal"
echo
else
print_info "~/.local/bin already in PATH configuration"
fi
fi
elif python3 -c "import RNS" 2>/dev/null; then
print_warning "Reticulum Python package installed, but rnsd command not found in PATH"
print_info "You may need to add ~/.local/bin to your PATH"
echo " Add to ~/.bashrc: export PATH=\"\$HOME/.local/bin:\$PATH\""
INSTALL_MODE="system"
RNS_PYTHON="python3"
else
print_error "Reticulum installation failed"
echo
echo "Please try installing manually:"
echo " pip install rns"
echo " # Then add to PATH: export PATH=\"\$HOME/.local/bin:\$PATH\""
echo "Or visit: https://reticulum.network"
exit 1
fi
@ -213,20 +266,20 @@ print_header "Installing System Dependencies"
if command -v apt-get &> /dev/null; then
# Debian/Ubuntu/Raspberry Pi OS
print_info "Detected Debian/Ubuntu-based system"
echo "Installing: python3-pip python3-gi python3-dbus python3-cairo bluez"
echo "Installing: python3-pip python3-gi python3-dbus python3-cairo bluez libcap2-bin"
# Use sudo only if not running as root
if [ "$EUID" -eq 0 ]; then
apt-get update
apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez
apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez libcap2-bin
else
sudo apt-get update
sudo apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez
sudo apt-get install -y python3-pip python3-gi python3-dbus python3-cairo bluez libcap2-bin
fi
print_success "System dependencies installed (using pre-compiled system packages)"
elif command -v pacman &> /dev/null; then
# Arch Linux
print_info "Detected Arch Linux"
echo "Installing: base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils"
echo "Installing: base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils libcap"
print_warning "Note: PyGObject will be compiled from pip due to version requirements (bluezero needs <3.52.0, Arch has 3.54.5)"
# Use sudo only if not running as root
if [ "$EUID" -eq 0 ]; then
@ -234,10 +287,10 @@ elif command -v pacman &> /dev/null; then
pacman -Sy --noconfirm
# Skip python-gobject to avoid version conflict - pip will compile PyGObject
# gobject-introspection provides dev files needed for PyGObject compilation
pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils
pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils libcap
else
sudo pacman -Sy --noconfirm
sudo pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils
sudo pacman -S --needed --noconfirm base-devel gobject-introspection python-pip python-dbus python-cairo bluez bluez-utils libcap
fi
print_success "System dependencies installed (PyGObject will be compiled from pip)"
else
@ -329,27 +382,241 @@ echo
# Step 5: Bluetooth permissions
print_header "Bluetooth Permissions"
print_info "For BLE to work without root, Python needs network capabilities"
echo
PYTHON_PATH=$(which python3)
echo "The following command will grant capabilities to Python:"
echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' $PYTHON_PATH"
echo
read -p "Grant Bluetooth permissions now? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
sudo setcap 'cap_net_raw,cap_net_admin+eip' "$PYTHON_PATH"
print_success "Bluetooth permissions granted"
# Check if user wants to skip Bluetooth permissions
if [ "$SKIP_BT_PERMISSIONS" = true ]; then
print_warning "Skipping Bluetooth permissions (--skip-bt-permissions flag)"
print_info "You will need to run rnsd with sudo, or grant permissions manually:"
echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' \$(readlink -f \$(which python3))"
echo
else
print_warning "Skipped. You may need to run rnsd with sudo"
echo " To grant permissions later, run:"
echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' \$(which python3)"
print_info "For BLE to work without root, Python needs network capabilities"
print_info "Automatically granting capabilities to Python..."
echo
# Check if setcap is available
if ! command -v setcap &> /dev/null; then
print_error "setcap command not found"
print_info "Installing libcap2-bin package..."
if command -v apt-get &> /dev/null; then
if [ "$EUID" -eq 0 ]; then
apt-get install -y libcap2-bin
else
sudo apt-get install -y libcap2-bin
fi
elif command -v pacman &> /dev/null; then
if [ "$EUID" -eq 0 ]; then
pacman -S --needed --noconfirm libcap
else
sudo pacman -S --needed --noconfirm libcap
fi
else
print_error "Could not install libcap2-bin/libcap automatically"
print_warning "Please install manually and re-run the installer"
echo
fi
# Verify setcap is now available
if ! command -v setcap &> /dev/null; then
print_error "setcap still not available after installation attempt"
print_warning "You will need to run rnsd with sudo, or manually install libcap2-bin"
echo
else
print_success "setcap installed successfully"
echo
fi
fi
if command -v setcap &> /dev/null; then
# Get python3 path
PYTHON_PATH=$(which python3)
print_info "Detected Python at: $PYTHON_PATH"
# Check if it's a symlink and resolve it
if [ -L "$PYTHON_PATH" ]; then
print_warning "Python3 is a symlink (setcap requires the actual binary)"
PYTHON_REAL=$(readlink -f "$PYTHON_PATH")
print_info "Resolved to actual binary: $PYTHON_REAL"
# Verify the resolved path exists and is a file
if [ ! -f "$PYTHON_REAL" ]; then
print_error "Could not resolve Python binary: $PYTHON_REAL"
print_warning "You may need to run rnsd with sudo"
echo
elif [ -L "$PYTHON_REAL" ]; then
print_error "Python path is still a symlink after resolution"
print_warning "Manual intervention required - you may need to run rnsd with sudo"
echo
else
PYTHON_PATH="$PYTHON_REAL"
fi
fi
# Grant capabilities if we have a valid path
if [ -f "$PYTHON_PATH" ] && [ ! -L "$PYTHON_PATH" ]; then
print_info "Granting capabilities to: $PYTHON_PATH"
sudo setcap 'cap_net_raw,cap_net_admin+eip' "$PYTHON_PATH"
if [ $? -eq 0 ]; then
print_success "Bluetooth permissions granted successfully"
# Verify capabilities were actually set
if command -v getcap &> /dev/null; then
CAPS=$(getcap "$PYTHON_PATH" 2>/dev/null)
if [ -n "$CAPS" ]; then
print_info "Verified: $CAPS"
fi
fi
else
print_error "Failed to grant Bluetooth permissions"
print_warning "You may need to run rnsd with sudo"
echo
print_info "To grant permissions manually, run:"
echo " sudo setcap 'cap_net_raw,cap_net_admin+eip' $PYTHON_PATH"
fi
else
print_error "Could not determine valid Python binary path"
print_warning "You may need to run rnsd with sudo"
fi
else
print_error "setcap command not available, cannot grant permissions"
print_warning "You will need to run rnsd with sudo"
fi
echo
fi
echo
# Step 5A: BlueZ Experimental Mode
print_header "BlueZ Experimental Mode"
# Check if bluetoothctl is available
if ! command -v bluetoothctl &> /dev/null; then
print_warning "bluetoothctl not found - BlueZ may not be installed"
print_info "BLE interface requires BlueZ for Bluetooth functionality"
echo
elif ! command -v systemctl &> /dev/null; then
print_warning "systemctl not found - cannot configure BlueZ experimental mode"
print_info "This system may not use systemd, or this may be a container environment"
echo
else
# Detect BlueZ version
BLUEZ_VERSION=$(bluetoothctl --version 2>/dev/null | grep -oP '\d+\.\d+' | head -1)
if [ -z "$BLUEZ_VERSION" ]; then
print_warning "Could not detect BlueZ version"
echo
else
print_info "Detected BlueZ version: $BLUEZ_VERSION"
# Parse version to check if >= 5.49
VERSION_MAJOR=$(echo "$BLUEZ_VERSION" | cut -d. -f1)
VERSION_MINOR=$(echo "$BLUEZ_VERSION" | cut -d. -f2)
if [ "$VERSION_MAJOR" -lt 5 ] || ([ "$VERSION_MAJOR" -eq 5 ] && [ "$VERSION_MINOR" -lt 49 ]); then
print_warning "BlueZ version $BLUEZ_VERSION does not support experimental mode (requires >= 5.49)"
print_info "BLE interface will work with standard connection methods"
print_info "Consider upgrading BlueZ for full BLE compatibility"
echo
else
# Check if experimental mode is already enabled
if systemctl status bluetooth 2>/dev/null | grep -q -- "-E\|--experimental"; then
print_success "BlueZ experimental mode already enabled"
echo
elif [ "$SKIP_BLUEZ_EXPERIMENTAL" = true ]; then
# User explicitly skipped experimental mode - show strong warning
echo
print_error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_error "WARNING: Skipping BlueZ experimental mode"
print_error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
echo -e "${RED}BLE connections may fail with errors like:${NC}"
echo " • br-connection-profile-unavailable"
echo " • ProfileUnavailable"
echo " • Immediate disconnections after pairing"
echo
echo -e "${RED}Your BLE interface may attempt Classic Bluetooth (BR/EDR)${NC}"
echo -e "${RED}connections instead of BLE (LE) connections.${NC}"
echo
echo -e "${YELLOW}This is NOT RECOMMENDED unless you have a specific reason.${NC}"
echo
echo "To enable experimental mode later:"
echo " 1. sudo systemctl edit bluetooth"
echo " 2. Add these lines:"
echo " [Service]"
echo " ExecStart="
echo " ExecStart=/usr/lib/bluetooth/bluetoothd -E"
echo " 3. sudo systemctl daemon-reload"
echo " 4. sudo systemctl restart bluetooth"
echo
print_error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo
else
# Enable experimental mode by default
print_info "Enabling BlueZ experimental mode (required for proper BLE connectivity)"
print_info "This enables LE-specific connection methods (ConnectDevice API)"
echo
# Find bluetoothd path
BLUETOOTHD_PATH=""
for path in /usr/lib/bluetooth/bluetoothd /usr/libexec/bluetooth/bluetoothd; do
if [ -f "$path" ]; then
BLUETOOTHD_PATH="$path"
break
fi
done
if [ -z "$BLUETOOTHD_PATH" ]; then
print_error "Could not find bluetoothd binary"
print_warning "Tried: /usr/lib/bluetooth/bluetoothd, /usr/libexec/bluetooth/bluetoothd"
echo
else
print_info "Using bluetoothd at: $BLUETOOTHD_PATH"
# Create systemd override
print_info "Creating systemd override for bluetooth service..."
# Use sudo only if not running as root
if [ "$EUID" -eq 0 ]; then
# Running as root - no sudo needed
mkdir -p /etc/systemd/system/bluetooth.service.d
cat > /etc/systemd/system/bluetooth.service.d/override.conf <<EOF
[Service]
ExecStart=
ExecStart=$BLUETOOTHD_PATH -E
EOF
systemctl daemon-reload
systemctl restart bluetooth
else
# Not root - use sudo
sudo mkdir -p /etc/systemd/system/bluetooth.service.d
sudo tee /etc/systemd/system/bluetooth.service.d/override.conf > /dev/null <<EOF
[Service]
ExecStart=
ExecStart=$BLUETOOTHD_PATH -E
EOF
sudo systemctl daemon-reload
sudo systemctl restart bluetooth
fi
# Verify bluetooth service is running
if systemctl is-active --quiet bluetooth; then
# Double-check that -E flag is actually set
if ps aux | grep bluetoothd | grep -q -- "-E"; then
print_success "BlueZ experimental mode enabled successfully"
else
print_warning "Bluetooth service restarted but -E flag not detected"
print_info "You may need to manually verify: ps aux | grep bluetoothd"
fi
else
print_error "Bluetooth service failed to start"
print_warning "Check status with: sudo systemctl status bluetooth"
echo
fi
echo
fi
fi
fi
fi
fi
# Step 6: Configuration
print_header "Configuration"
@ -361,27 +628,41 @@ echo
echo "1. Add the BLE interface to your Reticulum config:"
echo " File: $CONFIG_FILE"
echo
echo " Add this section:"
echo " ┌─────────────────────────────────────────┐"
echo " │ [[BLE Interface]] │"
echo " │ type = BLEInterface │"
echo " │ enabled = yes │"
echo " │ │"
echo " │ # Enable both modes for mesh │"
echo " │ enable_peripheral = yes │"
echo " │ enable_central = yes │"
echo " └─────────────────────────────────────────┘"
echo " Add this section (copy-paste ready):"
echo
echo " [[BLE Interface]]"
echo " type = BLEInterface"
echo " enabled = yes"
echo
echo " # Enable both modes for mesh"
echo " enable_peripheral = yes"
echo " enable_central = yes"
echo
echo "2. See examples/config_example.toml for all configuration options"
echo
echo "3. Start Reticulum:"
# Add PATH note only if rnsd is in user's local bin AND not added to .bashrc
STEP_NUM=3
if [ -f "$HOME/.local/bin/rnsd" ] && [ -f "$HOME/.bashrc" ]; then
# Only show if we didn't automatically add it (i.e., it wasn't already there)
if ! grep -q '.local/bin' "$HOME/.bashrc" 2>/dev/null; then
echo "3. Add ~/.local/bin to your PATH (for rnsd command):"
echo " echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc"
echo " source ~/.bashrc"
echo
STEP_NUM=4
fi
fi
echo "$STEP_NUM. Start Reticulum:"
if [ -n "$CUSTOM_CONFIG_DIR" ]; then
echo " rnsd --config $CONFIG_DIR --verbose"
else
echo " rnsd --verbose"
fi
echo
echo "4. Verify the interface is running:"
STEP_NUM=$((STEP_NUM + 1))
echo "$STEP_NUM. Verify the interface is running:"
echo " rnstatus"
echo

View file

@ -52,6 +52,7 @@ chmod +x install.sh
mkdir -p /tmp/test-config
# Run non-interactively (answer 'n' to bluetooth permissions prompt)
# Note: BlueZ experimental mode will be enabled by default (no prompt)
./install.sh --config /tmp/test-config <<EOF
n
EOF
@ -153,6 +154,49 @@ echo "Testing BLE interface import..."
cd /tmp/test-config/interfaces
python3 -c "import sys; sys.path.insert(0, '.'); from BLEInterface import BLEInterface; print(' ✓ BLEInterface imported successfully')" || { echo "FAIL: Cannot import BLEInterface"; exit 1; }
echo ""
# Check BlueZ experimental mode configuration
echo "Checking BlueZ experimental mode..."
if command -v systemctl &> /dev/null && command -v bluetoothctl &> /dev/null; then
# systemctl is available - check if experimental mode was configured
if [ -f /etc/systemd/system/bluetooth.service.d/override.conf ]; then
echo " ✓ Systemd override file created"
if grep -q -- "-E" /etc/systemd/system/bluetooth.service.d/override.conf; then
echo " ✓ Experimental mode flag (-E) configured"
else
echo " ⚠ WARNING: Override file exists but -E flag not found"
fi
else
# No override file - may have been already enabled or not supported
if systemctl status bluetooth 2>/dev/null | grep -q -- "-E\|--experimental"; then
echo " ✓ Experimental mode already enabled (not via installer)"
else
echo " ⚠ WARNING: Experimental mode not configured"
fi
fi
else
# systemctl or bluetoothctl not available (container environment)
echo " Systemd/BlueZ not available (container environment - OK)"
fi
echo ""
# Test --skip-experimental flag
echo "Testing --skip-experimental flag..."
cd "$(dirname "$0")/.."
# Run with --skip-experimental to verify it doesn't fail
./install.sh --config /tmp/test-config-skip --skip-experimental > /tmp/skip-test.log 2>&1 <<EOF
n
EOF
# Check that warning was shown
if grep -q "WARNING: Skipping BlueZ experimental mode" /tmp/skip-test.log; then
echo " ✓ --skip-experimental flag works (warning displayed)"
else
echo " ⚠ WARNING: --skip-experimental flag may not be working correctly"
fi
echo ""
echo "=== SUCCESS: All tests passed ==="
echo ""