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"(

)"); server.sendContent(R"(

)");