Measure mains voltage with ZMPT101B and ADS1115

What would be the most common/straightforward approach to integrate the ZMPT101B sensor into HA (through ADS1115)?

I found this Zmpt101b Precision Voltage Sensor Module - #7 by VdR and Zmpt101b Precision Voltage Sensor Module - #13 by mupsje

The latest looks more complex to me and I’m wondering if there is an easier way to accomplish this (reading VAC).


Hi, the problem is that you need to calibrate first.
Best with a oscilloscope.

I’m also busy with that code :stuck_out_tongue_winking_eye:

The one I made from emonlib is not 100%,
I’m now busy to make one with other library that calibrate and then makes the measurement.
There is no other yet available.

Trust me, I look whole internet and GitHub for it.
I have one with Mqtt ,but also not complete 100%
I will upload it later.

Also I noticed that the measurement with a Arduino or esp8266 is most stable and with power supply directly on the zmtp101b as in my drawing.

Covid is in the house at the moment.
So I need to get better first.


Work in progress, so not 100%

Make the code better and be nice… upload it here. :wink:

I notticed that the measurement is fluctuating to much , so that needs to be better.

//#include <ESP8266WiFi.h>                                       //esp8266
#include <WiFi.h>                                            //esp32
#include <PubSubClient.h>
#include "EmonLib.h"        

#define VOLT_CAL 123  // Calibration vallue
EnergyMonitor emon1;             

const char* ssid  = "#################";
const char* password  = "############";
const char* mqtt_server = "###########";
char* deviceId  = "Slpkm_Yanu_Voltage"; // Name of the sensor
char* stateTopic = "home-assistant/Slpkm_Yanu_Voltage/Voltage"; //name of the Topic
char buf[4]; // Buffer to store the sensor value
int updateInterval = 1000; // Interval in milliseconds

WiFiClient espClient;
PubSubClient client(espClient);
//unsigned long lastMsg = 0;                             //esp8266
long lastMsg = 0;                                      //esp32
char msg[50];
int value = 0;                                         //esp8266

