- Unterstützt "Funkhygene auch in Scripten
- Setzen von beliebiger Anzahl von Datenpunkten (Systemvariablen oder Datenpunkte eines Gerätes) in definierbarer zeitlicher Reihenfolge
- Automatisches Iterieren durch ein Gewerk und setzen eines Datenpunktes auf einen beliebigen Wert, auch hier in definierter Zeitlicher Reihenfolge
- Blockiert nicht HM Script, läuft nach dem Aufruf als prozess im Hintergrund
- ab V1.1 bei Aktoren, welche Übergabe der Einschaltdauer erlauben, kann dies jetzt als 4. Parameter mit übergeben werden (optional) (angelehnt an extratimer.tcl)
Bekannterweise gibt es HM-Script nicht her, aus einem Script heraus zeitverzögert Datenpunkte mit Werten zu beschreiben. In der Klick Bunti geht dieses über "verzögert um" aber halt nicht in Scripten. Gerade in Scripten ist es aber wünschenswert beispielsweise beim Iterieren durch Gewerke, aufgrund der Funkhygene Kommandos zeitverzögert abzusetzen. In Javascript zum Beispiel über den Befehl setStateDelayed in IOBroker. In HM Script ist dies bisher so nicht möglich.
(Die möglichen Umwege über temporäres Erzeugen eines temporären HM- Programms durch HMScript sowie nutzung von extratimer.tcl sind bekannt)
Die von mir hier vorgestellte Lösung geht einen anderen Ansatz:
Über einen CUxD.Exec Aufruf wird ein TCL Script gestartet, welches als Process auf der CCU läuft. In der Übergabe wird dem Script eine Bearbeitungsliste mitgegeben. Nachdem der Process gestartet wurde, erfolgt die Rückkehr in das HM Script. Das setzen der Datenpunkte erfolgt dann im Hintergrund, das HM-Script wird nicht blockiert durch die Verzögerungszeiten, die im Hintergrund im TCL process laufen.
Anwendungsgruppe: Eher Fortgeschrittene, die sich schon mit Scriptprogrammierung auskennen.
Lauffähig auf: Raspberrymatik (getestet mit 2.31.25.20180120 und CCU2 (getestet mit 2.31.25) mittlerweile mit 3.37.8.20181026
Voraussetzung : Installiertes CUxd mit CUxd Exec Gerät (Grund, ich hab den Prozess nicht stabil mit System.Exec als & gestatet bekommen)
Installation:
Das TCL Script unter dem Namen timeschedule.tcl abspeichern, via Filezilla oder ähnlichem Programm ins Verzeichnis /usr/local auf der CCU kopieren und dem Script die Rechte 755 geben
Aufruf und Syntax.
Aufruf aus einem HM Script heraus mit
Code: Alles auswählen
string sTimer= "Ich bin das Kommando";
dom.GetObject("CUxD.CUX2801001:1.CMD_EXEC").State (sTimer);
Option -c : Compare, es wird verglichen, ob momentane Wert des Datenpunktes dem neuen Wert entspricht, wenn ja, wird kein schreiben durchgeführt
Option -d: Debug. erfolgreiches Setzen eines Datenpunktes wird im Systemlog dokumentiert
Option -g: Gewerk, dazu später unten mehr
hierbei bedeutet ID1: ID des zu setzenden Datenpunktes (es wird testet, ob der DP Systemvariable oder Datenpunkt vom Kanal ist
T1 : Verzögerungszeit in Sekunden
V1 : neuer Wert
ED1: optinal Einschaltdauer in Sekunden (es erfolgt interner Test, ob zu dem Datenpunkt auch ein Datenpunkt ON_TIME gehört)
ID1 T1 und V1 ED1 jeweils getrennt durch Leerzeichen
bei mehreren Werten, komma, dann ID2 T2 und V2 , optinal ED2 gemäß wie oben
praktisches Beispiel, setzen von 2 Systemvariablen mit Namen S1 und S2 mit zeitverzögertem Versatz
sTimer='tclsh /usr/local/timerschedule.tcl -d "S1 4 HUHU,S2 4 Tralala"';
Wichtig
Das Kommando muss IMMER in Hochkommata " stehen
da debug angewählt wurde, lässt sich das ergebnis im log nachvollziehen
Code: Alles auswählen
Feb 8 14:55:13 homematic-raspi user.debug TimerPipe: [Start Scheduler mit (2) State(s) - Black in Januar 2018]
Feb 8 14:55:17 homematic-raspi user.debug TimerPipe: [S1 erfolgreich auf HUHU]
Feb 8 14:55:21 homematic-raspi user.debug TimerPipe: [S2 erfolgreich auf TRALALA]
Zu sehen ist hier nach dem Start des Schedulers die jeweils 4 Sekunden Differenzzeit zwischen den beiden Setzvorgängen. Daraus ergibt sich auch, die zeiten sind additive kettenzeiten, nach der Ausführung der ersten zeit begintn der Start der 2. Zeit. Muss man im Hinterkopf behalten bei der Parametrierung
Auch Wichtig.
Datenpunkte oder Systemvariablennabem mit Leerzeichen oder Sonderzeichen mag das Script nciht so wirklich. Lösungsansatz hier: Anstatt den Namen des Datenpunktes die ID des Datenpunktes übergeben.
sTimer= 'tclsh /usr/local/timerschedule.tcl -d "2654 4 NUMMER"'
2654 ist dem diesem Fall die ID der Systemvariablen S1 vom Beispiel vorher
Das Script verändert nur Datenpunkte mit der Typeigenschaft VARDP bzw HSSDP. hierbei wird schon abgefangen, wenn eine übergebene ID nicht existiert oder etwas anderes aus der regadom ist als der Identifier eines Datenpunktes oder einer Systemvariablen.
--------------------------------------------------------------------------------------------------
Aufruf mit Option -g in dem Moment läuft die Routine über ein Gewerk der CCU
tclsh /usr/local/timerschedule.tcl -g "GewerkID StateName T1 V1 (ED1)"
hierbei wird durch alle Kanäle des Gewerks Iteriert und dabei zeitverzögert die einzelnen Datenpunkte auf den Werte V1 gesetzt
Beispiel
tclsh /usr/local/timerschedule.tcl -d -g "Rollo LEVEL 4 1.0"
dieser Aufruf iteriert durch das Gewerk Rollo und setzt zeitlich verzögert alle Datenpunkte mit Namen "LEVEL" mit 4 Sekunden Abstand auf 1.0 = 100%
Auch hier gelten die gleichen Gesetzmäßigkeiten, enthält das Gewerk Leer oder Sonderzeichen die ID des Gewerkes übergeben.
Beispiel Gewerk Licht
tclsh /usr/local/timeschedule.tcl -d -g "Licht STATE 4 true 300"
Dieser Aufruf Iteriert durch das gewerk Licht und setzt Zeitverzögert um 4 sekunden alle DatenPunkte mit Namen STATE mit der internen Einschaltdauer 300s auf ein.
Mit dem Mode Debug wird dieses auch im Syslog geschrieben. Hat ein State keine Einschaltdauer oder wurde keine angegeben so fehlt auch im log der Text mit Einschaltdauer x s
Code: Alles auswählen
Feb 9 15:38:28 homematic-raspi user.debug TimerPipe: [Start Scheduler mit (2) State(s) - Black in Januar 2018]
Feb 9 15:38:28 homematic-raspi user.debug TimerPipe: [BidCos-RF.LEQ1319211:1.STATE erfolgreich auf 1 mit Einschaltdauer 10 s]
Feb 9 15:38:32 homematic-raspi user.debug TimerPipe: [BidCos-RF.LEQ1319211:2.STATE erfolgreich auf 1 mit Einschaltdauer 10 s]
Wichtig
Ist der Prozess einmal über CMD_EXEC gestartet, lässt er sich nicht stoppen durch erneutes Starten mit übergabe anderer Werte. Sollte wirklich einmal der Vorgang abgebrochen werden müssen, so muss die PID des Prozesses ermittelt werden und darauf dann ein KILL gesetzt werden. Normalerweise ist das bei reinem Einsatz zur Funkhygene unkritisch.
Danke an Uwe für den nächsten Tip. Auszug aus dem CUxD Handbuch:
Code: Alles auswählen
HM-Script ohne Rückgabe von STDOUT (Beispiel):
dom.GetObject("CUxD.CUX2801001:1.CMD_SETS").State("wget -q -O /dev/null
'http://192.168.0.99:50000/track=neue_email.mp3'");
dom.GetObject("CUxD.CUX2801001:1.CMD_RUNS").State(1);
Die Verarbeitung des HM-Scripts wird sofort und unabhängig von der Laufzeit des aufgerufenen
Programms fortgesetzt!
für Längere Zeiten gibt es aber andere, bessere Alternativen wie beispielsweise CronTab, CUxD timer
Script Version V1.3 Novemer 2018
Erweitert: Option -f (Syntax wie Gewerk, iteriert aber durch einen Favoriten)
Erweiterte Option -r (Syntax wie gewerk, iteriert aber durch einen Raum)
Zusatzoption -s
V1.2 Ergängung -s bei Gewerk,Raum,Favorit
Wenn als Datenpunkt Level angegeben wurde, wird bei -s geprüft.
gibt es keinen Datenpunkt Level, wird Versucht, den Datenpunkt "State" zu erreichen.
gibt es diesen Datenpunkt, so wird bei Level >=0 der Wert State 1 eingetragen, wei Wert Level=0 der Wert State =0 eingetragen
tclsh /usr/local/timeschedule.tcl -g -s "GwerkID StateName T1 V1 (ED)"
Beispiel für Aufruf -c -g -s "Licht LEVEL 1.0 0.0"
Script läuft durch das Gewerk Licht, Sucht alle Datenpunkte Level und setzt diese auf den Wert 0, so er nicht schon nicht den Wert 0 hat.
Hat der Channel keinen Datenpunkt Level, so wird der Datenpunkt State (Option -s) genommen. bei Wert V1= 0.0 wird State = false gesetzt so state noch nicht den Wert false hat, bei V1 <> 0.0 wird State auf true gesetzt, wenn er nocht nicht den wert true hat
Code: Alles auswählen
#!/bin/tclsh
# Zeitverzögertes setzen von Datenpunkten aus einem HM script heraus.
# Michael Thelen aka Black in November 2018
#
# bekannterweise gibt es keine sinnig vorhandene Methode, um zeitverzögert Datenpunkte zu setzen (Grund Funkdisziplin)
# Abhilfe: Dieses TCL Script
# Aufruf:bitte über CUxD.Exec, nich tüber system Exec. mit der & option hab ichs mit System Exec nicht stabil zum Laufen bekommen
# Syntax
# tclsh /usr/local/timeschedule.tcl [-c] [-d] "ID1 T1 V1 (ED)[, ID2 T2 V2 (ED2)]"
# hierbei bedeutet ID1: ID des zu setzenden Datenpunktes (es wird testet, ob der DP Systemvariable oder Datenpunkt vom Kanal ist
# T1 : Verzögerungszeit in Sekunden
# V1 : neuer Wert
# ID1 T1 und V1 jeweils getrennt durch Leerzeichen
# bei mehreren Werten, komma, dann ID2 T2 und V2 gemäß wie oben
#
# Aufruf mit Option -g in dem Moment läuft die Routine über ein Gewerk der CCU
# tclsh /opt/usr/timeschedule.tcl -g "GewerkID StateName T1 V1 (ED)"
# hierbei wird durch alle Kanäle des Gewerks Iteriert und dabei Zeitverögert die Einzelnen Datenpunkte auf den Werte V1 gesetzt
#
# Aufruf mit der Option -r in dem Moment läuft die Routine über einen Raum der CCU
# tclsh /opt/usr/timeschedule.tcl -r "RaumID StateName T1 V1 (ED)"
# hierbei wird durch alle Kanäle des Raumes Iteriert und dabei Zeitverögert die Einzelnen Datenpunkte auf den Werte V1 gesetzt
#
# Aufruf mit der Option -f in dem Moment läuft die Routine über einen Favoriten der CCU
# tclsh /opt/usr/timeschedule.tcl -f "FavoritenID StateName T1 V1 (ED)"
# hierbei wird durch alle Kanäle des Favoriten Iteriert und dabei Zeitverögert die Einzelnen Datenpunkte auf den Werte V1 gesetzt
#
# V1.1 Ergänzung Optionaler Parameter ED (Einschaltdauer)
# Wird dieser Parameter mitgegeben, wird überprüft, ob dieser Channel einen Datenpunkt ON_TIME besitzt,
# Wenn ja, wird dieser Datenpunkt mit der voreingestellten Zeit beschrieben,bevor der Schaltdatenpunkt gesetzt wird
#
# V1.2 Ergängung -s bei Gewerk,Raum,Favorit
# Wenn als Datenpunkt Level angegeben wurde, wird bei -s geprüft.
# gibt es keinen Datenpunkt Level, wird Versucht, den Datenpunkt "State" zu erreichen.
# gibt es diesen Datenpunkt, so wird bei Level >=0 der Wert State 1 eingetragen, wei Wert Level=0 der Wert State =0 eingetragen
# tclsh /usr/local/timeschedule.tcl -g -s "GwerkID StateName T1 V1 (ED)"
# Beispiel für Aufruf -c -g -s "Licht LEVEL 1.0 0.0"
# Script läuft durch das Gewerk Licht, Sucht alle Datenpunkte Level und setzt diese auf den Wert 0, so er nicht nicht den Wert 0 hat.
# Hat der Channel keinen Datenpunkt Level,
# so wird der Datenpunkt State (Option -s) genommen. bei Wert V1= 0.0 wird State = false gesetzt, bei V1 <> 0.0 wird State auf true gesetzt
#
# V1.3 Ergängung -z bei Gewerk, Raum, Favorit (Szene) noch nicht implementiert
# es wird iteriert und dabei aus
# Szene: Liest
# ##################################################################################################################################
load tclrega.so
proc option {_argv name {_var ""}} {
upvar 1 $_argv argv $_var var
set pos [lsearch -regexp $argv ^$name]
if {$pos>=0} {
if {$_var != ""} {
set var 1
}
set argv [lreplace $argv $pos $pos]
incr ::argc -1
return 1
}
return 0
}
set blackversion "TIME_Scheduler_V13"
# option compare: macht nur setstate wenn value <> neuem Wert ist
set compare [option argv -c]
# option debug: gibt über logger aus, wenn über hm script datenpunkt gesetzt wird
set debug [option argv -d]
# option gewerk: läuft über ein Gewerk
set gewerk [option argv -g]
# option Raum: läuft über einen Raum
set raum [option argv -r]
# option favorit: läuft über einen Favoriten
set favorit [option argv -f]
set iteration 0
if {$gewerk} {
set scriptid "ID_FUNCTIONS"
set scriptname "Gewerk"
set iteration 1
}
if ($raum) {
set scriptid "ID_ROOMS"
set scriptname "Raum"
set iteration 1
}
if ($favorit) {
set scriptid "ID_FAVORITES"
set scriptname "Favorit"
set iteration 1
}
# nur aktualisieren (es wird keinenuer wert zugewiesen sondern der alte Wert
# führt dazu, das ein Trigger auf aktualisierung erkannt wird ohne Wertänderung
# bei actual wird das compare Flag ignotiert
set actual [option argv -a]
# -s
# Wenn als Datenpunkt Level angegeben wurde, wird bei -s geprüft.
# gibt es keinen Datenpunkt Level, wird Versucht, den Datenpunkt "State" zu erreichen.
# gibt es diesen Datenpunkt, so wird bei Level >=0 der Wert State 1 eingetragen, wei Wert Level=0 der Wert State =0 eingetragen
set lstate [option argv -s]
#puts $actual
if { $argc < 1 } {
puts "$blackversion by Black in November 2018"
puts "Aufruf tclsh timeschedule.tcl \[-c\] \[-d\] \[-g\] \[-s\] \[-s\]\"COMMANDO\""
exit 1
}
if {$iteration} {
# Es ist eine Iteration, dann erstmal den Code für Iteration abarbeiten
set ts [split [lindex $argv 0] " "]
set duration [lindex $ts 4]
# wenn duration gültig, diese mit in den String einbinden, ansonsten EInfügestring = Leerstring
if {[string is double $duration] && ($duration> 0)} {
set sed " $duration"
} else {
set sed ""
}
append cmd "string sID;string sTCL=\"\";string sTime=\"0.0\";object oFunc= dom.GetObject ($scriptid).Get(\"[lindex $ts 0]\");"
append cmd "if (oFunc)\{foreach (sID,oFunc.EnumUsedIDs() ) \{"
set dp [string toupper [lindex $ts 1]]
puts $dp
append cmd "object oID= dom.GetObject (sID).DPByHssDP (\"$dp\");"
append cmd "if (oID) \{"
append cmd "if (sTCL != \"\") \{sTCL = sTCL # \",\";\}"
append cmd "sTCL= sTCL # oID.ID () # \" \" # sTime # \" [lindex $ts 3]$sed\";sTime=\"[lindex $ts 2]\";\}"
if {$lstate} {
if {[string compare $dp "LEVEL"]==0} {
# Der Original DP ist ein LEVEL
set newval [lindex $ts 3]
set stateval "true"
if {[string compare $newval "0.0"]==0} {
set stateval "false"
}
# Hier nun probieren, obs dort den Datenpunkte State gibt und diesen setzen
append cmd " else \{"
append cmd "oID= dom.GetObject (sID).DPByHssDP (\"STATE\");"
append cmd "if (oID) \{"
append cmd "if (sTCL != \"\") \{sTCL = sTCL # \",\";\}"
append cmd "sTCL= sTCL # oID.ID () # \" \" # sTime # \" $stateval$sed\";sTime=\"[lindex $ts 2]\";\}"
# das ist die klammer vom else
append cmd "\}"
# puts $stateval
}
}
# Dies ist die Klammer von foreach
append cmd "\}"
# Dies ist die Klammer von if (oFunc)
append cmd "\}"
# puts $cmd
array set values [rega_script $cmd]
unset cmd
if { [info exists values(sTCL)]} {
set x $values(sTCL)
set para [split $x ","]
} else {
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
exec logger -t $blackversion -p user.debug \[Error in HMScript $scriptname\]
exit 1
}
} else {
# Keine Iteration, damit die liste aus argv holen und splitten
set para [split [lindex $argv 0] ","]
}
# Ab hier läuft das Hauptprogramm
#puts $para
set params [llength $para]
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
exec logger -t $blackversion -p user.debug \[Start Scheduler mit \($params\) State\(s\) - Black in November 2018\]
# durch die Anzahl an Datenpunkten durchlaufen
foreach lData $para {
set ts [split $lData " "]
set id [lindex $ts 0]
set delay [lindex $ts 1]
set value [lindex $ts 2]
set duration [lindex $ts 3]
# Warten nur wenn delay sinnige eingabe ist
if {[string is double $delay] && ($delay> 0)} {
set wait_in_ms [expr $delay * 1000 + 1]
after [expr int($wait_in_ms)]
}
# Warten zuende
if {[string is double $duration] && ($duration> 0)} {
append sdur "object oChan= dom.GetObject (o1.Channel());if ((oChan) && (o1.Type()==OT_HSSDP)) \{"
append sdur "object oDur=oChan.DPByHssDP (\"ON_TIME\");if (oDur) \{"
append sdur "oDur.State ($duration);retDur= $duration;\}\}"
} else {
append sdur ""
}
append cmd "string retID=\"\";string retDur=\"\";object o1= dom.GetObject (\"$id\");if (o1)\{"
append cmd "if ((o1.Type()==OT_HSSDP)||(o1.Type()==OT_VARDP)) \{"
if { $compare && !$actual} {
append cmd "if (o1.Value()!=$value) \{"
}
# Zuweisung Schreiben
# Bei actual ist es der eigene Wert und es wird dabei auch keine Einschaltdauer geschrieben
if { $actual } {
append cmd "o1.State (o1.Value () ); "
set value "alten Wert aktualisiert"
} else {
append cmd "$sdur o1.State (\"$value\");"
}
# Nme in Rückgabe
append cmd "retID=o1.Name();"
if { $compare && !$actual} {
append cmd "\}"
}
append cmd "\} \}"
#puts $cmd
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
array set values [rega_script $cmd]
# Die reale zeitverzögerung Analysieren
if { [info exists values(retDur)] && ($values(retDur) != "")} {
set duration $values(retDur)
set durtext "mit Einschaltdauer $duration s"
} else {
set durtext ""
}
# Erfolg oder Misserfolg Parameter setzen
if { [info exists values(retID)] && ($values(retID) != "")} {
set x $values(retID)
if { $debug } {
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
exec logger -t $blackversion -p user.debug \[$x erfolgreich auf $value $durtext\]
#puts "Erfolgreich"
}
} else {
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
exec logger -t $blackversion -p user.debug \[ID \($id\) Fehler beim Versuch Wert \($value\)\]
#puts "Nicht erfolgreich"
}
unset cmd
unset sdur
}
Über Rückmeldugen / Anregungen freue ich mich natürlich,
Gruss, Black