Project Verdict & Architecture Decision
To control physical hardware from an Android device using a Raspberry Pi 3, the most reliable and lowest-latency architecture is a local MQTT broker (Mosquitto) running on the Pi, paired with a Python gpiozero script subscribing to topics, and an Android MQTT dashboard app publishing JSON commands. This setup bypasses the latency and privacy risks of cloud routing while keeping the Android app logic simple.
Architecture Decision Path
| Requirement | Architecture Choice | Verdict |
|---|---|---|
| Need < 50ms latency and 100% offline capability | Local MQTT (Mosquitto on Pi LAN) | DEFAULT PICK |
| Need remote access over cellular networks | Cloud MQTT (HiveMQ) + Pi forwarding | Use only if WAN access is mandatory |
| Need video streaming to Android | WebRTC / RTSP | Out of scope for simple GPIO switching |
Exact Parts List
- Board: Raspberry Pi 3 Model B+ (1GB RAM) running Raspberry Pi OS Bookworm (64-bit)
- Storage: 16GB SanDisk Extreme microSD (A1 rated for database/broker logging)
- Relay Module: 4-Channel 5V Relay Module with optocoupler isolation (Songle SRD-05VDC-SL-C)
- Wiring: 18 AWG stranded silicone wire, female-to-female Dupont jumpers
- Power: 5V 2.5A Micro-USB power supply (CanaKit or official Pi foundation)
- Android Client: Any MQTT app (we recommend IoT MQTT Panel from the Play Store)
Hardware Wiring & Pin Mapping
The Raspberry Pi 3 Model B+ GPIO header provides 3.3V logic, but most standard 4-channel relay modules require 5V for the relay coils and 3.3V-compatible logic for the optocoupler inputs. We will power the relay VCC from the Pi's 5V rail, and use BCM GPIO pins for the control signals.
| Relay Module Pin | Raspberry Pi 3 Pin (Physical) | BCM GPIO | Function |
|---|---|---|---|
| VCC | Pin 2 | 5V Power | Powers relay coils |
| GND | Pin 9 | Ground | Common ground |
| IN1 | Pin 11 | GPIO 17 | Relay 1 Control |
| IN2 | Pin 13 | GPIO 27 | Relay 2 Control |
| IN3 | Pin 15 | GPIO 22 | Relay 3 Control |
| IN4 | Pin 16 | GPIO 23 | Relay 4 Control |
Raspberry Pi 3 Software Setup & Python Code
This code targets the Raspberry Pi 3 Model B+ running Raspberry Pi OS Bookworm. It uses gpiozero (which defaults to the lgpio pin factory in Bookworm) and paho-mqtt v2.0.
Step 1: Install Dependencies
SSH into your Pi 3 and install the Mosquitto broker and Python libraries:
sudo apt update
sudo apt install mosquitto mosquitto-clients python3-gpiozero python3-pip -y
pip3 install paho-mqtt --break-system-packages
Step 2: The Python Control Script
Save the following code as android_relay_hub.py. Notice the use of active_high=False in the OutputDevice initialization; most optocoupler relay boards are "Active LOW", meaning the relay engages when the GPIO pin is pulled to ground (0V).
import paho.mqtt.client as mqtt
from gpiozero import OutputDevice
from signal import pause
import json
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Pin definitions (BCM numbering) - Active LOW for standard optocoupler relays
RELAY_PINS = {
'relay_1': OutputDevice(17, active_high=False, initial_value=False),
'relay_2': OutputDevice(27, active_high=False, initial_value=False),
'relay_3': OutputDevice(22, active_high=False, initial_value=False),
'relay_4': OutputDevice(23, active_high=False, initial_value=False)
}
MQTT_BROKER = 'localhost'
MQTT_PORT = 1883
MQTT_TOPIC = 'home/pi3/relays/control'
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
logging.info('Connected to Mosquitto broker')
client.subscribe(MQTT_TOPIC)
else:
logging.error(f'Connection failed with code: {reason_code}')
def on_message(client, userdata, msg):
try:
payload = json.loads(msg.payload.decode('utf-8'))
target = payload.get('target')
state = payload.get('state')
if target in RELAY_PINS and state in ['ON', 'OFF']:
if state == 'ON':
RELAY_PINS[target].on()
logging.info(f'{target} engaged')
else:
RELAY_PINS[target].off()
logging.info(f'{target} disengaged')
else:
logging.warning(f'Invalid payload structure: {payload}')
except json.JSONDecodeError:
logging.error('Received malformed JSON payload from Android client')
except Exception as e:
logging.error(f'Unexpected error processing message: {e}')
if __name__ == '__main__':
# paho-mqtt v2.0 requires explicit API version declaration
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id='pi3_android_hub')
client.on_connect = on_connect
client.on_message = on_message
try:
client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start()
logging.info('Hub active. Waiting for Android MQTT commands...')
pause()
except KeyboardInterrupt:
logging.info('Shutting down safely...')
except Exception as e:
logging.critical(f'Broker connection failed: {e}')
finally:
client.loop_stop()
for pin_name, device in RELAY_PINS.items():
device.off()
device.close()
Android Client Configuration
On your Android device, download IoT MQTT Panel (or a similar MQTT client). Configure the connection to your Pi 3's local IP address (e.g., 192.168.1.50) on port 1883.
Create a toggle switch widget in the app and set its publish topic to home/pi3/relays/control. The payload must be formatted as JSON. For example, to turn on Relay 2, configure the switch to publish:
{"target": "relay_2", "state": "ON"}
When you toggle the switch on your Android screen, the payload hits the Mosquitto broker on the Pi 3 in roughly 5-15ms over a standard 2.4GHz Wi-Fi network, triggering the Python callback and clicking the physical relay.
Debugging: When the Relays Don't Click
Embedded hardware debugging requires a systematic approach. If your Android commands aren't triggering the physical relays, check these exact error strings and follow the ranked causes.
The First Three Things to Check
- Broker Status: Run
systemctl status mosquitto. If it's dead, the Python script will instantly fail to connect. - 5V Rail Voltage: Measure Pin 2 to Pin 6 with a multimeter while triggering a relay. If it drops below 4.7V, the Pi 3's polyfuse is tripping or your power supply is inadequate. The optocoupler LEDs won't fire reliably under 4.8V.
- Optocoupler Jumper: Look at the relay board. There is a jumper connecting
VCCandJD-VCC. For standard Pi 3 wiring, this jumper must be in place. If removed, the board expects a separate 5V logic supply.
Ranked Error Causes & Fixes
| Exact Error String | Rank | Root Cause | Fix |
|---|---|---|---|
gpiozero.exc.PinFactoryFallback: Falling back from rpigpio: No access to /dev/mem |
1 | Legacy RPi.GPIO is being called without root, or lgpio is missing in Bookworm. |
Ensure you are using gpiozero (which defaults to lgpio in Bookworm). Run sudo apt install python3-lgpio. Do not use sudo to run the script; run it as the standard pi user. |
ConnectionRefusedError: [Errno 111] Connection refused |
2 | Mosquitto is not running, or it is bound only to localhost but the Python script is trying to reach a remote IP. | Start the broker: sudo systemctl start mosquitto. Verify localhost is used in the Python script if running on the same Pi. |
Warning: Received malformed JSON payload from Android client |
3 | The Android MQTT app is sending a plain string (e.g., "ON") instead of the expected JSON dictionary. | Reconfigure the Android app widget payload to strictly output {"target": "relay_1", "state": "ON"}. |
Scaling the Build: Extend or Simplify
Once the baseline Android-to-Pi 3 relay hub is stable, you will inevitably need to adapt it to your specific environment. Here is how to modify the build without rewriting the core architecture.
How to Simplify (For Single-Device Testing)
If you don't need 4 channels and just want to test the Android MQTT link with a simple indicator, strip out the relay module entirely. Swap the hardware for a single 2N2222 NPN transistor, a 1kΩ base resistor, and a 5V active piezo buzzer. Connect the buzzer's positive leg to the Pi's 5V rail, the negative leg to the transistor's collector, the emitter to GND, and the base to GPIO 17 via the resistor. The exact same Python code will drive the transistor, allowing you to verify the Android MQTT payload logic without dealing with optocoupler isolation quirks.
How to Extend (Adding Remote Sensor Nodes)
The true power of MQTT is decoupling the controller from the sensor. To extend this build into a multi-room system, leave the Pi 3 and Android app exactly as they are. Add an ESP32-WROOM-32 node in another room running a PIR motion sensor. Program the ESP32 to publish to home/sensors/motion. Add a second subscription in your Pi 3 Python script:
client.subscribe('home/sensors/motion')
When the ESP32 detects motion, the Pi 3 receives the payload, processes it, and automatically toggles relay_1 while simultaneously publishing a state update back to the Android app via a secondary topic (e.g., home/pi3/relays/status). This turns your basic Android remote control into a fully automated, event-driven smart home edge server.
For deeper reading on the underlying protocols, refer to the Eclipse Mosquitto documentation for broker configuration, and the gpiozero OutputDevice API for advanced relay toggling patterns. Always consult the official Raspberry Pi hardware guides when calculating power draw limits on the 5V rail.






