Browse Source

sensor/emon: refactoring (#2213)

- Update sensor classes to support a generic way to store energy values
- Update sensor conversion code to deal with units and not magnitudes
- Add magnitude<->unit for sensors, generic way of defining used unit. Convert from sensor magnitude unit to the one used for display.
- Reset energy value based on index through external means (MQTT, HTTP)
- Rework energy timestamping, update webui with 'last saved' value

While this solves the energy conversion issues and we are finally seeing the real value, what I don't really like:
- KilowattHour and WattHour are separate enum tags, thus sort-of are different types altogether
- Conversion code in Energy object should probably use some generic 'ratio' calculation? (https://en.cppreference.com/w/cpp/numeric/ratio/ratio)
- We are still using runtime checks to do calculations and depend that sensor outputs only one specific value type.

Consider this a fix for energy display / storage and preliminary work on sensor.ino
Further sensor refactoring... soon.
mcspr-patch-1
Max Prokhorov 4 years ago
committed by GitHub
parent
commit
cae50fa544
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 5976 additions and 5298 deletions
  1. +17
    -7
      code/espurna/config/types.h
  2. BIN
      code/espurna/data/index.all.html.gz
  3. BIN
      code/espurna/data/index.sensor.html.gz
  4. BIN
      code/espurna/data/index.thermostat.html.gz
  5. +1
    -0
      code/espurna/espurna.ino
  6. +5
    -5
      code/espurna/filters/BaseFilter.h
  7. +40
    -0
      code/espurna/filters/SumFilter.h
  8. +2
    -0
      code/espurna/gpio.h
  9. +4
    -6
      code/espurna/gpio.ino
  10. +4
    -6
      code/espurna/homeassistant.ino
  11. +10
    -4
      code/espurna/rtcmem.h
  12. +315
    -0
      code/espurna/sensor.h
  13. +961
    -933
      code/espurna/sensor.ino
  14. +63
    -54
      code/espurna/sensors/ADE7953Sensor.h
  15. +2
    -2
      code/espurna/sensors/AM2320Sensor.h
  16. +4
    -2
      code/espurna/sensors/AnalogSensor.h
  17. +2
    -2
      code/espurna/sensors/BH1750Sensor.h
  18. +2
    -2
      code/espurna/sensors/BMP180Sensor.h
  19. +17
    -12
      code/espurna/sensors/BMX280Sensor.h
  20. +33
    -0
      code/espurna/sensors/BaseAnalogSensor.h
  21. +97
    -0
      code/espurna/sensors/BaseEmonSensor.h
  22. +60
    -17
      code/espurna/sensors/BaseSensor.h
  23. +15
    -13
      code/espurna/sensors/CSE7766Sensor.h
  24. +1
    -1
      code/espurna/sensors/DHTSensor.h
  25. +10
    -3
      code/espurna/sensors/DallasSensor.h
  26. +1
    -1
      code/espurna/sensors/DigitalSensor.h
  27. +8
    -11
      code/espurna/sensors/ECH1560Sensor.h
  28. +1
    -1
      code/espurna/sensors/EZOPHSensor.h
  29. +7
    -5
      code/espurna/sensors/EmonADC121Sensor.h
  30. +8
    -3
      code/espurna/sensors/EmonADS1X15Sensor.h
  31. +6
    -4
      code/espurna/sensors/EmonAnalogSensor.h
  32. +10
    -22
      code/espurna/sensors/EmonSensor.h
  33. +1
    -1
      code/espurna/sensors/EventSensor.h
  34. +1
    -1
      code/espurna/sensors/GUVAS12SDSensor.h
  35. +17
    -13
      code/espurna/sensors/HLW8012Sensor.h
  36. +5
    -4
      code/espurna/sensors/I2CSensor.h
  37. +1
    -1
      code/espurna/sensors/LDRSensor.h
  38. +1
    -1
      code/espurna/sensors/MAX6675Sensor.h
  39. +1
    -1
      code/espurna/sensors/MHZ19Sensor.h
  40. +3
    -22
      code/espurna/sensors/MICS2710Sensor.h
  41. +3
    -22
      code/espurna/sensors/MICS5525Sensor.h
  42. +1
    -1
      code/espurna/sensors/NTCSensor.h
  43. +1
    -1
      code/espurna/sensors/PMSX003Sensor.h
  44. +170
    -62
      code/espurna/sensors/PZEM004TSensor.h
  45. +8
    -20
      code/espurna/sensors/PulseMeterSensor.h
  46. +1
    -1
      code/espurna/sensors/SDS011Sensor.h
  47. +2
    -2
      code/espurna/sensors/SHT3XI2CSensor.h
  48. +2
    -2
      code/espurna/sensors/SI7021Sensor.h
  49. +1
    -1
      code/espurna/sensors/SenseAirSensor.h
  50. +1
    -1
      code/espurna/sensors/SonarSensor.h
  51. +1
    -1
      code/espurna/sensors/T6613Sensor.h
  52. +1
    -1
      code/espurna/sensors/TMP3XSensor.h
  53. +7
    -12
      code/espurna/sensors/V9261FSensor.h
  54. +2
    -2
      code/espurna/sensors/VEML6075Sensor.h
  55. +2
    -2
      code/espurna/sensors/VL53L1XSensor.h
  56. +2178
    -2173
      code/espurna/static/index.all.html.gz.h
  57. +1747
    -1742
      code/espurna/static/index.sensor.html.gz.h
  58. +78
    -78
      code/espurna/static/index.thermostat.html.gz.h
  59. +5
    -0
      code/espurna/terminal.h
  60. +19
    -7
      code/html/custom.js
  61. +10
    -7
      code/html/index.html

+ 17
- 7
code/espurna/config/types.h View File

@ -277,15 +277,15 @@
// UNITS // UNITS
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#define POWER_WATTS 0
#define POWER_KILOWATTS 1
#define POWER_WATTS sensor::Unit::Watt
#define POWER_KILOWATTS sensor::Unit::Kilowatt
#define ENERGY_JOULES 0
#define ENERGY_KWH 1
#define ENERGY_JOULES sensor::Unit::Joule
#define ENERGY_KWH sensor::Unit::KilowattHour
#define TMP_CELSIUS 0
#define TMP_FAHRENHEIT 1
#define TMP_KELVIN 2
#define TMP_CELSIUS sensor::Unit::Celcius
#define TMP_FAHRENHEIT sensor::Unit::Farenheit
#define TMP_KELVIN sensor::Unit::Kelvin
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Sensor ID // Sensor ID
@ -371,6 +371,16 @@
#define MAGNITUDE_MAX 32 #define MAGNITUDE_MAX 32
#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
#define SENSOR_ERROR_TIMEOUT 3 // Response from sensor timed out
#define SENSOR_ERROR_UNKNOWN_ID 4 // Sensor did not report a known ID
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Telnet server // Telnet server
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------


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


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


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


+ 1
- 0
code/espurna/espurna.ino View File

@ -55,6 +55,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "rpc.h" #include "rpc.h"
#include "rpnrules.h" #include "rpnrules.h"
#include "rtcmem.h" #include "rtcmem.h"
#include "sensor.h"
#include "thermostat.h" #include "thermostat.h"
#include "tuya.h" #include "tuya.h"
#include "web.h" #include "web.h"


+ 5
- 5
code/espurna/filters/BaseFilter.h View File

@ -10,11 +10,11 @@
class BaseFilter { class BaseFilter {
public: public:
virtual void add(double value);
virtual unsigned char count();
virtual void reset();
virtual double result();
virtual void resize(unsigned char size);
virtual void add(double value) = 0;
virtual unsigned char count() = 0;
virtual void reset() = 0;
virtual double result() = 0;
virtual void resize(unsigned char size) = 0;
unsigned char size() { return _size; }; unsigned char size() { return _size; };
protected: protected:


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

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

+ 2
- 0
code/espurna/gpio.h View File

@ -10,6 +10,8 @@ Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "libs/BasePin.h" #include "libs/BasePin.h"
constexpr const size_t GpioPins = 17;
// real hardware pin // real hardware pin
class GpioPin final : virtual public BasePin { class GpioPin final : virtual public BasePin {
public: public:


+ 4
- 6
code/espurna/gpio.ino View File

@ -30,13 +30,11 @@ inline int GpioPin::digitalRead() {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
constexpr const size_t GPIO_PINS = 17;
std::bitset<GPIO_PINS> _gpio_locked;
std::bitset<GPIO_PINS> _gpio_available;
std::bitset<GpioPins> _gpio_locked;
std::bitset<GpioPins> _gpio_available;
bool gpioValid(unsigned char gpio) { bool gpioValid(unsigned char gpio) {
if (gpio >= GPIO_PINS) return false;
if (gpio >= GpioPins) return false;
return _gpio_available.test(gpio); return _gpio_available.test(gpio);
} }
@ -82,7 +80,7 @@ void gpioSetup() {
); );
// TODO: GPIO16 is only for basic I/O, gpioGetLock before attachInterrupt should check for that // TODO: GPIO16 is only for basic I/O, gpioGetLock before attachInterrupt should check for that
for (unsigned char pin=0; pin < GPIO_PINS; ++pin) {
for (unsigned char pin=0; pin < GpioPins; ++pin) {
if (pin <= 5) _gpio_available.set(pin); if (pin <= 5) _gpio_available.set(pin);
if (((pin == 9) || (pin == 10)) && (esp8285)) _gpio_available.set(pin); if (((pin == 9) || (pin == 10)) && (esp8285)) _gpio_available.set(pin);
if (12 <= pin && pin <= 16) _gpio_available.set(pin); if (12 <= pin && pin <= 16) _gpio_available.set(pin);


+ 4
- 6
code/espurna/homeassistant.ino View File

@ -201,12 +201,10 @@ void _haSendDiscovery() {
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i);
config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(type));
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
void _haSendMagnitude(unsigned char index, JsonObject& config) {
config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(magnitudeType(index)));
config["state_topic"] = mqttTopic(magnitudeTopicIndex(index).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(index);
} }
void ha_discovery_t::prepareMagnitudes(ha_config_t& config) { void ha_discovery_t::prepareMagnitudes(ha_config_t& config) {


+ 10
- 4
code/espurna/rtcmem.h View File

@ -22,11 +22,10 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
#define RTCMEM_BLOCKS 96u #define RTCMEM_BLOCKS 96u
// Change this when modifying RtcmemData // Change this when modifying RtcmemData
#define RTCMEM_MAGIC 0x45535075
#define RTCMEM_MAGIC 0x46535076
// XXX: All access must be 4-byte aligned and always at full length. // XXX: All access must be 4-byte aligned and always at full length.
//
// For example, using bitfields / inner structs / etc:
// Exactly like PROGMEM works. For example, using bitfields / inner structs / etc:
// ... // ...
// uint32_t a : 8; // uint32_t a : 8;
// uint32_t b : 8; // uint32_t b : 8;
@ -38,13 +37,20 @@ Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
// mem->d = 4; // mem->d = 4;
// TODO replace with custom memory segment in ldscript? // TODO replace with custom memory segment in ldscript?
// `magic` would need to be tracked differently
struct RtcmemEnergy {
uint32_t kwh;
uint32_t ws;
};
struct RtcmemData { struct RtcmemData {
uint32_t magic; uint32_t magic;
uint32_t sys; uint32_t sys;
uint32_t relay; uint32_t relay;
uint32_t mqtt; uint32_t mqtt;
uint64_t light; uint64_t light;
double energy[4];
RtcmemEnergy energy[4];
}; };
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");


+ 315
- 0
code/espurna/sensor.h View File

@ -0,0 +1,315 @@
/*
SENSOR MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#pragma once
struct sensor_magnitude_t;
//--------------------------------------------------------------------------------
namespace sensor {
namespace type {
enum Type : unsigned char {
Base = 0,
Emon = 1 << 0,
Analog = 1 << 1
};
} // namespace type
enum class Unit : int {
Min_,
None,
Celcius,
Farenheit,
Kelvin,
Percentage,
Hectopascal,
Ampere,
Volt,
Voltampere,
Kilovoltampere,
VoltampereReactive,
KilovoltampereReactive,
Watt,
Kilowatt,
WattSecond,
Joule = WattSecond,
KilowattHour,
PartsPerMillion,
Ohm,
MicrogrammPerCubicMeter, // The concentration of an air pollutant
MilligrammPerCubicMeter, //
Lux,
UltravioletIndex, // "measurement of the strength of sunburn-producing ultraviolet (UV) radiation at a particular place and time"
// (XXX: Not a unit. Distinguish from None and specify decimals)
CountsPerMinute, // Unit of local dose rate (Geiger counting)
MicrosievertPerHour, // 2nd unit of local dose rate (Geiger counting)
Meter,
Max_
};
// Base units are 32 bit since we are the fastest with them.
struct Ws {
Ws();
Ws(uint32_t);
uint32_t value;
};
struct Wh {
Wh();
Wh(Ws);
Wh(uint32_t);
uint32_t value;
};
struct KWh {
KWh();
KWh(Ws);
KWh(Wh);
KWh(uint32_t);
uint32_t value;
};
struct Energy {
constexpr static uint32_t KwhMultiplier = 3600000ul;
constexpr static uint32_t KwhLimit = ((1ul << 31ul) / KwhMultiplier);
Energy() = default;
// TODO: while we accept ws >= the kwh conversion limit,
// should this be dealt with on the unit level?
Energy(double);
Energy(KWh, Ws);
Energy(KWh);
Energy(Wh);
Energy(Ws);
// Sets internal counters to zero
void reset();
// Check whether we have *any* energy recorded. Can be zero:
// - on cold boot
// - on overflow
// - when we call `reset()`
operator bool();
// Generic conversion as-is
double asDouble();
// Convert back to input unit, with overflow mechanics when kwh values goes over 32 bit
Ws asWs();
// Generic sensors output energy in joules / watt-second
Energy& operator +=(Ws);
Energy operator +(Ws);
// But sometimes we want to accept asDouble() value back
Energy& operator =(double);
// We are storing a kind-of integral and fractional parts
// Using watt-second to avoid loosing precision, we don't expect these to be accessed directly
KWh kwh;
Ws ws;
};
}
String magnitudeName(unsigned char index);
String magnitudeUnits(unsigned char index);
unsigned char magnitudeType(unsigned char index);
// XXX: without param name it is kind of vague what exactly unsigned char is
// consider using index instead of type or adding stronger param type
String magnitudeTopic(unsigned char type);
String magnitudeTopic(const sensor_magnitude_t& magnitude);
String magnitudeUnits(const sensor_magnitude_t& magnitude);
unsigned char sensorCount();
unsigned char magnitudeCount();
double magnitudeValue(unsigned char index);
unsigned char magnitudeIndex(unsigned char index);
String magnitudeTopicIndex(unsigned char index);
void sensorSetup();
void sensorLoop();
//--------------------------------------------------------------------------------
#include "filters/LastFilter.h"
#include "filters/MaxFilter.h"
#include "filters/MedianFilter.h"
#include "filters/MovingAverageFilter.h"
#include "filters/SumFilter.h"
#include "sensors/BaseSensor.h"
#include "sensors/BaseEmonSensor.h"
#include "sensors/BaseAnalogSensor.h"
#if AM2320_SUPPORT
#include "sensors/AM2320Sensor.h"
#endif
#if ANALOG_SUPPORT
#include "sensors/AnalogSensor.h"
#endif
#if BH1750_SUPPORT
#include "sensors/BH1750Sensor.h"
#endif
#if BMP180_SUPPORT
#include "sensors/BMP180Sensor.h"
#endif
#if BMX280_SUPPORT
#include "sensors/BMX280Sensor.h"
#endif
#if CSE7766_SUPPORT
#include "sensors/CSE7766Sensor.h"
#endif
#if DALLAS_SUPPORT
#include "sensors/DallasSensor.h"
#endif
#if DHT_SUPPORT
#include "sensors/DHTSensor.h"
#endif
#if DIGITAL_SUPPORT
#include "sensors/DigitalSensor.h"
#endif
#if ECH1560_SUPPORT
#include "sensors/ECH1560Sensor.h"
#endif
#if EMON_ADC121_SUPPORT
#include "sensors/EmonADC121Sensor.h"
#endif
#if EMON_ADS1X15_SUPPORT
#include "sensors/EmonADS1X15Sensor.h"
#endif
#if EMON_ANALOG_SUPPORT
#include "sensors/EmonAnalogSensor.h"
#endif
#if EVENTS_SUPPORT
#include "sensors/EventSensor.h"
#endif
#if EZOPH_SUPPORT
#include "sensors/EZOPHSensor.h"
#endif
#if GEIGER_SUPPORT
#include "sensors/GeigerSensor.h"
#endif
#if GUVAS12SD_SUPPORT
#include "sensors/GUVAS12SDSensor.h"
#endif
#if HLW8012_SUPPORT
#include "sensors/HLW8012Sensor.h"
#endif
#if LDR_SUPPORT
#include "sensors/LDRSensor.h"
#endif
#if MAX6675_SUPPORT
#include "sensors/MAX6675Sensor.h"
#endif
#if MICS2710_SUPPORT
#include "sensors/MICS2710Sensor.h"
#endif
#if MICS5525_SUPPORT
#include "sensors/MICS5525Sensor.h"
#endif
#if MHZ19_SUPPORT
#include "sensors/MHZ19Sensor.h"
#endif
#if NTC_SUPPORT
#include "sensors/NTCSensor.h"
#endif
#if SDS011_SUPPORT
#include "sensors/SDS011Sensor.h"
#endif
#if SENSEAIR_SUPPORT
#include "sensors/SenseAirSensor.h"
#endif
#if PMSX003_SUPPORT
#include "sensors/PMSX003Sensor.h"
#endif
#if PULSEMETER_SUPPORT
#include "sensors/PulseMeterSensor.h"
#endif
#if PZEM004T_SUPPORT
#include "sensors/PZEM004TSensor.h"
#endif
#if SHT3X_I2C_SUPPORT
#include "sensors/SHT3XI2CSensor.h"
#endif
#if SI7021_SUPPORT
#include "sensors/SI7021Sensor.h"
#endif
#if SONAR_SUPPORT
#include "sensors/SonarSensor.h"
#endif
#if T6613_SUPPORT
#include "sensors/T6613Sensor.h"
#endif
#if TMP3X_SUPPORT
#include "sensors/TMP3XSensor.h"
#endif
#if V9261F_SUPPORT
#include "sensors/V9261FSensor.h"
#endif
#if VEML6075_SUPPORT
#include "sensors/VEML6075Sensor.h"
#endif
#if VL53L1X_SUPPORT
#include "sensors/VL53L1XSensor.h"
#endif
#if ADE7953_SUPPORT
#include "sensors/ADE7953Sensor.h"
#endif
//--------------------------------------------------------------------------------

+ 961
- 933
code/espurna/sensor.ino
File diff suppressed because it is too large
View File


+ 63
- 54
code/espurna/sensors/ADE7953Sensor.h View File

@ -11,9 +11,11 @@
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
#include "I2CSensor.h"
#include "../utils.h" #include "../utils.h"
#include "BaseEmonSensor.h"
#include "I2CSensor.h"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ADE7953 - Energy (Shelly 2.5) // ADE7953 - Energy (Shelly 2.5)
// //
@ -34,25 +36,24 @@
#define ADE7953_VOLTAGE 1 #define ADE7953_VOLTAGE 1
#define ADE7953_TOTAL_DEVICES 3 #define ADE7953_TOTAL_DEVICES 3
class ADE7953Sensor : public I2CSensor {
class ADE7953Sensor : public I2CSensor<BaseEmonSensor> {
protected: protected:
struct reading_t { struct reading_t {
float current = 0.0; float current = 0.0;
float power = 0.0; float power = 0.0;
float energy = 0.0;
}; };
public:
public:
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
ADE7953Sensor(): I2CSensor() {
_sensor_id = SENSOR_ADE7953_ID;
_readings.resize(ADE7953_TOTAL_DEVICES);
_energy_offsets.resize(ADE7953_TOTAL_DEVICES);
_count = _readings.size() * ADE7953_TOTAL_DEVICES + ADE7953_VOLTAGE; //10
ADE7953Sensor() {
resizeDevices(ADE7953_TOTAL_DEVICES);
_sensor_id = SENSOR_ADE7953_ID;
_readings.resize(countDevices());
_count = _readings.size() * countDevices() + ADE7953_VOLTAGE; //10
} }
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -78,23 +79,14 @@ class ADE7953Sensor : public I2CSensor {
return description(); return description();
}; };
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_VOLTAGE;
index = index % ADE7953_TOTAL_DEVICES;
if (index == 0) return MAGNITUDE_ENERGY;
if (index == 1) return MAGNITUDE_CURRENT;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
return MAGNITUDE_NONE;
}
// Pre-read hook (usually to populate registers with up-to-date data) // Pre-read hook (usually to populate registers with up-to-date data)
void pre() {
void pre() {
uint32_t active_power1 = 0; uint32_t active_power1 = 0;
uint32_t active_power2 = 0; uint32_t active_power2 = 0;
uint32_t current_rms1 = 0; uint32_t current_rms1 = 0;
uint32_t current_rms2 = 0; uint32_t current_rms2 = 0;
uint32_t voltage_rms = 0;
uint32_t voltage_rms = 0;
voltage_rms = read(_address, 0x31C); // Both relays voltage_rms = read(_address, 0x31C); // Both relays
current_rms1 = read(_address, 0x31B); // Relay 1 current_rms1 = read(_address, 0x31B); // Relay 1
@ -113,62 +105,79 @@ class ADE7953Sensor : public I2CSensor {
active_power2 = (int32_t)read(_address, 0x312); // Relay 2 active_power2 = (int32_t)read(_address, 0x312); // Relay 2
active_power2 = (active_power2 > 0) ? active_power2 : 0; active_power2 = (active_power2 > 0) ? active_power2 : 0;
} }
_voltage = (float) voltage_rms / ADE7953_UREF;
_voltage = (float) voltage_rms / ADE7953_UREF;
storeReading( storeReading(
ADE7953_ALL_RELAYS,
(float)(current_rms1 + current_rms2) / (ADE7953_IREF * 10),
ADE7953_ALL_RELAYS,
(float)(current_rms1 + current_rms2) / (ADE7953_IREF * 10),
(float)(active_power1 + active_power2) / (ADE7953_PREF / 10) (float)(active_power1 + active_power2) / (ADE7953_PREF / 10)
); );
storeReading( storeReading(
ADE7953_RELAY_1,
(float) current_rms1 / (ADE7953_IREF * 10),
ADE7953_RELAY_1,
(float) current_rms1 / (ADE7953_IREF * 10),
(float) active_power1 / (ADE7953_PREF / 10) (float) active_power1 / (ADE7953_PREF / 10)
);
);
storeReading( storeReading(
ADE7953_RELAY_2,
(float)current_rms2 / (ADE7953_IREF * 10),
ADE7953_RELAY_2,
(float)current_rms2 / (ADE7953_IREF * 10),
(float)active_power2 / (ADE7953_PREF / 10) (float)active_power2 / (ADE7953_PREF / 10)
); );
} }
inline void storeReading(unsigned int relay, float current, float power) { inline void storeReading(unsigned int relay, float current, float power) {
auto& reading_ref = _readings.at(relay);
auto& reading_ref = _readings.at(relay);
reading_ref.current = current; reading_ref.current = current;
reading_ref.power = power; reading_ref.power = power;
// TODO: chip already stores precise data about energy, see datasheet
static unsigned long last = 0; static unsigned long last = 0;
if (last > 0) {
reading_ref.energy += (power * (millis() - last) / 1000);
}
if (last > 0) {
const uint32_t delta = (fabs(power) * (millis() - last) / 1000);
_energy[relay] += sensor::Ws { delta };
}
last = millis(); last = millis();
} }
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) {
if (index == 0) return _voltage;
int relay = (index - 1) / ADE7953_TOTAL_DEVICES;
index = index % ADE7953_TOTAL_DEVICES;
if (index == 0) return _energy_offsets[relay] + _readings[relay].energy;
double value(unsigned char index) {
if (index == 0) return _voltage;
int relay = (index - 1) / countDevices();
index = index % countDevices();
if (index == 0) return getEnergy(relay);
if (index == 1) return _readings[relay].current; if (index == 1) return _readings[relay].current;
if (index == 2) return _readings[relay].power;
if (index == 2) return _readings[relay].power;
return 0; return 0;
} }
unsigned int getTotalDevices() {
return ADE7953_TOTAL_DEVICES;
// Type for slot # index
unsigned char type(unsigned char index) {
if (index == 0) return MAGNITUDE_VOLTAGE;
index = index % countDevices();
if (index == 0) return MAGNITUDE_ENERGY;
if (index == 1) return MAGNITUDE_CURRENT;
if (index == 2) return MAGNITUDE_POWER_ACTIVE;
return MAGNITUDE_NONE;
} }
void resetEnergy(int relay, double value = 0) {
_energy_offsets[relay] = value;
}
protected:
void _init() {
// Need at least 100mS to init ADE7953.
// TODO: add polling delay member instead of waiting right here?
nice_delay(100);
// Lock chip i2c address
uint8_t addresses[] = { ADE7953_ADDRESS };
_address = _begin_i2c(_address, sizeof(addresses), addresses);
if (0 == _address) return;
// TODO: we implement other i2c methods as local functions, as we need to address 16-bit registers
// should eventually be replaced with i2c module alternatives.
protected:
void _init() {
nice_delay(100); // Need 100mS to init ADE7953
write(_address, 0x102, 0x0004); // Locking the communication interface (Clear bit COMM_LOCK), Enable HPF write(_address, 0x102, 0x0004); // Locking the communication interface (Clear bit COMM_LOCK), Enable HPF
write(_address, 0x0FE, 0x00AD); // Unlock register 0x120 write(_address, 0x0FE, 0x00AD); // Unlock register 0x120
write(_address, 0x120, 0x0030); // Configure optimum setting
write(_address, 0x120, 0x0030); // Configure optimum setting
_ready = true; _ready = true;
} }
@ -206,7 +215,7 @@ class ADE7953Sensor : public I2CSensor {
} }
#endif #endif
void write(unsigned char address, uint16_t reg, uint32_t val) { void write(unsigned char address, uint16_t reg, uint32_t val) {
int size = reg_size(reg); int size = reg_size(reg);
if (size) { if (size) {
@ -220,9 +229,9 @@ class ADE7953Sensor : public I2CSensor {
delayMicroseconds(5); // Bus-free time minimum 4.7us delayMicroseconds(5); // Bus-free time minimum 4.7us
} }
} }
static uint32_t read(int address, uint16_t reg) { static uint32_t read(int address, uint16_t reg) {
uint32_t response = 0;
uint32_t response = 0;
const int size = reg_size(reg); const int size = reg_size(reg);
if (size) { if (size) {
Wire.beginTransmission(address); Wire.beginTransmission(address);
@ -239,9 +248,9 @@ class ADE7953Sensor : public I2CSensor {
return response; return response;
} }
std::vector<reading_t> _readings;
float _voltage = 0; float _voltage = 0;
std::vector<double> _energy_offsets;
std::vector<reading_t> _readings;
}; };
#endif // SENSOR_SUPPORT && ADE7953_SUPPORT #endif // SENSOR_SUPPORT && ADE7953_SUPPORT

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

@ -27,7 +27,7 @@ Retention | 0x06 | Device ID(24-31)Bit| 0x0E | Retention
Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F
*/ */
class AM2320Sensor : public I2CSensor {
class AM2320Sensor : public I2CSensor<> {
public: public:
@ -35,7 +35,7 @@ class AM2320Sensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
AM2320Sensor(): I2CSensor() {
AM2320Sensor() {
_count = 2; _count = 2;
_sensor_id = SENSOR_AM2320_ID; _sensor_id = SENSOR_AM2320_ID;
} }


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

@ -10,9 +10,11 @@
#include <Arduino.h> #include <Arduino.h>
#include "../debug.h" #include "../debug.h"
#include "BaseSensor.h" #include "BaseSensor.h"
#include "BaseAnalogSensor.h"
class AnalogSensor : public BaseSensor {
class AnalogSensor : public BaseAnalogSensor {
public: public:
@ -20,7 +22,7 @@ class AnalogSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
AnalogSensor(): BaseSensor() {
AnalogSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_ANALOG_ID; _sensor_id = SENSOR_ANALOG_ID;
} }


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

@ -21,7 +21,7 @@
#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms. #define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement. // Device is automatically set to Power Down after measurement.
class BH1750Sensor : public I2CSensor {
class BH1750Sensor : public I2CSensor<> {
public: public:
@ -29,7 +29,7 @@ class BH1750Sensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
BH1750Sensor(): I2CSensor() {
BH1750Sensor() {
_sensor_id = SENSOR_BH1750_ID; _sensor_id = SENSOR_BH1750_ID;
_count = 1; _count = 1;
} }


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

@ -36,7 +36,7 @@
#define BMP180_REGISTER_READTEMPCMD 0x2E #define BMP180_REGISTER_READTEMPCMD 0x2E
#define BMP180_REGISTER_READPRESSURECMD 0x34 #define BMP180_REGISTER_READPRESSURECMD 0x34
class BMP180Sensor : public I2CSensor {
class BMP180Sensor : public I2CSensor<> {
public: public:
@ -46,7 +46,7 @@ class BMP180Sensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
BMP180Sensor(): I2CSensor() {
BMP180Sensor() {
_sensor_id = SENSOR_BMP180_ID; _sensor_id = SENSOR_BMP180_ID;
_count = 2; _count = 2;
} }


+ 17
- 12
code/espurna/sensors/BMX280Sensor.h View File

@ -49,7 +49,7 @@
#define BMX280_REGISTER_TEMPDATA 0xFA #define BMX280_REGISTER_TEMPDATA 0xFA
#define BMX280_REGISTER_HUMIDDATA 0xFD #define BMX280_REGISTER_HUMIDDATA 0xFD
class BMX280Sensor : public I2CSensor {
class BMX280Sensor : public I2CSensor<> {
public: public:
@ -59,7 +59,7 @@ class BMX280Sensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
BMX280Sensor(): I2CSensor() {
BMX280Sensor() {
_sensor_id = SENSOR_BMX280_ID; _sensor_id = SENSOR_BMX280_ID;
} }
@ -97,16 +97,21 @@ class BMX280Sensor : public I2CSensor {
#endif #endif
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
// Number of decimals for a magnitude (or -1 for default)
signed char decimals(unsigned char type) {
// These numbers of decimals correspond to maximum sensor resolution settings
switch (type) {
case MAGNITUDE_TEMPERATURE: return 3;
case MAGNITUDE_PRESSURE: return 4;
case MAGNITUDE_HUMIDITY: return 2;
}
return -1;
}
// Number of decimals for a magnitude (or -1 for default)
// These numbers of decimals correspond to maximum sensor resolution settings
signed char decimals(sensor::Unit unit) {
switch (unit) {
case sensor::Unit::Celcius:
return 3;
case sensor::Unit::Hectopascal:
return 4;
case sensor::Unit::Percentage:
return 2;
default:
return -1;
}
}
// Pre-read hook (usually to populate registers with up-to-date data) // Pre-read hook (usually to populate registers with up-to-date data)
virtual void pre() { virtual void pre() {


+ 33
- 0
code/espurna/sensors/BaseAnalogSensor.h View File

@ -0,0 +1,33 @@
// -----------------------------------------------------------------------------
// Abstract emon sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#pragma once
#include "BaseSensor.h"
class BaseAnalogSensor : public BaseSensor {
public:
virtual unsigned long getR0() { return _R0; }
virtual void setR0(unsigned long value) { _R0 = value; }
virtual unsigned long getRL() { return _Rl; }
virtual void setRL(unsigned long value) { _Rl = value; }
virtual unsigned long getRS() { return _Rs; }
virtual void setRS(unsigned long value) { _Rs = value; }
virtual void calibrate() { }
unsigned char type() { return sensor::type::Analog; }
protected:
unsigned long _R0; // R0, calibration value at 25º
unsigned long _Rl; // RL, load resistance
unsigned long _Rs; // cached resistance
};

+ 97
- 0
code/espurna/sensors/BaseEmonSensor.h View File

@ -0,0 +1,97 @@
// -----------------------------------------------------------------------------
// Abstract emon sensor class (other sensor classes extend this class)
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#pragma once
#include "../sensor.h"
#include "BaseSensor.h"
class BaseEmonSensor : public BaseSensor {
public:
BaseEmonSensor(size_t devices) :
_energy(devices),
_devices(devices)
{}
BaseEmonSensor() :
BaseEmonSensor(1)
{}
unsigned char type() {
return sensor::type::Emon;
}
virtual void resizeDevices(size_t devices) {
_energy.resize(devices);
_devices = devices;
}
virtual size_t countDevices() {
return _devices;
}
virtual void resetEnergy(unsigned char index, sensor::Energy energy) {
_energy[index] = energy;
}
virtual void resetEnergy(unsigned char index) {
_energy[index].reset();
};
virtual void resetEnergy() {
for (auto& energy : _energy) {
energy.reset();
}
}
virtual sensor::Energy totalEnergy(unsigned char index) {
return _energy[index];
}
virtual sensor::Energy totalEnergy() {
return totalEnergy(0);
}
virtual double getEnergy(unsigned char index) {
return _energy[index].asDouble();
}
virtual double getEnergy() {
return getEnergy(0);
}
virtual void setCurrentRatio(double) {}
virtual void setVoltageRatio(double) {}
virtual void setPowerRatio(double) {}
virtual void setEnergyRatio(double) {}
// when sensor implements a single device
virtual double getCurrentRatio() { return 0.0; }
virtual double getVoltageRatio() { return 0.0; }
virtual double getPowerRatio() { return 0.0; }
virtual double getEnergyRatio() { return 0.0; }
// when sensor implements more than one device
virtual double getCurrentRatio(unsigned char index) { return getCurrentRatio(); }
virtual double getVoltageRatio(unsigned char index) { return getCurrentRatio(); }
virtual double getPowerRatio(unsigned char index) { return getCurrentRatio(); }
virtual double getEnergyRatio(unsigned char index) { return getCurrentRatio(); }
virtual void expectedCurrent(double value) {}
virtual void expectedVoltage(unsigned int value) {}
virtual void expectedPower(unsigned int value) {}
virtual void resetCalibration(double value) {}
virtual void resetRatios() {}
protected:
std::vector<sensor::Energy> _energy;
size_t _devices;
};

+ 60
- 17
code/espurna/sensors/BaseSensor.h View File

@ -3,25 +3,16 @@
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com> // Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if SENSOR_SUPPORT
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#define SENSOR_ERROR_OK 0 // No error
#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range
#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up
#define SENSOR_ERROR_TIMEOUT 3 // Response from sensor timed out
#define SENSOR_ERROR_UNKNOWN_ID 4 // Sensor did not report a known ID
#define SENSOR_ERROR_CRC 5 // Sensor data corrupted
#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address
#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use
#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated
#define SENSOR_ERROR_OTHER 99 // Any other error
#include <functional>
#include "../sensor.h"
typedef std::function<void(unsigned char, double)> TSensorCallback;
using TSensorCallback = std::function<void(unsigned char, double)>;
class BaseSensor { class BaseSensor {
@ -54,15 +45,21 @@ class BaseSensor {
// Descriptive name of the slot # index // Descriptive name of the slot # index
virtual String slot(unsigned char index) = 0; virtual String slot(unsigned char index) = 0;
// Type of sensor
virtual unsigned char type() { return sensor::type::Base; }
// Type for slot # index // Type for slot # index
virtual unsigned char type(unsigned char index) = 0; virtual unsigned char type(unsigned char index) = 0;
// Number of decimals for a magnitude (or -1 for default)
virtual signed char decimals(unsigned char type) { return -1; }
// Number of decimals for a unit (or -1 for default)
virtual signed char decimals(sensor::Unit) { return -1; }
// Current value for slot # index // Current value for slot # index
virtual double value(unsigned char index) = 0; virtual double value(unsigned char index) = 0;
// Generic calibration
virtual void calibrate() {};
// Retrieve current instance configuration // Retrieve current instance configuration
virtual void getConfig(JsonObject& root) {}; virtual void getConfig(JsonObject& root) {};
@ -90,6 +87,54 @@ class BaseSensor {
// Hook for event callback // Hook for event callback
void onEvent(TSensorCallback fn) { _callback = fn; }; void onEvent(TSensorCallback fn) { _callback = fn; };
// Specify units attached to magnitudes
virtual sensor::Unit units(unsigned char type) {
switch (type) {
case MAGNITUDE_TEMPERATURE:
return sensor::Unit::Celcius;
case MAGNITUDE_HUMIDITY:
case MAGNITUDE_POWER_FACTOR:
return sensor::Unit::Percentage;
case MAGNITUDE_PRESSURE:
return sensor::Unit::Hectopascal;
case MAGNITUDE_CURRENT:
return sensor::Unit::Ampere;
case MAGNITUDE_VOLTAGE:
return sensor::Unit::Volt;
case MAGNITUDE_POWER_ACTIVE:
return sensor::Unit::Watt;
case MAGNITUDE_POWER_APPARENT:
return sensor::Unit::Voltampere;
case MAGNITUDE_POWER_REACTIVE:
return sensor::Unit::VoltampereReactive;
case MAGNITUDE_ENERGY_DELTA:
return sensor::Unit::Joule;
case MAGNITUDE_ENERGY:
return sensor::Unit::KilowattHour;
case MAGNITUDE_PM1dot0:
case MAGNITUDE_PM2dot5:
return sensor::Unit::MicrogrammPerCubicMeter;
case MAGNITUDE_CO2:
case MAGNITUDE_NO2:
case MAGNITUDE_CO:
return sensor::Unit::PartsPerMillion;
case MAGNITUDE_LUX:
return sensor::Unit::Lux;
case MAGNITUDE_RESISTANCE:
return sensor::Unit::Ohm;
case MAGNITUDE_HCHO:
return sensor::Unit::MilligrammPerCubicMeter;
case MAGNITUDE_GEIGER_CPM:
return sensor::Unit::CountsPerMinute;
case MAGNITUDE_GEIGER_SIEVERT:
return sensor::Unit::MicrosievertPerHour;
case MAGNITUDE_DISTANCE:
return sensor::Unit::Meter;
default:
return sensor::Unit::None;
}
}
protected: protected:
TSensorCallback _callback = NULL; TSensorCallback _callback = NULL;
@ -100,5 +145,3 @@ class BaseSensor {
bool _ready = false; bool _ready = false;
}; };
#endif

+ 15
- 13
code/espurna/sensors/CSE7766Sensor.h View File

@ -12,9 +12,11 @@
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
#include "../debug.h" #include "../debug.h"
#include "BaseSensor.h" #include "BaseSensor.h"
#include "BaseEmonSensor.h"
class CSE7766Sensor : public BaseSensor {
class CSE7766Sensor : public BaseEmonSensor {
public: public:
@ -22,7 +24,7 @@ class CSE7766Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
CSE7766Sensor(): BaseSensor(), _data() {
CSE7766Sensor(): _data() {
_count = 7; _count = 7;
_sensor_id = SENSOR_CSE7766_ID; _sensor_id = SENSOR_CSE7766_ID;
} }
@ -99,14 +101,10 @@ class CSE7766Sensor : public BaseSensor {
return _ratioP; return _ratioP;
}; };
void resetRatios() {
void resetCalibration() {
_ratioC = _ratioV = _ratioP = 1.0; _ratioC = _ratioV = _ratioP = 1.0;
} }
void resetEnergy(double value = 0) {
_energy = value;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -177,7 +175,7 @@ class CSE7766Sensor : public BaseSensor {
if (index == 3) return _reactive; if (index == 3) return _reactive;
if (index == 4) return _voltage * _current; if (index == 4) return _voltage * _current;
if (index == 5) return ((_voltage > 0) && (_current > 0)) ? 100 * _active / _voltage / _current : 100; if (index == 5) return ((_voltage > 0) && (_current > 0)) ? 100 * _active / _voltage / _current : 100;
if (index == 6) return _energy;
if (index == 6) return getEnergy();
return 0; return 0;
} }
@ -287,16 +285,21 @@ class CSE7766Sensor : public BaseSensor {
} }
// Calculate energy // Calculate energy
unsigned int difference;
static unsigned int cf_pulses_last = 0;
unsigned int cf_pulses = _data[21] << 8 | _data[22];
uint32_t cf_pulses = _data[21] << 8 | _data[22];
static uint32_t cf_pulses_last = 0;
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses; if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
uint32_t difference;
if (cf_pulses < cf_pulses_last) { if (cf_pulses < cf_pulses_last) {
difference = cf_pulses + (0xFFFF - cf_pulses_last) + 1; difference = cf_pulses + (0xFFFF - cf_pulses_last) + 1;
} else { } else {
difference = cf_pulses - cf_pulses_last; difference = cf_pulses - cf_pulses_last;
} }
_energy += difference * (float) _coefP / 1000000.0;
_energy[0] += sensor::Ws {
static_cast<uint32_t>(difference * (float) _coefP / 1000000.0)
};
cf_pulses_last = cf_pulses; cf_pulses_last = cf_pulses;
} }
@ -383,7 +386,6 @@ class CSE7766Sensor : public BaseSensor {
double _reactive = 0; double _reactive = 0;
double _voltage = 0; double _voltage = 0;
double _current = 0; double _current = 0;
double _energy = 0;
double _ratioV = 1.0; double _ratioV = 1.0;
double _ratioC = 1.0; double _ratioC = 1.0;


+ 1
- 1
code/espurna/sensors/DHTSensor.h View File

@ -59,7 +59,7 @@ class DHTSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
DHTSensor(): BaseSensor() {
DHTSensor() {
_count = 2; _count = 2;
_sensor_id = SENSOR_DHTXX_ID; _sensor_id = SENSOR_DHTXX_ID;
} }


+ 10
- 3
code/espurna/sensors/DallasSensor.h View File

@ -70,7 +70,7 @@ class DallasSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
DallasSensor(): BaseSensor() {
DallasSensor() {
_sensor_id = SENSOR_DALLAS_ID; _sensor_id = SENSOR_DALLAS_ID;
} }
@ -267,8 +267,15 @@ class DallasSensor : public BaseSensor {
} }
// Number of decimals for a magnitude (or -1 for default) // Number of decimals for a magnitude (or -1 for default)
signed char decimals(unsigned char type) {
return 2; // smallest increment is 0.0625 C, so 2 decimals
signed char decimals(sensor::Unit unit) {
switch (unit) {
// Smallest increment is 0.0625 C, so 2 decimals
case sensor::Unit::Celcius:
return 2;
// In case we have DS2406, there is no decimal places
default:
return 0;
}
} }
// Pre-read hook (usually to populate registers with up-to-date data) // Pre-read hook (usually to populate registers with up-to-date data)


+ 1
- 1
code/espurna/sensors/DigitalSensor.h View File

@ -19,7 +19,7 @@ class DigitalSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
DigitalSensor(): BaseSensor() {
DigitalSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_DIGITAL_ID; _sensor_id = SENSOR_DIGITAL_ID;
} }


+ 8
- 11
code/espurna/sensors/ECH1560Sensor.h View File

@ -10,9 +10,11 @@
#include <Arduino.h> #include <Arduino.h>
#include "../debug.h" #include "../debug.h"
#include "BaseSensor.h" #include "BaseSensor.h"
#include "BaseEmonSensor.h"
class ECH1560Sensor : public BaseSensor {
class ECH1560Sensor : public BaseEmonSensor {
public: public:
@ -20,7 +22,7 @@ class ECH1560Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
ECH1560Sensor(): BaseSensor(), _data() {
ECH1560Sensor(): _data() {
_count = 3; _count = 3;
_sensor_id = SENSOR_ECH1560_ID; _sensor_id = SENSOR_ECH1560_ID;
} }
@ -61,12 +63,6 @@ class ECH1560Sensor : public BaseSensor {
return _inverted; return _inverted;
} }
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
_energy = value;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -123,7 +119,7 @@ class ECH1560Sensor : public BaseSensor {
if (index == 0) return _current; if (index == 0) return _current;
if (index == 1) return _voltage; if (index == 1) return _voltage;
if (index == 2) return _apparent; if (index == 2) return _apparent;
if (index == 3) return _energy;
if (index == 3) return getEnergy();
return 0; return 0;
} }
@ -272,7 +268,9 @@ class ECH1560Sensor : public BaseSensor {
static unsigned long last = 0; static unsigned long last = 0;
if (last > 0) { if (last > 0) {
_energy += (_apparent * (millis() - last) / 1000);
_energy[0] += sensor::Ws {
static_cast<uint32_t>(_apparent * (millis() - last) / 1000)
};
} }
last = millis(); last = millis();
@ -303,7 +301,6 @@ class ECH1560Sensor : public BaseSensor {
double _apparent = 0; double _apparent = 0;
double _voltage = 0; double _voltage = 0;
double _current = 0; double _current = 0;
double _energy = 0;
unsigned char _data[24]; unsigned char _data[24];


+ 1
- 1
code/espurna/sensors/EZOPHSensor.h View File

@ -22,7 +22,7 @@ class EZOPHSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
EZOPHSensor(): BaseSensor() {
EZOPHSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_EZOPH_ID; _sensor_id = SENSOR_EZOPH_ID;
} }


+ 7
- 5
code/espurna/sensors/EmonADC121Sensor.h View File

@ -33,7 +33,7 @@ class EmonADC121Sensor : public EmonSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
EmonADC121Sensor(): EmonSensor() {
EmonADC121Sensor() {
_channels = ADC121_CHANNELS; _channels = ADC121_CHANNELS;
_sensor_id = SENSOR_EMON_ADC121_ID; _sensor_id = SENSOR_EMON_ADC121_ID;
init(); init();
@ -86,13 +86,15 @@ class EmonADC121Sensor : public EmonSensor {
return; return;
} }
// only 1 channel, see ADC121_CHANNELS
_current[0] = read(0); _current[0] = read(0);
#if EMON_REPORT_ENERGY #if EMON_REPORT_ENERGY
static unsigned long last = 0; static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
}
_energy[0] += sensor::Ws {
static_cast<uint32_t>(_current[0] * _voltage * (millis() - last) / 1000)
};
last = millis(); last = millis();
#endif #endif
@ -126,7 +128,7 @@ class EmonADC121Sensor : public EmonSensor {
if (index == i++) return _current[channel] * _voltage; if (index == i++) return _current[channel] * _voltage;
#endif #endif
#if EMON_REPORT_ENERGY #if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
if (index == i) return _energy[channel].asDouble();
#endif #endif
return 0; return 0;
} }


+ 8
- 3
code/espurna/sensors/EmonADS1X15Sensor.h View File

@ -93,6 +93,7 @@
#define ADS1X15_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions #define ADS1X15_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions
#define ADS1X15_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default) #define ADS1X15_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default)
class EmonADS1X15Sensor : public EmonSensor { class EmonADS1X15Sensor : public EmonSensor {
public: public:
@ -101,7 +102,7 @@ class EmonADS1X15Sensor : public EmonSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
EmonADS1X15Sensor(): EmonSensor() {
EmonADS1X15Sensor() {
_channels = ADS1X15_CHANNELS; _channels = ADS1X15_CHANNELS;
_sensor_id = SENSOR_EMON_ADS1X15_ID; _sensor_id = SENSOR_EMON_ADS1X15_ID;
init(); init();
@ -162,6 +163,8 @@ class EmonADS1X15Sensor : public EmonSensor {
if (mask & 0x01) ++_ports; if (mask & 0x01) ++_ports;
mask = mask >> 1; mask = mask >> 1;
} }
resizeDevices(_ports);
_count = _ports * _magnitudes; _count = _ports * _magnitudes;
// Bit depth // Bit depth
@ -228,7 +231,9 @@ class EmonADS1X15Sensor : public EmonSensor {
unsigned char channel = getChannel(port); unsigned char channel = getChannel(port);
_current[port] = getCurrent(channel); _current[port] = getCurrent(channel);
#if EMON_REPORT_ENERGY #if EMON_REPORT_ENERGY
_energy[port] += (_current[port] * _voltage * (millis() - last) / 1000);
_energy[port] += sensor::Ws {
static_cast<uint32_t>(_current[port] * _voltage * (millis() - last) / 1000)
};
#endif #endif
} }
last = millis(); last = millis();
@ -247,7 +252,7 @@ class EmonADS1X15Sensor : public EmonSensor {
if (magnitude == i++) return _current[port] * _voltage; if (magnitude == i++) return _current[port] * _voltage;
#endif #endif
#if EMON_REPORT_ENERGY #if EMON_REPORT_ENERGY
if (magnitude == i) return _energy[port];
if (magnitude == i) return _energy[port].asDouble();
#endif #endif
return 0; return 0;
} }


+ 6
- 4
code/espurna/sensors/EmonAnalogSensor.h View File

@ -22,7 +22,7 @@ class EmonAnalogSensor : public EmonSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
EmonAnalogSensor(): EmonSensor() {
EmonAnalogSensor() {
_channels = EMON_ANALOG_CHANNELS; _channels = EMON_ANALOG_CHANNELS;
_sensor_id = SENSOR_EMON_ANALOG_ID; _sensor_id = SENSOR_EMON_ANALOG_ID;
init(); init();
@ -87,8 +87,10 @@ class EmonAnalogSensor : public EmonSensor {
#if EMON_REPORT_ENERGY #if EMON_REPORT_ENERGY
static unsigned long last = 0; static unsigned long last = 0;
if (last > 0) {
_energy[0] += (_current[0] * _voltage * (millis() - last) / 1000);
for (unsigned char channel = 0; channel < _channels; ++channel) {
_energy[channel] += sensor::Ws {
static_cast<uint32_t>(_current[channel] * _voltage * (millis() - last) / 1000)
};
} }
last = millis(); last = millis();
#endif #endif
@ -108,7 +110,7 @@ class EmonAnalogSensor : public EmonSensor {
if (index == i++) return _current[channel] * _voltage; if (index == i++) return _current[channel] * _voltage;
#endif #endif
#if EMON_REPORT_ENERGY #if EMON_REPORT_ENERGY
if (index == i) return _energy[channel];
if (index == i) return _energy[channel].asDouble();
#endif #endif
return 0; return 0;
} }


+ 10
- 22
code/espurna/sensors/EmonSensor.h View File

@ -10,12 +10,15 @@
#include <Arduino.h> #include <Arduino.h>
#include "../debug.h" #include "../debug.h"
#include "BaseEmonSensor.h"
#include "I2CSensor.h" #include "I2CSensor.h"
extern "C" { extern "C" {
#include "../libs/fs_math.h" #include "../libs/fs_math.h"
} }
class EmonSensor : public I2CSensor {
class EmonSensor : public I2CSensor<BaseEmonSensor> {
public: public:
@ -23,7 +26,7 @@ class EmonSensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
EmonSensor(): I2CSensor() {
EmonSensor() {
// Calculate # of magnitudes // Calculate # of magnitudes
#if EMON_REPORT_CURRENT #if EMON_REPORT_CURRENT
@ -48,17 +51,6 @@ class EmonSensor : public I2CSensor {
_dirty = true; _dirty = true;
} }
void resetEnergy() {
for (unsigned char i=0; i<_channels; i++) {
_energy[i] = 0;
}
}
void resetEnergy(unsigned char channel, double value = 0) {
if (channel >= _channels) return;
_energy[channel] = value;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
void setVoltage(double voltage) { void setVoltage(double voltage) {
@ -81,6 +73,10 @@ class EmonSensor : public I2CSensor {
_dirty = true; _dirty = true;
} }
void resetRatios() {
setCurrentRatio(0, EMON_CURRENT_RATIO);
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
double getVoltage() { double getVoltage() {
@ -111,7 +107,7 @@ class EmonSensor : public I2CSensor {
// Calculations // Calculations
for (unsigned char i=0; i<_channels; i++) { for (unsigned char i=0; i<_channels; i++) {
_energy[i] = _current[i] = 0;
_energy[i] = _current[i] = 0.0;
_pivot[i] = _adc_counts >> 1; _pivot[i] = _adc_counts >> 1;
calculateFactors(i); calculateFactors(i);
} }
@ -145,9 +141,6 @@ class EmonSensor : public I2CSensor {
_multiplier = new uint16_t[_channels]; _multiplier = new uint16_t[_channels];
_pivot = new double[_channels]; _pivot = new double[_channels];
_current = new double[_channels]; _current = new double[_channels];
#if EMON_REPORT_ENERGY
_energy = new uint32_t[_channels];
#endif
} }
virtual unsigned int readADC(unsigned char channel) = 0; virtual unsigned int readADC(unsigned char channel) = 0;
@ -246,11 +239,6 @@ class EmonSensor : public I2CSensor {
double * _pivot; // Moving average mid point (per channel) double * _pivot; // Moving average mid point (per channel)
double * _current; // Last current reading (per channel) double * _current; // Last current reading (per channel)
#if EMON_REPORT_ENERGY
uint32_t * _energy; // Aggregated energy (per channel)
#endif
}; };


+ 1
- 1
code/espurna/sensors/EventSensor.h View File

@ -23,7 +23,7 @@ class EventSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
EventSensor(): BaseSensor() {
EventSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_EVENTS_ID; _sensor_id = SENSOR_EVENTS_ID;
} }


+ 1
- 1
code/espurna/sensors/GUVAS12SDSensor.h View File

@ -36,7 +36,7 @@ class GUVAS12SDSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
GUVAS12SDSensor(): BaseSensor() {
GUVAS12SDSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_GUVAS12SD_ID; _sensor_id = SENSOR_GUVAS12SD_ID;
} }


+ 17
- 13
code/espurna/sensors/HLW8012Sensor.h View File

@ -11,9 +11,10 @@
#include <HLW8012.h> #include <HLW8012.h>
#include "../debug.h" #include "../debug.h"
#include "BaseSensor.h"
class HLW8012Sensor : public BaseSensor {
#include "BaseEmonSensor.h"
class HLW8012Sensor : public BaseEmonSensor {
public: public:
@ -21,8 +22,8 @@ class HLW8012Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
HLW8012Sensor(): BaseSensor() {
_count = 7;
HLW8012Sensor() {
_count = 8;
_sensor_id = SENSOR_HLW8012_ID; _sensor_id = SENSOR_HLW8012_ID;
_hlw8012 = new HLW8012(); _hlw8012 = new HLW8012();
} }
@ -48,11 +49,6 @@ class HLW8012Sensor : public BaseSensor {
_hlw8012->resetMultipliers(); _hlw8012->resetMultipliers();
} }
void resetEnergy(double value = 0) {
_energy_offset = value;
_hlw8012->resetEnergy();
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
void setSEL(unsigned char sel) { void setSEL(unsigned char sel) {
@ -185,10 +181,18 @@ class HLW8012Sensor : public BaseSensor {
if (index == 3) return MAGNITUDE_POWER_REACTIVE; if (index == 3) return MAGNITUDE_POWER_REACTIVE;
if (index == 4) return MAGNITUDE_POWER_APPARENT; if (index == 4) return MAGNITUDE_POWER_APPARENT;
if (index == 5) return MAGNITUDE_POWER_FACTOR; if (index == 5) return MAGNITUDE_POWER_FACTOR;
if (index == 6) return MAGNITUDE_ENERGY;
if (index == 6) return MAGNITUDE_ENERGY_DELTA;
if (index == 7) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
double getEnergyDelta() {
const auto result = _hlw8012->getEnergy();
_energy[0] += sensor::Ws { result };
_hlw8012->resetEnergy();
return result;
}
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) { double value(unsigned char index) {
if (index == 0) return _hlw8012->getCurrent(); if (index == 0) return _hlw8012->getCurrent();
@ -197,8 +201,9 @@ class HLW8012Sensor : public BaseSensor {
if (index == 3) return _hlw8012->getReactivePower(); if (index == 3) return _hlw8012->getReactivePower();
if (index == 4) return _hlw8012->getApparentPower(); if (index == 4) return _hlw8012->getApparentPower();
if (index == 5) return 100 * _hlw8012->getPowerFactor(); if (index == 5) return 100 * _hlw8012->getPowerFactor();
if (index == 6) return (_energy_offset + _hlw8012->getEnergy());
return 0;
if (index == 6) return getEnergyDelta();
if (index == 7) return getEnergy();
return 0.0;
} }
// Pre-read hook (usually to populate registers with up-to-date data) // Pre-read hook (usually to populate registers with up-to-date data)
@ -272,7 +277,6 @@ class HLW8012Sensor : public BaseSensor {
unsigned char _cf = GPIO_NONE; unsigned char _cf = GPIO_NONE;
unsigned char _cf1 = GPIO_NONE; unsigned char _cf1 = GPIO_NONE;
bool _sel_current = true; bool _sel_current = true;
double _energy_offset = 0;
HLW8012 * _hlw8012 = NULL; HLW8012 * _hlw8012 = NULL;


+ 5
- 4
code/espurna/sensors/I2CSensor.h View File

@ -27,14 +27,15 @@
#include "BaseSensor.h" #include "BaseSensor.h"
class I2CSensor : public BaseSensor {
template <typename T = BaseSensor>
class I2CSensor : public T {
public: public:
void setAddress(unsigned char address) { void setAddress(unsigned char address) {
if (_address == address) return; if (_address == address) return;
_address = address; _address = address;
_dirty = true;
T::_dirty = true;
} }
unsigned char getAddress() { unsigned char getAddress() {
@ -43,7 +44,7 @@ class I2CSensor : public BaseSensor {
// Descriptive name of the slot # index // Descriptive name of the slot # index
String slot(unsigned char index) { String slot(unsigned char index) {
return description();
return static_cast<T*>(this)->description();
}; };
// Address of the sensor (it could be the GPIO or I2C address) // Address of the sensor (it could be the GPIO or I2C address)
@ -80,7 +81,7 @@ class I2CSensor : public BaseSensor {
// Flag error // Flag error
if (0 == _previous_address) { if (0 == _previous_address) {
_error = SENSOR_ERROR_I2C;
T::_error = SENSOR_ERROR_I2C;
} }
return _previous_address; return _previous_address;


+ 1
- 1
code/espurna/sensors/LDRSensor.h View File

@ -30,7 +30,7 @@ class LDRSensor : public AnalogSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
LDRSensor(): AnalogSensor() {
LDRSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_LDR_ID; _sensor_id = SENSOR_LDR_ID;
} }


+ 1
- 1
code/espurna/sensors/MAX6675Sensor.h View File

@ -25,7 +25,7 @@ class MAX6675Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
MAX6675Sensor(): BaseSensor() {
MAX6675Sensor() {
_sensor_id = SENSOR_MAX6675_ID; _sensor_id = SENSOR_MAX6675_ID;
_count = 1; _count = 1;
} }


+ 1
- 1
code/espurna/sensors/MHZ19Sensor.h View File

@ -32,7 +32,7 @@ class MHZ19Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
MHZ19Sensor(): BaseSensor() {
MHZ19Sensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_MHZ19_ID; _sensor_id = SENSOR_MHZ19_ID;
} }


+ 3
- 22
code/espurna/sensors/MICS2710Sensor.h View File

@ -9,12 +9,12 @@
#include <Arduino.h> #include <Arduino.h>
#include "BaseSensor.h"
#include "BaseAnalogSensor.h"
extern "C" { extern "C" {
#include "../libs/fs_math.h" #include "../libs/fs_math.h"
} }
class MICS2710Sensor : public BaseSensor {
class MICS2710Sensor : public BaseAnalogSensor {
public: public:
@ -22,7 +22,7 @@ class MICS2710Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
MICS2710Sensor(): BaseSensor() {
MICS2710Sensor() {
_count = 2; _count = 2;
_sensor_id = SENSOR_MICS2710_ID; _sensor_id = SENSOR_MICS2710_ID;
} }
@ -49,22 +49,6 @@ class MICS2710Sensor : public BaseSensor {
return _preGPIO; return _preGPIO;
} }
void setRL(unsigned long Rl) {
if (Rl > 0) _Rl = Rl;
}
unsigned long getRL() {
return _Rl;
}
void setR0(unsigned long R0) {
if (R0 > 0) _R0 = R0;
}
unsigned long getR0() {
return _R0;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -174,9 +158,6 @@ class MICS2710Sensor : public BaseSensor {
bool _heating = false; bool _heating = false;
unsigned long _start = 0; // monitors the pre-heating time unsigned long _start = 0; // monitors the pre-heating time
unsigned long _R0 = MICS2710_R0; // R0, calikbration value at 25º
unsigned long _Rl = MICS2710_RL; // RL, load resistance
unsigned long _Rs = 0; // cached resistance
unsigned char _noxGPIO = MICS2710_PRE_PIN; unsigned char _noxGPIO = MICS2710_PRE_PIN;
unsigned char _preGPIO = MICS2710_NOX_PIN; unsigned char _preGPIO = MICS2710_NOX_PIN;


+ 3
- 22
code/espurna/sensors/MICS5525Sensor.h View File

@ -9,12 +9,12 @@
#include <Arduino.h> #include <Arduino.h>
#include "BaseSensor.h"
#include "BaseAnalogSensor.h"
extern "C" { extern "C" {
#include "../libs/fs_math.h" #include "../libs/fs_math.h"
} }
class MICS5525Sensor : public BaseSensor {
class MICS5525Sensor : public BaseAnalogSensor {
public: public:
@ -22,7 +22,7 @@ class MICS5525Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
MICS5525Sensor(): BaseSensor() {
MICS5525Sensor() {
_count = 2; _count = 2;
_sensor_id = SENSOR_MICS5525_ID; _sensor_id = SENSOR_MICS5525_ID;
} }
@ -41,22 +41,6 @@ class MICS5525Sensor : public BaseSensor {
return _redGPIO; return _redGPIO;
} }
void setRL(unsigned long Rl) {
if (Rl > 0) _Rl = Rl;
}
unsigned long getRL() {
return _Rl;
}
void setR0(unsigned long R0) {
if (R0 > 0) _R0 = R0;
}
unsigned long getR0() {
return _R0;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -130,9 +114,6 @@ class MICS5525Sensor : public BaseSensor {
} }
unsigned long _R0 = MICS5525_R0; // R0, calibration value at 25º on air
unsigned long _Rl = MICS5525_RL; // RL, load resistance
unsigned long _Rs = 0; // cached resistance
unsigned char _redGPIO = MICS5525_RED_PIN; unsigned char _redGPIO = MICS5525_RED_PIN;
}; };


+ 1
- 1
code/espurna/sensors/NTCSensor.h View File

@ -22,7 +22,7 @@ class NTCSensor : public AnalogSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
NTCSensor(): AnalogSensor() {
NTCSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_NTC_ID; _sensor_id = SENSOR_NTC_ID;
} }


+ 1
- 1
code/espurna/sensors/PMSX003Sensor.h View File

@ -159,7 +159,7 @@ class PMSX003Sensor : public BaseSensor, PMSX003 {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
PMSX003Sensor(): BaseSensor() {
PMSX003Sensor() {
_count = pms_specs[_type].slot_count; _count = pms_specs[_type].slot_count;
_sensor_id = SENSOR_PMSX003_ID; _sensor_id = SENSOR_PMSX003_ID;
} }


+ 170
- 62
code/espurna/sensors/PZEM004TSensor.h View File

@ -53,7 +53,10 @@
#include <PZEM004T.h> #include <PZEM004T.h>
#include "BaseSensor.h" #include "BaseSensor.h"
#include "BaseEmonSensor.h"
#include "../sensor.h"
#include "../terminal.h"
#define PZ_MAGNITUDE_COUNT 4 #define PZ_MAGNITUDE_COUNT 4
@ -62,20 +65,46 @@
#define PZ_MAGNITUDE_POWER_ACTIVE_INDEX 2 #define PZ_MAGNITUDE_POWER_ACTIVE_INDEX 2
#define PZ_MAGNITUDE_ENERGY_INDEX 3 #define PZ_MAGNITUDE_ENERGY_INDEX 3
class PZEM004TSensor : public BaseSensor {
class PZEM004TSensor : public BaseEmonSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
private:
PZEM004TSensor(): BaseSensor() {
// We can only create a single instance of the sensor class.
PZEM004TSensor() : BaseEmonSensor(0) {
_sensor_id = SENSOR_PZEM004T_ID; _sensor_id = SENSOR_PZEM004T_ID;
} }
~PZEM004TSensor() { ~PZEM004TSensor() {
if (_pzem) delete _pzem; 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() {}
void resetEnergy(unsigned char) {}
void resetEnergy(unsigned char, sensor::Energy) {}
// Override Base methods that deal with _energy[]
size_t countDevices() {
return _addresses.size();
}
double getEnergy(unsigned char index) {
return _readings[index].energy;
}
sensor::Energy totalEnergy(unsigned char index) {
return getEnergy(index);
} }
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -114,24 +143,19 @@ class PZEM004TSensor : public BaseSensor {
reading.power = PZEM_ERROR_VALUE; reading.power = PZEM_ERROR_VALUE;
reading.energy = PZEM_ERROR_VALUE; reading.energy = PZEM_ERROR_VALUE;
if (addr.fromString(address)) { if (addr.fromString(address)) {
_devices.push_back(addr);
_energy_offsets.push_back(0);
_addresses.push_back(addr);
_readings.push_back(reading); _readings.push_back(reading);
} }
address = strtok(0, sep); address = strtok(0, sep);
} }
_count = _devices.size() * PZ_MAGNITUDE_COUNT;
_dirty = true;
}
// Return the number of devices managed by this sensor
unsigned char getAddressesCount() {
return _devices.size();
_count = _addresses.size() * PZ_MAGNITUDE_COUNT;
_dirty = true;
} }
// Get device physical address based on the device index // Get device physical address based on the device index
String getAddress(unsigned char dev) { String getAddress(unsigned char dev) {
return _devices[dev].toString();
return _addresses[dev].toString();
} }
// Set the device physical address // Set the device physical address
@ -154,15 +178,6 @@ class PZEM004TSensor : public BaseSensor {
return _pin_tx; return _pin_tx;
} }
// ---------------------------------------------------------------------
// If called with value = -1, the offset will be the last energy reading
// otherwise, it will be the value provided
float resetEnergy(unsigned char dev, float value = -1) {
_energy_offsets[dev] = value != -1 ? value : _readings[dev].energy;
return _energy_offsets[dev];
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -177,7 +192,7 @@ class PZEM004TSensor : public BaseSensor {
} else { } else {
_pzem = new PZEM004T(_pin_rx, _pin_tx); _pzem = new PZEM004T(_pin_rx, _pin_tx);
} }
if(_devices.size() == 1) _pzem->setAddress(_devices[0]);
if(_addresses.size() == 1) _pzem->setAddress(_addresses[0]);
_ready = true; _ready = true;
_dirty = false; _dirty = false;
@ -205,7 +220,7 @@ class PZEM004TSensor : public BaseSensor {
// Address of the sensor (it could be the GPIO or I2C address) // Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) { String address(unsigned char index) {
int dev = index / PZ_MAGNITUDE_COUNT; int dev = index / PZ_MAGNITUDE_COUNT;
return _devices[dev].toString();
return _addresses[dev].toString();
} }
// Type for slot # index // Type for slot # index
@ -221,14 +236,33 @@ class PZEM004TSensor : public BaseSensor {
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) { double value(unsigned char index) {
double response = 0.0;
int dev = index / PZ_MAGNITUDE_COUNT; int dev = index / PZ_MAGNITUDE_COUNT;
index = index - (dev * PZ_MAGNITUDE_COUNT); index = index - (dev * PZ_MAGNITUDE_COUNT);
double response = 0;
if (index == PZ_MAGNITUDE_CURRENT_INDEX) response = _readings[dev].current;
if (index == PZ_MAGNITUDE_VOLTAGE_INDEX) response = _readings[dev].voltage;
if (index == PZ_MAGNITUDE_POWER_ACTIVE_INDEX) response = _readings[dev].power;
if (index == PZ_MAGNITUDE_ENERGY_INDEX) response = (_readings[dev].energy * 3600) - _energy_offsets[dev];
if (response < 0) response = 0;
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; return response;
} }
@ -254,66 +288,140 @@ class PZEM004TSensor : public BaseSensor {
// This we cannot do it from outside the library // This we cannot do it from outside the library
} }
float read;
float* readings_p;
switch(magnitude) {
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: case PZ_MAGNITUDE_CURRENT_INDEX:
read = _pzem->current(_devices[dev]);
read = _pzem->current(_addresses[dev]);
readings_p = &_readings[dev].current; readings_p = &_readings[dev].current;
break; break;
case PZ_MAGNITUDE_VOLTAGE_INDEX: case PZ_MAGNITUDE_VOLTAGE_INDEX:
read = _pzem->voltage(_devices[dev]);
read = _pzem->voltage(_addresses[dev]);
readings_p = &_readings[dev].voltage; readings_p = &_readings[dev].voltage;
break; break;
case PZ_MAGNITUDE_POWER_ACTIVE_INDEX: case PZ_MAGNITUDE_POWER_ACTIVE_INDEX:
read = _pzem->power(_devices[dev]);
read = _pzem->power(_addresses[dev]);
readings_p = &_readings[dev].power; readings_p = &_readings[dev].power;
break; break;
case PZ_MAGNITUDE_ENERGY_INDEX: case PZ_MAGNITUDE_ENERGY_INDEX:
read = _pzem->energy(_devices[dev]);
read = _pzem->energy(_addresses[dev]);
readings_p = &_readings[dev].energy; readings_p = &_readings[dev].energy;
break; break;
default: default:
_busy = false; _busy = false;
return; return;
} }
if(read == PZEM_ERROR_VALUE) {
if (read == PZEM_ERROR_VALUE) {
_error = SENSOR_ERROR_TIMEOUT; _error = SENSOR_ERROR_TIMEOUT;
} else { } else {
*readings_p = read; *readings_p = read;
} }
if(++dev == _devices.size()) {
dev = 0;
last_millis = millis();
if(++magnitude == PZ_MAGNITUDE_COUNT) {
magnitude = 0;
}
}
_busy = false;
} }
protected:
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
bool _busy = false;
typedef struct {
struct reading_t {
float voltage; float voltage;
float current; float current;
float power; float power;
float energy; float energy;
} reading_t;
};
unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN;
bool _busy = false;
std::vector<reading_t> _readings; std::vector<reading_t> _readings;
std::vector<float> _energy_offsets;
std::vector<IPAddress> _devices;
std::vector<IPAddress> _addresses;
HardwareSerial * _serial = NULL; HardwareSerial * _serial = NULL;
PZEM004T * _pzem = NULL; PZEM004T * _pzem = NULL;
}; };
PZEM004TSensor* PZEM004TSensor::instance = nullptr;
#if TERMINAL_SUPPORT
void pzem004tInitCommands() {
terminalRegisterCommand(F("PZ.ADDRESS"), [](Embedis* e) {
if (!PZEM004TSensor::instance) return;
if (e->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(e->argc == 2) {
IPAddress addr;
if (addr.fromString(String(e->argv[1]))) {
if(PZEM004TSensor::instance->setDeviceAddress(&addr)) {
terminalOK();
}
} else {
terminalError(F("Invalid address argument"));
}
} else {
terminalError(F("Wrong arguments"));
}
});
terminalRegisterCommand(F("PZ.RESET"), [](Embedis* e) {
if(e->argc > 2) {
terminalError(F("Wrong arguments"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->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"), [](Embedis* e) {
if(e->argc > 2) {
terminalError(F("Wrong arguments"));
} else {
unsigned char init = e->argc == 2 ? String(e->argv[1]).toInt() : 0;
unsigned char limit = e->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 #endif // SENSOR_SUPPORT && PZEM004T_SUPPORT

+ 8
- 20
code/espurna/sensors/PulseMeterSensor.h View File

@ -10,9 +10,11 @@
#include <Arduino.h> #include <Arduino.h>
#include "../debug.h" #include "../debug.h"
#include "BaseSensor.h" #include "BaseSensor.h"
#include "BaseEmonSensor.h"
class PulseMeterSensor : public BaseSensor {
class PulseMeterSensor : public BaseEmonSensor {
public: public:
@ -20,7 +22,7 @@ class PulseMeterSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
PulseMeterSensor(): BaseSensor() {
PulseMeterSensor() {
_count = 2; _count = 2;
_sensor_id = SENSOR_PULSEMETER_ID; _sensor_id = SENSOR_PULSEMETER_ID;
} }
@ -29,10 +31,6 @@ class PulseMeterSensor : public BaseSensor {
_enableInterrupts(false); _enableInterrupts(false);
} }
void resetEnergy(double value = 0) {
_energy = value;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) { void setGPIO(unsigned char gpio) {
@ -41,10 +39,6 @@ class PulseMeterSensor : public BaseSensor {
_dirty = true; _dirty = true;
} }
void setEnergyRatio(unsigned long ratio) {
if (ratio > 0) _ratio = ratio;
}
void setInterruptMode(unsigned char interrupt_mode) { void setInterruptMode(unsigned char interrupt_mode) {
_interrupt_mode = interrupt_mode; _interrupt_mode = interrupt_mode;
} }
@ -59,10 +53,6 @@ class PulseMeterSensor : public BaseSensor {
return _gpio; return _gpio;
} }
unsigned long getEnergyRatio() {
return _ratio;
}
unsigned char getInterruptMode() { unsigned char getInterruptMode() {
return _interrupt_mode; return _interrupt_mode;
} }
@ -109,9 +99,9 @@ class PulseMeterSensor : public BaseSensor {
unsigned long pulses = _pulses - _previous_pulses; unsigned long pulses = _pulses - _previous_pulses;
_previous_pulses = _pulses; _previous_pulses = _pulses;
unsigned long _energy_delta = 1000 * 3600 * pulses / _ratio;
_energy += _energy_delta;
if (lapse > 0) _active = 1000 * _energy_delta / lapse;
sensor::Ws delta = 1000 * 3600 * pulses / getEnergyRatio();
if (lapse > 0) _active = 1000 * delta.value / lapse;
_energy[0] += delta;
} }
@ -125,7 +115,7 @@ class PulseMeterSensor : public BaseSensor {
// Current value for slot # index // Current value for slot # index
double value(unsigned char index) { double value(unsigned char index) {
if (index == 0) return _active; if (index == 0) return _active;
if (index == 1) return _energy;
if (index == 1) return _energy[0].asDouble();
return 0; return 0;
} }
@ -171,11 +161,9 @@ class PulseMeterSensor : public BaseSensor {
unsigned char _previous = GPIO_NONE; unsigned char _previous = GPIO_NONE;
unsigned char _gpio = GPIO_NONE; unsigned char _gpio = GPIO_NONE;
unsigned long _ratio = PULSEMETER_ENERGY_RATIO;
unsigned long _debounce = PULSEMETER_DEBOUNCE; unsigned long _debounce = PULSEMETER_DEBOUNCE;
double _active = 0; double _active = 0;
double _energy = 0;
volatile unsigned long _pulses = 0; volatile unsigned long _pulses = 0;
unsigned long _previous_pulses = 0; unsigned long _previous_pulses = 0;


+ 1
- 1
code/espurna/sensors/SDS011Sensor.h View File

@ -24,7 +24,7 @@ class SDS011Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
SDS011Sensor(): BaseSensor() {
SDS011Sensor() {
_count = 2; _count = 2;
_sensor_id = SENSOR_SDS011_ID; _sensor_id = SENSOR_SDS011_ID;
} }


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

@ -12,7 +12,7 @@
#include "I2CSensor.h" #include "I2CSensor.h"
#include "../utils.h" #include "../utils.h"
class SHT3XI2CSensor : public I2CSensor {
class SHT3XI2CSensor : public I2CSensor<> {
public: public:
@ -20,7 +20,7 @@ class SHT3XI2CSensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
SHT3XI2CSensor(): I2CSensor() {
SHT3XI2CSensor() {
_sensor_id = SENSOR_SHT3X_I2C_ID; _sensor_id = SENSOR_SHT3X_I2C_ID;
_count = 2; _count = 2;
} }


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

@ -25,7 +25,7 @@
PROGMEM const char si7021_chip_si7021_name[] = "SI7021"; PROGMEM const char si7021_chip_si7021_name[] = "SI7021";
PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D"; PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D";
class SI7021Sensor : public I2CSensor {
class SI7021Sensor : public I2CSensor<> {
public: public:
@ -33,7 +33,7 @@ class SI7021Sensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
SI7021Sensor(): I2CSensor() {
SI7021Sensor() {
_sensor_id = SENSOR_SI7021_ID; _sensor_id = SENSOR_SI7021_ID;
} }


+ 1
- 1
code/espurna/sensors/SenseAirSensor.h View File

@ -116,7 +116,7 @@ class SenseAirSensor : public BaseSensor, SenseAir {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
SenseAirSensor(): BaseSensor() {
SenseAirSensor() {
_count = 1; _count = 1;
_co2 = 0; _co2 = 0;
_lastCo2 = 0; _lastCo2 = 0;


+ 1
- 1
code/espurna/sensors/SonarSensor.h View File

@ -21,7 +21,7 @@ class SonarSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
SonarSensor(): BaseSensor() {
SonarSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_SONAR_ID; _sensor_id = SENSOR_SONAR_ID;
} }


+ 1
- 1
code/espurna/sensors/T6613Sensor.h View File

@ -28,7 +28,7 @@ class T6613Sensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
T6613Sensor(): BaseSensor() {
T6613Sensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_T6613_ID; _sensor_id = SENSOR_T6613_ID;
} }


+ 1
- 1
code/espurna/sensors/TMP3XSensor.h View File

@ -23,7 +23,7 @@ class TMP3XSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
TMP3XSensor(): BaseSensor() {
TMP3XSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_TMP3X_ID; _sensor_id = SENSOR_TMP3X_ID;
} }


+ 7
- 12
code/espurna/sensors/V9261FSensor.h View File

@ -10,12 +10,12 @@
#include <Arduino.h> #include <Arduino.h>
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
#include "BaseSensor.h"
#include "BaseEmonSensor.h"
extern "C" { extern "C" {
#include "../libs/fs_math.h" #include "../libs/fs_math.h"
} }
class V9261FSensor : public BaseSensor {
class V9261FSensor : public BaseEmonSensor {
public: public:
@ -23,7 +23,7 @@ class V9261FSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
V9261FSensor(): BaseSensor(), _data() {
V9261FSensor(): _data() {
_count = 6; _count = 6;
_sensor_id = SENSOR_V9261F_ID; _sensor_id = SENSOR_V9261F_ID;
} }
@ -56,12 +56,6 @@ class V9261FSensor : public BaseSensor {
return _inverted; return _inverted;
} }
// ---------------------------------------------------------------------
void resetEnergy(double value = 0) {
_energy = value;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -124,7 +118,7 @@ class V9261FSensor : public BaseSensor {
if (index == 3) return _reactive; if (index == 3) return _reactive;
if (index == 4) return _apparent; if (index == 4) return _apparent;
if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100; if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
if (index == 6) return _energy;
if (index == 6) return _energy[0].asDouble();
return 0; return 0;
} }
@ -218,7 +212,9 @@ class V9261FSensor : public BaseSensor {
_apparent = fs_sqrt(_reactive * _reactive + _active * _active); _apparent = fs_sqrt(_reactive * _reactive + _active * _active);
if (last > 0) { if (last > 0) {
_energy += (_active * (millis() - last) / 1000);
_energy[0] += sensor::Ws {
static_cast<uint32_t>(_active * (millis() / last) / 1000)
};
} }
last = millis(); last = millis();
@ -263,7 +259,6 @@ class V9261FSensor : public BaseSensor {
double _voltage = 0; double _voltage = 0;
double _current = 0; double _current = 0;
double _apparent = 0; double _apparent = 0;
double _energy = 0;
double _ratioP = V9261F_POWER_FACTOR; double _ratioP = V9261F_POWER_FACTOR;
double _ratioC = V9261F_CURRENT_FACTOR; double _ratioC = V9261F_CURRENT_FACTOR;


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

@ -12,7 +12,7 @@
#include "I2CSensor.h" #include "I2CSensor.h"
class VEML6075Sensor : public I2CSensor {
class VEML6075Sensor : public I2CSensor<> {
public: public:
@ -20,7 +20,7 @@ class VEML6075Sensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
VEML6075Sensor(): I2CSensor() {
VEML6075Sensor() {
_count = 3; _count = 3;
_sensor_id = SENSOR_VEML6075_ID; _sensor_id = SENSOR_VEML6075_ID;
_veml6075 = new VEML6075(); _veml6075 = new VEML6075();


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

@ -12,7 +12,7 @@
#include "I2CSensor.h" #include "I2CSensor.h"
class VL53L1XSensor : public I2CSensor {
class VL53L1XSensor : public I2CSensor<> {
public: public:
@ -20,7 +20,7 @@ class VL53L1XSensor : public I2CSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
VL53L1XSensor(): I2CSensor() {
VL53L1XSensor() {
_count = 1; _count = 1;
_sensor_id = SENSOR_VL53L1X_ID; _sensor_id = SENSOR_VL53L1X_ID;
_vl53l1x = new VL53L1X(); _vl53l1x = new VL53L1X();


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


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


+ 78
- 78
code/espurna/static/index.thermostat.html.gz.h View File

@ -2367,82 +2367,82 @@ const uint8_t webui_image[] PROGMEM = {
0xcc,0xed,0x0a,0xa0,0xd3,0x80,0xef,0x50,0x00,0x70,0xd4,0xbc,0xca,0x15,0x14,0x84,0x8f,0x57,0xf3,0x1d, 0xcc,0xed,0x0a,0xa0,0xd3,0x80,0xef,0x50,0x00,0x70,0xd4,0xbc,0xca,0x15,0x14,0x84,0x8f,0x57,0xf3,0x1d,
0xaf,0xee,0x7d,0x39,0x76,0xd2,0xef,0x7d,0xa9,0xdc,0x19,0xf2,0x9e,0xb7,0xe8,0x54,0xf8,0x11,0x73,0x06, 0xaf,0xee,0x7d,0x39,0x76,0xd2,0xef,0x7d,0xa9,0xdc,0x19,0xf2,0x9e,0xb7,0xe8,0x54,0xf8,0x11,0x73,0x06,
0x94,0xb8,0xb0,0x52,0x0a,0x4e,0xb9,0xb4,0x52,0x12,0x4e,0x29,0xbc,0xad,0x57,0x5e,0xae,0x7e,0xa6,0xdd, 0x94,0xb8,0xb0,0x52,0x0a,0x4e,0xb9,0xb4,0x52,0x12,0x4e,0x29,0xbc,0xad,0x57,0x5e,0xae,0x7e,0xa6,0xdd,
0x8c,0x6f,0x54,0x3c,0x01,0x98,0x41,0x95,0xaa,0x0f,0x30,0x3a,0x3b,0x70,0xbe,0x4c,0xbc,0xff,0xf3,0xbf,
0x9f,0xd0,0x97,0xff,0x9f,0xff,0xfd,0x48,0xf9,0x1b,0x2d,0x11,0xba,0x07,0xa1,0xac,0x1a,0x77,0x65,0x35,
0xf3,0xe3,0xc3,0xa3,0x75,0x85,0x27,0xe7,0x58,0x61,0xaf,0x5d,0xfb,0xeb,0x75,0x9d,0x45,0x2b,0xfd,0x51,
0x15,0x63,0xba,0x91,0x19,0xbc,0x09,0xf6,0xde,0xa8,0x7b,0x66,0x5e,0xae,0xf2,0x58,0x22,0xe0,0x05,0xcb,
0x2a,0x8d,0xce,0x58,0x95,0x28,0x85,0xce,0xe8,0x9e,0xbd,0xa4,0x25,0xa1,0x62,0xb5,0xac,0x15,0xea,0xed,
0x6a,0xd7,0xda,0xa5,0x2f,0x58,0x55,0x5f,0x50,0x8f,0xf9,0x34,0x29,0x5b,0xcf,0xe4,0x95,0x26,0xc6,0x74,
0xa6,0x9b,0xdf,0xde,0xd1,0xc1,0x69,0xfe,0xc0,0x33,0x4d,0xaf,0x00,0x97,0x4d,0xdd,0x5f,0xb5,0x74,0x7f,
0xd5,0xec,0xfe,0x4a,0xe6,0x42,0xfa,0x5f,0xd5,0xa1,0xfa,0x7f,0x11,0x36,0x85,0x11,0xd5,0x17,0x5c,0x54,
0x5f,0x70,0x21,0x4e,0x2b,0xa6,0x06,0xba,0x7b,0xec,0x66,0xeb,0xe8,0xc8,0x17,0xcb,0x5e,0xc0,0x10,0x95,
0xfc,0xf3,0xa6,0x3a,0x85,0xc4,0xb1,0x9e,0x8e,0xdc,0xcb,0xe2,0x3c,0x38,0xa3,0xbf,0xf4,0x05,0xac,0xa0,
0xbb,0xa2,0xd5,0x7f,0x75,0x74,0xae,0x25,0x06,0xbd,0xde,0x95,0x7f,0x36,0xbd,0x9a,0x89,0xa3,0x8a,0xd1,
0xb6,0xd6,0xcf,0x2b,0x64,0x11,0xd3,0x98,0xdd,0x79,0xce,0x39,0xcf,0xd8,0xc9,0x63,0x10,0x2f,0xbb,0x76,
0x72,0xfd,0x90,0xeb,0x3b,0x65,0xd4,0xb9,0x08,0xae,0x91,0xbf,0xaf,0xae,0xa9,0x2c,0x03,0xfb,0x04,0xb2,
0x1e,0xaa,0xbb,0xd7,0x78,0x57,0xc1,0xea,0x34,0xab,0x7e,0xd7,0x8a,0xdd,0xf0,0x29,0xc0,0xcc,0x58,0xbc,
0x30,0x97,0x42,0x47,0x38,0xa3,0x3c,0x27,0x31,0xc4,0x97,0x15,0x57,0x53,0x0e,0x96,0xd4,0x4a,0x0b,0xff,
0x13,0x08,0x51,0xbc,0xdc,0xc9,0xf2,0xfc,0x78,0xfc,0xf8,0x36,0x8e,0xe7,0xf2,0xd3,0x1c,0xcf,0x58,0x8b,
0x8a,0x9c,0x7e,0xb7,0x5e,0x46,0xdb,0x6f,0x9d,0x65,0xc5,0x0f,0x9b,0xc5,0xca,0xa9,0xb6,0x04,0x28,0xb6,
0x15,0xb4,0xad,0xef,0x9c,0x55,0xae,0x7c,0xc4,0x71,0x63,0x29,0xe5,0xc6,0x12,0x0d,0x90,0xd0,0x9a,0x13,
0x8c,0xb1,0x93,0x13,0x09,0x8d,0x1b,0xa8,0x76,0x31,0x77,0x72,0x72,0xca,0xe7,0x5f,0x80,0x1a,0xea,0x3e,
0x33,0x5e,0x1e,0x64,0x06,0x12,0x22,0xa4,0x26,0x2f,0x28,0x25,0xfd,0xe6,0x73,0x35,0x0f,0x1c,0x30,0xe0,
0xda,0xb7,0x76,0x5c,0xc9,0xa2,0x03,0xde,0x6f,0x7c,0x20,0x7f,0xc0,0xcc,0xda,0x57,0x88,0xb2,0xfd,0x0a,
0xe1,0xf1,0x29,0xba,0xc7,0x78,0x3f,0xa8,0x79,0x4f,0x78,0x2b,0x4c,0x7d,0x49,0xff,0x60,0xcf,0x09,0xc3,
0x6b,0x34,0x05,0x1f,0xa0,0xe2,0xde,0x9a,0xf8,0x1c,0x0c,0xaf,0x06,0x8d,0xfc,0xc9,0xc0,0xf0,0xff,0xfe,
0x8d,0xa3,0xe5,0x1d,0xbf,0xaf,0x69,0x6c,0x95,0x80,0xf1,0x6a,0xc6,0xeb,0xea,0x63,0x68,0x15,0xa5,0xc4,
0xe0,0x54,0x12,0xf8,0xe2,0x80,0x4c,0xf5,0x2b,0xbe,0xca,0x8b,0x49,0x7a,0x2f,0x93,0xf4,0xb1,0x29,0x4b,
0x90,0xf2,0x56,0x9e,0xd3,0xcf,0x50,0xd5,0xbe,0xd7,0x44,0xaf,0xf9,0x65,0x7c,0xd5,0xd1,0x1f,0x26,0x2a,
0x7d,0xee,0xfd,0x7b,0x57,0x47,0x6f,0x3e,0x2d,0x78,0x15,0x56,0xe5,0xec,0xaf,0x30,0x1d,0x7a,0xf5,0x07,
0x3a,0xe4,0x89,0x45,0x25,0xce,0x43,0xe2,0x6f,0x79,0xf6,0x2c,0xeb,0x48,0xb6,0x5a,0xb4,0xc4,0x51,0x95,
0x3f,0x52,0x15,0x1b,0x77,0x5c,0x7e,0x86,0x27,0x54,0xd7,0x52,0x27,0xb9,0x32,0xd2,0x60,0x87,0xeb,0xb8,
0xf1,0x83,0xf2,0x11,0x34,0x28,0xfc,0x2c,0x97,0x29,0xdf,0x38,0x62,0x7f,0x86,0x4f,0x7d,0x54,0x7b,0xa9,
0x6e,0xa1,0xae,0x2b,0x3f,0xb1,0x87,0x29,0x43,0x09,0x20,0x9a,0x56,0xcc,0x0a,0xc9,0x2e,0x82,0x2b,0xf6,
0x7a,0x95,0xc1,0xa2,0x6d,0xf7,0x1b,0x72,0x4e,0x7e,0x6b,0x2c,0x5c,0x8d,0x99,0xb2,0xf3,0x12,0x29,0x81,
0x35,0xfa,0xd5,0x1b,0xa4,0xd4,0x3f,0xd1,0xc6,0x01,0x01,0x4c,0xf5,0xa8,0x9b,0x22,0x78,0x60,0xff,0xb6,
0x5e,0xf0,0xdb,0x5d,0xbd,0x50,0x2f,0x5b,0x7a,0xa1,0xde,0x7c,0xb2,0x17,0x43,0xdf,0xaf,0x09,0x2a,0x7f,
0xce,0x97,0x20,0x07,0x37,0x6c,0x67,0x99,0xc3,0x75,0xac,0x0c,0xa6,0xde,0x15,0xce,0x2c,0xe3,0x4c,0xe2,
0x69,0x1b,0x2e,0xe0,0xe4,0x5f,0x78,0xb3,0x56,0xda,0x81,0x0a,0xf8,0x8c,0x80,0xe3,0xf9,0xab,0x1f,0xf9,
0xd4,0x08,0x54,0x22,0xd6,0x4f,0x99,0xcd,0xb3,0x25,0xf3,0x06,0xf2,0x73,0x8b,0xdb,0x6d,0x59,0xae,0x8a,
0x11,0x30,0xc5,0x4c,0xf2,0x84,0x3b,0x72,0x55,0x54,0x45,0xa8,0x33,0x94,0x67,0xd4,0x96,0x3e,0xb2,0xd0,
0xf5,0xd4,0x90,0x9d,0x64,0xdc,0x38,0x1d,0x82,0xfa,0xe3,0x10,0x72,0x20,0x86,0x2f,0x31,0xd7,0x80,0x2f,
0x10,0x43,0xd2,0x9b,0xcb,0xb8,0xbc,0xc8,0x16,0x23,0xef,0xbb,0xe3,0x13,0x2f,0x98,0x67,0x82,0x10,0x40,
0xe7,0xf8,0x82,0xae,0x28,0x09,0xed,0xc1,0x91,0x57,0x10,0xd1,0xe8,0xcb,0x4a,0xf5,0xb6,0xfe,0xa0,0xbc,
0xa0,0x33,0xcd,0xfe,0xe6,0x7b,0x87,0x87,0x61,0xa8,0x85,0xe9,0x93,0xae,0x72,0xa2,0xdc,0xdf,0xd7,0xde,
0x94,0x74,0xf8,0x21,0x44,0x6e,0xa0,0xdf,0xf0,0xe0,0xbc,0x89,0x4f,0x5f,0xd3,0xef,0xb8,0xec,0xea,0x4f,
0x62,0x67,0x08,0xe8,0x36,0xd4,0x65,0xf2,0x56,0xeb,0x4e,0xd0,0x7f,0x83,0xeb,0x77,0xc0,0xf1,0xb4,0xbc,
0xb7,0x08,0x77,0x63,0x25,0xe6,0x92,0x98,0x3b,0x89,0xa5,0x24,0xc2,0x41,0x72,0x4c,0xbb,0xc4,0x96,0x8d,
0xc1,0x93,0x44,0xf7,0x39,0x4b,0xb9,0xd7,0x4e,0x1f,0xe6,0xb0,0x95,0x7b,0x0a,0x37,0x5d,0x6c,0x65,0x0b,
0x2b,0xd3,0x0f,0xea,0xf6,0x95,0x8f,0x64,0x1a,0x12,0x46,0xf9,0x2b,0x94,0xdf,0x5b,0xe5,0x00,0x10,0x28,
0x80,0xe2,0x17,0x3f,0x88,0xcd,0x25,0x7b,0xca,0x18,0xd3,0x70,0x4f,0x00,0x79,0x5a,0x44,0x53,0x43,0xd3,
0x52,0xc3,0xf1,0xd0,0xe9,0x3b,0x1c,0x4e,0x9c,0xae,0x5b,0x9d,0x0d,0x89,0xc0,0x9a,0x8f,0xb0,0xf4,0x12,
0xb6,0xf5,0x20,0xf2,0xb2,0xad,0xe2,0x36,0xf8,0x8a,0xe9,0xe8,0xc8,0xb1,0xeb,0xa4,0x45,0x20,0x7e,0xe9,
0xce,0xe8,0xb8,0xae,0xb4,0xb5,0x02,0x8d,0x15,0xaa,0xa3,0xa7,0x46,0x79,0x59,0x70,0x64,0x18,0xde,0x06,
0x23,0x01,0x29,0xaa,0x27,0x17,0x9c,0x4e,0xb4,0x54,0x32,0xe1,0x0e,0x4c,0x4d,0x38,0x6b,0xdd,0x6c,0x38,
0xbf,0xd9,0xd6,0x49,0xf6,0x48,0xdc,0x1e,0x90,0x81,0x7b,0xda,0x2c,0x57,0x1b,0x57,0xaa,0xe5,0x4e,0x0d,
0x28,0xc4,0x85,0x19,0x0d,0x6a,0xe8,0x8d,0x41,0x53,0x2d,0x14,0xec,0x18,0xe9,0x0a,0x01,0x73,0xcb,0xc6,
0xf5,0xad,0x96,0x31,0x9f,0x67,0x17,0xe3,0xd8,0x3d,0x36,0x0a,0xc0,0x46,0x51,0xd5,0xde,0x47,0xbe,0xfe,
0xb2,0x91,0xd1,0x18,0x43,0x72,0xbe,0x1d,0xde,0x4d,0x87,0xec,0xd6,0x34,0x3c,0x3c,0x54,0x56,0x3c,0x4a,
0xc5,0x2e,0xd2,0xad,0x9a,0x5a,0x5e,0xb9,0x07,0xb4,0x64,0xec,0x5b,0xa2,0xd4,0x96,0x12,0xce,0x67,0xe9,
0x92,0x5a,0x5c,0xb5,0xb3,0x6c,0x1d,0xdc,0xc1,0x29,0xad,0x7d,0x6c,0xdc,0x22,0x72,0x74,0xd4,0x32,0x1a,
0xef,0x97,0x7a,0x5e,0xf5,0xc2,0xc9,0x0e,0x79,0x6d,0x9f,0x4d,0x86,0x6b,0xd9,0x61,0x97,0xec,0xe4,0xbc,
0x88,0xfa,0x73,0x2d,0x90,0x74,0x72,0x6a,0xc3,0x62,0x27,0xb7,0x32,0xda,0xad,0x65,0xb5,0x0d,0x84,0x1b,
0x3c,0x5d,0x65,0x19,0x6c,0x10,0xe7,0xba,0x2d,0x45,0xac,0x16,0xfa,0x4c,0xc6,0xda,0x1b,0xc1,0x1b,0x27,
0xbf,0xe1,0xb0,0x05,0xae,0xb6,0x5e,0x4a,0x1c,0x1a,0xda,0x4b,0x68,0x13,0xfb,0xc6,0x88,0x72,0x72,0x7b,
0x19,0x65,0x50,0x5e,0x2f,0x63,0xdb,0xb4,0xcb,0xaa,0xb7,0xcc,0xe0,0x6d,0x5d,0xb8,0xed,0x09,0x51,0x5b,
0x81,0x72,0x88,0x37,0x56,0x1e,0x27,0x3b,0x59,0x2b,0x68,0x86,0x3e,0xdb,0xb5,0xf7,0x2b,0x68,0x86,0xc6,
0x97,0xb4,0x41,0x47,0x38,0x95,0x45,0xab,0x04,0x20,0x5c,0x4e,0x49,0x57,0x04,0xdb,0xd6,0xcd,0xbe,0x18,
0xfe,0xbb,0xc5,0x9a,0x18,0xd3,0xb5,0xa5,0x50,0xf3,0x08,0xb6,0xdc,0x03,0x1a,0xab,0xc6,0xca,0x3a,0x90,
0xb1,0xdb,0x11,0x67,0xcf,0xb8,0x15,0xd7,0x2a,0x40,0xba,0x82,0x94,0xd4,0x8a,0x31,0xf6,0xb3,0x75,0x09,
0x04,0x5d,0x0c,0xda,0x6d,0xed,0x1c,0x75,0xb8,0xb6,0x9d,0xb4,0x34,0x08,0xbe,0x65,0x9a,0xd5,0xa8,0x51,
0xcc,0x99,0x76,0xd8,0xf6,0x58,0x35,0xdb,0x17,0xb4,0x1b,0x65,0x0c,0x35,0x1a,0x06,0x06,0x27,0x6e,0xd4,
0x1f,0x6e,0x5b,0xaa,0xa7,0xfb,0x49,0xde,0xa8,0xb6,0xba,0xe2,0xb4,0xe5,0x17,0x84,0xb5,0x96,0x02,0x7c,
0x27,0x73,0x37,0x60,0xbc,0xec,0x8b,0xf8,0x77,0xa7,0xf1,0x2c,0x72,0x43,0xdd,0xdc,0xd7,0x48,0x0e,0xf5,
0x10,0x43,0xec,0x0a,0xe9,0x62,0x83,0x94,0xf2,0x21,0x0b,0x13,0x79,0xcf,0xde,0x15,0xfa,0xe2,0x54,0xf1,
0xc0,0xb7,0xe4,0xd5,0x57,0x76,0x37,0xb3,0xa7,0x83,0x8d,0xaa,0x5e,0xeb,0xc8,0x0a,0xce,0x80,0xab,0xd8,
0x91,0x4b,0x8e,0xd4,0xb3,0x6d,0x7a,0xa7,0xb5,0x03,0x39,0xd4,0xed,0x23,0x5a,0xa1,0x29,0x07,0x99,0xfc,
0x10,0x24,0xcb,0x1b,0x16,0xad,0x40,0xc0,0xc4,0x58,0x49,0xe0,0xa1,0xb1,0x20,0x47,0xb8,0xed,0xd4,0xf9,
0x23,0xcd,0x28,0xdb,0xd8,0xd1,0x74,0xde,0xbf,0x26,0x4a,0x37,0xbf,0x78,0x89,0x98,0x97,0x45,0xfd,0xec,
0xa7,0x6f,0xc0,0x4b,0x09,0x9b,0xc8,0xaa,0x4a,0xcf,0x60,0x44,0xc6,0x93,0x8a,0x7f,0x19,0xb5,0xb2,0x17,
0x5b,0x1b,0x6f,0xfd,0x00,0x12,0x9e,0x07,0xff,0x17,0xc7,0x7d,0xb5,0x88,0xcb,0x9a,0x02,0x00
0x8c,0x6f,0x54,0x3c,0x01,0x98,0x41,0x95,0xaa,0x0f,0xb0,0xfb,0x21,0x9f,0x2f,0x13,0xef,0xff,0xfc,0xef,
0x27,0xf4,0xe5,0xff,0xe7,0x7f,0x3f,0x52,0xfe,0x46,0x4b,0x84,0xee,0x41,0x28,0xab,0xc6,0x5d,0x59,0xcd,
0xfc,0xf8,0xf0,0x68,0x5d,0xe1,0xc9,0x39,0x56,0xd8,0x6b,0xd7,0xfe,0x7a,0x5d,0x67,0xd1,0x4a,0x7f,0x54,
0xc5,0x98,0x6e,0x64,0x06,0x6f,0x82,0xbd,0x37,0xea,0x9e,0x99,0x97,0xab,0x3c,0x96,0x08,0x78,0xc1,0xb2,
0x4a,0xa3,0x33,0x56,0x25,0x4a,0xa1,0x33,0xba,0x67,0x2f,0x69,0x49,0xa8,0x58,0x2d,0x6b,0x85,0x7a,0xbb,
0xda,0xb5,0x76,0xe9,0x0b,0x56,0xd5,0x17,0xd4,0x63,0x3e,0x4d,0xca,0xd6,0x33,0x79,0xa5,0x89,0x31,0x9d,
0xe9,0xe6,0xb7,0x77,0x74,0x70,0x9a,0x3f,0xf0,0x4c,0xd3,0x2b,0xc0,0x65,0x53,0xf7,0x57,0x2d,0xdd,0x5f,
0x35,0xbb,0xbf,0x92,0xb9,0x90,0xfe,0x57,0x75,0xa8,0xfe,0x5f,0x84,0x4d,0x61,0x44,0xf5,0x05,0x17,0xd5,
0x17,0x5c,0x88,0xd3,0x8a,0xa9,0x81,0xee,0x1e,0xbb,0xd9,0x3a,0x3a,0xf2,0xc5,0xb2,0x17,0x30,0x44,0x25,
0xff,0xbc,0xa9,0x4e,0x21,0x71,0xac,0xa7,0x23,0xf7,0xb2,0x38,0x0f,0xce,0xe8,0x2f,0x7d,0x01,0x2b,0xe8,
0xae,0x68,0xf5,0x5f,0x1d,0x9d,0x6b,0x89,0x41,0xaf,0x77,0xe5,0x9f,0x4d,0xaf,0x66,0xe2,0xa8,0x62,0xb4,
0xad,0xf5,0xf3,0x0a,0x59,0xc4,0x34,0x66,0x77,0x9e,0x73,0xce,0x33,0x76,0xf2,0x18,0xc4,0xcb,0xae,0x9d,
0x5c,0x3f,0xe4,0xfa,0x4e,0x19,0x75,0x2e,0x82,0x6b,0xe4,0xef,0xab,0x6b,0x2a,0xcb,0xc0,0x3e,0x81,0xac,
0x87,0xea,0xee,0x35,0xde,0x55,0xb0,0x3a,0xcd,0xaa,0xdf,0xb5,0x62,0x37,0x7c,0x0a,0x30,0x33,0x16,0x2f,
0xcc,0xa5,0xd0,0x11,0xce,0x28,0xcf,0x49,0x0c,0xf1,0x65,0xc5,0xd5,0x94,0x83,0x25,0xb5,0xd2,0xc2,0xff,
0x04,0x42,0x14,0x2f,0x77,0xb2,0x3c,0x3f,0x1e,0x3f,0xbe,0x8d,0xe3,0xb9,0xfc,0x34,0xc7,0x33,0xd6,0xa2,
0x22,0xa7,0xdf,0xad,0x97,0xd1,0xf6,0x5b,0x67,0x59,0xf1,0xc3,0x66,0xb1,0x72,0xaa,0x2d,0x01,0x8a,0x6d,
0x05,0x6d,0xeb,0x3b,0x67,0x95,0x2b,0x1f,0x71,0xdc,0x58,0x4a,0xb9,0xb1,0x44,0x03,0x24,0xb4,0xe6,0x04,
0x63,0xec,0xe4,0x44,0x42,0xe3,0x06,0xaa,0x5d,0xcc,0x9d,0x9c,0x9c,0xf2,0xf9,0x17,0xa0,0x86,0xba,0xcf,
0x8c,0x97,0x07,0x99,0x81,0x84,0x08,0xa9,0xc9,0x0b,0x4a,0x49,0xbf,0xf9,0x5c,0xcd,0x03,0x07,0x0c,0xb8,
0xf6,0xad,0x1d,0x57,0xb2,0xe8,0x80,0xf7,0x1b,0x1f,0xc8,0x1f,0x30,0xb3,0xf6,0x15,0xa2,0x6c,0xbf,0x42,
0x78,0x7c,0x8a,0xee,0x31,0xde,0x0f,0x6a,0xde,0x13,0xde,0x0a,0x53,0x5f,0xd2,0x3f,0xd8,0x73,0xc2,0xf0,
0x1a,0x4d,0xc1,0x07,0xa8,0xb8,0xb7,0x26,0x3e,0x07,0xc3,0xab,0x41,0x23,0x7f,0x32,0x30,0xfc,0xbf,0x7f,
0xe3,0x68,0x79,0xc7,0xef,0x6b,0x1a,0x5b,0x25,0x60,0xbc,0x9a,0xf1,0xba,0xfa,0x18,0x5a,0x45,0x29,0x31,
0x38,0x95,0x04,0xbe,0x38,0x20,0x53,0xfd,0x8a,0xaf,0xf2,0x62,0x92,0xde,0xcb,0x24,0x7d,0x6c,0xca,0x12,
0xa4,0xbc,0x95,0xe7,0xf4,0x33,0x54,0xb5,0xef,0x35,0xd1,0x6b,0x7e,0x19,0x5f,0x75,0xf4,0x87,0x89,0x4a,
0x9f,0x7b,0xff,0xde,0xd5,0xd1,0x9b,0x4f,0x0b,0x5e,0x85,0x55,0x39,0xfb,0x2b,0x4c,0x87,0x5e,0xfd,0x81,
0x0e,0x79,0x62,0x51,0x89,0xf3,0x90,0xf8,0x5b,0x9e,0x3d,0xcb,0x3a,0x92,0xad,0x16,0x2d,0x71,0x54,0xe5,
0x8f,0x54,0xc5,0xc6,0x1d,0x97,0x9f,0xe1,0x09,0xd5,0xb5,0xd4,0x49,0xae,0x8c,0x34,0xd8,0xe1,0x3a,0x6e,
0xfc,0xa0,0x7c,0x04,0x0d,0x0a,0x3f,0xcb,0x65,0xca,0x37,0x8e,0xd8,0x9f,0xe1,0x53,0x1f,0xd5,0x5e,0xaa,
0x5b,0xa8,0xeb,0xca,0x4f,0xec,0x61,0xca,0x50,0x02,0x88,0xa6,0x15,0xb3,0x42,0xb2,0x8b,0xe0,0x8a,0xbd,
0x5e,0x65,0xb0,0x68,0xdb,0xfd,0x86,0x9c,0x93,0xdf,0x1a,0x0b,0x57,0x63,0xa6,0xec,0xbc,0x44,0x4a,0x60,
0x8d,0x7e,0xf5,0x06,0x29,0xf5,0x4f,0xb4,0x71,0x40,0x00,0x53,0x3d,0xea,0xa6,0x08,0x1e,0xd8,0xbf,0xad,
0x17,0xfc,0x76,0x57,0x2f,0xd4,0xcb,0x96,0x5e,0xa8,0x37,0x9f,0xec,0xc5,0xd0,0xf7,0x6b,0x82,0xca,0x9f,
0xf3,0x25,0xc8,0xc1,0x0d,0xdb,0x59,0xe6,0x70,0x1d,0x2b,0x83,0xa9,0x77,0x85,0x33,0xcb,0x38,0x93,0x78,
0xda,0x86,0x0b,0x38,0xf9,0x17,0xde,0xac,0x95,0x76,0xa0,0x02,0x3e,0x23,0xe0,0x78,0xfe,0xea,0x47,0x3e,
0x35,0x02,0x95,0x88,0xf5,0x53,0x66,0xf3,0x6c,0xc9,0xbc,0x81,0xfc,0xdc,0xe2,0x76,0x5b,0x96,0xab,0x62,
0x04,0x4c,0x31,0x93,0x3c,0xe1,0x8e,0x5c,0x15,0x55,0x11,0xea,0x0c,0xe5,0x19,0xb5,0xa5,0x8f,0x2c,0x74,
0x3d,0x35,0x64,0x27,0x19,0x37,0x4e,0x87,0xa0,0xfe,0x38,0x84,0x1c,0x88,0xe1,0x4b,0xcc,0x35,0xe0,0x0b,
0xc4,0x90,0xf4,0xe6,0x32,0x2e,0x2f,0xb2,0xc5,0xc8,0xfb,0xee,0xf8,0xc4,0x0b,0xe6,0x99,0x20,0x04,0xd0,
0x39,0xbe,0xa0,0x2b,0x4a,0x42,0x7b,0x70,0xe4,0x15,0x44,0x34,0xfa,0xb2,0x52,0xbd,0xad,0x3f,0x28,0x2f,
0xe8,0x4c,0xb3,0xbf,0xf9,0xde,0xe1,0x61,0x18,0x6a,0x61,0xfa,0xa4,0xab,0x9c,0x28,0xf7,0xf7,0xb5,0x37,
0x25,0x1d,0x7e,0x08,0x91,0x1b,0xe8,0x37,0x3c,0x38,0x6f,0xe2,0xd3,0xd7,0xf4,0x3b,0x2e,0xbb,0xfa,0x93,
0xd8,0x19,0x02,0xba,0x0d,0x75,0x99,0xbc,0xd5,0xba,0x13,0xf4,0xdf,0xe0,0xfa,0x1d,0x70,0x3c,0x2d,0xef,
0x2d,0xc2,0xdd,0x58,0x89,0xb9,0x24,0xe6,0x4e,0x62,0x29,0x89,0x70,0x90,0x1c,0xd3,0x2e,0xb1,0x65,0x63,
0xf0,0x24,0xd1,0x7d,0xce,0x52,0xee,0xb5,0xd3,0x87,0x39,0x6c,0xe5,0x9e,0xc2,0x4d,0x17,0x5b,0xd9,0xc2,
0xca,0xf4,0x83,0xba,0x7d,0xe5,0x23,0x99,0x86,0x84,0x51,0xfe,0x0a,0xe5,0xf7,0x56,0x39,0x00,0x04,0x0a,
0xa0,0xf8,0xc5,0x0f,0x62,0x73,0xc9,0x9e,0x32,0xc6,0x34,0xdc,0x13,0x40,0x9e,0x16,0xd1,0xd4,0xd0,0xb4,
0xd4,0x70,0x3c,0x74,0xfa,0x0e,0x87,0x13,0xa7,0xeb,0x56,0x67,0x43,0x22,0xb0,0xe6,0x23,0x2c,0xbd,0x84,
0x6d,0x3d,0x88,0xbc,0x6c,0xab,0xb8,0x0d,0xbe,0x62,0x3a,0x3a,0x72,0xec,0x3a,0x69,0x11,0x88,0x5f,0xba,
0x33,0x3a,0xae,0x2b,0x6d,0xad,0x40,0x63,0x85,0xea,0xe8,0xa9,0x51,0x5e,0x16,0x1c,0x19,0x86,0xb7,0xc1,
0x48,0x40,0x8a,0xea,0xc9,0x05,0xa7,0x13,0x2d,0x95,0x4c,0xb8,0x03,0x53,0x13,0xce,0x5a,0x37,0x1b,0xce,
0x6f,0xb6,0x75,0x92,0x3d,0x12,0xb7,0x07,0x64,0xe0,0x9e,0x36,0xcb,0xd5,0xc6,0x95,0x6a,0xb9,0x53,0x03,
0x0a,0x71,0x61,0x46,0x83,0x1a,0x7a,0x63,0xd0,0x54,0x0b,0x05,0x3b,0x46,0xba,0x42,0xc0,0xdc,0xb2,0x71,
0x7d,0xab,0x65,0xcc,0xe7,0xd9,0xc5,0x38,0x76,0x8f,0x8d,0x02,0xb0,0x51,0x54,0xb5,0xf7,0x91,0xaf,0xbf,
0x6c,0x64,0x34,0xc6,0x90,0x9c,0x6f,0x87,0x77,0xd3,0x21,0xbb,0x35,0x0d,0x0f,0x0f,0x95,0x15,0x8f,0x52,
0xb1,0x8b,0x74,0xab,0xa6,0x96,0x57,0xee,0x01,0x2d,0x19,0xfb,0x96,0x28,0xb5,0xa5,0x84,0xf3,0x59,0xba,
0xa4,0x16,0x57,0xed,0x2c,0x5b,0x07,0x77,0x70,0x4a,0x6b,0x1f,0x1b,0xb7,0x88,0x1c,0x1d,0xb5,0x8c,0xc6,
0xfb,0xa5,0x9e,0x57,0xbd,0x70,0xb2,0x43,0x5e,0xdb,0x67,0x93,0xe1,0x5a,0x76,0xd8,0x25,0x3b,0x39,0x2f,
0xa2,0xfe,0x5c,0x0b,0x24,0x9d,0x9c,0xda,0xb0,0xd8,0xc9,0xad,0x8c,0x76,0x6b,0x59,0x6d,0x03,0xe1,0x06,
0x4f,0x57,0x59,0x06,0x1b,0xc4,0xb9,0x6e,0x4b,0x11,0xab,0x85,0x3e,0x93,0xb1,0xf6,0x46,0xf0,0xc6,0xc9,
0x6f,0x38,0x6c,0x81,0xab,0xad,0x97,0x12,0x87,0x86,0xf6,0x12,0xda,0xc4,0xbe,0x31,0xa2,0x9c,0xdc,0x5e,
0x46,0x19,0x94,0xd7,0xcb,0xd8,0x36,0xed,0xb2,0xea,0x2d,0x33,0x78,0x5b,0x17,0x6e,0x7b,0x42,0xd4,0x56,
0xa0,0x1c,0xe2,0x8d,0x95,0xc7,0xc9,0x4e,0xd6,0x0a,0x9a,0xa1,0xcf,0x76,0xed,0xfd,0x0a,0x9a,0xa1,0xf1,
0x25,0x6d,0xd0,0x11,0x4e,0x65,0xd1,0x2a,0x01,0x08,0x97,0x53,0xd2,0x15,0xc1,0xb6,0x75,0xb3,0x2f,0x86,
0xff,0x6e,0xb1,0x26,0xc6,0x74,0x6d,0x29,0xd4,0x3c,0x82,0x2d,0xf7,0x80,0xc6,0xaa,0xb1,0xb2,0x0e,0x64,
0xec,0x76,0xc4,0xd9,0x33,0x6e,0xc5,0xb5,0x0a,0x90,0xae,0x20,0x25,0xb5,0x62,0x8c,0xfd,0x6c,0x5d,0x02,
0x41,0x17,0x83,0x76,0x5b,0x3b,0x47,0x1d,0xae,0x6d,0x27,0x2d,0x0d,0x82,0x6f,0x99,0x66,0x35,0x6a,0x14,
0x73,0xa6,0x1d,0xb6,0x3d,0x56,0xcd,0xf6,0x05,0xed,0x46,0x19,0x43,0x8d,0x86,0x81,0xc1,0x89,0x1b,0xf5,
0x87,0xdb,0x96,0xea,0xe9,0x7e,0x92,0x37,0xaa,0xad,0xae,0x38,0x6d,0xf9,0x05,0x61,0xad,0xa5,0x00,0xdf,
0xc9,0xdc,0x0d,0x18,0x2f,0xfb,0x22,0xfe,0xdd,0x69,0x3c,0x8b,0xdc,0x50,0x37,0xf7,0x35,0x92,0x43,0x3d,
0xc4,0x10,0xbb,0x42,0xba,0xd8,0x20,0xa5,0x7c,0xc8,0xc2,0x44,0xde,0xb3,0x77,0x85,0xbe,0x38,0x55,0x3c,
0xf0,0x2d,0x79,0xf5,0x95,0xdd,0xcd,0xec,0xe9,0x60,0xa3,0xaa,0xd7,0x3a,0xb2,0x82,0x33,0xe0,0x2a,0x76,
0xe4,0x92,0x23,0xf5,0x6c,0x9b,0xde,0x69,0xed,0x40,0x0e,0x75,0xfb,0x88,0x56,0x68,0xca,0x41,0x26,0x3f,
0x04,0xc9,0xf2,0x86,0x45,0x2b,0x10,0x30,0x31,0x56,0x12,0x78,0x68,0x2c,0xc8,0x11,0x6e,0x3b,0x75,0xfe,
0x48,0x33,0xca,0x36,0x76,0x34,0x9d,0xf7,0xaf,0x89,0xd2,0xcd,0x2f,0x5e,0x22,0xe6,0x65,0x51,0x3f,0xfb,
0xe9,0x1b,0xf0,0x52,0xc2,0x26,0xb2,0xaa,0xd2,0x33,0x18,0x91,0xf1,0xa4,0xe2,0x5f,0x46,0xad,0xec,0xc5,
0xd6,0xc6,0x5b,0x3f,0x80,0x84,0xe7,0xc1,0xff,0x05,0x84,0x0f,0x1a,0x62,0xcb,0x9a,0x02,0x00
}; };

+ 5
- 0
code/espurna/terminal.h View File

@ -14,8 +14,13 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
using embedis_command_f = void (*)(Embedis*); using embedis_command_f = void (*)(Embedis*);
void terminalOK();
void terminalError(const String& error);
void terminalRegisterCommand(const String& name, embedis_command_f func); void terminalRegisterCommand(const String& name, embedis_command_f func);
void terminalInject(void *data, size_t len); void terminalInject(void *data, size_t len);
Stream& terminalSerial(); Stream& terminalSerial();
void terminalSetup();
#endif // TERMINAL_SUPPORT == 1 #endif // TERMINAL_SUPPORT == 1

+ 19
- 7
code/html/custom.js View File

@ -1263,8 +1263,9 @@ function initMagnitudes(data) {
var line = $(template).clone(); var line = $(template).clone();
$("label", line).html(magnitude.name); $("label", line).html(magnitude.name);
$("div.hint", line).html(magnitude.description);
$("input", line).attr("data", i); $("input", line).attr("data", i);
$("div.sns-desc", line).html(magnitude.description);
$("div.sns-info", line).hide();
line.appendTo("#magnitudes"); line.appendTo("#magnitudes");
} }
@ -1756,12 +1757,20 @@ function processData(data) {
if ("magnitudes" === key) { if ("magnitudes" === key) {
for (var i=0; i<value.size; ++i) { for (var i=0; i<value.size; ++i) {
var inputElem = $("input[name='magnitude'][data='" + i + "']");
var infoElem = inputElem.parent().parent().find("div.sns-info");
var error = value.error[i] || 0; var error = value.error[i] || 0;
var text = (0 === error) ?
value.value[i] + magnitudes[i].units :
magnitudeError(error);
var element = $("input[name='magnitude'][data='" + i + "']");
element.val(text);
var text = (0 === error)
? value.value[i] + magnitudes[i].units
: magnitudeError(error);
inputElem.val(text);
if (value.info !== undefined) {
var info = value.info[i] || 0;
infoElem.toggle(info != 0);
infoElem.text(info);
}
} }
return; return;
} }
@ -2004,7 +2013,7 @@ function processData(data) {
} }
<!-- removeIf(!thermostat)--> <!-- removeIf(!thermostat)-->
if ("tmpUnits" == key) { if ("tmpUnits" == key) {
$("span.tmpUnit").html(data[key] == 1 ? "ºF" : "ºC");
$("span.tmpUnit").html(data[key] == 3 ? "ºF" : "ºC");
} }
<!-- endRemoveIf(!thermostat)--> <!-- endRemoveIf(!thermostat)-->
@ -2261,6 +2270,9 @@ $(function() {
// don't autoconnect when opening from filesystem // don't autoconnect when opening from filesystem
if (window.location.protocol === "file:") { if (window.location.protocol === "file:") {
processData({"webMode": 0});
processData({"hlwVisible":1,"pwrVisible":1,"tmpCorrection":0,"humCorrection":0,"luxCorrection":0,"snsRead":5,"snsReport":10,"snsSave":2,"magnitudesConfig":{"index":[0,0,0,0,0,0,0,0],"type":[4,5,6,8,7,9,11,10],"units":["A","V","W","VAR","VA","%","J","kWh"],"description":["HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)","HLW8012 @ GPIO(5,14,13)"],"size":8}});
processData({"magnitudes":{"value":["0.079","218","37","0","17","100","96","0.001"],"error":[0,0,0,0,0,0,0,0],"info":[0,0,0,0,0,0,0,"Last saved: 2020-04-07 21:10:15"],"size":8}});
return; return;
} }


+ 10
- 7
code/html/index.html View File

@ -1583,24 +1583,25 @@
<div class="pure-g module module-pwr"> <div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Power units</label> <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"> <select name="pwrUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Watts (W)</option>
<option value="1">Kilowatts (kW)</option>
<option value="13">Watts (W)</option>
<option value="14">Kilowatts (kW)</option>
</select> </select>
</div> </div>
<div class="pure-g module module-pwr"> <div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-lg-1-4">Energy units</label> <label class="pure-u-1 pure-u-lg-1-4">Energy units</label>
<select name="eneUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4"> <select name="eneUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Joules (J)</option>
<option value="1">Kilowatts·hour (kWh)</option>
<option value="15">Joules (J)</option>
<option value="16">Kilowatts·hour (kWh)</option>
</select> </select>
</div> </div>
<div class="pure-g module module-temperature"> <div class="pure-g module module-temperature">
<label class="pure-u-1 pure-u-lg-1-4">Temperature units</label> <label class="pure-u-1 pure-u-lg-1-4">Temperature units</label>
<select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4"> <select name="tmpUnits" tabindex="16" class="pure-u-1 pure-u-lg-1-4">
<option value="0">Celsius (&deg;C)</option>
<option value="1">Fahrenheit (&deg;F)</option>
<option value="2">Celsius (&deg;C)</option>
<option value="3">Fahrenheit (&deg;F)</option>
<option value="4">Kelvin (K)</option>
</select> </select>
</div> </div>
@ -2076,7 +2077,9 @@
<div class="pure-u-1 pure-u-lg-1-4"> <div class="pure-u-1 pure-u-lg-1-4">
<input class="pure-u-1 pure-u-lg-23-24 center" type="text" name="magnitude" data="256" readonly /> <input class="pure-u-1 pure-u-lg-23-24 center" type="text" name="magnitude" data="256" readonly />
</div> </div>
<div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
<div class="pure-u-1 pure-u-lg-1-2 hint center sns-desc"></div>
<div class="pure-u-1 pure-u-lg-1 hint sns-info">
</div>
</div> </div>
</div> </div>
<!-- endRemoveIf(!sensor) --> <!-- endRemoveIf(!sensor) -->


Loading…
Cancel
Save