CustomIntent mit Dialog

Begonnen von gregorv, 03 Oktober 2024, 10:54:24

Vorheriges Thema - Nächstes Thema

gregorv

ZitatM.E. ist die Prüfung auf HASH (an dieser Stelle, habe die Idee aber an anderer Stelle recycled)

Ich sende Dir hier  mal vorab, was ich eingebaut hatte:

In der respond() anstelle der auskommentierte Zeile;
#   $sendData->{customData} = toJSON($data); # @@@ replaced by following lines until end marker
    delete $sendData->{customData};                            # cannot add hash element if this is a string
    my @exclude = qw{Device Room Value confidence customData input intent lang rawInput requestType sessionId siteId};
    for my $key (keys %{$data}) {                              # add all keys in $data but not standard keys
        if (!grep { $_ eq $key } @exclude) {
            $sendData->{customData}->{$key} = $data->{$key};
        }
    }
    if ( ref $data->{customData} eq 'HASH' ) {                 # if customData is hash add all keys     
        for my $key (keys %{$data->{customData}}) {     
            $sendData->{customData}->{$key} = $data->{customData}->{$key};    
        }
    }
    $sendData->{customData} = toJSON($sendData->{customData});

Die Duplikate sind durch den Inhalt von $sendData->customData entstanden - das ist nämlich nach den ersten Durchlauf bereits mit dem JSON gefüllt.
Ich habe mich allerdings dazu entschieden den Inhalt für das JSON bei jedem Durchlauf neu zu erstellen, weil es ja sein kann, dass man bei anderen Intents, die in der 'sonst noch was' Folge gestartet werden, bestimmte Optionen nicht, oder andere zusätzlich gewünscht sind z.B. []{RetryIntent:true}.
Die Standardkeys werden nicht mehr in customData eingepackt oder sieht Du bei denen einen Nutzen? (Device oder Room könnten evtl. mal von Nutzen sein.)

ZitatMit der neuen Funktion _get_filter...() ist es möglich
Haber ich bisher noch nicht geprüft, kann man das für 'normale' Intents nutzen - über einen weiteren Key? Wenn []{reopenVoiceInput:25} aktiv ist, hatte ich nur gesehen dass CancelAction nicht funktioniert.
In dem Zusammenhang auch:
ZitatWas noch fehlt, ist die Behandlung von "lass gut sein"-Sätzen in handleIntentCancelAction()
CancelAction müsste andere Antworten liefern können 'habe abgebrochen' geht zur Not, ist aber nicht die Goldrand-Variante. Ich hatte einen CutomIntent CancelSession gemacht mit einfachem OK als Antwort, was aber nicht zur ursprünglichen Idee von CancelAction passt.

gregorv

ZitatIch frage mich nur, ob der "kaputte" string dann auch wieder in RHASSPY funktioniert
Habe ich zwar noch nicht probiert, aber sollte funktionieren - zumindest nachdem die mehr als zweifache ToJSON nicht mehr vorkommt. \u0022 ist eine andere Schreibweise für doublequote ".

