If you are installing a standard 4500W, 240V electric water heater, the direct answer for the electric water heater wiring code is a 30-amp double-pole breaker and 10 AWG copper wire (either NM-B or THHN) with an equipment grounding conductor, per NEC Article 422.13. No neutral wire is required for standard residential tank heaters.

But knowing the code is only half the battle for modern makers. Once the high-voltage side is legally and safely terminated, many DIYers want to track energy consumption or build smart cutoffs. Shoving a 3.3V microcontroller into a 240V junction box is a fast track to a melted enclosure and a failed inspection. In this guide, we will cover the exact NEC sizing rules, then transition into building a code-compliant, isolated ESP32-based current monitor using a split-core transformer.

⚠️ MAINS VOLTAGE SAFETY WARNING: Working near a 240V water heater circuit involves lethal voltage. Always de-energize the circuit at the main panel, apply a lockout/tagout (LOTO) device, and verify the circuit is dead using a known-working CAT III or CAT IV multimeter or non-contact voltage tester before touching any conductors. Local codes may require a licensed electrician for panel work.

NEC Electric Water Heater Wiring Code Requirements

The National Electrical Code (NEC) treats water heaters as continuous loads if they are expected to run for three hours or more, though many local Authority Having Jurisdiction (AHJ) inspectors apply the 125% continuous load multiplier to all water heaters regardless of tank size to be safe. This means your breaker must be rated for at least 125% of the heater's maximum ampacity.

Here is the exact sizing matrix based on the 60°C ampacity column (which applies to NM-B cable per NEC 334.80) and standard 75°C rated breakers.

Heater Wattage Voltage Base Amps 125% Cont. Load Min Copper Wire Breaker Size NEC Reference
3000W 240V 12.5A 15.6A 14 AWG 20A (2-Pole) 422.13, 210.20
3800W 240V 15.8A 19.8A 12 AWG 20A (2-Pole) 422.13, 210.20
4500W 240V 18.75A 23.4A 10 AWG 30A (2-Pole) 422.13, 210.20
5500W 240V 22.9A 28.6A 10 AWG 30A (2-Pole) 422.13, 210.20

The 60°C vs 75°C Gotcha: Even though THHN wire in conduit is rated for 90°C, and most modern breakers have 75°C terminals, if you are using standard yellow NM-B (Romex) cable, NEC 334.80 strictly limits you to the 60°C ampacity column in NEC Table 310.16. In the 60°C column, 10 AWG is capped at exactly 30 amps, making it the perfect, code-compliant match for a 4500W heater on a 30A breaker.

Embedded Monitor: Parts List & Pin Mapping

To monitor this circuit without violating the electric water heater wiring code, we keep the microcontroller entirely isolated from the 240V lines. We use a Split-Core Current Transformer (CT) clamped around only one of the hot legs (Black or Red) outside the panel or in a dedicated, code-compliant junction box.

Target Board Variant: ESP32-WROOM-32 DevKit V1 (30-pin or 38-pin variant). We specifically target the ESP32 because its 12-bit ADC and built-in WiFi allow for high-resolution RMS current sampling and MQTT telemetry without external modules.

Bill of Materials

  • Microcontroller: ESP32-WROOM-32 DevKit V1
  • Current Sensor: SCT-013-000 (100A max, 50mA secondary output)
  • ADC Module (Optional but recommended): ADS1115 16-bit I2C ADC (The native ESP32 ADC is notoriously non-linear near 0V and 3.3V; the ADS1115 fixes this).
  • Burden Resistor: 33Ω (1/2W metal film) — See design note below.
  • Bias Resistors: 2x 10kΩ (Creates a 1.65V DC midpoint for the AC signal)
  • Capacitor: 10µF electrolytic (Stabilizes the 1.65V bias)
💡 The 3.3V Burden Resistor Trap: Most online guides for the SCT-013-000 tell you to use a 62Ω burden resistor. That is for 5V Arduinos. The ESP32 operates at 3.3V. A 62Ω resistor will generate a 3.1V peak AC signal, which clips on the ESP32's ADC and causes massive reading errors. Using a 33Ω resistor yields a 1.65V peak, perfectly centering the waveform in the ESP32's 0-3.3V ADC window.

Pin Mapping Table (Using Native ESP32 ADC)

Component ESP32 Pin Notes
CT Sensor Signal (via Burden) GPIO 34 (ADC1_CH6) Input only pin, no internal pull-up
CT Sensor DC Bias (1.65V) 3V3 Via 10kΩ voltage divider
System Ground GND Shared with bias divider bottom

Step-by-Step Build & Compilable Code

Before uploading code, wire the CT sensor circuit on a breadboard. The SCT-013-000 outputs AC current. The 33Ω burden resistor converts this to AC voltage. The two 10kΩ resistors create a 1.65V DC offset so the ESP32 can read the negative half of the AC wave (which would otherwise be clipped at 0V).

  1. Clamp the CT: Open the SCT-013 and clamp it around the Black (Line 1) 10 AWG wire only. Do not clamp it around the entire NM-B cable, or the magnetic fields from the two hot legs will cancel each other out, yielding a 0A reading.
  2. Wire the Bias: Connect the two 10kΩ resistors in series between 3V3 and GND. Connect the junction between them to one wire of the CT sensor.
  3. Wire the Burden: Connect the 33Ω resistor across the two CT sensor wires.
  4. Connect to ADC: Connect the CT wire that is not attached to the bias junction to ESP32 GPIO 34.

