Compare commits
32 Commits
WORKING-20
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| de21cff76c | |||
| c390829f35 | |||
| 9bebffc743 | |||
| 5ca6425918 | |||
| 9b7b08e9cb | |||
| 556f277016 | |||
| 2652de5cb8 | |||
| a3076b9b2f | |||
| b8d79dbc67 | |||
| d4282d3fba | |||
| 8024c2ddcd | |||
| ccc9eea312 | |||
| 465d4e0624 | |||
| 1336d3103f | |||
| 46fcc4b872 | |||
| f72c04b9b3 | |||
| 951d15ac81 | |||
| 7700e9bd86 | |||
| 0215f2ab94 | |||
| 0eb51d8ad6 | |||
| c4d30371cf | |||
| 7aa39483de | |||
| 1b1f295b23 | |||
| 177cb68987 | |||
| b6ea584a4b | |||
| 801b99a3a7 | |||
| 4e5fff2498 | |||
| b88df843ad | |||
| ae1c6f5cda | |||
| 71490427f2 | |||
| e604f90a14 | |||
| 194da1a78b |
180
index.html
180
index.html
@ -1,8 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
|
<meta charset="UTF-8">
|
||||||
<head>
|
<head>
|
||||||
<title id="title"></title>
|
<title id="title"></title>
|
||||||
<link rel="icon" type="image/svg" href="icon.svg">
|
<link rel="icon" type="image/svg" href="icon.svg">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@ -27,7 +29,7 @@
|
|||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input[type=text], input[type=number], select {
|
||||||
all: unset;
|
all: unset;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -55,29 +57,59 @@
|
|||||||
.state {
|
.state {
|
||||||
}
|
}
|
||||||
|
|
||||||
.countdown {
|
.topic {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stateOn {
|
.stateOn {
|
||||||
background-color: palegreen;
|
background-color: #7aca7a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stateOff {
|
.stateOff {
|
||||||
background-color: indianred;
|
background-color: #ae4d4d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switchOn {
|
.switchOn {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
filter: brightness(50%);
|
||||||
background-color: palegreen;
|
background-color: palegreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switchOn_Active {
|
||||||
|
z-index: 9999;
|
||||||
|
filter: brightness(120%);
|
||||||
|
box-shadow: 0 0 5px #006400, 0 0 10px #228B22, 0 0 20px #32CD32;
|
||||||
|
}
|
||||||
|
|
||||||
.switchOff {
|
.switchOff {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
filter: brightness(50%);
|
||||||
background-color: indianred;
|
background-color: indianred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switchOff_Active {
|
||||||
|
z-index: 9999;
|
||||||
|
filter: brightness(120%);
|
||||||
|
box-shadow: 0 0 5px #8B0000, 0 0 10px #B22222, 0 0 20px #FF4500;
|
||||||
|
}
|
||||||
|
|
||||||
.switchCycle {
|
.switchCycle {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
filter: brightness(50%);
|
||||||
background-color: lightskyblue;
|
background-color: lightskyblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switchCycle_Active {
|
||||||
|
z-index: 9999;
|
||||||
|
filter: brightness(120%);
|
||||||
|
box-shadow: 0 0 5px #00008B, 0 0 10px #1E90FF, 0 0 20px #00BFFF;
|
||||||
|
}
|
||||||
|
|
||||||
.config {
|
.config {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
@ -87,10 +119,19 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.initial {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adminHidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1000px) {
|
@media (min-width: 1000px) {
|
||||||
body {
|
body {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.relayBox {
|
.relayBox {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
@ -102,18 +143,26 @@
|
|||||||
|
|
||||||
<div id="relayList"></div>
|
<div id="relayList"></div>
|
||||||
|
|
||||||
|
<button id="admin" onclick="toggleAdmin()">Admin</button>
|
||||||
|
|
||||||
|
<div id="gridPowerDelta"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
let admin = false;
|
||||||
|
|
||||||
|
function toggleAdmin() {
|
||||||
|
admin = !admin;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
const title = document.getElementById("title");
|
const title = document.getElementById("title");
|
||||||
|
const gridPowerDelta = document.getElementById("gridPowerDelta");
|
||||||
const relayList = document.getElementById("relayList");
|
const relayList = document.getElementById("relayList");
|
||||||
|
|
||||||
function getUrl(path) {
|
function getUrl(path) {
|
||||||
return `http://10.42.0.204/${path}`;
|
return `http://10.42.0.204/${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setState(index, value) {
|
|
||||||
set("state", index, value ? 'true' : 'false');
|
|
||||||
}
|
|
||||||
|
|
||||||
function set(key, index, value) {
|
function set(key, index, value) {
|
||||||
request(`${key}${index}=${encodeURIComponent(value)}`);
|
request(`${key}${index}=${encodeURIComponent(value)}`);
|
||||||
}
|
}
|
||||||
@ -122,21 +171,20 @@
|
|||||||
|
|
||||||
function updateValue(tag, clazz, innerTag, value) {
|
function updateValue(tag, clazz, innerTag, value) {
|
||||||
const input = tag.getElementsByClassName(clazz)[0].getElementsByTagName(innerTag)[0];
|
const input = tag.getElementsByClassName(clazz)[0].getElementsByTagName(innerTag)[0];
|
||||||
if (document.activeElement !== input) {
|
if (input.type === "checkbox") {
|
||||||
|
input.checked = value;
|
||||||
|
} else if (document.activeElement !== input) {
|
||||||
input.value = value;
|
input.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateState(relayTag, state) {
|
function updateState(relayTag, state) {
|
||||||
const tag = relayTag.getElementsByClassName("state")[0];
|
|
||||||
if (state) {
|
if (state) {
|
||||||
tag.innerText = "Ein";
|
relayTag.classList.add("stateOn");
|
||||||
tag.classList.add("stateOn");
|
relayTag.classList.remove("stateOff");
|
||||||
tag.classList.remove("stateOff");
|
|
||||||
} else {
|
} else {
|
||||||
tag.innerText = "Aus";
|
relayTag.classList.add("stateOff");
|
||||||
tag.classList.add("stateOff");
|
relayTag.classList.remove("stateOn");
|
||||||
tag.classList.remove("stateOn");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,10 +194,12 @@
|
|||||||
const DAY = (24 * HOUR);
|
const DAY = (24 * HOUR);
|
||||||
|
|
||||||
function countdownString(relayData, millis) {
|
function countdownString(relayData, millis) {
|
||||||
const rest = Math.ceil((millis - relayData.stateMillis - (Date.now() - dataAge)) / SECOND) * SECOND;
|
const rest = Math.ceil((millis - relayData.stateAgeMillis - (Date.now() - dataAge)) / SECOND) * SECOND;
|
||||||
if (millis <= 0 || (relayData.onCount === 0 && !relayData.state)) {
|
const cycle = relayData.onCount !== 0 && relayData.onMillis > 0 && relayData.offMillis > 0;
|
||||||
|
if (millis <= 0 || (!cycle && !relayData.state)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const days = rest / DAY;
|
const days = rest / DAY;
|
||||||
const hours = rest / HOUR;
|
const hours = rest / HOUR;
|
||||||
const minutes = rest / MINUTE;
|
const minutes = rest / MINUTE;
|
||||||
@ -189,30 +239,61 @@
|
|||||||
timeout = setTimeout(() => request(), 2000);
|
timeout = setTimeout(() => request(), 2000);
|
||||||
|
|
||||||
const r = new XMLHttpRequest();
|
const r = new XMLHttpRequest();
|
||||||
r.open("GET", getUrl(`set?${query}`));
|
r.open("GET", getUrl(`status?${query}`));
|
||||||
r.onreadystatechange = () => {
|
r.onreadystatechange = () => {
|
||||||
if (r.readyState === 4 && r.status === 200) {
|
if (r.readyState === 4 && r.status === 200) {
|
||||||
data = JSON.parse(r.response);
|
data = JSON.parse(r.response);
|
||||||
dataAge = Date.now();
|
dataAge = Date.now();
|
||||||
title.innerText = data.hostname;
|
title.innerText = data.wifi.hostname;
|
||||||
|
gridPowerDelta.innerText = "gridPowerDelta=" + data.gridPowerDeltaValue + "W";
|
||||||
for (let index = 0; index < data.relays.length; index++) {
|
for (let index = 0; index < data.relays.length; index++) {
|
||||||
const relayData = data.relays[index];
|
const relayData = data.relays[index];
|
||||||
const relay = document.getElementById("relay" + index) || create(index);
|
const relayTag = document.getElementById("relay" + index) || create(index);
|
||||||
updateValue(relay, "name", "input", relayData.name);
|
updateValue(relayTag, "name", "input", relayData.name);
|
||||||
updateState(relay, relayData.state);
|
updateValue(relayTag, "topic", "input", relayData.topic);
|
||||||
updateCountdown(relay, relayData);
|
updateState(relayTag, relayData.state);
|
||||||
updateValue(relay, "onMillis", "input", relayData.onMillis);
|
updateCountdown(relayTag, relayData);
|
||||||
updateValue(relay, "offMillis", "input", relayData.offMillis);
|
updateValue(relayTag, "onMillis", "input", relayData.onMillis);
|
||||||
updateValue(relay, "initial", "select", relayData.initial);
|
updateValue(relayTag, "offMillis", "input", relayData.offMillis);
|
||||||
|
updateValue(relayTag, "initial", "select", relayData.initial);
|
||||||
|
updateValue(relayTag, "gridPowerDeltaOnEnabled", "input", relayData.gridPowerDeltaOnEnabled);
|
||||||
|
updateValue(relayTag, "gridPowerDeltaOnThreshold", "input", relayData.gridPowerDeltaOnThreshold);
|
||||||
|
updateValue(relayTag, "gridPowerDeltaOnDelay", "input", relayData.gridPowerDeltaOnDelay);
|
||||||
|
updateValue(relayTag, "gridPowerDeltaOffEnabled", "input", relayData.gridPowerDeltaOffEnabled);
|
||||||
|
updateValue(relayTag, "gridPowerDeltaOffThreshold", "input", relayData.gridPowerDeltaOffThreshold);
|
||||||
|
updateValue(relayTag, "gridPowerDeltaOffDelay", "input", relayData.gridPowerDeltaOffDelay);
|
||||||
|
|
||||||
|
const cycle = relayData.onCount !== 0 && relayData.onMillis > 0 && relayData.offMillis > 0;
|
||||||
|
|
||||||
|
const switchOn = relayTag.getElementsByClassName("switchOn")[0];
|
||||||
|
if (!cycle && relayData.state) {
|
||||||
|
switchOn.classList.add("switchOn_Active");
|
||||||
|
} else {
|
||||||
|
switchOn.classList.remove("switchOn_Active");
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchOff = relayTag.getElementsByClassName("switchOff")[0];
|
||||||
|
if (!cycle && !relayData.state) {
|
||||||
|
switchOff.classList.add("switchOff_Active");
|
||||||
|
} else {
|
||||||
|
switchOff.classList.remove("switchOff_Active");
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchCycle = relayTag.getElementsByClassName("switchCycle")[0];
|
||||||
|
if (cycle) {
|
||||||
|
switchCycle.classList.add("switchCycle_Active");
|
||||||
|
} else {
|
||||||
|
switchCycle.classList.remove("switchCycle_Active");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.send();
|
r.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
function newDiv(parent, name) {
|
function newDiv(parent, clazz) {
|
||||||
const div = document.createElement("div")
|
const div = document.createElement("div")
|
||||||
div.className = name;
|
div.className = clazz;
|
||||||
parent.append(div);
|
parent.append(div);
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
@ -228,6 +309,22 @@
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newCheckbox(relayIndex, parent, clazz, name, text) {
|
||||||
|
const div = newDiv(parent, clazz);
|
||||||
|
|
||||||
|
const label = document.createElement("label")
|
||||||
|
label.onchange = () => set(name, relayIndex, input.checked);
|
||||||
|
div.append(label);
|
||||||
|
|
||||||
|
const input = document.createElement("input")
|
||||||
|
input.type = "checkbox";
|
||||||
|
input.onchange = () => set(name, relayIndex, input.value);
|
||||||
|
label.append(input);
|
||||||
|
label.append(text);
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
function newButton(relayIndex, parent, clazz, key, value, text) {
|
function newButton(relayIndex, parent, clazz, key, value, text) {
|
||||||
const div = newDiv(parent, clazz);
|
const div = newDiv(parent, clazz);
|
||||||
|
|
||||||
@ -269,17 +366,29 @@
|
|||||||
const header = newDiv(relay, "flex");
|
const header = newDiv(relay, "flex");
|
||||||
newInput(relayIndex, header, "name", "name", "text");
|
newInput(relayIndex, header, "name", "name", "text");
|
||||||
newDiv(header, "countdown");
|
newDiv(header, "countdown");
|
||||||
newDiv(header, "state");
|
|
||||||
|
|
||||||
const config = newDiv(relay, "flex");
|
const topicDiv = newDiv(relay, "flex admin adminHidden");
|
||||||
|
newInput(relayIndex, topicDiv, "topic", "topic", "text");
|
||||||
|
|
||||||
|
const config = newDiv(relay, "flex admin adminHidden");
|
||||||
newInput(relayIndex, config, "config onMillis", "onMillis", "number");
|
newInput(relayIndex, config, "config onMillis", "onMillis", "number");
|
||||||
newInput(relayIndex, config, "config offMillis", "offMillis", "number");
|
newInput(relayIndex, config, "config offMillis", "offMillis", "number");
|
||||||
newSelect(relayIndex, config, "config initial", "initial", [["OFF", "Init: Aus"], ["ON", "Init: Ein"], ["CYCLE", "Init: Zyklus"]]);
|
newSelect(relayIndex, config, "config initial", "initial", [["OFF", "Init: Aus"], ["ON", "Init: Ein"], ["CYCLE", "Init: Zyklus"]]);
|
||||||
|
|
||||||
|
const gridPowerDeltaOn = newDiv(relay, "flex admin adminHidden");
|
||||||
|
newCheckbox(relayIndex, gridPowerDeltaOn, "config gridPowerDeltaOnEnabled", "gridPowerDeltaOnEnabled", "Überschuss EIN");
|
||||||
|
newInput(relayIndex, gridPowerDeltaOn, "config gridPowerDeltaOnThreshold", "gridPowerDeltaOnThreshold", "number");
|
||||||
|
newInput(relayIndex, gridPowerDeltaOn, "config gridPowerDeltaOnDelay", "gridPowerDeltaOnDelay", "number");
|
||||||
|
|
||||||
|
const gridPowerDeltaOff = newDiv(relay, "flex admin adminHidden");
|
||||||
|
newCheckbox(relayIndex, gridPowerDeltaOff, "config gridPowerDeltaOffEnabled", "gridPowerDeltaOffEnabled", "Defizit AUS");
|
||||||
|
newInput(relayIndex, gridPowerDeltaOff, "config gridPowerDeltaOffThreshold", "gridPowerDeltaOffThreshold", "number");
|
||||||
|
newInput(relayIndex, gridPowerDeltaOff, "config gridPowerDeltaOffDelay", "gridPowerDeltaOffDelay", "number");
|
||||||
|
|
||||||
const switches = newDiv(relay, "flex");
|
const switches = newDiv(relay, "flex");
|
||||||
|
newButton(relayIndex, switches, "switch switchCycle", "onCount", -1, "Zyklus");
|
||||||
newButton(relayIndex, switches, "switch switchOn", "state", "true", "Ein");
|
newButton(relayIndex, switches, "switch switchOn", "state", "true", "Ein");
|
||||||
newButton(relayIndex, switches, "switch switchOff", "state", "false", "Aus");
|
newButton(relayIndex, switches, "switch switchOff", "state", "false", "Aus");
|
||||||
newButton(relayIndex, switches, "switch switchCycle", "onCount", -1, "Zyklus");
|
|
||||||
return relay;
|
return relay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +402,13 @@
|
|||||||
const relayData = data.relays[index];
|
const relayData = data.relays[index];
|
||||||
const relayTag = document.getElementById("relay" + index) || create(index);
|
const relayTag = document.getElementById("relay" + index) || create(index);
|
||||||
updateCountdown(relayTag, relayData);
|
updateCountdown(relayTag, relayData);
|
||||||
|
for (let element of document.getElementsByClassName("admin")) {
|
||||||
|
if (admin) {
|
||||||
|
element.classList.remove("adminHidden");
|
||||||
|
} else {
|
||||||
|
element.classList.add("adminHidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
113
platformio.ini
113
platformio.ini
@ -1,19 +1,104 @@
|
|||||||
[env:Sonoff4ChPro]
|
[common]
|
||||||
|
framework = arduino
|
||||||
|
upload_speed = 921600
|
||||||
|
monitor_speed = 115200
|
||||||
|
build.filesystem = littlefs
|
||||||
|
lib_deps = bblanchon/ArduinoJson @ 7.4.2
|
||||||
|
knolleary/PubSubClient
|
||||||
|
|
||||||
|
[ESP8285]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
board = esp8285
|
board = esp8285
|
||||||
framework = arduino
|
framework = ${common.framework}
|
||||||
upload_speed = 921600
|
upload_speed = ${common.upload_speed}
|
||||||
upload_port = 10.0.0.178
|
monitor_speed = ${common.monitor_speed}
|
||||||
monitor_speed = 115200
|
build.filesystem = ${common.build.filesystem}
|
||||||
build.filesystem = littlefs
|
lib_deps = ${common.lib_deps}
|
||||||
lib_deps = bblanchon/ArduinoJson @ 7.4.2
|
board_build.ldscript = eagle.flash.1m64.ld
|
||||||
|
|
||||||
[env:ESP32Test]
|
[Ch4Pro]
|
||||||
|
platform = ${ESP8285.platform}
|
||||||
|
board = ${ESP8285.board}
|
||||||
|
framework = ${ESP8285.framework}
|
||||||
|
upload_speed = ${ESP8285.upload_speed}
|
||||||
|
monitor_speed = ${ESP8285.monitor_speed}
|
||||||
|
build.filesystem = ${ESP8285.build.filesystem}
|
||||||
|
lib_deps = ${ESP8285.lib_deps}
|
||||||
|
board_build.ldscript = ${ESP8285.board_build.ldscript}
|
||||||
|
build_flags = -D Ch4Pro
|
||||||
|
|
||||||
|
[SP111]
|
||||||
|
platform = ${ESP8285.platform}
|
||||||
|
board = ${ESP8285.board}
|
||||||
|
framework = ${ESP8285.framework}
|
||||||
|
upload_speed = ${ESP8285.upload_speed}
|
||||||
|
monitor_speed = ${ESP8285.monitor_speed}
|
||||||
|
build.filesystem = ${ESP8285.build.filesystem}
|
||||||
|
lib_deps = ${ESP8285.lib_deps}
|
||||||
|
board_build.ldscript = ${ESP8285.board_build.ldscript}
|
||||||
|
build_flags = -D SP111
|
||||||
|
|
||||||
|
[ESP32_TEST]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = ${common.framework}
|
||||||
upload_speed = 921600
|
upload_speed = ${common.upload_speed}
|
||||||
monitor_speed = 115200
|
monitor_speed = ${common.monitor_speed}
|
||||||
build.filesystem = littlefs
|
build_type = debug
|
||||||
lib_deps = bblanchon/ArduinoJson @ 7.4.2
|
debug_tool = esp-prog
|
||||||
build_flags = -D STATUS_PIN=2 -D STATUS_INVERT=false -D CORE_DEBUG_LEVEL=0
|
monitor_filters = esp32_exception_decoder
|
||||||
|
build.filesystem = ${common.build.filesystem}
|
||||||
|
lib_deps = ${common.lib_deps}
|
||||||
|
build_flags = -D ESP32_TESTBOARD -D CORE_DEBUG_LEVEL=0
|
||||||
|
|
||||||
|
[env:ESP32_Ch4Pro]
|
||||||
|
platform = ${ESP32_TEST.platform}
|
||||||
|
board = ${ESP32_TEST.board}
|
||||||
|
framework = ${ESP32_TEST.framework}
|
||||||
|
upload_speed = ${ESP32_TEST.upload_speed}
|
||||||
|
monitor_speed = ${ESP32_TEST.monitor_speed}
|
||||||
|
build_type = ${ESP32_TEST.build_type}
|
||||||
|
debug_tool = ${ESP32_TEST.debug_tool}
|
||||||
|
monitor_filters = ${ESP32_TEST.monitor_filters}
|
||||||
|
build.filesystem = ${ESP32_TEST.build.filesystem}
|
||||||
|
lib_deps = ${ESP32_TEST.lib_deps}
|
||||||
|
build_flags = ${Ch4Pro.build_flags} -D WIFI_HOSTNAME_FALLBACK=\"ESP32_Test_Ch4Pro\" ${ESP32_TEST.build_flags}
|
||||||
|
|
||||||
|
[env:ESP32_SP111]
|
||||||
|
platform = ${ESP32_TEST.platform}
|
||||||
|
board = ${ESP32_TEST.board}
|
||||||
|
framework = ${ESP32_TEST.framework}
|
||||||
|
upload_speed = ${ESP32_TEST.upload_speed}
|
||||||
|
monitor_speed = ${ESP32_TEST.monitor_speed}
|
||||||
|
build_type = ${ESP32_TEST.build_type}
|
||||||
|
debug_tool = ${ESP32_TEST.debug_tool}
|
||||||
|
monitor_filters = ${ESP32_TEST.monitor_filters}
|
||||||
|
build.filesystem = ${ESP32_TEST.build.filesystem}
|
||||||
|
lib_deps = ${ESP32_TEST.lib_deps}
|
||||||
|
build_flags = ${SP111.build_flags} -D WIFI_HOSTNAME_FALLBACK=\"ESP32_Test_SP111\" ${ESP32_TEST.build_flags}
|
||||||
|
|
||||||
|
[env:Gewaechshaus]
|
||||||
|
platform = ${Ch4Pro.platform}
|
||||||
|
board = ${Ch4Pro.board}
|
||||||
|
framework = ${Ch4Pro.framework}
|
||||||
|
upload_speed = ${Ch4Pro.upload_speed}
|
||||||
|
monitor_speed = ${Ch4Pro.monitor_speed}
|
||||||
|
build.filesystem = ${Ch4Pro.build.filesystem}
|
||||||
|
lib_deps = ${Ch4Pro.lib_deps}
|
||||||
|
board_build.ldscript = ${Ch4Pro.board_build.ldscript}
|
||||||
|
build_flags = ${Ch4Pro.build_flags} -D WIFI_HOSTNAME_FALLBACK=\"Gewaechshaus\"
|
||||||
|
upload_protocol = espota
|
||||||
|
upload_port = 10.0.0.178
|
||||||
|
|
||||||
|
[env:Infrarotheizung]
|
||||||
|
platform = ${SP111.platform}
|
||||||
|
board = ${SP111.board}
|
||||||
|
framework = ${SP111.framework}
|
||||||
|
upload_speed = ${SP111.upload_speed}
|
||||||
|
monitor_speed = ${SP111.monitor_speed}
|
||||||
|
build.filesystem = ${SP111.build.filesystem}
|
||||||
|
lib_deps = ${SP111.lib_deps}
|
||||||
|
board_build.ldscript = ${SP111.board_build.ldscript}
|
||||||
|
build_flags = ${SP111.build_flags} -D WIFI_HOSTNAME_FALLBACK=\"Infrarotheizung\"
|
||||||
|
upload_protocol = espota
|
||||||
|
upload_port = 10.0.0.179
|
||||||
|
|||||||
54
src/Output.h
54
src/Output.h
@ -27,14 +27,20 @@ protected:
|
|||||||
|
|
||||||
unsigned long stateMillis = 0;
|
unsigned long stateMillis = 0;
|
||||||
|
|
||||||
void _write(const bool state) {
|
virtual void publish() {
|
||||||
if (state != get()) {
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
void _write(const bool wanted) {
|
||||||
|
const auto current = get();
|
||||||
|
if (wanted != current) {
|
||||||
|
digitalWrite(pin, wanted ^ inverted ? HIGH : LOW);
|
||||||
|
publish();
|
||||||
if (logState) {
|
if (logState) {
|
||||||
Serial.printf("[RELAY] \"%s\" = %s\n", name.c_str(), state ? "ON" : "OFF");
|
Serial.printf("[RELAY] \"%s\": %s\n", name.c_str(), wanted ? "ON" : "OFF");
|
||||||
}
|
}
|
||||||
stateMillis = millis();
|
stateMillis = millis();
|
||||||
}
|
}
|
||||||
digitalWrite(pin, state ^ inverted ? HIGH : LOW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applyInitial() {
|
void _applyInitial() {
|
||||||
@ -63,9 +69,9 @@ public:
|
|||||||
return (digitalRead(pin) == HIGH) ^ inverted;
|
return (digitalRead(pin) == HIGH) ^ inverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(const bool state) {
|
virtual void set(const bool state) {
|
||||||
_write(state);
|
|
||||||
onCount = 0;
|
onCount = 0;
|
||||||
|
_write(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggle() {
|
void toggle() {
|
||||||
@ -73,19 +79,21 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cycle(const unsigned long onMillis_, const unsigned long offMillis_, const unsigned long onCount_ = -1) {
|
void cycle(const unsigned long onMillis_, const unsigned long offMillis_, const unsigned long onCount_ = -1) {
|
||||||
|
set(false); // this sets onCount=0, so do this first
|
||||||
this->onMillis = onMillis_;
|
this->onMillis = onMillis_;
|
||||||
this->offMillis = offMillis_;
|
this->offMillis = offMillis_;
|
||||||
this->onCount = onCount_;
|
this->onCount = onCount_;
|
||||||
set(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
virtual void loop() {
|
||||||
if (get()) {
|
const auto status = get();
|
||||||
if (onMillis > 0 && millis() - stateMillis > onMillis) {
|
const auto ageMillis = millis() - stateMillis;
|
||||||
|
if (status) {
|
||||||
|
if (onMillis > 0 && ageMillis >= onMillis) {
|
||||||
_write(false);
|
_write(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (offMillis > 0 && millis() - stateMillis > offMillis && onCount != 0) {
|
if (offMillis > 0 && ageMillis >= offMillis && onCount != 0) {
|
||||||
_write(true);
|
_write(true);
|
||||||
if (onCount > 0) {
|
if (onCount > 0) {
|
||||||
onCount--;
|
onCount--;
|
||||||
@ -114,30 +122,6 @@ public:
|
|||||||
offMillis = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
189
src/Relay.h
189
src/Relay.h
@ -3,46 +3,164 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "Output.h"
|
#include "Output.h"
|
||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
class Relay final : public Output {
|
class Relay final : public Output {
|
||||||
|
|
||||||
String nameFallback;
|
String nameFallback;
|
||||||
|
|
||||||
|
String topicFallback;
|
||||||
|
|
||||||
const uint8_t index;
|
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 gridPowerDeltaAlarmSince = 0;
|
||||||
|
|
||||||
public:
|
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) {
|
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 {
|
void setup() override {
|
||||||
Output::setup();
|
Output::setup();
|
||||||
Output::setName(configRead(path("name"), nameFallback));
|
|
||||||
Output::setInitial(configRead(path("initial"), INITIAL_OFF));
|
Output::setName(loadString(path("name"), nameFallback));
|
||||||
Output::setOnMillis(configRead(path("onMillis"), 0L));
|
Output::setInitial(loadInitial(path("initial"), INITIAL_OFF));
|
||||||
Output::setOffMillis(configRead(path("offMillis"), 0L));
|
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();
|
_applyInitial();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
Output::loop();
|
||||||
|
doGridPowerDelta();
|
||||||
|
}
|
||||||
|
|
||||||
void setName(const String &value) override {
|
void setName(const String &value) override {
|
||||||
Output::setName(value);
|
Output::setName(value);
|
||||||
configWrite(path("name"), nameFallback, value, false);
|
storeString(path("name"), nameFallback, value);
|
||||||
|
publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInitial(const Initial value) override {
|
void setInitial(const Initial value) override {
|
||||||
Output::setInitial(value);
|
Output::setInitial(value);
|
||||||
configWrite(path("initial"), INITIAL_OFF, value);
|
storeInitial(path("initial"), INITIAL_OFF, value);
|
||||||
|
publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOnMillis(const unsigned long value) override {
|
void setOnMillis(const unsigned long value) override {
|
||||||
Output::setOnMillis(value);
|
Output::setOnMillis(value);
|
||||||
configWrite(path("onMillis"), 0L, value);
|
storeLong(path("onMillis"), 0L, value);
|
||||||
|
publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOffMillis(const unsigned long value) override {
|
void setOffMillis(const unsigned long value) override {
|
||||||
Output::setOffMillis(value);
|
Output::setOffMillis(value);
|
||||||
configWrite(path("offMillis"), 0L, value);
|
storeLong(path("offMillis"), 0L, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTopic(const String &value) {
|
||||||
|
topic = value;
|
||||||
|
storeString(path("topic"), topicFallback, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridPowerDeltaOnEnabled(const bool value) {
|
||||||
|
gridPowerDeltaOnEnabled = value;
|
||||||
|
storeBool(path("gridPowerDeltaOnEnabled"), false, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridPowerDeltaOnThreshold(const long value) {
|
||||||
|
gridPowerDeltaOnThreshold = value;
|
||||||
|
storeLong(path("gridPowerDeltaOnThreshold"), 0L, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridPowerDeltaOnDelay(const long value) {
|
||||||
|
gridPowerDeltaOnDelay = value;
|
||||||
|
storeLong(path("gridPowerDeltaOnDelay"), 0L, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridPowerDeltaOffEnabled(const bool value) {
|
||||||
|
gridPowerDeltaOffEnabled = value;
|
||||||
|
storeBool(path("gridPowerDeltaOffEnabled"), false, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridPowerDeltaOffThreshold(const long value) {
|
||||||
|
gridPowerDeltaOffThreshold = value;
|
||||||
|
storeLong(path("gridPowerDeltaOffThreshold"), 0L, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGridPowerDeltaOffDelay(const long value) {
|
||||||
|
gridPowerDeltaOffDelay = value;
|
||||||
|
storeLong(path("gridPowerDeltaOffDelay"), 0L, value);
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
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["gridPowerDeltaAlarmSince"] = gridPowerDeltaAlarmSince;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
gridPowerDeltaAlarmSince = 0;
|
||||||
|
Output::set(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void publish() override {
|
||||||
|
JsonDocument doc;
|
||||||
|
json(doc.to<JsonObject>());
|
||||||
|
mqttPublish(topic, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -53,6 +171,59 @@ private:
|
|||||||
return String(path);
|
return String(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void doGridPowerDelta() {
|
||||||
|
if (get()) {
|
||||||
|
gridPowerDeltaOff();
|
||||||
|
} else {
|
||||||
|
gridPowerDeltaOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gridPowerDeltaConfigInvalid() {
|
||||||
|
return gridPowerDeltaOffThreshold < gridPowerDeltaOnThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gridPowerDeltaOff() {
|
||||||
|
if (!gridPowerDeltaOffEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto now = max(1UL, millis());
|
||||||
|
const auto invalid = isnan(gridPowerDeltaValue) || now - gridPowerDeltaMillis > 10000 || gridPowerDeltaConfigInvalid();
|
||||||
|
if (gridPowerDeltaValue > gridPowerDeltaOffThreshold || invalid) {
|
||||||
|
if (gridPowerDeltaAlarmSince == 0 && gridPowerDeltaOffDelay > 0) {
|
||||||
|
Serial.printf("[RELAY] \"%s\": CONSUMING TOO MUCH: Powering OFF in %ld ms...\n", name.c_str(), gridPowerDeltaOffDelay);
|
||||||
|
gridPowerDeltaAlarmSince = now;
|
||||||
|
}
|
||||||
|
if (now - gridPowerDeltaAlarmSince >= gridPowerDeltaOffDelay) {
|
||||||
|
Serial.printf("[RELAY] \"%s\": CONSUMING TOO MUCH: Powering OFF!\n", name.c_str());
|
||||||
|
set(false);
|
||||||
|
}
|
||||||
|
} else if (gridPowerDeltaAlarmSince > 0) {
|
||||||
|
Serial.printf("[RELAY] \"%s\": Powering off CANCELED!\n", name.c_str());
|
||||||
|
gridPowerDeltaAlarmSince = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gridPowerDeltaOn() {
|
||||||
|
if (!gridPowerDeltaOnEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto now = max(1UL, millis());
|
||||||
|
if (gridPowerDeltaValue < gridPowerDeltaOnThreshold && !gridPowerDeltaConfigInvalid()) {
|
||||||
|
if (gridPowerDeltaAlarmSince == 0 && gridPowerDeltaOnDelay > 0) {
|
||||||
|
Serial.printf("[RELAY] \"%s\": PRODUCING TOO MUCH: Powering ON in %ld ms...\n", name.c_str(), gridPowerDeltaOnDelay);
|
||||||
|
gridPowerDeltaAlarmSince = now;
|
||||||
|
}
|
||||||
|
if (now - gridPowerDeltaAlarmSince >= gridPowerDeltaOnDelay) {
|
||||||
|
Serial.printf("[RELAY] \"%s\": PRODUCING TOO MUCH: Powering ON!\n", name.c_str());
|
||||||
|
set(true);
|
||||||
|
}
|
||||||
|
} else if (gridPowerDeltaAlarmSince > 0) {
|
||||||
|
Serial.printf("[RELAY] \"%s\": Powering on CANCELED!\n", name.c_str());
|
||||||
|
gridPowerDeltaAlarmSince = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -46,7 +46,7 @@ void listDir(const String &path, const String &indent) {
|
|||||||
|
|
||||||
void configSetup() {
|
void configSetup() {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
LittleFS.begin(true)
|
LittleFS.begin(true);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
if (!LittleFS.begin()) {
|
if (!LittleFS.begin()) {
|
||||||
@ -55,7 +55,7 @@ void configSetup() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Serial.println("Filesystem-content:");
|
Serial.println("Filesystem-content:");
|
||||||
listDir("/", "");
|
listDir("/", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
File configOpen(const String &path, const bool write) {
|
File configOpen(const String &path, const bool write) {
|
||||||
@ -75,14 +75,14 @@ void doLog(const String &path, const String &value, const bool isPassword, const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Serial.printf(
|
Serial.printf(
|
||||||
"[CONFIG] %-20s = %-30s [%s]\n",
|
"[CONFIG] %-40s = %-30s [%s]\n",
|
||||||
path.c_str(),
|
path.c_str(),
|
||||||
isPassword ? "*" : value.c_str(),
|
isPassword ? "*" : value.c_str(),
|
||||||
type == CONFIG_LOG_FALLBACK ? "fallback" : type == CONFIG_LOG_READ ? "READ" : type == CONFIG_LOG_UNCHANGED ? "UNCHANGED" : type == CONFIG_LOG_WRITE ? "WRITE" : ""
|
type == CONFIG_LOG_FALLBACK ? "fallback" : type == CONFIG_LOG_READ ? "READ" : type == CONFIG_LOG_UNCHANGED ? "UNCHANGED" : type == CONFIG_LOG_WRITE ? "WRITE" : ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
long configRead(const String &path, const long fallback, const bool log) {
|
long loadLong(const String &path, const long fallback, const bool log) {
|
||||||
if (auto file = configOpen(path, false)) {
|
if (auto file = configOpen(path, false)) {
|
||||||
const auto content = file.readString();
|
const auto content = file.readString();
|
||||||
file.close();
|
file.close();
|
||||||
@ -94,8 +94,8 @@ long configRead(const String &path, const long fallback, const bool log) {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool configWrite(const String &path, const long fallback, const long value) {
|
bool storeLong(const String &path, const long fallback, const long value) {
|
||||||
if (configRead(path, fallback, false) == value) {
|
if (loadLong(path, fallback, false) == value) {
|
||||||
doLog(path, String(value), false, CONFIG_LOG_UNCHANGED, true);
|
doLog(path, String(value), false, CONFIG_LOG_UNCHANGED, true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -109,51 +109,67 @@ bool configWrite(const String &path, const long fallback, const long value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool configRead(const String &path, const bool fallback) {
|
bool loadBool(const String &path, const bool fallback, const bool log) {
|
||||||
return configRead(path, fallback ? 1L : 0L) > 0;
|
const auto value = loadString(path, fallback ? "true" : "false", log);
|
||||||
}
|
if (value == "true") {
|
||||||
|
return true;
|
||||||
bool configWrite(const String &path, const bool fallback, const bool value) {
|
|
||||||
return configWrite(path, fallback ? 1L : 0L, value ? 1L : 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
String configRead(const String &path, const char *fallback) {
|
|
||||||
return configRead(path, String(fallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool configWrite(const String &path, const char *fallback, const char *value) {
|
|
||||||
return configWrite(path, String(fallback), String(value), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
String configRead(const String &path, const String &fallback, const bool log, const bool isPassword) {
|
|
||||||
if (auto file = configOpen(path, false)) {
|
|
||||||
const auto value = file.readString();
|
|
||||||
file.close();
|
|
||||||
doLog(path, value.c_str(), isPassword, CONFIG_LOG_READ, log);
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
doLog(path, fallback.c_str(), isPassword, CONFIG_LOG_FALLBACK, log);
|
if (value == "false") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Serial.printf("[CONFIG] Not a boolean: path=%s, value=%s\n", path.c_str(), value.c_str());
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool configWrite(const String &path, const String &fallback, const String &value, const bool isPassword) {
|
bool storeBool(const String &path, const bool fallback, const bool value) {
|
||||||
if (configRead(path, fallback, false) == value) {
|
return storeString(path, fallback ? "true" : "false", value ? "true" : "false");
|
||||||
doLog(path, value.c_str(), isPassword, CONFIG_LOG_UNCHANGED, true);
|
}
|
||||||
|
|
||||||
|
String _loadString(const String &path, const String &fallback, const bool password, const bool log) {
|
||||||
|
if (auto file = configOpen(path, false)) {
|
||||||
|
const auto value = file.readString();
|
||||||
|
file.close();
|
||||||
|
doLog(path, value.c_str(), password, CONFIG_LOG_READ, log);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
doLog(path, fallback.c_str(), password, CONFIG_LOG_FALLBACK, log);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _storeString(const String &path, const String &fallback, const bool password, const String &value) {
|
||||||
|
if (_loadString(path, fallback, password, false) == value) {
|
||||||
|
doLog(path, value.c_str(), password, CONFIG_LOG_UNCHANGED, true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (auto file = configOpen(path, true)) {
|
if (auto file = configOpen(path, true)) {
|
||||||
file.write(reinterpret_cast<const uint8_t *>(value.c_str()), value.length());
|
file.write(reinterpret_cast<const uint8_t *>(value.c_str()), value.length());
|
||||||
file.close();
|
file.close();
|
||||||
doLog(path, value.c_str(), isPassword, CONFIG_LOG_WRITE, true);
|
doLog(path, value.c_str(), password, CONFIG_LOG_WRITE, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Initial configRead(const String &path, const Initial &fallback) {
|
String loadString(const String &path, const String &fallback, const bool log) {
|
||||||
return stringToInitial(configRead(path, initialToString(fallback)));
|
return _loadString(path, fallback, false, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool configWrite(const String &path, const Initial &fallback, const Initial &value) {
|
bool storeString(const String &path, const String &fallback, const String &value) {
|
||||||
return configWrite(path, initialToString(fallback), initialToString(value));
|
return _storeString(path, fallback, false, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String loadPassword(const String &path, const String &fallback, const bool log) {
|
||||||
|
return _loadString(path, fallback, true, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool storePassword(const String &path, const String &fallback, const String &value) {
|
||||||
|
return _storeString(path, fallback, true, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Initial loadInitial(const String &path, const Initial &fallback, const bool log) {
|
||||||
|
return stringToInitial(loadString(path, initialToString(fallback), log));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool storeInitial(const String &path, const Initial &fallback, const Initial &value) {
|
||||||
|
return storeString(path, initialToString(fallback), initialToString(value));
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/config.h
20
src/config.h
@ -7,24 +7,24 @@
|
|||||||
|
|
||||||
void configSetup();
|
void configSetup();
|
||||||
|
|
||||||
long configRead(const String &path, long fallback, bool log = true);
|
long loadLong(const String &path, long fallback, bool log = true);
|
||||||
|
|
||||||
bool configWrite(const String &path, long fallback, long value);
|
bool storeLong(const String &path, long fallback, long value);
|
||||||
|
|
||||||
bool configRead(const String &path, bool fallback);
|
bool loadBool(const String &path, bool fallback, bool log = true);
|
||||||
|
|
||||||
bool configWrite(const String &path, bool fallback, bool value);
|
bool storeBool(const String &path, bool fallback, bool value);
|
||||||
|
|
||||||
String configRead(const String &path, const char *fallback);
|
String loadString(const String &path, const String &fallback, bool log = true);
|
||||||
|
|
||||||
bool configWrite(const String &path, const char *fallback, const char *value);
|
bool storeString(const String &path, const String &fallback, const String &value);
|
||||||
|
|
||||||
String configRead(const String &path, const String &fallback, bool log = true, bool isPassword = false);
|
String loadPassword(const String &path, const String &fallback, bool log = true);
|
||||||
|
|
||||||
bool configWrite(const String &path, const String &fallback, const String &value, bool isPassword);
|
bool storePassword(const String &path, const String &fallback, const String &value);
|
||||||
|
|
||||||
Initial configRead(const String &path, const Initial &fallback);
|
Initial loadInitial(const String &path, const Initial &fallback, bool log = true);
|
||||||
|
|
||||||
bool configWrite(const String &path, const Initial &fallback, const Initial &value);
|
bool storeInitial(const String &path, const Initial &fallback, const Initial &value);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
162
src/http.cpp
162
src/http.cpp
@ -4,6 +4,8 @@
|
|||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#include "wifi.h"
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
#include <ESP8266WebServer.h>
|
#include <ESP8266WebServer.h>
|
||||||
|
|
||||||
@ -18,73 +20,86 @@ WebServer server(80);
|
|||||||
|
|
||||||
bool httpRunning = false;
|
bool httpRunning = false;
|
||||||
|
|
||||||
void httpRelay(const int index, Output &relay) {
|
File httpUploadFile;
|
||||||
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;
|
void httpString(const String &key, const std::function<void(const String &)> &modifier) {
|
||||||
if (server.hasArg(stateKey)) {
|
if (server.hasArg(key)) {
|
||||||
const auto state = server.arg(stateKey);
|
const auto name = server.arg(key);
|
||||||
if (state == "true") {
|
modifier(name);
|
||||||
relay.set(true);
|
|
||||||
} else if (state == "false") {
|
|
||||||
relay.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto initialKey = String("initial") + index;
|
|
||||||
if (server.hasArg(initialKey)) {
|
|
||||||
const auto initial = server.arg(initialKey);
|
|
||||||
if (initial == "OFF") {
|
|
||||||
relay.setInitial(INITIAL_OFF);
|
|
||||||
} else if (initial == "ON") {
|
|
||||||
relay.setInitial(INITIAL_ON);
|
|
||||||
} else if (initial == "CYCLE") {
|
|
||||||
relay.setInitial(INITIAL_CYCLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto onCountKey = String("onCount") + index;
|
|
||||||
if (server.hasArg(onCountKey)) {
|
|
||||||
const auto value = server.arg(onCountKey).toInt();
|
|
||||||
relay.setOnCount(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto onMillisKey = String("onMillis") + index;
|
|
||||||
if (server.hasArg(onMillisKey)) {
|
|
||||||
const auto value = server.arg(onMillisKey).toInt();
|
|
||||||
relay.setOnMillis(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto offMillisKey = String("offMillis") + index;
|
|
||||||
if (server.hasArg(offMillisKey)) {
|
|
||||||
const auto value = server.arg(offMillisKey).toInt();
|
|
||||||
relay.setOffMillis(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void httpRelayJson(const Output &relay, const JsonObject json) {
|
void httpBool(const String &key, const std::function<void(const bool &)> &modifier) {
|
||||||
json["name"] = relay.getName();
|
if (server.hasArg(key)) {
|
||||||
json["state"] = relay.get();
|
const auto state = server.arg(key);
|
||||||
json["stateMillis"] = relay.getStateMillis();
|
if (state == "true") {
|
||||||
json["initial"] = initialToString(relay.getInitial());
|
modifier(true);
|
||||||
json["onCount"] = relay.getOnCount();
|
} else if (state == "false") {
|
||||||
json["onMillis"] = relay.getOnMillis();
|
modifier(false);
|
||||||
json["offMillis"] = relay.getOffMillis();
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpLong(const String &key, const std::function<void(const long &)> &modifier) {
|
||||||
|
if (server.hasArg(key)) {
|
||||||
|
modifier(server.arg(key).toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpInitial(const String &key, const std::function<void(const Initial &)> &modifier) {
|
||||||
|
httpString(key, [&modifier](const String &value) {
|
||||||
|
if (value == "OFF") {
|
||||||
|
modifier(INITIAL_OFF);
|
||||||
|
} else if (value == "ON") {
|
||||||
|
modifier(INITIAL_ON);
|
||||||
|
} else if (value == "CYCLE") {
|
||||||
|
modifier(INITIAL_CYCLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpRelay(const int index, Relay &relay) {
|
||||||
|
const String suffix(index);
|
||||||
|
httpString("name" + suffix, [&relay](const String &value) { relay.setName(value); });
|
||||||
|
httpInitial("initial" + suffix, [&relay](const Initial &value) { relay.setInitial(value); });
|
||||||
|
httpLong("onCount" + suffix, [&relay](const long &value) { relay.setOnCount(value); });
|
||||||
|
httpLong("onMillis" + suffix, [&relay](const long &value) { relay.setOnMillis(value); });
|
||||||
|
httpLong("offMillis" + suffix, [&relay](const long &value) { relay.setOffMillis(value); });
|
||||||
|
|
||||||
|
httpString("topic" + suffix, [&relay](const String &value) { relay.setTopic(value); });
|
||||||
|
|
||||||
|
httpBool("gridPowerDeltaOnEnabled" + suffix, [&relay](const long &value) { relay.setGridPowerDeltaOnEnabled(value); });
|
||||||
|
httpLong("gridPowerDeltaOnThreshold" + suffix, [&relay](const long &value) { relay.setGridPowerDeltaOnThreshold(value); });
|
||||||
|
httpLong("gridPowerDeltaOnDelay" + suffix, [&relay](const long &value) { relay.setGridPowerDeltaOnDelay(value); });
|
||||||
|
httpBool("gridPowerDeltaOffEnabled" + suffix, [&relay](const long &value) { relay.setGridPowerDeltaOffEnabled(value); });
|
||||||
|
httpLong("gridPowerDeltaOffThreshold" + suffix, [&relay](const long &value) { relay.setGridPowerDeltaOffThreshold(value); });
|
||||||
|
httpLong("gridPowerDeltaOffDelay" + suffix, [&relay](const long &value) { relay.setGridPowerDeltaOffDelay(value); });
|
||||||
|
|
||||||
|
httpBool("state" + suffix, [&relay](const bool &value) { relay.set(value); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void httpStatus() {
|
void httpStatus() {
|
||||||
JsonDocument json;
|
JsonDocument json;
|
||||||
json["hostname"] = WiFi.getHostname();
|
|
||||||
|
const auto wifi = json["wifi"].to<JsonObject>();
|
||||||
|
wifi["hostname"] = WiFi.getHostname();
|
||||||
|
wifi["ssid"] = WiFi.SSID();
|
||||||
|
|
||||||
|
const auto mqtt = json["mqtt"].to<JsonObject>();
|
||||||
|
mqtt["user"] = getMqttUser();
|
||||||
|
mqtt["host"] = getMqttHost();
|
||||||
|
mqtt["port"] = getMqttPort();
|
||||||
|
|
||||||
|
json["gridPowerDeltaValue"] = gridPowerDeltaValue;
|
||||||
|
json["gridPowerDeltaAge"] = millis() - gridPowerDeltaMillis;
|
||||||
|
|
||||||
const auto relays = json["relays"].to<JsonArray>();
|
const auto relays = json["relays"].to<JsonArray>();
|
||||||
httpRelayJson(relay0, relays.add<JsonObject>());
|
relay0.json(relays.add<JsonObject>());
|
||||||
httpRelayJson(relay1, relays.add<JsonObject>());
|
#ifdef Ch4Pro
|
||||||
httpRelayJson(relay2, relays.add<JsonObject>());
|
relay1.json(relays.add<JsonObject>());
|
||||||
httpRelayJson(relay3, relays.add<JsonObject>());
|
relay2.json(relays.add<JsonObject>());
|
||||||
|
relay3.json(relays.add<JsonObject>());
|
||||||
|
#endif
|
||||||
|
|
||||||
String response;
|
String response;
|
||||||
serializeJson(json, response);
|
serializeJson(json, response);
|
||||||
@ -92,24 +107,27 @@ void httpStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void httpSet() {
|
void httpSet() {
|
||||||
|
httpString("wifiHostname", wifiSetHostname);
|
||||||
|
httpString("wifiSSID", wifiSetSSID);
|
||||||
|
httpString("wifiPassword", wifiSetPassword);
|
||||||
|
|
||||||
|
httpString("mqttHost", mqttSetHost);
|
||||||
|
httpLong("mqttPort", mqttSetPort);
|
||||||
|
httpString("mqttUser", mqttSetUser);
|
||||||
|
httpString("mqttPassword", mqttSetPassword);
|
||||||
|
|
||||||
httpRelay(0, relay0);
|
httpRelay(0, relay0);
|
||||||
|
#ifdef Ch4Pro
|
||||||
httpRelay(1, relay1);
|
httpRelay(1, relay1);
|
||||||
httpRelay(2, relay2);
|
httpRelay(2, relay2);
|
||||||
httpRelay(3, relay3);
|
httpRelay(3, relay3);
|
||||||
|
#endif
|
||||||
|
|
||||||
httpStatus();
|
httpStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void httpOff() {
|
|
||||||
relay0.set(false);
|
|
||||||
relay1.set(false);
|
|
||||||
relay2.set(false);
|
|
||||||
relay3.set(false);
|
|
||||||
httpStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
File httpUploadFile;
|
|
||||||
|
|
||||||
void httpUpload(const char *name) {
|
void httpUpload(const char *name) {
|
||||||
|
yield();
|
||||||
const auto upload = server.upload();
|
const auto upload = server.upload();
|
||||||
if (upload.status == UPLOAD_FILE_START) {
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
char path[64];
|
char path[64];
|
||||||
@ -136,17 +154,15 @@ void httpSetup() {
|
|||||||
server.serveStatic("/", LittleFS, "/index.html");
|
server.serveStatic("/", LittleFS, "/index.html");
|
||||||
server.serveStatic("/icon.svg", LittleFS, "/icon.svg");
|
server.serveStatic("/icon.svg", LittleFS, "/icon.svg");
|
||||||
|
|
||||||
server.on("/set", httpSet);
|
server.on("/status", httpSet);
|
||||||
server.on("/set/", httpSet);
|
server.on("/status/", httpSet);
|
||||||
server.on("/off", httpOff);
|
|
||||||
server.on("/off/", httpOff);
|
|
||||||
|
|
||||||
server.on("/upload/index", HTTP_POST, [] { server.send(200); }, [] { httpUpload("index.html"); });
|
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.on("/upload/icon", HTTP_POST, [] { server.send(200); }, [] { httpUpload("icon.svg"); });
|
||||||
|
|
||||||
server.begin();
|
server.begin();
|
||||||
|
|
||||||
Serial.println("HTTP server started");
|
Serial.println("[HTTP] Server started");
|
||||||
httpRunning = true;
|
httpRunning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +174,6 @@ void httpStop() {
|
|||||||
if (httpRunning) {
|
if (httpRunning) {
|
||||||
httpRunning = false;
|
httpRunning = false;
|
||||||
server.stop();
|
server.stop();
|
||||||
Serial.println("HTTP server stopped");
|
Serial.println("[HTTP] Server stopped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/io.cpp
45
src/io.cpp
@ -1,22 +1,28 @@
|
|||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
|
||||||
#ifndef STATUS_PIN
|
#ifdef SP111
|
||||||
#define STATUS_PIN 13
|
|
||||||
|
#ifndef ESP32_TESTBOARD
|
||||||
|
#define STATUS_PIN 0
|
||||||
|
#define STATUS_INVERT true
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef STATUS_INVERT
|
Button button0(13, true, true, [](const ButtonEvent event) { buttonCallback(relay0, event); });
|
||||||
#define STATUS_INVERT true
|
|
||||||
|
Relay relay0(0, "fallback/relay0", "RELAY #0", 15, false, true);
|
||||||
|
|
||||||
|
#ifdef SP111
|
||||||
|
Output relay0Led("relay0Led", 2, true, false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Relay relay0(0, "RELAY #0", 12, false, true);
|
#endif
|
||||||
|
|
||||||
Relay relay1(1, "RELAY #1", 5, false, true);
|
#ifdef Ch4Pro
|
||||||
|
|
||||||
Relay relay2(2, "RELAY #2", 4, false, true);
|
#ifndef ESP32_TESTBOARD
|
||||||
|
#define STATUS_PIN 13
|
||||||
Relay relay3(3, "RELAY #3", 15, false, true);
|
#define STATUS_INVERT true
|
||||||
|
#endif
|
||||||
Output status("Status", STATUS_PIN, STATUS_INVERT, false);
|
|
||||||
|
|
||||||
Button button0(0, true, true, [](const ButtonEvent event) { buttonCallback(relay0, event); });
|
Button button0(0, true, true, [](const ButtonEvent event) { buttonCallback(relay0, event); });
|
||||||
|
|
||||||
@ -26,6 +32,23 @@ Button button2(10, true, true, [](const ButtonEvent event) { buttonCallback(rela
|
|||||||
|
|
||||||
Button button3(14, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); });
|
Button button3(14, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); });
|
||||||
|
|
||||||
|
Relay relay0(0, "fallback/relay0", "RELAY #0", 12, false, true);
|
||||||
|
|
||||||
|
Relay relay1(1, "fallback/relay1", "RELAY #1", 5, false, true);
|
||||||
|
|
||||||
|
Relay relay2(2, "fallback/relay2", "RELAY #2", 4, false, true);
|
||||||
|
|
||||||
|
Relay relay3(3, "fallback/relay3", "RELAY #3", 15, false, true);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32_TESTBOARD
|
||||||
|
#define STATUS_PIN 2
|
||||||
|
#define STATUS_INVERT false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Output status("Status", STATUS_PIN, STATUS_INVERT, false);
|
||||||
|
|
||||||
void buttonCallback(Output &output, const ButtonEvent event) {
|
void buttonCallback(Output &output, const ButtonEvent event) {
|
||||||
if (event == BUTTON_PRESSED) {
|
if (event == BUTTON_PRESSED) {
|
||||||
output.toggle();
|
output.toggle();
|
||||||
|
|||||||
49
src/io.h
49
src/io.h
@ -6,50 +6,65 @@
|
|||||||
|
|
||||||
void buttonCallback(Output &output, ButtonEvent event);
|
void buttonCallback(Output &output, ButtonEvent event);
|
||||||
|
|
||||||
extern Relay relay0;
|
|
||||||
|
|
||||||
extern Relay relay1;
|
|
||||||
|
|
||||||
extern Relay relay2;
|
|
||||||
|
|
||||||
extern Relay relay3;
|
|
||||||
|
|
||||||
extern Output status;
|
extern Output status;
|
||||||
|
|
||||||
extern Button button0;
|
extern Button button0;
|
||||||
|
|
||||||
|
extern Relay relay0;
|
||||||
|
|
||||||
|
#ifdef SP111
|
||||||
|
extern Output relay0Led;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Ch4Pro
|
||||||
|
|
||||||
extern Button button1;
|
extern Button button1;
|
||||||
|
|
||||||
extern Button button2;
|
extern Button button2;
|
||||||
|
|
||||||
extern Button button3;
|
extern Button button3;
|
||||||
|
|
||||||
|
extern Relay relay1;
|
||||||
|
|
||||||
|
extern Relay relay2;
|
||||||
|
|
||||||
|
extern Relay relay3;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
inline void ioSetup() {
|
inline void ioSetup() {
|
||||||
|
status.setup();
|
||||||
button0.setup();
|
button0.setup();
|
||||||
|
relay0.setup();
|
||||||
|
#ifdef SP111
|
||||||
|
relay0Led.setup();
|
||||||
|
#endif
|
||||||
|
#ifdef Ch4Pro
|
||||||
button1.setup();
|
button1.setup();
|
||||||
button2.setup();
|
button2.setup();
|
||||||
button3.setup();
|
button3.setup();
|
||||||
|
|
||||||
status.setup();
|
|
||||||
|
|
||||||
relay0.setup();
|
|
||||||
relay1.setup();
|
relay1.setup();
|
||||||
relay2.setup();
|
relay2.setup();
|
||||||
relay3.setup();
|
relay3.setup();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void ioLoop() {
|
inline void ioLoop() {
|
||||||
|
status.loop();
|
||||||
button0.loop();
|
button0.loop();
|
||||||
|
relay0.loop();
|
||||||
|
#ifdef SP111
|
||||||
|
relay0Led.set(relay0.get());
|
||||||
|
relay0Led.loop();
|
||||||
|
#endif
|
||||||
|
#ifdef Ch4Pro
|
||||||
button1.loop();
|
button1.loop();
|
||||||
button2.loop();
|
button2.loop();
|
||||||
button3.loop();
|
button3.loop();
|
||||||
|
relay1.loop();
|
||||||
status.loop();
|
|
||||||
|
|
||||||
relay0.loop();
|
|
||||||
relay0.loop();
|
|
||||||
relay2.loop();
|
relay2.loop();
|
||||||
relay3.loop();
|
relay3.loop();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
144
src/mqtt.cpp
Normal file
144
src/mqtt.cpp
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#include "mqtt.h"
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
#include "PubSubClient.h"
|
||||||
|
|
||||||
|
#define MQTT_HOST_KEY "/mqtt/host"
|
||||||
|
#define MQTT_HOST_FALLBACK "10.0.0.50"
|
||||||
|
|
||||||
|
#define MQTT_PORT_KEY "/mqtt/port"
|
||||||
|
#define MQTT_PORT_FALLBACK 1883
|
||||||
|
|
||||||
|
#define MQTT_USER_KEY "/mqtt/user"
|
||||||
|
#define MQTT_USER_FALLBACK ""
|
||||||
|
|
||||||
|
#define MQTT_PASSWORD_KEY "/mqtt/password"
|
||||||
|
#define MQTT_PASSWORD_FALLBACK ""
|
||||||
|
|
||||||
|
const String GRID_POWER_DELTA_TOPIC = "electricity/grid/power/signed/w";
|
||||||
|
|
||||||
|
WiFiClient wifiClient;
|
||||||
|
|
||||||
|
PubSubClient client(wifiClient);
|
||||||
|
|
||||||
|
String mqttHost = MQTT_HOST_FALLBACK;
|
||||||
|
|
||||||
|
long mqttPort = MQTT_PORT_FALLBACK;
|
||||||
|
|
||||||
|
String mqttUser = MQTT_USER_FALLBACK;
|
||||||
|
|
||||||
|
bool mqttShouldConnect = false;
|
||||||
|
|
||||||
|
unsigned long mqttLast = 0;
|
||||||
|
|
||||||
|
unsigned long warningLast = 0;
|
||||||
|
|
||||||
|
void mqttSetup() {
|
||||||
|
mqttShouldConnect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double gridPowerDeltaValue = NAN;
|
||||||
|
|
||||||
|
double gridPowerDeltaMillis = 0;
|
||||||
|
|
||||||
|
void mqttReceive(const char *topic, const uint8_t *bytes, const unsigned int length) {
|
||||||
|
if (length >= 100) {
|
||||||
|
Serial.println("[MQTT] Inbound buffer overflow");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char string[100];
|
||||||
|
memcpy(string, bytes, length);
|
||||||
|
const auto payload = String(string);
|
||||||
|
if (GRID_POWER_DELTA_TOPIC == topic) {
|
||||||
|
gridPowerDeltaValue = payload.toDouble();
|
||||||
|
gridPowerDeltaMillis = millis();
|
||||||
|
} else {
|
||||||
|
Serial.printf("[MQTT] Received unexpected topic: %s\n", topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttLoop() {
|
||||||
|
if (client.loop()) {
|
||||||
|
if (!mqttShouldConnect) {
|
||||||
|
client.disconnect();
|
||||||
|
Serial.println("[MQTT] Stopped.");
|
||||||
|
}
|
||||||
|
} else if (mqttShouldConnect) {
|
||||||
|
mqttHost = loadString(MQTT_HOST_KEY, MQTT_HOST_FALLBACK);
|
||||||
|
mqttPort = loadLong(MQTT_PORT_KEY, MQTT_PORT_FALLBACK);
|
||||||
|
mqttUser = loadString(MQTT_USER_KEY, MQTT_USER_FALLBACK);
|
||||||
|
const auto mqttPass = loadPassword(MQTT_PASSWORD_KEY, MQTT_PASSWORD_FALLBACK);
|
||||||
|
if (mqttHost == "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mqttLast == 0 || millis() - mqttLast >= 3000) {
|
||||||
|
mqttLast = max(1UL, millis());
|
||||||
|
client.setServer(mqttHost.c_str(), mqttPort);
|
||||||
|
client.setBufferSize(512);
|
||||||
|
Serial.printf("[MQTT] Connecting: %s:%ld\n", mqttHost.c_str(), mqttPort);
|
||||||
|
if (client.connect(WiFi.getHostname(), mqttUser.c_str(), mqttPass.c_str())) {
|
||||||
|
Serial.printf("[MQTT] Connected.\n");
|
||||||
|
client.subscribe(GRID_POWER_DELTA_TOPIC.c_str());
|
||||||
|
client.setCallback(mqttReceive);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[MQTT] Failed to connect.\n");
|
||||||
|
}
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttStop() {
|
||||||
|
mqttShouldConnect = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttPublish(const String &topic, const JsonDocument &json) {
|
||||||
|
if (!mqttShouldConnect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char buffer[512];
|
||||||
|
serializeJson(json, buffer);
|
||||||
|
client.publish(topic.c_str(), buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMqttUser() {
|
||||||
|
return mqttUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMqttHost() {
|
||||||
|
return mqttHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getMqttPort() {
|
||||||
|
return mqttPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttSetHost(const String &value) {
|
||||||
|
mqttHost = value;
|
||||||
|
storeString(MQTT_HOST_KEY, String(MQTT_HOST_FALLBACK), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttSetPort(const long &value) {
|
||||||
|
mqttPort = value;
|
||||||
|
storeLong(MQTT_PORT_KEY,MQTT_PORT_FALLBACK, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttSetUser(const String &value) {
|
||||||
|
mqttUser = value;
|
||||||
|
storeString(MQTT_USER_KEY, String(MQTT_USER_FALLBACK), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqttSetPassword(const String &value) {
|
||||||
|
storePassword(MQTT_PASSWORD_KEY,MQTT_PASSWORD_FALLBACK, value);
|
||||||
|
}
|
||||||
32
src/mqtt.h
Normal file
32
src/mqtt.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef MQTT_H
|
||||||
|
#define MQTT_H
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
extern double gridPowerDeltaValue;
|
||||||
|
|
||||||
|
extern double gridPowerDeltaMillis;
|
||||||
|
|
||||||
|
void mqttSetup();
|
||||||
|
|
||||||
|
void mqttLoop();
|
||||||
|
|
||||||
|
void mqttStop();
|
||||||
|
|
||||||
|
void mqttPublish(const String &topic, const JsonDocument &json);
|
||||||
|
|
||||||
|
String getMqttUser();
|
||||||
|
|
||||||
|
String getMqttHost();
|
||||||
|
|
||||||
|
long getMqttPort();
|
||||||
|
|
||||||
|
void mqttSetHost(const String &value);
|
||||||
|
|
||||||
|
void mqttSetPort(const long &value);
|
||||||
|
|
||||||
|
void mqttSetUser(const String &value);
|
||||||
|
|
||||||
|
void mqttSetPassword(const String &value);
|
||||||
|
|
||||||
|
#endif
|
||||||
56
src/wifi.cpp
56
src/wifi.cpp
@ -3,16 +3,16 @@
|
|||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
|
||||||
#define CONFIG_HOSTNAME "/wifi/hostname"
|
|
||||||
#define CONFIG_WIFI_SSID "/wifi/ssid"
|
|
||||||
#define CONFIG_WIFI_PASSWORD "/wifi/password"
|
|
||||||
|
|
||||||
#define DEFAULT_HOSTNAME "PatrixSonoff4ChPro"
|
|
||||||
#define DEFAULT_WIFI_SSID "HappyNet"
|
|
||||||
#define DEFAULT_WIFI_PASSWORD "1Grausame!Sackratte7"
|
|
||||||
|
|
||||||
#include <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
|
|
||||||
|
#define WIFI_HOSTNAME_KEY "/wifi/hostname"
|
||||||
|
|
||||||
|
#define WIFI_SSID_KEY "/wifi/ssid"
|
||||||
|
#define WIFI_SSID_FALLBACK "HappyNet"
|
||||||
|
|
||||||
|
#define WIFI_PASSWORD_KEY "/wifi/password"
|
||||||
|
#define WIFI_PASSWORD_FALLBACK "1Grausame!Sackratte7"
|
||||||
|
|
||||||
bool wifiConnected = false;
|
bool wifiConnected = false;
|
||||||
|
|
||||||
unsigned long wifiLast = 0;
|
unsigned long wifiLast = 0;
|
||||||
@ -26,10 +26,11 @@ void wifiConnect() {
|
|||||||
|
|
||||||
status.cycle(500, 500);
|
status.cycle(500, 500);
|
||||||
|
|
||||||
const auto hostname = configRead(CONFIG_HOSTNAME, DEFAULT_HOSTNAME);
|
const auto hostname = loadString(WIFI_HOSTNAME_KEY, WIFI_HOSTNAME_FALLBACK);
|
||||||
const auto wifiSSID = configRead(CONFIG_WIFI_SSID, DEFAULT_WIFI_SSID);
|
const auto wifiSSID = loadString(WIFI_SSID_KEY, WIFI_SSID_FALLBACK);
|
||||||
const auto wifiPass = configRead(CONFIG_WIFI_PASSWORD, DEFAULT_WIFI_PASSWORD, true, true);
|
const auto wifiPass = loadPassword(WIFI_PASSWORD_KEY, WIFI_PASSWORD_FALLBACK);
|
||||||
|
|
||||||
|
Serial.printf("[WiFi] Connecting: \"%s\"\n", wifiSSID.c_str());
|
||||||
WiFi.hostname(hostname);
|
WiFi.hostname(hostname);
|
||||||
WiFi.begin(wifiSSID.c_str(), wifiPass.c_str());
|
WiFi.begin(wifiSSID.c_str(), wifiPass.c_str());
|
||||||
wifiLast = max(1UL, millis());
|
wifiLast = max(1UL, millis());
|
||||||
@ -41,18 +42,21 @@ void wifiLoop() {
|
|||||||
if (wifiConnected) {
|
if (wifiConnected) {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
httpLoop();
|
httpLoop();
|
||||||
|
mqttLoop();
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[WiFi] Disconnected!\n");
|
Serial.printf("[WiFi] Disconnected!\n");
|
||||||
ArduinoOTA.end();
|
ArduinoOTA.end();
|
||||||
httpStop();
|
httpStop();
|
||||||
|
mqttStop();
|
||||||
wifiConnect();
|
wifiConnect();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
status.set(false);
|
status.set(false);
|
||||||
Serial.printf("[WiFi] Connected as \"%s\" (%s)\n", WiFi.getHostname(), WiFi.localIP().toString().c_str());
|
Serial.printf("[WiFi] Connected \"%s\" as \"%s\" (%s)\n", WiFi.SSID().c_str(), WiFi.getHostname(), WiFi.localIP().toString().c_str());
|
||||||
ArduinoOTA.begin();
|
ArduinoOTA.begin();
|
||||||
httpSetup();
|
httpSetup();
|
||||||
|
mqttSetup();
|
||||||
} else if (wifiLast == 0 || millis() - wifiLast >= 10000) {
|
} else if (wifiLast == 0 || millis() - wifiLast >= 10000) {
|
||||||
if (wifiLast > 0) {
|
if (wifiLast > 0) {
|
||||||
Serial.printf("[WiFi] Timeout!\n");
|
Serial.printf("[WiFi] Timeout!\n");
|
||||||
@ -63,20 +67,6 @@ void wifiLoop() {
|
|||||||
wifiConnected = connected;
|
wifiConnected = connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifiChangeHostname(const char *hostname) {
|
|
||||||
if (configWrite(CONFIG_HOSTNAME, DEFAULT_HOSTNAME, hostname)) {
|
|
||||||
WiFi.setHostname(hostname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void wifiChangeSSID(const char *ssid) {
|
|
||||||
configWrite(CONFIG_WIFI_SSID, DEFAULT_WIFI_SSID, ssid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wifiChangePassword(const char *password) {
|
|
||||||
configWrite(CONFIG_WIFI_PASSWORD, DEFAULT_WIFI_PASSWORD, password, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wifiSetup() {
|
void wifiSetup() {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
esp_log_level_set("wifi", ESP_LOG_NONE);
|
esp_log_level_set("wifi", ESP_LOG_NONE);
|
||||||
@ -98,3 +88,17 @@ void wifiSetup() {
|
|||||||
status.set(false);
|
status.set(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void wifiSetHostname(const String &hostname) {
|
||||||
|
if (storeString(WIFI_HOSTNAME_KEY, WIFI_HOSTNAME_FALLBACK, hostname)) {
|
||||||
|
WiFi.setHostname(hostname.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wifiSetSSID(const String &ssid) {
|
||||||
|
storeString(WIFI_SSID_KEY, WIFI_SSID_FALLBACK, ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wifiSetPassword(const String &password) {
|
||||||
|
storePassword(WIFI_PASSWORD_KEY, WIFI_PASSWORD_FALLBACK, password);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
#ifndef WIFI_H
|
#ifndef WIFI_H
|
||||||
#define WIFI_H
|
#define WIFI_H
|
||||||
|
|
||||||
void wifiChangeHostname(const char *hostname);
|
#include <WString.h>
|
||||||
|
|
||||||
void wifiChangeSSID(const char *ssid);
|
void wifiSetSSID(const String &ssid);
|
||||||
|
|
||||||
void wifiChangePassword(const char *password);
|
void wifiSetPassword(const String &password);
|
||||||
|
|
||||||
void wifiSetup();
|
void wifiSetup();
|
||||||
|
|
||||||
void wifiLoop();
|
void wifiLoop();
|
||||||
|
|
||||||
|
void wifiSetHostname(const String &hostname);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
12
upload.sh
12
upload.sh
@ -4,5 +4,13 @@ cd "$(dirname "$0")" || exit 1
|
|||||||
|
|
||||||
minify index.html | sed 's|http://10.42.0.204||g' > index.html.min || exit 2
|
minify index.html | sed 's|http://10.42.0.204||g' > index.html.min || exit 2
|
||||||
|
|
||||||
curl -s 'http://10.42.0.204/upload/index' -F "file=@index.html.min"
|
#curl -s 'http://10.42.0.204/upload/index' -F "file=@index.html.min"
|
||||||
curl -s 'http://10.42.0.204/upload/icon' -F "file=@icon.svg"
|
#curl -s 'http://10.42.0.204/upload/icon' -F "file=@icon.svg"
|
||||||
|
|
||||||
|
# Greenhouse
|
||||||
|
curl -s 'http://10.0.0.178/upload/index' -F "file=@index.html.min"
|
||||||
|
#curl -s 'http://10.0.0.178/upload/icon' -F "file=@icon.svg"
|
||||||
|
|
||||||
|
# InfraredHeater
|
||||||
|
#curl -s 'http://10.0.0.179/upload/index' -F "file=@index.html.min"
|
||||||
|
#curl -s 'http://10.0.0.179/upload/icon' -F "file=@icon.svg"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user