diff --git a/src/Output.h b/src/Output.h index 68dd9ea..edaf97e 100644 --- a/src/Output.h +++ b/src/Output.h @@ -85,7 +85,7 @@ public: this->onCount = onCount_; } - void loop() { + virtual void loop() { const auto status = get(); const auto ageMillis = millis() - stateMillis; if (status) { diff --git a/src/Relay.h b/src/Relay.h index b42fa26..77824b9 100644 --- a/src/Relay.h +++ b/src/Relay.h @@ -11,36 +11,55 @@ class Relay final : public Output { String topicFallback; + const uint8_t index; + String topic; - const uint8_t index; + long gridPowerDeltaOnThreshold = 0; + + long gridPowerDeltaOnDelay = 0; + + long gridPowerDeltaOffThreshold = 0; + + long gridPowerDeltaOffDelay = 0; + + unsigned long gridPowerDeltaMillis = 0; public: - Relay(const uint8_t index, const String &topic, const char *name, const uint8_t pin, const bool inverted, const bool logState) : Output(name, pin, inverted, logState), nameFallback(name), topicFallback(topic), topic(topic), index(index) { + Relay(const uint8_t index, const String &topic, const char *name, const uint8_t pin, const bool inverted, const bool logState) : Output(name, pin, inverted, logState), nameFallback(name), topicFallback(topic), index(index), topic(topic) { // } void setup() override { Output::setup(); + Output::setName(configRead(path("name"), nameFallback)); - topic = configRead(path("topic"), topicFallback); Output::setInitial(configRead(path("initial"), INITIAL_OFF)); Output::setOnMillis(configRead(path("onMillis"), 0L)); Output::setOffMillis(configRead(path("offMillis"), 0L)); + + topic = configRead(path("topic"), topicFallback); + + gridPowerDeltaOnThreshold = configRead(path("autoOnThreshold"), -400L); + gridPowerDeltaOnDelay = configRead(path("autoOnDelay"), 30000L); + + gridPowerDeltaOffThreshold = configRead(path("autoOffThreshold"), 100L); + gridPowerDeltaOffDelay = configRead(path("autoOffDelay"), 30000L); + _applyInitial(); } + void loop() override { + Output::loop(); + doGridPowerDelta(); + } + void setName(const String &value) override { Output::setName(value); configWrite(path("name"), nameFallback, value); } - void setTopic(const String &value) { - topic = value; - configWrite(path("topic"), topicFallback, value); - } - void setInitial(const Initial value) override { Output::setInitial(value); configWrite(path("initial"), INITIAL_OFF, value); @@ -56,23 +75,46 @@ public: configWrite(path("offMillis"), 0L, value); } + void setTopic(const String &value) { + topic = value; + configWrite(path("topic"), topicFallback, value); + } + + void setAutoOnThreshold(const long value) { + gridPowerDeltaOnThreshold = value; + configWrite(path("autoOnThreshold"), 0L, value); + } + + void setAutoOnDelay(const long value) { + gridPowerDeltaOnDelay = value; + configWrite(path("autoOnDelay"), 0L, value); + } + + void setAutoOffThreshold(const long value) { + gridPowerDeltaOffThreshold = value; + configWrite(path("autoOffThreshold"), 0L, value); + } + + void setAutoOffDelay(const long value) { + gridPowerDeltaOffDelay = value; + configWrite(path("autoOffDelay"), 0L, value); + } + void json(const JsonObject json) const { json["name"] = name; - json["topic"] = topic; json["state"] = get(); json["stateAgeMillis"] = millis() - stateMillis; json["initial"] = initialToString(initial); json["onCount"] = onCount; json["onMillis"] = onMillis; json["offMillis"] = offMillis; - } -private: + json["topic"] = topic; - String path(const char *name) const { - char path[64]; - snprintf(path, sizeof(path), "/relay%d/%s", index, name); - return String(path); + json["autoOnThreshold"] = gridPowerDeltaOnThreshold; + json["autoOnDelay"] = gridPowerDeltaOnDelay; + json["autoOffThreshold"] = gridPowerDeltaOffThreshold; + json["autoOffDelay"] = gridPowerDeltaOffDelay; } protected: @@ -83,6 +125,49 @@ protected: mqttPublish(topic, doc); } +private: + + String path(const char *name) const { + char path[64]; + snprintf(path, sizeof(path), "/relay%d/%s", index, name); + return String(path); + } + + void doGridPowerDelta() { + const auto age = millis() - gridPowerDeltaMillis; + if (get()) { + if (gridPowerDelta > gridPowerDeltaOffThreshold) { + if (gridPowerDeltaMillis == 0) { + Serial.printf("[%s] CONSUMING TOO MUCH: Preparing to power OFF...\n", name.c_str()); + gridPowerDeltaMillis = max(1UL, millis()); + } else { + if (age > gridPowerDeltaOffDelay) { + gridPowerDeltaMillis = 0; + set(false); + } + } + } else if (gridPowerDeltaMillis > 0) { + Serial.printf("[%s] Powering off CANCELED!\n", name.c_str()); + gridPowerDeltaMillis = 0; + } + } else { + if (gridPowerDelta < gridPowerDeltaOnThreshold) { + if (gridPowerDeltaMillis == 0) { + Serial.printf("[%s] PRODUCING TOO MUCH: Preparing to power ON...\n", name.c_str()); + gridPowerDeltaMillis = max(1UL, millis()); + } else { + if (age > gridPowerDeltaOnDelay) { + gridPowerDeltaMillis = 0; + set(true); + } + } + } else if (gridPowerDeltaMillis > 0) { + Serial.printf("[%s] Powering on CANCELED!\n", name.c_str()); + gridPowerDeltaMillis = 0; + } + } + } + }; #endif diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 1aced01..c4b1724 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -5,6 +5,8 @@ #include "PubSubClient.h" +const String GRID_POWER_DELTA_TOPIC = "electricity/grid/power/signed/w"; + WiFiClient wifiClient; PubSubClient client(wifiClient); @@ -13,10 +15,32 @@ bool mqttShouldConnect = false; unsigned long mqttLast = 0; +unsigned long warningLast = 0; + void mqttSetup() { mqttShouldConnect = true; } +double gridPowerDelta = NAN; + +double gridPowerDeltaMillis = 0; + +void mqttReceive(const char *topic, const uint8_t *bytes, const unsigned int length) { + if (length >= 100) { + Serial.println("[MQTT] Inbound buffer overflow"); + return; + } + char string[100]; + memcpy(string, bytes, length); + const auto payload = String(string); + if (GRID_POWER_DELTA_TOPIC == topic) { + gridPowerDelta = payload.toDouble(); + gridPowerDeltaMillis = millis(); + } else { + Serial.printf("[MQTT] Received unexpected topic: %s\n", topic); + } +} + void mqttLoop() { if (client.loop()) { if (!mqttShouldConnect) { @@ -24,20 +48,22 @@ void mqttLoop() { Serial.println("[MQTT] Stopped."); } } else if (mqttShouldConnect) { - const auto host = configRead("/mqtt/host", "", false); + const auto host = configRead("/mqtt/host", "10.0.0.50", false); if (host == "") { return; } if (mqttLast == 0 || millis() - mqttLast >= 3000) { - const auto id = configRead("/mqtt/id", "test", false); - const auto user = configRead("/mqtt/user", "", false); - const auto pass = configRead("/mqtt/pass", "", false, true); - const auto port = configRead("/mqtt/port", 1883L, false); + const auto id = configRead("/mqtt/id", "test"); + const auto user = configRead("/mqtt/user", ""); + const auto pass = configRead("/mqtt/pass", "", true, true); + const auto port = configRead("/mqtt/port", 1883L); mqttLast = max(1UL, millis()); client.setServer(host.c_str(), port); Serial.printf("[MQTT] Connecting: %s:%ld\n", host.c_str(), port); if (client.connect(id.c_str(), user.c_str(), pass.c_str())) { Serial.printf("[MQTT] Connected.\n"); + client.subscribe(GRID_POWER_DELTA_TOPIC.c_str()); + client.setCallback(mqttReceive); } else { Serial.printf("[MQTT] Failed to connect.\n"); } diff --git a/src/mqtt.h b/src/mqtt.h index 8e9e1af..1fc5787 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -3,6 +3,10 @@ #include +extern double gridPowerDelta; + +extern double gridPowerDeltaMillis; + void mqttSetup(); void mqttLoop();