Mirror of espurna firmware for wireless switches and more
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.
 
 
 
 
 
 

308 lines
8.7 KiB

// -----------------------------------------------------------------------------
// Abstract Energy Monitor Sensor (other EMON sensors extend this class)
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
//
// Modified to be an extended version of the BaseEmonSensor and have more reusable code
// Copyright (C) 2020-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once
#include "BaseEmonSensor.h"
extern "C" {
#include "../libs/fs_math.h"
}
class BaseAnalogEmonSensor : public BaseEmonSensor {
public:
virtual unsigned int analogRead() = 0;
virtual void setVoltage(double) = 0;
virtual double getVoltage() const = 0;
virtual void setReferenceVoltage(double) = 0;
virtual double getReferenceVoltage() const = 0;
virtual void setCurrentRatio(double) = 0;
virtual double getCurrentRatio() const = 0;
virtual void setPivot(double) = 0;
virtual double getPivot() const = 0;
virtual void updateCurrent(double) = 0;
virtual double getCurrent() const = 0;
BaseAnalogEmonSensor() {
_count = 4;
}
double defaultCurrentRatio() const override {
return EMON_CURRENT_RATIO;
}
unsigned char type() override {
return sensor::type::AnalogEmon;
}
double defaultVoltage() const {
return EMON_MAINS_VOLTAGE;
}
double defaultReferenceVoltage() const {
return EMON_REFERENCE_VOLTAGE;
}
void setSamplesMax(size_t samples) {
_samples = samples;
_samples_max = samples;
_dirty = true;
}
void setResolution(size_t resolution) {
_resolution = resolution;
_adc_counts = 1 << _resolution;
setPivot(_adc_counts >> 1);
}
void expectedPower(unsigned int expected) final override {
unsigned int actual = getCurrent() * getVoltage();
if ((!actual) || (expected == actual)) {
return;
}
setCurrentRatio(getCurrentRatio() * ((double) expected / (double) actual));
calculateFactors();
_dirty = true;
}
void resetRatios() override {
setCurrentRatio(defaultCurrentRatio());
calculateFactors();
}
// ---------------------------------------------------------------------
// Sensor API
// ---------------------------------------------------------------------
void begin() {
updateCurrent(0.0);
setPivot(_adc_counts >> 1); // aka divide by 2
calculateFactors();
_ready = true;
_dirty = false;
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[EMON] Reference (mV): %d\n"), int(1000 * getReferenceVoltage()));
DEBUG_MSG_P(PSTR("[EMON] ADC counts: %lu\n"), _adc_counts);
DEBUG_MSG_P(PSTR("[EMON] Channel current ratio (mA/V): %d\n"), int(1000 * getCurrentRatio()));
DEBUG_MSG_P(PSTR("[EMON] Channel current factor (mA/bit): %d\n"), int(1000 * _current_factor));
DEBUG_MSG_P(PSTR("[EMON] Channel multiplier: %u\n"), _multiplier);
#endif
}
void pre() override {
updateCurrent(sampleCurrent());
if (_initial) {
_initial = false;
} else {
_energy[0] += sensor::Ws {
static_cast<uint32_t>(getCurrent() * getVoltage() * (millis() - _last_reading) / 1000)
};
}
_last_reading = millis();
_error = SENSOR_ERROR_OK;
}
unsigned char type(unsigned char index) override {
switch (index) {
case 0:
return MAGNITUDE_CURRENT;
case 1:
return MAGNITUDE_VOLTAGE;
case 2:
return MAGNITUDE_POWER_APPARENT;
case 3:
return MAGNITUDE_ENERGY;
}
return MAGNITUDE_NONE;
}
double value(unsigned char index) override {
switch (index) {
case 0:
return getCurrent();
case 1:
return getVoltage();
case 2:
return getCurrent() * getVoltage();
case 3:
return _energy[0].asDouble();
}
return 0.0;
}
double sampleCurrent() {
int max = 0;
int min = _adc_counts;
double sum = 0;
auto pivot = getPivot();
unsigned long time_span = millis();
for (unsigned long i=0; i<_samples; i++) {
int sample;
double filtered;
sample = this->analogRead();
if (sample > max) max = sample;
if (sample < min) min = sample;
// Digital low pass filter extracts the VDC offset
pivot = (pivot + (sample - pivot) / EMON_FILTER_SPEED);
filtered = sample - pivot;
// Root-mean-square method
sum += (filtered * filtered);
}
time_span = millis() - time_span;
// Quick fix
if (pivot < min || max < pivot) {
pivot = (max + min) / 2.0;
}
setPivot(pivot);
// Calculate current
double rms = _samples > 0 ? fs_sqrt(sum / _samples) : 0;
double current = _current_factor * rms;
current = (double) (int(current * _multiplier) - 1) / _multiplier;
if (current < 0) {
current = 0;
}
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[EMON] Total samples: %d\n"), _samples);
DEBUG_MSG_P(PSTR("[EMON] Total time (ms): %d\n"), time_span);
DEBUG_MSG_P(PSTR("[EMON] Sample frequency (Hz): %d\n"), int(1000 * _samples / time_span));
DEBUG_MSG_P(PSTR("[EMON] Max value: %d\n"), max);
DEBUG_MSG_P(PSTR("[EMON] Min value: %d\n"), min);
DEBUG_MSG_P(PSTR("[EMON] Midpoint value: %d\n"), int(getPivot()));
DEBUG_MSG_P(PSTR("[EMON] RMS value: %d\n"), int(rms));
DEBUG_MSG_P(PSTR("[EMON] Current (mA): %d\n"), int(1000 * current));
#endif
// Check timing
if ((time_span > EMON_MAX_TIME)
|| ((time_span < EMON_MAX_TIME) && (_samples < _samples_max))) {
_samples = (_samples * EMON_MAX_TIME) / time_span;
}
return current;
}
void calculateFactors() {
_current_factor = getCurrentRatio() * getReferenceVoltage() / _adc_counts;
unsigned int s = 1;
unsigned int i = 1;
unsigned int m = 1;
unsigned int multiplier = 1;
while (m * _current_factor < 1) {
multiplier = m;
i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
if (i == 1) s *= 10;
m = s * i;
}
_multiplier = multiplier;
}
private:
bool _initial { true };
unsigned long _last_reading { millis() };
double _current_factor { 1.0 }; // Calculated, reads (RMS) to current
unsigned int _multiplier { 1 }; // Calculated, error
size_t _samples_max { EMON_MAX_SAMPLES }; // Number of samples, will be adjusted at runtime
size_t _samples { _samples_max }; // based on the maximum value
size_t _resolution { EMON_ANALOG_RESOLUTION }; // ADC resolution (in bits)
size_t _adc_counts { static_cast<size_t>(1) << _resolution }; // Max count
};
// Provide EMON API helper where we don't care about specifics of how the values are stored
class SimpleAnalogEmonSensor : public BaseAnalogEmonSensor {
public:
SimpleAnalogEmonSensor() = default;
SimpleAnalogEmonSensor(const SimpleAnalogEmonSensor&) = default;
SimpleAnalogEmonSensor(SimpleAnalogEmonSensor&&) = default;
// ---------------------------------------------------------------------
// EMON API
// ---------------------------------------------------------------------
void setVoltage(double voltage) override {
_voltage = voltage;
_dirty = true;
}
double getVoltage() const override {
return _voltage;
}
void setReferenceVoltage(double voltage) override {
_reference_voltage = voltage;
_dirty = true;
}
double getReferenceVoltage() const override {
return _reference_voltage;
}
void setCurrentRatio(double ratio) override {
_current_ratio = ratio;
_dirty = true;
}
double getCurrentRatio() const override {
return _current_ratio;
}
void setPivot(double pivot) override {
_pivot = pivot;
_dirty = true;
}
double getPivot() const override {
return _pivot;
}
void updateCurrent(double current) override {
_current = current;
}
double getCurrent() const override {
return _current;
}
private:
double _voltage { 0.0 };
double _reference_voltage { 0.0 };
double _pivot { 0.0 };
double _current_ratio { EMON_CURRENT_RATIO };
double _current { 0.0 };
};
#endif // SENSOR_SUPPORT