void setup() {


 emon1.voltage(35, VOLT_CAL, 1.7);  
  client.setServer(mqtt_server, 1883);


void setup_wifi() {
  Serial.print("Connecting to ");

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {

  Serial.println("WiFi connected");
  Serial.println("IP address: ");

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(". Message: ");
  String messageVolt;
  for (int i = 0; i < length; i++) {
    messageVolt += (char)message[i];

void reconnect(){
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      // Subscribe
    } else {
      Serial.print("failed, rc=");
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
void loop() {
  if (!client.connected()) {

  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;

  emon1.calcVI(20,2000);         // Calculate all. No.of half wavelengths (crossings), time-out
  float supplyVoltage   = emon1.Vrms; 

    char tempString[8];
    dtostrf(supplyVoltage, 1, 2, tempString);
    Serial.print("Voltage: ");
//    client.publish("sensorDB", tempString);
    client.publish(stateTopic, itoa(supplyVoltage, buf, 10));

Ohh, get better soon :raised_hands:


To answer your first question, I do not think there is a much easier way than the one I used. I agree it is not totally straight forward, but indeed still simpler than the approach by mupsje (niet kwaad bedoeld).

To answer your last question. Yes there is an easier way to read Vac. Use a power meter with an RS485 bus. But these are generally not cheap. I have A Carlo Gavazzi since I installed a charger for my EV, but have not connected it yet to HA. I saw that ESPhome now has an integration for an Eastron SDM modbus energy monitor.

PZEM 004T that’s the one @VdR means!.

Full integration ESPHome.

@VdR , for me, I just want a voltage measure from a old school light dimmer :wink:
So I can dim my new Led-bulbs
(goedkoopste oplossing voor hallogeenspots naar led)

1 Like

It’s realy not fun!

1 Like

What I’d like to understand is the values you came up with for the filters:

- offset: -2.5485   # compensate the offset (calibration)
- multiply: 338   # calculate mains voltage (calibration)

I’m trying to adjust them so that I get the same readings as my volt meter, but not sure what I’m doing :sweat_smile:

The output signal of the Zmpt101b is an attenuated sinewave with an offset of 1/2Vcc (about 2.5V). To be able to get a good RMS reading from this you need to know the offset and the attenuation.

You change the attenuation with the potentiometer on the Zmpt101b. You want it as low as possible (large signal) but high enough that you get a full sinewave signal (not clipped) up to say 260Vrms. To tune that you need to connect an oscilloscope so you can see the signal.

Once you have set that attenuation you can measure the offset voltage on the output with a multimeter by not connecting anything to the input. That’s your off-sett calibration, you must substract the offset, hence the negative number.

Now you can calculate your attenuation, by comparing the Vrms reading from your ESPhome device with the reading from a multimeter. You tweak that number until you get the same reading.

You will be working with mains voltages, be extremely careful please, double check every move you make.

If you do not have an oscilloscope at hand, set the multiplier to a high value (say 360), get the off-set as described above, then tune the attenuation with the potentiometer, comparing the readout of your ESPhome device with your multimeter. I’m assuming here that you are measuring a 240V mains line.


I guess this can be done with an Arduino and the IDE serial plotter. For 220Vac (I live in Argentina) what values should I look for while adjusting the potentiometer? All I know is that I need an almost perfect sinewave, not chopped at its peak.

When you say “readings”, you mean the Vrms value from the previous step? Or something in the range of 200-240Vac?

Thanks so much for your help!

If your Arduino setup (I’m not familiar) samples fast enough to show the sine wave then that’s all you need. And, correct you need a sinewave that is not chopped at its peak. But also leave some room for when the mains voltage is high.

No, that is not what I mean. After you upload the code from HA to your ESPHome device, ESPHome will start showing the data that is sent to HA. The reading it sent for the mains voltage should be the same as what you measure with your meter.

Like below. It shows the measurements it takes from the ads and the mains voltage it calculated. The mains current comes from the current transformer connected to the same node.

See below again my full (except the passwords part) code for note:

# ========================

# i2C bus

  - sda: D2
    scl: D1
    scan: no
    frequency: 400kHz
    id: bus_a

  - address: 0x48
#   continuous_mode: on

# Switches


  # LED 1
  - platform: gpio
    name: "Mains voltage high"
      number: D3
      inverted: no
    id: voltage_high

  # LED 2
  - platform: gpio
    name: "Mains voltage ok"
      number: D4
      inverted: no
    id: voltage_ok

  # LED 3
  - platform: gpio
    name: "Mains voltage low"
      number: D5
      inverted: no
    id: voltage_low

# Sensors


  # Mains voltage sensor
  - platform: ads1115
    name: "Mains voltage"
    unit_of_measurement: "V"
    accuracy_decimals: 0
    icon: "mdi:flash"
    multiplexer: A0_GND
    gain: 6.144
    update_interval: 24ms

      - offset: -2.5475                       # compenstate the offset (value measured at 0V)
      - lambda: return x * x;
      - sliding_window_moving_average:
          window_size: 1250                   # average over 30 seconds
          send_every: 208                     # report every 05 seconds
          send_first_at: 208
      - lambda: return sqrt(x);
      - multiply: 338                         # calculate mains voltage (calibration)     
    id: mains_voltage

      - above: 240
          - switch.turn_on: voltage_high
          - switch.turn_off: voltage_ok
          - switch.turn_off: voltage_low
      - below: 240
        above: 220
          - switch.turn_off: voltage_high
          - switch.turn_on: voltage_ok
          - switch.turn_off: voltage_low
      - below: 220
          - switch.turn_off: voltage_high
          - switch.turn_off: voltage_ok
          - switch.turn_on: voltage_low

  # Mains current sensor
  - platform: ads1115
    name: "Mains current"
    unit_of_measurement: "A"
    accuracy_decimals: 1
    icon: "mdi:gauge"
    multiplexer: 'A2_A3'
    gain: 1.024
    update_interval: 24ms

      - offset: 0                             # compenstate the offset (value measured at 0A)
      - lambda: return x * x;
      - sliding_window_moving_average:
          window_size: 500                    # average over 12 seconds
          send_every: 83                      # report every 02 seconds
          send_first_at: 83
      - lambda: return sqrt(x);
      - multiply: 52.743                      # calculate mains current (0.5 V = 25 A)     
    id: mains_current

  # Mains power sensor
  - platform: template
    name: "Mains power"
    unit_of_measurement: "W"
    accuracy_decimals: 0
    update_interval: 5s                       # send every 5 seconds
    lambda: return id(mains_current).state * id(mains_voltage).state;
    id: mains_power

  # Peak voltage sensor
  - platform: template
    name: "Peak voltage"
    unit_of_measurement: "V"
    accuracy_decimals: 0
    update_interval: 5s
    lambda: return max( id(mains_voltage).state, id(peak_voltage).state );
    id: peak_voltage

  # Bottom voltage sensor
  - platform: template
    name: "Bottom voltage"
    unit_of_measurement: "V"
    accuracy_decimals: 0
    update_interval: 5s
    lambda: return min( id(mains_voltage).state, id(bottom_voltage).state );
    id: bottom_voltage

  # Peak current sensor
  - platform: template
    name: "Peak current"
    unit_of_measurement: "A"
    accuracy_decimals: 1
    update_interval: 5s
    lambda: return max( id(mains_current).state, id(peak_current).state );
    id: peak_current


  # Status
  - platform: status
    name: "Mains Status"

# End
1 Like