From ab8823a80d2d7b323dfcb20e646052a5e9e00ee8 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 5 Aug 2025 22:41:26 +0200 Subject: [PATCH] [P102] Add support for PZEM-017v1 --- lib/PZEM-004T-v30-master/PZEM004Tv30.cpp | 200 ++++++++++++++++++----- lib/PZEM-004T-v30-master/PZEM004Tv30.h | 35 +++- src/_P102_PZEM004Tv3.ino | 74 ++++++--- 3 files changed, 238 insertions(+), 71 deletions(-) diff --git a/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp b/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp index fd834487f..13c500f40 100644 --- a/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp +++ b/lib/PZEM-004T-v30-master/PZEM004Tv30.cpp @@ -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 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(); diff --git a/lib/PZEM-004T-v30-master/PZEM004Tv30.h b/lib/PZEM-004T-v30-master/PZEM004Tv30.h index 6579b0418..c5a65e9c3 100644 --- a/lib/PZEM-004T-v30-master/PZEM004Tv30.h +++ b/lib/PZEM-004T-v30-master/PZEM004Tv30.h @@ -28,9 +28,20 @@ #include #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 diff --git a/src/_P102_PZEM004Tv3.ino b/src/_P102_PZEM004Tv3.ino index 545d5471e..a2fd17e5e 100644 --- a/src/_P102_PZEM004Tv3.ino +++ b/src/_P102_PZEM004Tv3.ino @@ -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("
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(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("
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.
")); + addHtml(F("
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.
")); { 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 single PZEMv30 whatever its address")); + addHtml(F(" Address 0 allows to communicate with any single 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(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; } }