Merge pull request #3337 from TD-er/playground/PZEM004Tv30_Multiple

[Playground] Move P251 PZEM 004T-v30 from Playground to main repo
This commit is contained in:
TD-er
2020-11-17 22:48:34 +01:00
committed by GitHub
11 changed files with 1172 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jakub Mandula
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+450
View File
@@ -0,0 +1,450 @@
#include "PZEM004Tv30.h"
#include <stdio.h>
#include <ESPeasySerial.h>
#define REG_VOLTAGE 0x0000
#define REG_CURRENT_L 0x0001
#define REG_CURRENT_H 0X0002
#define REG_POWER_L 0x0003
#define REG_POWER_H 0x0004
#define REG_ENERGY_L 0x0005
#define REG_ENERGY_H 0x0006
#define REG_FREQUENCY 0x0007
#define REG_PF 0x0008
#define REG_ALARM 0x0009
#define CMD_RHR 0x03
#define CMD_RIR 0X04
#define CMD_WSR 0x06
#define CMD_CAL 0x41
#define CMD_REST 0x42
#define WREG_ALARM_THR 0x0001
#define WREG_ADDR 0x0002
#define UPDATE_TIME 200
#define RESPONSE_SIZE 32
#define READ_TIMEOUT 100
#define PZEM_BAUD_RATE 9600
extern HardwareSerial Serial;
ESPeasySerial *swSerial = nullptr;
#define DEBUG
// Debugging function;
void printBuf(uint8_t* buffer, uint16_t len){
#ifdef DEBUG
for(uint16_t i = 0; i < len; i++){
char temp[6];
sprintf(temp, "%.2x ", buffer[i]);
Serial.print(temp);
}
Serial.println();
#endif
}
/*! * PZEM004Tv30::PZEM004Tv30 *
* Software Serial constructor *
* @param receivePin RX pin
* @param transmitPin TX pin
* @param addr Slave address of device*/
#if defined(PZEM004_SOFTSERIAL)
PZEM004Tv30::PZEM004Tv30(const ESPEasySerialPort port, uint8_t receivePin, uint8_t transmitPin, uint8_t addr)
{
swSerial= new ESPeasySerial(port, receivePin, transmitPin);
swSerial->begin(PZEM_BAUD_RATE);
init(addr);
}
#endif
/*!
* PZEM004Tv30::~PZEM004Tv30
*
* Destructor deleting software serial
*
*/
PZEM004Tv30::~PZEM004Tv30()
{
// if(_isSoft)
// delete this->_serial;
}
/*! * PZEM004Tv30::voltage *
* Get line voltage in Volts *
* @return current L-N volage*/
float PZEM004Tv30::voltage()
{
if(!updateValues()) // Update vales if necessary
return NAN; // Update did not work, return NAN
return _currentValues.voltage;
}
/*! * PZEM004Tv30::current *
* Get line in Amps *
* @return line current*/
float PZEM004Tv30::current()
{
if(!updateValues())// Update vales if necessary
return NAN; // Update did not work, return NAN
return _currentValues.current;
}
/*! * PZEM004Tv30::power *
* Get Active power in W *
* @return active power in W*/
float PZEM004Tv30::power()
{
if(!updateValues()) // Update vales if necessary
return NAN; // Update did not work, return NAN
return _currentValues.power;
}
/*! * PZEM004Tv30::energy *
* Get Active energy in kWh since last reset *
* @return active energy in kWh*/
float PZEM004Tv30::energy()
{
if(!updateValues()) // Update vales if necessary
return NAN; // Update did not work, return NAN
return _currentValues.energy;
}
/*! * PZEM004Tv30::frequeny *
* Get current line frequency in Hz *
* @return line frequency in Hz*/
float PZEM004Tv30::frequency()
{
if(!updateValues()) // Update vales if necessary
return NAN; // Update did not work, return NAN
return _currentValues.frequeny;
}
/*! * PZEM004Tv30::pf *
* Get power factor of load *
* @return load power factor*/
float PZEM004Tv30::pf()
{
if(!updateValues()) // Update vales if necessary
return NAN; // Update did not work, return NAN
return _currentValues.pf;
}
/*! * PZEM004Tv30::sendCmd8 *
* Prepares the 8 byte command buffer and sends *
* @param[in] cmd - Command to send (position 1)
* @param[in] rAddr - Register address (postion 2-3)
* @param[in] val - Register value to write (positon 4-5)
* @param[in] check - perform a simple read check after write *
* @return success*/
bool PZEM004Tv30::sendCmd8(uint8_t cmd, uint16_t rAddr, uint16_t val, bool check){
uint8_t sendBuffer[8]; // Send buffer
uint8_t respBuffer[8]; // Response buffer (only used when check is true)
sendBuffer[0] = _addr; // Set slave address
sendBuffer[1] = cmd; // Set command
sendBuffer[2] = (rAddr >> 8) & 0xFF; // Set high byte of register address
sendBuffer[3] = (rAddr) & 0xFF; // Set low byte =//=
sendBuffer[4] = (val >> 8) & 0xFF; // Set high byte of register value
sendBuffer[5] = (val) & 0xFF; // Set low byte =//=
setCRC(sendBuffer, 8); // Set CRC of frame
swSerial->write(sendBuffer, 8); // send frame
if(check) {
if(!recieve(respBuffer, 8)){ // if check enabled, read the response
return false;
}
// Check if response is same as send
for(uint8_t i = 0; i < 8; i++){
if(sendBuffer[i] != respBuffer[i])
return false;
}
}
return true;
}
/*!
* PZEM004Tv30::setAddress *
* Set a new device address and update the device
* WARNING - should be used to set up devices once.
* Code initializtion will still have old address on next run! *
* @param[in] addr New device address 0x01-0xF7 *
* @return success*/
bool PZEM004Tv30::setAddress(uint8_t addr)
{
if(addr < 0x01 || addr > 0xF7) // sanity check
return false;
// Write the new address to the address register
if(!sendCmd8(CMD_WSR, WREG_ADDR, addr, true))
return false;
_addr = addr; // If successful, update the current slave address
return true;
}
/*! * PZEM004Tv30::getAddress *
* Get the current device address *
* @return address*/
uint8_t PZEM004Tv30::getAddress()
{
return _addr;
}
/*!
* PZEM004Tv30::setPowerAlarm *
* 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;
}
// 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
return _currentValues.alarms != 0x0000;
}
/*! * PZEM004Tv30::init *
* initialization common to all consturctors *
* @param[in] addr - device address *
* @return success*/
void PZEM004Tv30::init(uint8_t addr){
if(addr < 0x01 || addr > 0xF8) // Sanity check of address
addr = PZEM_DEFAULT_ADDR;
_addr = addr;
// Set initial lastRed time so that we read right away
_lastRead = 0;
_lastRead -= UPDATE_TIME;
}
/*!
* PZEM004Tv30::updateValues *
* Read all registers of device and update the local values *
* @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);
if(recieve(response, 25) != 25){ // 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;
_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.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.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]);
// Record current time as _lastRead
_lastRead = millis();
return true;
}
/*! * PZEM004Tv30::resetEnergy *
* Reset the Energy counter on the device *
* @return success*/
bool PZEM004Tv30::resetEnergy(){
uint8_t buffer[] = {0x00, CMD_REST, 0x00, 0x00};
uint8_t reply[5];
buffer[0] = _addr;
setCRC(buffer, 4);
swSerial->write(buffer, 4);
uint16_t length = recieve(reply, 5);
if(length == 0 || length == 5){
return false;
}
return true;
}
/*!
* PZEM004Tv30::recieve *
* Receive data from serial with buffer limit and timeout *
* @param[out] resp Memory buffer to hold response. Must be at least `len` long
* @param[in] len Max number of bytes to read *
* @return number of bytes read*/
uint16_t PZEM004Tv30::recieve(uint8_t *resp, uint16_t len)
{
unsigned long startTime = millis(); // Start time for Timeout
uint8_t index = 0; // Bytes we have read
while((index < len) && (millis() - startTime < READ_TIMEOUT))
{
if(swSerial->available() > 0)
{
uint8_t c = (uint8_t)swSerial->read();
resp[index++] = c;
}
yield(); // do background netw tasks while blocked for IO (prevents ESP watchdog trigger)
}
// Check CRC with the number of bytes read
if(!checkCRC(resp, index)){
return 0;
}
return index;
}
/*! * PZEM004Tv30::checkCRC *
* Performs CRC check of the buffer up to len-2 and compares check sum to last two bytes *
* @param[in] data Memory buffer containing the frame to check
* @param[in] len Length of the respBuffer including 2 bytes for CRC *
* @return is the buffer check sum valid*/
bool PZEM004Tv30::checkCRC(const uint8_t *buf, uint16_t len){
if(len <= 2) // Sanity check
return false;
uint16_t crc = CRC16(buf, len - 2); // Compute CRC of data
return ((uint16_t)buf[len-2] | (uint16_t)buf[len-1] << 8) == crc;
}
/*! * PZEM004Tv30::setCRC *
* Set last two bytes of buffer to CRC16 of the buffer up to byte len-2
* Buffer must be able to hold at least 3 bytes
*
* @param[out] data Memory buffer containing the frame to checksum and write CRC to
* @param[in] len Length of the respBuffer including 2 bytes for CRC
*
*/
void PZEM004Tv30::setCRC(uint8_t *buf, uint16_t len){
if(len <= 2) // Sanity check
return;
uint16_t crc = CRC16(buf, len - 2); // CRC of data
// Write high and low byte to last two positions
buf[len - 2] = crc & 0xFF; // Low byte first
buf[len - 1] = (crc >> 8) & 0xFF; // High byte second
}
/*! * PZEM004Tv30::CRC16 *
* Calculate the CRC16-Modbus for a buffer
* Based on https://www.modbustools.com/modbus_crc16.html *
* @param[in] data Memory buffer containing the data to checksum
* @param[in] len Length of the respBuffer
*
* @return Calculated CRC
*/
uint16_t PZEM004Tv30::CRC16(const uint8_t *data, uint16_t len)
{
// Pre computed CRC table
static const uint16_t crcTable[] = {
0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };
uint8_t nTemp; // CRC table index
uint16_t crc = 0xFFFF; // Default value
while (len--)
{
nTemp = *data++ ^ crc;
crc >>= 8;
crc ^= crcTable[nTemp];
}
return crc;
}
+95
View File
@@ -0,0 +1,95 @@
/*
* PZEM-004Tv30.h
*
* Interface library for the upgraded version of PZEM-004T v3.0
* Based on the PZEM004T library by @olehs https://github.com/olehs/PZEM004T
*
* Author: Jakub Mandula https://github.com/mandulaj
*
*
*/
#ifndef PZEM004TV30_H
#define PZEM004TV30_H
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#define PZEM004_SOFTSERIAL
#if defined(PZEM004_SOFTSERIAL)
//#include <SoftwareSerial.h>
#include <ESPeasySerial.h>
#endif
#define PZEM_DEFAULT_ADDR 0xF8
class PZEM004Tv30
{
public:
#if defined(PZEM004_SOFTSERIAL)
PZEM004Tv30(const ESPEasySerialPort port, uint8_t receivePin, uint8_t transmitPin, uint8_t addr=PZEM_DEFAULT_ADDR);
#endif
~PZEM004Tv30();
float voltage();
float current();
float power();
float energy();
float frequency();
float pf();
bool setAddress(uint8_t addr);
uint8_t getAddress();
bool setPowerAlarm(uint16_t watts);
bool getPowerAlarm();
bool resetEnergy();
void init(uint8_t addr); // Init common to all constructors
private:
Stream* _serial; // Serial interface
bool _isSoft; // Is serial interface software
uint8_t _addr; // Device address
struct {
float voltage;
float current;
float power;
float energy;
float frequeny;
float pf;
uint16_t alarms;
} _currentValues; // Measured values
uint64_t _lastRead; // Last time values were updated
//void init(uint8_t addr); // Init common to all constructors
bool updateValues(); // Get most up to date values from device registers and cache them
uint16_t recieve(uint8_t *resp, uint16_t len); // Receive len bytes into a buffer
bool sendCmd8(uint8_t cmd, uint16_t rAddr, uint16_t val, bool check=false); // Send 8 byte command
void setCRC(uint8_t *buf, uint16_t len); // Set the CRC for a buffer
bool checkCRC(const uint8_t *buf, uint16_t len); // Check CRC of buffer
uint16_t CRC16(const uint8_t *data, uint16_t len); // Calculate CRC of buffer
};
#endif // PZEM004T_H
+83
View File
@@ -0,0 +1,83 @@
# PZEM-004T v3.0
ESPeasy library for Peacefair PZEM-004T-10A and PZEM-004T-100A v3.0 Energy monitor.
(Based on Arduino communication library for Peacefair PZEM-004T-10A and PZEM-004T-100A v3.0 Energy monitor.)
***
This module is an upgraded version of the PZEM-004T with frequency and power factor measurement features available at the usual places. It communicates using a TTL interface over a Modbus-RTU like communication protocol but is incompatible with the older [@olehs](https://github.com/olehs) library found here: [https://github.com/olehs/PZEM004T](https://github.com/olehs/PZEM004T). I would like to thank [@olehs](https://github.com/olehs) for the great library which inspired me to write this one.
### Manufacturer (optimistic) specifications
| Function | Measuring range | Resolution | Accuracy | TODO: Realistic specifications |
|---------------|--------------------|-----------------|----------|--------------------------------|
| Voltage | 80~260V | 0.1V | 0.5% | |
| Current | 0\~10A or 0\~100A* | 0.01A or 0.02A* | 0.5% | |
| Active power | 0\~2.3kW or 0\~23kW* | 0.1W | 0.5% | |
| Active energy | 0~9999.99kWh | 1Wh | 0.5% | |
| Frequency | 45~65Hz | 0.1Hz | 0.5% | |
| Power factor | 0.00~1.00 | 0.01 | 1% | |
\* Using the external current transformer instead of the built in shunt
#### Other features
* 247 unique programmable slave addresses
* Enables multiple slaves to use the same Serial interface
* Over power alarm
* Energy counter reset
* CRC16 checksum
* Better, but not perfect mains isolation
### Example
```c++
#include <PZEM004Tv30.h>
PZEM004Tv30 pzem(&Serial3);
void setup() {
Serial.begin(115200);
Serial.print("Reset Energy");
pzem.resetEnergy();
Serial.print("Set address to 0x42");
pzem.setAddress(0x42);
}
void loop() {
float volt = pzem.voltage();
Serial.print("Voltage: ");
Serial.print(volt);
Serial.println("V");
float cur = pzem.current();
Serial.print("Current: ");
Serial.print(cur);
Serial.println("A");
float powe = pzem.power();
Serial.print("Power: ");
Serial.print(powe);
Serial.println("W");
float ener = pzem.energy();
Serial.print("Energy: ");
Serial.print(ener,3);
Serial.println("kWh");
float freq = pzem.frequency();
Serial.print("Frequency: ");
Serial.print(freq);
Serial.println("Hz");
float pf = pzem.pf();
Serial.print("PF: ");
Serial.println(pf);
delay(1000);
}
```
***
Thank you to [@olehs](https://github.com/olehs) for inspiring this library.
@@ -0,0 +1,21 @@
#include <PZEM004Tv30.h>
PZEM004Tv30 pzem(&Serial3);
void setup() {
Serial.begin(115200);
}
uint8_t addr = 0x01;
void loop() {
pzem.setAddress(addr);
Serial.print("Current address:");
Serial.println(pzem.getAddress());
Serial.println();
if(++addr == 0xF8)
addr = 0x01;
delay(1000);
}
@@ -0,0 +1,54 @@
#include <PZEM004Tv30.h>
PZEM004Tv30 pzem(&Serial3);
void setup() {
Serial.begin(115200);
}
void loop() {
float voltage = pzem.voltage();
if(voltage != NAN){
Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
} else {
Serial.println("Error reading voltage");
}
float current = pzem.current();
if(current != NAN){
Serial.print("Current: "); Serial.print(current); Serial.println("A");
} else {
Serial.println("Error reading current");
}
float power = pzem.power();
if(current != NAN){
Serial.print("Power: "); Serial.print(power); Serial.println("W");
} else {
Serial.println("Error reading power");
}
float energy = pzem.energy();
if(current != NAN){
Serial.print("Energy: "); Serial.print(energy,3); Serial.println("kWh");
} else {
Serial.println("Error reading energy");
}
float frequency = pzem.frequency();
if(current != NAN){
Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
} else {
Serial.println("Error reading frequency");
}
float pf = pzem.pf();
if(current != NAN){
Serial.print("PF: "); Serial.println(pf);
} else {
Serial.println("Error reading power factor");
}
Serial.println();
delay(2000);
}
@@ -0,0 +1,54 @@
#include <PZEM004Tv30.h>
PZEM004Tv30 pzem(11, 12);
void setup() {
Serial.begin(115200);
}
void loop() {
float voltage = pzem.voltage();
if(voltage != NAN){
Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
} else {
Serial.println("Error reading voltage");
}
float current = pzem.current();
if(current != NAN){
Serial.print("Current: "); Serial.print(current); Serial.println("A");
} else {
Serial.println("Error reading current");
}
float power = pzem.power();
if(current != NAN){
Serial.print("Power: "); Serial.print(power); Serial.println("W");
} else {
Serial.println("Error reading power");
}
float energy = pzem.energy();
if(current != NAN){
Serial.print("Energy: "); Serial.print(energy,3); Serial.println("kWh");
} else {
Serial.println("Error reading energy");
}
float frequency = pzem.frequency();
if(current != NAN){
Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
} else {
Serial.println("Error reading frequency");
}
float pf = pzem.pf();
if(current != NAN){
Serial.print("PF: "); Serial.println(pf);
} else {
Serial.println("Error reading power factor");
}
Serial.println();
delay(2000);
}
+26
View File
@@ -0,0 +1,26 @@
#######################################
# Datatypes (KEYWORD1)
#######################################
PZEM004Tv30 KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
voltage KEYWORD2
current KEYWORD2
power KEYWORD2
energy KEYWORD2
frequency KEYWORD2
pf KEYWORD2
getAddress KEYWORD2
setAddress KEYWORD2
setPowerAlarm KEYWORD2
getPowerAlarm KEYWORD2
resetEnergy KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
PZEM_DEFAULT_ADDR LITERAL1
+25
View File
@@ -0,0 +1,25 @@
{
"name": "PZEM-004T-v30",
"frameworks": "arduino",
"keywords": "peacefair, pzem, powermeter, v3",
"description": "Enables communication to Peacefair PZEM-004T v3.0 Power and Energy monitor",
"url": "https://github.com/mandulaj/PZEM-004T-v30",
"authors": [
{
"name": "Jakub Mandula"
}
],
"repository": {
"type": "git",
"url": "https://github.com/mandulaj/PZEM-004T-v30"
},
"platforms": "*",
"version": "1.0.0",
"dependencies": [
{
"name": "EspSoftwareSerial",
"version": ">=3.2.0",
"platforms": "espressif8266"
}
]
}
@@ -0,0 +1,9 @@
name=PZEM-004T-v30
version=1.0.0
author=Jakub Mandula
maintainer=
sentence=Enables communication to Peacefair PZEM-004T v3.0 Power and Energy monitor
paragraph=
category=Sensors
url=https://github.com/mandulaj/PZEM-004T-v30
architectures=*
+334
View File
@@ -0,0 +1,334 @@
#include "_Plugin_Helper.h"
#ifdef USES_P102
// #######################################################################################################
// #################################### Plugin 102: PZEM004T v30 with modbus mgt##########################
// #######################################################################################################
//
# include <ESPeasySerial.h>
# include <PZEM004Tv30.h>
# define PLUGIN_102
# define PLUGIN_ID_102 102
# define PLUGIN_102_DEBUG true // activate extra log info in the debug
# define PLUGIN_NAME_102 "PZEM-004Tv30-Multiple [TESTING]"
# define P102_PZEM_mode PCONFIG(1) // 0=read value ; 1=reset energy; 2=programm address
# define P102_PZEM_ADDR PCONFIG(2)
# define P102_QUERY1 PCONFIG(3)
# define P102_QUERY2 PCONFIG(4)
# define P102_QUERY3 PCONFIG(5)
# define P102_QUERY4 PCONFIG(6)
# define P102_PZEM_FIRST PCONFIG(7)
# define P102_PZEM_ATTEMPT PCONFIG(8)
# define P102_PZEM_mode_DFLT 0 // Read value
# define P102_QUERY1_DFLT 0 // Voltage (V)
# define P102_QUERY2_DFLT 1 // Current (A)
# define P102_QUERY3_DFLT 2 // Power (W)
# define P102_QUERY4_DFLT 3 // Energy (WH)
# define P102_NR_OUTPUT_VALUES 4
# define P102_NR_OUTPUT_OPTIONS 6
# define P102_QUERY1_CONFIG_POS 3
# define P102_PZEM_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value
PZEM004Tv30 *P102_PZEM_sensor = nullptr;
boolean Plugin_102_init = false;
uint8_t P102_PZEM_ADDR_SET = 0; // Flag for status of programmation/Energy reset: 0=Reading / 1=Prog confirmed / 3=Prog done / 4=Reset
// energy done
boolean Plugin_102(byte function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_102;
Device[deviceCount].Type = DEVICE_TYPE_SERIAL;
Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 4;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = false;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_102);
success = true;
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
for (byte i = 0; i < VARS_PER_TASK; ++i) {
if (i < P102_NR_OUTPUT_VALUES) {
byte choice = PCONFIG(i + P102_QUERY1_CONFIG_POS);
safe_strncpy(
ExtraTaskSettings.TaskDeviceValueNames[i],
p102_getQueryString(choice),
sizeof(ExtraTaskSettings.TaskDeviceValueNames[i]));
} else {
ZERO_FILL(ExtraTaskSettings.TaskDeviceValueNames[i]);
}
}
break;
}
case PLUGIN_SET_DEFAULTS:
{
// Load some defaults
P102_PZEM_mode = P102_PZEM_mode_DFLT;
P102_QUERY1 = P102_QUERY1_DFLT;
P102_QUERY2 = P102_QUERY2_DFLT;
P102_QUERY3 = P102_QUERY3_DFLT;
P102_QUERY4 = P102_QUERY4_DFLT;
success = true;
break;
}
case PLUGIN_GET_DEVICEGPIONAMES:
{
serialHelper_getGpioNames(event);
// event->String3 = formatGpioName_output(F("Reset"));
break;
}
case PLUGIN_WEBFORM_SHOW_CONFIG:
{
string += serialHelper_getSerialTypeLabel(event);
success = true;
break;
}
case PLUGIN_WEBFORM_LOAD: {
if (P102_PZEM_sensor == nullptr) { P102_PZEM_FIRST = event->TaskIndex; // To detect if first PZEM or not
}
if (P102_PZEM_FIRST == event->TaskIndex) // If first PZEM, serial config available
{
addHtml(F("<br><B>This PZEM is the first. Its configuration of serial Pins will affect next PZEM. </B>"));
addHtml(F(
"<span style=\"color:red\"> <br><B>If several PZEMs foreseen, don't use HW serial (or invert Tx and Rx to configure as SW serial).</B></span>"));
addFormSubHeader(F("PZEM actions"));
String options_model[3] = { F("Read_value"), F("Reset_Energy"), F("Program_adress") };
addFormSelector(F("PZEM Mode"), F("P102_PZEM_mode"), 3, options_model, NULL, P102_PZEM_mode);
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>"));
String options_confirm[2] = { F("NO"), F("YES") };
addFormSelector(F("Confirm address programming ?"), F("P102_PZEM_addr_set"), 2, options_confirm, NULL, P102_PZEM_ADDR_SET);
addFormNumericBox(F("Address of PZEM"), F("P102_PZEM_addr"), (P102_PZEM_ADDR < 1) ? 1 : P102_PZEM_ADDR, 1, 247);
addHtml(F("Select the address to set PZEM. Programming address 0 is forbidden."));
}
else
{
addFormNumericBox(F("Address of PZEM"), F("P102_PZEM_addr"), P102_PZEM_ADDR, 0, 247);
addHtml(F(" Address 0 allows to communicate with any <B>single</B> PZEMv30 whatever its address"));
}
if (P102_PZEM_ADDR_SET == 3) // If address programming done
{
addHtml(F("<span style=\"color:green\"> <br><B>Address programming done ! </B></span>"));
P102_PZEM_ADDR_SET = 0; // Reset programming confirmation
}
}
else
{
addFormSubHeader(F("PZEM actions"));
String options_model[2] = { F("Read_value"), F("Reset_Energy") };
addFormSelector(F("PZEM Mode"), F("P102_PZEM_mode"), 2, options_model, NULL, P102_PZEM_mode);
addHtml(F(" Tx/Rx Pins config disabled: Configuration is available in the first PZEM plugin.<br>"));
addFormNumericBox(F("Address of PZEM"), F("P102_PZEM_addr"), P102_PZEM_ADDR, 1, 247);
}
addHtml(F("<br><br> Reset energy can be done also by: http://*espeasyip*/control?cmd=resetenergy,*PZEM address*"));
if (P102_PZEM_ADDR_SET == 4)
{
addHtml(F("<span style=\"color:blue\"> <br><B>Energy reset on current PZEM ! </B></span>"));
P102_PZEM_ADDR_SET = 0; // Reset programming confirmation
}
// To select the data in the 4 fields. In a separate scope to free memory of String array as soon as possible
sensorTypeHelper_webformLoad_header();
String options[P102_NR_OUTPUT_OPTIONS];
for (uint8_t i = 0; i < P102_NR_OUTPUT_OPTIONS; ++i) {
options[i] = p102_getQueryString(i);
}
for (byte i = 0; i < P102_NR_OUTPUT_VALUES; ++i) {
const byte pconfigIndex = i + P102_QUERY1_CONFIG_POS;
sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P102_NR_OUTPUT_OPTIONS, options);
}
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE: {
serialHelper_webformSave(event);
// Save output selector parameters.
for (byte i = 0; i < P102_NR_OUTPUT_VALUES; ++i) {
const byte pconfigIndex = i + P102_QUERY1_CONFIG_POS;
const byte choice = PCONFIG(pconfigIndex);
sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, p102_getQueryString(choice));
}
P102_PZEM_mode = getFormItemInt(F("P102_PZEM_mode"));
P102_PZEM_ADDR = getFormItemInt(F("P102_PZEM_addr"));
P102_PZEM_ADDR_SET = getFormItemInt(F("P102_PZEM_addr_set"));
Plugin_102_init = false; // Force device setup next time
success = true;
break;
}
case PLUGIN_INIT:
{
if (P102_PZEM_FIRST == event->TaskIndex) // If first PZEM, serial config available
{
int rxPin = CONFIG_PIN1;
int txPin = CONFIG_PIN2;
const ESPEasySerialPort port = static_cast<ESPEasySerialPort>(CONFIG_PORT);
if (P102_PZEM_sensor != nullptr) {
// Regardless the set pins, the software serial must be deleted.
delete P102_PZEM_sensor;
P102_PZEM_sensor = nullptr;
}
// Hardware serial is RX on 3 and TX on 1
P102_PZEM_sensor = new PZEM004Tv30(port, rxPin, txPin);
// Sequence for changing PZEM address
if (P102_PZEM_ADDR_SET == 1) // if address programming confirmed
{
P102_PZEM_sensor->setAddress(P102_PZEM_ADDR);
P102_PZEM_mode = 0; // Back to read mode
P102_PZEM_ADDR_SET = 3; // Address programmed
}
}
P102_PZEM_sensor->init(P102_PZEM_ADDR);
// Sequence for reseting PZEM energy
if (P102_PZEM_mode == 1)
{
P102_PZEM_sensor->resetEnergy();
P102_PZEM_mode = 0; // Back to read mode
P102_PZEM_ADDR_SET = 4; // Energy reset done
}
Plugin_102_init = true;
success = true;
break;
}
case PLUGIN_EXIT:
{
if (P102_PZEM_FIRST == event->TaskIndex) // If first PZEM, serial config available
{
if (P102_PZEM_sensor)
{
delete P102_PZEM_sensor;
P102_PZEM_sensor = nullptr;
}
}
break;
}
case PLUGIN_READ:
{
if (Plugin_102_init && (P102_PZEM_mode == 0)) // Read sensor
{
// When new data is available, return true
P102_PZEM_sensor->init(P102_PZEM_ADDR);
float PZEM[6];
PZEM[0] = P102_PZEM_sensor->voltage();
PZEM[1] = P102_PZEM_sensor->current();
PZEM[2] = P102_PZEM_sensor->power();
PZEM[3] = P102_PZEM_sensor->energy();
PZEM[4] = P102_PZEM_sensor->pf();
PZEM[5] = P102_PZEM_sensor->frequency();
for (byte i = 0; i < 6; i++) // Check each PZEM field
{
if (PZEM[i] != PZEM[i]) // Check if NAN
{
P102_PZEM_ATTEMPT == P102_PZEM_MAX_ATTEMPT ? P102_PZEM_ATTEMPT = 0 : P102_PZEM_ATTEMPT++;
break; // if one is Not A Number, break
}
P102_PZEM_ATTEMPT = 0;
}
if (P102_PZEM_ATTEMPT == 0)
{
UserVar[event->BaseVarIndex] = PZEM[P102_QUERY1];
UserVar[event->BaseVarIndex + 1] = PZEM[P102_QUERY2];
UserVar[event->BaseVarIndex + 2] = PZEM[P102_QUERY3];
UserVar[event->BaseVarIndex + 3] = PZEM[P102_QUERY4];
// sendData(event); //To send externally from the pluggin (to controller or to rules trigger)
}
success = true;
}
break;
}
case PLUGIN_WRITE:
{
if (Plugin_102_init)
{
String command = parseString(string, 1);
if ((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;
}
}
}
break;
}
}
return success;
}
String p102_getQueryString(byte query) {
switch (query)
{
case 0: return F("Voltage_V");
case 1: return F("Current_A");
case 2: return F("Power_W");
case 3: return F("Energy_WH");
case 4: return F("Power_Factor_cosphi");
case 5: return F("Frequency Hz");
}
return "";
}
#endif // USES_P102