Seite 1 von 1

Sytaxprobleme in Rule und "gibts das auch in schön"

Verfasst: 22. Dez 2024 23:04
von bender.ac
:!: EDIT: Da sich der Inhalt des Beitrags etwas gedreht hat, hab ich den Titel angepasst. :!:
---


Hallo liebes Forum,

ich kämpfe mal wieder mit den Rules und deren Syntax...

Ich habe den folgenden Ausschnitt aus einer Rule

Code: Alles auswählen

rule "Durchsage"
when
    Item Durchsage_Buero received command or
    Item Durchsage_Aussen received command or
    Item Durchsage_EG received command or
    Item Durchsage_OG received command 
then
    //Quelle identifizieren
    var string _src = triggeringItemName.toString
    logInfo("###DEBUG", _src) 
    
    //Text bestimmen
    var String _txt = ""       
    if(_src == 'Durchsage_Buero')_txt = Durchsage_Buero.state.toString
    else if( _src == "Durchsage_Aussen" ) _txt = Durchsage_Aussen.state.toString
    else if( _src == "Durchsage_OG" ) _txt = Durchsage_OG.state.toString
    else if( _src == "Durchsage_EG" ) _txt = Durchsage_EG.state.toString
    [...]
Speichern der Rule geht noch aber wenn sie getriggert wird liest man das hier:
2024-12-22 22:59:30.820 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Durchsage-1' failed: Unknown variable or command '=='; line 14, column 8, length 25 in Durchsage
Zeile 14 ist die mit if am Anfang.
Das Equal ist sicher nicht falsch aber ich sehe nicht was das eigentliche Problem ist?
Muss man Strings irgendwie anders vergleichen (an C denkend)?

Viele Grüße
Achim

Re: Sytaxprobleme in Rule, Equal in If condition

Verfasst: 22. Dez 2024 23:09
von nw378
Da fehlt einfach ein Leerzeichen zwischen Klammer zu und _txt, würd ich sagen.

Re: Sytaxprobleme in Rule, Equal in If condition

Verfasst: 22. Dez 2024 23:23
von bender.ac
Ne das Leerzeichen wars nicht... Aber ich hab die Lösung

var string _src
geht nicht
var String _src
geht

mit großen "S" gehts in zwei Varianten:

Code: Alles auswählen

if(_src == "Durchsage_Buero") _txt = Durchsage_Buero.state.toString()
und

Code: Alles auswählen

if(_src.equals("Durchsage_Buero")) _txt = Durchsage_Buero.state.toString()
Oh das hat mich nun eine Stunde gekostet :oops:

Re: [solved]Sytaxprobleme in Rule, Equal in If condition

Verfasst: 23. Dez 2024 05:44
von udo1toni
Ja, es stehen nicht alle Variablentypen als Primitive zur Verfügung.
Andererseits sollte man ohnehin immer Objekte verwenden, solange es keinen zwingenden Grund für ein Primitive gibt.

Das ganze Konstrukt ließe sich vermutlich einfacher programmieren :)

Aber zunächst mal: Dir ist bewusst, dass das empfangene Kommando keinesfalls mit dem Status eines Items gleichgesetzt werden kann?

In Rules stehen implizite Variablen zur Verfügung, beispielsweise triggringItemName enthält den Namen des Items, welches die Rule getriggert hat (sofern der Trigger Item ... lautet). Diese Variable ist vom Typ String, das .toString ist also unnötig.
Eine weitere implizite Variable wäre newState, diese ist bei Rules gesetzt, die auf received update oder changed getriggert haben, aber es gibt auch receivedCommand, welches den empfangenen Befehl beinhaltet (und das wäre hier vermutlich der gesuchte Wert, nicht der Status). Zu bemerken wäre hier noch, dass newState (genau wie previousState bei changed Ereignissen) einen Status enthalten, während receivedCommand ein Command enthält. Beide wären streng genommen mittels .toString zunächst in einen String zu wandeln, bevor man sie mit Stringoperationen verwendet. openHAB wird aber an vielen Stellen stillschweigend die Konvertierung selbst vornehmen..