Below is the complete, compilable Arduino IDE C++ code. It uses the standard EmonLib library to calculate True RMS current. It includes WiFi connection error handling and serial debugging.

#include <WiFi.h>
#include <EmonLib.h>

// --- Network Configuration ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// --- Hardware Configuration ---
const int CT_PIN = 34;          // GPIO 34 (ADC1_CH6)
const double VOLTAGE = 240.0;   // Nominal line voltage
const double CALIBRATION = 29.5; // Calibrate this with a known load (Clamp meter)

EnergyMonitor emon1;

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("[System] ESP32 Water Heater Monitor Booting...");

  // Initialize EmonLib
  emon1.current(CT_PIN, CALIBRATION);

  // Connect to WiFi with timeout error handling
  Serial.print("[WiFi] Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  
  int timeout = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    timeout++;
    if (timeout > 40) { // 20 second timeout
      Serial.println("\n[ERROR] WiFi connection failed. Check SSID/Pass or signal.");
      Serial.println("[System] Rebooting in 5 seconds...");
      delay(5000);
      ESP.restart();
    }
  }
  
  Serial.println("\n[WiFi] Connected!");
  Serial.print("[WiFi] IP Address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  // Calculate Irms. 1480 samples takes roughly 1 second at 60Hz
  double Irms = emon1.calcIrms(1480); 
  
  // Filter out ghost voltages/noise when heater is off
  if (Irms < 0.20) {
    Irms = 0.0;
  }

  double apparentPower = Irms * VOLTAGE;

  Serial.print("[Data] Heater Current (A): ");
  Serial.print(Irms, 2);
  Serial.print(" | Apparent Power (W): ");
  Serial.println(apparentPower, 1);

  // Push to MQTT or HTTP endpoint here in a production build
  
  delay(2000); // Sample every 2 seconds to prevent serial buffer flooding
}

Debugging: "EmonLib ADC Saturation" & Zero Readings

When working with AC signals on microcontrollers, the most common failure mode is seeing the exact error string [EmonLib] ADC reading stuck at 0 in your serial monitor, or seeing wild, erratic spikes like Irms: 45.2A when the heater is physically off. According to the OpenEnergyMonitor technical documentation, this is almost always an analog front-end issue, not a code bug.

If your monitor fails to read correctly, here are the first three things to check:

  1. Verify the DC Bias Voltage: Unplug the ESP32. Use your multimeter to measure the voltage at GPIO 34 relative to GND. It should read exactly 1.65V (half of 3.3V). If it reads 0V or 3.3V, your 10kΩ voltage divider is wired incorrectly, and the ESP32 ADC is saturating against the rails.
  2. Check the CT Clamp Orientation: If your reading is exactly 0.00A while the heater is actively heating, you likely clamped the SCT-013 around the entire NM-B cable instead of stripping the jacket back to isolate a single hot conductor. The opposing magnetic fields of Line 1 and Line 2 cancel each other out perfectly.
  3. Inspect the Burden Resistor Value: If your readings are highly distorted or clipping at around 15A when the heater should be pulling 18.7A, you likely used a 62Ω resistor instead of the 33Ω resistor required for the ESP32's 3.3V logic. The ADC is clipping the peaks of the sine wave, ruining the RMS calculation.

Note on the ESP32 Native ADC: The Espressif ESP32 ADC documentation explicitly notes non-linearity at the extreme edges of the 0-3.3V range. If your low-current readings (under 2A) are noisy, upgrade to an external ADS1115 I2C ADC module, which provides true 16-bit linear resolution.

Extending or Simplifying the Build

Building this from scratch gives you total control over the sampling rate and data payload, but it requires careful analog circuit design. Depending on your goals, you may want to adjust the project scope.

How to Simplify (The Commercial Route)

If your primary goal is simply to view water heater energy usage in Home Assistant without soldering burden resistors, abandon the raw ESP32 build. Purchase a Shelly EM or Emporia Vue. These are UL-listed, code-compliant IoT energy monitors with pre-calibrated CT clamps and built-in WiFi. They mount cleanly on a DIN rail inside a code-compliant enclosure, entirely bypassing the analog design headaches while strictly adhering to the electric water heater wiring code regarding enclosure fill and wire bending space.

How to Extend (Adding MQTT & Safety Cutoffs)

To extend this DIY build into a full smart-home safety node:

  • Add MQTT: Integrate the PubSubClient library to publish the Irms and Apparent Power variables to a local Mosquitto broker. This allows Home Assistant to track exactly how many kWh the water heater uses daily.
  • Dry Leak Detection: Add a simple resistive water leak sensor (like the Grove Water Sensor) to GPIO 32. If water is detected on the floor, trigger an MQTT alert.
  • The Cutoff Problem: Makers often ask if they can use an ESP32 relay to turn off the water heater. Do not do this. Standard 30A mechanical relays will weld their contacts shut under the high inrush current of cold heating elements. If you must build a remote cutoff, use a 40A solid-state relay (SSR) rated for 240V AC, mounted on a massive heat sink, and ensure it is enclosed in a NEMA-rated box with proper THHN pigtails.

By respecting the 10 AWG / 30A baseline dictated by the NEC, and keeping your 3.3V logic strictly isolated via a split-core transformer, you get the best of both worlds: a safe, inspectable high-voltage installation and a highly granular embedded telemetry node.