Zitatdass Rhasspy anscheinend komisch reagiert
Da habe ich auch lange dran gesucht. Aber im Nachhinein ist das klar:
in der 'sauberen' Variante steht als Inhalt von customData:
\"customData\":{\"Devi ...}Also fängt der Inhalt von customData mit { an und endet mit } ganz sauber, aber leider kein String der muss nämlich in " eingeschlossen sein.
\"customData\":"{\"Devi ...}"Da in einem JSON string auch numerische Werte und Leerzeichen sein können, sieht das für Rhasspy so aus, als würde man mehrere Parameter übergeben wollen.
Das Problem mit den verbleibenden \u0022 könnte man auch lösen, wenn man die JSON Umwandlung in der respond() nicht macht und dafür in der activateVoiceInput() im zu JSON umgewandelten String $json noch die zwei " per RegEx einfügt. Falls Dir das passende RegEx dafür 'zufliegt' würde das Log noch  hübscher - ich hatte bisher zumindest nur sehr eingeschränkte Lust, das zu probieren- wäre aber gespannt, ob ich das lesen kann.



gregorv

#152
Neue Version läuft!
Kleinigkeit noch angepasst in respond():
delete $sendData->{customData};statt $data -die störenden alten JSONs kommen schon in $sendData an (werden oben schon aus $data kopiert)
Aber dazu hatte ich im vorletzten Post noch einen Vorschlag gemacht um nicht zu viel Müll zu transportieren UND um Key/Values, die möglicherweise schon in customData drin sind, mitzunehmen (die könnten von CustomIntents kommen).

Das mit SilentCancel habe ich zentral in respond( umgesetzt:
Einmal am Anfang noch eine Zeile eingefügt:
delete $response->{text} if ref $response eq 'HASH' && $response->{text} eq 'SilentClosure';falls $response ein hash mit text Parameter ist.

und weiter unten alle Aufkommen (3) von:
$sendData->{text} = $response;durch
$sendData->{text} = $response if $response;ersetzt.
Dadurch wird der text Parameter gar nicht erst geschaffen. Und an allen Stellen wo jetzt SilentClosure drin steht, kann es bleiben.

Das Ignorieren von Geräuschen statt Ansage muss ich mir morgen mal anschauen - das ist nicht so einfach.
  • Meine CustomIntent liefert z.B. auch ein hash und würde dann oben schon ausgefiltert.
  • Bei Intents ohne reopenVoiceInput wird der Dialog nach IntentNotRecognized beendet und dann sollte doch eine Ansage kommen oder besser der Dialog neu gestartet werden.
Muss ich mal überlegen - da fällt mir schon was ein.
Gruß aus dem kalten Norden

Beta-User

#153
Zitat von: gregorv am 11 November 2024, 18:51:10Gruß aus dem kalten Norden
Hier im Süden ist es auch nicht wirklich gemütlich...

In Kleinigkeiten überarbeitete nächste Fassung anbei.

Zitat von: gregorv am 11 November 2024, 18:51:10Kleinigkeit noch angepasst in respond():
Thx!
Zitat von: gregorv am 11 November 2024, 18:51:10Aber dazu hatte ich im vorletzten Post noch einen Vorschlag gemacht um nicht zu viel Müll zu transportieren UND um Key/Values, die möglicherweise schon in customData drin sind, mitzunehmen (die könnten von CustomIntents kommen).
Hab's gesehen, weiß im Moment allerdings noch nicht, was da wie sinnvoll ist. Grade weil wir das im Moment ja nicht aktiv nutzen, würde ich erst mal "alles" mitschleifen, allerdings eben nur, was in $data (ausgenommen der customData-Key) steht. Dann ist zumindest klar, dass der User (oder wir) halt aktiv hergehen muss, weiter benötigte/gewünschte Keys zu übernehmen.

Zitat von: gregorv am 11 November 2024, 18:51:10Das mit SilentCancel habe ich zentral in respond( umgesetzt:
Guter Ansatz, hab's übernommen und noch etwas ausgebaut - da war im alten Code für bestimmte Fälle noch was potentiell schräges drin :o ...

Zitat von: gregorv am 11 November 2024, 18:51:10Das Ignorieren von Geräuschen statt Ansage muss ich mir morgen mal anschauen - das ist nicht so einfach.
Eigentlich ist da schon Code für diesen Fall drin, allerdings wurde da noch $data weitergegeben, in dem bei "not recognized" ja nichts brauchbares drin steht. Jetzt wird das mit (ohne Ansage oder so) $customData versucht, mal sehen, ob das hilft.
Zitat von: gregorv am 11 November 2024, 17:03:15Habe ich zwar noch nicht probiert, aber sollte funktionieren - zumindest nachdem die mehr als zweifache ToJSON nicht mehr vorkommt. \u0022 ist eine andere Schreibweise für doublequote ".
Auf diese Weise sollten wir auch sehen, ob das in der Variante jetzt so funktioniert wie gedacht oder nicht. Es wäre so jedenfalls relativ einfach, die Daten durchzureichen und weiter zu verwenden; eine Regex oder irgendwas selbstgebrautes (Umkehrfunktion zu "parseParams()"?) wäre da sehr viel mehr Aufwand...

Zitat von: gregorv am 11 November 2024, 16:14:38hatte ich nur gesehen dass CancelAction nicht funktioniert.
Hmmm, ok.
Habe festgestellt, dass in Zeile 3047 die Array-Boundaries nicht drin waren. Habe mal versucht, das reinzuknödeln, aber bei sowas tue ich mich mit der Syntax auch schwer und hoffe, dass das nicht crasht...
Ansonsten sollte das "eigentlich" lt. Doku in https://rhasspy.readthedocs.io/en/latest/reference/#dialogue-manager funktionieren. Allerdings bin ich beim Durchwursteln des Codes wieder auf Zeile 3802 gestoßen
my @ca_strings = configure_DialogManager($hash,$data->{siteId}, $toDisable, 'false', undef, 1 );und habe mich zum x-ten Mal gefragt, warum an dieser Stelle (bzw. danach) nicht die Text-Variante verwendet wird sondern die (key-value-) Objekt-Variante, die eigentlich für "hermes/dialogueManager/configure" vorgesehen ist. Offensichtlich funktioniert es auch mit dem Objekt...
Da müssen wir jedenfalls nochmal tiefer ran, ich warte mal auf deinen debug-output. Wenn es so jetzt nicht aktiviert wird, bräuchte ich Info, was da konkret via MQTT versendet wird. (für sowas verwende ich üblicherweise mosquitto_sub).

Zitat von: gregorv am 11 November 2024, 16:14:38CancelAction müsste andere Antworten liefern können
Habe jetzt mal noch den speziellen Key "closeSession" reingeknödelt. Wenn der da ist, sollte die Sitzung immer beendet werden - so könnte man also auch verschiedene Abbruch-Szenarien/Sätze handhaben (oder vielleicht in allen "Abbruch"-Varianten den Dialog zumachen?).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Beta-User

Zitat von: gregorv am 07 November 2024, 17:13:50Die handleIntentSetNumeric() habe ich erweitert, damit weitere Kommandos verarbeitet werden können
Nachtrag dazu noch:

Magst du einen (ggf. auch deutschen) kurzen Text für die commandref beisteuern, wie es gemeint ist?

Das mit "cmdUp" etc. gefällt mir in dem Zusammenhang allerdings nicht, ich halte das immer noch für "eigentlich unnötig", das ist mir zu viel "mapping" und besser via "onOff"-Intent zu lösen. Es kann aber sein, dass dein Problem mit onOff eigentlich daher resultiert, dass RHASSPY intern eine "legacy"-Schicht enthält, die genau mit "Auf" und "Zu" speziell umgeht. Vielleicht wäre es Zeit, das jetzt auszubauen (angekündigt hatte ich das schon vor ewigen Zeiten)?

(auf die Schnelle müßte es sich um _getGenericOnOff() drehen).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

gregorv

@Beta-User
ZitatDas mit "cmdUp" etc. gefällt mir in dem Zusammenhang allerdings nicht

Weiter unten wird die Idee eventuell klarer. Aber zunächst zur letzten Verion vom 12.11.
Da war noch offen, ob IntentNotRecognized bei Geräuschen (Input=""), so abläuft, dass die Session einfach kommentarlos weitergeführt wird.
Ich habe die in der o.Version eingesetzte Variante versucht zum Laufen zu bekommen, aber das scheitert aus mehreren Gründen.
Wenn ein IntentNotRecognized auftritt, dann kommen die Daten in customData (nach reopenVoiceInput) bereits als JSON an. Damit scheitert die Prüfung eq 'HASH', weil es ein String ist.
Auch erwartet activateVoiceInput() als $h Parameter ein hash und steigt bei einem JSON String aus. Man kann zwar den JSON string entpacken und und dann als hash übergeben, aber ich habe es nicht hinbekommen, dass Rhasspy dann still ist (Rhasspy - WAV wurde abgespielt). Wie dem auch sei, die es gäbe sicher eine Möglichkeit das zum Funktionieren zu bringen, aber m.E. erheblich aufwändiger als eine andere Variante.
Ausgehend von der Überlegung, dass die gewünschte Lösung eigentlich nichts anderes ist, als die 'wie bitte?' Funktion, nur ohne 'wie bitte?', habe ich folgendes eingebaut.
    if ( !$data->{input} || $data->{customData} =~ /RetryIntent/) {
        $response = getResponse( $hash, 'RetryIntent');
        $response = 'SilentClosure' if !$data->{input} ;
        $reaction = {
            text => $response,
            sendIntentNotRecognized => 'true',
            customData => $data->{customData}
        };
        return respond( $hash, $data, $reaction);                                                              # keep session open and continue listening
    }
Damit wird die Session (allerdings mit gleicher sessionID) weitergeführt, wahlweise ohne Audio oder mit 'wie bitte?'
Das ist völlig unabhängig davon, in welcher Form die customData ankommen - nach Wake-Word z.B. als String mit Wake-Word-Id.
Das =~ /RetryIntent/i wirst Du sicher noch zu m/{... umbauen - ich muss mich da erst mal schlau machen, wie da der Syntax ist.
! defined $data->{input} habe ich zu !$data->{input} geändert, weil der input Parameter immer da ist - also defined ??
Die Abfrage length($data->{input}) < 4) ist aus irgendwelchen Gründen immer wahr - ich hab das nicht weiter analysiert, weil ich es erst mal funktionierend sehen wollte.
Bleibt noch offen Exit Variante return if ref $data->{customData} ne 'HASH'; die (weil JSON!=HASH) immer zuschlägt und deren Hintergrund ich nicht kenne.
Wenn es hier für bestimmte Fälle einen Ausgang benötigt, muss das umgebaut werden.

Die Tests verschiedenster Szenarien haben keinerlei Unregelmäßigkeiten gezeigt - selbst bei einem Geräusch unmittelbar nach einem Wake-Word (also ohne, dass vorher ein Intent aktiv war) funktionieren einwandfrei und sehen im Log sauber aus.

Schau Dir das bitte mal an. Ich meine es gibt keinen Grund eine neue Session mit neuer sessionId aufzumachen.

Und nun zur handleIntentSetNumeric()
Ob da cmdOn oder cmdDown verwendet wird, ändert nichts am derzeitigen Code. Hauptsache, es gibt ein Mapping - unliebsame cmdXXX müssten explizit ausgeschlossen werden, aber warum?

Es gibt aber noch weitere Gründe, weil ich noch mehr umgesetzt habe - wie sieht das als sentences aus:
... $de.fhem.Device-light{Device} [<rooms>] ( (an|ein){Change:cmdOn} | aus{Change:cmdOff}) {Type:timedOn}
... $de.fhem.Device-light{Device} [<rooms>] [für] (1..20){Value} [(minute|minuten)] an {Type:timedOn}
Ich habe den Type timedOn in _analyze_genDevType() z.B. hier eingebaut (vorletze Zeile):
        if ( $gdt eq 'light' && $allset =~ m{\bdim([\b:\s]|\Z)}xms ) {
            my $maxval = InternalVal($device, 'TYPE', 'unknown') eq 'ZWave' ? 99 : 100;
            $currentMapping->{SetNumeric} = {
            brightness => { cmd => 'dim', currentVal => 'state', maxVal => $maxval, minVal => '0', step => '3', type => 'brightness'},
            timedOn => {currentVal => 'timedOn', cmd => 'on-for-timer', map => 'mult60', cmdOn => 'on', cmdOff =>'off', type => 'timedOn'}};
        }
Alternativ kann man das auch im rhasspyMapping machen - falls es durch gdt nicht gesetzt wird (bei Z-Wave Aktoren ist es so aktiv).
Besser wäre es diesen Type generell für alle $gdt eq 'light' zu setzen, wenn ( $gdt eq 'light' && $allset =~ m{\bon-for-timer([\b:\s]|\Z)}xms ) zutrifft. Wenn das OK ist, würde ich das so umbauen.

Dazu auch ein weiterer Wert für map map:mult60 (oder wie man den auch immer benennen möchte) jedenfalls kann damit der gesprochene Wert mit 60 multipliziert werden, weil die Lampen, die ich kenne nur mit Sekundenwerten arbeiten.
Hier wird neben on-for-timer noch cmdOn und cmdOff von der handleIntentSetNumeric() verarbeitet. Da sind nur zwei kleine Änderungen nötig:
Unter der Zeile my $forcePercent ... noch einfügen:
Zitatmy $multiply60 =  ( defined $mapping->{map} && lc $mapping->{map} eq 'mult60' ) ? 60 : 1;
und hier die letzte Zeile noch einfügen:
        } elsif ( !$ispct && !$forcePercent ) {
            $newVal = $value;
            $newVal *= $multiply60 ; # @@@                                       
Aber bevor ich da noch mit meinem SetNumeric => { setTarget => { cmdOn => 'on',... ankomme, möchte ich erst mal Deine Meinung zu solchen Erweiterungen hören.

Übrigens habe ich bei meinen Forschungsarbeiten an diesen CodeStücken auch festgestellt, dass meine frühere Feststellung, dass Werte von eventMap als rhasspyMapping übernommen werden, nicht korrekt ist. Das Mapping taucht zwar in $allset aber, dass ich das im hash gefunden hatte, war schlicht ein vergessenes rhasspyMapping.

gregorv

#156
Zwei Korrekturen hatte ich noch vergessen:
In der respond()
delete $response if $response eq 'SilentClosure');durch
$response = q{} if $response eq 'SilentClosure'delete geht nur für hash keys

und
my $json = _toCleanJSON($sendData);durch
my $json = toJSON($sendData);Wenn man _toCleanJSON verwendet, wird "true" in eine 1 umgewandelt. Das ist zwar richtig, aber wenn Rhasspy das zurück sendet, gibt es in RHASSPY den Fehler JSON: Cannot encode 1. (mit toJSON bleibt es 'true')
Von meinem Bauchgefühl her sollte man das besser auf der Empfangseite ändern, weil numerische Werte ja eigentlich in JSON und im hash erlaubt sind.

und noch - ebenfalls response() unter der 1. Zeile noch die 2. hinzugefügt:
delete $sendData->{customData};
delete $data->{customData}; # @@@
Vermutlich ist aber delete $sendData->{customData}; nicht mehr erforderlich, weil es weiter unten komplett neu geschrieben wird.

Beta-User

Danke für's debugging, wird allerdings etwas dauern, bis ich das für mich aufgearbeitet haben werde.

Vor allem müssen wir klären, wann bzw. ob wir überhaupt aus customData wieder einen HASH generieren (können). "Eigentlich" sollten die Altdaten auch bei IntentNotRecognized bereits wieder ausgepackt sein...

Generelle Anmerkung: Von Rudi habe ich u.a. gelernt, dass man
a) gewollte features sauber (aber knackig) dokumentiert, und
b) features nur einbaut, wenn es "notwendig" ist.

Anders formuliert: Wenn du erreichen willst, dass "cmd.*" dauerhafter Bestandteil von RHASSPY wird, muss ich verstehen, warum man es braucht. Das einzige Beispiel, das ich dazu bisher gesehen habe, ist der Versuch, mit "SetNumeric" das zu erreichen, was "SetOnOff" eigentlich können sollte - jetzt mit dem weiteren Zusatz, damit auch noch "SetTimedOnOff" zu doppeln.

Jetzt schreibst du ergänzend, dass das Mapping (vielleich?) kaputt war... 

Von daher würde ich nochmals dringlich darum bitten, erst zu klären, ob man das "wirklich braucht". Bisher _glaube_ ich das weiterhin nicht, weil es m.E. entweder ein Konfigurationsfehler war/ist, oder ein Problem mit der "legacy"-Schicht (die ich gerne (!) ausbaue).

Dass mir "OnOff" als Einsteiger-Intent (auch in der korrekten Doku, btw.!) besonders wichtig ist, hatte ich ja bereits erwähnt, und ich bin immer noch der Ansicht, dass es (v.a. vor dem Hintergrund, dass es sowieso insgesamt sehr umfangreich und damit für Einsteiger schwer zu überblicken ist, was man konfigurieren kann) nur jeweils eine (funktionierende) Option braucht, um allgemein interessierende Problemstellungen mit RHASSPY umzusetzen.

PS: Es gibt in "SetNumeric" schon sowas wie "factor".
PPS: Das mit "kennt das Device einen eigene Timer-Funktion" finde ich einen interessanten Ansatz. Kommt auf die Merkliste :) . Muss aber mal schauen, ob es dazu einen Eintrag in mapping "braucht", oder ob man das (da eher selten) nicht besser "on the fly" macht? Da könnte man auch auf "off-for..." prüfen, ohne "den User" mit allzu unübersichtlichen Mappings zu konfrontieren ;) .
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

