From 1cbb33600af6f248e345e6554f57c8345b5bae33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Mon, 8 Sep 2025 08:57:52 +0200 Subject: [PATCH] Relay2 WIP --- src/Property.h | 41 +++++++++++ src/PropertyBool.h | 34 +++++++++ src/PropertyLong.h | 28 ++++++++ src/Relay2.h | 169 +++++++++++++++++++++++++++++++++++++++++++++ src/io.h | 3 + src/log.cpp | 13 ++++ src/log.h | 6 ++ 7 files changed, 294 insertions(+) create mode 100644 src/Property.h create mode 100644 src/PropertyBool.h create mode 100644 src/PropertyLong.h create mode 100644 src/Relay2.h create mode 100644 src/log.cpp create mode 100644 src/log.h diff --git a/src/Property.h b/src/Property.h new file mode 100644 index 0000000..b2966c2 --- /dev/null +++ b/src/Property.h @@ -0,0 +1,41 @@ +#ifndef PROPERTY_H +#define PROPERTY_H + +#include + +template +class Property { + +public: + + const String path; + + T fallback; + + void load() { + String content = ""; + if (auto file = LittleFS.open(path, "r")) { + content = file.readString(); + file.close(); + } + value = toValue(content); + } + +protected: + + T value; + + explicit Property(const String &path, const T fallback) + : path(path), + fallback(fallback), + value(fallback) { + // + } + + virtual ~Property() = default; + + virtual T toValue(const String &content) =0; + +}; + +#endif diff --git a/src/PropertyBool.h b/src/PropertyBool.h new file mode 100644 index 0000000..aef8457 --- /dev/null +++ b/src/PropertyBool.h @@ -0,0 +1,34 @@ +#ifndef PROPERTY_BOOL_H +#define PROPERTY_BOOL_H + +#include + +class PropertyBool final : public Property { + +public: + + PropertyBool(const String &name, const bool fallback) + : Property(name, fallback) { + // + } + + // ReSharper disable once CppNonExplicitConversionOperator + operator bool() const { + return value; + } + +protected: + + bool toValue(const String &content) override { + if (content == "true") { + return true; + } + if (content == "false") { + return false; + } + return fallback; + } + +}; + +#endif diff --git a/src/PropertyLong.h b/src/PropertyLong.h new file mode 100644 index 0000000..9aca19e --- /dev/null +++ b/src/PropertyLong.h @@ -0,0 +1,28 @@ +#ifndef PROPERTY_LONG_H +#define PROPERTY_LONG_H + +#include + +class PropertyLong final : public Property { + +public: + + PropertyLong(const String &name, const long fallback) + : Property(name, fallback) { + // + } + + // ReSharper disable once CppNonExplicitConversionOperator + operator long() const { + return value; + } + +protected: + + long toValue(const String &content) override { + return content.toInt(); + } + +}; + +#endif diff --git a/src/Relay2.h b/src/Relay2.h new file mode 100644 index 0000000..d4f693f --- /dev/null +++ b/src/Relay2.h @@ -0,0 +1,169 @@ +#ifndef RELAY2_H +#define RELAY2_H + +#include + +#include "log.h" +#include "PropertyBool.h" +#include "PropertyLong.h" + +class Relay2 { + + bool state = false; + + unsigned long stateSince = 0; + + unsigned long manualSince = 0; + +public: + + const int index; + + const int pin; + + const bool inverted; + + String prefix; + + PropertyBool initialState; + + PropertyBool maxOnEnabled; + + PropertyLong maxOnSeconds; + + PropertyBool maxOffEnabled; + + PropertyLong maxOffSeconds; + + PropertyBool buttonOnEnabled; + + PropertyLong buttonOnSeconds; + + PropertyBool buttonOffEnabled; + + PropertyLong buttonOffSeconds; + + PropertyBool powerOnEnabled; + + PropertyLong powerOnThreshold; + + PropertyLong powerOnSeconds; + + PropertyBool powerOffEnabled; + + PropertyLong powerOffThreshold; + + PropertyLong powerOffSeconds; + + Relay2(const int index, const int pin, const bool inverted) + : index(index), + pin(pin), + inverted(inverted), + prefix("/relay" + String(index)), + initialState(prefix + "initialState", false), + maxOnEnabled(prefix + "maxOnEnabled", false), + maxOnSeconds(prefix + "maxOnSeconds", 0), + maxOffEnabled(prefix + "maxOffEnabled", false), + maxOffSeconds(prefix + "maxOffSeconds", 0), + buttonOnEnabled(prefix + "buttonOnEnabled", false), + buttonOnSeconds(prefix + "buttonOnSeconds", 0), + buttonOffEnabled(prefix + "buttonOffEnabled", false), + buttonOffSeconds(prefix + "buttonOffSeconds", 0), + powerOnEnabled(prefix + "powerOnEnabled", false), + powerOnThreshold(prefix + "powerOnThreshold", 0), + powerOnSeconds(prefix + "powerOnSeconds", 0), + powerOffEnabled(prefix + "powerOffEnabled", false), + powerOffThreshold(prefix + "powerOffThreshold", 0), + powerOffSeconds(prefix + "powerOffSeconds", 0) { + // + } + + void setup() { + pinMode(pin, OUTPUT); + + load(); + + state = !initialState; + setState(initialState, false); + } + + void loop() { + const auto stateAge = millis() - stateSince; + loopMax(stateAge); + if (manualSince > 0) { + loopManual(stateAge); + } else { + loopPower(stateAge); + } + } + + bool getState() const { + return state; + } + + void setManual(const bool newState) { + setState(newState, false); + } + +protected: + + void setState(const bool newState, const bool manual) { + if (manual) { + manualSince = max(1UL, millis()); + } else { + manualSince = 0; + } + if (state != newState) { + info("[RELAY%d] %s", index, state ? "ON" : "OFF"); + state = newState; + stateSince = millis(); + digitalWrite(pin, state ^ inverted ? HIGH : LOW); + } + } + +private: + + void loopMax(const long stateAge) { + if (state && maxOnEnabled && maxOnSeconds && stateAge >= maxOnSeconds) { + setState(false, false); + } + if (!state && maxOffEnabled && maxOffSeconds && stateAge >= maxOffSeconds) { + setState(true, false); + } + } + + void loopManual(const long stateAge) { + if (state && buttonOnEnabled && buttonOnSeconds && stateAge >= buttonOnSeconds) { + setState(false, false); + } + if (!state && buttonOffEnabled && buttonOffSeconds && stateAge >= buttonOffSeconds) { + setState(true, false); + } + } + + void loopPower(const long stateAge) { + const auto valid = !isnan(gridPowerDeltaValue) && millis() - gridPowerDeltaMillis <= 10000; + // TODO + } + + void load() { + initialState.load(); + maxOnEnabled.load(); + maxOnSeconds.load(); + maxOffEnabled.load(); + maxOffSeconds.load(); + buttonOnEnabled.load(); + buttonOnSeconds.load(); + buttonOffEnabled.load(); + buttonOffSeconds.load(); + powerOnEnabled.load(); + powerOnThreshold.load(); + powerOnSeconds.load(); + powerOffEnabled.load(); + powerOffThreshold.load(); + powerOffSeconds.load(); + } + +}; + +#endif diff --git a/src/io.h b/src/io.h index 9f42ba2..d7f4f4d 100644 --- a/src/io.h +++ b/src/io.h @@ -3,6 +3,7 @@ #include "Relay.h" #include "Button.h" +#include "Relay2.h" void buttonCallback(Output &output, ButtonEvent event); @@ -10,6 +11,8 @@ extern Output status; extern Button button0; +extern Relay2 relay99; + extern Relay relay0; #ifdef GosundSP111 diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..48d8c5b --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,13 @@ +#include "log.h" + +#include +#include + +void info(const char *format, ...) { + char message[512]; + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + Serial.println(message); + va_end(args); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..12a57fa --- /dev/null +++ b/src/log.h @@ -0,0 +1,6 @@ +#ifndef LOG_H +#define LOG_H + +void info(const char *format, ...); + +#endif