-
-
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 |
hello to all, i'm trying to conect my pzem004t v3 to HA to get the readings, but i don't understand how and where to put your file?
Can you explain to me, please?
Hello, first of all, thank you for your work. I used your code and it works perfect. In this moment, I want to expand my current work I want to use three PZEM sensor. I looked into the modbus_tk library documentation but it is not clear to me a way to read data from three sensors in the same channel. I would be grateful if you could support me in this project. Thank you very much.
Hello, first of all, thank you for your work. I used your code and it works perfect. In this moment, I want to expand my current work I want to use three PZEM sensor. I looked into the modbus_tk library documentation but it is not clear to me a way to read data from three sensors in the same channel. I would be grateful if you could support me in this project. Thank you very much.
Hi, Felix. I'm glad you found this useful.
If you have 3 sensors communicating over UART (let's say using USB to UART adapters on a PC/Raspberry Pi), then you need 3 separate UART channels (3 USB to UART adapters). Then you will have three sensors defined like this:
sensor1 = serial.Serial(
port='/dev/ttyUSB0',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
xonxoff=0
)
sensor2 = serial.Serial(
port='/dev/ttyUSB1',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
xonxoff=0
)
sensor3 = serial.Serial(
port='/dev/ttyUSB2',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
xonxoff=0
)
The /dev/ttyUSBX
definition for the assigned ports is just an example. This may vary on your system.
Then apply modbus initialization and commands on each sensorX object separately.
Cheers.
Wow, thanks connermacleod69!! That's great info to have at hand. Would you mind sharing what model(s) from the catalog you have used?
Merci @bandaangosta !
this only print the data once right and not a loop? what does the .close() method do?
this only print the data once right and not a loop?
Hi, @FujiwaraKengo . That is correct, data is printed out only once and then the scripts ends, closing the communication channel. You could put the reading part in a loop (lines 27 to 43), allowing for a reasonable waiting time between iterations, or call the full script periodically.
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, 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.
Thank you for this effort! Worked like a charm on my version 3 board under Linux. Great to test the board before moving it to Raspberry Pi monitoring system (Domoticz) to be certain it was working.
Cheers!!