If you want to control hardware directly from an Android phone without relying on a local WiFi router or cloud broker, Bluetooth Low Energy (BLE) is the definitive choice. When pairing Android and Raspberry Pi 3 hardware, the Pi 3 Model B+ serves as an excellent BLE peripheral (server) thanks to its onboard Cypress CYW43455 wireless chip. This guide walks through building a BLE-controlled 2-channel relay system, complete with pin mappings, production-ready Python code, and solutions to the notorious BlueZ permission errors that stall most builders.
The Decision Tree: Connecting Android to Raspberry Pi 3
Before wiring anything, you must choose your communication protocol. Builders often default to MQTT over WiFi, but that introduces network dependency and latency. Use this decision matrix to select the right interface for your embedded project.
| Protocol | Network Dependency | Latency | Setup Complexity | Effective Range |
|---|---|---|---|---|
| MQTT over WiFi | High (Requires Router/Broker) | Medium (10-50ms) | High (Network config + Broker) | ~30m (Indoor) |
| USB OTG Serial | None | Lowest (<5ms) | Low (Direct cable) | 1m (Cable length) |
| BLE GATT Server | None (Direct P2P) | Low (15-30ms) | Medium (Code + DBus) | ~10m (No walls) |
Hardware Spec Sheet & Parts List
This build specifically targets the Raspberry Pi 3 Model B+ (Broadcom BCM2837B0, 1GB RAM). Do not use the Pi 3 Model A+ or older Pi 2 boards; the B+ features improved thermal management and the dual-band Wi-Fi/BT 4.2 chip required for stable BLE peripheral advertising.
| Component | Exact Variant / Model | Estimated Cost (2026) | Notes |
|---|---|---|---|
| Microcontroller | Raspberry Pi 3 Model B+ (1GB) | $35 - $45 (Used/Refurb) | Must have onboard CYW43455 BT chip. |
| Power Supply | Official 5.1V 2.5A Micro-USB PSU | $12 | Prevents brownout warnings under relay load. |
| Relay Module | Songle SRD-05VDC-SL-C (2-Channel) | $8 | Optocoupler isolated, active LOW trigger. |
| Android Client | Any Android 10+ device | N/A | Use the free nRF Connect app from Play Store. |
Pin Mapping & Wiring the Relay Module
The Pi 3's 3.3V GPIO logic is not sufficient to drive 5V relay coils directly. We use the relay module's onboard optocouplers and power the coil side from the Pi's 5V rail. Ensure your relay module is set to Active LOW (the default for most Songle modules with jumper caps installed).
| Pi 3 Physical Pin | BCM GPIO Number | Relay Module Pin | Wire Color (Standard) |
|---|---|---|---|
| Pin 2 | 5V Power | VCC | Red |
| Pin 6 | Ground | GND | Black |
| Pin 11 | GPIO 17 | IN1 (Relay 1) | Yellow |
| Pin 13 | GPIO 27 | IN2 (Relay 2) | Orange |
The Python BLE Server Code (Targeting Pi 3 Model B+)
This script uses the bluezero library, which wraps the complex Linux BlueZ D-Bus API into manageable Python objects. It advertises a custom BLE service with two characteristics (one for each relay). The Android app writes a 1 or 0 to toggle the GPIO pins.
Prerequisites: Run sudo pip3 install bluezero RPi.GPIO on your Pi.
#!/usr/bin/env python3
import sys
import time
import RPi.GPIO as GPIO
from bluezero import peripheral
# --- PIN DEFINITIONS ---
RELAY_1_PIN = 17 # BCM 17 / Physical Pin 11
RELAY_2_PIN = 27 # BCM 27 / Physical Pin 13
# --- BLE UUIDs ---
# Generate your own using `uuidgen` in terminal for production
SERVICE_UUID = '0000fff0-0000-1000-8000-00805f9b34fb'
RELAY1_CHAR_UUID = '0000fff1-0000-1000-8000-00805f9b34fb'
RELAY2_CHAR_UUID = '0000fff2-0000-1000-8000-00805f9b34fb'
def setup_hardware():
GPIO.setmode(GPIO.BCM)
GPIO.setup(RELAY_1_PIN, GPIO.OUT, initial=GPIO.HIGH) # HIGH = OFF for Active LOW
GPIO.setup(RELAY_2_PIN, GPIO.OUT, initial=GPIO.HIGH)
def update_relay(characteristic):
"""Callback triggered when Android writes to the BLE characteristic."""
value = int(characteristic.value[0])
pin = RELAY_1_PIN if characteristic.uuid == RELAY1_CHAR_UUID else RELAY_2_PIN
# Active LOW logic: 0 turns relay ON, 1 turns relay OFF
if value == 1:
GPIO.output(pin, GPIO.LOW)
print(f'Relay on Pin {pin} ENGAGED')
else:
GPIO.output(pin, GPIO.HIGH)
print(f'Relay on Pin {pin} DISENGAGED')
def main():
setup_hardware()
# Define the BLE Peripheral
pi_ble = peripheral.Peripheral(
adapter_name='hci0',
local_name='Pi3-Relay-Server',
appearance=0x0000
)
# Add Service
pi_ble.add_service(srv_id=1, uuid=SERVICE_UUID, primary=True)
# Add Characteristics with write callbacks
pi_ble.add_characteristic(
srv_id=1, chr_id=1, uuid=RELAY1_CHAR_UUID,
value=[], notifying=False,
flags=['read', 'write'],
write_callback=update_relay,
read_callback=None
)
pi_ble.add_characteristic(
srv_id=1, chr_id=2, uuid=RELAY2_CHAR_UUID,
value=[], notifying=False,
flags=['read', 'write'],
write_callback=update_relay,
read_callback=None
)
print('Starting BLE Peripheral. Scan with nRF Connect on Android...')
try:
pi_ble.publish()
except KeyboardInterrupt:
print('\nShutting down...')
except Exception as e:
print(f'Fatal BLE Error: {e}')
finally:
GPIO.cleanup()
print('GPIO cleaned up. Safe to power off.')
if __name__ == '__main__':
main()
Debugging the "AccessDenied" BlueZ Error
When running BLE GATT servers on Linux, the D-Bus message broker strictly enforces permissions. The most common failure mode when executing the script above as a standard user (e.g., pi) is an immediate crash with the following exact error string:
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.AccessDenied: Rejected send message, 1 matched rules; type="method_call", sender=":1.14" (uid=1000 pid=1234 comm="python3") destination="org.bluez" (uid=0 pid=456 comm="/usr/lib/bluetooth/bluetoothd ")
Ranked Causes & Fixes
- Cause 1: Missing D-Bus Policy Permissions (Most Likely). The standard user lacks rights to register a GATT application with the root-owned
bluetoothddaemon.
Fix: Run the script withsudo python3 ble_relay.py. For a permanent fix without sudo, create a custom D-Bus policy file in/etc/dbus-1/system.d/granting thebluetoothgroup access to theorg.bluezinterface. - Cause 2: Bluetooth Service Not Active. The
bluetoothddaemon crashed or is masked.
Fix: Runsudo systemctl restart bluetoothand verify withsystemctl status bluetooth. - Cause 3: HCI0 Interface Soft-Blocked. The RF kill switch has disabled the Bluetooth radio at the kernel level.
Fix: Runrfkill list. If Bluetooth shows "Soft blocked: yes", runsudo rfkill unblock bluetooth.
The First Three Things to Check When It Fails
If your Android phone sees the Pi advertising but drops the connection immediately upon writing a value, run through this checklist:
- Verify MTU Negotiation: Android devices often request a larger Maximum Transmission Unit (MTU). Ensure your
bluezerocharacteristic isn't hardcoded to a 20-byte payload limit if your app is sending larger arrays. (Our code uses single-byte integer writes, bypassing this issue). - Check Power Supply Brownouts: When the relay coil energizes, it draws ~70mA. If your Pi 3 power supply is marginal, the 5V rail dips, resetting the CYW43455 Bluetooth chip. Check
dmesg | grep -i undervoltage. If you see warnings, upgrade to a 3A power supply. - Inspect GPIO State: Use
raspi-gpio getin a separate terminal while triggering the Android app to confirm the software is actually toggling the BCM pins, isolating the issue to hardware wiring vs. software logic.
Extending or Simplifying the Build
Depending on your final application, you may need to scale this architecture up or strip it down.
How to Simplify (The "I Just Want a Button" Route)
If setting up custom GATT UUIDs and D-Bus permissions is overkill, scrap bluezero and install the BlueDot library. BlueDot creates a virtual joystick over standard Bluetooth Serial (RFCOMM) rather than BLE. It requires pairing via the Raspberry Pi desktop GUI, but the Python code drops to five lines, and the companion Android app provides a ready-made UI without needing nRF Connect.
How to Extend (Whole-Home Mesh)
The Pi 3's BLE range is limited to about 10 meters through walls. To extend this to a whole-home automation system without running Cat6 cable:
- Keep the Raspberry Pi 3 as the central logic hub and MQTT broker.
- Deploy ESP32-C3 modules ($4 each) at the actual relay locations. The ESP32 natively supports BLE and WiFi.
- Configure the ESP32s as BLE-to-WiFi bridges. The Pi 3 pushes MQTT messages over your home WiFi to the ESP32, which then executes local GPIO toggles. This preserves the Pi's processing power for dashboards and logging while offloading the RF heavy lifting to dedicated microcontrollers.
For deeper reading on the Linux Bluetooth stack architecture, consult the official BlueZ project documentation and the Raspberry Pi hardware compute modules guide for specific thermal and power delivery constraints of the Pi 3 generation.






