Integrating an Android Studio ESP8266 workflow requires bridging two entirely different ecosystems: the bare-metal C++ environment of the ESP8266 and the JVM-based Kotlin environment of Android. The most reliable, low-latency approach for local network control is running a lightweight HTTP server on the ESP8266 and triggering it via asynchronous HTTP GET requests from your Android app. This guide walks through building a Wi-Fi relay controller using a Wemos D1 Mini, detailing the exact hardware constraints, compilable firmware, Kotlin coroutine networking code, and the specific error strings you will encounter when the connection fails.

Project Overview & Hardware Spec Sheet

Difficulty Rating: Intermediate (Requires basic mains wiring safety if switching AC loads)
Estimated Time: 2 hours (Hardware + Firmware + App UI)
Estimated Cost: $8 - $12 USD (Microcontroller + Relay module)

A common beginner mistake is wiring a standard 5V relay coil directly to an ESP8266 GPIO pin. The ESP8266EX GPIO pins operate at 3.3V logic and can safely source a maximum of 12mA. A standard 5V Songle relay coil requires roughly 70mA to pull in the contactor. Attempting to drive this directly will cause a brownout, resetting the ESP8266, or permanently damage the silicon. The solution is using a relay module with an optocoupler and a separate power jumper (JD-VCC), which isolates the coil current from the ESP's logic pin.

Hardware & Network Specifications

Parameter Wemos D1 Mini (ESP8266EX) 5V Optocoupler Relay Module
Operating Voltage 3.3V Logic (5V USB Input) 5V DC (Coil), up to 250VAC (Load)
Logic Trigger Level 3.3V HIGH / 0V LOW 3.3V compatible (Optocoupler LED draws ~2mA)
Max GPIO Current 12mA continuous (Safe operating area) N/A (Draws 70mA from JD-VCC 5V rail)
Wi-Fi / Network 802.11 b/g/n (2.4GHz only, WPA2) N/A
Flash Memory 4MB (Typical for v3.1 clones) N/A

ESP8266 Firmware: Pin Mapping & Compilable Code

This code targets the Wemos D1 Mini (ESP8266EX, 4MB flash). In the Arduino IDE, select "LOLIN(WEMOS) D1 R2 & mini" as your board variant. The firmware uses the ESP8266WebServer library to expose two endpoints: /relay/on and /relay/off.

Pin Mapping Table

Wemos D1 Mini Pin ESP8266 GPIO Connected To Purpose
5V VBUS Relay JD-VCC Provides 5V power to the relay coil
G GND Relay GND Common ground reference
D1 GPIO5 Relay IN 3.3V logic trigger for optocoupler
⚠️ Mains Voltage Warning: If you are switching 120V/240V AC mains with the relay's COM/NO terminals, de-energize the circuit at the breaker, verify dead with a multimeter, and ensure all AC connections are inside a rated junction box. Never leave mains connections exposed on a breadboard.

Compilable C++ Firmware

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// Network credentials
const char* ssid = "YOUR_2.4GHZ_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// Pin definition (GPIO5 is D1 on Wemos D1 Mini)
const int RELAY_PIN = 5; 

ESP8266WebServer server(80);

void handleRelayOn() {
  digitalWrite(RELAY_PIN, LOW); // Most optocoupler relays are Active LOW
  server.send(200, "text/plain", "RELAY_ON");
}

void handleRelayOff() {
  digitalWrite(RELAY_PIN, HIGH);
  server.send(200, "text/plain", "RELAY_OFF");
}

void handleNotFound() {
  server.send(404, "text/plain", "404: Endpoint not found");
}

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH); // Start in OFF state

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  
  Serial.print("Connecting to WiFi");
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 40) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nConnected! IP address: " + WiFi.localIP().toString());
  } else {
    Serial.println("\nWiFi connection failed. Rebooting...");
    ESP.restart();
  }

  server.on("/relay/on", HTTP_GET, handleRelayOn);
  server.on("/relay/off", HTTP_GET, handleRelayOff);
  server.onNotFound(handleNotFound);
  server.begin();
}

void loop() {
  server.handleClient();
  
  // Auto-reconnect logic if Wi-Fi drops
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Wi-Fi lost. Reconnecting...");
    WiFi.reconnect();
    delay(2000);
  }
}

Android Studio Integration: Kotlin HTTP Client

On the Android Studio side, you must execute network requests off the main UI thread. Attempting to run a blocking HTTP call on the main thread will immediately throw a NetworkOnMainThreadException and crash the app. We use Kotlin Coroutines with Dispatchers.IO and the built-in HttpURLConnection class to keep the Gradle dependencies minimal (no need for Retrofit or OkHttp for simple GET requests).

