To build a safe, reliable 120V smart lighting controller for home wiring projects, use an ESP32-WROOM-32 DevKit v1 (30-pin) paired with a 5V opto-isolated 4-channel relay module. The microcontroller switches the low-voltage DC side, while the relays switch the 120V AC line (black) conductor, keeping the neutral (white) continuous. This guide provides the exact pin mapping, NEC-style mains wiring procedures, and production-ready MQTT C++ code to get your smart home branch circuit online.

⚠️ DANGER: MAINS VOLTAGE HAZARD
This project involves wiring 120V AC branch circuits. Before touching any conductors, turn off the branch circuit breaker at the main panel, lock or tag the breaker, and verify the circuit is dead using a tested non-contact voltage tester or multimeter. Local electrical codes (NEC/NFPA 70) may require this work to be performed or inspected by a licensed electrician. Never bypass grounding or overcurrent protective devices.

Project Overview & Difficulty Rating

Bridging embedded logic with home electrical wiring requires respecting both domains. A common failure in DIY smart home wiring projects is treating 120V AC like low-voltage DC, leading to arcing, melted terminal blocks, or lethal shock hazards. This build uses opto-isolation to physically separate the 3.3V ESP32 logic from the 120V AC switching path, and routes mains wiring through proper wire nuts and terminal blocks rather than breadboards.

  • Difficulty: Intermediate (Requires soldering, mains wiring knowledge, and basic C++/Arduino IDE experience)
  • Time to Build: 2–3 hours
  • Target Board Variant: ESP32-WROOM-32 DevKit v1 (30-pin layout)
  • Estimated Cost: $35–$45 (excluding enclosure and hand tools)

Parts List & Spec Sheet

Do not substitute the relay module with a non-isolated version. The opto-isolator (typically a PC817) is your primary safety barrier between the microcontroller and the mains.

ComponentExact Model / SpecEst. Cost (2026)Notes
MicrocontrollerESP32-WROOM-32 DevKit v1 (30-pin)$7.00Avoid 38-pin variants; pinouts differ.
Relay Module4-Channel 5V Opto-Isolated (Songle SRD-05VDC-SL-C)$6.50Must have removable jumper for separate VCC.
Relay Power Supply5V 2A Switching PSU (Mean Well or generic)$5.00Required to prevent ESP32 brownouts.
Mains Wire14 AWG THHN (Black, White, Green)$8.00Use stranded for terminal blocks, solid for NM-B pigtails.
EnclosurePlastic DIN Rail Project Box (e.g., Stiles & May)$12.00Must be non-conductive or properly grounded.
ConnectorsLever-nuts (Wago 221) or wire nuts$4.00Wago 221s highly recommended for DIY enclosures.

Pin Mapping & Wiring Diagram

When designing embedded wiring projects, avoiding GPIO strapping pins is critical. The ESP32 uses GPIO 0, 2, 12, and 15 during boot. If you pull these high or low with relay coils, the ESP32 will fail to flash or boot into the wrong mode. We use GPIO 16, 17, 18, and 19 for the relays.

ESP32 PinRelay Module PinFunctionLogic State
GPIO 16IN1Relay 1 (Light Zone A)Active LOW
GPIO 17IN2Relay 2 (Light Zone B)Active LOW
GPIO 18IN3Relay 3 (Light Zone C)Active LOW
GPIO 19IN4Relay 4 (Light Zone D)Active LOW
GNDGNDShared Logic GroundN/A
VIN (5V)Do Not ConnectSee power note belowN/A
Pro-Tip: Relay Power Isolation
Remove the VCC/JDVCC jumper on the relay module. Connect the module's VCC to the external 5V PSU positive, and JDVCC to the ESP32's 3.3V or 5V pin (depending on module logic level, usually 5V for standard opto modules). Connect the external PSU GND to the relay module GND and ESP32 GND. This prevents the relay coil back-EMF and current draw from resetting the ESP32.

