The PZEM-004T v3.0 is a complete energy meter on one board: mains voltage, current through its CT, power, energy, frequency and power factor, sent over a 9600-baud TTL line. With a NodeMCU it becomes a Wi-Fi home energy monitor — useful for tracking a geyser, a pump or the whole flat’s load against the electricity slab.
The NodeMCU’s hardware UART stays on USB, so the PZEM talks over SoftwareSerial on D5 and D6, pins with no boot role. The PZEM’s TX is a 5 V signal and goes through a 1 kΩ / 2 kΩ divider.
Everything on the L, N and CT side is 230 V and electrician-only. Only the four TTL pins are wired on the bench.
Wiring
| NodeMCU ESP8266 | PZEM-004T v3.0 AC energy meter (100 A with CT) | Wire | Notes |
|---|---|---|---|
| — | L | — | To Mains live — 230 V, electrician only (not drawn) |
| — | N | — | To Mains neutral — 230 V, electrician only (not drawn) |
| — | CT1 | — | To Current-transformer lead (the CT clamps around the live wire only) (not drawn) |
| — | CT2 | — | To Current-transformer lead (other wire) (not drawn) |
| VIN | 5V | red | Powers the opto-isolated TTL side (5 V while the NodeMCU runs from USB). |
| GND | GND | black | TTL-side ground — isolated from mains by the optocouplers. |
| D5 | TX | yellow | Via 1 kΩ / 2 kΩ divider — PZEM TX idles at 5 V. 1 kΩ from TX to D5 (GPIO14), 2 kΩ from D5 to GND → about 3.3 V. |
| D6 | RX | green | GPIO12. 3.3 V from the NodeMCU drives the PZEM’s opto input. |
TTL side 5 V from your board; measuring side powered from the 230 V it measures part, 3.3 V board. The TTL side is opto-isolated and powered from your board. Its TX line is pulled up to whatever you feed the 5V pin: on ESP32/Pico, keep 5 V and put a 1 kΩ / 2 kΩ divider on PZEM TX → board RX (many v3.0 units also work with 3.3 V on the 5V pin). On Uno connect directly.
Board quirks
- L, N and the CT are a 230 V installation. An electrician should wire L/N from an MCB- and RCCB-protected circuit, with the earth intact, and pass only the live wire through the CT, with the module in a closed box. You wire only the four TTL pins, before mains is connected.
- The ESP8266 has one full UART, and it is wired to USB. Rather than swapping it away from the Serial Monitor, the sketch uses SoftwareSerial on D5 (RX) and D6 (TX) — at the PZEM’s 9600 baud that is reliable, even with Wi-Fi running.
- Why not D8 or D3/D4? The PZEM’s RX input idles HIGH through its opto LED. On D8 (GPIO15, must be LOW at boot) that stops the NodeMCU starting; D3 and D4 are boot pins too. D5/D6 have no boot role.
- The ESP8266 is not 5 V tolerant, so PZEM TX goes through the 1 kΩ / 2 kΩ divider. Many v3.0 boards also work with 3.3 V on their 5V pin (then no divider), but not all — 5 V plus the divider works on every board.
- The meter chip is powered from the mains side. On a bench without 230 V it never replies, even if the TTL LEDs blink.
- Energy (kWh) is stored inside the PZEM and survives power cuts. The CT must clamp the live wire alone — around a cable with live and neutral the currents cancel.
Code
// PZEM-004T v3.0 energy meter on a NodeMCU (ESP8266) over SoftwareSerial (Modbus-RTU, 9600 baud).
// Low-voltage side: 5V → VIN, GND → GND, PZEM TX → 1 kΩ/2 kΩ divider → D5 (GPIO14), PZEM RX ← D6 (GPIO12).
// L/N and the CT clamp are on 230 V — electrician only. The meter answers only while mains is connected.
// The hardware UART stays on USB for Serial; D5/D6 keep clear of the boot pins D3, D4 and D8.
#include <SoftwareSerial.h>
#include <PZEM004Tv30.h>
SoftwareSerial pzemSerial(D5, D6); // RX, TX
PZEM004Tv30 pzem(pzemSerial);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("PZEM-004T v3.0 on D5/D6 (SoftwareSerial)");
}
void loop() {
float v = pzem.voltage();
if (isnan(v)) {
Serial.println("No reply - is 230 V connected to L/N? Check TX/RX and the divider.");
} else {
Serial.printf("%.1f V %.3f A %.1f W %.3f kWh %.1f Hz PF %.2f\n", v, pzem.current(), pzem.power(),
pzem.energy(), pzem.frequency(), pzem.pf());
}
delay(2000); // also yields to the Wi-Fi stack
}// PZEM-004T v3.0 energy meter on a NodeMCU (ESP8266) over SoftwareSerial (Modbus-RTU, 9600 baud).
// Low-voltage side: 5V → VIN, GND → GND, PZEM TX → 1 kΩ/2 kΩ divider → D5 (GPIO14), PZEM RX ← D6 (GPIO12).
// L/N and the CT clamp are on 230 V — electrician only. The meter answers only while mains is connected.
// The hardware UART stays on USB for Serial; D5/D6 keep clear of the boot pins D3, D4 and D8.
#include <SoftwareSerial.h>
#include <PZEM004Tv30.h>
SoftwareSerial pzemSerial(D5, D6); // RX, TX
PZEM004Tv30 pzem(pzemSerial);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("PZEM-004T v3.0 on D5/D6 (SoftwareSerial)");
}
void loop() {
float v = pzem.voltage();
if (isnan(v)) {
Serial.println("No reply - is 230 V connected to L/N? Check TX/RX and the divider.");
} else {
Serial.printf("%.1f V %.3f A %.1f W %.3f kWh %.1f Hz PF %.2f\n", v, pzem.current(), pzem.power(),
pzem.energy(), pzem.frequency(), pzem.pf());
}
delay(2000); // also yields to the Wi-Fi stack
}Library: PZEM004Tv30 (Jakub Mandula) — compiled with PZEM004Tv30 1.2.1.
Expected serial output
PZEM-004T v3.0 on D5/D6 (SoftwareSerial)
229.8 V 1.104 A 243.6 W 3.512 kWh 50.0 Hz PF 0.96
229.6 V 1.101 A 242.9 W 3.512 kWh 50.0 Hz PF 0.96PZEM-004T v3.0 on D5/D6 (SoftwareSerial) 229.8 V 1.104 A 243.6 W 3.512 kWh 50.0 Hz PF 0.96 229.6 V 1.101 A 242.9 W 3.512 kWh 50.0 Hz PF 0.96
Common errors
“No reply” on every reading
Mains not connected to L/N (the meter chip needs it), TX/RX swapped, or the divider the wrong way round (1 kΩ on the PZEM side, 2 kΩ to GND). Also check it is a v3.0 — v1/v2 use a different protocol.
NodeMCU won’t boot with the PZEM connected
A PZEM wire is on D3, D4 or D8. Use D5 and D6 as shown. See boot mode (1,6).
Current reads 0 while voltage is right
The CT isn’t around the live wire alone, the load is off, or the CT leads are loose in CT1/CT2. Ask the electrician — don’t open the box with mains on.