Ensure your AndroidManifest.xml includes the internet permission:

<uses-permission android:name="android.permission.INTERNET" />

Kotlin Network Function

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.HttpURLConnection
import java.net.URL

/**
 * Sends an HTTP GET request to the ESP8266.
 * @param ip The local IP address of the ESP8266 (e.g., "192.168.1.50")
 * @param state "on" or "off"
 * @return Boolean indicating success (HTTP 200)
 */
suspend fun sendEsp8266Command(ip: String, state: String): Boolean {
    return withContext(Dispatchers.IO) {
        try {
            val url = URL("http://$ip/relay/$state")
            val conn = url.openConnection() as HttpURLConnection
            conn.requestMethod = "GET"
            conn.connectTimeout = 2000 // 2 second timeout for local LAN
            conn.readTimeout = 2000
            
            val responseCode = conn.responseCode
            conn.disconnect()
            
            responseCode == 200
        } catch (e: Exception) {
            // Log the specific exception here in your actual app
            e.printStackTrace()
            false
        }
    }
}

Debugging Connection Failures: The "First Three" Checklist

When your Android app fails to toggle the relay, the Android Studio Logcat will throw specific exceptions. Here are the first three things to check, mapped to the exact error strings you will see.

1. The "Failed to Connect" Error

Exact Error String: java.net.ConnectException: Failed to connect to /192.168.1.50:80

  • Cause A (Most Likely): The Android phone and the ESP8266 are not on the same subnet. This happens frequently if your phone is on a 5GHz Wi-Fi network with AP Isolation enabled, or connected to cellular data instead of the local Wi-Fi.
  • Cause B: The ESP8266 failed to get a DHCP lease and fell back to an APIPA address (169.254.x.x), or your router assigned it a different IP than the one hardcoded in your Android app.
  • Fix: Ping the ESP's IP from a laptop on the same network. Check the ESP8266 Serial Monitor at 115200 baud to verify its actual assigned IP address upon boot.

2. The "Socket Timeout" Error

Exact Error String: java.net.SocketTimeoutException: connect timed out

  • Cause A: The ESP8266 is powered and on the network, but the Wi-Fi radio is experiencing heavy 2.4GHz interference, causing packet drops.
  • Cause B: The ESP8266 firmware has crashed or is stuck in a blocking loop (e.g., a delay() call longer than 100ms inside the loop() function), preventing the server.handleClient() from processing the TCP handshake in time.
  • Fix: Ensure your C++ loop() is non-blocking. Move the ESP8266 closer to the router to rule out RF interference.

3. The "Cleartext Traffic" Error (Android 9+)

Exact Error String: java.io.IOException: Cleartext HTTP traffic to 192.168.1.50 not permitted

  • Cause: Starting with Android 9 (API level 28), cleartext (HTTP) traffic is disabled by default. The ESP8266 is serving plain HTTP, not HTTPS.
  • Fix: Add android:usesCleartextTraffic="true" to the <application> tag in your AndroidManifest.xml. For a production IoT app, you should instead implement TLS on the ESP8266 using WiFiClientSecure, though this consumes significantly more RAM.

Extending and Simplifying the Build

Depending on your end goal, you may want to scale this project up for production or strip it down for a quick weekend prototype.

How to Extend the Build

  • Switch to MQTT: HTTP polling and direct IP targeting don't scale well if you want to control the device outside your local network or integrate it with Home Assistant. Replace the ESP8266WebServer with the PubSubClient MQTT library, and use Eclipse Paho MQTT in Android Studio. This allows communication via a cloud broker (like HiveMQ or Mosquitto) without port forwarding.
  • Add State Feedback: The current HTTP setup is "fire and forget." To make the Android app reflect the actual state of the relay (e.g., if someone pressed a physical button), add a /status endpoint on the ESP8266 that returns JSON, and poll it via an Android WorkManager background task.

How to Simplify the Build

  • Use mDNS (Multicast DNS): Hardcoding IP addresses in your Kotlin app is fragile because routers frequently reassign DHCP leases. Include ESP8266mDNS.h in your firmware and set MDNS.begin("esp-relay"). You can then target http://esp-relay.local in your Android app (requires Android's NsdManager to resolve the hostname).
  • Skip Android Studio Entirely: If you don't actually need a custom native UI and just want phone control, flash the ESP8266 with ESPHome or Tasmota. You can then control it via a web browser or the Home Assistant companion app, saving you the overhead of maintaining a Kotlin codebase.

For deeper technical specifications on the ESP8266's RF and memory constraints, refer to the Espressif ESP8266EX Datasheet. For Android networking best practices and coroutine implementations, consult the official Android Developer Network Operations Guide.