Browse Source

Option to save total energy in EEPROM after X reports, disabled by default

ech1560
Xose Pérez 5 years ago
parent
commit
d546a069e2
14 changed files with 5191 additions and 5002 deletions
  1. +8
    -0
      code/espurna/config/sensors.h
  2. BIN
      code/espurna/data/index.all.html.gz
  3. BIN
      code/espurna/data/index.sensor.html.gz
  4. +40
    -0
      code/espurna/filters/LastFilter.h
  5. +90
    -21
      code/espurna/sensor.ino
  6. +2
    -2
      code/espurna/sensors/CSE7766Sensor.h
  7. +15
    -0
      code/espurna/sensors/ECH1560Sensor.h
  8. +5
    -0
      code/espurna/sensors/EmonSensor.h
  9. +4
    -2
      code/espurna/sensors/HLW8012Sensor.h
  10. +12
    -1
      code/espurna/sensors/PZEM004TSensor.h
  11. +21
    -6
      code/espurna/sensors/V9261FSensor.h
  12. +3052
    -3046
      code/espurna/static/index.all.html.gz.h
  13. +1929
    -1923
      code/espurna/static/index.sensor.html.gz.h
  14. +13
    -1
      code/html/index.html

+ 8
- 0
code/espurna/config/sensors.h View File

@ -36,6 +36,14 @@
#define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report
#endif
#ifndef SENSOR_SAVE_EVERY
#define SENSOR_SAVE_EVERY 0 // Save accumulating values to EEPROM (atm only energy)
// A 0 means do not save and it's the default value
// A number different from 0 means it should store the value in EEPROM
// after these many reports
// Warning: this might wear out flash fast!
#endif
#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses
#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses


BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


+ 40
- 0
code/espurna/filters/LastFilter.h View File

@ -0,0 +1,40 @@
// -----------------------------------------------------------------------------
// Last Filter
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "BaseFilter.h"
class LastFilter : public BaseFilter {
public:
void add(double value) {
_value = value;
}
unsigned char count() {
return 1;
}
void reset() {
_value = 0;
}
double result() {
return _value;
}
void resize(unsigned char size) {}
protected:
double _value = 0;
};
#endif // SENSOR_SUPPORT

+ 90
- 21
code/espurna/sensor.ino View File

