-
-
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 | |
# 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 |
Hi, first of all, thank you so much for your useful code. Everything is so clear. Searching on web, i didnt find any clue or code sample for “reseting energy (Wh) data”. If you can share an simple example for “reseting energy (Wh) data for pzem004-t”, i and any other researchers will be so happy. Thanks a lot for your sharings again.
Can this script be modified for usage with Windows? Changing "/dev/PZEM_sensor" to the comport of the FTDI-cable results in
Traceback (most recent call last): File "g:\PZEM-004T\pzem_004t.py", line 27, in <module> data = master.execute(1, cst.READ_INPUT_REGISTERS, 0, 10) File "c:\Users\Bart\AppData\Local\Programs\Python\Python310\lib\site-packages\modbus_tk\utils.py", line 39, in new raise excpt File "c:\Users\Bart\AppData\Local\Programs\Python\Python310\lib\site-packages\modbus_tk\utils.py", line 37, in new ret = fcn(*args, **kwargs) File "c:\Users\Bart\AppData\Local\Programs\Python\Python310\lib\site-packages\modbus_tk\modbus.py", line 356, in execute response_pdu = query.parse_response(response) File "c:\Users\Bart\AppData\Local\Programs\Python\Python310\lib\site-packages\modbus_tk\modbus_rtu.py", line 46, in parse_response raise ModbusInvalidResponseError("Response length is invalid {0}".format(len(response))) modbus_tk.exceptions.ModbusInvalidResponseError: Response length is invalid 0
Yes, I checked the Tx-/Rx-connections.
edit: Hmm, seems it does this when there are no readings (didn't connect AC-side), I am getting output when AC-side is connected. So I added an try/except for modbus_rtu.ModbusInvalidResponseError and an if-statement to check if data contains something.
Now to figure out how to change the address from Python. edit:
data = master.execute(1, cst.WRITE_SINGLE_REGISTER, 2, output_value=2)
seems to do that :)
hi, @bartgrefte. By now you already responded to your own questions :) Thanks for sharing for everyone's benefit!
Regarding the first one, yes, this will work on Windows too. The only change is the way you initialize the serial object: for example, change port='/dev/ttyUSB0' to port='COM3'. COM3 is just an example, check which port was assigned to your serial device (typically, a USB-to-RS232 adapter).
Yes, this PZEM-004t module requires the AC side to be connected to work! This is not intuitive.
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.
@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.
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 👍🏻
@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.
Can this script be modified for usage with Windows? Changing "/dev/PZEM_sensor" to the comport of the FTDI-cable results in
Yes, I checked the Tx-/Rx-connections.
edit: Hmm, seems it does this when there are no readings (didn't connect AC-side), I am getting output when AC-side is connected. So I added an try/except for modbus_rtu.ModbusInvalidResponseError and an if-statement to check if data contains something.
Now to figure out how to change the address from Python.
edit:
data = master.execute(1, cst.WRITE_SINGLE_REGISTER, 2, output_value=2)
seems to do that :)