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

ComponentSpecific Variant / ModelNotes
MicrocontrollerArduino Uno R3 (ATmega328P)Clone boards with CH340G USB IC work fine.
Bluetooth ModuleHC-05 on ZS-040 breakout boardMust be HC-05 (master/slave), not HC-06 (slave only).
Relay Module5V Single-Channel Relay (Optocoupler)Look for 'Low Level Trigger' or adjustable jumper.
Resistors1kΩ and 2kΩ (1/4W)Mandatory for HC-05 RX pin voltage division.
Power5V 2A USB Power SupplyRelay 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.

Safety Callout: If your relay module is switching mains voltage (120V/240V AC), de-energize the circuit, verify dead with a multimeter, and ensure all high-voltage terminals are enclosed in an insulated junction box. Never leave mains terminals exposed on a breadboard.
Arduino Uno PinHC-05 / Relay PinWiring Notes
5VHC-05 VCCProvides 5V to the module's onboard 3.3V regulator.
GNDHC-05 GND & Relay GNDCommon ground is essential for logic reference.
D10 (Software TX)HC-05 RXConnect via voltage divider: 1kΩ from D10 to RX, 2kΩ from RX to GND.
D11 (Software RX)HC-05 TXDirect connection. HC-05 TX outputs 3.3V, which Uno reads as HIGH safely.
D8Relay 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.

How to Simplify: If you only need one-way communication (Android sending commands, no ACK required), strip the 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.