gregorv

Zitat"Eigentlich" sollten die Altdaten auch bei IntentNotRecognized bereits wieder ausgepackt sein.
Das ist auch richtig, aber derzeit ist $data->{customData} ja bereits zu JSON konvertiert, bevor $data selbst noch einmal zu JSON konvertiert wird. Das führt natürlich bei der Event Verarbeitung, wo JSON wieder zu hash konvertiert wird dazu, dass das doppelt konvertierte $data->{customData} erst mal JSON bleibt. Das ist im Log auch zu sehen, weil die \u0022 dann nicht mehr da sind.
Ich stimme Dir eindeutig zu, dass das Inhalt von $data->{customData} auch wieder zu hash konvertiert werden sollte. Die Prüfung auf bestimmte Keys in customData ist derzeit nur möglich, wenn man den gesamten customData String per Regex nach einem Key durchsucht, gar nicht zu reden von dem zugehörigen Wert.
Wenn nach dem decode $data noch ein decode $data->{customData} eingesetzt wird sollte das sicher funktionieren. Man muss vorher prüfen, ob der Inhalt wirklich ein JSON ist (kann ja auch ein Text sein).

ZitatJetzt schreibst du ergänzend, dass das Mapping (vielleich?) kaputt war...
Sorry, da muss ich etwas 'ungünstig' formuliert haben. Das Mapping funktioniert einwandfrei. Wo hast Du das herausgelesen ?

Anders formuliert: Wenn du erreichen willst, dass "cmd.*" dauerhafter Bestandteil ...Aus einem früheren Post hatte ich mal herausgelesen, dass SetNumeric eigentlich der 'Alleskönner' werden sollte. Für mich passte das recht gut zu meinem Use Case (custom Intent Blinds), weil ich so nicht erst das Kommando analysieren muss bevor ich entscheide, ob SetOnOff oder SetNumeric aufgerufen werden muss. Da ist es natürlich einfacher, wenn nur SetNumeric benötigt wird. Type timedOn (on-for-timer) würde ich in die gleiche Kategorie wie Type brightness packen, kann man manuell machen, aber schönes Featuere, wenn gdt das macht.
An dieser Stelle kommen wir zu:
Zitatfeatures nur einbaut, wenn es "notwendig" ist.
Die Definition von "notwendig" ist kompliziert - etwa von 'geht sonst gar nicht' über 'besser als andere' zu 'besonders komfortabel'.
Ein gutes Beispiel ist da HA. HA ist einfacher zu handhaben für Basic Funktionen als FHEM, aber mit FHEM kann man viel mehr machen. Ich denke nicht, dass ein FHEM User FHEM ausgewählt hat, weil es so einfach ist.
Aber das ist mein subjektives Empfinden. Generell bist Du auch dabei, um zu entscheiden, was aufgenommen sollte und was nicht - also keine Sorge. Falls da was dabei ist, was ich unbedingt haben will, dann kann ich das ja machen, ohne dass es in dem offiziellen Modul freigegeben werden muss.

