Instructions on how to use energy monitoring PZEM-004T v3 in the Home Assistant system with native Modbus
-
Home Assistant with Bluetooth support (can be external USB dongle), in Debian-like system installed “sudo apt -y install bluez”
-
PZEM-004T v3 module itself
-
Bluetooth module SPP, popular HC-05 and its cheaper analog JDY-33 it can SPP or BLE. Will be suitable and others similar in function. Of course you can still use a direct connection of any kind “/dev/ttyUSB0” UART - but directly connect to the high-voltage board single-board computer is strongly unwanted. It is necessary to use a wireless connection… And not necessarily Bluetooth connection (it has a range enough to swear by) can be a variant of a pair of modules transceiver-receiver (but in this case, one of the modules must be directly connected to the single-board via UART, and the second to PZEM-004T beforehand configuring and checking the two modules to work in tandem) at which the range will be longer, such as HC-12 (it is on the 433Mhz frequency of radio) it has a range of about 600-700 meters in a clear field at a standard speed of 9600 bits.
-
Create a dialout group and add your Home Assistant user to this group:
sudo newgrp dialout
sudo gpasswd --add <user> dialout
sudo usermod -a -G dialout <user>
(note the <user> - this should be your user)
- If we use the HC-05 module, we only have access to the SPP protocol, if we use the JDY-33 module, we have the option to use JDY-33-SPP (Bluetooth 3.0) or JDY-33-BLE (Bluetooth 4.0+), let’s consider the universal SPP protocol first.
Instruction SPP
5.1. For Bluetooth SPP module to automatically connect in Armbian you need to connect to it via bluetoothctl and make it trusted:
sudo bluetoothctl
scan on
<During scanning, find the mac address opposite the name HC-05 or JDY-33-SPP.>
pair <mac>
trust <mac>
connect <mac>
exit
(pay attention to <mac> - this should be the mac address of the detected device, in the process when you make pair - it will ask for pin code, usually by default it is “1234”)
5.2. Next, you need to make a bind so that your /dev/rfcomm0 is detected in the devfs tree:
sudo rfcomm bind rfcomm0 <mac>
(pay attention to <mac> - this should be the mac address of the same SPP device)
5.3. Now it should be tested as a normal UART for example echo commands, does it work as it should or not.
To do this, short TX and RX and open two consoles in parallel in Armbian, one with the command “cat /dev/rfcomm0” running, and the other with the command “echo 12345 > /dev/rfcomm0” if you see the line “12345” in the first console, then the connection works.
If we don’t see anything, then you need to search for the problem and you will not be able to continue point by point.
5.4. After a successful connection check, we need to add the previous command to the /etc/rc.local file, so that when we boot our Bluetooth-UART module will bind automatically after a boot:
sudo nano /etc/rc.local
(here I use nano to edit this file, you can use whatever editor you prefer)
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
rfcomm bind rfcomm0 <mac>
exit 0
(pay attention to <mac> - this should be the mac address of the same SPP device)
5.5. To avoid doing a reboot on running single-board you can start restarting the rc.local boot service with the command:
sudo systemctl restart rc-local.service
Alternative option instead of SPP use BLE on the example of “JDY-33-BLE” (BT 4.0+) - in my case it reduces power consumption and is slightly more stable than compared to JDY-33-SPP (because SPP uses an outdated version of BT 3.0), but the range of BLE is poor:
Instruction BLE
5.1. If you have already bound JDY-33-SPP into the system, JDY-33-BLE will not appear until you unbind and remove SPP by bluetoothctl “remove ” also you need to do “rfcomm unbind rfcomm0” .
For Linux there is a Python utility to create a Serial port via the BLE service: GitHub - Jakeler/ble-serial: "RFCOMM for BLE" a UART over Bluetooth low energy (4+) bridge for Linux, Mac and Windows.
Install ble-serial:
sudo python3 -m pip install ble-serial
(Note that I’m doing it from sudo because I’ll be linking the port to the /dev/* folder - which is where the Home Assistant Supervisor container will see the device. If you still want to link the port from your user, then first of all: install ble-serial without sudo as your user, and secondly: link the port to a folder within Home Assistant’s visibility area, and your user (non-sudo) must have full access to this folder)
5.2. Run a scan to find the mac address of our BLE:
sudo ble-scan
In my case, the output is:
deoptim@homeassistant:~$ sudo ble-scan
Started general BLE scan
28:82:3F:XX:XX:XX (rssi=-91): JDY-33-BLE
...
...
Finished general BLE scan
deoptim@homeassistant:~$
5.3. Now let’s check what services are available to us:
sudo ble-scan -d 28:82:3F:XX:XX:XX
In my case, the output is:
deoptim@homeassistant:~$ sudo ble-scan -d 28:82:3F:XX:XX:XX
Started general BLE scan
28:82:3F:XX:XX:XX (rssi=-93): JDY-33-BLE
...
...
...
Finished general BLE scan
Started deep scan of 28:82:3F:XX:XX:XX
Found device 28:82:3F:XX:XX:XX: JDY-33-BLE (out of 1)
SERVICE 0000ffe0-0000-1000-8000-00805f9b34fb (Handle: 14): Vendor specific
CHARACTERISTIC 0000ffe2-0000-1000-8000-00805f9b34fb (Handle: 18): Vendor specific ['write-without-response', 'write']
CHARACTERISTIC 0000ffe1-0000-1000-8000-00805f9b34fb (Handle: 15): Vendor specific ['write-without-response', 'write', 'notify']
DESCRIPTOR 00002902-0000-1000-8000-00805f9b34fb (Handle: 17): Client Characteristic Configuration
SERVICE 00001801-0000-1000-8000-00805f9b34fb (Handle: 10): Generic Attribute Profile
CHARACTERISTIC 00002a05-0000-1000-8000-00805f9b34fb (Handle: 11): Service Changed ['indicate']
DESCRIPTOR 00002902-0000-1000-8000-00805f9b34fb (Handle: 13): Client Characteristic Configuration
Completed deep scan of 28:82:3F:XX:XX:XX
deoptim@homeassistant:~$
5.4. We need the service 0000ffe1-0000-1000-8000-00805f9b34fb for the serial you need to set manually:
sudo ble-serial -d 28:82:3F:XX:XX:XX -w 0000ffe1-0000-1000-8000-00805f9b34fb -r 0000ffe1-0000-1000-8000-00805f9b34fb -p /dev/ttyBLE
(note that I am linking the port to /dev/* instead of the default /tmp/* because Home Assistant container does not see our system /tmp/* and will not be able to use the port, which is why we use ble-serial as root (sudo). If you want to link the port from your user, then first of all: install ble-serial without sudo as your user, and secondly: link the port to a folder within Home Assistant’s visibility area, and your user (non-sudo) must have full access to this folder)
In my case, the output is:
deoptim@homeassistant:~$ sudo ble-serial -d 28:82:3F:XX:XX:XX -w 0000ffe1-0000-1000-8000-00805f9b34fb -r 0000ffe1-0000-1000-8000-00805f9b34fb -p /dev/ttyBLE
10:35:04.590 | INFO | linux_pty.py: Port endpoint created on /dev/ttyBLE -> /dev/pts/2
10:35:04.592 | INFO | ble_interface.py: Receiver set up
10:35:04.952 | INFO | ble_interface.py: Trying to connect with 28:82:3F:XX:XX:XX: JDY-33-BLE
10:35:07.557 | INFO | ble_interface.py: Device 28:82:3F:XX:XX:XX connected
10:35:07.559 | INFO | ble_interface.py: Found write characteristic 0000ffe1-0000-1000-8000-00805f9b34fb (H. 15)
10:35:07.560 | INFO | ble_interface.py: Found notify characteristic 0000ffe1-0000-1000-8000-00805f9b34fb (H. 15)
10:35:08.249 | INFO | main.py: Running main loop!
Voila, so now we check the port to see if it works - it should work.
5.5. To automatically up a BLE port at bootime, you need:
- Create a folder as root and navigate to it:
sudo mkdir /etc/ble-serial
cd /etc/ble-serial
- Download the missing scripts to the /etc/ble-serial/ folder:
sudo wget https://github.com/Jakeler/ble-serial/raw/main/helper/autoconnect.ini
sudo wget https://github.com/Jakeler/ble-serial/raw/main/helper/ble-autoconnect.py
- Edit /etc/ble-serial/autoconnect.ini for our device:
### Example config file
# Values from the DEFAULT section are applied to all devices, other sections with the
# MAC address can override config elements for the specific device.
# Only devices that have a section are considered by the auto detection mechanism, but
# it can be empty to run with pure defaults.
# Key names are identical to the long parameters without dashes:
# specify "mtu = 20" to get "--mtu 20", this works also with flags like "--verbose".
# The only special key is "executable", which specifies the program that is started.
[DEFAULT]
executable = ble-serial
timeout = 10
mtu = 20
port = /dev/ttyBLE
[28:82:3F:XX:XX:XX]
dev = 28:82:3F:XX:XX:XX
address-type = public
port = /dev/ttyBLE
w = 0000ffe1-0000-1000-8000-00805f9b34fb
r = 0000ffe1-0000-1000-8000-00805f9b34fb
(Saving)
- Create a system service ble-autoconnect.service:
sudo nano /etc/systemd/system/ble-autoconnect.service
(I use the nano editor, you can use one that is convenient for you)
Contents of ble-autoconnect.service:
[Unit]
Description=Automatically connect to available bluettooth devices from config
[Service]
WorkingDirectory=/etc/ble-serial
ExecStart=/usr/bin/python3 ble-autoconnect.py
[Install]
WantedBy=multi-user.target
- Activate and start the system service ble-autoconnect.service:
sudo systemctl enable ble-autoconnect.service
sudo systemctl start ble-autoconnect.service
(to check the status, use the command “sudo systemctl status ble-autoconnect.service”. If port successful brings-up, the last line in the log will be “main.py: Running main loop!” otherwise it will try to connect in a loop).
- Next you can connect PZEM-004T with HC-05 (or similar), don’t forget about individual power supply 5V (or 3.3V depending on which SPP you have).
Now what my configuration.yaml looks like
modbus:
#https://community.home-assistant.io/t/assistance-with-modbus-and-pzem-004t/330723/3
- name: PZEM_004T_V3
type: serial
method: rtu
port: /dev/rfcomm0
baudrate: 9600
stopbits: 1
bytesize: 8
parity: N
sensors:
- name: "PZEM 004T Voltage [V]"
unique_id: pzem_004t_voltage_v
device_class: voltage
unit_of_measurement: V
state_class: measurement
slave: 1
address: 0
input_type: input
scale: 0.1
offset: 0
precision: 1
data_type: uint16
- name: "PZEM 004T Current [A]"
unique_id: pzem_004t_current_a
unit_of_measurement: A
state_class: measurement
device_class: current
slave: 1
address: 1
input_type: input
scale: 0.001
offset: 0
precision: 3
data_type: uint32
swap: word
- name: "PZEM 004T Power [W]"
unique_id: pzem_004t_power_w
unit_of_measurement: W
state_class: measurement
device_class: power
slave: 1
address: 3
input_type: input
scale: 0.1
offset: 0
precision: 1
data_type: uint32
swap: word
- name: "PZEM 004T Energy [Wh]"
unique_id: pzem_004t_energy_wh
unit_of_measurement: Wh
state_class: total_increasing
device_class: energy
slave: 1
address: 5
input_type: input
scale: 1
offset: 0
precision: 0
data_type: uint32
swap: word
- name: "PZEM 004T Frequency [Hz]"
unique_id: pzem_004t_frequency_hz
unit_of_measurement: Hz
state_class: measurement
slave: 1
address: 7
input_type: input
scale: 0.1
offset: 0
precision: 1
data_type: uint16
- name: "PZEM 004T Power factor [PF]"
unique_id: pzem_004t_power_factor_pf
unit_of_measurement: PF
state_class: measurement
slave: 1
address: 8
input_type: input
scale: 0.01
offset: 0
precision: 2
data_type: uint16
- name: "PZEM 004T Power Alarm Value [W]"
unique_id: pzem_004t_power_alarm_value_w
unit_of_measurement: W
slave: 1
address: 1
#Input Registers - Function Code 0x04
#Holding Registers - Function Code 0x03 & 0x10
input_type: holding
scale: 1
offset: 0
precision: 0
data_type: uint16
binary_sensors:
- name: "PZEM 004T Power Alarm Status"
unique_id: pzem_004t_power_alarm_status
slave: 1
address: 9
input_type: input
template:
#https://community.home-assistant.io/t/use-last-value-instead-of-unknown/614331/5
- trigger:
- platform: state
entity_id: sensor.pzem_004t_energy_wh
not_to:
- unknown
- unavailable
sensor:
- name: PZEM 004t Energy Wh Buffer
state: '{{ trigger.to_state.state }}'
device_class: energy
unit_of_measurement: Wh
state_class: total_increasing
unique_id: pzem_004t_energy_wh_buffer
input_number:
pzem_004t_box1:
name: "PZEM 004T Power Alarm Threshold [W]"
initial: 1
min: 1
#For PZEM-004T-10A use max value 2300 W,
#for PZEM-004T-100A use max value 23000 W
max: 23000
step: 1
mode: box
unit_of_measurement: W
input_button:
pzem_004t_button0:
name: "PZEM 004T Set Power Alarm"
icon: mdi:flash-triangle
pzem_004t_button1:
name: "PZEM 004T Reset Energy Consumption"
icon: mdi:reload
switch:
- platform: template
switches:
pzem_004t_turn_modbus:
unique_id: pzem_004t_turn_modbus
friendly_name: "PZEM 004T Turn Modbus"
value_template: >-
{% if states('sensor.pzem_004t_voltage_v') != 'unavailable' %}
on
{% else %}
off
{% endif %}
turn_on:
- service: modbus.restart
data:
hub: PZEM_004T_V3
turn_off:
- service: modbus.stop
data:
hub: PZEM_004T_V3
homeassistant:
name: Home
unit_system: metric
customize:
sensor.pzem_004t_energy_wh:
last_reset: '1970-01-01T00:00:00+00:00'
sensor.pzem_004t_frequency_hz:
icon: mdi:current-ac
sensor.pzem_004t_power_factor_pf:
icon: mdi:angle-acute
sensor.pzem_004t_power_alarm_value_w:
icon: mdi:meter-electric
shell_command:
#https://community.home-assistant.io/t/how-to-use-pzem004t-energy-monitor-with-esphome/107083/186
send_reset_to_pzem_004t: echo -en '\x01\x42\x80\x11' > /dev/rfcomm0
(note that I have not fully posted my configuration file, but only the main parts related to modbus, template and all buttons(input_button), input_number, switch, shell_command) that are involved in this configuration. Also in case you will use ble-serial change in modbus /dev/rfcomm0 → /dev/ttyBLE and don’t forget to change for shell_command also. Pay attention to another sensor “PZEM 004t Energy Wh Buffer” (template) - it duplicates the modbus sensor pzem_004t_energy_wh but keeps the last value (even after reboot) without unknown/unavailable states - in some cases it is necessary, for example if the connection with the energy monitor over SPP/BLE/Radio is lost, the second sensor “PZEM 004t Energy Wh Buffer” will not distort the Utility Meter counting statistics, this sensor can be useful in place of the modbus energy sensor).
Now what my automations.yaml configuration looks like
- id: reset_pzem_004t_on_button
alias: Reset Energy Consumption on Button
description: ''
trigger:
- platform: state
entity_id: input_button.pzem_004t_button1
condition: []
action:
- service: automation.trigger
data:
skip_condition: true
target:
entity_id: automation.reset_energy_consumption_every_month
initial_state: 'on'
- id: reset_pzem_004t_every_month
alias: Reset Energy Consumption every Month
description: ''
trigger:
- platform: time
at: 00:00:01
- platform: homeassistant
event: start
- platform: time_pattern
hours: "1"
condition:
- condition: template
value_template: "{% if now().day == 1 -%}\n {{ now() - state_attr('automation.reset_energy_consumption_every_month',
'last_triggered') > timedelta(hours=24) }}\n{%- else -%}\n False\n{%- endif
%}"
action:
- service: shell_command.send_reset_to_pzem_004t
data: {}
mode: single
- id: set_power_alarm_of_pzem_004t_on_button
alias: Set Power Alarm on Button
description: ''
trigger:
- platform: state
entity_id:
- input_button.pzem_004t_button0
condition: []
action:
- service: modbus.write_register
data:
address: 1
hub: PZEM_004T_V3
value: '{{ states(''input_number.pzem_004t_box1'') | round(0) }}'
slave: 1
mode: single
(here I will explain, in addition to automating the reset the collected energy statistics over input button, the reset is done every first day of the new month at 00:00:01 (first second of the day), also if the reset did not happen for some reason (let’s say Home Assistant was not loaded at that moment or other reasons), the automation will try to reset the collected energy statistics every hour during the first day of the month (24 hours) or after Home Assistant is loaded)
Connection of several PZEM-004T's to one Modbus bus (e.g. for monitoring a three-phase grid)
Important! You can skip these instructions if you use a separate Bluetooth-SPP (or any other module) for each PZEM-004T board.
If you want to use one Modbus module for all PZEM-004T boards (e.g. transceiver-receiver module option where we have only one UART), you need to assign a different address for each PZEM-004T in advance.
Assign an address for each PZEM-004T is quite simple, it can be done through the Home Assistant interface itself, connecting first to each board of the energy monitor separately (it is not necessary to connect to the electricity panel while it can be done at least on the table and indicators when connected shows zero, but the some data must be going - that will indicate communication between PZEM-004T <=> Home Assistant). For one of the PZEM-004T’s we will leave the address unchanged “1” (default), we will configure only the other two PZEM’s.
- Connect the configurable PZEM-004T to the Home Assistant (make sure that any data comes from it all the time).
- open the tab “Developer tools” → “SERVICES”
- Select “Modbus: Write register” service
- Assign Address: 2 (this is the write register address, it should be unchanged for all configurable PZEM-004T)
- Assign Value: <new address of our current PZEM-004T> (it makes sense to number in order from 1 to …, the default address on all PZEM-004T’s is “1” and therefore they cannot work simultaneously all at once on the same Modbus).
- As the Hub name (Modbus hub name) select our Modbus connection for the specific connected PZEM-004T.
- Press “CALL SERVICE”
After that, if everything is done correctly, we should stop updating data from PZEM-004T with settings “slave: 1”.
To fix this, you need to copy all sensors in the Home Assistant configuration file in the Modbus group of the same PZEM-004T module and add new ones with “slave: 2” (or the address you specified). Accordingly, each sensor must have its own different name and unique ID (it must be renamed so that the sensors are not duplicated).
If we have three PZEM-004T, then accordingly in configuration.yaml we should have in one common Modbus section (with the same name), three groups of identical copies of sensors but with different slave address, name and unique_id.
- Restart Home Assistant.
After we have restarted Home Assistant we should receive data from the “slave: 2” (or your specified address) sensors, which indicates that the PZEM-004T is correctly configured and it should be disconnected, then connect the next one and so on…
Important! After we have configured all PZEM-004T’s to a unique address, only then we can connect them in parallel to the same Modbus bus.
To reset the collected energy statistics for slave address “1” we know, but the other two PZEM-004T will have different slave address and now must be different shell_command, these commands (with their CRC sums) can be seen at this link (of course there are not values for all 247 possible addresses, but I think it is enough for most cases).
Dashboard
Photo
Source Link.