Ich hab jetzt noch mal intensiv über die Aufgabe gehirnt
Dabei habe ich eine, wie ich glaube, recht schicke Variante:
Items (nur angedeutet...)
Code: Alles auswählen
Group SchaltUhr // Alle Stell-Items
Number Uhr1_Ein_H (SchaltUhr)
Number Uhr1_Ein_M (SchaltUhr)
Number Uhr1_Aus_H (SchaltUhr)
Number Uhr1_Aus_M (SchaltUhr)
Number Uhr2_Ein_H (SchaltUhr)
Number Uhr2_Ein_M (SchaltUhr)
Number Uhr2_Aus_H (SchaltUhr)
Number Uhr2_Aus_M (SchaltUhr)
Number Uhr6_Aus_H (SchaltUhr)
Number Uhr6_Aus_M (SchaltUhr)
Number Uhr7_Aus_H (SchaltUhr)
Number Uhr7_Aus_M (SchaltUhr)
Group SchaltMinute // Alle Schaltzeiten
Number Uhr1_Ein (SchaltMinute)
Number Uhr2_Ein (SchaltMinute)
Number Uhr1_Aus (SchaltMinute)
Number Uhr2_Aus (SchaltMinute)
Number Uhr6_Aus (SchaltMinute)
Number Uhr7_Aus (SchaltMinute)
Switch planTimer // ein Trigger
Die Rules:
Code: Alles auswählen
var String nextName // Name des aktuellen Timers
var Timer nextTimer = null // der Timer
rule "Zeiten berechnen"
when
Time cron " 0 0 0 * * ?" or // Mitternacht
System started or // Neustart oder Rules neu geladen
Member of SchaltUhr changed // oder Schaltzeit geändert
then
logDebug("timer","Zeiten berechnen")
var Number hour = null
var Number minute = null
SchaltMinute.members.forEach[n|
hour = if(SchaltUhr.filter[u|u.name.contains(n.name + "_H")].head.state instanceof Number) SchaltUhr.filter[u|u.name.contains(n.name + "_H")].head.state else null // falls gültige Stunde, übernehmen
minute = if(SchaltUhr.filter[u|u.name.contains(n.name + "_M")].head.state instanceof Number) SchaltUhr.filter[u|u.name.contains(n.name + "_M")].head.state else null // falls gültige Minute, übernehmen
n.postUpdate(if((hour instanceof Number) && (minute instanceof Number)) hour * 60 + minute else NULL) // Falls gültige Zeit, eintragen
logInfo("timer","Schaltzeit {} auf {}:{} Uhr ({}) gesetzt.",n.name,hour,minute,n.state)
]
logDebug("timer","Zeiten berechnen fertig")
end
rule "Timer anlegen"
when
Member of SchaltMinute received update or // Schaltzeit getriggert
planTimer received command // Nachtriggern (nächsten Timer setzen)
then
logDebug("timer","Timer setzen")
if(nextTimer !== null) nextTimer. cancel // alten Timer löschen, falls noch aktiv
nextName = SchaltMinute.filter[n| // nur gültige Schaltzeiten
n.state instanceof Number
].filter[m| // nur solche, die in der Zukunft liegen
(m.state as Number) > now.getMinuteOfDay
].sortBy[state].head.name // nur der nächste -> Namen merken
logDebug("timer","Timer setzen für {}",nextName)
nextTimer = createTimer(now.withTimeAtStartOfDay.plusMinutes(SchaltMinute.filter[n| // setze Timer
n.state instanceof Number // nur gültige Schaltzeit
].filter[m|
(m.state as Number) > now.getMinuteOfDay // nur in der Zukunft
].sortBy[state].head.state),[ // nur der nächste
logInfo("timer","Timer ausgelöst: {}",nextName)
if(Schalter_manu_BZ.state != OFF) { // falls manueller Betrieb
logInfo("timer","Aber manueller Betrieb, Abbruch!")
planTimer.sendCommand(OFF) // nächsten Timer planen
return; // und Schluss
}
val Number soll = if(nextName.contains("Ein") 11 else 1 // Sollzustand nach Timer
logInfo("timer","geforderter Schaltvorgang: {}",Soll)
if((nextName.contains("1") || // Soll der Timer berücksichtigt werden?
(nextName.contains("2") && SW_2_BZ.state == ON) ||
(nextName.contains("6") && SW_Sa_BZ.state == ON && now.getDayOfWeek == 6) ||
(nextName.contains("7") && SW_So_BZ.state == ON) && now.getDayOfWeek == 7) {
logInfo("timer","Schaltvorgang aktiv")
if((Thermostat_BZ.state as Number) != soll) Thermostat_BZ.sendCommand(soll) else logInfo("timer","aber Zustand bereits erreicht")
} else {
logInfo("timer","Schaltvorgang inaktiv")
}
planTimer.sendCommand(OFF)
])
logDebug("timer","Timer setzen fertig")
end
WARNUNG: Der Code ist ungetestet! Falls Du magst, kannst Du den Code parallel einrichten, dann solltest Du die echten Schaltvorgänge auskommentieren und mal beobachten, ob die Rules so wie geplant funktionieren.
Die Idee hinter dem Code:
Die erste Rule kümmert sich nur darum, die gewählten Zeiten in "Tagesminuten" umzurechnen. Dabei werden die Zeiten in korrespondierenden Items gespeichert. Durch Verwendung der Gruppen ist der entstehende Code extrem kompakt.
Die zweite Rule erzeugt jeweils den nächsten anstehenden Timer. Dabei wird bewusst darauf verzichtet, auf Schaltstellungen zu achten. Dadurch muss der Timer nur dann neu erzeugt werden, wenn eine Schaltzeit geändert wird, nicht aber, wenn der Modus gewechselt wird. Ein angelegter Timer kostet nur wenig Speicher und praktisch keine Rechenzeit, da sich der Scheduler um die Ausführung kümmert.
Wenn der Timer abläuft, wird geprüft, ob der manuelle Modus aktiv ist. sollte das der Fall sein, wird der nächste Timer generiert und danach die Ausführung abgebrochen.
Als nächstes wird der Sollzustand ermittelt.
Dann wird geprüft, ob die Schaltzeit berücksichtigt werden soll (2. Schaltzeit aktiv, Samstag aktiv, Sonntag aktiv), dies natürlich in Bezug auf die gerade aktive Schaltzeit und unter Berücksichtigung des Wochentags.
Abschließend wird, falls alles passt, der Schaltbefehl ausgeführt, natürlich nur, falls das nötig ist.
Die Zeiten werden streng aufsteigend abgearbeitet, falls man z.B. folgende Zeiten einstellt (alle aktiv):
1. Ein 4:00
1. Aus 20:00
2. Ein 15:00
2. Aus 12:00
6. Aus 14:00
7: Aus 19:00
wird Samstags um 4 Uhr eingeschaltet (1. Timer),
um 12 Uhr ausgeschaltet (2. Timer),
um 14 Uhr nochmals ausgeschaltet, falls zwischenzeitlich anderweitig wieder eingeschaltet wurde (Samstag),
um 15 Uhr eingeschaltet (2. Timer) und
um 20 Uhr ausgeschaltet (1. Timer).
Die Codezeilen mit filter[] können natürlich auch in einer Zeile geschrieben werden, ich wollte hier lediglich die Lesbarkeit erhöhen
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet