Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

435 lines
15 KiB

// -----------------------------------------------------------------------------
// PZEM004T based power monitor
// Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
// Connection Diagram:
// -------------------
//
// Needed when connecting multiple PZEM004T devices on the same UART
// *You must set the PZEM004T device address prior using this configuration*
//
// +---------+
// | ESPurna | +VCC
// | Node | ^
// | G T R | |
// +-+--+--+-+ R (10K)
// | | | |
// | | +-----------------+---------------+---------------+
// | +-----------------+--|------------+--|------------+ |
// +-----------------+--|--|---------+--|--|---------+ | |
// | | | | | | | | |
// | | V | | V | | V
// | | - | | - | | -
// +-+--+--+-+ +-+--+--+-+ +-+--+--+-+
// | G R T | | G R T | | G R T |
// |PZEM-004T| |PZEM-004T| |PZEM-004T|
// | Module | | Module | | Module |
// +---------+ +---------+ +---------+
//
// Where:
// ------
// G = GND
// R = ESPurna UART RX
// T = ESPurna UART TX
// V = Small Signal Schottky Diode, like BAT43,
// Cathode to PZEM TX, Anode to Espurna RX
// R = Resistor to VCC, 10K
//
// More Info:
// ----------
// See ESPurna Wiki - https://github.com/xoseperez/espurna/wiki/Sensor-PZEM004T
//
// Reference:
// ----------
// UART/TTL-Serial network with single master and multiple slaves:
// http://cool-emerald.blogspot.com/2009/10/multidrop-network-for-rs232.html
#if SENSOR_SUPPORT && PZEM004T_SUPPORT
#pragma once
#include <PZEM004T.h>
#include "BaseSensor.h"
#include "BaseEmonSensor.h"
#include "../sensor.h"
#include "../terminal.h"
#define PZ_MAGNITUDE_COUNT 4
#define PZ_MAGNITUDE_CURRENT_INDEX 0
#define PZ_MAGNITUDE_VOLTAGE_INDEX 1
#define PZ_MAGNITUDE_POWER_ACTIVE_INDEX 2
#define PZ_MAGNITUDE_ENERGY_INDEX 3
class PZEM004TSensor : public BaseEmonSensor {
private:
// We can only create a single instance of the sensor class.
PZEM004TSensor() : BaseEmonSensor(0) {
_sensor_id = SENSOR_PZEM004T_ID;
}
~PZEM004TSensor() {
if (_pzem) delete _pzem;
PZEM004TSensor::instance = nullptr;
}
public:
static PZEM004TSensor* instance;
static PZEM004TSensor* create() {
if (PZEM004TSensor::instance) return PZEM004TSensor::instance;
PZEM004TSensor::instance = new PZEM004TSensor();
return PZEM004TSensor::instance;
}
// ---------------------------------------------------------------------
// We can't modify PZEM values, just ignore this
void resetEnergy() override {}
void resetEnergy(unsigned char) override {}
void resetEnergy(unsigned char, sensor::Energy) override {}
// Override Base methods that deal with _energy[]
size_t countDevices() override {
return _addresses.size();
}
double getEnergy(unsigned char index) override {
return _readings[index].energy;
}
sensor::Energy totalEnergy(unsigned char index) override {
return getEnergy(index);
}
// ---------------------------------------------------------------------
void setRX(unsigned char pin_rx) {
if (_pin_rx == pin_rx) return;
_pin_rx = pin_rx;
_dirty = true;
}
void setTX(unsigned char pin_tx) {
if (_pin_tx == pin_tx) return;
_pin_tx = pin_tx;
_dirty = true;
}
void setSerial(HardwareSerial * serial) {
_serial = serial;
_dirty = true;
}
// Set the devices physical addresses managed by this sensor
void setAddresses(const char *addresses) {
char const * sep = " ";
char tokens[strlen(addresses) + 1];
strlcpy(tokens, addresses, sizeof(tokens));
char *address = tokens;
int i = 0;
address = strtok(address, sep);
while (address != 0 && i++ < PZEM004T_MAX_DEVICES) {
IPAddress addr;
reading_t reading;
reading.current = PZEM_ERROR_VALUE;
reading.voltage = PZEM_ERROR_VALUE;
reading.power = PZEM_ERROR_VALUE;
reading.energy = PZEM_ERROR_VALUE;
if (addr.fromString(address)) {
_addresses.push_back(addr);
_readings.push_back(reading);
}
address = strtok(0, sep);
}
_count = _addresses.size() * PZ_MAGNITUDE_COUNT;
_dirty = true;
}
// Get device physical address based on the device index
String getAddress(unsigned char dev) {
return _addresses[dev].toString();
}
// Set the device physical address
bool setDeviceAddress(IPAddress *addr) {
while(_busy) { yield(); };
_busy = true;
bool res = _pzem->setAddress(*addr);
_busy = false;
return res;
}
// ---------------------------------------------------------------------
unsigned char getRX() {
return _pin_rx;
}
unsigned char getTX() {
return _pin_tx;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
void begin() {
if (!_dirty) return;
if (_pzem) delete _pzem;
if (_serial) {
_pzem = new PZEM004T(_serial);
if ((_pin_tx == 15) && (_pin_rx == 13)) {
_serial->flush();
_serial->swap();
}
} else {
_pzem = new PZEM004T(_pin_rx, _pin_tx);
}
if(_addresses.size() == 1) _pzem->setAddress(_addresses[0]);
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
String description() {
char buffer[27];
if (_serial) {
snprintf(buffer, sizeof(buffer), "PZEM004T @ HwSerial");
} else {
snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx);
}
return String(buffer);
}
// Descriptive name of the slot # index
String description(unsigned char index) {
auto dev = local(index);
char buffer[25];
snprintf(buffer, sizeof(buffer), "(%u/%s)", dev, getAddress(dev).c_str());
return description() + String(buffer);
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return _addresses[local(index)].toString();
}
// Convert slot # to a magnitude #
unsigned char local(unsigned char index) override {
return index / PZ_MAGNITUDE_COUNT;
}
// Type for slot # index
unsigned char type(unsigned char index) {
index = index - (local(index) * PZ_MAGNITUDE_COUNT);
if (index == PZ_MAGNITUDE_CURRENT_INDEX) return MAGNITUDE_CURRENT;
if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) return MAGNITUDE_VOLTAGE;
if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) return MAGNITUDE_POWER_ACTIVE;
if (index == PZ_MAGNITUDE_ENERGY_INDEX) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
double response = 0.0;
int dev = index / PZ_MAGNITUDE_COUNT;
index = index - (dev * PZ_MAGNITUDE_COUNT);
switch (index) {
case PZ_MAGNITUDE_CURRENT_INDEX:
response = _readings[dev].current;
break;
case PZ_MAGNITUDE_VOLTAGE_INDEX:
response = _readings[dev].voltage;
break;
case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
response = _readings[dev].power;
break;
case PZ_MAGNITUDE_ENERGY_INDEX: {
response = _readings[dev].energy;
break;
}
default:
break;
}
if (response < 0.0) {
response = 0.0;
}
return response;
}
// Post-read hook (usually to reset things)
void post() {
_error = SENSOR_ERROR_OK;
}
// Loop-like method, call it in your main loop
void tick() {
static unsigned char dev = 0;
static unsigned char magnitude = 0;
static unsigned long last_millis = 0;
if (_busy || millis() - last_millis < PZEM004T_READ_INTERVAL) return;
_busy = true;
// Clear buffer in case of late response(Timeout)
if (_serial) {
while(_serial->available() > 0) _serial->read();
} else {
// This we cannot do it from outside the library
}
tickStoreReading(dev, magnitude);
if(++dev == _addresses.size()) {
dev = 0;
last_millis = millis();
if(++magnitude == PZ_MAGNITUDE_COUNT) {
magnitude = 0;
}
}
_busy = false;
}
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
void tickStoreReading(unsigned char dev, unsigned char magnitude) {
float read = PZEM_ERROR_VALUE;
float* readings_p = nullptr;
switch (magnitude) {
case PZ_MAGNITUDE_CURRENT_INDEX:
read = _pzem->current(_addresses[dev]);
readings_p = &_readings[dev].current;
break;
case PZ_MAGNITUDE_VOLTAGE_INDEX:
read = _pzem->voltage(_addresses[dev]);
readings_p = &_readings[dev].voltage;
break;
case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
read = _pzem->power(_addresses[dev]);
readings_p = &_readings[dev].power;
break;
case PZ_MAGNITUDE_ENERGY_INDEX:
read = _pzem->energy(_addresses[dev]);
readings_p = &_readings[dev].energy;
break;
default:
_busy = false;
return;
}
if (read == PZEM_ERROR_VALUE) {
_error = SENSOR_ERROR_TIMEOUT;
} else {
*readings_p = read;
}
}
struct reading_t {
float voltage;
float current;
float power;
float energy;
};
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
bool _busy = false;
std::vector<reading_t> _readings;
std::vector<IPAddress> _addresses;
HardwareSerial * _serial = NULL;
PZEM004T * _pzem = NULL;
};
PZEM004TSensor* PZEM004TSensor::instance = nullptr;
#if TERMINAL_SUPPORT
void pzem004tInitCommands() {
terminalRegisterCommand(F("PZ.ADDRESS"), [](const terminal::CommandContext& ctx) {
if (!PZEM004TSensor::instance) return;
if (ctx.argc == 1) {
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
unsigned char dev_count = PZEM004TSensor::instance->countDevices();
for(unsigned char dev = 0; dev < dev_count; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s\n"), dev, PZEM004TSensor::instance->getAddress(dev).c_str());
}
terminalOK();
} else if(ctx.argc == 2) {
IPAddress addr;
if (addr.fromString(ctx.argv[1])) {
if(PZEM004TSensor::instance->setDeviceAddress(&addr)) {
terminalOK();
}
} else {
terminalError(F("Invalid address argument"));
}
} else {
terminalError(F("Wrong arguments"));
}
});
terminalRegisterCommand(F("PZ.RESET"), [](const terminal::CommandContext& ctx) {
if(ctx.argc > 2) {
terminalError(F("Wrong arguments"));
} else {
unsigned char init = ctx.argc == 2 ? ctx.argv[1].toInt() : 0;
unsigned char limit = ctx.argc == 2 ? init +1 : PZEM004TSensor::instance->countDevices();
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
for(unsigned char dev = init; dev < limit; dev++) {
PZEM004TSensor::instance->resetEnergy(dev);
}
terminalOK();
}
});
terminalRegisterCommand(F("PZ.VALUE"), [](const terminal::CommandContext& ctx) {
if(ctx.argc > 2) {
terminalError(F("Wrong arguments"));
} else {
unsigned char init = ctx.argc == 2 ? ctx.argv[1].toInt() : 0;
unsigned char limit = ctx.argc == 2 ? init +1 : PZEM004TSensor::instance->countDevices();
DEBUG_MSG_P(PSTR("[SENSOR] PZEM004T\n"));
for(unsigned char dev = init; dev < limit; dev++) {
DEBUG_MSG_P(PSTR("Device %d/%s - Current: %s Voltage: %s Power: %s Energy: %s\n"), //
dev,
PZEM004TSensor::instance->getAddress(dev).c_str(),
String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_CURRENT_INDEX)).c_str(),
String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_VOLTAGE_INDEX)).c_str(),
String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_POWER_ACTIVE_INDEX)).c_str(),
String(PZEM004TSensor::instance->value(dev * PZ_MAGNITUDE_ENERGY_INDEX)).c_str());
}
terminalOK();
}
});
}
#endif // TERMINAL_SUPPORT == 1
#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT