Die Anzahl der Schaltprogramme muss im Sketch analog der Anzahl im Html Dokument eingestellt sein!
Die Lokalzeit.ino ist zum ausführen der Zeitschaltuhr einzubinden.
Esp8266 Zeitschaltuhr Dual als Arduino Tab.
Zeitschaltuhr.ino
// ****************************************************************
// Sketch Esp8266 Zeitschaltuhr Dual Modular(Tab)
// created: Jens Fleischer, 2019-02-24
// last mod: Jens Fleischer, 2023-01-09
// For more information visit: https://fipsok.de
// ****************************************************************
// Hardware: Esp8266, Relais Modul o. Mosfet IRF3708 o. Fotek SSR-40 DA
// für Relais Modul
// GND an GND
// IN1 an D5 = GPIO14
// IN2 an D6 = GPIO12
// VCC an VIN -> je nach verwendeten Esp.. möglich
// Jumper JD-VCC VCC
// alternativ ext. 5V Netzteil verwenden
//
// für Mosfet IRF3708
// Source an GND
// Mosfet1 Gate an D5 = GPIO14
// Mosfet2 Gate an D6 = GPIO12
//
// für 3V Solid State Relais
// GND an GND
// SSR1 Input + an D5 = GPIO14
// SSR2 Input + an D6 = GPIO12
//
// Software: Esp8266 Arduino Core 2.6.0 - 3.1.0
// Getestet auf: Nodemcu, Wemos D1 Mini Pro, Sonoff Dual
/******************************************************************
Copyright (c) 2018 Jens Fleischer. All rights reserved.
This file is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*******************************************************************/
// Diese Version von Wochenzeitschaltuhr sollte als Tab eingebunden werden.
// #include <LittleFS.h> #include <ESP8266WebServer.h> müssen im Haupttab aufgerufen werden.
// Die Funktionalität des ESP8266 Webservers ist erforderlich.
// Der Lokalzeit Tab ist zum ausführen der Zeitschaltuhr einzubinden.
// Die Funktion "setupTimerSwitch();" muss im Setup aufgerufen werden.
// Zum schalten muss die Funktion "dualTimerSwitch();" im loop(); aufgerufen werden.
/**************************************************************************************/
constexpr auto active = LOW; // LOW für LOW aktive Relais oder HIGH für HIGH aktive (zB. SSR, Mosfet) einstellen
constexpr uint8_t devicePin[] = {D5, D6}; // Pin für Relais einstellen
constexpr auto count = 10; // Anzahl Schaltzeiten (analog Html Dokument) einstellen 2 bis 16
bool deviceState[] {!active, !active};
struct Collection {
byte switchActive[count];
byte wday[count];
char switchTime[count * 2][6];
};
Collection times;
void setupTimerSwitch() {
for (auto pin : devicePin) digitalWrite(pin, !active), pinMode(pin, OUTPUT);
File file = LittleFS.open("/stimes.dat", "r");
if (file && file.size() == sizeof(times)) { // Einlesen aller Daten falls die Datei im Dateisystem vorhanden und deren Größe stimmt.
file.read(reinterpret_cast<byte*>(×), sizeof(times)); // Deserialisierung
file.close();
} else { // Sollte die Datei nicht existieren
for (auto i = 0; i < count; i++) {
times.switchActive[i] = 1; // werden alle Schaltzeiten
times.wday[i] = ~times.wday[i]; // und alle Wochentage aktiviert.
}
}
server.on("/timer", HTTP_POST, []() {
if (server.args() == 1) {
times.switchActive[server.argName(0).toInt()] = server.arg(0).toInt();
toSave();
String temp = "\"";
for (auto& elem : times.switchActive) {
temp += elem;
}
temp += "\"";
server.send(200, "application/json", temp);
}
if (server.hasArg("sTime")) {
byte i {0};
char str[count * 14];
strcpy (str, server.arg("sTime").c_str());
char* ptr = strtok(str, ",");
while (ptr != NULL) {
strcpy (times.switchTime[i++], ptr);
ptr = strtok(NULL, ",");
}
if (server.arg("sDay")) {
strcpy (str, server.arg("sDay").c_str());
ptr = strtok(str, ",");
i = 0;
while (ptr != NULL) {
times.wday[i++] = atoi(ptr);
ptr = strtok(NULL, ",");
}
toSave();
}
else {
server.send(400, "");
}
}
String temp = "[";
for (auto& elem : times.switchTime) {
if (temp != "[") temp += ',';
temp += "\"" + static_cast<String>(elem) + "\"";
}
temp += ",\"";
for (auto& elem : times.switchActive) {
temp += elem;
}
for (auto& elem : times.wday) {
temp += "\",\"";
temp += elem;
}
temp += "\"]";
server.send(200, "application/json", temp);
});
server.on("/timer", HTTP_GET, []() {
if (server.hasArg("tog") && server.arg(0) == "0") {
deviceState[0] = !deviceState[0]; // Relais1 Status manuell ändern
}
if (server.hasArg("tog") && server.arg(0) == "1") {
deviceState[1] = !deviceState[1]; // Relais2 Status manuell ändern
}
server.send(200, "application/json", ("[\"" + static_cast<String>(deviceState[0] == active) + "\",\"" + static_cast<String>(deviceState[1] == active) + "\"," + localTime() + "]"));
});
}
void toSave() {
File file = LittleFS.open("/stimes.dat", "w");
if (file) {
file.write(reinterpret_cast<byte*>(×), sizeof(times)); // Serialisierung
file.close();
}
}
void dualTimerSwitch() {
static uint8_t lastmin {CHAR_MAX}, lastState[] {active, active};
localTime();
if (tm.tm_min != lastmin) {
lastmin = tm.tm_min;
char buf[6];
snprintf(buf, sizeof(buf), "%.2d:%.2d", tm.tm_hour, tm.tm_min);
for (auto i = 0; i < count * 2; i++) {
if (times.switchActive[i / 2] && !strcmp(times.switchTime[i], buf)) {
if (times.wday[i / 2] & (1 << (tm.tm_wday ? tm.tm_wday - 1 : 6))) {
i < (count % 2 ? count + 1 : count) ? deviceState[0] = i % 2 ? !active : active : deviceState[1] = i % 2 ? !active : active; // Relais Status nach Zeit ändern
}
}
}
}
if (deviceState[0] != lastState[0] || deviceState[1] != lastState[1]) { // Relais schalten wenn sich der Status geändert hat
for (auto i = 0; i < 2; i++) {
lastState[i] = deviceState[i];
digitalWrite(devicePin[i], deviceState[i]);
Serial.printf("Gerät %d %s\n", 1 + i, digitalRead(devicePin[i]) == active ? "an" : "aus");
}
}
}
Die Anzahl der Schaltprogramme muss im Html Dokument analog der Anzahl im Sketch eingestellt sein!
Die Webseite zur Esp8266 Zeitschaltuhr dual.
zeitschaltuhrdual.html
<!DOCTYPE HTML> <!-- For more information visit: https://fipsok.de -->
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<title>Dual Zeitschaltuhr</title>
<style>
main, #bu, .tab button, input:checked + label {
color: #15dfdf;
}
div {
display: flex;
}
span {
padding: 0.5em;
}
input {
height: auto;
font-weight: bold;
background-color: inherit;
font-size: 3em;
color: inherit;
border: solid #555;
}
label {
font-size: .9em;
}
svg {
height: 4em;
}
* + [id*=ak]{
margin-top: .6em;
}
div+span {
margin-left: 3em;
}
input + label {
color: #777;
font-style: italic;
}
#tog0,#tog1 {
margin: .7em 1em;
cursor: pointer;
color: #777;
}
time {
text-shadow: 2px 2px 2px black;
font-size: 1.3em;
font-weight: bold;
margin: auto;
}
.note:after {
content: "Schaltzeiten gespeichert";
color: #777;
}
#bu {
border: outset #555;
}
.tab {
overflow: hidden;
}
.tab button {
background-color: #999;
border: none;
margin-top: 0em;
transition: 0.8s;
border-radius: .5em .5em 0 0;
}
.tab button:hover {
background-color: #666;
}
#bu, .tabcontent, .tab button.active {
background-color: #333;
}
.tabcontent {
display: block;
padding: .5em .7em .5em .1em;
box-shadow: 5px 3px 10px #4e4d4d;
}
.tabcontent [name^=bu] {
width: 2em;
cursor: pointer;
}
.none {
color: #777 !important;
}
.hide {
display: none;
}
@media only screen and (max-width: 600px) {
input {
font-size: 2.4em;
width: auto;
border: none;
}
.tab button,#bu {
width: 7em;
}
#tog1 {
margin: .7em 0 0 0;
}
div+span {
margin-left: .2em;
}
}
</style>
<script>
var count = 10; <!-- Anzahl Schaltzeiten (analog Sketch) einstellen 2 bis 40 -->
var d = document;
d.addEventListener('DOMContentLoaded', () => {
dom(), renew();
d.querySelector('#bu').addEventListener('click', () => {
let arr = [], formData = new FormData();
formData.append('sTime', Array.from(d.querySelectorAll('input[type=time]')).map(x => x.value != 0 ? x.value : 0));
for (let i = 0; i < count; i++) {
let x = 0;
d.querySelectorAll(`input[name=c${i}]`).forEach((el, i) => { if (el.checked) x = x | (1 << i) });
arr.push(x);
}
formData.append(`sDay`, arr);
send(formData);
});
d.querySelector('#tab1').addEventListener('click', openTab);
d.querySelector('#tab2').addEventListener('click', openTab);
d.querySelector('#tog0').addEventListener('click', renew);
d.querySelector('#tog1').addEventListener('click', renew);
for (var i = 0; i < count;) d.querySelector(`[name=bu${i++}]`).addEventListener('click', setActive);
},send(), setInterval(renew, 1000));
function dom() {
var buf = '';
for (var i = 0; i < count*2; i++) {
buf += `${i%2 ? `<span> -- </span>` : `<div id="ak${i/2}"><span name="bu${i/2}"></span>`}<input type="time" name="sz${i}" value="">${i%2 ? `</div><span id="t${i/2|0}">` : ""}`;
if (i%2) {
['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'].forEach(v => {
buf += `<input type="checkbox" name="c${(i-1)/2}"><label>${v} </label>`;
});
buf += "</span>";
}
if (i == (count%2 ? count : count-1)) {
d.querySelector('#ctab1').insertAdjacentHTML('beforeend', buf);
buf = '';
}if (i == count*2-1) d.querySelector('#ctab2').insertAdjacentHTML('beforeend', buf);
}
}
function setActive() {
let formData = new FormData();
formData.append(this.parentNode.id.substr(2, 5), this.textContent == 'ON' ? '0' : '1');
send(formData);
}
function send(arg) {
fetch('/timer', {
method: 'post',
body: arg
}).then(resp => {
if (resp.ok) {
if(arg && arg.has('sTime')) {
let el = d.querySelector('u').classList;
el.add('note');
setTimeout(() => {
el.remove('note');
}, 5e3);
}
}
return resp.json();
}).then(array => {
if (array.length > count) {
array.forEach((v, i) => {
if (i < count*2) d.querySelector(`[name=sz${i}]`).value = v;
if (i == count*2) getActive(v);
if (i > count*2) {
let el = d.getElementsByName(`c${i - count * 2 - 1}`);
for (let k in el) {
v & (1 << k) ? el[k].checked = true : el.checked = false;
}
}
});
}
else {
getActive(array);
}
});
}
function getActive(arg) {
for (var i = 0; i < count; i++) {
d.querySelector(`[name=bu${i}]`).textContent = (arg[i]%2 ? 'ON' : 'OFF');
let el = d.getElementById(`ak${i}`).classList;
arg[i]%2 ? el.remove("none") : el.add("none");
d.getElementById(`t${i}`).childNodes.forEach(v => {arg[i]%2 ? v.classList.remove("none") : v.classList.add("none")});
}
}
function openTab() {
let a = event.target.id.charAt(3)%2+1;
d.getElementById(`ctab${a}`).classList.add("hide");
d.getElementById(`tab${a}`).classList.remove("active");
d.getElementById('c' + event.target.id).classList.remove("hide");
event.target.classList.add("active")
}
function renew(ev) {
if (ev) ev = ev.currentTarget.id.slice(3, 4);
fetch(`timer?tog=${ev}`).then(resp => {
return resp.json();
}).then(array => {
for (var i = 0; i < 2; i++) {
d.getElementById(`body${i}`).style.fill=array[i] == 0 ? '' : '#ff0';
d.getElementById(`on${i}`).style.visibility=array[i] == 0 ? 'hidden' : 'visible';
}
d.querySelector('time').innerHTML = array[2][0];
});
}
</script>
</head>
<body>
<h2>Zeitschaltuhr</h2>
<main>
<div class="tab">
<button class="active" id="tab1">⏳ Device 1</button>
<button id="tab2">⏳ Device 2</button>
<time>00:00:00</time>
</div>
<div id="ctab1" class="tabcontent">
</div>
<div id="ctab2" class="tabcontent hide">
</div>
</main>
<div>
<button class="button" id="bu">⏰ Speichern</button>
<div id="tog0">
D1
<svg viewBox="0 0 486 486">
<use id="body0" href="#bulb"/>
<use id="on0" href="#beam"/>
</svg>
</div>
<div id="tog1">
D2
<svg viewBox="0 0 486 486">
<use id="body1" href="#bulb"/>
<use id="on1" href="#beam"/>
</svg>
</div>
</div>
<u></u>
<svg class="hide">
<path id="bulb" d="m256.5 160.8c0-7.4-6-13.5-13.5-13.5-47.6 0-86.4 38.7-86.4 86.4 0 7.4 6 13.5 13.5 13.5 7.4 0 13.5-6 13.5-13.5 0-32.8 26.7-59.4 59.4-59.4 7.5 0 13.5-6 13.5-13.5zm106.2 72.5c0 32.3-12.8 61.6-33.6 83.1-15.8 16.4-26 37.3-29.4 59.6-1.5 9.6-9.8 16.7-19.6 16.7h-74.3c-9.7 0-18.1-7-19.5-16.6-3.5-22.3-13.8-43.5-29.6-59.8-20.4-21.2-33.1-50-33.4-81.7-0.7-66.6 52.3-120.5 118.9-121 66.5-0.5 120.5 53.3 120.5 119.7zm-64.3 191.4v14.2c0 11.3-8.3 20.7-19.1 22.3l-3.5 12.9c-1.9 7-8.2 11.9-15.5 11.9h-34.7c-7.3 0-13.6-4.9-15.5-11.9l-3.4-12.9c-10.9-1.7-19.2-11-19.2-22.4v-14.2c0-7.6 6.1-13.7 13.7-13.7h83.5c7.6 0.1 13.7 6.2 13.7 13.8z"/>
<path id="beam" fill="#ff0" d="m376.57 341.98c-5.3-5.3-13.8-5.3-19.1 0s-5.3 13.8 0 19.1l33.5 33.5c2.6 2.6 6.1 3.9 9.5 3.9s6.9-1.3 9.5-3.9c5.3-5.3 5.3-13.8 0-19.1zm-262.8-224.8c2.6 2.6 6.1 3.9 9.5 3.9s6.9-1.3 9.5-3.9c5.3-5.3 5.3-13.8 0-19.1l-33.5-33.5c-5.3-5.3-13.8-5.3-19.1 0s-5.3 13.8 0 19.1zm253.3 4c3.4 0 6.9-1.3 9.5-3.9l33.5-33.5c5.3-5.3 5.3-13.8 0-19.1s-13.8-5.3-19.1 0l-33.5 33.5c-5.3 5.3-5.3 13.8 0 19.1 2.7 2.6 6.1 3.9 9.6 3.9zm-253.3 220.8-33.5 33.5c-5.3 5.3-5.3 13.8 0 19.1 2.6 2.6 6.1 3.9 9.5 3.9s6.9-1.3 9.5-3.9l33.5-33.5c5.3-5.3 5.3-13.8 0-19.1-5.2-5.3-13.8-5.3-19 0zm351.1-125.9h-47.3c-7.4 0-13.5 6-13.5 13.5 0 7.4 6 13.5 13.5 13.5h47.3c7.4 0 13.5-6 13.5-13.5 0-7.4-6-13.5-13.5-13.5zm-378.6 13.5c0-7.4-6-13.5-13.5-13.5h-47.3c-7.4 0-13.5 6-13.5 13.5 0 7.4 6 13.5 13.5 13.5h47.3c7.5 0 13.5-6 13.5-13.5zm158.9-158.9c7.4 0 13.5-6 13.5-13.5v-47.3c0-7.4-6-13.5-13.5-13.5s-13.5 6-13.5 13.5v47.3c0 7.5 6.1 13.5 13.5 13.5z"/>
</svg>
</body>
</html>