Auf meinem neuen Display-Taster (HmIP-WRCD) wollte ich gerne die Anzahl der angeschalteten Lampen darstellen. Ich hatte mir sowas ähnliches ganz einfach für meine Fenster geschrieben, doch ist das für Lampen kaum geeignet:
!Check Fenster------------------------
var WZ_Fenster = "HmIP-RF.xxxxxxxxxxxxxx:1";
var KU_Fenster = "HmIP-RF.xxxxxxxxxxxxxx:1";
var BAD_Fenster = "HmIP-RF.xxxxxxxxxxxxxx:1";
var SZ_Fenster = "HmIP-RF.xxxxxxxxxxxxxx:1";
var SZ_Tuer = "HmIP-RF.xxxxxxxxxxxxxx:1";
var OpenWindows =
dom.GetObject(WZ_Fenster + ".STATE").Value() +
dom.GetObject(KU_Fenster + ".STATE").Value() +
dom.GetObject(BAD_Fenster + ".STATE").Value() +
dom.GetObject(SZ_Fenster + ".STATE").Value() +
dom.GetObject(SZ_Tuer + ".STATE").Value();
if(dom.GetObject("Check_FensterOffen").State() != OpenWindows ){
dom.GetObject("Check_FensterOffen").State(OpenWindows);
}
Ich habe also ganz einfach alle Fenster/Türen aufgelistet (haben ja nicht so viele einen Sensor). Den Auf/Zu Wert habe ich dann einfach summiert, und diese Zahl wird dann in eine Systemvariable "Check_FensterOffen" gespeichert. Das Skript ist einfach, schnell geschrieben, aber hat den entscheidenen Nachteil, das man die Geräte alle einzeln "hart verdrahten" muss. Das hätte ich nun bei den Lampen ebenso machen können, aber davon besitze ich einige mehr. Zum Hintergrund, ich setze auch ein "Philips Hue" System ein, da ist jede Glühbirne ein Gerät.
Also kam mir die Idee, einfach ein Skript zu schreiben, welches alle Lampen durchparst. Am sinnvollsten geht man über die "Gewerke" (also dem Funktionstyp des Gerätes). Ich habe also mein Skript mit:
var i = dom.GetObject("Licht");
begonnen, wie man es in so manchen Tutorial sieht. Funktioniert aber nicht, da es kein Objekt mit diesem Namen gibt. Ich hab also so mal verschiedenes ausprobiert, aber ich konnte das "Licht"-Objekt einfach nicht finden. Hmm, dann habe ich mal folgendes probiert:
!Alle Gewerke--------------------------
object lFunctions = dom.GetObject(ID_FUNCTIONS);
string lFunction;
WriteLine("Gewerke:");
foreach(lFunction,lFunctions.EnumNames())
{
WriteLine(lFunction);
}
Ok, Ok, die heißen nun also:
Gewerke:
funcButton
funcCentral
funcClimateControl
funcEnergy
funcEnvironment
funcHeating
funcLight
funcLock
funcSecurity
funcWeather
Ja, sinnvoll, müsste man aber erst wissen... Nun gut dann gehts also weiter:
!Check Lampen innerhalb Gewerk----------------------------------
var i = dom.GetObject("funcLight");
var noLightsOn = 0;
string itemID;
foreach(itemID, i.EnumUsedIDs()) {
var oLight = dom.GetObject(itemID);
WriteLine("---");
WriteLine("n:"+oLight.Name());
WriteLine("s:"+oLight.State().ToString());
}
WriteLine("Ende");
Da kommt ja schon so einiges raus, aber eigentlich brauch ich ja nur immer ein Kanal, aber wie kann ich Ihn greifen? Ich habe ein bißchen gesucht, und folgende Lösung gefunden:
!Check Lampen innerhalb Gewerk----------------------------------
var i = dom.GetObject("funcLight");
var noLightsOn = 0;
string itemID;
foreach(itemID, i.EnumUsedIDs()) {
var oLight = dom.GetObject(itemID);
var dev = dom.GetObject(oLight.Device());
var oCh1Light = dev.Channels().GetAt(1);
!is channel1?
if(oLight.Name() == oCh1Light.Name()){
WriteLine("---");
WriteLine("n:"+oLight.Name());
WriteLine("d:"+dev.HssType());
WriteLine("s:"+oLight.State().ToString());
}
}
WriteLine("Ende");
Es wird also zu jedem ermittelten Kanal das Gerät ermittelt, und von dort der Kanal 1 ermittelt. Nur wenn es sich um Kanal 1 handelt erfolgt nun eine Ausgabe.
Das sieht schonmal besser aus. Doch einige Lampen geben gar kein Ergebnis aus :-(. Also habe ich mal kurz mit dem XMP-API-CCU Addon recherchiert, und folgendes "für mich" gefunden:
Ich besitze die folgenden Lampentypen:
- -VIR-LG-RGBW-DIM, das sind Hue-RGB-Lampen, welche vom Philips Hue Addon kommen (über CUxD importiert)
- -VIR-LG-WHITE-DIM, das gleiche wie oben, nur sind das Weiße Lampen
- -VIR-LG-ONOFF, eine Steckdose, wie oben Hue über CuxD importiert.
- -HmIP-BSM, einige Lampen habe ich nicht über Hue, sonder klassisch über Homematic Ip angebunden.
Nun verhalten sich die Lampentypen unterschiedlich: Bei VIR-LG-ONOFF konnte man noch einfach den An/Aus-Wert finden. Er ist im Kanal 1 unter STATE abgespeichert, man muss ".toString()" verwenden, das der Wert auch mittels WriteLine() angezeigt wird. Bei VIR-LG-RGBW-DIM kommt ein Float raus, es handelt sich um den Dimmwert. Beim Weißen Licht VIR-LG-WHITE-DIM befinden sich die Werte abwechslungsweise mal im Kanal 2, auch hier als Float mit Dimmwert. Bei HmIP-BSM wundert sich der Anfänger: Bei Homematic-IP Aktoren befinden sich normalerweise auf:
- -Kanal 1: Allgemeine Daten (allgemein üblich bei Homematic)
- -Kanal 2: Taster unten (ja bei Homematic oben und unten immer vertauscht)
- -Kanal 3: Taster oben
- -Kanal 4: Aktor
- -Kanal 5: weiterer Aktorkanal
- -Kanal 6: weiterer Aktorkanal
- -Kanal 7: Strommesser
- -Kanal 8: Benachrichtigung vom Strom abhängig
- -Kanal 9: Timer
Der "interessante" Ein/Aus Wert befindet sich also auf Kanal 4. Nun gut, also muss noch für jeden Schaltertyp eine eigene "Extrawurst" gebraten werden, um einheitlich die Anzahl der angeschalteten Lampen zu ermitteln. Hier also das komplette Skript:
!Check Lampen innerhalb Gewerk----------------------------------
var i = dom.GetObject("funcLight");
var noLightsOn = 0;
string itemID;
foreach(itemID, i.EnumUsedIDs()) {
var oLight = dom.GetObject(itemID);
var dev = dom.GetObject(oLight.Device());
var oCh1Light = dev.Channels().GetAt(1);
var oCh2Light = dev.Channels().GetAt(2);
var oCh4Light = dev.Channels().GetAt(4);
!is channel1?
if(oLight.Name() == oCh1Light.Name()){
if(dev.HssType() == "VIR-LG-RGBW-DIM"){
WriteLine("---");
WriteLine("n:"+oLight.Name());
WriteLine("d:"+dev.HssType());
var tmpState = false;
if(oLight.State() > 0){
tmpState = true;
noLightsOn = noLightsOn + 1;
}
WriteLine("s:"+tmpState.ToString());
}
if(dev.HssType() == "VIR-LG-ONOFF"){
WriteLine("---");
WriteLine("n:"+oLight.Name());
WriteLine("d:"+dev.HssType());
WriteLine("s:"+oLight.State().ToString());
if(oLight.State()){
noLightsOn = noLightsOn + 1;
}
}
}
if(oLight.Name() == oCh2Light.Name()){
if(dev.HssType() == "VIR-LG-WHITE-DIM"){
WriteLine("---");
WriteLine("n:"+oLight.Name());
WriteLine("d:"+dev.HssType());
var tmpState = false;
if(oLight.State() > 0){
tmpState = true;
noLightsOn = noLightsOn + 1;
}
WriteLine("s:"+tmpState.ToString());
}
}
if(oLight.Name() == oCh4Light.Name()){
if(dev.HssType() == "HmIP-BSM"){
var tmplightname = "HmIP-RF." + oLight.Address() + ".STATE";
var oLightState = dom.GetObject(tmplightname).Value();
WriteLine("---");
WriteLine("n:"+oLight.Name());
WriteLine("d:"+dev.HssType());
WriteLine("a:"+tmplightname);
WriteLine("s:"+oLightState.ToString());
if(oLightState){
noLightsOn = noLightsOn + 1;
}
}
}
}
if(dom.GetObject("Check_LichtAn").State() != noLightsOn){
dom.GetObject("Check_LichtAn").State(noLightsOn);
}
WriteLine("Ende");
Was ist noch dazugekommen? Ich habe hier für jeden Typ nach dem entsprechenden Kanal gefiltert. Bei VIR-LG-RGBW-DIM und VIR-LG-WHITE-DIM habe ich mittels
if(oLight.State() > 0){
noLightsOn = noLightsOn + 1;
}
den Wert in 0 und 1 umgewandelt. Beim HmIP-BSM kommt nach der bewährten Methode nur "null" raus, also habe ich direkt den Datenkanal angezapft:
var tmplightname = "HmIP-RF." + oLight.Address() + ".STATE";
var oLightState = dom.GetObject(tmplightname).Value();
if(oLight.State()){
noLightsOn = noLightsOn + 1;
}
Dazu habe ich die vorhandene Adresse verwendet, das Protokoll noch davorgeschrieben, dann klappt mit dem ".STATE". Zur Veranschaulichung der Zugriff auf den Datenkanal:
"HmIP-RF.xxxxxxxxxxxxxx:4.STATE"
Die ganzen Daten werden wie hier nun in Systemvariablen geschrieben. Hier im Beispiel:
if(dom.GetObject("Check_LichtAn").State() != noLightsOn){
dom.GetObject("Check_LichtAn").State(noLightsOn);
}
Die zusätzliche Abfrage verhindert ein aktualisieren des Zugriffes, und damit eine Aktualisierung der Variable. In meinem separaten Skript zum Display, benutze ich dann die Aktualisierungszeit der Systemvariable. Wer sich hierrüber wundert: Ja das ist eine der zahlreichen undokumentierten Funktionen von Homematic-Skript.
Hier mal das Display-Skript:
var cntMinutes = 1;
var FLDisplay = "HmIP-RF.xxxxxxxxxxxxxx:3";
var CurrTimestamp = system.Date().ToTime().ToInteger();
var oFensterOffen= dom.GetObject(ID_SYSTEM_VARIABLES).Get("Check_FensterOffen");
var oHeizungAuto = dom.GetObject(ID_SYSTEM_VARIABLES).Get("Check_HeizungAuto");
var oLichtAn = dom.GetObject(ID_SYSTEM_VARIABLES).Get("Check_LichtAn");
var uhrzeit = system.Date("%d.%m %H:%M");
var dt = "{DDBC=WHITE,DDTC=BLACK,DDA=CENTER,DDS=Lich/Fen: ";
dt = dt+ oLichtAn.Value().ToInteger()#"/"#oFensterOffen.Value().ToInteger()#",DDID=2},";
dt = dt+"{DDBC=WHITE,DDTC=BLACK,DDA=CENTER,DDS=Heiz Auto:"#oHeizungAuto.Value().ToInteger()#",DDID=3},";
dt = dt+"{DDBC=WHITE,DDTC=BLACK,DDA=CENTER,DDS="#uhrzeit#",DDID=4,DDC=true}";
if((CurrTimestamp - oFensterOffen.Timestamp().ToInteger()) < 60*cntMinutes){
dom.GetObject(FLDisplay+ ".COMBINED_PARAMETER").State(dt);
WriteLine("Fenster");
}elseif((CurrTimestamp - oHeizungAuto .Timestamp().ToInteger()) < 60*cntMinutes){
dom.GetObject(FLDisplay+ ".COMBINED_PARAMETER").State(dt);
WriteLine("Heizung");
}elseif((CurrTimestamp - oLichtAn.Timestamp().ToInteger()) < 60*cntMinutes){
dom.GetObject(FLDisplay+ ".COMBINED_PARAMETER").State(dt);
WriteLine("Licht");
}
WriteLine("Ende");
Es werden alle Daten in einem Rutsch übermittelt, dazu kann man das Format:
var FLDisplay = "HmIP-RF.xxxxxxxxxxxxxx:3";
dom.GetObject(FLDisplay+ ".COMBINED_PARAMETER").State("{DDBC=WHITE,DDTC=BLACK,DDI=1,DDA=CENTER,DDS=Zeile1,DDID=1},{DDBC=WHITE,DDTC=BLACK,DDI=2,DDA=CENTER,DDS=Zeile2,DDID=2},{DDBC=WHITE,DDTC=BLACK,DDI=3,DDA=CENTER,DDS=Zeile3,DDID=3},{DDBC=WHITE,DDTC=BLACK,DDI=5,DDA=CENTER,DDS=Zeile4,DDID=4},{DDBC=WHITE,DDTC=BLACK,DDI=3,DDA=CENTER,DDS=Zeile5,DDID=5,DDC=true},{R=1,IN=5,ANS=4}");
Wichtig ist hierbei, keine Zeilenumbrüche einzubauen, sonst kommt nicht mehr alles auch dem Display an. Hier eine Erklärung zu obiger Zeichenkette:
DDBC = BackgroundColor, (WHITE, BLACK)
DDTC = TextColor (WHITE, BLACK)
DDI = Image (s.Liste)
DDA = TextAlign (CENTER, LEFT, RIGHT)
DDS = Textstring max 14 Zeichen ohne Bild, danach wird automatisch hinten abgeschnitten. Bei Bild in der Zeile: max.11, danach wird automatisch vorne abgeschnitten
DDID = Textzeile (1,2,3,4,5)
DDC = Commit = AusführungR = Repetiton Wiederholung
IN = Intervall Abstand TöneDDI Image
0 = Nicht benutzt
1 = Lampe aus
2 = Lampe ein
3 = Schloss auf
4 = Schloss zu
5 = X
6 = Häckchen
7 = Info
8 = Briefumschlag
9 = Schraubenschlüssel
10 = Sonne
11 = Mond
12 = Wind
13 = Wolke
14 = Wolke/Blitz
15 = Wolke/leichter Regen
16 = Wolke/Mond
17 = Wolke/Regen
18 = Wolke/Schnee
19 = Wolke/Sonne
20 = Wolke/Sonne/Regen
21 = Wolke/Schneeflocke
22 = Wolke/Regentropfen
23 = Flamme
24 = Fenster auf
25 = Rollladen
26 = Eco
27 = Unscharf
28 = Hüllschutz
29 = Vollschutz
30 = Benachrichtigung
31 = UhrANS Sound
-1 = Nicht benutzt
0 = Batterie leer, kurz-kurz-kurz
1 = Alarm aus, lang-kurz
2 = Externer Alarm aktiviert, lang-kurz-kurz
3 = Interner Alarm aktiviert, lang-kurz
4 = Externer Alarm verzögert aktiviert, kurz-kurz
5 = Interner Alarm verzögert aktiviert, kurz
7 = Fehler, lang
6 = Event, mittel
Eine Systemvariable ließt man nun etwas indirekter:
var oFensterOffen= dom.GetObject(ID_SYSTEM_VARIABLES).Get("Check_FensterOffen");
var Fensterwert = oFensterOffen.State();
var LastAccess = oFensterOffen.Timestamp();
anstatt des simplen "dom.GetObject("varname").State()". Ich ermittele also wann zum letzten mal der Wert aktualisieret wurde, und nur dann, wird das Display überhaupt aktualisiert. Bei läuft das Skript jede Minute, und mein DutyCycle liegt derzeit bei 6%, also alles problemlos. Leider Kommt bei mir die "Burst-Limit" Meldung. Ich überlege ob ich die nicht abschalte (unter "Geräte" auch "Netzbetrieb" umschalten) allerdings würde dann der Schalter mehr Strom verbrauchen, und ich müsste öfters die Batterie wechseln. Ich teste erstmal so.
Ich hoffe der ein oder andere konnte was lernen. Das Skript ist nicht die "feinste" Art des Programmierens (ich habe öfters z.B. das DRY-Prinzip verletzt, aber der Editor von Homeatic ist beschränkt und auch die Skriptsprache umständlich zu debuggen. Ich hoffe Ihr könnt drüber hinwegsehen :-)