Skip to content

Commit 54194cb

Browse files
committed
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 f412cc6 commit 54194cb

File tree

11 files changed

+512
-0
lines changed

11 files changed

+512
-0
lines changed

drivers/sensor/CMakeLists.txt

+1
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

+1
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"
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#SPDX - License - Identifier : Apache - 2.0
2+
3+
zephyr_library()
4+
5+
zephyr_library_sources(pzem004t.c)

drivers/sensor/pzem004t/Kconfig

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
select MODBUS
11+
select MODBUS_SERIAL
12+
select 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+
default n
19+
depends on PZEM004T
20+
help
21+
Enable support for resetting the energy counter on the PZEM004T device.

drivers/sensor/pzem004t/pzem004t.c

+338
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
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 != 0x42) {
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+
static void register_custom_fc(const int iface)
43+
{
44+
MODBUS_CUSTOM_FC_DEFINE(custom_fc, custom_fc_handler, 0x42, NULL);
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 = 0x42,
60+
.length = 0,
61+
};
62+
63+
int err = modbus_raw_backend_txn(iface, &adu);
64+
65+
if (err) {
66+
return err;
67+
}
68+
69+
if (adu.fc == 0x42) {
70+
return 0;
71+
} else if (adu.fc == 0xC2) {
72+
return -EIO;
73+
} else {
74+
return -EIO;
75+
}
76+
return 0;
77+
}
78+
79+
#endif /* CONFIG_PZEM004T_ENABLE_RESET_ENERGY */
80+
81+
/**
82+
* @brief Check if the Modbus client is initialized
83+
*
84+
* @param iface The Modbus interface index
85+
* @return true if the Modbus client is initialized, false otherwise
86+
*/
87+
bool is_modbus_client_initialized(int iface)
88+
{
89+
struct modbus_adu adu = {0};
90+
int ret;
91+
92+
/* Prepare a dummy ADU to test the interface */
93+
adu.fc = 0x03;
94+
adu.unit_id = 1;
95+
adu.length = 0;
96+
97+
ret = modbus_raw_backend_txn(iface, &adu);
98+
99+
if (ret == 0) {
100+
return true;
101+
} else {
102+
return false;
103+
}
104+
}
105+
106+
static int pzem004t_init(const struct device *dev)
107+
{
108+
const struct pzem004t_config *config = dev->config;
109+
struct pzem004t_data *data = dev->data;
110+
int iface = modbus_iface_get_by_name(config->modbus_iface_name);
111+
112+
if (iface < 0) {
113+
LOG_ERR("Failed to get Modbus interface: %s", config->modbus_iface_name);
114+
return -ENODEV;
115+
}
116+
117+
if (!(is_modbus_client_initialized(iface))) {
118+
int err = modbus_init_client(iface, config->client_param);
119+
120+
if (err) {
121+
LOG_ERR("Modbus RTU client initialization failed (err %d)", err);
122+
return err;
123+
}
124+
}
125+
126+
data->iface = iface;
127+
128+
#if CONFIG_PZEM004T_ENABLE_RESET_ENERGY
129+
register_custom_fc(data->iface);
130+
#endif /* CONFIG_PZEM004T_ENABLE_RESET_ENERGY */
131+
132+
return 0;
133+
}
134+
135+
static int pzem004t_sample_fetch(const struct device *dev, enum sensor_channel chan)
136+
{
137+
const struct pzem004t_config *config = dev->config;
138+
struct pzem004t_data *sensor_data = dev->data;
139+
140+
uint16_t reg_buf[MESUREMENT_REGISTER_TOTAL_LENGTH] = {0};
141+
int err = modbus_read_input_regs(sensor_data->iface, config->modbus_address,
142+
MESUREMENT_REGISTER_START_ADDRESS, reg_buf,
143+
MESUREMENT_REGISTER_TOTAL_LENGTH);
144+
145+
if (err != 0) {
146+
LOG_ERR("Failed to fetch sensor data at address 0x%02x: %d",
147+
config->modbus_address, err);
148+
return err;
149+
}
150+
151+
sensor_data->voltage = reg_buf[0];
152+
sensor_data->current = ((reg_buf[2] << 16) | reg_buf[1]);
153+
sensor_data->power = ((reg_buf[4] << 16) | reg_buf[3]);
154+
sensor_data->energy = ((reg_buf[6] << 16) | reg_buf[5]);
155+
sensor_data->frequency = reg_buf[7];
156+
sensor_data->power_factor = reg_buf[8];
157+
sensor_data->alarm_status = reg_buf[9];
158+
159+
return 0;
160+
}
161+
162+
static int pzem004t_channel_get(const struct device *dev, enum sensor_channel chan,
163+
struct sensor_value *val)
164+
{
165+
struct pzem004t_data *sensor_data = dev->data;
166+
167+
switch ((uint32_t)chan) {
168+
case SENSOR_CHAN_VOLTAGE:
169+
val->val1 = sensor_data->voltage / PZEM004T_VOLTAGE_SCALE;
170+
val->val2 = (sensor_data->voltage % PZEM004T_VOLTAGE_SCALE);
171+
break;
172+
case SENSOR_CHAN_CURRENT:
173+
val->val1 = sensor_data->current / PZEM004T_CURRENT_SCALE;
174+
val->val2 = (sensor_data->current % PZEM004T_CURRENT_SCALE);
175+
break;
176+
case SENSOR_CHAN_POWER:
177+
val->val1 = sensor_data->power / PZEM004T_POWER_SCALE;
178+
val->val2 = (sensor_data->power % PZEM004T_POWER_SCALE);
179+
break;
180+
case (enum sensor_channel)SENSOR_CHAN_ENERGY:
181+
val->val1 = sensor_data->energy / PZEM004T_ENERGY_SCALE;
182+
val->val2 = (sensor_data->energy % PZEM004T_ENERGY_SCALE);
183+
break;
184+
case SENSOR_CHAN_FREQUENCY:
185+
val->val1 = sensor_data->frequency / PZEM004T_FREQUENCY_SCALE;
186+
val->val2 = (sensor_data->frequency % PZEM004T_FREQUENCY_SCALE);
187+
break;
188+
case (enum sensor_channel)SENSOR_CHAN_POWER_FACTOR:
189+
val->val1 = sensor_data->power_factor / PZEM004T_POWER_FACTOR_SCALE;
190+
val->val2 = (sensor_data->power_factor % PZEM004T_POWER_FACTOR_SCALE);
191+
break;
192+
case (enum sensor_channel)SENSOR_CHAN_ALARM_STATUS:
193+
val->val1 = sensor_data->alarm_status;
194+
val->val2 = 0;
195+
break;
196+
default:
197+
return -ENOTSUP;
198+
}
199+
200+
return 0;
201+
}
202+
203+
static int pzem004t_attr_get(const struct device *dev, enum sensor_channel chan,
204+
enum sensor_attribute attr, struct sensor_value *val)
205+
{
206+
const struct pzem004t_config *config = dev->config;
207+
struct pzem004t_data *data = dev->data;
208+
209+
int err;
210+
uint16_t reg_buf[1] = {0};
211+
212+
if (chan != (enum sensor_channel)SENSOR_CHAN_POWER_ALARM_THRESHOLD &&
213+
chan != (enum sensor_channel)SENSOR_CHAN_MODBUS_RTU_ADDRESS) {
214+
LOG_ERR("Channel not supported for setting Request");
215+
return -ENOTSUP;
216+
}
217+
218+
switch ((uint32_t)attr) {
219+
case (enum sensor_attribute)SENSOR_ATTR_POWER_ALARM_THRESHOLD:
220+
err = modbus_read_holding_regs(data->iface, config->modbus_address,
221+
POWER_ALARM_THRESHOLD_ADDRESS, reg_buf,
222+
POWER_ALARM_THRESHOLD_REGISTER_LENGTH);
223+
val->val1 = reg_buf[0];
224+
val->val2 = 0;
225+
break;
226+
227+
case (enum sensor_attribute)SENSOR_ATTR_MODBUS_RTU_ADDRESS:
228+
err = modbus_read_holding_regs(data->iface, config->modbus_address,
229+
MODBUS_RTU_ADDRESS_REGISTER, reg_buf,
230+
MODBUS_RTU_ADDRESS_REGISTER_LENGTH);
231+
val->val1 = reg_buf[0];
232+
val->val2 = 0;
233+
break;
234+
235+
default:
236+
LOG_ERR("Unsupported Attribute");
237+
return -ENOTSUP;
238+
}
239+
240+
return 0;
241+
}
242+
243+
static int pzem004t_attr_set(const struct device *dev, enum sensor_channel chan,
244+
enum sensor_attribute attr, const struct sensor_value *val)
245+
{
246+
const struct pzem004t_config *config = dev->config;
247+
struct pzem004t_data *data = dev->data;
248+
int err;
249+
250+
if (chan != (enum sensor_channel)SENSOR_CHAN_POWER_ALARM_THRESHOLD &&
251+
chan != (enum sensor_channel)SENSOR_CHAN_MODBUS_RTU_ADDRESS &&
252+
chan != (enum sensor_channel)SENSOR_CHAN_RESET_ENERGY) {
253+
LOG_ERR("Channel not supported for setting attribute");
254+
return -ENOTSUP;
255+
}
256+
257+
switch ((uint32_t)attr) {
258+
case (enum sensor_attribute)SENSOR_ATTR_POWER_ALARM_THRESHOLD:
259+
if (val->val1 < 0 || val->val1 > PZEM004T_MAX_POWER_ALARM_THRESHOLD) {
260+
LOG_ERR("Power alarm threshold out of range");
261+
return -EINVAL;
262+
}
263+
264+
err = modbus_write_holding_reg(data->iface, config->modbus_address,
265+
POWER_ALARM_THRESHOLD_ADDRESS, val->val1);
266+
if (err != 0) {
267+
return err;
268+
}
269+
break;
270+
271+
case (enum sensor_attribute)SENSOR_ATTR_MODBUS_RTU_ADDRESS:
272+
if (val->val1 < 0 || val->val1 > PZEM004T_MAX_MODBUS_RTU_ADDRESS) {
273+
LOG_ERR("Address out of range");
274+
return -EINVAL;
275+
}
276+
277+
err = modbus_write_holding_reg(data->iface, config->modbus_address,
278+
MODBUS_RTU_ADDRESS_REGISTER, val->val1);
279+
280+
if (err != 0) {
281+
return err;
282+
}
283+
break;
284+
285+
#if CONFIG_PZEM004T_ENABLE_RESET_ENERGY
286+
case (enum sensor_attribute)SENSOR_ATTR_RESET_ENERGY:
287+
err = pzem004t_reset_energy(data->iface, config->modbus_address);
288+
if (err != 0) {
289+
LOG_ERR("Failed to reset energy");
290+
return err;
291+
}
292+
break;
293+
#else
294+
case SENSOR_ATTR_RESET_ENERGY:
295+
LOG_ERR("Reset energy is not enabled by default. Enable "
296+
"CONFIG_PZEM004T_ENABLE_RESET_ENERGY in prj.conf.");
297+
return -ENOTSUP;
298+
#endif /* CONFIG_PZEM004T_ENABLE_RESET_ENERGY */
299+
300+
default:
301+
LOG_ERR("Unsupported Attribute");
302+
return -ENOTSUP;
303+
}
304+
305+
return 0;
306+
}
307+
308+
static DEVICE_API(sensor, pzem004t_api) = {
309+
.sample_fetch = pzem004t_sample_fetch,
310+
.channel_get = pzem004t_channel_get,
311+
.attr_get = pzem004t_attr_get,
312+
.attr_set = pzem004t_attr_set,
313+
};
314+
315+
#define PZEM004T_DEFINE(inst) \
316+
static const struct pzem004t_config pzem004t_config_##inst = { \
317+
.modbus_iface_name = DEVICE_DT_NAME(DT_PARENT(DT_INST(inst, peacefair_pzem004t))), \
318+
.modbus_address = DT_INST_PROP(inst, modbus_address), \
319+
.client_param = \
320+
{ \
321+
.mode = MODBUS_MODE_RTU, \
322+
.rx_timeout = 100000, \
323+
.serial = \
324+
{ \
325+
.baud = 9600, \
326+
.parity = UART_CFG_PARITY_NONE, \
327+
.stop_bits_client = UART_CFG_STOP_BITS_1, \
328+
}, \
329+
}, \
330+
}; \
331+
\
332+
static struct pzem004t_data pzem004t_data_##inst; \
333+
\
334+
SENSOR_DEVICE_DT_INST_DEFINE(inst, &pzem004t_init, NULL, &pzem004t_data_##inst, \
335+
&pzem004t_config_##inst, POST_KERNEL, \
336+
CONFIG_SENSOR_INIT_PRIORITY, &pzem004t_api);
337+
338+
DT_INST_FOREACH_STATUS_OKAY(PZEM004T_DEFINE)

0 commit comments

Comments
 (0)