WiFi, Hostname, OTA, Button, Relay, Status, config

This commit is contained in:
Patrick Haßel 2025-08-27 23:12:18 +02:00
commit 805e492fe9
11 changed files with 334 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.pio

8
platformio.ini Normal file
View File

@ -0,0 +1,8 @@
[env:Sonoff4ChPro]
platform = espressif8266
board = esp8285
framework = arduino
upload_speed = 921600
upload_port = 10.0.0.178
monitor_speed = 115200
build.filesystem = littlefs

74
src/Button.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef BUTTON_H
#define BUTTON_H
#include <Arduino.h>
enum ButtonEvent {
BUTTON_PRESSED,
BUTTON_PRESSED_LONG,
BUTTON_RELEASED,
BUTTON_RELEASED_SHORT,
BUTTON_RELEASED_LONG,
};
class Button {
public:
typedef void (*callback_t)(ButtonEvent event);
private:
const uint8_t pin;
const bool pullup;
const bool inverted;
const callback_t callback;
bool lastState = false;
bool pressedLong = false;
unsigned long lastMillis = 0;
public:
explicit Button(const uint8_t pin, const bool pullup, const bool inverted, const callback_t callback) : pin(pin), pullup(pullup), inverted(inverted), callback(callback) {
//
}
void setup() {
pinMode(pin, pullup ? INPUT_PULLUP : INPUT);
lastMillis = millis();
lastState = (digitalRead(pin) == HIGH) ^ inverted;
}
void loop() {
const auto currentMillis = millis();
const auto currentState = digitalRead(pin) == LOW;
const auto duration = currentMillis - lastMillis;
if (lastState != currentState && duration >= 200) {
if (currentState) {
callback(BUTTON_PRESSED);
} else {
callback(BUTTON_RELEASED);
if (pressedLong) {
callback(BUTTON_RELEASED_LONG);
} else {
callback(BUTTON_RELEASED_SHORT);
}
pressedLong = false;
}
lastState = currentState;
lastMillis = currentMillis;
} else if (lastState && duration >= 3000 && !pressedLong) {
pressedLong = true;
callback(BUTTON_PRESSED_LONG);
}
}
};
#endif

47
src/Output.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef OUTPUT_H
#define OUTPUT_H
#include <Arduino.h>
class Output {
const char *name;
const uint8_t pin;
const bool inverted;
const bool logState;
public:
Output(const char *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);
}
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);
}
bool get() const {
return (digitalRead(pin) == HIGH) ^ inverted;
}
void toggle() const {
set(!get());
}
void loop() const {
// TODO auto off etc
}
};
#endif

35
src/config.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "config.h"
#include <LittleFS.h>
void configSetup() {
LittleFS.begin();
}
File configOpen(const char *name, const char *mode) {
char path[64];
snprintf(path, sizeof(path), "/%s", name);
return LittleFS.open(path, mode);
}
String configReadString(const char *name, const char *fallback) {
if (auto file = configOpen(name, "r")) {
const auto content = file.readString();
file.close();
return content;
}
return fallback;
}
bool configWriteString(const char *name, const char *fallback, const String &value) {
if (configReadString(name, fallback) == value) {
return false;
}
Serial.printf("\"%s\" = \"%s\"", name, value.c_str());
if (auto file = configOpen(name, "w")) {
file.write(value.c_str(), value.length());
file.close();
return true;
}
return false;
}

12
src/config.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <WString.h>
void configSetup();
String configReadString(const char *name, const char *fallback);
bool configWriteString(const char *name, const char *fallback, const String &value);
#endif

7
src/io.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "io.h"
void buttonCallback(const Output &output, const ButtonEvent event) {
if (event == BUTTON_PRESSED) {
output.toggle();
}
}

55
src/io.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef IO_H
#define IO_H
#include "Button.h"
#include "Output.h"
void buttonCallback(const Output &output, ButtonEvent event);
inline Output relay1("RELAY #1", 12, false, true);
inline Output relay2("RELAY #2", 5, false, true);
inline Output relay3("RELAY #3", 4, false, true);
inline Output relay4("RELAY #4", 15, false, true);
inline Output status("Status", 13, true, false);
inline Button button1(0, true, true, [](const ButtonEvent event) { buttonCallback(relay1, event); });
inline Button button2(9, true, true, [](const ButtonEvent event) { buttonCallback(relay2, event); });
inline Button button3(10, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); });
inline Button button4(14, true, true, [](const ButtonEvent event) { buttonCallback(relay4, event); });
inline void ioSetup() {
button1.setup();
button2.setup();
button3.setup();
button4.setup();
status.setup();
relay1.setup();
relay2.setup();
relay3.setup();
relay4.setup();
}
inline void ioLoop() {
button1.loop();
button2.loop();
button3.loop();
button4.loop();
status.loop();
relay1.loop();
relay2.loop();
relay3.loop();
relay4.loop();
}
#endif

20
src/main.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <ArduinoOTA.h>
#include "config.h"
#include "io.h"
#include "wifi.h"
void setup() {
delay(500);
Serial.begin(115200);
Serial.print("\n\n\nStartup!\n");
configSetup();
ioSetup();
wifiSetup();
}
void loop() {
ioLoop();
ArduinoOTA.handle();
}

63
src/wifi.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "wifi.h"
#include <ArduinoOTA.h>
#include "config.h"
#include "io.h"
#define DEFAULT_HOSTNAME "PatrixSonoff4ChPro"
#define DEFAULT_WIFI_SSID "HappyNet"
#define DEFAULT_WIFI_PASS "1Grausame!Sackratte7"
void wifiChangeHostname(const char *hostname) {
if (configWriteString("hostname", DEFAULT_HOSTNAME, hostname)) {
WiFi.setHostname(hostname);
Serial.printf("Changed hostname to: %s\n", hostname);
}
}
void wifiChangeSSID(const char *ssid) {
if (configWriteString("wifiSSID", DEFAULT_WIFI_SSID, ssid)) {
Serial.printf("Changed SSID to: %s\n", ssid);
}
}
void wifiChangePassword(const char *password) {
configWriteString("wifiPass", DEFAULT_WIFI_PASS, password);
Serial.printf("Changed password\n");
}
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...");
status.set(true);
});
ArduinoOTA.onProgress([](const unsigned progress, const unsigned total) {
Serial.printf("OTA: %3d%%\r", 100 * progress / total);
status.toggle();
});
ArduinoOTA.onEnd([] {
Serial.println("\nOTA success!");
status.set(true);
});
ArduinoOTA.onError([](const ota_error_t error) {
Serial.printf("\nOTA error %u\n", error);
status.set(false);
});
ArduinoOTA.begin();
delay(1000);
}

12
src/wifi.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef WIFI_H
#define WIFI_H
void wifiChangeHostname(const char *hostname);
void wifiChangeSSID(const char *ssid);
void wifiChangePassword(const char *password);
void wifiSetup();
#endif