ZitatEs gibt in "SetNumeric" schon sowas wie "factor"
Ja, hatte ich auch schon benutzt. Weil das aber in einem anderen Zusammenhang genutzt wird, hatte ich Nebenwirkungen befürchtet. Alternative wäre, wenn man die Multiplikation mit $factor von dem Type timedOn abhängig macht.

Da fällt mir aber noch was anderes ein. Die Übergabe von Key Value Paaren aus einem sentence ist nicht gerade schön ([]{RetryIntent:true} []{reopenVoiceInput:25}...). Wenn man das auch in FHEM an einem Device oder Group festmachen könnte wäre das m.E. 'sauberer' und zudem flexibler. Entweder eine neue rhasspyKeys oder als rhasspyMapping Erweiterung so etwas option:Factor=60,RetryIntent=true,reopenVoiceInput=25, ...

Dabei bin ich noch auf eine andere Eigenschaft im Mapping gestoßen, die möglicherweise anderen auch schon vor Rätsel gestellt hat. Wenn man mit rhasspyMapping z.B. SetOnOff:cmdOn=An konfiguriert, kann man z.B. das Licht nicht mehr ausschalten, weil das Mapping cmdOn=Off gelöscht ist. Also generell, sobald man in rhasspyMapping einen Wert ändert, sind alle durch gdt gesetzten Werte aus diesem Segment/Type weg. Aufgefallen ist mir das bei map:. Da wollte ich einen anderen Wert einsetzen (nur für map) und danach ging gar nichts mehr von SetNumeric.

