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
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 |
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 theloop()function), preventing theserver.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 yourAndroidManifest.xml. For a production IoT app, you should instead implement TLS on the ESP8266 usingWiFiClientSecure, 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
ESP8266WebServerwith thePubSubClientMQTT 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
/statusendpoint on the ESP8266 that returns JSON, and poll it via an AndroidWorkManagerbackground 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.hin your firmware and setMDNS.begin("esp-relay"). You can then targethttp://esp-relay.localin 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.






