The standard North American electrical color wire code for 120/240V split-phase AC power is Black (Line 1), Red (Line 2), White (Neutral), and Green or Bare Copper (Equipment Ground). When integrating embedded microcontrollers like an ESP32 into mains-powered smart home projects, confusing low-voltage logic jumper colors with high-voltage THHN conductor colors is a fatal mistake. A single misrouted 120V black wire into a 3.3V GPIO pin will instantly vaporize your microcontroller and pose a severe shock hazard.
This guide bridges the physical requirements of the National Electrical Code (NEC) with embedded firmware design. We will build a dual-channel ESP32 smart relay controller designed to switch 240V split-phase loads (like a baseboard heater or well pump), strictly adhering to mains color codes while implementing robust firmware error handling to prevent brownouts and relay chatter.
The North American Electrical Color Wire Code Matrix
Before stripping a single wire, you must map your physical conductors to their NEC-mandated functions. The NFPA 70 (National Electrical Code) strictly governs these colors to ensure any electrician can safely service the panel. For embedded projects, we also map the required isolation boundary.
| Wire Color | NEC Function | Split-Phase Voltage | Insulation / Gauge | ESP32 Isolation Rule |
|---|---|---|---|---|
| Black | Line 1 (Hot / Ungrounded) | 120V to Neutral | 12 AWG THHN (20A) | Never connect to logic. Route through relay NO/COM. |
| Red | Line 2 (Hot / Ungrounded) | 120V to Neutral / 240V to L1 | 12 AWG THHN (20A) | Never connect to logic. Route through relay NO/COM. |
| White | Neutral (Grounded Conductor) | 0V Nominal (Carries return current) | 12 AWG THHN (20A) | Must not be shared with logic DC ground. |
| Green / Bare | Equipment Ground (EGC) | 0V (Fault path only) | 12 AWG Bare/Green | Can bond to metal DIN rail, but keep away from 3.3V traces. |
| Blue / Yellow | Travelers / Switch Legs | 120V (Switched) | 14/12 AWG THHN | Used for 3-way physical overrides; treat as live. |
Pro-Tip: Never use white or green Dupont jumpers for your 3.3V logic signals. If a mains wire nut fails and a white neutral wire drops onto your breadboard, a white logic jumper makes it visually difficult to trace the fault. Use distinct colors like orange, purple, or blue for your low-voltage I2C and GPIO lines.
Parts List & Hardware Pin Mapping
This build targets the ESP32-WROOM-32 (30-pin DevKit V1). We are using a 5V relay module with optocouplers to maintain galvanic isolation between the 120/240V AC side and the 3.3V DC logic side.
Bill of Materials (BOM)
- Microcontroller: ESP32-WROOM-32 DevKit V1 (30-pin, Type-C or Micro-USB)
- Relay Module: 2-Channel 10A Relay Module with Optocoupler (5V coil, Omron G5LE-14 DC5)
- Mains Wiring: 12 AWG THHN Copper (Black, Red, White, Green) - approx. $0.15/ft
- Logic Wiring: 22 AWG stranded silicone wire (Orange, Purple, Brown)
- Power Supply: 5V 2A USB-C wall adapter (Hi-Link HLK-PM01 for internal panel mounting)
- Enclosure: NEMA 1 ABS plastic project box with DIN rail
ESP32 to Relay Pin Mapping
| ESP32 GPIO | Relay Module Pin | Wire Color (Logic) | Function |
|---|---|---|---|
| GPIO 26 | IN1 | Orange | Relay 1 Control (Line 1 Switching) |
| GPIO 27 | IN2 | Purple | Relay 2 Control (Line 2 Switching) |
| 5V (VIN) | VCC | Red (22AWG) | Relay Coil Power |
| GND | GND | Brown | Logic Ground Reference |
Step-by-Step Wiring & Safety Protocol
- Prepare the Enclosure: Mount the DIN rail inside the NEMA 1 box. Install terminal blocks for the 12 AWG THHN wires. Do not mount the ESP32 directly next to the AC terminal blocks; maintain at least a 1-inch physical air gap or use a plastic isolation barrier.
- Wire the Mains Side (AC): Strip 1/2 inch of insulation from your 12 AWG Black and Red wires. Crimp on ferrules before inserting them into the relay's Normally Open (NO) and Common (COM) screw terminals. Torque the relay screws to 0.5 Nm to prevent cold joints and arcing. Route the White (Neutral) and Green (Ground) directly to their respective isolated terminal blocks, bypassing the relay entirely.
- Wire the Logic Side (DC): Connect your 22 AWG orange and purple wires to GPIO 26 and 27. Critical Step: On the relay module, locate the jumper cap labeled "VCC to JD-VCC". Remove this jumper. This activates the optocoupler isolation, ensuring the relay coil's back-EMF does not feed back into the ESP32's fragile 3.3V regulator.
- Power the Relay Coils: Feed 5V from the ESP32's VIN pin to the relay module's VCC (with the jumper removed, JD-VCC is powered separately via the module's onboard 5V input, or you power the module's JD-VCC from a dedicated 5V source while sharing the GND). For simplicity on low-current coils, powering VCC from ESP32 5V and GND to ESP32 GND works, provided the optocoupler jumper is removed.
- Verify with a Multimeter: Before applying AC power, use your multimeter's continuity mode. Check between the ESP32 GND pin and the AC Green ground wire. You should read OL (Open Loop). If you read near 0 ohms, your logic and mains grounds are bonded, creating a massive shock hazard. Fix it before proceeding.
Complete ESP32 Firmware with Error Handling
The following C++ code is written for the Arduino IDE (ESP32 board package v2.0.x or v3.0.x). It includes non-blocking WiFi connection handling, active-low relay logic, and a safe-state fallback if the network drops. The relays are defined as ACTIVE_LOW, meaning a LOW signal energizes the coil, and HIGH de-energizes it. We initialize them as HIGH (OFF) in setup to prevent loads from triggering during the ESP32 boot sequence.
#include <WiFi.h>
// --- Pin Definitions ---
#define RELAY_L1_PIN 26
#define RELAY_L2_PIN 27
#define STATUS_LED 2 // Built-in LED on most DevKit V1 boards
// --- Network Credentials ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// --- System State ---
bool wifiConnected = false;
unsigned long lastReconnectAttempt = 0;
const unsigned long reconnectInterval = 10000; // 10 seconds
void setupRelays() {
pinMode(RELAY_L1_PIN, OUTPUT);
pinMode(RELAY_L2_PIN, OUTPUT);
pinMode(STATUS_LED, OUTPUT);
// Relays are Active LOW. HIGH = OFF (Safe State)
digitalWrite(RELAY_L1_PIN, HIGH);
digitalWrite(RELAY_L2_PIN, HIGH);
Serial.println("[SYSTEM] Relays initialized to SAFE (OFF) state.");
}
void connectWiFi() {
Serial.print("[WIFI] Connecting to ");
Serial.print(ssid);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
Serial.println("\n[WIFI] Connected. IP: " + WiFi.localIP().toString());
} else {
wifiConnected = false;
Serial.println("\n[ERROR] WiFi connection failed. Relays remain OFF.");
}
}
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to attach
Serial.println("\n--- ESP32 Split-Phase Relay Controller ---");
setupRelays();
connectWiFi();
}
void loop() {
// Non-blocking WiFi watchdog
if (!wifiConnected) {
if (millis() - lastReconnectAttempt >= reconnectInterval) {
lastReconnectAttempt = millis();
Serial.println("[WIFI] Attempting reconnect...");
connectWiFi();
}
} else {
if (WiFi.status() != WL_CONNECTED) {
wifiConnected = false;
Serial.println("[ERROR] WiFi dropped. Forcing relays to SAFE state.");
digitalWrite(RELAY_L1_PIN, HIGH);
digitalWrite(RELAY_L2_PIN, HIGH);
}
}
// Status LED feedback
digitalWrite(STATUS_LED, wifiConnected ? HIGH : LOW);
// --- Insert Application Logic Here ---
// Example: Turn on L1 if temperature < 65F
// if (tempSensor.read() < 65.0 && wifiConnected) {
// digitalWrite(RELAY_L1_PIN, LOW); // Energize (ON)
// }
delay(100); // Small yield to prevent watchdog triggers
}
Debugging: The "Brownout Detector" Error
When mixing inductive AC loads with 3.3V logic, the most common failure mode is not a blown fuse, but a microcontroller reset. If your ESP32 randomly reboots when the relay clicks, and your serial monitor spits out the following exact error string:
Brownout detector was triggered
abort() was called at PC 0x40081234 on core 0
Backtrace: 0x40081234:0x3ffb1234 ...
This means the voltage on the 3.3V rail dropped below ~2.4V for a few microseconds, triggering the ESP32's internal hardware protection. According to the Espressif ESP32 Datasheet, the brownout detector is highly sensitive to sudden current spikes.
The First Three Things to Check When It Fails:
- Missing Flyback Diode: When the relay coil de-energizes, the collapsing magnetic field generates a massive reverse voltage spike (back-EMF). If your relay module lacks a flyback diode across the coil terminals, this spike travels back through the 5V rail and collapses the ESP32's onboard AMS1117-3.3 voltage regulator. Fix: Verify your module has a 1N4148 or 1N4007 diode soldered in reverse bias across the coil pins.
- Optocoupler Jumper Installed: If the "JD-VCC" jumper cap is still on the relay module, the high-current relay coil shares the exact same power rail as the ESP32 logic. The inrush current of the coil (approx. 70mA) causes a momentary voltage sag on the USB 5V line. Fix: Remove the jumper to engage the optical isolation barrier.
- Undersized USB Cable: Cheap, thin USB cables have high resistance. When the relay pulls 70mA, the voltage drop across a 28-AWG USB cable can pull the 5V input at the ESP32 down to 4.2V, which is below the dropout voltage of the 3.3V regulator. Fix: Use a heavy-duty 20-AWG USB cable or power the ESP32 via the 5V/GND header pins using a dedicated 5V 2A buck converter.
Extending and Simplifying the Build
How to Extend: Add Current Monitoring
To verify that the load is actually drawing power (and detect a tripped breaker or burnt heating element), add a SCT-013-000 (100A/50mA) split-core Current Transformer (CT). Clamp the CT only around the Black (Line 1) or Red (Line 2) THHN wire. Never clamp it around the White neutral or the Romex outer jacket, as the opposing magnetic fields will cancel out and read zero. Feed the CT's 3.5mm jack into an analog front-end circuit (using a 10k burden resistor and 10uF decoupling capacitor) and read the waveform via the ESP32's ADC (GPIO 34) using the EmonLib library.
How to Simplify: The Pre-Isolated Alternative
If stripping 12 AWG THHN wire and managing the electrical color wire code inside a custom NEMA enclosure feels outside your comfort zone, simplify the hardware. Purchase a commercially certified smart plug (like a Sonoff S31 or Shelly Plug US) that supports custom firmware flashing via a serial header. This delegates all mains isolation, relay arc suppression, and NEC color-code wiring to the factory, allowing you to focus purely on writing the ESP8266/ESP32 C++ logic and integrating it with Home Assistant via MQTT.
Whether you build from bare THHN wire or flash a commercial plug, respecting the boundary between high-voltage AC conductors and low-voltage logic is the defining line between a reliable smart home device and a hazardous failure. Always verify your wiring with a meter before applying power.






