[P102] Add support for PZEM-017v1

This commit is contained in:
Ton Huisman
2025-08-05 22:41:26 +02:00
parent c967bbc626
commit ab8823a80d
3 changed files with 238 additions and 71 deletions
+158 -42
View File
@@ -14,6 +14,10 @@
#define REG_PF 0x0008
#define REG_ALARM 0x0009
#define REG_CURRENT 0x0001
#define REG_HVALARM 0x0006
#define REG_LVALARM 0x0007
#define CMD_RHR 0x03
#define CMD_RIR 0X04
#define CMD_WSR 0x06
@@ -24,6 +28,9 @@
#define WREG_ALARM_THR 0x0001
#define WREG_ADDR 0x0002
#define WREG_HV_ALARM_THR 0x0000
#define WREG_LV_ALARM_THR 0x0001
#define UPDATE_TIME 200
#define RESPONSE_SIZE 32
@@ -84,6 +91,23 @@ PZEM004Tv30::~PZEM004Tv30()
// delete this->_serial;
}
void PZEM004Tv30::setModel(PZEM_model model) {
if (model == _model) {
return;
}
_model = model;
switch (_model) {
case PZEM_model::PZEM004Tv30:
_expectedResponse = PZEM004Tv30_EXPECTED_RESPONSE;
_requestRegisters = PZEM004Tv30_REQUEST_REGISTERS;
break;
case PZEM_model::PZEM017Tv1:
_expectedResponse = PZEM017v1_EXPECTED_RESPONSE;
_requestRegisters = PZEM017v1_REQUEST_REGISTERS;
break;
}
}
/*! * PZEM004Tv30::voltage *
* Get line voltage in Volts *
* @return current L-N volage*/
@@ -222,28 +246,97 @@ uint8_t PZEM004Tv30::getAddress()
* Set power alarm threshold in watts *
* @param[in] watts Alamr theshold *
* @return success*/
bool PZEM004Tv30::setPowerAlarm(uint16_t watts)
{
if (watts > 25000){ // Sanitych check
watts = 25000;
}
// bool PZEM004Tv30::setPowerAlarm(uint16_t watts)
// {
// if (watts > 25000){ // Sanitych check
// watts = 25000;
// }
// Write the watts threshold to the Alarm register
if(!sendCmd8(CMD_WSR, WREG_ALARM_THR, watts, true))
return false;
return true;
}
// // Write the watts threshold to the Alarm register
// if(!sendCmd8(CMD_WSR, WREG_ALARM_THR, watts, true))
// return false;
// return true;
// }
/*! * PZEM004Tv30::getPowerAlarm *
* Is the power alarm set * *
* @return arlam triggerd*/
bool PZEM004Tv30::getPowerAlarm()
{
if(!updateValues()) // Update vales if necessary
return NAN; // Update did not work, return NAN
// bool PZEM004Tv30::getPowerAlarm()
// {
// if(!updateValues()) // Update vales if necessary
// return NAN; // Update did not work, return NAN
// return _currentValues.alarms != 0x0000;
// }
/*!
* PZEM004Tv30::setHighVoltAlarm
* Set HV alarm threshold in volts
* @param[in] volt Alarm theshold
* @return success */
// bool PZEM004Tv30::setHighvoltAlarm(uint16_t volts)
// {
// if (volts < 500){ // Sanity check
// volts = 500;
// }
// if (volts > 34999){ // Sanity check
// volts = 34999;
// }
// // Write the volts threshold to the alarm register
// if(!sendCmd8(CMD_WSR, WREG_HV_ALARM_THR, volts, true))
// return false;
// return true;
// }
/*!
* PZEM004Tv30::setLowVoltAlarm
* Set LV alarm threshold in volts
* @param[in] volt Alarm theshold
* @return success */
// bool PZEM004Tv30::setLowvoltAlarm(uint16_t volts)
// {
// if (volts < 100){ // Sanity check
// volts = 100;
// }
// if (volts > 34999){ // Sanity check
// volts = 34999;
// }
// // Write the volts threshold to the alarm register
// if(!sendCmd8(CMD_WSR, WREG_LV_ALARM_THR, volts, true))
// return false;
// return true;
// }
/*!
* PZEM004Tv30::isHighVoltAlarmOn GET
* Is the HV alarm set
* @return alarm triggerd*/
// bool PZEM004Tv30::isHighvoltAlarmOn()
// {
// if(!updateValues()) // Update vales if necessary
// return NAN; // Update did not work, return NAN
// return _currentValues.HVAlarms != 0x0000;
// }
/*!
* PZEM004Tv30::isLowVoltAlarmOn GET
* Is the LV alarm set
* @return alarm triggerd*/
// bool PZEM004Tv30::isLowvoltAlarmOn()
// {
// if(!updateValues()) // Update vales if necessary
// return NAN; // Update did not work, return NAN
// return _currentValues.LVAlarms != 0x0000;
// }
return _currentValues.alarms != 0x0000;
}
/*! * PZEM004Tv30::init *
* initialization common to all consturctors *
@@ -266,52 +359,75 @@ void PZEM004Tv30::init(uint8_t addr){
* @return success*/
bool PZEM004Tv30::updateValues()
{
//static uint8_t buffer[] = {0x00, CMD_RIR, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00};
static uint8_t response[25];
// If we read before the update time limit, do not update
if(_lastRead + UPDATE_TIME > millis()){
return true;
}
// Read 10 registers starting at 0x00 (no check)
sendCmd8(CMD_RIR, 0x00, 0x0A, false);
// Read <model> registers starting at 0x00 (no check)
sendCmd8(CMD_RIR, 0x00, _requestRegisters, false);
if(recieve(response, 25) != 25){ // Something went wrong
if(recieve(_response, _expectedResponse) != _expectedResponse){ // Something went wrong
return false;
}
// Update the current values
_currentValues.voltage = ((uint32_t)response[3] << 8 | // Raw voltage in 0.1V
(uint32_t)response[4])/10.0;
switch(_model) {
case PZEM_model::PZEM004Tv30:
_currentValues.voltage = ((uint32_t)_response[3] << 8 | // Raw voltage in 0.1V
(uint32_t)_response[4]) / 10.0;
_currentValues.current = ((uint32_t)response[5] << 8 | // Raw current in 0.001A
(uint32_t)response[6] |
(uint32_t)response[7] << 24 |
(uint32_t)response[8] << 16) / 1000.0;
_currentValues.current = ((uint32_t)_response[5] << 8 | // Raw current in 0.001A
(uint32_t)_response[6] |
(uint32_t)_response[7] << 24 |
(uint32_t)_response[8] << 16) / 1000.0;
_currentValues.power = ((uint32_t)response[9] << 8 | // Raw power in 0.1W
(uint32_t)response[10] |
(uint32_t)response[11] << 24 |
(uint32_t)response[12] << 16) / 10.0;
_currentValues.power = ((uint32_t)_response[9] << 8 | // Raw power in 0.1W
(uint32_t)_response[10] |
(uint32_t)_response[11] << 24 |
(uint32_t)_response[12] << 16) / 10.0;
_currentValues.energy = ((uint32_t)response[13] << 8 | // Raw Energy in 1Wh
(uint32_t)response[14] |
(uint32_t)response[15] << 24 |
(uint32_t)response[16] << 16) / 1000.0;
_currentValues.energy = ((uint32_t)_response[13] << 8 | // Raw Energy in 1Wh
(uint32_t)_response[14] |
(uint32_t)_response[15] << 24 |
(uint32_t)_response[16] << 16) / 1000.0;
_currentValues.frequeny =((uint32_t)response[17] << 8 | // Raw Frequency in 0.1Hz
(uint32_t)response[18]) / 10.0;
_currentValues.frequeny =((uint32_t)_response[17] << 8 | // Raw Frequency in 0.1Hz
(uint32_t)_response[18]) / 10.0;
_currentValues.pf = ((uint32_t)response[19] << 8 | // Raw pf in 0.01
(uint32_t)response[20])/100.0;
_currentValues.pf = ((uint32_t)_response[19] << 8 | // Raw pf in 0.01
(uint32_t)_response[20]) / 100.0;
_currentValues.alarms = ((uint32_t)response[21] << 8 | // Raw alarm value
(uint32_t)response[22]);
_currentValues.alarms = ((uint32_t)_response[21] << 8 | // Raw alarm value
(uint32_t)_response[22]);
break;
case PZEM_model::PZEM017Tv1:
_currentValues.voltage = ((uint32_t)_response[3] << 8 | // Raw voltage in 0.01V
(uint32_t)_response[4]) / 100.0;
_currentValues.current = ((uint32_t)_response[5] << 8 | // Raw voltage in 0.01A
(uint32_t)_response[6]) / 100.0;
_currentValues.power = ((uint32_t)_response[7] << 8 | // Raw power in 0.1W
(uint32_t)_response[8] |
(uint32_t)_response[9] << 24 |
(uint32_t)_response[10] << 16) / 10.0;
_currentValues.energy = ((uint32_t)_response[11] << 8 | // Raw Energy in 1Wh
(uint32_t)_response[12] |
(uint32_t)_response[13] << 24 |
(uint32_t)_response[14] << 16) / 1000.0;
_currentValues.HVAlarms = ((uint32_t)_response[15] << 8 | // Raw alarm value
(uint32_t)_response[16]);
_currentValues.LVAlarms = ((uint32_t)_response[17] << 8 | // Raw alarm value
(uint32_t)_response[18]);
break;
}
// Record current time as _lastRead
_lastRead = millis();
+30 -5
View File
@@ -28,9 +28,20 @@
#include <ESPeasySerial.h>
#endif
#define PZEM004Tv30_EXPECTED_RESPONSE 25
#define PZEM004Tv30_REQUEST_REGISTERS 10
#define PZEM017v1_EXPECTED_RESPONSE 21
#define PZEM017v1_REQUEST_REGISTERS 8
#define PZEM_DEFAULT_ADDR 0xF8
#define PZEM_MAX_EXPECTED_RESPONSE 25 // PZEM004v30 needs this buffer size, adjust for models that need a bigger buffer
enum class PZEM_model : uint8_t {
PZEM004Tv30 = 0, // Buffer size 25, request 10 registers
PZEM017Tv1 = 1, // Buffer size 21, request 8 registers
};
class PZEM004Tv30
{
@@ -40,6 +51,7 @@ public:
#endif
~PZEM004Tv30();
void setModel(PZEM_model model); // Sets some communication buffer params
float voltage();
float current();
@@ -52,8 +64,14 @@ public:
bool setAddress(uint8_t addr);
uint8_t getAddress();
bool setPowerAlarm(uint16_t watts);
bool getPowerAlarm();
// Unused methods:
// bool setPowerAlarm(uint16_t watts);
// bool getPowerAlarm();
// bool setHighvoltAlarm(uint16_t volts); //moja uprava
// bool setLowvoltAlarm(uint16_t volts); //moja uprava
// bool isHighvoltAlarmOn(); //upravil som
// bool isLowvoltAlarmOn(); //upravil som
bool resetEnergy();
void init(uint8_t addr); // Init common to all constructors
@@ -64,15 +82,22 @@ private:
bool _isSoft; // Is serial interface software
uint8_t _addr; // Device address
PZEM_model _model = PZEM_model::PZEM004Tv30; // Current default
uint8_t _response[PZEM_MAX_EXPECTED_RESPONSE];
uint8_t _expectedResponse = PZEM004Tv30_EXPECTED_RESPONSE;
uint8_t _requestRegisters = PZEM004Tv30_REQUEST_REGISTERS;
struct {
float voltage;
float current;
float power;
float energy;
float frequeny;
float pf;
uint16_t alarms;
float frequeny; // PZEM004
float pf; // PZEM004
uint16_t alarms; // PZEM004
uint16_t HVAlarms; //upravil som PZEM017
uint16_t LVAlarms; //upravil sOM PZEM017
} _currentValues; // Measured values
uint64_t _lastRead; // Last time values were updated
+50 -24
View File
@@ -8,6 +8,8 @@
//
/** Changelog:
* 2025-08-05 tonhuisman: Add support for PZEM-017v1 from a forum suggestion:
* https://www.letscontrolit.com/forum/viewtopic.php?p=74069#p74064
* 2025-01-17 tonhuisman: Implement support for MQTT AutoDiscovery (partially)
* 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported yet for PZEM00x)
*/
@@ -21,7 +23,7 @@
# define PLUGIN_102
# define PLUGIN_ID_102 102
# define PLUGIN_102_DEBUG true // activate extra log info in the debug
# define PLUGIN_NAME_102 "Energy (AC) - PZEM-004Tv30-Multiple"
# define PLUGIN_NAME_102 "Energy (AC) - PZEM-004Tv30 / PZEM-017v1"
# define P102_PZEM_mode PCONFIG(1) // 0=read value ; 1=reset energy; 2=programm address
# define P102_PZEM_ADDR PCONFIG(2)
@@ -32,6 +34,7 @@
# define P102_QUERY4 PCONFIG(6)
# define P102_PZEM_FIRST PCONFIG(7)
# define P102_PZEM_ATTEMPT PCONFIG_LONG(1)
# define P102_PZEM_TYPE PCONFIG_LONG(0)
# define P102_PZEM_mode_DFLT 0 // Read value
# define P102_QUERY1_DFLT 0 // Voltage (V)
@@ -44,6 +47,10 @@
# define P102_PZEM_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value
# define P102_PZEM004_VALUE_COUNT 6
# define P102_PZEM017_VALUE_COUNT 4
PZEM004Tv30 * P102_PZEM_sensor = nullptr;
boolean Plugin_102_init = false;
@@ -56,7 +63,7 @@ const __FlashStringHelper* p102_getQueryString(uint8_t query);
boolean Plugin_102(uint8_t function, struct EventStruct *event, String& string)
{
boolean success = false;
bool success = false;
switch (function)
{
@@ -126,6 +133,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
case PLUGIN_WEBFORM_SHOW_CONFIG:
{
string += serialHelper_getSerialTypeLabel(event);
string += strformat(F("<BR>addr: 0x%02x"), P102_PZEM_ADDR); // Show modbus address
success = true;
break;
}
@@ -134,14 +142,17 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
{
// To select the data in the 4 fields.
const __FlashStringHelper *options[P102_NR_OUTPUT_OPTIONS];
const uint8_t nrOptions = PZEM_model::PZEM004Tv30 == static_cast<PZEM_model>(P102_PZEM_TYPE)
? P102_PZEM004_VALUE_COUNT
: P102_PZEM017_VALUE_COUNT;
for (uint8_t i = 0; i < P102_NR_OUTPUT_OPTIONS; ++i) {
for (uint8_t i = 0; i < nrOptions; ++i) {
options[i] = p102_getQueryString(i);
}
for (uint8_t i = 0; i < P102_NR_OUTPUT_VALUES; ++i) {
const uint8_t pconfigIndex = i + P102_QUERY1_CONFIG_POS;
sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P102_NR_OUTPUT_OPTIONS, options);
sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, nrOptions, options);
}
success = true;
@@ -149,7 +160,19 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
break;
}
case PLUGIN_WEBFORM_LOAD: {
case PLUGIN_WEBFORM_LOAD:
{
{
const __FlashStringHelper*pzemModels[] = {
F("PZEM004Tv30"),
F("PZEM017v1"),
};
constexpr int pzemCount = NR_ELEMENTS(pzemModels);
FormSelectorOptions selector(pzemCount, pzemModels);
selector.reloadonchange = true;
selector.addFormSelector(F("PZEM Model"), F("pztype"), P102_PZEM_TYPE);
}
if (P102_PZEM_sensor == nullptr) { P102_PZEM_FIRST = event->TaskIndex; // To detect if first PZEM or not
}
@@ -168,8 +191,8 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
if (P102_PZEM_mode == 2)
{
addHtml(F("<span style=\"color:red\"> <br>When programming an address, only one PZEMv30 must be connected. "
"Otherwise, all connected PZEMv30s will get the same address, which would cause a conflict during reading.</span>"));
addHtml(F("<span style=\"color:red\"> <br>When programming an address, only one PZEM must be connected. "
"Otherwise, all connected PZEMs will get the same address, which would cause a conflict during reading.</span>"));
{
const __FlashStringHelper *options_confirm[] = { F("NO"), F("YES") };
constexpr size_t optionCount = NR_ELEMENTS(options_confirm);
@@ -182,7 +205,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
else
{
addFormNumericBox(F("Address of PZEM"), F("PZEM_addr"), P102_PZEM_ADDR, 0, 247);
addHtml(F(" Address 0 allows to communicate with any <B>single</B> PZEMv30 whatever its address"));
addHtml(F(" Address 0 allows to communicate with any <B>single</B> PZEM whatever its address"));
}
if (P102_PZEM_ADDR_SET == 3) // If address programming done
@@ -216,7 +239,8 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
break;
}
case PLUGIN_WEBFORM_SAVE: {
case PLUGIN_WEBFORM_SAVE:
{
serialHelper_webformSave(event);
// Save output selector parameters.
@@ -225,6 +249,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
const uint8_t choice = PCONFIG(pconfigIndex);
sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, p102_getQueryString(choice));
}
P102_PZEM_TYPE = getFormItemInt(F("pztype"));
P102_PZEM_mode = getFormItemInt(F("PZEM_mode"));
P102_PZEM_ADDR = getFormItemInt(F("PZEM_addr"));
P102_PZEM_ADDR_SET = getFormItemInt(F("PZEM_addr_set"));
@@ -346,13 +371,18 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
// FIXME TD-er: Calling these functions is probably done within the 200 msec timeout used in the library.
// If not, this should be cached in a task data struct.
string += LoRa_addFloat(P102_PZEM_sensor->voltage(), PackedData_int16_1e1);
string += LoRa_addFloat(P102_PZEM_sensor->current(), PackedData_int32_1e3);
string += LoRa_addFloat(P102_PZEM_sensor->power(), PackedData_int32_1e1);
string += LoRa_addFloat(P102_PZEM_sensor->energy(), PackedData_int32_1e1);
string += LoRa_addFloat(P102_PZEM_sensor->pf(), PackedData_uint16_1e2);
string += LoRa_addFloat(P102_PZEM_sensor->frequency() - 40, PackedData_uint8_1e1);
event->Par1 = 6; // valuecount
string += LoRa_addFloat(P102_PZEM_sensor->voltage(), PackedData_int16_1e1);
string += LoRa_addFloat(P102_PZEM_sensor->current(), PackedData_int32_1e3);
string += LoRa_addFloat(P102_PZEM_sensor->power(), PackedData_int32_1e1);
string += LoRa_addFloat(P102_PZEM_sensor->energy(), PackedData_int32_1e1);
if (PZEM_model::PZEM004Tv30 == static_cast<PZEM_model>(P102_PZEM_TYPE)) {
string += LoRa_addFloat(P102_PZEM_sensor->pf(), PackedData_uint16_1e2);
string += LoRa_addFloat(P102_PZEM_sensor->frequency() - 40, PackedData_uint8_1e1);
event->Par1 = P102_PZEM004_VALUE_COUNT; // valuecount
} else {
event->Par1 = P102_PZEM017_VALUE_COUNT; // valuecount
}
success = true;
break;
@@ -366,14 +396,10 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
{
const String command = parseString(string, 1);
if ((equals(command, F("resetenergy"))) && (P102_PZEM_FIRST == event->TaskIndex))
{
if ((event->Par1 >= 0) && (event->Par1 <= 247))
{
P102_PZEM_sensor->init(event->Par1);
P102_PZEM_sensor->resetEnergy();
success = true;
}
if (equals(command, F("resetenergy")) && (event->Par1 >= 0) && (event->Par1 <= 247)) {
P102_PZEM_sensor->init(event->Par1);
P102_PZEM_sensor->resetEnergy();
success = true;
}
}