Die Namenszuordnung (wenn man das denn überhaupt bräuchte) könnte etwas hübscher aussehen:

Code: Alles auswählen

    var String _txt = ""       
    switch(_src) {
        case "Durchsage_Buero"  : _txt = Durchsage_Buero.state.toString
        case "Durchsage_Aussen" : _txt = Durchsage_Aussen.state.toString
        case "Durchsage_OG"     : _txt = Durchsage_OG.state.toString
        case "Durchsage_EG"     : _txt = Durchsage_EG.state.toString
    }
aber selbst das ginge wesentlich eleganter, vorausgesetzt, man hat alle Items zu einer Gruppe zusammengefasst:

Code: Alles auswählen

rule "Durchsage"
when
    Item Durchsage_Buero received command or
    Item Durchsage_Aussen received command or
    Item Durchsage_EG received command or
    Item Durchsage_OG received command 
then
    //Quelle identifizieren
    var string _src = triggeringItemName
    logInfo("durchsage", "Quelle: {}",_src) 
    
    //Text bestimmen
    var String _txt = ""       
    _txt = gDurchsage.members.filter[i|i.name == _src].head.state.toString
Wobei man dann auch gleich die Gruppe als Trigger verwenden kann:

Code: Alles auswählen

rule "Durchsage"
when
    Member of gDurchsage received command
then
    //Quelle identifizieren
    var string _src = triggeringItem.name
    logInfo("durchsage", "Quelle: {}",_src) 
    
    //Text bestimmen
    var String _txt = ""       
    _txt = triggeringItem.state.toString
Wie gesagt, vermutlich möchtest Du eigentlich receivedCommand verwenden...

Kleines Detail: bei Member of gibt es die implizite Variable triggeringItem (ohne Name), und diese Variable ist vom Typ genericItem, d.h. sie verhält sich exakt wie das Item, welches die Rule ausgelöst hat. Man kann auf alle Aspekte dieses Items über die implizite Variable zugreifen, ohne noch weiteren Aufwand zu treiben.

Was die logInfo() Action betrifft: Bitte, bitte... openHAB hat eine äußerst leistungsfähige Logger Engine verbaut. logInfo("durchsage","Meine Meldung") erzeugt einen Logeintrag des Levels INFO (nicht DEBUG), und zwar mit dem Logger org.openhab.core.model.script.durchsage und dem Text Meine Meldung. Bitte im ersten String keine Sonderzeichen verwenden, bitte keine Leerzeichen verwenden, es handelt sich um den letzten Teil des Loggernamens.
Es gibt die Möglichkeit, im laufenden Betrieb den Loglevel pro Logger zu beeinflussen. Das geht zunächst mal über die Karaf Konsole, aber sobald der Logger einmal explizit genannt wurde (über die Karaf Konsole...), kann man den Loglevel in openHAB4.3.0stable auch direkt in der neuen UI Log Konsole umschalten, von INFO (das ist der default Wert) z.B. auf WARN (damit wäre logInfo für den betreffenden Logger dann "abgeschaltet"), aber auch auf DEBUG. Es gibt nämlich nicht nur logInfo(), sondern auch noch logDebug(), logWarn() und logError(). Die verschiedenen Level werden dann nicht nur unterschiedlich in der Log Ausgabe dargestellt, sondern gar nicht erst erfasst, wenn man das LogLevel entsprechend einstellt, alles unterhalb des gewählten Levels wird ignoriert.
Außerdem substituiert die Action die geschweiften Klammern {} im zweiten String mit den nachfolgenden Parametern, im Bespiel oben also dem Inhalt von _src, das ist besser, als einfach die Variable so zu übergeben.
Zusammengefasst: Es lohnt sich, den Logger so zu verwenden, wie es vom System gedacht ist. ;)

Re: Sytaxprobleme in Rule, Equal in If condition

