Skip to the content.

MycilaPZEM

Latest Release PlatformIO Registry

License: MIT Contributor Covenant

Build GitHub latest commit Gitpod Ready-to-Code

Arduino / ESP32 library for the PZEM-004T power and energy monitor (v3 and v4).

Note on “v4”: Some sellers now market a PZEM-004T “v4”. In practice, this is the same device as v3 with updated electronic components; the protocol and behavior are identical. This library fully supports both v3 and v4.

✨ Highlights

🧭 Table of contents

🧰 About the hardware and compatibility

If your unit is labeled or sold as “v4”, treat it as a v3 for wiring, protocol, and features. No special configuration is required.

📦 Installation

PlatformIO (recommended)

Arduino IDE

Optional JSON support

🚀 Quick start

Recommended usage: non-blocking (async) with a callback

Mycila::PZEM pzem;

void setup() {
  pzem.begin(Serial1, 14, 27, MYCILA_PZEM_DEFAULT_ADDRESS, true); // async
  // React to fresh values as soon as they are read
  pzem.setCallback([](const Mycila::PZEM::EventType evt, const Mycila::PZEM::Data &data){
    if (evt == Mycila::PZEM::EventType::EVT_READ) {
      // Example: print a few metrics
      Serial.printf("V=%.2fV I=%.3fA P=%.1fW PF=%.2f\n", data.voltage, data.current, data.activePower, data.powerFactor);
    }
  });
}

void loop() {
  // no need to call read() in async mode
}

🧠 API overview (metrics and helpers)

The Data structure exposed by this library contains the following metrics and helpers:

/** Frequency in hertz (Hz). */
float frequency = NAN; // Hz

/** Voltage in volts (V). */
float voltage = NAN;

/** Current in amperes (A). */
float current = NAN;

/** Active power in watts (W). */
float activePower = NAN;

/** Power factor. */
float powerFactor = NAN;

/** Apparent power in volt-amperes (VA). */
float apparentPower = NAN;

/** Reactive power in volt-amperes reactive (VAr). */
float reactivePower = NAN;

/** Active energy in watt-hours (Wh). */
uint32_t activeEnergy = 0;

/** Compute THDi (assumes THDu = 0). */
float thdi(float phi = 0) const;

/** Compute load resistance (ohms). */
float resistance() const;

/** Compute dimmed voltage (V = P / I). */
float dimmedVoltage() const;

/** Compute nominal power (P = V^2 / R). */
float nominalPower() const;

🔌 Wiring

Typical ESP32 ↔ PZEM-004T wiring:

Notes:

Example pin mapping used in examples: RX=14, TX=27

🔗 Using multiple devices on one UART

This library supports multiple PZEM-004T devices on the same RX/TX port using PZEM addresses.

Basic steps:

  1. Connect the first PZEM-004T and set its address (use examples/SetAddress/SetAddress.ino).
  2. Disconnect it, connect the second one, and set a different address.
  3. Connect both devices to the same RX/TX port; read each by its address.

🔔 Callbacks and async mode

See examples/Callback and examples/CallbackAsync.

♻️ Energy reset

pzem.resetEnergy();

🏷️ Read and set address

pzem.readDeviceAddress();
pzem.readDeviceAddress(true); // will read the address and use it as the new address

pzem.setDeviceAddress(0x42);

🎯 Start a PZEM with a specific address

pzem.begin(Serial1, 14, 27, address);

🧾 JSON support (optional)

JSON is optional; the recommended way is async + callbacks. If you still need JSON serialization, enable: -D MYCILA_JSON_SUPPORT and include ArduinoJson:

#include <ArduinoJson.h>

🧪 Callback example