Neues Finding: (für mich zumindest)
... $de.fhem.Device-light{Device} [<rooms>] ( ( (an|ein|einschalten){Change:cmdOn} | (aus|ausschalten){Change:cmdOff}){Type:SetOnOff})In einem SetNumeric sentence kann man den Type SetOnOff angeben und braucht dann keine 'artfremden' Mappings innerhalb von Type SetNumeric.
Damit ist mein Blinds Problem auch gelöst. Man kann sich sogar mit rhasspyMapping mit z.B. MyType:cmdMyCMD=driveUp einen eigenen Type schaffen und im sentence entsprechend unterbringen.
handleIntentSetNumeric() übernimmt die Mappings immer aus dem angegebenen Type (oder SetNumeric, falls kein Type). Damit aber was anderes als cmdStop funktioniert, wäre schön, wenn cmd.+ weiter akzeptiert wird.

Ja, und Dokumentation habe ich schon angefangen, aber da ist ja noch einiges im Werden. So zuletzt, das stille IntentNotRecognized - das sollte zumindest erwähnt werden, falls jemand, der irgendwie einen Intent über ein AudioSignal auslöst, was nicht Sprache ist, sich wundert, dass das nicht mehr geht.

Beta-User

Zitat von: gregorv am 16 November 2024, 15:27:12Aus einem früheren Post hatte ich mal herausgelesen, dass SetNumeric eigentlich der 'Alleskönner' werden sollte.
Hmmm, vielleicht habe ich mich da missverständlich ausgedrückt... Ich würde heute vermutlich manches anders machen, und ja, ich bin auch ein vielen Fällen der Meinung, dass getrennte Intents eher hinderlich sind. Aber was das konkret bedeutet und ob bzw. wie es besser ginge, ist damit noch lange nicht geklärt ;D .

Mal anders rum: Deine Vorschläge laufen darauf hinaus, sowas wie einen "IntentSetGeneric" zu schaffen. Finde ich mindestens mal einen durchdenkenswerten Ansatz, dazu weiter unten mehr.

Sowas wie timedOn/off würde ich allerdings bei derartigen "generic"-Überlegungen außen vor lassen. Da _glaube_ ich, dass ein "Sprechender" in so einem (sowieso schon kpmplizierteren) Satz kaum erwartet, dass er noch zusätzlich irgendeine andere sinnvolle Anweisung geben kann. Das ist in den entsprechenden Intents m.E. auch soweit ganz gut abgebildet, und wir können allenfalls noch überlegen, ob wir "on the fly" prüfen können, ob und inwieweit das Device den Userwunsch selbst verwalten kann, oder ob wir (RHASSPY-) intern einen timer brauchen. Dazu braucht es m.E. aber kein explizites Mapping.

Das mit dem "zeilenweisen" Überschreiben der mappings ist in der Tat zumindest in der commandref nicht wirklich gut erklärt. Wenn du eh' dabei bist, die Doku anzupassen, wäre es klasse, wenn du diesen Gesamtkontext mal einer Revision unterziehen könntest (das ist historisch so gewachsen und jetzt nach meinem Eindruck irgendwie "verdreht").

Generell müßte man für einen "IntentSetGeneric" halt dann erst mal im Vorfeld klären, was alles zulässig sein soll, und wo die Grenzen (und Prioritäten in der Abarbeitung) liegen könnten. Also erst mal: Was sagt denn überhaupt (sinnvollerweise) ein Sprechender...?

Was das Auspacken von customData angeht, bin ich etwas skeptisch, ob das mit dem doppelten decode wirklich funktionieren würde. Einfach den Versuch zu unternehmen, und bei Fehlschlag dann halt einfach den Text zu belassen, wäre ggf. mal einen Test wert? (eval {decode...()}). Ansonsten wäre es m.E. eventuell sinnvoller, die bereits mal skizzierte Umkehrfunktion zu "parseParams()" zu designen.

Zitat von: gregorv am 16 November 2024, 15:27:12Neues Finding: (für mich zumindest)
War mir auch nicht unbedingt so präsent, ist aber m.E. eine Folge von dem, dass ich selbst irgendwann bestimmte Intent-Grenzen als "nicht organisch" angesehen habe.

Zitat von: gregorv am 16 November 2024, 15:27:12Die Definition von "notwendig" ist kompliziert - etwa von 'geht sonst gar nicht' über 'besser als andere' zu 'besonders komfortabel'.
"Notwendig" ist in der Tat relativ. Ich habe keine prinzipiellen Probleme damit, Features, die es noch nicht gibt, die aber "wirklich cool" sind, in den Code einfließen zu lassen. Aber wie du am Beispiel von "cmdOff" ja selbst gesehen hast, gibt es bereits jetzt sehr viele Optionen, wie man - ohne allzu großen Aufwand - das gewünschte Ergebnis erzielen kann.