Verfasst: 23. Dez 2024 09:02
von nw378
bender.ac hat geschrieben: 22. Dez 2024 23:23 Ne das Leerzeichen wars nicht... Aber ich hab die Lösung

var string _src
geht nicht
var String _src
geht

mit großen "S" gehts in zwei Varianten:

Code: Alles auswählen

if(_src == "Durchsage_Buero") _txt = Durchsage_Buero.state.toString()
und

Code: Alles auswählen

if(_src.equals("Durchsage_Buero")) _txt = Durchsage_Buero.state.toString()
Oh das hat mich nun eine Stunde gekostet :oops:
Das 'String' könntest Du übrigens auch ganz weglassen, welcher Typ der Variablen benötigt wird, erkennt OH selbstständig wegen dem "";
ähnlich wie

Code: Alles auswählen

var n=1
Noch ein Tip: Das Programmieren lässt sich vereinfachen, wenn man VSC https://www.google.com/url?sa=t&rct=j&q ... i=89978449 nutzt, da gibt es ein PlugIn für openHAB. So lassen sich solche Fehler direkt erkennen.
Bild1.png

Re: [solved]Sytaxprobleme in Rule, Equal in If condition

Verfasst: 23. Dez 2024 10:11
von bender.ac
Guten Morgen,
vielen dank für die lange Antwort. Mein Problem ist gelöst (naja eher umschifft) aber hier gibts in der Tat für mich noch viel zu lernen. Ich sehe es sportlich will es nicht irgendwie sondern gut machen :)
udo1toni hat geschrieben: 23. Dez 2024 05:44 Das ganze Konstrukt ließe sich vermutlich einfacher programmieren :)
Ja meine Rules sind ein Kompromiss zwischen dem was ich weiß und dem was ich in den Rules in endlicher Zeit umgesetzt bekomme ohne dabei alle Haare zu verlieren. Challenge accepted :)
udo1toni hat geschrieben: 23. Dez 2024 05:44 Aber zunächst mal: Dir ist bewusst, dass das empfangene Kommando keinesfalls mit dem Status eines Items gleichgesetzt werden kann?
Ja, so halbwegs. Aber so ganz klar aus welchen Properties und Methoden ein Item besteht ist mir zugegeben nicht...
:arrow: Direkt eine Frage: Ich verwende VisualStudioCode mit dem Openhab Plugin
Gibt es hier eine Möglichkeit wenn ich einen item eintippe und auf '.' tippe alle Properties und Methoden des Objekts aufgelistet zu bekommen? Das würde mir, als jemand der sich zwei mal im Jahr mit OH auseinander setzt die Sache erheblich erleichtern. :?:

Code: Alles auswählen

var String _txt = receivedCommand.toString
ist natürlich eine andere Hausnummer! Cool!

Du hast richtig geraten, ich organisiere meine Durchsagequellen in einer Gruppe. Diese Gruppe als Triggersource zu nehmen wird auch umgesetzt.
udo1toni hat geschrieben: 23. Dez 2024 05:44 Kleines Detail: bei Member of gibt es die implizite Variable triggeringItem (ohne Name), und diese Variable ist vom Typ genericItem, d.h. sie verhält sich exakt wie das Item, welches die Rule ausgelöst hat. Man kann auf alle Aspekte dieses Items über die implizite Variable zugreifen, ohne noch weiteren Aufwand zu treiben.
Langsam aber sicher beginne ich zu verstehen warum es eine domain specific language ist...
Das ist cool und mächtig setzt aber auch immer präzise Kenntnisse voraus.

So mit neuem Wissen gehts nun an die Optimierung. Wie gesagt, mein Werk ist immer ein Kompromiss zwischen dem was ich weiß und dem was ich in endlicher Zeit umgesetzt bekomme und diese Grenzen haben sich jetzt ein klein wenig verschoben :D

Danke Dir für den extrem hilfreichen Beitrag!

Re: [solved]Sytaxprobleme in Rule, Equal in If condition

Verfasst: 23. Dez 2024 10:59
von bender.ac
Ich kann noch mal einen Anstoß brauchen....

