#ifndef RELAY_H #define RELAY_H #include "config.h" #include "Output.h" #include "mqtt.h" class Relay final : public Output { String nameFallback; String topicFallback; const uint8_t index; String topic; bool gridPowerDeltaOffEnabled = false; long gridPowerDeltaOnThreshold = 0; long gridPowerDeltaOnDelay = 0; bool gridPowerDeltaOnEnabled = false; long gridPowerDeltaOffThreshold = 0; long gridPowerDeltaOffDelay = 0; unsigned long gridPowerDeltaLast = 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), index(index), topic(topic) { // } void setup() override { Output::setup(); Output::setName(loadString(path("name"), nameFallback)); Output::setInitial(loadInitial(path("initial"), INITIAL_OFF)); Output::setOnMillis(loadLong(path("onMillis"), 0L)); Output::setOffMillis(loadLong(path("offMillis"), 0L)); topic = loadString(path("topic"), topicFallback); gridPowerDeltaOnEnabled = loadBool(path("gridPowerDeltaOnEnabled"), false); gridPowerDeltaOnThreshold = loadLong(path("gridPowerDeltaOnThreshold"), 0L); gridPowerDeltaOnDelay = loadLong(path("gridPowerDeltaOnDelay"), 0L); gridPowerDeltaOffEnabled = loadBool(path("gridPowerDeltaOffEnabled"), false); gridPowerDeltaOffThreshold = loadLong(path("gridPowerDeltaOffThreshold"), 0L); gridPowerDeltaOffDelay = loadLong(path("gridPowerDeltaOffDelay"), 0L); _applyInitial(); } void loop() override { Output::loop(); doGridPowerDelta(); } void setName(const String &value) override { Output::setName(value); storeString(path("name"), nameFallback, value); } void setInitial(const Initial value) override { Output::setInitial(value); storeInitial(path("initial"), INITIAL_OFF, value); } void setOnMillis(const unsigned long value) override { Output::setOnMillis(value); storeLong(path("onMillis"), 0L, value); } void setOffMillis(const unsigned long value) override { Output::setOffMillis(value); storeLong(path("offMillis"), 0L, value); } void setTopic(const String &value) { topic = value; storeString(path("topic"), topicFallback, value); } void setGridPowerDeltaOnEnabled(const bool value) { gridPowerDeltaOnEnabled = value; storeBool(path("gridPowerDeltaOnEnabled"), false, value); } void setGridPowerDeltaOnThreshold(const long value) { gridPowerDeltaOnThreshold = value; storeLong(path("gridPowerDeltaOnThreshold"), 0L, value); } void setGridPowerDeltaOnDelay(const long value) { gridPowerDeltaOnDelay = value; storeLong(path("gridPowerDeltaOnDelay"), 0L, value); } void setGridPowerDeltaOffEnabled(const bool value) { gridPowerDeltaOffEnabled = value; storeBool(path("gridPowerDeltaOffEnabled"), false, value); } void setGridPowerDeltaOffThreshold(const long value) { gridPowerDeltaOffThreshold = value; storeLong(path("gridPowerDeltaOffThreshold"), 0L, value); } void setGridPowerDeltaOffDelay(const long value) { gridPowerDeltaOffDelay = value; storeLong(path("gridPowerDeltaOffDelay"), 0L, value); } void json(const JsonObject json) const { json["name"] = name; json["initial"] = initialToString(initial); json["onCount"] = onCount; json["onMillis"] = onMillis; json["offMillis"] = offMillis; json["topic"] = topic; json["gridPowerDeltaOffEnabled"] = gridPowerDeltaOffEnabled; json["gridPowerDeltaOnThreshold"] = gridPowerDeltaOnThreshold; json["gridPowerDeltaOnDelay"] = gridPowerDeltaOnDelay; json["gridPowerDeltaOnEnabled"] = gridPowerDeltaOnEnabled; json["gridPowerDeltaOffThreshold"] = gridPowerDeltaOffThreshold; json["gridPowerDeltaOffDelay"] = gridPowerDeltaOffDelay; json["state"] = get(); json["stateAgeMillis"] = millis() - stateMillis; } void set(const bool state) override { Output::set(state); gridPowerDeltaLast = 0; } protected: void publish() override { JsonDocument doc; json(doc.to()); 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() { if (get()) { gridPowerDeltaOff(); } else { gridPowerDeltaOn(); } } bool gridPowerDeltaConfigInvalid() { return gridPowerDeltaOffThreshold < gridPowerDeltaOnThreshold; } void gridPowerDeltaOff() { if (!gridPowerDeltaOffEnabled) { return; } const auto invalid = isnan(gridPowerDeltaValue) || millis() - gridPowerDeltaMillis > 10000 || gridPowerDeltaConfigInvalid(); if (gridPowerDeltaValue > gridPowerDeltaOffThreshold || invalid) { if (gridPowerDeltaLast == 0 && gridPowerDeltaOffDelay > 0) { Serial.printf("[RELAY] \"%s\": CONSUMING TOO MUCH: Preparing to power OFF...\n", name.c_str()); gridPowerDeltaLast = max(1UL, millis()); } else { if (millis() - gridPowerDeltaLast > gridPowerDeltaOffDelay) { set(false); } } } else if (gridPowerDeltaLast > 0) { Serial.printf("[RELAY] \"%s\": Powering off CANCELED!\n", name.c_str()); gridPowerDeltaLast = 0; } } void gridPowerDeltaOn() { if (!gridPowerDeltaOnEnabled) { return; } if (gridPowerDeltaValue < gridPowerDeltaOnThreshold && !gridPowerDeltaConfigInvalid()) { if (gridPowerDeltaLast == 0 && gridPowerDeltaOnDelay > 0) { Serial.printf("[RELAY] \"%s\": PRODUCING TOO MUCH: Preparing to power ON...\n", name.c_str()); gridPowerDeltaLast = max(1UL, millis()); } else { if (millis() - gridPowerDeltaLast > gridPowerDeltaOnDelay) { set(true); } } } else if (gridPowerDeltaLast > 0) { Serial.printf("[RELAY] \"%s\": Powering on CANCELED!\n", name.c_str()); gridPowerDeltaLast = 0; } } }; #endif