When bridging 3.3V microcontroller logic with 120V or 240V AC mains, the physical insulation on your wires is your first line of defense against a catastrophic short. Misinterpreting the regional colour wiring code when terminating AC into an embedded power supply or relay block is the most common cause of lethal faults and fried low-voltage silicon in DIY smart home builds. This guide walks through building a robust, WiFi-controlled smart mains relay using an ESP32, with a strict focus on correctly applying mains wiring standards, isolating the control circuit, and debugging the inevitable firmware panics.
The Decision Path: IEC vs NEC Colour Wiring Code
Before stripping a single wire, you must determine which standard your local Authority Having Jurisdiction (AHJ) enforces. The international colour wiring code differs drastically from North American standards. Use the decision tree below to select the correct wire colours for the AC input terminals of the Hi-Link power supply and the Omron relay.
| Your Region | Standard | Live (Line) | Neutral | Earth (Ground) |
|---|---|---|---|---|
| UK, EU, Australia, NZ | IEC 60446 / AS/NZS 3000 | Brown | Blue | Green/Yellow |
| USA, Canada | NEC (NFPA 70) | Black | White | Green / Bare |
Concrete Pick for this Build: If you are building this in a region following IEC standards, terminate Brown to the 'L' (Live) terminal on the HLK-PM01 and the SSR, Blue to the 'N' (Neutral) terminal, and Green/Yellow strictly to the DIN-rail earth bus. Never switch the Neutral line; always switch the Live line to ensure the load is de-energized when off.
Hardware Spec Sheet & Parts List
To prevent the ESP32 from resetting due to AC switching noise, we use an isolated power supply and a zero-crossing solid-state relay (SSR). Mechanical relays cause contact bounce and massive inductive kickback that will brownout the 3.3V rail.
| Component | Exact Variant / Model | Purpose & Notes |
|---|---|---|
| Microcontroller | ESP32-WROOM-32 DevKit v1 (30-pin) | Must be the 30-pin variant. 38-pin variants have different GPIO mappings. |
| AC-DC PSU | Hi-Link HLK-PM01 (5V 600mA) | Provides isolated 5V to feed the ESP32's onboard 3.3V regulator. |
| Solid State Relay | Omron G3NA-210B (10A, Zero-Cross) | Zero-cross switching prevents high dV/dt EMI spikes. |
| Terminal Blocks | Phoenix Contact UTTB 2.5 (DIN-rail) | Use ferrules on all stranded mains wires before terminating. |
Pin Mapping & Mains Wiring Execution
Follow these numbered steps to wire the system. Ensure the ESP32 is completely disconnected from USB power during mains termination.
- Prepare Mains Wires: Strip 8mm of insulation from your Live, Neutral, and Earth wires. Crimp 2.5mm² bootlace ferrules onto the stranded copper. This prevents stray strands from bridging terminals.
- Earth First: Connect the Green/Yellow (or Green/Bare) Earth wire directly to the metal DIN-rail earth bus and the metal mounting tab of the Omron SSR (if applicable). Earth never passes through a switch or fuse.
- Power Supply AC In: Connect Live (Brown/Black) to the HLK-PM01 'L' pad and Neutral (Blue/White) to the 'N' pad. Use heat-shrink tubing over the solder joints or screw terminals to prevent accidental contact.
- SSR AC Load Switching: Connect the incoming Live wire to Omron SSR Terminal 1. Connect SSR Terminal 2 to the Live terminal of your target load (e.g., a lamp or appliance). Connect the load's Neutral directly to the incoming Neutral bus.
- Low Voltage Control: Wire the HLK-PM01 5V DC output to the ESP32 DevKit 'VIN' and 'GND' pins. Wire ESP32 GPIO 25 to the Omron SSR Control Input (+). Wire ESP32 GND to the SSR Control Input (-).
Complete ESP32 Control Firmware
This code targets the ESP32-WROOM-32 DevKit v1 (30-pin) using the Arduino IDE (ESP32 Core v3.x). It sets up a robust HTTP server to toggle the relay, includes a hardware watchdog timer (WDT) to recover from lockups, and handles WiFi connection timeouts gracefully.
#include <WiFi.h>
#include <WebServer.h>
#include <esp_task_wdt.h>
// --- PIN DEFINITIONS (ESP32 DevKit v1 30-pin) ---
#define PIN_SSR_CTRL 25 // GPIO 25: Safe output pin, no boot strapping issues
#define PIN_STATUS_LED 2 // GPIO 2: Onboard blue LED
// --- NETWORK CREDENTIALS ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
WebServer server(80);
bool relayState = false;
void handleRoot() {
String html = "<h1>Smart Mains Relay</h1>";
html += "<p>State: " + String(relayState ? "ON" : "OFF") + "</p>";
html += "<a href='/on'><button>Turn ON</button></a> ";
html += "<a href='/off'><button>Turn OFF</button></a>";
server.send(200, "text/html", html);
}
void turnOn() {
digitalWrite(PIN_SSR_CTRL, HIGH);
digitalWrite(PIN_STATUS_LED, HIGH);
relayState = true;
server.send(200, "text/plain", "Relay ON");
}
void turnOff() {
digitalWrite(PIN_SSR_CTRL, LOW);
digitalWrite(PIN_STATUS_LED, LOW);
relayState = false;
server.send(200, "text/plain", "Relay OFF");
}
void setup() {
Serial.begin(115200);
pinMode(PIN_SSR_CTRL, OUTPUT);
pinMode(PIN_STATUS_LED, OUTPUT);
digitalWrite(PIN_SSR_CTRL, LOW); // Fail-safe: Start OFF
// Initialize Task Watchdog Timer (3 seconds)
esp_task_wdt_init(3, true);
esp_task_wdt_add(NULL);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 20) {
delay(500);
Serial.print(".");
timeout++;
esp_task_wdt_reset(); // Feed the dog during blocking connect
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected! IP: " + WiFi.localIP().toString());
server.on("/", handleRoot);
server.on("/on", turnOn);
server.on("/off", turnOff);
server.begin();
} else {
Serial.println("\nWiFi Failed. Rebooting in safe mode.");
// Blink LED to indicate failure, then reboot
for(int i=0; i<5; i++) {
digitalWrite(PIN_STATUS_LED, HIGH); delay(100);
digitalWrite(PIN_STATUS_LED, LOW); delay(100);
}
ESP.restart();
}
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
server.handleClient();
}
esp_task_wdt_reset(); // Feed the watchdog in the main loop
delay(2); // Yield to RTOS background tasks
}
Debugging: SSR Chatter & Guru Meditation Errors
When integrating high-voltage switching with RF microcontrollers, EMI is your biggest enemy. If your ESP32 reboots randomly when the relay switches, or throws the following exact error in the Serial Monitor:
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)
Do not blindly increase the watchdog timeout. This error means the CPU is locked up, usually due to an interrupt storm or a severe voltage brownout. Here is the ranked cause list and the first three things to check:
- Check the 5V Rail Under Load (Brownout): When the SSR switches a heavy inductive load, it can pull the mains voltage down momentarily, causing the HLK-PM01 output to dip below 4.5V. The ESP32's AMS1117 drops out, causing a brownout reset. Fix: Add a 470µF electrolytic capacitor and a 0.1µF ceramic capacitor across the 5V DC output of the HLK-PM01.
- Verify SSR Type (EMI Spike): If you substituted the Omron G3NA-210B with a cheap, non-zero-crossing SSR (like a generic DC-DC or random-fire AC SSR), the dV/dt spike at turn-on will radiate EMI directly into the ESP32's antenna trace, corrupting the WiFi stack and triggering the WDT. Fix: You must use a zero-crossing SSR for AC loads.
- Check Neutral Continuity: A floating or high-resistance neutral on the AC side will cause the HLK-PM01 to behave erratically. Fix: Measure AC voltage between Live and Neutral at the terminal block under load; it should remain within 5% of nominal (e.g., 230V ± 11V).
How to Extend or Simplify the Build
Depending on your bench setup and end goal, you can scale this project up or down.
Simplify: The Safe Bench Mockup
If you are not ready to terminate mains voltage, drop the HLK-PM01 entirely. Power the ESP32 DevKit v1 via its micro-USB port using a standard 5V phone charger. Replace the Omron AC SSR with a 5V DC mechanical relay module (like the Songle SRD-05VDC-SL-C) to switch a 12V LED strip. This allows you to debug the WiFi stack, HTTP server, and GPIO logic with zero risk of electrocution.
Extend: Add Power Monitoring
To turn this into a smart energy monitor, add a YHDC SCT-013-000 (100A) split-core current transformer. Clamp it around the Live wire only (never clamp both Live and Neutral, or the fields cancel out). Wire the SCT-013's 3.5mm jack to ESP32 GPIO 35 (an ADC-capable input pin) with a 22Ω burden resistor and a 10kΩ/10kΩ voltage divider to bias the AC signal to 1.65V. Use the EmonLib library to calculate real-time RMS current and push the data via MQTT.
By respecting the physical NEC and IEC wiring standards and understanding the ESP32 GPIO hardware constraints, you bridge the gap between a fragile breadboard prototype and a reliable, jobsite-ready smart electrical device.