Vorab: Was treib ich hier eigentlich?
Wir haben einige Amazon Echos im Haus verteilt. Je nach Event soll aus den Dingern eine Ansage ertönen.
Aber eben nicht bei allen jede Nachricht. Beispiel: "Es hat an der Tür geklingelt" ist für jeden Empfänger sinnvoll.
Der Akku des EV ist voll interessiert meine Kinder überhaupt nicht.

In einer Gruppe habe ich die verschiedenen Gruppen als Item hinterlegt, welche (noch -> siehe oben) die Gruppe triggern.

Mit udo1tonis Hilfe hat sich das "Quelle und Meldung identifizieren" auf zwei Zeilen zusammengedampft

Code: Alles auswählen

//Quelle und Meldung identifizieren
    var String _src = triggeringItemName
    var String _txt = receivedCommand.toString
Nächster Schritt wäre die Verteilung. Wie geschrieben, je nach Quelle auf verschiedene Endgeräte.
Hier gehe ich so vor, dass ich die aktuelle Lautstärke des Gerätes nehme, ein wegnig aufschlage und die Ansage mache. Daran sind drei Items beteiligt.

Code: Alles auswählen

        _l = (Alexa_Buro_Lautstarke.state as Number).floatValue
        _sl = _l + _inc
        Alexa_Buro_Sprich_Lautstarke.sendCommand(_sl as Number)
        Alexa_Buro_Sprich.sendCommand(_txt)
Gemein haben die Items, dass sie Alexa_<Raum>_{Lautstarke,Sprich_Lautstarke,Sprich} heißen und liegen jeweils in einer Gruppe die sicherlich auch nen einheitlichen Namen hat.

Nun zur Frage:
Die stumpfe Methode wäre pro Quelle oder Ziel (weniger Quellen als Ziele, daher lieber pro Quelle)
If-Strukturen aufspannen, deren Code, abgesehen vom Mittlerenteil der Itemsnames (-> <Raum>) immer gleich ist. Klar geht, aber wenn man was lernen will muss das schöner gehen. Nur wie?

Nachgedacht habe ich über dictionaries und dann drüber iterieren aber so richtig auf nen grünen Zweig bin ich nicht gekommen. Ideen?

:arrow: Kann man innerhalb von Rules Funktionen definieren?

Code: Alles auswählen

void function Durchsage(item laustarke, item Sprich_Lautstarke, item Sprich)
:arrow: Kann man sich das item holen wenn man den Namen weiß?

Code: Alles auswählen

item getItemByName(string itemname)
:arrow: Gibt es Structs in der DSL bzw innerhalb einer rule?

Code: Alles auswählen

struct Durchsageset{
   item Lautstarke;
   item Sprich_Lautstarke;
   item Sprich;
}
Ideen über Ideen aber wahrscheinlich führen alle nicht zu elegantesten Lösung :?

Re: Sytaxprobleme in Rule und "gibts das auch in schön"

Verfasst: 23. Dez 2024 18:40
von udo1toni
Die DSL kennt keine Unterprogramme, so wie Du es als Pseudo-Code aufgeschrieben hast.
Man kann Lambdas definieren, aber das ist in den meisten Fällen nicht zielführend (hier definitiv nicht).

Was Du brauchst, ist eine Quelle, aus der hervorgeht, welche Items angesprochen werden müssen. Außerdem brauchst Du (mindestens) eine Gruppe, in der alle Items zusammengefasst sind. Alternativ kannst Du auch pro Funktion eine eigene Gruppe machen (also je eine Gruppe gLautstaerke, gSprich_Lautstaerke, gSprich)
Beispiel:
Items:

Code: Alles auswählen

Group gAlexa
Group gMeldungsquelle

