Compare commits
4 Commits
805e492fe9
...
f54f387b10
| Author | SHA1 | Date | |
|---|---|---|---|
| f54f387b10 | |||
| 65507f96a7 | |||
| c12ae7a50f | |||
| 7154e16b42 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
.pio
|
.pio
|
||||||
|
/index.html.min
|
||||||
5
icon.svg
Normal file
5
icon.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M17 12a1 1 0 0 1 1 1v9h-2v-8H8v8H6v-9a1 1 0 0 1 1-1h10zm-5 4v2h-2v-2h2zm0-10a6 6 0 0 1 5.368 3.316l-1.79.895a4 4 0 0 0-7.157 0l-1.789-.895A6 6 0 0 1 12 6zm0-4a10 10 0 0 1 8.946 5.527l-1.789.895A8 8 0 0 0 12 4a8 8 0 0 0-7.157 4.422l-1.79-.895A10 10 0 0 1 12 2z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 420 B |
167
index.html
Normal file
167
index.html
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<title id="title"></title>
|
||||||
|
<link rel="icon" type="image/svg" href="icon.svg">
|
||||||
|
<style>
|
||||||
|
.relay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relay > * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-weight: bold;;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number], select {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="relayList">
|
||||||
|
<div class="relay">
|
||||||
|
<div class="header name">Name</div>
|
||||||
|
<div class="header state">Status</div>
|
||||||
|
<div class="header initial">Initial</div>
|
||||||
|
<div class="header onMillis">Auto Aus</div>
|
||||||
|
<div class="header offMillis">Auto An</div>
|
||||||
|
<div class="header switchOn">Ein</div>
|
||||||
|
<div class="header switchOff">Aus</div>
|
||||||
|
<div class="header switchCycle">Zyklus</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre id="info"></pre>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const title = document.getElementById("title");
|
||||||
|
const info = document.getElementById("info");
|
||||||
|
const relayList = document.getElementById("relayList");
|
||||||
|
|
||||||
|
function getUrl(path) {
|
||||||
|
return `http://10.42.0.204/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setState(index, value) {
|
||||||
|
set("state", index, value ? 'true' : 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(key, index, value) {
|
||||||
|
request(`${key}${index}=${encodeURIComponent(value)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
function request(query = "") {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => request(), 2000);
|
||||||
|
|
||||||
|
const r = new XMLHttpRequest();
|
||||||
|
r.open("GET", getUrl(`set?${query}`));
|
||||||
|
r.onreadystatechange = () => {
|
||||||
|
if (r.readyState === 4 && r.status === 200) {
|
||||||
|
const data = JSON.parse(r.response);
|
||||||
|
title.innerText = data.hostname;
|
||||||
|
for (let index = 0; index < data.relays.length; index++) {
|
||||||
|
const relayData = data.relays[index];
|
||||||
|
const tag = document.getElementById("relay" + index) || create(index);
|
||||||
|
tag.getElementsByClassName("name")[0].getElementsByTagName("input")[0].value = relayData.name;
|
||||||
|
tag.getElementsByClassName("state")[0].innerText = relayData.state ? "Ein" : "Aus";
|
||||||
|
tag.getElementsByClassName("onMillis")[0].getElementsByTagName("input")[0].value = relayData.onMillis;
|
||||||
|
tag.getElementsByClassName("offMillis")[0].getElementsByTagName("input")[0].value = relayData.offMillis;
|
||||||
|
tag.getElementsByClassName("initial")[0].getElementsByTagName("select")[0].value = relayData.initial;
|
||||||
|
}
|
||||||
|
info.innerText = JSON.stringify(data, null, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function newDiv(relayIndex, relayTag, name) {
|
||||||
|
const div = document.createElement("div")
|
||||||
|
div.className = name;
|
||||||
|
relayTag.append(div);
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newInput(relayIndex, relayTag, name, type) {
|
||||||
|
const div = newDiv(relayIndex, relayTag, name);
|
||||||
|
|
||||||
|
const input = document.createElement("input")
|
||||||
|
input.type = type;
|
||||||
|
input.onchange = () => set(name, relayIndex, input.value);
|
||||||
|
div.append(input);
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newButton(relayIndex, relayTag, clazz, key, value, text) {
|
||||||
|
const div = newDiv(relayIndex, relayTag, clazz);
|
||||||
|
|
||||||
|
const button = document.createElement("button")
|
||||||
|
button.innerText = text;
|
||||||
|
button.onclick = () => set(key, relayIndex, value);
|
||||||
|
div.append(button);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newSelect(relayIndex, relayTag, name, options) {
|
||||||
|
const div = newDiv(relayIndex, relayTag, name);
|
||||||
|
|
||||||
|
const select = document.createElement("select")
|
||||||
|
select.onchange = () => set(name, relayIndex, select.value);
|
||||||
|
div.append(select);
|
||||||
|
|
||||||
|
for (const [value, text] of options) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = value;
|
||||||
|
option.innerText = text;
|
||||||
|
select.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return select;
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(relayIndex) {
|
||||||
|
const relayTag = document.createElement("div");
|
||||||
|
relayTag.id = "relay" + relayIndex;
|
||||||
|
relayTag.className = "relay";
|
||||||
|
relayList.append(relayTag);
|
||||||
|
|
||||||
|
newInput(relayIndex, relayTag, "name", "text");
|
||||||
|
newDiv(relayIndex, relayTag, "state");
|
||||||
|
newSelect(relayIndex, relayTag, "initial", [["OFF", "Aus"], ["ON", "Ein"], ["CYCLE", "Zyklus"]]);
|
||||||
|
newInput(relayIndex, relayTag, "onMillis", "number");
|
||||||
|
newInput(relayIndex, relayTag, "offMillis", "number");
|
||||||
|
newButton(relayIndex, relayTag, "switchOn", "state", "true", "Ein");
|
||||||
|
newButton(relayIndex, relayTag, "switchOff", "state", "false", "Aus");
|
||||||
|
newButton(relayIndex, relayTag, "switchCycle", "onCount", -1, "Zyklus");
|
||||||
|
return relayTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
request();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -5,4 +5,15 @@ framework = arduino
|
|||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
upload_port = 10.0.0.178
|
upload_port = 10.0.0.178
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
build.filesystem = littlefs
|
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
|
||||||
31
src/Initial.h
Normal file
31
src/Initial.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef INITIAL_H
|
||||||
|
#define INITIAL_H
|
||||||
|
|
||||||
|
enum Initial {
|
||||||
|
INITIAL_OFF,
|
||||||
|
INITIAL_ON,
|
||||||
|
INITIAL_CYCLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Initial stringToInitial(const String &str) {
|
||||||
|
if (str == "ON") {
|
||||||
|
return INITIAL_ON;
|
||||||
|
}
|
||||||
|
if (str == "CYCLE") {
|
||||||
|
return INITIAL_CYCLE;
|
||||||
|
}
|
||||||
|
return INITIAL_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *initialToString(const Initial value) {
|
||||||
|
switch (value) {
|
||||||
|
case INITIAL_ON:
|
||||||
|
return "ON";
|
||||||
|
case INITIAL_CYCLE:
|
||||||
|
return "CYCLE";
|
||||||
|
default:
|
||||||
|
return "OFF";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
122
src/Output.h
122
src/Output.h
@ -3,9 +3,13 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "Initial.h"
|
||||||
|
|
||||||
class Output {
|
class Output {
|
||||||
|
|
||||||
const char *name;
|
protected:
|
||||||
|
|
||||||
|
String name;
|
||||||
|
|
||||||
const uint8_t pin;
|
const uint8_t pin;
|
||||||
|
|
||||||
@ -13,33 +17,125 @@ class Output {
|
|||||||
|
|
||||||
const bool logState;
|
const bool logState;
|
||||||
|
|
||||||
|
Initial initial = INITIAL_OFF;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyInitial() {
|
||||||
|
_write(initial != INITIAL_OFF);
|
||||||
|
if (initial != INITIAL_CYCLE) {
|
||||||
|
onCount = 0;
|
||||||
|
} else {
|
||||||
|
onCount = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
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 {
|
virtual ~Output() = default;
|
||||||
pinMode(pin, OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(const bool state) const {
|
virtual void setup() {
|
||||||
if (logState && state != get()) {
|
pinMode(pin, OUTPUT);
|
||||||
Serial.printf("%s: %s\n", name, state ? "ON" : "OFF");
|
_applyInitial();
|
||||||
}
|
|
||||||
digitalWrite(pin, state ^ inverted ? HIGH : LOW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get() const {
|
bool get() const {
|
||||||
return (digitalRead(pin) == HIGH) ^ inverted;
|
return (digitalRead(pin) == HIGH) ^ inverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggle() const {
|
void set(const bool state) {
|
||||||
|
_write(state);
|
||||||
|
onCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggle() {
|
||||||
set(!get());
|
set(!get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() const {
|
void cycle(const unsigned long onMillis_, const unsigned long offMillis_, const unsigned long onCount_ = -1) {
|
||||||
// TODO auto off etc
|
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 Initial 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Initial 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName() const {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
50
src/Relay.h
Normal file
50
src/Relay.h
Normal file
@ -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) : Output(name, pin, inverted, logState), nameFallback(name), index(index) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
Output::setup();
|
||||||
|
Output::setName(configRead(String("relayName") + index, nameFallback));
|
||||||
|
Output::setInitial(configRead(String("relayInitial") + index, INITIAL_OFF));
|
||||||
|
Output::setOnMillis(configRead(String("relayOnMillis") + index, 0L));
|
||||||
|
Output::setOffMillis(configRead(String("relayOffMillis") + index, 0L));
|
||||||
|
_applyInitial();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(const String &value) override {
|
||||||
|
Output::setName(value);
|
||||||
|
configWrite(String("relayName") + index, nameFallback, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInitial(const Initial value) override {
|
||||||
|
Output::setInitial(value);
|
||||||
|
configWrite(String("relayInitial") + index, INITIAL_OFF, 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
|
||||||
@ -1,18 +1,58 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
void configSetup() {
|
void configSetup() {
|
||||||
LittleFS.begin();
|
LittleFS.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
File configOpen(const char *name, const char *mode) {
|
File configOpen(const String &name, const char *mode) {
|
||||||
char path[64];
|
char path[64];
|
||||||
snprintf(path, sizeof(path), "/%s", name);
|
snprintf(path, sizeof(path), "/%s", name.c_str());
|
||||||
return LittleFS.open(path, mode);
|
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("[CONFIG] \"%s\" = \"%ld\"\n", name.c_str(), value);
|
||||||
|
if (auto file = configOpen(name, "w")) {
|
||||||
|
const auto content = String(value);
|
||||||
|
file.write(reinterpret_cast<const uint8_t *>(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")) {
|
if (auto file = configOpen(name, "r")) {
|
||||||
const auto content = file.readString();
|
const auto content = file.readString();
|
||||||
file.close();
|
file.close();
|
||||||
@ -21,15 +61,23 @@ String configReadString(const char *name, const char *fallback) {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool configWriteString(const char *name, const char *fallback, const String &value) {
|
bool configWrite(const String &name, const String &fallback, const String &value) {
|
||||||
if (configReadString(name, fallback) == value) {
|
if (configRead(name, fallback) == value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Serial.printf("\"%s\" = \"%s\"", name, value.c_str());
|
Serial.printf("[CONFIG] \"%s\" = \"%s\"\n", name.c_str(), value.c_str());
|
||||||
if (auto file = configOpen(name, "w")) {
|
if (auto file = configOpen(name, "w")) {
|
||||||
file.write(value.c_str(), value.length());
|
file.write(reinterpret_cast<const uint8_t *>(value.c_str()), value.length());
|
||||||
file.close();
|
file.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Initial configRead(const String &name, const Initial &fallback) {
|
||||||
|
return stringToInitial(configRead(name, initialToString(fallback)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configWrite(const String &name, const Initial &fallback, const Initial &value) {
|
||||||
|
return configWrite(name, initialToString(fallback), initialToString(value));
|
||||||
|
}
|
||||||
|
|||||||
22
src/config.h
22
src/config.h
@ -3,10 +3,28 @@
|
|||||||
|
|
||||||
#include <WString.h>
|
#include <WString.h>
|
||||||
|
|
||||||
|
#include "Initial.h"
|
||||||
|
|
||||||
void configSetup();
|
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);
|
||||||
|
|
||||||
|
Initial configRead(const String &name, const Initial &fallback);
|
||||||
|
|
||||||
|
bool configWrite(const String &name, const Initial &fallback, const Initial &value);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
171
src/http.cpp
Normal file
171
src/http.cpp
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#include "http.h"
|
||||||
|
#include "io.h"
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
|
||||||
|
ESP8266WebServer server(80);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool httpRunning = false;
|
||||||
|
|
||||||
|
void httpRelay(const int index, Output &relay) {
|
||||||
|
const auto nameKey = String("name") + index;
|
||||||
|
if (server.hasArg(nameKey)) {
|
||||||
|
const auto name = server.arg(nameKey);
|
||||||
|
relay.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "OFF") {
|
||||||
|
Serial.printf("[HTTP] %s = %s\n", initialKey.c_str(), initial.c_str());
|
||||||
|
relay.setInitial(INITIAL_OFF);
|
||||||
|
} else if (initial == "ON") {
|
||||||
|
Serial.printf("[HTTP] %s = %s\n", initialKey.c_str(), initial.c_str());
|
||||||
|
relay.setInitial(INITIAL_ON);
|
||||||
|
} else if (initial == "CYCLE") {
|
||||||
|
Serial.printf("[HTTP] %s = %s\n", initialKey.c_str(), initial.c_str());
|
||||||
|
relay.setInitial(INITIAL_CYCLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpRelayJson(const Output &relay, const JsonObject json) {
|
||||||
|
json["name"] = relay.getName();
|
||||||
|
json["state"] = relay.get();
|
||||||
|
json["stateMillis"] = relay.getStateMillis();
|
||||||
|
json["initial"] = initialToString(relay.getInitial());
|
||||||
|
json["onCount"] = relay.getOnCount();
|
||||||
|
json["onMillis"] = relay.getOnMillis();
|
||||||
|
json["offMillis"] = relay.getOffMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpStatus() {
|
||||||
|
JsonDocument json;
|
||||||
|
json["hostname"] = WiFi.getHostname();
|
||||||
|
|
||||||
|
const auto relays = json["relays"].to<JsonArray>();
|
||||||
|
httpRelayJson(relay0, relays.add<JsonObject>());
|
||||||
|
httpRelayJson(relay1, relays.add<JsonObject>());
|
||||||
|
httpRelayJson(relay2, relays.add<JsonObject>());
|
||||||
|
httpRelayJson(relay3, relays.add<JsonObject>());
|
||||||
|
|
||||||
|
String response;
|
||||||
|
serializeJson(json, response);
|
||||||
|
server.send(200, "application/json", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpSet() {
|
||||||
|
httpRelay(0, relay0);
|
||||||
|
httpRelay(1, relay1);
|
||||||
|
httpRelay(2, relay2);
|
||||||
|
httpRelay(3, relay3);
|
||||||
|
httpStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpOff() {
|
||||||
|
relay0.set(false);
|
||||||
|
relay1.set(false);
|
||||||
|
relay2.set(false);
|
||||||
|
relay3.set(false);
|
||||||
|
httpStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
File httpUploadFile;
|
||||||
|
|
||||||
|
void httpUpload(const char *name) {
|
||||||
|
const auto upload = server.upload();
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
char path[64];
|
||||||
|
snprintf(path, sizeof(path), "/%s", name);
|
||||||
|
httpUploadFile = LittleFS.open(path, "w");
|
||||||
|
Serial.printf("[HTTP] Uploading: %s\n", name);
|
||||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
|
if (httpUploadFile) {
|
||||||
|
httpUploadFile.write(upload.buf, upload.currentSize);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (httpUploadFile) {
|
||||||
|
httpUploadFile.close();
|
||||||
|
Serial.printf("[HTTP] Upload complete: %s (%d bytes)\n", name, upload.currentSize);
|
||||||
|
server.send(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpSetup() {
|
||||||
|
server.enableCORS(true);
|
||||||
|
|
||||||
|
server.serveStatic("", LittleFS, "/index.html");
|
||||||
|
server.serveStatic("/", LittleFS, "/index.html");
|
||||||
|
server.serveStatic("/icon.svg", LittleFS, "/icon.svg");
|
||||||
|
|
||||||
|
server.on("/set", httpSet);
|
||||||
|
server.on("/set/", httpSet);
|
||||||
|
server.on("/off", httpOff);
|
||||||
|
server.on("/off/", httpOff);
|
||||||
|
|
||||||
|
server.on("/upload/index", HTTP_POST, [] { server.send(200); }, [] { httpUpload("index.html"); });
|
||||||
|
server.on("/upload/icon", HTTP_POST, [] { server.send(200); }, [] { httpUpload("icon.svg"); });
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/http.h
Normal file
10
src/http.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef HTTP_H
|
||||||
|
#define HTTP_H
|
||||||
|
|
||||||
|
void httpSetup();
|
||||||
|
|
||||||
|
void httpLoop();
|
||||||
|
|
||||||
|
void httpStop();
|
||||||
|
|
||||||
|
#endif
|
||||||
28
src/io.cpp
28
src/io.cpp
@ -1,6 +1,32 @@
|
|||||||
#include "io.h"
|
#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 relay0(1, "RELAY #0", 12, false, true);
|
||||||
|
|
||||||
|
Relay relay1(2, "RELAY #1", 5, false, true);
|
||||||
|
|
||||||
|
Relay relay2(3, "RELAY #2", 4, false, true);
|
||||||
|
|
||||||
|
Relay relay3(4, "RELAY #3", 15, false, true);
|
||||||
|
|
||||||
|
Output status("Status", STATUS_PIN, STATUS_INVERT, false);
|
||||||
|
|
||||||
|
Button button0(0, true, true, [](const ButtonEvent event) { buttonCallback(relay0, event); });
|
||||||
|
|
||||||
|
Button button1(9, true, true, [](const ButtonEvent event) { buttonCallback(relay1, event); });
|
||||||
|
|
||||||
|
Button button2(10, true, true, [](const ButtonEvent event) { buttonCallback(relay2, event); });
|
||||||
|
|
||||||
|
Button button3(14, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); });
|
||||||
|
|
||||||
|
void buttonCallback(Output &output, const ButtonEvent event) {
|
||||||
if (event == BUTTON_PRESSED) {
|
if (event == BUTTON_PRESSED) {
|
||||||
output.toggle();
|
output.toggle();
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/io.h
32
src/io.h
@ -1,55 +1,55 @@
|
|||||||
#ifndef IO_H
|
#ifndef IO_H
|
||||||
#define IO_H
|
#define IO_H
|
||||||
|
|
||||||
|
#include "Relay.h"
|
||||||
#include "Button.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 relay0;
|
||||||
|
|
||||||
inline Output relay2("RELAY #2", 5, false, true);
|
extern Relay relay1;
|
||||||
|
|
||||||
inline Output relay3("RELAY #3", 4, false, true);
|
extern Relay relay2;
|
||||||
|
|
||||||
inline Output relay4("RELAY #4", 15, false, true);
|
extern Relay relay3;
|
||||||
|
|
||||||
inline Output status("Status", 13, true, false);
|
extern Output status;
|
||||||
|
|
||||||
inline Button button1(0, true, true, [](const ButtonEvent event) { buttonCallback(relay1, event); });
|
extern Button button0;
|
||||||
|
|
||||||
inline Button button2(9, true, true, [](const ButtonEvent event) { buttonCallback(relay2, event); });
|
extern Button button1;
|
||||||
|
|
||||||
inline Button button3(10, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); });
|
extern Button button2;
|
||||||
|
|
||||||
inline Button button4(14, true, true, [](const ButtonEvent event) { buttonCallback(relay4, event); });
|
extern Button button3;
|
||||||
|
|
||||||
inline void ioSetup() {
|
inline void ioSetup() {
|
||||||
|
button0.setup();
|
||||||
button1.setup();
|
button1.setup();
|
||||||
button2.setup();
|
button2.setup();
|
||||||
button3.setup();
|
button3.setup();
|
||||||
button4.setup();
|
|
||||||
|
|
||||||
status.setup();
|
status.setup();
|
||||||
|
|
||||||
|
relay0.setup();
|
||||||
relay1.setup();
|
relay1.setup();
|
||||||
relay2.setup();
|
relay2.setup();
|
||||||
relay3.setup();
|
relay3.setup();
|
||||||
relay4.setup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void ioLoop() {
|
inline void ioLoop() {
|
||||||
|
button0.loop();
|
||||||
button1.loop();
|
button1.loop();
|
||||||
button2.loop();
|
button2.loop();
|
||||||
button3.loop();
|
button3.loop();
|
||||||
button4.loop();
|
|
||||||
|
|
||||||
status.loop();
|
status.loop();
|
||||||
|
|
||||||
relay1.loop();
|
relay0.loop();
|
||||||
|
relay0.loop();
|
||||||
relay2.loop();
|
relay2.loop();
|
||||||
relay3.loop();
|
relay3.loop();
|
||||||
relay4.loop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -10,11 +10,13 @@ void setup() {
|
|||||||
Serial.print("\n\n\nStartup!\n");
|
Serial.print("\n\n\nStartup!\n");
|
||||||
|
|
||||||
configSetup();
|
configSetup();
|
||||||
|
|
||||||
ioSetup();
|
ioSetup();
|
||||||
wifiSetup();
|
wifiSetup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
wifiLoop();
|
||||||
ioLoop();
|
ioLoop();
|
||||||
ArduinoOTA.handle();
|
ArduinoOTA.handle();
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/wifi.cpp
100
src/wifi.cpp
@ -1,63 +1,101 @@
|
|||||||
#include "wifi.h"
|
#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 <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
|
|
||||||
#include "config.h"
|
bool wifiConnected = false;
|
||||||
#include "io.h"
|
|
||||||
|
|
||||||
#define DEFAULT_HOSTNAME "PatrixSonoff4ChPro"
|
unsigned long wifiLast = 0;
|
||||||
#define DEFAULT_WIFI_SSID "HappyNet"
|
|
||||||
#define DEFAULT_WIFI_PASS "1Grausame!Sackratte7"
|
void wifiConnect() {
|
||||||
|
WiFi.disconnect();
|
||||||
|
WiFi.enableAP(false);
|
||||||
|
WiFi.setAutoConnect(false);
|
||||||
|
WiFi.setAutoReconnect(false);
|
||||||
|
yield();
|
||||||
|
|
||||||
|
status.cycle(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) {
|
void wifiChangeHostname(const char *hostname) {
|
||||||
if (configWriteString("hostname", DEFAULT_HOSTNAME, hostname)) {
|
if (configWrite(CONFIG_HOSTNAME, DEFAULT_HOSTNAME, hostname)) {
|
||||||
WiFi.setHostname(hostname);
|
WiFi.setHostname(hostname);
|
||||||
Serial.printf("Changed hostname to: %s\n", hostname);
|
Serial.printf("[WiFi] hostname: %s\n", hostname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifiChangeSSID(const char *ssid) {
|
void wifiChangeSSID(const char *ssid) {
|
||||||
if (configWriteString("wifiSSID", DEFAULT_WIFI_SSID, ssid)) {
|
if (configWrite(CONFIG_WIFI_SSID, DEFAULT_WIFI_SSID, ssid)) {
|
||||||
Serial.printf("Changed SSID to: %s\n", ssid);
|
Serial.printf("[WiFi] SSID: %s\n", ssid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifiChangePassword(const char *password) {
|
void wifiChangePassword(const char *password) {
|
||||||
configWriteString("wifiPass", DEFAULT_WIFI_PASS, password);
|
configWrite(CONFIG_WIFI_PASSWORD, DEFAULT_WIFI_PASSWORD, password);
|
||||||
Serial.printf("Changed password\n");
|
Serial.printf("[WiFi] password: ***");
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifiSetup() {
|
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([] {
|
ArduinoOTA.onStart([] {
|
||||||
Serial.println("OTA begin...");
|
Serial.println("[OTA] Begin!");
|
||||||
status.set(true);
|
status.set(true);
|
||||||
});
|
});
|
||||||
ArduinoOTA.onProgress([](const unsigned progress, const unsigned total) {
|
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();
|
status.toggle();
|
||||||
});
|
});
|
||||||
ArduinoOTA.onEnd([] {
|
ArduinoOTA.onEnd([] {
|
||||||
Serial.println("\nOTA success!");
|
Serial.println("\n[OTA] Success!");
|
||||||
status.set(true);
|
status.set(true);
|
||||||
});
|
});
|
||||||
ArduinoOTA.onError([](const ota_error_t error) {
|
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);
|
status.set(false);
|
||||||
});
|
});
|
||||||
ArduinoOTA.begin();
|
|
||||||
delay(1000);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,4 +9,6 @@ void wifiChangePassword(const char *password);
|
|||||||
|
|
||||||
void wifiSetup();
|
void wifiSetup();
|
||||||
|
|
||||||
|
void wifiLoop();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user