In diesem Sinne "notwendig" ist also eher, die vorhandenen Optionen so darzustellen, dass man diese auch als "interessierter User" findet und für sich nutzbar machen kann. Und da zeigt sich vielleicht grade mit diesem Finding: Weniger ist mehr, und neue/noch mehr Optionen verwirren ggf. mehr als sie helfen, die eigentliche Problemstellung des Users zu lösen...
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

gregorv

ZitatWas das Auspacken von customData angeht, bin ich etwas skeptisch
Ja ich habe das gerade gemacht und schick die Änderungen in ca.1h das hat einige Seiteneffekte, die ich, bis auf einen, schon erledigt habe.
Du brauchst also den Code nur verbessern falls nötig, kommt gleich.

Zitatsowas wie timedOn/off würde ich allerdings bei derartigen "generic ...
Wäre gut, wenn der generic dann irgendwo nachschauen könnte, was es ggf. modifizieren soll z.B. newVal mit irgendwas multiplizieren / verändern.


gregorv

Hier die Änderungen um customData wieder zu hashen
In der parseJSONPayload() unter dem alten decoden bis "Standard Keys auslesen" einfügen:
    my $customData; # @@@ may be nested JSON -> decode and attach to $data
    if ( defined $decoded->{customData} ) {
        if ( !eval { $customData = JSON->new->decode($decoded->{customData}) ; 1} ) {
            $data->{customData} = $decoded->{customData} ;
        }else{
            $data->{customData} = $customData;
        }
    }
    # Standard-Keys auslesen
    ($data->{intent} = $decoded->{intent}{intentName}) =~ s{\A.*.:}{}x if exists $decoded->{intent}{intentName};
    $data->{confidence} = $decoded->{intent}{confidenceScore} // 0.75;
    for my $key (qw(sessionId siteId input rawInput lang)) { # @@@ customData removed
und 4 Zeilen tiefer das customData aus dem qw{ ...} rausnehmen, damit nicht das JSON wieder da drin landet.

Im Log sieht das dann so aus: (print von $data)
$VAR1 = {
  "Device" => "licht",
  "RetryIntent" => "true",
  "Room" => "bad",
  "Type" => "SetOnOff",
  "Value" => 0,
  "confidence" => 1,
  "customData" => {
    "Change" => "cmdOn",
    "Device" => "licht",
    "RetryIntent" => "true",
    "Type" => "SetOnOff",
    "Value" => -10,
    "confidence" => 1,
    "input" => "mach das licht cmdOn SetOnOff true 25",
    "intent" => "SetNumeric",
    "lang" => undef,
    "rawInput" => "mach das licht an",
    "reopenVoiceInput" => 25,
    "requestType" => "voice",
    "sessionId" => "arbeitszimmer-Roberta_de_linux_v3_0_0-9dc19104-c534-420e-b0ef-9d34b7629058",
    "siteId" => "arbeitszimmer"
  },
  "input" => "mach das licht bad cmdOn SetOnOff true 25",
  "intent" => "SetNumeric",
  "lang" => undef,
  "rawInput" => "mach das licht bad an",
  "reopenVoiceInput" => 25,
  "requestType" => "voice",
  "sessionId" => "a82f4e03-b03f-4475-baa3-401f0eed37b3",
  "siteId" => "arbeitszimmer"
};

Seiteneffekte:
In der analyzeMQTTmessage() unter =~ m{sessionStarted}x die Zeile
activateVoiceInput($hash,[$data->{siteId}]);rausnehmen, war ohnehin noch eine Leiche von der letzten Änderung und ist nur nicht aufgefallen, weil customData kein hash war. Dass dadurch auch der sessionTimeout nicht mehr gesetzt wurde ist mir bei meinen Tests auch durchgeschlupft.

Weiterhin
In der respond() unter der Zeile delete $sendData->{customData}; (was jetzt wieder richtig ist) noch diese Zeile einfügen:
delete $data->{customData}->{customData} if ref $data->{customData} eq 'HASH' && defined $data->{customData}->{customData};sonst gibt es wieder wachsende Daten.

Und zuletzt: in der handleIntentNotRecognized in dem Codestück, was ich gestern geschickt hatte, die erste Zeile so anpassen, dass RetryIntent nicht mehr durch Regex geprüft wird:
if ( !$data->{input} || defined $data->{customData}->{RetryIntent} ) {
Mit customerData als hash habe ich noch nicht wirklich viel getestet, aber im Log sieht alles sehr sauber aus.

Ach und noch:
Wenn du eh' dabei bist, die Doku anzupassen, wäre es klasse, wenn du diesen Gesamtkontext mal einer Revision unterziehen könntest An das Wiki hatte ich eigentlich noch nicht gedacht, muss ich mich reinarbeiten - genau so wie in die Command Ref auch. Kommt aber noch - nach dem Doku Post im 05... Thread.

Beta-User

Zitat von: gregorv am 16 November 2024, 21:20:32Wäre gut, wenn der generic dann irgendwo nachschauen könnte, was es ggf. modifizieren soll z.B. newVal mit irgendwas multiplizieren / verändern.
Na ja, wie gesagt: timed.* würde ich zumindest im Moment nicht in "generic" sehen.

Und allgemein wieder: Es gibt bereits eine ganze Menge Keys, die irgendwo genutzt werden, bestimmte Bedeutungen haben und teils auch "mathematisch" behandelt werden (für Minuten und/oder Stunden müßte es z.B. schon was geben, was aber hier gar nicht her gehört). Wenn irgend sinnvoll, würde ich dazu tendieren, die im bereits in der commandref beschriebenen Sinn weiter zu verwenden, jedenfalls da, wo es keine Probleme mit Mehrdeutigkeiten gibt ("Value" dürfte da das "schlechteste" Beispiel sein).

Auch "Factor" war da vermutlich wirklich kein allzugutes Beispiel, das mit der "Mehrdeutigkeit" und unbeabsichtigten  Seiteneffekten ist in dem Fall wirklich was, auf das man aufpassen müßte. Aber das ist im Moment alles ziemlich abstrakt; wir sollten bei allem Coding v.a. erst mal schauen, was der geneigte Sprechende denn typischerweise eigentlich alles so äußern würde. Ausgehend davon können wir dann mal sehen, ob/was denn ggf. fehlt; ich habe da auf Basis von pah's "Testsuite" schon ziemlich viel reingebastelt.

Kann auch sein, dass die Doku im Moment weniger hergibt als der Code ("eigentlich") schon kann...

Zitat von: gregorv am 16 November 2024, 22:10:08An das Wiki hatte ich eigentlich noch nicht gedacht, muss ich mich reinarbeiten - genau so wie in die Command Ref auch. Kommt aber noch - nach dem Doku Post im 05... Thread.
Hmm, an das Wiki hatte ich erst mal noch gar nicht gedacht. Das würde ich erst mal auf später verschieben. Aber commandref ist wichtig, und darauf bezog sich auch mein Kommentar von heute mittag.

Ansonsten: Cool, du kommst richtig gut voran!!!

Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

gregorv

Da ich ja eher der try&error Typ bin wirkt sich das positiv auf die Anzahl der Tests aus. Damit es nicht langweilig wird habe ich mal (nur zum Test) rhasspyMappings - option:reopenVoiceInput=25,RetryIntent=true,Factor=60 ausprobiert.
Der print von hash->...$Device sieht dann so aus
$VAR1 = {
  "alias" => "licht",
  "groups" => "lampen",
  "intents" => {
    "GetOnOff" => {
      "GetOnOff" => {
        "currentVal" => "state",
        "type" => "GetOnOff",
        "valueOff" => "off"
      }
    },
    "SetNumeric" => {
      "timedOn" => {
        "cmd" => "on-for-timer",
        "cmdOff" => "off",
        "cmdOn" => "on",
        "currentVal" => "timedOn",
        "map" => "mult60",
        "type" => "timedOn"
      }
    },
    "SetOnOff" => {
      "SetOnOff" => {
        "cmdOff" => "off",
        "cmdOn" => "on",
        "type" => "SetOnOff"
      }
    },
    "option" => {
      "option" => {
        "Factor" => 60,
        "RetryIntent" => "true",
        "reopenVoiceInput" => 25,
        "type" => "option"
      }
    }
  },
  "names" => "licht,lampe,b\303\274ro licht",
  "rooms" => "arbeitszimmer,b\303\274ro"
In die handleIntentSetNumeric() muss dann das rein, sobald das Device bekannt ist:
    my $options = getMapping($hash, $device, 'option', { undef, undef });
    for my $key (keys %{$options}) {
        $data->{$key} = $options->{$key};
    }
Damit werden alle Optionen zu $data keys, Viola!
Soawas wäre, meine ich am Besten (bis auf Factor) in der respond() aufgehoben - sonst müsste man es in jeder handleIntent...() machen.

wenn dann noch die Zeilen:
    my $factor = $data->{Factor} // 1;
    $factor = 1 if !looks_like_number($factor);
direkt da drunter verschoben werden, wird der Wert von Factor auch in $factor übertragen.

Ein eventuelles gdt Mapping schrumpft damit auf timedOn => {currentVal => 'timedOn', cmd => 'on-for-timer', type => 'timedOn'}

Sorry für den permanent neuen Kram - dafür teste ich entsprechend mehr.
@JensS
Hattest Du sowas nicht auch gesucht?

gregorv

@Beta-User
Frage: Gibt es eine Möglichkeit mit rhasspyMapping ein sub hash von einem Intent zu erzeugen - ähnlich wie gdt das mit timedOn oder brightness macht ?
Bisher habe ich nur hinbekommen, dass ein sub hash auf Ebene Intents erzeugt wird.
Das wäre ja nötig, wenn man timedOn aus gdt herausnimmt.

Falls Du das nicht weißt bitte die pm Datei, wo die rhasspyMappings in das hash geschrieben werden

Danke Gregor