Skip to content

Instantly share code, notes, and snippets.

@bandaangosta
Last active October 29, 2024 09:16
Show Gist options
  • Save bandaangosta/134c9d84ae9bd317297e96dcc0b9c860 to your computer and use it in GitHub Desktop.
Save bandaangosta/134c9d84ae9bd317297e96dcc0b9c860 to your computer and use it in GitHub Desktop.
Reading PZEM-004t power sensor (new version v3.0) through Modbus-RTU protocol over TTL UART
# Reading PZEM-004t power sensor (new version v3.0) through Modbus-RTU protocol over TTL UART
# Run as:
# python3 pzem_004t.py
# To install dependencies:
# pip install modbus-tk
# pip install pyserial
import serial
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu
# Connect to the sensor
sensor = serial.Serial(
port='/dev/PZEM_sensor',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
xonxoff=0
)
master = modbus_rtu.RtuMaster(sensor)
master.set_timeout(2.0)
master.set_verbose(True)
data = master.execute(1, cst.READ_INPUT_REGISTERS, 0, 10)
voltage = data[0] / 10.0 # [V]
current = (data[1] + (data[2] << 16)) / 1000.0 # [A]
power = (data[3] + (data[4] << 16)) / 10.0 # [W]
energy = data[5] + (data[6] << 16) # [Wh]
frequency = data[7] / 10.0 # [Hz]
powerFactor = data[8] / 100.0
alarm = data[9] # 0 = no alarm
print('Voltage [V]: ', voltage)
print('Current [A]: ', current)
print('Power [W]: ', power) # active power (V * I * power factor)
print('Energy [Wh]: ', energy)
print('Frequency [Hz]: ', frequency)
print('Power factor []: ', powerFactor)
print('Alarm : ', alarm)
# Changing power alarm value to 100 W
# master.execute(1, cst.WRITE_SINGLE_REGISTER, 1, output_value=100)
try:
master.close()
if sensor.is_open:
sensor.close()
except:
pass
@bartgrefte
Copy link

bartgrefte commented Oct 25, 2024

@bandaangosta
"Regarding the first one, yes, this will work on Windows too"
Check, the script wouldn't run because I missed the bit that the AC-side had to be connected.

I have since then rewritten the script a little, I added for example

import serial.tools.list_ports
ports = list(serial.tools.list_ports.comports())
for port in ports:
    if port.serial_number == "A501JT6HA":
        comport = port.device

to find the comport assigned to the FTDI-cable with a certain serial number.

Also, the

sensor = serial.Serial(

bit is in an if-statement and won't run if comport var is empty, in that same if-statement there\s a variable called Run that is set true, default is false, so if there's no FTDI-cable detected (comport var is empty), then Run = false which will cause the main loop (while Run) to not run.

Inside the main (while) loop I've got

for PZEM in PZEMs
    data = master.execute(PZEM, cst.READ_INPUT_REGISTERS, 0, 10) 

where

PZEMs = range(1, 6)

so it iterates/loops through the code accessing 5 modules, with try/except.

To do: Copy over some Home Assistant MQTT Auto Discovery code from my P1 DSMR smart meter script, I ended up spreading that one over several files and a whole bunch of functions.

@bandaangosta
Copy link
Author

bandaangosta commented Oct 25, 2024 via email

@donemuhendislik
Copy link

Hi, @donemuhendislik. I'm happy to hear this code is useful. I remember reading (it's been a couple of years) that this version of the module does not allow for resetting the energy counter, although supposedly the function code 0x42 ("power zero clearing") does the trick. I have never tried it. In general, there is no need to clear the energy count. Just read the value at the beginning and end of a certain period. The difference is the energy consumption of the period.

Thank you so much @bandaangosta for your reply. In deed, i was meaning that 0x42 and i was afraid of trying that data to pzem-004t for may i could break it. So, your solution is best. Saving the initial energy value and saving last one and difference is consumption 👍🏻 or calculating energy consumption with Wh formula that contains voltage and current. Again thank you so much for your helpful idea, i will use it 👍🏻

@bartgrefte
Copy link

bartgrefte commented Oct 27, 2024

@bandaangosta Do you happen to have a TCP-version of this script? I'm now trying to access the data through a serial server running on an ESP-module. I can see from the log (of ESP Easy) that my script connects to the serial server, there's a single blink from a red LED on the PZEM-004t, but no data back :(

edit: Got as far as

import socket, sys

socket.setdefaulttimeout(5)

try:
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print('Failed to create socket')
    sys.exit()

print('Socket Created')
ESP_IP= "*.*.*.*"
ESP_PORT = *
client.connect((ESP_IP,ESP_PORT))
print('Socket Connected to ' + ESP_IP )

try:
    print("Send command")
    client.send(bytes("\x01\x04\x00\x00\x00\x0A\x70\x0D", 'ascii'))
    print("Receive data")
    data = client.recv(1024)
    print(data)
    
except socket.error as e:
    print(e)

The PZEM-module reacts to this (Rx LED goes on, followed by Tx-LED), but not receiving data back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment