Skip to content

Commit 09ab462

Browse files
srishtik2310kartben
authored andcommitted
drivers: sensor: pzem004t: add pzem004t AC parameter sensor driver
Adds driver for Peacefair pzem004t multifunction AC parameter sensor. Signed-off-by: Srishtik Bhandarkar <srishtik.bhandarkar2000@gmail.com>
1 parent 8959d28 commit 09ab462

File tree

11 files changed

+547
-0
lines changed

11 files changed

+547
-0
lines changed

drivers/sensor/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ add_subdirectory_ifdef(CONFIG_MHZ19B mhz19b)
5757
add_subdirectory_ifdef(CONFIG_NCT75 nct75)
5858
add_subdirectory_ifdef(CONFIG_NTC_THERMISTOR ntc_thermistor)
5959
add_subdirectory_ifdef(CONFIG_PMS7003 pms7003)
60+
add_subdirectory_ifdef(CONFIG_PZEM004T pzem004t)
6061
add_subdirectory_ifdef(CONFIG_QDEC_SAM qdec_sam)
6162
add_subdirectory_ifdef(CONFIG_RPI_PICO_TEMP rpi_pico_temp)
6263
add_subdirectory_ifdef(CONFIG_S11059 s11059)

drivers/sensor/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ source "drivers/sensor/mhz19b/Kconfig"
145145
source "drivers/sensor/nct75/Kconfig"
146146
source "drivers/sensor/ntc_thermistor/Kconfig"
147147
source "drivers/sensor/pms7003/Kconfig"
148+
source "drivers/sensor/pzem004t/Kconfig"
148149
source "drivers/sensor/qdec_sam/Kconfig"
149150
source "drivers/sensor/rpi_pico_temp/Kconfig"
150151
source "drivers/sensor/s11059/Kconfig"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) 2025 Srishtik Bhandarkar
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
zephyr_library()
5+
6+
zephyr_library_sources(pzem004t.c)

drivers/sensor/pzem004t/Kconfig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# PZEM004T Multifunction AC Meter configuration options
2+
3+
# Copyright (c) 2025 Srishtik Bhandarkar
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
config PZEM004T
7+
bool "PZEM004T Multifunction AC Meter"
8+
default y
9+
depends on DT_HAS_PEACEFAIR_PZEM004T_ENABLED
10+
depends on MODBUS
11+
depends on MODBUS_SERIAL
12+
depends on MODBUS_RAW_ADU
13+
help
14+
Enable the driver for the Peacefair PZEM004T Multifunction AC Meter.
15+
16+
config PZEM004T_ENABLE_RESET_ENERGY
17+
bool "Include support for resetting energy value"
18+
depends on PZEM004T
19+
help
20+
Enable support for resetting the energy counter on the PZEM004T device.

drivers/sensor/pzem004t/pzem004t.c

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/*
2+
* Copyright (c) 2025 Srishtik Bhandarkar
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT peacefair_pzem004t
8+
9+
/*
10+
* sensor pzem004t.c - Driver for peacefair PZEM004T sensor
11+
* PZEM004T product: https://en.peacefair.cn/product/772.html
12+
*/
13+
14+
#include <errno.h>
15+
#include <zephyr/init.h>
16+
#include <zephyr/kernel.h>
17+
#include <zephyr/drivers/sensor.h>
18+
#include <zephyr/logging/log.h>
19+
#include <zephyr/modbus/modbus.h>
20+
#include <zephyr/drivers/sensor/pzem004t.h>
21+
#include "pzem004t.h"
22+
23+
LOG_MODULE_REGISTER(pzem004t, CONFIG_SENSOR_LOG_LEVEL);
24+
25+
/* Custom function code handler */
26+
#if CONFIG_PZEM004T_ENABLE_RESET_ENERGY
27+
28+
static bool custom_fc_handler(const int iface, const struct modbus_adu *rx_adu,
29+
struct modbus_adu *tx_adu, uint8_t *const excep_code,
30+
void *const user_data)
31+
{
32+
/* Validate the received function code */
33+
if (rx_adu->fc != PZEM004T_RESET_ENERGY_CUSTOM_FC) {
34+
LOG_ERR("Unexpected function code: 0x%02X", rx_adu->fc);
35+
*excep_code = MODBUS_EXC_ILLEGAL_FC;
36+
return true;
37+
}
38+
39+
return true;
40+
}
41+
42+
MODBUS_CUSTOM_FC_DEFINE(custom_fc, custom_fc_handler, PZEM004T_RESET_ENERGY_CUSTOM_FC, NULL);
43+
44+
static void register_custom_fc(const int iface)
45+
{
46+
int err = modbus_register_user_fc(iface, &modbus_cfg_custom_fc);
47+
48+
if (err) {
49+
LOG_ERR("Failed to register custom function code (err %d)", err);
50+
} else {
51+
LOG_INF("Custom function code 0x42 registered successfully");
52+
}
53+
}
54+
55+
static int pzem004t_reset_energy(int iface, uint8_t address)
56+
{
57+
struct modbus_adu adu = {
58+
.unit_id = address,
59+
.fc = PZEM004T_RESET_ENERGY_CUSTOM_FC,
60+
.length = 0,
61+
};
62+
63+
int err = modbus_raw_backend_txn(iface, &adu);
64+
65+
if (err) {
66+
return err;
67+
}
68+
69+
return (adu.fc == PZEM004T_RESET_ENERGY_CUSTOM_FC) ? 0 : -EIO;
70+
}
71+
72+
#endif /* CONFIG_PZEM004T_ENABLE_RESET_ENERGY */
73+
74+
/**
75+
* @brief Check if the Modbus client is initialized
76+
*
77+
* @param iface The Modbus interface index
78+
* @return true if the Modbus client is initialized, false otherwise
79+
*/
80+
static bool is_modbus_client_initialized(int iface)
81+
{
82+
struct modbus_adu adu = {0};
83+
int ret;
84+
85+
/* Prepare a dummy ADU to test the interface */
86+
adu.fc = 0x03;
87+
adu.unit_id = 1;
88+
adu.length = 0;
89+
90+
ret = modbus_raw_backend_txn(iface, &adu);
91+
92+
return (ret == 0);
93+
}
94+
95+
static int pzem004t_init(const struct device *dev)
96+
{
97+
const struct pzem004t_config *config = dev->config;
98+
struct pzem004t_data *data = dev->data;
99+
int iface = modbus_iface_get_by_name(config->modbus_iface_name);
100+
101+
if (iface < 0) {
102+
LOG_ERR("Failed to get Modbus interface: %s", config->modbus_iface_name);
103+
return -ENODEV;
104+
}
105+
106+
if (!(is_modbus_client_initialized(iface))) {
107+
int err = modbus_init_client(iface, config->client_param);
108+
109+
if (err) {
110+
LOG_ERR("Modbus RTU client initialization failed (err %d)", err);
111+
return err;
112+
}
113+
}
114+
115+
data->iface = iface;
116+
117+
#if CONFIG_PZEM004T_ENABLE_RESET_ENERGY
118+
register_custom_fc(data->iface);
119+
#endif /* CONFIG_PZEM004T_ENABLE_RESET_ENERGY */
120+
121+
return 0;
122+
}
123+
124+
static int pzem004t_sample_fetch(const struct device *dev, enum sensor_channel chan)
125+
{
126+
struct pzem004t_data *sensor_data = dev->data;
127+
128+
uint16_t reg_buf[MEASUREMENT_REGISTER_TOTAL_LENGTH] = {0};
129+
int err = modbus_read_input_regs(sensor_data->iface, sensor_data->modbus_address,
130+
MEASUREMENT_REGISTER_START_ADDRESS, reg_buf,
131+
MEASUREMENT_REGISTER_TOTAL_LENGTH);
132+
133+
if (err != 0) {
134+
LOG_ERR("Failed to fetch sensor data at address 0x%02x: %d",
135+
sensor_data->modbus_address, err);
136+
return err;
137+
}
138+
139+
sensor_data->voltage = reg_buf[0];
140+
sensor_data->current = ((reg_buf[2] << 16) | reg_buf[1]);
141+
sensor_data->power = ((reg_buf[4] << 16) | reg_buf[3]);
142+
sensor_data->energy = ((reg_buf[6] << 16) | reg_buf[5]);
143+
sensor_data->frequency = reg_buf[7];
144+
sensor_data->power_factor = reg_buf[8];
145+
sensor_data->alarm_status = reg_buf[9];
146+
147+
return 0;
148+
}
149+
150+
static int pzem004t_channel_get(const struct device *dev, enum sensor_channel chan,
151+
struct sensor_value *val)
152+
{
153+
struct pzem004t_data *sensor_data = dev->data;
154+
155+
switch ((uint32_t)chan) {
156+
case SENSOR_CHAN_VOLTAGE:
157+
val->val1 = sensor_data->voltage / PZEM004T_VOLTAGE_SCALE;
158+
val->val2 = (sensor_data->voltage % PZEM004T_VOLTAGE_SCALE);
159+
break;
160+
case SENSOR_CHAN_CURRENT:
161+
val->val1 = sensor_data->current / PZEM004T_CURRENT_SCALE;
162+
val->val2 = (sensor_data->current % PZEM004T_CURRENT_SCALE);
163+
break;
164+
case SENSOR_CHAN_POWER:
165+
val->val1 = sensor_data->power / PZEM004T_POWER_SCALE;
166+
val->val2 = (sensor_data->power % PZEM004T_POWER_SCALE);
167+
break;
168+
case (enum sensor_channel)SENSOR_CHAN_PZEM004T_ENERGY:
169+
val->val1 = sensor_data->energy / PZEM004T_ENERGY_SCALE;
170+
val->val2 = (sensor_data->energy % PZEM004T_ENERGY_SCALE);
171+
break;
172+
case SENSOR_CHAN_FREQUENCY:
173+
val->val1 = sensor_data->frequency / PZEM004T_FREQUENCY_SCALE;
174+
val->val2 = (sensor_data->frequency % PZEM004T_FREQUENCY_SCALE);
175+
break;
176+
case (enum sensor_channel)SENSOR_CHAN_PZEM004T_POWER_FACTOR:
177+
val->val1 = sensor_data->power_factor / PZEM004T_POWER_FACTOR_SCALE;
178+
val->val2 = (sensor_data->power_factor % PZEM004T_POWER_FACTOR_SCALE);
179+
break;
180+
case (enum sensor_channel)SENSOR_CHAN_PZEM004T_ALARM_STATUS:
181+
val->val1 = sensor_data->alarm_status;
182+
val->val2 = 0;
183+
break;
184+
default:
185+
return -ENOTSUP;
186+
}
187+
188+
return 0;
189+
}
190+
191+
static int pzem004t_attr_get(const struct device *dev, enum sensor_channel chan,
192+
enum sensor_attribute attr, struct sensor_value *val)
193+
{
194+
struct pzem004t_data *data = dev->data;
195+
196+
int err;
197+
uint16_t reg_buf[1] = {0};
198+
199+
if (chan != (enum sensor_channel)SENSOR_CHAN_PZEM004T_POWER_ALARM_THRESHOLD &&
200+
chan != (enum sensor_channel)SENSOR_CHAN_PZEM004T_MODBUS_RTU_ADDRESS) {
201+
LOG_ERR("Channel not supported for setting Request");
202+
return -ENOTSUP;
203+
}
204+
205+
switch ((uint32_t)attr) {
206+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_POWER_ALARM_THRESHOLD:
207+
err = modbus_read_holding_regs(data->iface, data->modbus_address,
208+
POWER_ALARM_THRESHOLD_ADDRESS, reg_buf,
209+
POWER_ALARM_THRESHOLD_REGISTER_LENGTH);
210+
if (err != 0) {
211+
return err;
212+
}
213+
val->val1 = reg_buf[0];
214+
val->val2 = 0;
215+
break;
216+
217+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_MODBUS_RTU_ADDRESS:
218+
err = modbus_read_holding_regs(data->iface, data->modbus_address,
219+
MODBUS_RTU_ADDRESS_REGISTER, reg_buf,
220+
MODBUS_RTU_ADDRESS_REGISTER_LENGTH);
221+
if (err != 0) {
222+
return err;
223+
}
224+
val->val1 = reg_buf[0];
225+
val->val2 = 0;
226+
break;
227+
228+
default:
229+
LOG_ERR("Unsupported Attribute");
230+
return -ENOTSUP;
231+
}
232+
233+
return 0;
234+
}
235+
236+
static int pzem004t_attr_set(const struct device *dev, enum sensor_channel chan,
237+
enum sensor_attribute attr, const struct sensor_value *val)
238+
{
239+
struct pzem004t_data *data = dev->data;
240+
int err;
241+
242+
if (chan != (enum sensor_channel)SENSOR_CHAN_PZEM004T_POWER_ALARM_THRESHOLD &&
243+
chan != (enum sensor_channel)SENSOR_CHAN_PZEM004T_MODBUS_RTU_ADDRESS &&
244+
chan != (enum sensor_channel)SENSOR_CHAN_PZEM004T_ADDRESS_INST_SET &&
245+
chan != (enum sensor_channel)SENSOR_CHAN_PZEM004T_RESET_ENERGY) {
246+
LOG_ERR("Channel not supported for setting attribute");
247+
return -ENOTSUP;
248+
}
249+
250+
switch ((uint32_t)attr) {
251+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_POWER_ALARM_THRESHOLD:
252+
if (val->val1 < 0 || val->val1 > PZEM004T_MAX_POWER_ALARM_THRESHOLD) {
253+
LOG_ERR("Power alarm threshold out of range");
254+
return -EINVAL;
255+
}
256+
257+
err = modbus_write_holding_reg(data->iface, data->modbus_address,
258+
POWER_ALARM_THRESHOLD_ADDRESS, val->val1);
259+
if (err != 0) {
260+
return err;
261+
}
262+
break;
263+
264+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_MODBUS_RTU_ADDRESS:
265+
if (val->val1 < 0 || val->val1 > PZEM004T_MAX_MODBUS_RTU_ADDRESS) {
266+
LOG_ERR("Address out of range");
267+
return -EINVAL;
268+
}
269+
270+
err = modbus_write_holding_reg(data->iface, data->modbus_address,
271+
MODBUS_RTU_ADDRESS_REGISTER, val->val1);
272+
273+
if (err != 0) {
274+
return err;
275+
}
276+
break;
277+
278+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_ADDRESS_INST_SET:
279+
if (val->val1 < 0 || val->val1 > PZEM004T_MAX_MODBUS_RTU_ADDRESS) {
280+
LOG_ERR("Address out of range");
281+
return -EINVAL;
282+
}
283+
284+
data->modbus_address = val->val1;
285+
break;
286+
287+
#if CONFIG_PZEM004T_ENABLE_RESET_ENERGY
288+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_RESET_ENERGY:
289+
err = pzem004t_reset_energy(data->iface, data->modbus_address);
290+
if (err != 0) {
291+
LOG_ERR("Failed to reset energy");
292+
return err;
293+
}
294+
break;
295+
#else
296+
case (enum sensor_attribute)SENSOR_ATTR_PZEM004T_RESET_ENERGY:
297+
LOG_ERR("Reset energy is not enabled by default. Enable "
298+
"CONFIG_PZEM004T_ENABLE_RESET_ENERGY in prj.conf.");
299+
return -ENOTSUP;
300+
#endif /* CONFIG_PZEM004T_ENABLE_RESET_ENERGY */
301+
302+
default:
303+
LOG_ERR("Unsupported Attribute");
304+
return -ENOTSUP;
305+
}
306+
307+
return 0;
308+
}
309+
310+
static DEVICE_API(sensor, pzem004t_api) = {
311+
.sample_fetch = pzem004t_sample_fetch,
312+
.channel_get = pzem004t_channel_get,
313+
.attr_get = pzem004t_attr_get,
314+
.attr_set = pzem004t_attr_set,
315+
};
316+
317+
#define PZEM004T_DEFINE(inst) \
318+
static const struct pzem004t_config pzem004t_config_##inst = { \
319+
.modbus_iface_name = DEVICE_DT_NAME(DT_PARENT(DT_INST(inst, peacefair_pzem004t))), \
320+
.client_param = \
321+
{ \
322+
.mode = MODBUS_MODE_RTU, \
323+
.rx_timeout = 100000, \
324+
.serial = \
325+
{ \
326+
.baud = 9600, \
327+
.parity = UART_CFG_PARITY_NONE, \
328+
.stop_bits_client = UART_CFG_STOP_BITS_1, \
329+
}, \
330+
}, \
331+
}; \
332+
\
333+
static struct pzem004t_data pzem004t_data_##inst = { \
334+
.modbus_address = PZEM004T_DEFAULT_MODBUS_ADDRESS, \
335+
}; \
336+
\
337+
SENSOR_DEVICE_DT_INST_DEFINE(inst, &pzem004t_init, NULL, &pzem004t_data_##inst, \
338+
&pzem004t_config_##inst, POST_KERNEL, \
339+
CONFIG_SENSOR_INIT_PRIORITY, &pzem004t_api);
340+
341+
DT_INST_FOREACH_STATUS_OKAY(PZEM004T_DEFINE)

0 commit comments

Comments
 (0)