Dimmer Alexa_Raum1_Lautstaerke       "Lautstärke"         (gAlexa)
Dimmer Alexa_Raum1_SprichLautstaerke "Lautstärke Sprache" (gAlexa)
String Alexa_Raum1_Sprich            "Text"               (gAlexa)
Dimmer Alexa_Raum2_Lautstaerke       "Lautstärke"         (gAlexa)
Dimmer Alexa_Raum2_SprichLautstaerke "Lautstärke Sprache" (gAlexa)
String Alexa_Raum2_Sprich            "Text"               (gAlexa)
String Meldung1                      "Meldung 1"          (gMeldungsquelle)
String Meldung2                      "Meldung 2"          (gMeldungsquelle)
String Meldung3                      "Meldung 3"          (gMeldungsquelle)
Rule:

Code: Alles auswählen

import java.util.HashMap

// Eine Liste mit einer Zuordnung Quelle -> Ziele
val HashMap<String,String> hmMeldung = newHashMap(
  "Meldung1" -> "Raum1,Raum2",
  "Meldung2" -> "Raum1,Raum3",
  "Meldung3" -> "Raum2,Konstantinopel"

)

rule "Alexa Ausgabe"
 when
    Member of gMeldungsquelle received update
 then
    val iInc = 5
    val lZiel = hmMeldung.get(triggeringItem.name) // Liste aller Zielräume
    val allItems = gAlexa.members.sortBy[ name ].filter[i| lZiel.contains(i.name.split("_").get(1))] // Bilde Liste aller Items
    allItems.filter[i|i.name.endsWith("_Lautstaerke")].forEach[j| // für jedes Lautstärke-Item
        j.sendCommand((j.state as Number).intValue + iInc)        // erhöhe Lautstärke
    ]
    allItems.filter[i|i.name.endsWith("_Sprich")].forEach[j|      // Für jedes Textitem
        j.sendCommand(newState.toString)                          // Sende Text
    ]
 end
Was passiert da?
Die Rule triggert auf das Update eines Members der Gruppe gMeldungsquelle. Das kann Meldung1, Meldung2 oder Meldung3 sein.
Nun fischt sich die Rule aus der HashMap hmMeldung den zugehörigen Eintrag.
Anschließend wird die Gruppe gAlexa gefiltert, nach allen Items, in deren Name an zweiter Stelle (also nach dem 1. _) ein Teilstring steht, der so in der zuvor ermittelten Liste enthalten ist.
Diese Liste von Items wird unter dem Namen allItems verfügbar gemacht.
Nun filtert die Rule diese Liste nach den Items, deren Name auf _Lautstaerke endet. Für jedes dieser Items erhöht sie anschließend die Lautstärke um den in iInc hinterlegten Wert.
Abschließend wird die Liste allItems nach den Items gefiltert, deren Name auf _Sprich endet. An jedes dieser Items wird der empfangene Text gesendet.

Es fehlt hier natürlich noch der Schritt, die Lautstärke wieder auf das ursprüngliche Level abzusenken :) aber der Punkt ist: die Listen sind extrem mächtig und machen oftmals Subroutinen komplett überflüssig.
Ich habe im Beispiel erst alle Lautstärken angepasst und anschließend die Texte versendet, das ginge natürlich auch andersrum. In diesem Fall wäre es vermutlich sinnvoller, über die Liste der Räume zu iterieren, in der Form

Code: Alles auswählen

val lZiel = hmMeldung.get(triggeringItem.name).split(",")
lZiel.forEach[str|
    gAlexa.members.filter[i|i.name.split("_").get(1) == str ].forEach[j|
        if(j.name.endsWith("_Lautstaerke")) 
            j.sendCommand((j.state as Number).intValue + iInc)
        else if(j.name.endsWith("_Sprich"))
            j.sendCommand(newState.toString)
    ]
]
Allerdings habe ich das nicht getestet. Eventuell muss die Liste dann als Array definiert sein...

Eine weitere Möglichkeit bestünde darin, die Items passend zu taggen und dann nach den Tags zu filtern, dann könnte man auf die HashMap verzichten.
Hach, es gibt so viele Möglichkeiten...

Ach so... wozu dient das Item ..._SprichLautstaerke? Eigentlich brauchst Du ja nur zwei Items pro Alexa...