From 7154e16b421c392c9af0d69299141685c48609f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Thu, 28 Aug 2025 11:57:55 +0200 Subject: [PATCH] Better WiFi, Relay => Output+Relay, persist Relay config --- platformio.ini | 13 ++++- src/Output.h | 107 ++++++++++++++++++++++++++++++++++++----- src/Relay.h | 50 ++++++++++++++++++++ src/config.cpp | 54 ++++++++++++++++++--- src/config.h | 16 ++++++- src/http.cpp | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ src/http.h | 10 ++++ src/io.cpp | 28 ++++++++++- src/io.h | 22 ++++----- src/main.cpp | 2 + src/wifi.cpp | 100 +++++++++++++++++++++++++++------------ src/wifi.h | 2 + 12 files changed, 464 insertions(+), 66 deletions(-) create mode 100644 src/Relay.h create mode 100644 src/http.cpp create mode 100644 src/http.h diff --git a/platformio.ini b/platformio.ini index b0e3e11..beb6aff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -5,4 +5,15 @@ framework = arduino upload_speed = 921600 upload_port = 10.0.0.178 monitor_speed = 115200 -build.filesystem = littlefs \ No newline at end of file +build.filesystem = littlefs +lib_deps = bblanchon/ArduinoJson @ 7.4.2 + +[env:ESP32Test] +platform = espressif32 +board = esp32dev +framework = arduino +upload_speed = 921600 +monitor_speed = 115200 +build.filesystem = littlefs +lib_deps = bblanchon/ArduinoJson @ 7.4.2 +build_flags = -D STATUS_PIN=2 -D STATUS_INVERT=false \ No newline at end of file diff --git a/src/Output.h b/src/Output.h index d5d406b..47dcf5f 100644 --- a/src/Output.h +++ b/src/Output.h @@ -5,7 +5,9 @@ class Output { - const char *name; +protected: + + String name; const uint8_t pin; @@ -13,33 +15,112 @@ class Output { const bool logState; + bool initial = false; + + long onCount = -1; + + unsigned long onMillis = 0; + + unsigned long offMillis = 0; + + unsigned long stateMillis = 0; + + void _write(const bool state) { + if (state != get()) { + if (logState) { + Serial.printf("%s: %s\n", name.c_str(), state ? "ON" : "OFF"); + } + stateMillis = millis(); + } + digitalWrite(pin, state ^ inverted ? HIGH : LOW); + } + public: - Output(const char *name, const uint8_t pin, const bool inverted, const bool logState): name(name), pin(pin), inverted(inverted), logState(logState) { + Output(const String &name, const uint8_t pin, const bool inverted, const bool logState) : name(name), pin(pin), inverted(inverted), logState(logState) { // } - void setup() const { - pinMode(pin, OUTPUT); - } + virtual ~Output() = default; - void set(const bool state) const { - if (logState && state != get()) { - Serial.printf("%s: %s\n", name, state ? "ON" : "OFF"); - } - digitalWrite(pin, state ^ inverted ? HIGH : LOW); + virtual void setup() { + pinMode(pin, OUTPUT); + _write(initial); } bool get() const { return (digitalRead(pin) == HIGH) ^ inverted; } - void toggle() const { + void set(const bool state) { + _write(state); + onCount = 0; + } + + void toggle() { set(!get()); } - void loop() const { - // TODO auto off etc + void blink(const unsigned long onMillis_, const unsigned long offMillis_, const unsigned long onCount_ = -1) { + this->onMillis = onMillis_; + this->offMillis = offMillis_; + this->onCount = onCount_; + set(false); + } + + void loop() { + if (get()) { + if (onMillis > 0 && millis() - stateMillis > onMillis) { + _write(false); + } + } else { + if (offMillis > 0 && millis() - stateMillis > offMillis && onCount != 0) { + _write(true); + if (onCount > 0) { + onCount--; + } + } + } + } + + virtual void setName(const String &value) { + name = value; + } + + virtual void setInitial(const bool value) { + initial = value; + } + + void setOnCount(const long value) { + onCount = value; + } + + virtual void setOnMillis(const unsigned long value) { + onMillis = value; + } + + virtual void setOffMillis(const unsigned long value) { + offMillis = value; + } + + bool getInitial() const { + return initial; + } + + long getOnCount() const { + return onCount; + } + + unsigned long getOnMillis() const { + return onMillis; + } + + unsigned long getOffMillis() const { + return offMillis; + } + + unsigned long getStateMillis() const { + return millis() - stateMillis; } }; diff --git a/src/Relay.h b/src/Relay.h new file mode 100644 index 0000000..81ba12f --- /dev/null +++ b/src/Relay.h @@ -0,0 +1,50 @@ +#ifndef RELAY_H +#define RELAY_H + +#include "config.h" +#include "Output.h" + +class Relay final : public Output { + + String nameFallback; + + const uint8_t index; + +public: + + Relay(const uint8_t index, const char *name, const uint8_t pin, const bool inverted, const bool logState) : nameFallback(String("relay") + index), Output(nameFallback, pin, inverted, logState), index(index) { + // + } + + void setup() override { + Output::setup(); + Output::setName(configRead(String("relayName") + index, nameFallback)); + Output::setInitial(configRead(String("relayInitial") + index, false)); + Output::setOnMillis(configRead(String("relayOnMillis") + index, 0L)); + Output::setOffMillis(configRead(String("relayOffMillis") + index, 0L)); + _write(initial); + } + + void setName(const String &value) override { + Output::setName(value); + configWrite(String("relayName") + index, nameFallback, value); + } + + void setInitial(const bool value) override { + Output::setInitial(value); + configWrite(String("relayInitial") + index, false, value); + } + + void setOnMillis(const unsigned long value) override { + Output::setOnMillis(value); + configWrite(String("relayOnMillis") + index, 0L, value); + } + + void setOffMillis(const unsigned long value) override { + Output::setOffMillis(value); + configWrite(String("relayOffMillis") + index, 0L, value); + } + +}; + +#endif diff --git a/src/config.cpp b/src/config.cpp index 3aa0c5e..e18dcc5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,18 +1,58 @@ #include "config.h" #include +#include void configSetup() { LittleFS.begin(); } -File configOpen(const char *name, const char *mode) { +File configOpen(const String &name, const char *mode) { char path[64]; - snprintf(path, sizeof(path), "/%s", name); + snprintf(path, sizeof(path), "/%s", name.c_str()); return LittleFS.open(path, mode); } -String configReadString(const char *name, const char *fallback) { +long configRead(const String &name, const long fallback) { + if (auto file = configOpen(name, "r")) { + const auto content = file.readString(); + file.close(); + return content.toInt(); + } + return fallback; +} + +bool configWrite(const String &name, const long fallback, const long value) { + if (configRead(name, fallback) == value) { + return false; + } + Serial.printf("\"%s\" = \"%ld\"", name.c_str(), value); + if (auto file = configOpen(name, "w")) { + const auto content = String(value); + file.write(reinterpret_cast(content.c_str()), content.length()); + file.close(); + return true; + } + return false; +} + +bool configRead(const String &name, const bool fallback) { + return configRead(name, fallback ? 1L : 0L) > 0; +} + +bool configWrite(const String &name, const bool fallback, const bool value) { + return configWrite(name, fallback ? 1L : 0L, value ? 1L : 0L); +} + +String configRead(const String &name, const char *fallback) { + return configRead(name, String(fallback)); +} + +bool configWrite(const String &name, const char *fallback, const char *value) { + return configWrite(name, String(fallback), String(value)); +} + +String configRead(const String &name, const String &fallback) { if (auto file = configOpen(name, "r")) { const auto content = file.readString(); file.close(); @@ -21,13 +61,13 @@ String configReadString(const char *name, const char *fallback) { return fallback; } -bool configWriteString(const char *name, const char *fallback, const String &value) { - if (configReadString(name, fallback) == value) { +bool configWrite(const String &name, const String &fallback, const String &value) { + if (configRead(name, fallback) == value) { return false; } - Serial.printf("\"%s\" = \"%s\"", name, value.c_str()); + Serial.printf("\"%s\" = \"%s\"", name.c_str(), value.c_str()); if (auto file = configOpen(name, "w")) { - file.write(value.c_str(), value.length()); + file.write(reinterpret_cast(value.c_str()), value.length()); file.close(); return true; } diff --git a/src/config.h b/src/config.h index b1a4a9a..217b769 100644 --- a/src/config.h +++ b/src/config.h @@ -5,8 +5,20 @@ void configSetup(); -String configReadString(const char *name, const char *fallback); +long configRead(const String &name, long fallback); -bool configWriteString(const char *name, const char *fallback, const String &value); +bool configWrite(const String &name, long fallback, long value); + +bool configRead(const String &name, bool fallback); + +bool configWrite(const String &name, bool fallback, bool value); + +String configRead(const String &name, const char *fallback); + +bool configWrite(const String &name, const char *fallback, const char *value); + +String configRead(const String &name, const String &fallback); + +bool configWrite(const String &name, const String &fallback, const String &value); #endif diff --git a/src/http.cpp b/src/http.cpp new file mode 100644 index 0000000..e18344f --- /dev/null +++ b/src/http.cpp @@ -0,0 +1,126 @@ +#include "http.h" +#include "io.h" + +#include + +#ifdef ESP8266 +#include + +ESP8266WebServer server(80); +#endif + +#ifdef ESP32 +#include + +WebServer server(80); +#endif + +bool httpRunning = false; + +void httpRelay(const int index, Output &relay) { + const auto stateKey = String("state") + index; + if (server.hasArg(stateKey)) { + const auto state = server.arg(stateKey); + if (state == "true") { + Serial.printf("[HTTP] %s = %s\n", stateKey.c_str(), state.c_str()); + relay.set(true); + } else if (state == "false") { + Serial.printf("[HTTP] %s = %s\n", stateKey.c_str(), state.c_str()); + relay.set(false); + } + } + + const auto initialKey = String("initial") + index; + if (server.hasArg(initialKey)) { + const auto initial = server.arg(initialKey); + if (initial == "true") { + Serial.printf("[HTTP] %s = %s\n", initialKey.c_str(), initial.c_str()); + relay.setInitial(true); + } else if (initial == "false") { + Serial.printf("[HTTP] %s = %s\n", initialKey.c_str(), initial.c_str()); + relay.setInitial(false); + } + } + + const auto onMillisKey = String("onMillis") + index; + if (server.hasArg(onMillisKey)) { + const auto value = server.arg(onMillisKey).toInt(); + Serial.printf("[HTTP] %s = %ld\n", onMillisKey.c_str(), value); + relay.setOnMillis(value); + } + + const auto offMillisKey = String("offMillis") + index; + if (server.hasArg(offMillisKey)) { + const auto value = server.arg(offMillisKey).toInt(); + Serial.printf("[HTTP] %s = %ld\n", offMillisKey.c_str(), value); + relay.setOffMillis(value); + } + + const auto onCountKey = String("onCount") + index; + if (server.hasArg(onCountKey)) { + const auto value = server.arg(onCountKey).toInt(); + Serial.printf("[HTTP] %s = %ld\n", onCountKey.c_str(), value); + relay.setOnCount(value); + } +} + +void httpRelayJson(const Output &relay, const JsonObject json) { + json["state"] = relay.get(); + json["stateMillis"] = relay.getStateMillis(); + json["initial"] = relay.getInitial(); + json["onCount"] = relay.getOnCount(); + json["onMillis"] = relay.getOnMillis(); + json["offMillis"] = relay.getOffMillis(); +} + +void httpStatus() { + JsonDocument json; + json.to(); + + httpRelayJson(relay1, json["relay1"].to()); + httpRelayJson(relay2, json["relay2"].to()); + httpRelayJson(relay3, json["relay3"].to()); + httpRelayJson(relay4, json["relay4"].to()); + + String response; + serializeJson(json, response); + server.send(200, "application/json", response); +} + +void httpSet() { + httpRelay(1, relay1); + httpRelay(2, relay2); + httpRelay(3, relay3); + httpRelay(4, relay4); + httpStatus(); +} + +void httpOff() { + relay1.set(false); + relay2.set(false); + relay3.set(false); + relay4.set(false); + httpStatus(); +} + +void httpSetup() { + server.on("", httpSet); + server.on("/", httpSet); + server.on("/off", httpOff); + server.on("/off/", httpOff); + server.begin(); + Serial.println("HTTP server started"); + httpRunning = true; +} + +void httpLoop() { + server.handleClient(); +} + +void httpStop() { + if (httpRunning) { + httpRunning = false; + server.stop(); + Serial.println("HTTP server stopped"); + } +} diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..d59b25c --- /dev/null +++ b/src/http.h @@ -0,0 +1,10 @@ +#ifndef HTTP_H +#define HTTP_H + +void httpSetup(); + +void httpLoop(); + +void httpStop(); + +#endif diff --git a/src/io.cpp b/src/io.cpp index dcbdd49..57b0479 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -1,6 +1,32 @@ #include "io.h" -void buttonCallback(const Output &output, const ButtonEvent event) { +#ifndef STATUS_PIN +#define STATUS_PIN 13 +#endif + +#ifndef STATUS_INVERT +#define STATUS_INVERT true +#endif + +Relay relay1(1, "RELAY #1", 12, false, true); + +Relay relay2(2, "RELAY #2", 5, false, true); + +Relay relay3(3, "RELAY #3", 4, false, true); + +Relay relay4(4, "RELAY #4", 15, false, true); + +Output status("Status", STATUS_PIN, STATUS_INVERT, false); + +Button button1(0, true, true, [](const ButtonEvent event) { buttonCallback(relay1, event); }); + +Button button2(9, true, true, [](const ButtonEvent event) { buttonCallback(relay2, event); }); + +Button button3(10, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); }); + +Button button4(14, true, true, [](const ButtonEvent event) { buttonCallback(relay4, event); }); + +void buttonCallback(Output &output, const ButtonEvent event) { if (event == BUTTON_PRESSED) { output.toggle(); } diff --git a/src/io.h b/src/io.h index 5ce4547..1a0afad 100644 --- a/src/io.h +++ b/src/io.h @@ -1,28 +1,28 @@ #ifndef IO_H #define IO_H +#include "Relay.h" #include "Button.h" -#include "Output.h" -void buttonCallback(const Output &output, ButtonEvent event); +void buttonCallback(Output &output, ButtonEvent event); -inline Output relay1("RELAY #1", 12, false, true); +extern Relay relay1; -inline Output relay2("RELAY #2", 5, false, true); +extern Relay relay2; -inline Output relay3("RELAY #3", 4, false, true); +extern Relay relay3; -inline Output relay4("RELAY #4", 15, false, true); +extern Relay relay4; -inline Output status("Status", 13, true, false); +extern Output status; -inline Button button1(0, true, true, [](const ButtonEvent event) { buttonCallback(relay1, event); }); +extern Button button1; -inline Button button2(9, true, true, [](const ButtonEvent event) { buttonCallback(relay2, event); }); +extern Button button2; -inline Button button3(10, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); }); +extern Button button3; -inline Button button4(14, true, true, [](const ButtonEvent event) { buttonCallback(relay4, event); }); +extern Button button4; inline void ioSetup() { button1.setup(); diff --git a/src/main.cpp b/src/main.cpp index 3b38c71..47a0c73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,11 +10,13 @@ void setup() { Serial.print("\n\n\nStartup!\n"); configSetup(); + ioSetup(); wifiSetup(); } void loop() { + wifiLoop(); ioLoop(); ArduinoOTA.handle(); } diff --git a/src/wifi.cpp b/src/wifi.cpp index 963d2eb..4446b7d 100644 --- a/src/wifi.cpp +++ b/src/wifi.cpp @@ -1,63 +1,101 @@ #include "wifi.h" +#include "config.h" +#include "http.h" +#include "io.h" + +#define CONFIG_HOSTNAME "hostname" +#define CONFIG_WIFI_SSID "wifiSSID" +#define CONFIG_WIFI_PASSWORD "wifiPassword" + +#define DEFAULT_HOSTNAME "PatrixSonoff4ChPro" +#define DEFAULT_WIFI_SSID "HappyNet" +#define DEFAULT_WIFI_PASSWORD "1Grausame!Sackratte7" #include -#include "config.h" -#include "io.h" +bool wifiConnected = false; -#define DEFAULT_HOSTNAME "PatrixSonoff4ChPro" -#define DEFAULT_WIFI_SSID "HappyNet" -#define DEFAULT_WIFI_PASS "1Grausame!Sackratte7" +unsigned long wifiLast = 0; + +void wifiConnect() { + WiFi.disconnect(); + WiFi.enableAP(false); + WiFi.setAutoConnect(false); + WiFi.setAutoReconnect(false); + yield(); + + status.blink(500, 500); + + const auto hostname = configRead(CONFIG_HOSTNAME, DEFAULT_HOSTNAME); + const auto wifiSSID = configRead(CONFIG_WIFI_SSID, DEFAULT_WIFI_SSID); + const auto wifiPass = configRead(CONFIG_WIFI_PASSWORD, DEFAULT_WIFI_PASSWORD); + + WiFi.hostname(hostname); + WiFi.begin(wifiSSID.c_str(), wifiPass.c_str()); + wifiLast = max(1UL, millis()); + yield(); +} + +void wifiLoop() { + const auto connected = WiFi.localIP() != 0UL; + if (wifiConnected) { + if (connected) { + httpLoop(); + } else { + Serial.printf("[WiFi] Disconnected!\n"); + ArduinoOTA.end(); + httpStop(); + wifiConnect(); + } + } else { + if (connected) { + status.set(false); + Serial.printf("[WiFi] Connected as \"%s\" (%s)\n", WiFi.getHostname(), WiFi.localIP().toString().c_str()); + ArduinoOTA.begin(); + httpSetup(); + } else if (wifiLast == 0 || millis() - wifiLast >= 10000) { + if (wifiLast > 0) { + Serial.printf("[WiFi] Timeout!\n"); + } + wifiConnect(); + } + } + wifiConnected = connected; +} void wifiChangeHostname(const char *hostname) { - if (configWriteString("hostname", DEFAULT_HOSTNAME, hostname)) { + if (configWrite(CONFIG_HOSTNAME, DEFAULT_HOSTNAME, hostname)) { WiFi.setHostname(hostname); - Serial.printf("Changed hostname to: %s\n", hostname); + Serial.printf("[WiFi] hostname: %s\n", hostname); } } void wifiChangeSSID(const char *ssid) { - if (configWriteString("wifiSSID", DEFAULT_WIFI_SSID, ssid)) { - Serial.printf("Changed SSID to: %s\n", ssid); + if (configWrite(CONFIG_WIFI_SSID, DEFAULT_WIFI_SSID, ssid)) { + Serial.printf("[WiFi] SSID: %s\n", ssid); } } void wifiChangePassword(const char *password) { - configWriteString("wifiPass", DEFAULT_WIFI_PASS, password); - Serial.printf("Changed password\n"); + configWrite(CONFIG_WIFI_PASSWORD, DEFAULT_WIFI_PASSWORD, password); + Serial.printf("[WiFi] password: ***"); } void wifiSetup() { - const auto hostname = configReadString("hostname", DEFAULT_HOSTNAME); - const auto wifiSSID = configReadString("wifiSSID", DEFAULT_WIFI_SSID); - const auto wifiPass = configReadString("wifiPass", DEFAULT_WIFI_PASS); - WiFi.hostname(hostname); - WiFi.begin(wifiSSID, wifiPass); - while (WiFi.localIP() == 0UL) { - delay(500); - status.toggle(); - } - - yield(); - status.set(false); - Serial.printf("Connected as \"%s\" (%s)\n", WiFi.hostname().c_str(), WiFi.localIP().toString().c_str()); - ArduinoOTA.onStart([] { - Serial.println("OTA begin..."); + Serial.println("[OTA] Begin!"); status.set(true); }); ArduinoOTA.onProgress([](const unsigned progress, const unsigned total) { - Serial.printf("OTA: %3d%%\r", 100 * progress / total); + Serial.printf("[OTA] %3d%%\r", 100 * progress / total); status.toggle(); }); ArduinoOTA.onEnd([] { - Serial.println("\nOTA success!"); + Serial.println("\n[OTA] Success!"); status.set(true); }); ArduinoOTA.onError([](const ota_error_t error) { - Serial.printf("\nOTA error %u\n", error); + Serial.printf("\n[OTA] Error %u\n", error); status.set(false); }); - ArduinoOTA.begin(); - delay(1000); } diff --git a/src/wifi.h b/src/wifi.h index cb6cf16..800ef91 100644 --- a/src/wifi.h +++ b/src/wifi.h @@ -9,4 +9,6 @@ void wifiChangePassword(const char *password); void wifiSetup(); +void wifiLoop(); + #endif