Step-by-Step Mains Wiring Procedure

Follow these steps to wire the 120V AC side. This assumes a standard US/NEC color code: Black (Line/Hot), White (Neutral), Bare/Green (Ground).

  1. Verify Dead Circuit: Confirm 0V AC between Line and Neutral, and Line and Ground using a multimeter.
  2. Bond the Ground: Connect the incoming bare/green ground wire to the enclosure's ground bus bar (if metal) or a Wago connector. Pigtail grounds to all connected loads. Never switch the ground conductor.
  3. Splice the Neutrals: Connect the incoming white neutral wire directly to the load neutrals using a Wago lever-nut. Relays only switch the line conductor; neutrals must remain continuous.
  4. Wire the Line (Hot): Connect the incoming black line wire to the Common (COM) terminal of each relay channel.
  5. Wire the Loads: Connect the black load wires (going to the light fixtures) to the Normally Open (NO) terminal of each relay channel.
  6. Torque and Tug: Ensure all terminal block screws are tight (approx. 0.5 Nm for small terminal blocks) and give each wire a firm tug test.

Complete MQTT Control Code

This code targets the ESP32-WROOM-32 DevKit v1. It uses the PubSubClient library for MQTT communication. It includes automatic WiFi and MQTT reconnection logic, and initializes relays in the OFF state (HIGH for active-low modules) to prevent lights from flashing on boot.

#include <WiFi.h>
#include <PubSubClient.h>

// --- BOARD & PIN DEFINITIONS (ESP32 DevKit v1 30-pin) ---
#define RELAY_1 16
#define RELAY_2 17
#define RELAY_3 18
#define RELAY_4 19

// --- NETWORK & MQTT CONFIG ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "192.168.1.100"; // Local Mosquitto broker IP
const int mqtt_port = 1883;

WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {
  delay(10);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  String msg = "";
  for (int i = 0; i < length; i++) msg += (char)payload[i];
  
  // Payload expected: "ON" or "OFF"
  int state = (msg == "ON") ? LOW : HIGH; // Active LOW relays

  if (String(topic) == "home/lighting/zone1") digitalWrite(RELAY_1, state);
  else if (String(topic) == "home/lighting/zone2") digitalWrite(RELAY_2, state);
  else if (String(topic) == "home/lighting/zone3") digitalWrite(RELAY_3, state);
  else if (String(topic) == "home/lighting/zone4") digitalWrite(RELAY_4, state);
}

void reconnect() {
  while (!client.connected()) {
    String clientId = "ESP32-Lighting-";
    clientId += String(random(0xffff), HEX);
    if (client.connect(clientId.c_str())) {
      client.subscribe("home/lighting/zone1");
      client.subscribe("home/lighting/zone2");
      client.subscribe("home/lighting/zone3");
      client.subscribe("home/lighting/zone4");
    } else {
      delay(5000); // Wait 5s before retrying
    }
  }
}

void setup() {
  Serial.begin(115200);
  
  // Initialize pins HIGH (OFF state for active-low relays) BEFORE setting pinMode
  digitalWrite(RELAY_1, HIGH);
  digitalWrite(RELAY_2, HIGH);
  digitalWrite(RELAY_3, HIGH);
  digitalWrite(RELAY_4, HIGH);
  
  pinMode(RELAY_1, OUTPUT);
  pinMode(RELAY_2, OUTPUT);
  pinMode(RELAY_3, OUTPUT);
  pinMode(RELAY_4, OUTPUT);

  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) reconnect();
  client.loop();
}

Debugging: Boot Failures & Connection Errors

When embedded wiring projects fail, the issue is usually power delivery or GPIO conflicts. Here is how to diagnose the two most common fatal errors.

Error 1: Brownout detector was triggered