@ -9,6 +9,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if SENSOR_SUPPORT
#include <vector>
#include "filters/LastFilter.h"
#include "filters/MaxFilter.h"
#include "filters/MedianFilter.h"
#include "filters/MovingAverageFilter.h"
@ -34,6 +35,7 @@ unsigned char _counts[MAGNITUDE_MAX];
bool _sensor_realtime = API_REAL_TIME_VALUES;
unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL;
unsigned char _sensor_report_every = SENSOR_REPORT_EVERY;
unsigned char _sensor_save_every = SENSOR_SAVE_EVERY;
unsigned char _sensor_power_units = SENSOR_POWER_UNITS;
unsigned char _sensor_energy_units = SENSOR_ENERGY_UNITS;
unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS;
@ -100,7 +102,7 @@ bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, "sns", 3) == 0) return true;
if (strncmp(key, "tmp", 3) == 0) return true;
if (strncmp(key, "hum", 3) == 0) return true;
if (strncmp(key, "energy", 6) == 0) return true;
if (strncmp(key, "ene", 3) == 0) return true;
return false;
}
@ -127,8 +129,8 @@ void _sensorWebSocketSendData(JsonObject& root) {
element["error"] = magnitude.sensor->error();
if (magnitude.type == MAGNITUDE_ENERGY) {
if (_sensor_energy_reset_ts.length() == 0) _sensorReset();
element["description"] = magnitude.sensor->slot(magnitude.local) + _sensor_energy_reset_ts;
if (_sensor_energy_reset_ts.length() == 0) _sensorResetTS();
element["description"] = magnitude.sensor->slot(magnitude.local) + String(" (since ") + _sensor_energy_reset_ts + String(")");
} else {
element["description"] = magnitude.sensor->slot(magnitude.local);
}
@ -196,12 +198,13 @@ void _sensorWebSocketStart(JsonObject& root) {
root["snsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
root["energyUnits"] = _sensor_energy_units;
root["eneUnits"] = _sensor_energy_units;
root["tmpUnits"] = _sensor_temperature_units;
root["tmpCorrection"] = _sensor_temperature_correction;
root["humCorrection"] = _sensor_humidity_correction;
root["snsRead"] = _sensor_read_interval / 1000;
root["snsReport"] = _sensor_report_every;
root["snsSave"] = _sensor_save_every;
}
/*
@ -293,11 +296,18 @@ void _sensorPost() {
}
}
void _sensorReset() {
void _sensorResetTS() {
#if NTP_SUPPORT
if (ntpSynced()) {
_sensor_energy_reset_ts = String(" (since ") + ntpDateTime() + String(")");
if (_sensor_energy_reset_ts.length() == 0) {
_sensor_energy_reset_ts = ntpDateTime(now() - millis() / 1000);
} else {
_sensor_energy_reset_ts = ntpDateTime(now());
}
} else {
_sensor_energy_reset_ts = String();
}
setSetting("snsResetTS", _sensor_energy_reset_ts);
#endif
}
@ -613,6 +623,7 @@ void _sensorCallback(unsigned char i, unsigned char type, double value) {
void _sensorInit() {
_sensors_ready = true;
_sensor_save_every = getSetting("snsSave", 0).toInt();
for (unsigned char i=0; i<_sensors.size(); i++) {
@ -642,9 +653,11 @@ void _sensorInit() {
new_magnitude.filtered = 0;
new_magnitude.reported = 0;
new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) {
if (MAGNITUDE_ENERGY == type) {
new_magnitude.filter = new LastFilter();
} else if (MAGNITUDE_DIGITAL == type) {
new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_COUNT || type == MAGNITUDE_GEIGER_CPM|| type == MAGNITUDE_GEIGER_SIEVERT) { // For geiger counting moving average filter is the most appropriate if needed at all.
} else if (MAGNITUDE_COUNT == type || MAGNITUDE_GEIGER_CPM == type || MAGNITUDE_GEIGER_SIEVERT == type) { // For geiger counting moving average filter is the most appropriate if needed at all.
new_magnitude.filter = new MovingAverageFilter();
} else {
new_magnitude.filter = new MedianFilter();
@ -671,6 +684,8 @@ void _sensorInit() {
EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i];
sensor->setCurrentRatio(0, getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat());
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
double value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0;
if (value > 0) sensor->resetEnergy(0, value);
}
#endif // EMON_ANALOG_SUPPORT
@ -692,6 +707,9 @@ void _sensorInit() {
value = getSetting("pwrRatioP", HLW8012_POWER_RATIO).toFloat();
if (value > 0) sensor->setPowerRatio(value);
value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0;
if (value > 0) sensor->resetEnergy(value);
}
#endif // HLW8012_SUPPORT
@ -713,6 +731,9 @@ void _sensorInit() {
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) sensor->setPowerRatio(value);
value = (_sensor_save_every > 0) ? getSetting("eneTotal", 0).toInt() : 0;
if (value > 0) sensor->resetEnergy(value);
}
#endif // CSE7766_SUPPORT
@ -726,12 +747,14 @@ void _sensorConfigure() {
// General sensor settings
_sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL);
_sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY);
_sensor_save_every = getSetting("snsSave", SENSOR_SAVE_EVERY).toInt();
_sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_sensor_power_units = getSetting("pwrUnits", SENSOR_POWER_UNITS).toInt();
_sensor_energy_units = getSetting("energyUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_energy_units = getSetting("eneUnits", SENSOR_ENERGY_UNITS).toInt();
_sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt();
_sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat();
_sensor_humidity_correction = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat();
_sensor_energy_reset_ts = getSetting("snsResetTS", "");
// Specific sensor settings
for (unsigned char i=0; i<_sensors.size(); i++) {
@ -755,7 +778,8 @@ void _sensorConfigure() {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt());
@ -769,7 +793,8 @@ void _sensorConfigure() {
EmonADC121Sensor * sensor = (EmonADC121Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
}
#endif
@ -779,7 +804,8 @@ void _sensorConfigure() {
EmonADS1X15Sensor * sensor = (EmonADS1X15Sensor *) _sensors[i];
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
}
#endif
@ -809,7 +835,8 @@ void _sensorConfigure() {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
@ -847,7 +874,8 @@ void _sensorConfigure() {
if (getSetting("pwrResetE", 0).toInt() == 1) {
sensor->resetEnergy();
_sensorReset();
delSetting("eneTotal");
_sensorResetTS();
}
if (getSetting("pwrResetCalibration", 0).toInt() == 1) {
@ -868,6 +896,11 @@ void _sensorConfigure() {
_magnitudes[i].filter->resize(_sensor_report_every);
}
// General processing
if (0 == _sensor_save_every) {
delSetting("eneTotal");
}
// Save settings
delSetting("pwrExpectedP");
delSetting("pwrExpectedC");
@ -1024,6 +1057,7 @@ void sensorSetup() {
// Backwards compatibility
moveSetting("powerUnits", "pwrUnits");
moveSetting("energyUnits", "eneUnits");
// Load sensors
_sensorLoad();
@ -1074,6 +1108,7 @@ void sensorLoop() {
// Check if we should read new data
static unsigned long last_update = 0;
static unsigned long report_count = 0;
static unsigned long save_count = 0;
if (millis() - last_update > _sensor_read_interval) {
last_update = millis();
@ -1097,6 +1132,10 @@ void sensorLoop() {
if (magnitude.sensor->status()) {
// -------------------------------------------------------------
// Instant value
// -------------------------------------------------------------
current = magnitude.sensor->value(magnitude.local);
// Completely remove spurious values if relay is OFF
@ -1113,17 +1152,26 @@ void sensorLoop() {
}
#endif
// -------------------------------------------------------------
// Processing (filters)
// -------------------------------------------------------------
magnitude.filter->add(current);
// Special case
if (magnitude.type == MAGNITUDE_COUNT) {
// Special case for MovingAvergaeFilter
if (MAGNITUDE_COUNT == magnitude.type ||
MAGNITUDE_GEIGER_CPM ==magnitude. type ||
MAGNITUDE_GEIGER_SIEVERT == magnitude.type) {
current = magnitude.filter->result();
}
current = _magnitudeProcess(magnitude.type, current);
_magnitudes[i].current = current;
// -------------------------------------------------------------
// Debug
// -------------------------------------------------------------
#if SENSOR_DEBUG
{
char buffer[64];
@ -1137,8 +1185,12 @@ void sensorLoop() {
}
#endif // SENSOR_DEBUG
// Time to report (we do it every _sensor_report_every readings)
if (report_count == 0) {
// -------------------------------------------------------------
// Report
// (we do it every _sensor_report_every readings)
// -------------------------------------------------------------
if (0 == report_count) {
filtered = magnitude.filter->result();
magnitude.filter->reset();
@ -1147,13 +1199,30 @@ void sensorLoop() {
// Check if there is a minimum change threshold to report
if (fabs(filtered - magnitude.reported) >= magnitude.min_change) {
_magnitudes[i].reported = filtered;
_sensorReport(i, filtered);
} // if (fabs(filtered - magnitude.reported) >= magnitude.min_change)
// -------------------------------------------------------------
// Saving to EEPROM
// (we do it every _sensor_save_every readings)
// -------------------------------------------------------------
if (_sensor_save_every > 0) {
save_count = (save_count + 1) % _sensor_save_every;
if (0 == save_count) {
if (MAGNITUDE_ENERGY == magnitude.type) {
setSetting("eneTotal", current);
saveSettings();
}
} // if (0 == save_count)
} // if (_sensor_save_every > 0)
} // if (report_count == 0)
} // if (magnitude.sensor->status())
} // for (unsigned char i=0; i<_magnitudes.size(); i++)


+ 2
- 2
code/espurna/sensors/CSE7766Sensor.h View File

@ -102,8 +102,8 @@ class CSE7766Sensor : public BaseSensor {
_ratioC = _ratioV = _ratioP = 1.0;
}
void resetEnergy() {
_energy = 0;
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------


+ 15
- 0
code/espurna/sensors/ECH1560Sensor.h View File

@ -59,6 +59,12 @@ class ECH1560Sensor : public BaseSensor {
return _inverted;
}
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -106,6 +112,7 @@ class ECH1560Sensor : public BaseSensor {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_APPARENT;
if (index == 3) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
@ -114,6 +121,7 @@ class ECH1560Sensor : public BaseSensor {
if (index == 0) return _current;
if (index == 1) return _voltage;
if (index == 2) return _apparent;
if (index == 3) return _energy;
return 0;
}
@ -260,6 +268,12 @@ class ECH1560Sensor : public BaseSensor {
_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_current = _apparent / _voltage;
static unsigned long last = 0;
if (last > 0) {
_energy += (_apparent * (millis() - last) / 1000);
}
last = millis();
_dosync = false;
}
@ -287,6 +301,7 @@ class ECH1560Sensor : public BaseSensor {
double _apparent = 0;
double _voltage = 0;
double _current = 0;
double _energy = 0;
unsigned char _data[24];


+ 5
- 0
code/espurna/sensors/EmonSensor.h View File

@ -55,6 +55,11 @@ class EmonSensor : public I2CSensor {
}
}
void resetEnergy(unsigned char channel, double value = 0) {
if (channel >= _channels) return;
_energy[i] = value;
}
// ---------------------------------------------------------------------
void setVoltage(double voltage) {


+ 4
- 2
code/espurna/sensors/HLW8012Sensor.h View File

@ -48,7 +48,8 @@ class HLW8012Sensor : public BaseSensor {
_hlw8012->resetMultipliers();
}
void resetEnergy() {
void resetEnergy(double value = 0) {
_energy_offset = value;
_hlw8012->resetEnergy();
}
@ -200,7 +201,7 @@ class HLW8012Sensor : public BaseSensor {
if (index == 3) return _hlw8012->getReactivePower();
if (index == 4) return _hlw8012->getApparentPower();
if (index == 5) return 100 * _hlw8012->getPowerFactor();
if (index == 6) return _hlw8012->getEnergy();
if (index == 6) return (_energy_offset + _hlw8012->getEnergy());
return 0;
}
@ -261,6 +262,7 @@ class HLW8012Sensor : public BaseSensor {
unsigned char _cf = GPIO_NONE;
unsigned char _cf1 = GPIO_NONE;
bool _sel_current = true;
double _energy_offset = 0;
HLW8012 * _hlw8012 = NULL;


+ 12
- 1
code/espurna/sensors/PZEM004TSensor.h View File

@ -59,6 +59,16 @@ class PZEM004TSensor : public BaseSensor {
return _pin_tx;
}
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
if (_ready) {
_energy_offset = value - (_pzem->energy(_ip) * 3600);
} else {
_energy_offset = value;
}
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -117,7 +127,7 @@ class PZEM004TSensor : public BaseSensor {
if (index == 0) response = _pzem->current(_ip);
if (index == 1) response = _pzem->voltage(_ip);
if (index == 2) response = _pzem->power(_ip);
if (index == 3) response = _pzem->energy(_ip) * 3600;
if (index == 3) response = _energy_offset + (_pzem->energy(_ip) * 3600);
if (response < 0) response = 0;
return response;
}
@ -133,6 +143,7 @@ class PZEM004TSensor : public BaseSensor {
IPAddress _ip;
HardwareSerial * _serial = NULL;
PZEM004T * _pzem = NULL;
double _energy_offset = 0;
};


+ 21
- 6
code/espurna/sensors/V9261FSensor.h View File

@ -56,6 +56,12 @@ class V9261FSensor : public BaseSensor {
return _inverted;
}
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
_energy = value;
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
@ -106,6 +112,7 @@ class V9261FSensor : public BaseSensor {
if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE;
}
@ -117,6 +124,7 @@ class V9261FSensor : public BaseSensor {
if (index == 3) return _reactive;
if (index == 4) return _apparent;
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
if (index == 6) return _energy;
return 0;
}
@ -130,6 +138,7 @@ class V9261FSensor : public BaseSensor {
static unsigned char state = 0;
static unsigned long last = 0;
static unsigned long ts = 0;
static bool found = false;
static unsigned char index = 0;
@ -138,10 +147,10 @@ class V9261FSensor : public BaseSensor {
while (_serial->available()) {
_serial->flush();
found = true;
last = millis();
ts = millis();
}
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
if (found && (millis() - ts > V9261F_SYNC_INTERVAL)) {
_serial->flush();
index = 0;
state = 1;
@ -164,7 +173,7 @@ class V9261FSensor : public BaseSensor {
_data[index] = _serial->read();
if (index++ >= 19) {
_serial->flush();
last = millis();
ts = millis();
state = 3;
}
}
@ -208,9 +217,14 @@ class V9261FSensor : public BaseSensor {
_apparent = fs_sqrt(_reactive * _reactive + _active * _active);
if (last > 0) {
_energy += (_active * (millis() - last) / 1000);
}
last = millis();
}
last = millis();
ts = millis();
index = 0;
state = 4;
@ -218,10 +232,10 @@ class V9261FSensor : public BaseSensor {
while (_serial->available()) {
_serial->flush();
last = millis();
ts = millis();
}
if (millis() - last > V9261F_SYNC_INTERVAL) {
if (millis() - ts > V9261F_SYNC_INTERVAL) {
state = 1;
}
@ -249,6 +263,7 @@ class V9261FSensor : public BaseSensor {
double _voltage = 0;
double _current = 0;
double _apparent = 0;
double _energy = 0;
double _ratioP = V9261F_POWER_FACTOR;
double _ratioC = V9261F_CURRENT_FACTOR;


+ 3052
- 3046
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 1929
- 1923
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 13
- 1
code/html/index.html View File

@ -1183,6 +1183,18 @@
</div>
</div>
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Save every</label>
<div class="pure-u-1 pure-u-lg-1-4"><input name="snsSave" class="pure-u-1" type="number" min="0" step="1" max="200" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
Save aggregated data to EEPROM after these many reports. At the moment this only applies to total energy readings.
Please mind: saving data to EEPROM too often will wear out the flash memory quickly.
Set it to 0 to disable this feature (default value).
</div>
</div>
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Power units</label>
<select name="pwrUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
@ -1191,7 +1203,7 @@
</select>
</div>
<div class="pure-g module module-hlw module-cse module-emon module-pzem">
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Energy units</label>
<select name="energyUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Joules (J)</option>


Loading…
Cancel
Save