Project Overview & Hardware Requirements
Connecting a custom Android app built in Android Studio to physical hardware is a rite of passage for embedded makers. While Wi-Fi and BLE are common, Bluetooth Classic (SPP) remains the most reliable, lowest-latency method for simple point-to-point control without the overhead of network stacks. This guide walks through building a Bluetooth-controlled relay using an Arduino and debugging the most notorious connection error in the Android Bluetooth API.
Build Specifications
- Target Board Variant: Arduino Uno R3 (ATmega328P)
- Difficulty Rating: Intermediate (Requires hardware voltage division and Android runtime permissions)
- Estimated Time: 90 minutes (Hardware: 30m, Firmware: 20m, Android Studio: 40m)
- Communication Protocol: UART over Bluetooth Classic (SPP)
Exact Parts List
| Component | Specific Variant / Model | Notes |
|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P) | Clone boards with CH340G USB IC work fine. |
| Bluetooth Module | HC-05 on ZS-040 breakout board | Must be HC-05 (master/slave), not HC-06 (slave only). |
| Relay Module | 5V Single-Channel Relay (Optocoupler) | Look for 'Low Level Trigger' or adjustable jumper. |
| Resistors | 1kΩ and 2kΩ (1/4W) | Mandatory for HC-05 RX pin voltage division. |
| Power | 5V 2A USB Power Supply | Relay coil draws ~70mA; Uno + HC-05 draws ~50mA. |
Pin Mapping and Wiring Diagram
The most common hardware failure in this build is frying the HC-05 module. The Arduino Uno outputs 5V logic on its TX pin, but the HC-05 RX pin is strictly 3.3V tolerant. You must use a voltage divider on the RX line.
| Arduino Uno Pin | HC-05 / Relay Pin | Wiring Notes |
|---|---|---|
| 5V | HC-05 VCC | Provides 5V to the module's onboard 3.3V regulator. |
| GND | HC-05 GND & Relay GND | Common ground is essential for logic reference. |
| D10 (Software TX) | HC-05 RX | Connect via voltage divider: 1kΩ from D10 to RX, 2kΩ from RX to GND. |
| D11 (Software RX) | HC-05 TX | Direct connection. HC-05 TX outputs 3.3V, which Uno reads as HIGH safely. |
| D8 | Relay IN (Signal) | Direct connection to the optocoupler input. |
Arduino Firmware: Bluetooth Serial Control
We use SoftwareSerial on pins 10 and 11. Using the hardware serial pins (0 and 1) for Bluetooth blocks USB programming and prevents you from using the Serial Monitor for debugging. The code below includes non-blocking serial reads and state validation to prevent relay chatter.
#include <SoftwareSerial.h>
// Pin Definitions
const int BT_RX_PIN = 11; // Arduino RX -> HC-05 TX
const int BT_TX_PIN = 10; // Arduino TX -> HC-05 RX (via voltage divider)
const int RELAY_PIN = 8; // Relay Signal Pin
// Initialize SoftwareSerial at default HC-05 baud rate
SoftwareSerial btSerial(BT_RX_PIN, BT_TX_PIN);
// State tracking
bool relayState = false;
unsigned long lastCommandTime = 0;
const unsigned long DEBOUNCE_MS = 200;
void setup() {
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH); // HIGH = OFF for most low-level trigger relays
Serial.begin(9600); // Hardware serial for USB debugging
btSerial.begin(9600); // Software serial for HC-05
Serial.println("System Ready. Waiting for Bluetooth commands...");
}
void loop() {
if (btSerial.available() > 0) {
char command = btSerial.read();
unsigned long currentTime = millis();
// Debounce to prevent rapid state toggling from buffer dumps
if (currentTime - lastCommandTime > DEBOUNCE_MS) {
lastCommandTime = currentTime;
processCommand(command);
}
}
}
void processCommand(char cmd) {
switch (cmd) {
case '1': // Turn ON
if (!relayState) {
digitalWrite(RELAY_PIN, LOW); // LOW = ON for low-level trigger
relayState = true;
btSerial.println("ACK:RELAY_ON");
Serial.println("Relay Engaged");
}
break;
case '0': // Turn OFF
if (relayState) {
digitalWrite(RELAY_PIN, HIGH);
relayState = false;
btSerial.println("ACK:RELAY_OFF");
Serial.println("Relay Disengaged");
}
break;
case '?': // Status Query
if (relayState) {
btSerial.println("STATUS:ON");
} else {
btSerial.println("STATUS:OFF");
}
break;
default:
// Ignore noise or unsupported characters
Serial.print("Ignored invalid byte: ");
Serial.println((int)cmd);
break;
}
}
Debugging the 'Socket Closed or Timeout' Error
When writing the Android Studio companion app in Kotlin or Java, you will inevitably encounter the most infamous exception in the Android Bluetooth API. If your app crashes or fails to connect, check your Logcat for this exact string:
java.io.IOException: read failed, socket might closed or timeout, read ret: -1
This error occurs when the Android device attempts to open an RFCOMM socket to the HC-05, but the connection drops immediately or the module rejects the handshake. Here are the first three things to check, ranked by probability:
1. Missing Android 12+ Runtime Permissions (Most Likely)
If you are targeting API 31 (Android 12) or higher in Android Studio, the legacy BLUETOOTH and BLUETOOTH_ADMIN permissions are no longer sufficient. You must explicitly request BLUETOOTH_CONNECT at runtime. If you don't, the socket creation silently fails and throws the timeout exception.
Fix: Add this to your AndroidManifest.xml:
<uses-permission android:name='android.permission.BLUETOOTH_CONNECT' />
And request it in your Activity using ActivityCompat.requestPermissions() before calling socket.connect().
2. Incorrect SPP UUID in Android Studio
The HC-05 uses the standard Serial Port Profile (SPP). If your Kotlin code uses a custom or randomly generated UUID in createRfcommSocketToServiceRecord(), the HC-05 will reject the connection.
Fix: Ensure your UUID exactly matches the standard SPP UUID:
val SPP_UUID: UUID = UUID.fromString('00001101-0000-1000-8000-00805F9B34FB')
3. HC-05 Baud Rate Mismatch or AT Mode Lock
If the HC-05 LED is blinking slowly (once every 2 seconds), it is in AT command mode, defaulting to 38400 baud. Your Arduino code and Android app are likely expecting 9600 baud. The socket connects, but the immediate data mismatch causes a timeout.
Fix: Ensure the KEY pin on the ZS-040 breakout is floating (disconnected) or LOW when powering on the module to force it into standard data mode (fast blinking LED, 9600 baud).
Scaling the Build: Extensions and Simplifications
Depending on your end goal, you may need to adjust the complexity of this bridge between Android Studio and Arduino.
btSerial.println() responses from the Arduino code. This eliminates the need for an input stream reader thread in Android Studio, reducing your Kotlin codebase by about 40%.
How to Extend: To control multiple peripherals or read sensors, abandon single-character commands. Implement a structured JSON or delimited string protocol (e.g., <RELAY1:ON,TEMP:24.5>). On the Arduino side, use a library like SerialTransfer or ArduinoJson to parse the payload reliably. On the Android side, use a background Service with a HandlerThread to manage the Bluetooth socket so the UI thread remains responsive.
Frequently Asked Questions
Can I connect Android Studio and Arduino Uno via USB OTG instead of Bluetooth?
Yes, but it requires a different approach. You can use a USB OTG (On-The-Go) cable to connect the Android phone directly to the Arduino's USB port. In Android Studio, you cannot use the standard Bluetooth API for this. Instead, you must use the Android USB Host API or a third-party library like usb-serial-for-android. This method provides much higher bandwidth and zero pairing friction, but it physically tethers the phone to the hardware and drains the phone's battery to power the Arduino.
Why does the HC-05 LED blink rapidly and refuse to pair with my phone?
A rapidly blinking LED (several times per second) indicates the HC-05 is in standard data mode and is discoverable. If your phone sees the device but fails to pair, or asks for a PIN and rejects '1234' or '0000', the module's internal EEPROM might be corrupted or set to a custom PIN. Connect the HC-05 to your PC via a USB-to-Serial adapter, pull the KEY pin HIGH before powering it on to enter AT mode, and send the command AT+PSWD=1234 to reset the pairing code.
How do I handle Android 14 background Bluetooth restrictions?
Android 14 introduced stricter limitations on background activity. If your Android Studio app needs to maintain the Arduino connection while the screen is off or the app is minimized, you must declare a foreground service with the connectedDevice type in your manifest. Without a persistent foreground notification, the Android OS will aggressively kill your Bluetooth socket thread to save battery, resulting in a silent disconnect on the Arduino side.