Exact Serial Output: Brownout detector was triggered followed by a continuous reboot loop.

  • Cause 1 (Most Likely): Powering 4 relay coils directly from the ESP32's VIN/5V pin. The coil inrush current exceeds the USB cable or onboard regulator's capacity, dropping the 3.3V rail.
  • Cause 2: Using a low-quality, high-resistance USB cable or a 500mA phone charger.
  • Fix: Use a dedicated 5V 2A power supply for the relay module's VCC. Share the GND with the ESP32.

Error 2: E (1452) mqtt_client: Error transport connect

Exact Serial Output: E (xxxx) mqtt_client: Error transport connect (or PubSubClient state = -2).

  • Cause 1: MQTT broker IP is incorrect or the Mosquitto service is stopped on your server.
  • Cause 2: Firewall blocking port 1883, or broker is configured for TLS (8883) while code uses 1883.
  • Fix: Verify broker status (systemctl status mosquitto) and test connectivity from a PC using mosquitto_pub.
The First 3 Things to Check When It Fails:
  1. Power Isolation: Did you remove the VCC/JDVCC jumper on the relay board and supply external 5V?
  2. Strapping Pins: Are any relays wired to GPIO 0, 2, 12, or 15? Move them to 16-19.
  3. Logic Levels: Use a multimeter to verify the ESP32 GPIO pins are actually pulling down to ~0V when triggered. Some 5V relay modules require a logic-level MOSFET to trigger reliably from 3.3V GPIOs.

Extending and Simplifying the Build

To Simplify: If you only need to control a single lamp, drop the 4-channel module for a 1-channel 5V relay and use an ESP-01S module. The ESP-01S is cheaper ($2) and fits in tighter spaces, but requires an external USB-to-serial adapter for flashing and has fewer GPIOs (use GPIO 0 and GPIO 2, keeping them pulled HIGH on boot).

To Extend: Turn this into an energy-monitoring smart panel by adding an SCT-013-030 split-core current transformer. Clamp it around the main 14 AWG line conductor feeding the relays, connect the SCT-013's 3.5mm jack to an ESP32 ADC pin (via a voltage divider biasing circuit to center the 1V AC output at 1.65V DC), and use the EmonLib library to calculate real-time wattage and push it to Home Assistant via MQTT.

FAQ: Common Wiring Projects Questions

What wire gauge is required for 15A lighting wiring projects?

For a standard 15A residential lighting branch circuit protected by a 15A breaker, you must use a minimum of 14 AWG copper wire (NEC Article 240.4(D)). While 12 AWG is also acceptable and offers lower voltage drop over long runs, 14 AWG is the standard for lighting due to its flexibility and ease of termination in crowded smart-enclosures. Never use 16 AWG or smaller for mains branch wiring, regardless of the actual load draw of the LED fixtures.

Can I use an ESP8266 instead of an ESP32 for home wiring projects?

Yes, but with caveats. The ESP8266 (NodeMCU) has fewer GPIO pins, only one ADC channel, and is more susceptible to WiFi stack crashes when handling multiple simultaneous relay switching events. For a 1-channel or 2-channel smart switch, the ESP8266 is fine. For a 4-channel panel controller with MQTT telemetry, the ESP32's dual-core processor and dedicated hardware RTC make it significantly more reliable. Furthermore, the ESP32's 3.3V logic is generally more stable when driving opto-isolators than the ESP8266's occasionally noisy 3.3V rail.

How do I safely ground the enclosure in DIY smart wiring projects?

If you are using a metal enclosure (like a standard NEMA 1 junction box or a metal DIN rail box), the enclosure must be bonded to the equipment grounding conductor (the bare/green wire from your NM-B cable). Use a 10-32 green grounding screw tapped into the enclosure's designated hole, and pigtail the ground wire to it. If you are using a plastic/ABS project box (highly recommended for DIY embedded projects to eliminate shock risk), grounding the box itself is not required, but all internal metal components (like DIN rails) should still be bonded to the ground bus just in case a live wire comes loose and touches them.

References: Espressif ESP32 GPIO & Strapping Pin Documentation, NFPA 70: National Electrical Code (NEC).