Skip to content

Commit

Permalink
Add proof of concept WattGauge that approximates current watt usage
Browse files Browse the repository at this point in the history
Feed it current power totals at regular intervals and get current power
usage in return.
  • Loading branch information
wdoekes committed Jan 16, 2021
1 parent 5a16ead commit d844533
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ example.diff: example.log
example.log: raw.log
./raw2example < raw.log > example.log

pe32me162ir_pub.test: $(OBJECTS)
pe32me162ir_pub.test: $(OBJECTS) WattGauge.h config.h
$(LINK.cc) -o $@ $^
103 changes: 103 additions & 0 deletions WattGauge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#ifndef INCLUDED_WATTGAUGE_H
#define INCLUDED_WATTGAUGE_H

/**
* WattGauge attempts to approximate current Watt (Joule/s)
* production/consumption based on a regular input of current absolute
* (increasing) watt hour values.
*
* For power/electricity meters that do not provide "current" Watt usage
* but do provide current totals, feeding this regular updates will allow
* a fair average to be calculated.
*
* Usage:
*
* prod = WattGauge()
*
* // Running this more often will get better averages.
* schedule_every_second(
* function() { prod.set_watthour(millis(), get_watthour()) })
*
* // Running this less often will get better averages;
* // .. but you're free to get_watt()/reset() whenever you like;
* // .. and an interval of 15s is fine for higher wattage (>1500).
* schedule_every_x_seconds(
* function() { push(prod.get_power()); prod.reset(); }
*/
class WattGauge
{
private:
long _t0; /* first time in a series */
long _tlast; /* latest time */
long _p0; /* first watt hour in a series */
long _plast; /* latest watt hour (1 wh = 3600 joule) */
float _watt; /* average value, but only if it makes some sense */

/* _tdelta can be negative when the time wraps! */
inline long _tdelta() { return _tlast - _t0; }
/* _pdelta should never be negative */
inline long _pdelta() { return _plast - _p0; }

/* Are there enough values to make any reasonable estimate?
* - Minimum sampling interval: 12s
* - Minimum sampling size: 5 */
inline bool _there_are_enough_values() {
return (
(_tdelta() >= 12000 && _pdelta() >= 5) ||
(_tdelta() >= 50000 && _pdelta() >= 2));
}

/* Recalculate watt usage, but only if there are enough values */
inline void _recalculate_if_sensible() {
if (_there_are_enough_values()) {
_watt = (_pdelta() * 1000 * 3600 / _tdelta());
}
}

public:
WattGauge() : _t0(-1), _watt(0.0) {}

/* Get the latest stored value in watt hours */
inline long get_energy_total() {
return _plast;
}

/* Get a best guess of the current power usage in watt */
inline float get_power() {
return _watt;
}

/* After reading get_power() you'll generally want to reset the
* state to start a new measurement interval */
inline void reset() {
if (_there_are_enough_values()) {
/* We don't touch the _watt average. Also note that we update to
* the latest time-in-which-there-was-a-change. */
_t0 = _tlast;
_p0 = _plast;
}
}

/* Feed data to the WattGauge: do this often */
inline void set_watthour(long time_ms, long current_wh) {
if (current_wh == _plast) {
/* Do nothing and especially not update averages */
} else {
/* There was a change: update values. Only store the time
* when there were changes. */
_tlast = time_ms;
_plast = current_wh;
/* Update average values if it is sensible to do so */
if (_t0 > 0) {
/* Only recalculate now that we have a new value */
_recalculate_if_sensible();
} else /* happens only once after construction */ {
_t0 = time_ms;
_p0 = current_wh;
}
}
}
};

// vim: set ts=8 sw=4 sts=4 et ai:
#endif //INCLUDED_WATTGAUGE_H
35 changes: 35 additions & 0 deletions pe32me162ir_pub.ino
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ const char mqtt_topic[] = "some/topic";
*/
#include "config.h"

#include "WattGauge.h"

/* Include files specific to the platform (ESP8266, Arduino or TEST) */
#if defined(ARDUINO_ARCH_ESP8266)
# include <Arduino.h> /* Serial, pinMode, INPUT, OUTPUT, ... */
Expand Down Expand Up @@ -1058,12 +1060,45 @@ static void test_data_readout_to_obis()
printf("\n");
}

void test_wattgauge()
{
WattGauge positive;
positive.set_watthour(1000, 32826545);
positive.set_watthour(2000, 32826545);
INT_EQ("WattGauge.get_power", positive.get_power(), 0);
positive.set_watthour(3000, 32826546);
positive.set_watthour(4000, 32826546);
positive.set_watthour(5000, 32826546);
INT_EQ("WattGauge.get_power", positive.get_power(), 0);
positive.set_watthour(15000, 32826548);
positive.set_watthour(25000, 32826550);
INT_EQ("WattGauge.get_power", positive.get_power(), 750);
positive.reset();
positive.set_watthour(30000, 32826552);
INT_EQ("WattGauge.get_power", positive.get_power(), 750);
positive.set_watthour(35000, 32826554);
INT_EQ("WattGauge.get_power", positive.get_power(), 750);
positive.set_watthour(40000, 32826556);
INT_EQ("WattGauge.get_power", positive.get_power(), 1440);
positive.set_watthour(45000, 32826558);
INT_EQ("WattGauge.get_power", positive.get_power(), 1440);
positive.reset();
positive.set_watthour(60000, 32826558);
INT_EQ("WattGauge.get_power", positive.get_power(), 1440);
positive.set_watthour(90000, 32826559);
INT_EQ("WattGauge.get_power", positive.get_power(), 1440);
positive.set_watthour(120000, 32826560);
INT_EQ("WattGauge.get_power", positive.get_power(), 96);
printf("\n");
}

int main()
{
test_cescape();
test_din_66219_bcc();
test_obis();
test_data_readout_to_obis();
test_wattgauge();

on_hello("ISK5ME162-0033", 14, STATE_RD_IDENTIFICATION);
on_response("(0032826.545*kWh)", 17, OBIS_1_8_0);
Expand Down

0 comments on commit d844533

Please sign in to comment.