Reading a load for ~2 seconds after it is turned on:

  static Mycila::PZEM::Data prevData;
  pzem.setCallback([](const Mycila::PZEM::EventType eventType, const Mycila::PZEM::Data& data) {
    int64_t now = esp_timer_get_time();
    switch (eventType) {
      case Mycila::PZEM::EventType::EVT_READ:
        Serial.printf(" - %" PRId64 " EVT_READ\n", now);
        if (data != prevData) {
          Serial.printf(" - %" PRIu32 " EVT_CHANGE: %f V, %f A, %f W\n", millis(), data.voltage, data.current, data.activePower);
          prevData = data;
        }
        break;
      default:
        Serial.printf(" - %" PRId64 " ERR\n", now);
        break;
    }
  });
 - 227 EVT_CHANGE: 236.199997 V, 0.000000 A, 0.000000 W
 - 287749 EVT_READ
 - 347736 EVT_READ
 - 407822 EVT_READ
 - 468011 EVT_READ
 - 527993 EVT_READ
 - 588081 EVT_READ
 - 648269 EVT_READ
 - 708261 EVT_READ
 - 768343 EVT_READ
 - 828434 EVT_READ
 - 888633 EVT_READ
 - 948717 EVT_READ
 - 1008799 EVT_READ
 - 1068889 EVT_READ
 - 1128980 EVT_READ
 - 1189069 EVT_READ
 - 1249167 EVT_READ
 - 1249 EVT_CHANGE: 233.399994 V, 2.262000 A, 497.899994 W
 - 1309258 EVT_READ
 - 1369438 EVT_READ
 - 1434762 EVT_READ
 - 1494647 EVT_READ
 - 1554730 EVT_READ
 - 1614816 EVT_READ
 - 1674910 EVT_READ
 - 1674 EVT_CHANGE: 233.399994 V, 2.262000 A, 497.899994 W
 - 1734995 EVT_READ
 - 1795086 EVT_READ
 - 1855168 EVT_READ
 - 1915365 EVT_READ
 - 1975447 EVT_READ
 - 2035538 EVT_READ
 - 2095633 EVT_READ
 - 2155718 EVT_READ
 - 2215800 EVT_READ
 - 2275891 EVT_READ
 - 2335982 EVT_READ
 - 2396071 EVT_READ
 - 2461382 EVT_READ
 - 2521268 EVT_READ
 - 2581354 EVT_READ
 - 2641551 EVT_READ
 - 2701644 EVT_READ
 - 2701 EVT_CHANGE: 233.899994 V, 2.261000 A, 539.500000 W
 - 2761619 EVT_READ
 - 2821705 EVT_READ
 - 2881902 EVT_READ
 - 2941987 EVT_READ
 - 3002075 EVT_READ
 - 3062161 EVT_READ
 - 3122255 EVT_READ
 - 3182440 EVT_READ
 - 3242428 EVT_READ
 - 3302514 EVT_READ
 - 3362608 EVT_READ
 - 3422690 EVT_READ
 - 3488113 EVT_READ
 - 3547891 EVT_READ
 - 3608088 EVT_READ
 - 3668170 EVT_READ
 - 3728260 EVT_READ
 - 3788346 EVT_READ
 - 3848441 EVT_READ
 - 3908634 EVT_READ
 - 3908 EVT_CHANGE: 236.600006 V, 0.000000 A, 0.000000 W

📈 Performance notes

Here are below some test results for the PZEM for 50 consecutive reads on a nominal load of about 650W, controlled with a random SSR relay (0-100%).

PerfTest1

pzem.read():
 - Errors: 0
 - Average read time: 120433 us
 - Min read time: 107118 us
 - Max read time: 126020 us

PerfTest2

pzem.read():
 - ROUND: 1
   * Load Detection time: 1207002 us (10 reads)
   * Ramp up time: 2534171 us (21 reads)
   * Ramp down time: 2537724 us (21 reads)
 - ROUND: 2
   * Load Detection time: 1328073 us (11 reads)
   * Ramp up time: 2655242 us (22 reads)
   * Ramp down time: 2539094 us (21 reads)
 - ROUND: 3
   * Load Detection time: 1206870 us (10 reads)
   * Ramp up time: 2534040 us (21 reads)
   * Ramp down time: 2537968 us (21 reads)
 - ROUND: 4
   * Load Detection time: 1328062 us (11 reads)
   * Ramp up time: 2655334 us (22 reads)
   * Ramp down time: 2538888 us (21 reads)

The “Load Detection time” is the time it takes for the PZEM to see a load > 0 just after the relay was closed on a constant resistive load.

The “Ramp up time” is the time it takes for the PZEM to stabilize (with a maximum delta of 1% from last measurement) and see an expected load value.

The “Ramp down time” is the time it takes for the PZEM to return to 0W after the relay was opened.

You can run the PerfTests from the examples. The numbers might change a little but the order of magnitude should stay the same.

What these results mean ?

The PZEM likely applies a moving-average-like window of ~2 seconds with updates every 1 second.

🛠️ Troubleshooting

No data / timeouts

CRC errors / noise

Address issues

Multiple devices on one UART

CT wiring pitfalls

📚 Reference material

📁 Examples

Browse the examples/ folder for ready-to-run sketches:

📜 License and conduct