diff --git a/src/BASICS.h b/src/BASICS.h
index 0596c64..5dc2432 100644
--- a/src/BASICS.h
+++ b/src/BASICS.h
@@ -7,7 +7,8 @@
#define _ false
#define ____ 0
-#define HALF 127
+#define QUAR 64
+#define HALF 128
#define FULL 255
#define countof(x) (sizeof(x) / sizeof(x[0]))
diff --git a/src/display/Color.cpp b/src/display/Color.cpp
index 3be724d..b7b473a 100644
--- a/src/display/Color.cpp
+++ b/src/display/Color.cpp
@@ -8,7 +8,7 @@ const Color RED = {FULL, ____, ____};
const Color GREEN = {____, FULL, ____};
-const Color ORANGE = {FULL, HALF, ____};
+const Color ORANGE = {FULL, QUAR, ____};
const Color BLUE = {____, ____, FULL};
diff --git a/src/display/Display.cpp b/src/display/Display.cpp
index d51790c..9d059d6 100644
--- a/src/display/Display.cpp
+++ b/src/display/Display.cpp
@@ -99,6 +99,13 @@ bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT] = {
_, _, _,
_, _, _,
},
+ {
+ X, _, _,
+ _, _, X,
+ _, X, _,
+ X, _, _,
+ _, _, X,
+ },
// this must always be the last symbol (fallback)
{
X, X, X,
diff --git a/src/display/Display.h b/src/display/Display.h
index 24474c9..0fd9a34 100644
--- a/src/display/Display.h
+++ b/src/display/Display.h
@@ -5,8 +5,9 @@
#include "Adafruit_NeoPixel.h"
#include "Vector.h"
-#define SYMBOL_COUNT 15
+#define SYMBOL_COUNT 16
#define SYMBOL_DASH 13
+#define SYMBOL_PERCENT 14
#define DISPLAY_CHAR_WIDTH 3
#define DISPLAY_CHAR_HEIGHT 5
@@ -94,12 +95,12 @@ public:
LEFT, RIGHT
};
- uint8_t print(int x, int y, double valueDbl, Color colorPositive, Color colorZero, Color colorNegative, ALIGN align = LEFT) {
+ uint8_t print2(int x, int y, double valueDbl, Color colorPositive, Color colorZero, Color colorNegative, ALIGN align = RIGHT) {
const Color color = valueDbl == 0 ? colorZero : (valueDbl < 0 ? colorNegative : colorPositive);
- return print(x, y, valueDbl, color, align);
+ return print2(x, y, valueDbl, color, align);
}
- uint8_t print(int x, int y, double valueDbl, Color color, ALIGN align = LEFT) {
+ uint8_t print2(int x, int y, double valueDbl, Color color, ALIGN align = RIGHT) {
if (isnan(valueDbl)) {
x -= 3 * (DISPLAY_CHAR_WIDTH + 1) - 1;
x += print(x, y, SYMBOL_DASH, color, true) + 1;
@@ -140,7 +141,7 @@ public:
return DISPLAY_CHAR_WIDTH;
}
if (index >= SYMBOL_COUNT) {
- Serial.printf("Cannot print symbol #%u.\n", index);
+ Serial.printf("Cannot print2 symbol #%u.\n", index);
index = SYMBOL_COUNT - 1;
}
bool *symbolBit = SYMBOLS[index];
diff --git a/src/mode.cpp b/src/mode.cpp
index 88e4a3c..8bcbba2 100644
--- a/src/mode.cpp
+++ b/src/mode.cpp
@@ -9,7 +9,8 @@
#include "mode/Starfield/Starfield.h"
#include "mode/Matrix/Matrix.h"
#include "display.h"
-#include "mode/Electricity/Electricity.h"
+#include "mode/Power/Power.h"
+#include "mode/Energy/Energy.h"
ModeId currentModeId = NONE;
@@ -109,8 +110,11 @@ void loadNewMode() {
case MATRIX:
mode = new Matrix(display);
break;
- case ELECTRICITY:
- mode = new Electricity(display);
+ case POWER:
+ mode = new Power(display);
+ break;
+ case ENERGY:
+ mode = new Energy(display);
break;
default:
Serial.print("No mode loaded.\n");
diff --git a/src/mode/Energy/Energy.h b/src/mode/Energy/Energy.h
new file mode 100644
index 0000000..7d35cb4
--- /dev/null
+++ b/src/mode/Energy/Energy.h
@@ -0,0 +1,71 @@
+#ifndef MODE_ENERGY_H
+#define MODE_ENERGY_H
+
+#include "mode/Mode.h"
+#include "mqtt.h"
+
+#define POWER_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE 287.995
+#define PV_COST_TOTAL_EURO 576.52
+#define GRID_KWH_EURO 0.33
+#define PV_COST_AMORTISATION_KWH ( PV_COST_TOTAL_EURO / GRID_KWH_EURO )
+
+class Energy : public Mode {
+
+private:
+
+ int page = 0;
+
+public:
+
+ explicit Energy(Display &display) :
+ Mode(display) {
+ timer(0, 7000);
+ }
+
+ const char *getName() override {
+ return "Energy";
+ }
+
+protected:
+
+ void tick(uint8_t index, microseconds_t microseconds) override {
+ page = (page + 1) % 3;
+ }
+
+ void step(microseconds_t microseconds) override {
+ if (realtimeChanged) {
+ markDirty();
+ }
+ }
+
+ void draw(Display &display) override {
+ const double produced = getPhotovoltaicEnergyKWh();
+ const double imported = getGridImportKWh();
+ const double exported = getGridExportKWh();
+ const double producedAfterMeterChange = produced - POWER_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE;
+ const double selfAfterMeterChange = producedAfterMeterChange - exported;
+ const double selfRatio = selfAfterMeterChange / producedAfterMeterChange;
+ const double selfConsumedKWh = selfRatio * produced;
+ const double costSaved = selfConsumedKWh * GRID_KWH_EURO;
+ const double amortisationPercent = selfConsumedKWh / PV_COST_AMORTISATION_KWH * 100;
+
+ const uint8_t l = (DISPLAY_CHAR_WIDTH + 1) * 4 - 1;
+ const uint8_t r = width;
+
+ display.clear();
+ if (page == 0) {
+ display.print2(l, 0, costSaved, GREEN);
+ uint8_t x = display.print2(r - DISPLAY_CHAR_WIDTH - 1, 0, amortisationPercent, WHITE);
+ display.print(x, 0, SYMBOL_PERCENT, WHITE, true);
+ } else if (page == 1) {
+ display.print2(l, 0, getPhotovoltaicEnergyKWh(), BLUE);
+ display.print2(r, 0, selfConsumedKWh, GREEN);
+ } else {
+ display.print2(l, 0, imported, ORANGE);
+ display.print2(r, 0, exported, MAGENTA);
+ }
+ }
+
+};
+
+#endif
diff --git a/src/mode/Mode.h b/src/mode/Mode.h
index c74ffe4..e59b1a9 100644
--- a/src/mode/Mode.h
+++ b/src/mode/Mode.h
@@ -23,7 +23,8 @@ enum ModeId {
COUNT_DOWN_BARS,
STARFIELD,
MATRIX,
- ELECTRICITY,
+ POWER,
+ ENERGY,
};
class Mode {
diff --git a/src/mode/Electricity/Electricity.h b/src/mode/Power/Power.h
similarity index 55%
rename from src/mode/Electricity/Electricity.h
rename to src/mode/Power/Power.h
index 793642c..cd0dc1c 100644
--- a/src/mode/Electricity/Electricity.h
+++ b/src/mode/Power/Power.h
@@ -1,5 +1,5 @@
-#ifndef MODE_ELECTRICITY_H
-#define MODE_ELECTRICITY_H
+#ifndef MODE_POWER_H
+#define MODE_POWER_H
#include "mode/Mode.h"
#include "mqtt.h"
@@ -7,17 +7,17 @@
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnusedValue"
-class Electricity : public Mode {
+class Power : public Mode {
public:
- explicit Electricity(Display &display) :
+ explicit Power(Display &display) :
Mode(display) {
// nothing
}
const char *getName() override {
- return "Electricity";
+ return "Power";
}
protected:
@@ -30,8 +30,8 @@ protected:
void draw(Display &display) override {
display.clear();
- display.print((DISPLAY_CHAR_WIDTH + 1) * 3 - 1, 0, getPhotovoltaicPowerW(), GREEN, Color(), Color(), Display::RIGHT);
- display.print(width, 0, getGridPowerW(), ORANGE, WHITE, MAGENTA, Display::RIGHT);
+ display.print2((DISPLAY_CHAR_WIDTH + 1) * 3 - 1, 0, getPhotovoltaicPowerW(), GREEN);
+ display.print2(width, 0, getGridPowerW(), ORANGE, WHITE, MAGENTA);
}
};
diff --git a/src/mqtt.cpp b/src/mqtt.cpp
index 13e2d25..3a0d43f 100644
--- a/src/mqtt.cpp
+++ b/src/mqtt.cpp
@@ -8,9 +8,11 @@
#define MQTT_MAX_MESSAGE_AGE_MILLIS 11000
-#define PHOTOVOLTAIC_POWER "openDTU/pv/ac/power"
-
-#define GRID_POWER "electricity/grid/power/signed/w"
+#define PHOTOVOLTAIC_POWER_W "openDTU/pv/ac/power"
+#define PHOTOVOLTAIC_ENERGY_KWH "openDTU/pv/ac/yieldtotal"
+#define GRID_POWER_W "electricity/grid/power/signed/w"
+#define GRID_IMPORT_WH "electricity/grid/energy/import/wh"
+#define GRID_EXPORT_WH "electricity/grid/energy/export/wh"
WiFiClient espClient;
@@ -24,10 +26,22 @@ double photovoltaicPowerW = NAN;
unsigned long photovoltaicPowerWLast = 0;
+double photovoltaicEnergyKWh = NAN;
+
+unsigned long photovoltaicEnergyKWhLast = 0;
+
double gridPowerW = NAN;
unsigned long gridPowerWLast = 0;
+double gridImportKWh = NAN;
+
+unsigned long gridImportKWhLast = 0;
+
+double gridExportKWh = NAN;
+
+unsigned long gridExportKWhLast = 0;
+
void mqttDisconnect();
void mqttCallback(char *topic, uint8_t *payload, unsigned int length) {
@@ -38,12 +52,21 @@ void mqttCallback(char *topic, uint8_t *payload, unsigned int length) {
}
memcpy(message, payload, length);
message[length] = 0;
- if (strcmp(PHOTOVOLTAIC_POWER, topic) == 0) {
+ if (strcmp(PHOTOVOLTAIC_POWER_W, topic) == 0) {
photovoltaicPowerW = strtod(message, nullptr);
photovoltaicPowerWLast = millis();
- } else if (strcmp(GRID_POWER, topic) == 0) {
+ } else if (strcmp(PHOTOVOLTAIC_ENERGY_KWH, topic) == 0) {
+ photovoltaicEnergyKWh = strtod(message, nullptr);
+ photovoltaicEnergyKWhLast = millis();
+ } else if (strcmp(GRID_POWER_W, topic) == 0) {
gridPowerW = strtod(message, nullptr);
gridPowerWLast = millis();
+ } else if (strcmp(GRID_IMPORT_WH, topic) == 0) {
+ gridImportKWh = strtod(message, nullptr) / 1000;
+ gridImportKWhLast = millis();
+ } else if (strcmp(GRID_EXPORT_WH, topic) == 0) {
+ gridExportKWh = strtod(message, nullptr) / 1000;
+ gridExportKWhLast = millis();
}
}
@@ -59,8 +82,11 @@ void mqtt_loop() {
if (mqttConnected) {
Serial.printf("Successfully connected mqtt broker at %s:%d\n", "10.0.0.50", 1883);
mqtt.setCallback(mqttCallback);
- mqtt.subscribe(PHOTOVOLTAIC_POWER);
- mqtt.subscribe(GRID_POWER);
+ mqtt.subscribe(PHOTOVOLTAIC_POWER_W);
+ mqtt.subscribe(PHOTOVOLTAIC_ENERGY_KWH);
+ mqtt.subscribe(GRID_POWER_W);
+ mqtt.subscribe(GRID_IMPORT_WH);
+ mqtt.subscribe(GRID_EXPORT_WH);
} else {
Serial.printf("ERROR: Failed to connect MQTT broker at %s:%d\n", "10.0.0.50", 1883);
mqttDisconnect();
@@ -86,9 +112,30 @@ double getPhotovoltaicPowerW() {
return photovoltaicPowerW;
}
+double getPhotovoltaicEnergyKWh() {
+ if (millis() - photovoltaicEnergyKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
+ return NAN;
+ }
+ return photovoltaicEnergyKWh;
+}
+
double getGridPowerW() {
if (millis() - gridPowerWLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
return NAN;
}
return gridPowerW;
}
+
+double getGridImportKWh() {
+ if (millis() - gridImportKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
+ return NAN;
+ }
+ return gridImportKWh;
+}
+
+double getGridExportKWh() {
+ if (millis() - gridExportKWhLast > MQTT_MAX_MESSAGE_AGE_MILLIS) {
+ return NAN;
+ }
+ return gridExportKWh;
+}
diff --git a/src/mqtt.h b/src/mqtt.h
index 485d326..189c25a 100644
--- a/src/mqtt.h
+++ b/src/mqtt.h
@@ -5,6 +5,12 @@ void mqtt_loop();
double getPhotovoltaicPowerW();
+double getPhotovoltaicEnergyKWh();
+
double getGridPowerW();
+double getGridImportKWh();
+
+double getGridExportKWh();
+
#endif
diff --git a/src/server.cpp b/src/server.cpp
index 74875fc..7c26226 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -105,7 +105,8 @@ void web_index() {
server.sendContent(R"(COUNT_DOWN_BARS
)");
server.sendContent(R"(STARFIELD
)");
server.sendContent(R"(MATRIX
)");
- server.sendContent(R"(ELECTRICITY
)");
+ server.sendContent(R"(POWER
)");
+ server.sendContent(R"(ENERGY
)");
server.sendContent(R"(
)");