mqtt + admin ui + cycle FIX
This commit is contained in:
parent
801b99a3a7
commit
b6ea584a4b
111
index.html
111
index.html
@ -55,29 +55,55 @@
|
||||
.state {
|
||||
}
|
||||
|
||||
.countdown {
|
||||
.topic {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stateOn {
|
||||
background-color: palegreen;
|
||||
background-color: #7aca7a;
|
||||
}
|
||||
|
||||
.stateOff {
|
||||
background-color: indianred;
|
||||
background-color: #ae4d4d;
|
||||
}
|
||||
|
||||
.switchOn {
|
||||
border-radius: 0.5em;
|
||||
filter: brightness(50%);
|
||||
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 {
|
||||
border-radius: 0.5em;
|
||||
filter: brightness(50%);
|
||||
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 {
|
||||
border-radius: 0.5em;
|
||||
filter: brightness(50%);
|
||||
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 {
|
||||
flex: 1;
|
||||
}
|
||||
@ -91,6 +117,10 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.adminHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
body {
|
||||
font-size: 16px;
|
||||
@ -107,7 +137,16 @@
|
||||
|
||||
<div id="relayList"></div>
|
||||
|
||||
<button id="admin" onclick="toggleAdmin()">Admin</button>
|
||||
|
||||
<script>
|
||||
let admin = false;
|
||||
|
||||
function toggleAdmin() {
|
||||
admin = !admin;
|
||||
update();
|
||||
}
|
||||
|
||||
const title = document.getElementById("title");
|
||||
const relayList = document.getElementById("relayList");
|
||||
|
||||
@ -115,10 +154,6 @@
|
||||
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)}`);
|
||||
}
|
||||
@ -133,15 +168,10 @@
|
||||
}
|
||||
|
||||
function updateState(relayTag, state) {
|
||||
const tag = relayTag.getElementsByClassName("state")[0];
|
||||
if (state) {
|
||||
tag.classList.add("stateOn");
|
||||
tag.classList.remove("stateOff");
|
||||
relayTag.classList.add("stateOn");
|
||||
relayTag.classList.remove("stateOff");
|
||||
} else {
|
||||
tag.classList.add("stateOff");
|
||||
tag.classList.remove("stateOn");
|
||||
relayTag.classList.add("stateOff");
|
||||
relayTag.classList.remove("stateOn");
|
||||
}
|
||||
@ -153,10 +183,12 @@
|
||||
const DAY = (24 * HOUR);
|
||||
|
||||
function countdownString(relayData, millis) {
|
||||
const rest = Math.ceil((millis - relayData.stateMillis - (Date.now() - dataAge)) / SECOND) * SECOND;
|
||||
if (millis <= 0 || (relayData.onCount === 0 && !relayData.state)) {
|
||||
const rest = Math.ceil((millis - relayData.stateAgeMillis - (Date.now() - dataAge)) / SECOND) * SECOND;
|
||||
const cycle = relayData.onCount !== 0 && relayData.onMillis > 0 && relayData.offMillis > 0;
|
||||
if (millis <= 0 || (!cycle && !relayData.state)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const days = rest / DAY;
|
||||
const hours = rest / HOUR;
|
||||
const minutes = rest / MINUTE;
|
||||
@ -204,13 +236,37 @@
|
||||
title.innerText = data.hostname;
|
||||
for (let index = 0; index < data.relays.length; index++) {
|
||||
const relayData = data.relays[index];
|
||||
const relay = document.getElementById("relay" + index) || create(index);
|
||||
updateValue(relay, "name", "input", relayData.name);
|
||||
updateState(relay, relayData.state);
|
||||
updateCountdown(relay, relayData);
|
||||
updateValue(relay, "onMillis", "input", relayData.onMillis);
|
||||
updateValue(relay, "offMillis", "input", relayData.offMillis);
|
||||
updateValue(relay, "initial", "select", relayData.initial);
|
||||
const relayTag = document.getElementById("relay" + index) || create(index);
|
||||
updateValue(relayTag, "name", "input", relayData.name);
|
||||
updateValue(relayTag, "topic", "input", relayData.topic);
|
||||
updateState(relayTag, relayData.state);
|
||||
updateCountdown(relayTag, relayData);
|
||||
updateValue(relayTag, "onMillis", "input", relayData.onMillis);
|
||||
updateValue(relayTag, "offMillis", "input", relayData.offMillis);
|
||||
updateValue(relayTag, "initial", "select", relayData.initial);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,9 +332,11 @@
|
||||
const header = newDiv(relay, "flex");
|
||||
newInput(relayIndex, header, "name", "name", "text");
|
||||
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 offMillis", "offMillis", "number");
|
||||
newSelect(relayIndex, config, "config initial", "initial", [["OFF", "Init: Aus"], ["ON", "Init: Ein"], ["CYCLE", "Init: Zyklus"]]);
|
||||
@ -300,6 +358,13 @@
|
||||
const relayData = data.relays[index];
|
||||
const relayTag = document.getElementById("relay" + index) || create(index);
|
||||
updateCountdown(relayTag, relayData);
|
||||
for (let element of document.getElementsByClassName("admin")) {
|
||||
if (admin) {
|
||||
element.classList.remove("adminHidden");
|
||||
} else {
|
||||
element.classList.add("adminHidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
build.filesystem = littlefs
|
||||
lib_deps = bblanchon/ArduinoJson @ 7.4.2
|
||||
knolleary/PubSubClient
|
||||
|
||||
[env:Sonoff4ChPro]
|
||||
platform = ${common.platform}
|
||||
@ -24,6 +25,9 @@ board = esp32dev
|
||||
framework = ${common.framework}
|
||||
upload_speed = ${common.upload_speed}
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
build_type = debug
|
||||
debug_tool = esp-prog
|
||||
monitor_filters = esp32_exception_decoder
|
||||
build.filesystem = ${common.build.filesystem}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = -D Sonoff4ChPro -D ESP32_TESTBOARD -D CORE_DEBUG_LEVEL=0
|
||||
@ -45,6 +49,9 @@ board = esp32dev
|
||||
framework = ${common.framework}
|
||||
upload_speed = ${common.upload_speed}
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
build_type = debug
|
||||
debug_tool = esp-prog
|
||||
monitor_filters = esp32_exception_decoder
|
||||
build.filesystem = ${common.build.filesystem}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = -D GosundSP111 -D ESP32_TESTBOARD -D CORE_DEBUG_LEVEL=0
|
||||
31
src/Output.h
31
src/Output.h
@ -27,15 +27,20 @@ protected:
|
||||
|
||||
unsigned long stateMillis = 0;
|
||||
|
||||
virtual void publish() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
void _write(const bool wanted) {
|
||||
const auto current = get();
|
||||
if (wanted != current) {
|
||||
digitalWrite(pin, wanted ^ inverted ? HIGH : LOW);
|
||||
publish();
|
||||
if (logState) {
|
||||
Serial.printf("[RELAY] \"%s\" = %s\n", name.c_str(), wanted ? "ON" : "OFF");
|
||||
}
|
||||
stateMillis = millis();
|
||||
}
|
||||
digitalWrite(pin, wanted ^ inverted ? HIGH : LOW);
|
||||
}
|
||||
|
||||
void _applyInitial() {
|
||||
@ -117,30 +122,6 @@ public:
|
||||
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
|
||||
|
||||
34
src/Relay.h
34
src/Relay.h
@ -3,22 +3,28 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "Output.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
class Relay final : public Output {
|
||||
|
||||
String nameFallback;
|
||||
|
||||
String topicFallback;
|
||||
|
||||
String topic;
|
||||
|
||||
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) {
|
||||
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), topic(topic), index(index) {
|
||||
//
|
||||
}
|
||||
|
||||
void setup() override {
|
||||
Output::setup();
|
||||
Output::setName(configRead(path("name"), nameFallback));
|
||||
topic = configRead(path("topic"), topicFallback);
|
||||
Output::setInitial(configRead(path("initial"), INITIAL_OFF));
|
||||
Output::setOnMillis(configRead(path("onMillis"), 0L));
|
||||
Output::setOffMillis(configRead(path("offMillis"), 0L));
|
||||
@ -27,7 +33,12 @@ public:
|
||||
|
||||
void setName(const String &value) override {
|
||||
Output::setName(value);
|
||||
configWrite(path("name"), nameFallback, value, false);
|
||||
configWrite(path("name"), nameFallback, value);
|
||||
}
|
||||
|
||||
void setTopic(const String &value) {
|
||||
topic = value;
|
||||
configWrite(path("topic"), topicFallback, value);
|
||||
}
|
||||
|
||||
void setInitial(const Initial value) override {
|
||||
@ -45,6 +56,17 @@ public:
|
||||
configWrite(path("offMillis"), 0L, value);
|
||||
}
|
||||
|
||||
void json(const JsonObject json) const {
|
||||
json["name"] = name;
|
||||
json["topic"] = topic;
|
||||
json["state"] = get();
|
||||
json["stateAgeMillis"] = millis() - stateMillis;
|
||||
json["initial"] = initialToString(initial);
|
||||
json["onCount"] = onCount;
|
||||
json["onMillis"] = onMillis;
|
||||
json["offMillis"] = offMillis;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
String path(const char *name) const {
|
||||
@ -53,6 +75,14 @@ private:
|
||||
return String(path);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void publish() override {
|
||||
JsonDocument doc;
|
||||
json(doc.to<JsonObject>());
|
||||
mqttPublish(topic, doc);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
26
src/http.cpp
26
src/http.cpp
@ -18,13 +18,19 @@ WebServer server(80);
|
||||
|
||||
bool httpRunning = false;
|
||||
|
||||
void httpRelay(const int index, Output &relay) {
|
||||
void httpRelay(const int index, Relay &relay) {
|
||||
const auto nameKey = String("name") + index;
|
||||
if (server.hasArg(nameKey)) {
|
||||
const auto name = server.arg(nameKey);
|
||||
relay.setName(name);
|
||||
}
|
||||
|
||||
const auto topicKey = String("topic") + index;
|
||||
if (server.hasArg(topicKey)) {
|
||||
const auto topic = server.arg(topicKey);
|
||||
relay.setTopic(topic);
|
||||
}
|
||||
|
||||
const auto stateKey = String("state") + index;
|
||||
if (server.hasArg(stateKey)) {
|
||||
const auto state = server.arg(stateKey);
|
||||
@ -66,26 +72,16 @@ void httpRelay(const int index, Output &relay) {
|
||||
}
|
||||
}
|
||||
|
||||
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>());
|
||||
relay0.json(relays.add<JsonObject>());
|
||||
#ifdef Sonoff4ChPro
|
||||
httpRelayJson(relay1, relays.add<JsonObject>());
|
||||
httpRelayJson(relay2, relays.add<JsonObject>());
|
||||
httpRelayJson(relay3, relays.add<JsonObject>());
|
||||
relay1.json(relays.add<JsonObject>());
|
||||
relay2.json(relays.add<JsonObject>());
|
||||
relay3.json(relays.add<JsonObject>());
|
||||
#endif
|
||||
|
||||
String response;
|
||||
|
||||
14
src/io.cpp
14
src/io.cpp
@ -9,7 +9,7 @@
|
||||
|
||||
Button button0(13, true, true, [](const ButtonEvent event) { buttonCallback(relay0, event); });
|
||||
|
||||
Relay relay0(0, "RELAY #0", 15, false, true);
|
||||
Relay relay0(0, "fallback/relay0", "RELAY #0", 15, false, true);
|
||||
|
||||
#endif
|
||||
|
||||
@ -20,8 +20,6 @@ Relay relay0(0, "RELAY #0", 15, false, true);
|
||||
#define STATUS_INVERT true
|
||||
#endif
|
||||
|
||||
Output status("Status", 13, true, false);
|
||||
|
||||
Button button0(0, true, true, [](const ButtonEvent event) { buttonCallback(relay0, event); });
|
||||
|
||||
Button button1(9, true, true, [](const ButtonEvent event) { buttonCallback(relay1, event); });
|
||||
@ -30,13 +28,13 @@ Button button2(10, true, true, [](const ButtonEvent event) { buttonCallback(rela
|
||||
|
||||
Button button3(14, true, true, [](const ButtonEvent event) { buttonCallback(relay3, event); });
|
||||
|
||||
Relay relay0(0, "RELAY #0", 12, false, true);
|
||||
Relay relay0(0, "fallback/relay0", "RELAY #0", 12, false, true);
|
||||
|
||||
Relay relay1(1, "RELAY #1", 5, false, true);
|
||||
Relay relay1(1, "fallback/relay1", "RELAY #1", 5, false, true);
|
||||
|
||||
Relay relay2(2, "RELAY #2", 4, false, true);
|
||||
Relay relay2(2, "fallback/relay2", "RELAY #2", 4, false, true);
|
||||
|
||||
Relay relay3(3, "RELAY #3", 15, false, true);
|
||||
Relay relay3(3, "fallback/relay3", "RELAY #3", 15, false, true);
|
||||
|
||||
#endif
|
||||
|
||||
@ -45,7 +43,7 @@ Relay relay3(3, "RELAY #3", 15, false, true);
|
||||
#define STATUS_INVERT false
|
||||
#endif
|
||||
|
||||
Output status("Status", STATUS_PIN, STATUS_INVERT, true);
|
||||
Output status("Status", STATUS_PIN, STATUS_INVERT, false);
|
||||
|
||||
void buttonCallback(Output &output, const ButtonEvent event) {
|
||||
if (event == BUTTON_PRESSED) {
|
||||
|
||||
60
src/mqtt.cpp
Normal file
60
src/mqtt.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "mqtt.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#include "PubSubClient.h"
|
||||
|
||||
WiFiClient wifiClient;
|
||||
|
||||
PubSubClient client(wifiClient);
|
||||
|
||||
bool mqttShouldConnect = false;
|
||||
|
||||
unsigned long mqttLast = 0;
|
||||
|
||||
void mqttSetup() {
|
||||
mqttShouldConnect = true;
|
||||
}
|
||||
|
||||
void mqttLoop() {
|
||||
if (client.loop()) {
|
||||
if (!mqttShouldConnect) {
|
||||
client.disconnect();
|
||||
Serial.println("[MQTT] Stopped.");
|
||||
}
|
||||
} else if (mqttShouldConnect) {
|
||||
const auto host = configRead("/mqtt/host", "", false);
|
||||
if (host == "") {
|
||||
return;
|
||||
}
|
||||
if (mqttLast == 0 || millis() - mqttLast >= 3000) {
|
||||
const auto id = configRead("/mqtt/id", "test", false);
|
||||
const auto user = configRead("/mqtt/user", "", false);
|
||||
const auto pass = configRead("/mqtt/pass", "", false, true);
|
||||
const auto port = configRead("/mqtt/port", 1883L, false);
|
||||
mqttLast = max(1UL, millis());
|
||||
client.setServer(host.c_str(), port);
|
||||
Serial.printf("[MQTT] Connecting: %s:%ld\n", host.c_str(), port);
|
||||
if (client.connect(id.c_str(), user.c_str(), pass.c_str())) {
|
||||
Serial.printf("[MQTT] Connected.\n");
|
||||
} 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[256];
|
||||
const auto size = serializeJson(json, buffer);
|
||||
client.publish(topic.c_str(), buffer, size);
|
||||
}
|
||||
14
src/mqtt.h
Normal file
14
src/mqtt.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef MQTT_H
|
||||
#define MQTT_H
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void mqttSetup();
|
||||
|
||||
void mqttLoop();
|
||||
|
||||
void mqttStop();
|
||||
|
||||
void mqttPublish(const String &topic, const JsonDocument &json);
|
||||
|
||||
#endif
|
||||
@ -42,10 +42,12 @@ void wifiLoop() {
|
||||
if (wifiConnected) {
|
||||
if (connected) {
|
||||
httpLoop();
|
||||
mqttLoop();
|
||||
} else {
|
||||
Serial.printf("[WiFi] Disconnected!\n");
|
||||
ArduinoOTA.end();
|
||||
httpStop();
|
||||
mqttStop();
|
||||
wifiConnect();
|
||||
}
|
||||
} else {
|
||||
@ -54,6 +56,7 @@ void wifiLoop() {
|
||||
Serial.printf("[WiFi] Connected \"%s\" as \"%s\" (%s)\n", WiFi.SSID().c_str(), WiFi.getHostname(), WiFi.localIP().toString().c_str());
|
||||
ArduinoOTA.begin();
|
||||
httpSetup();
|
||||
mqttSetup();
|
||||
} else if (wifiLast == 0 || millis() - wifiLast >= 10000) {
|
||||
if (wifiLast > 0) {
|
||||
Serial.printf("[WiFi] Timeout!\n");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user