Neuigkeiten:

Am Sonntag den 8.12.2024 kann es ab ca. 8:00 Uhr zu kurzzeitigen Einschränkungen / Ausfällen bei den Diensten des FHEM Vereines kommen.
Die Server müssen mal gewartet und dabei neu gestartet werden ;)

Hauptmenü

CustomIntent mit Dialog

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

Vorheriges Thema - Nächstes Thema

gregorv

Ahh... jetzt gehen mir einige Lichter auf.
customData werden beim publish immer mit eingepackt und stehen deshalb auch innerhalb von Rhasspy selbst zur Verfügung.
Weiterhin werden die von Rhasspy bei bestimmmten MQTT messages wieder zurück geliefert (möglicherweise geändert, wenn man das auf Rhasspy Seite veranlasst)
Auch die Aussage: customData kann nur ein String sein ergibt für mich jetzt Sinn, weil man auf Rhasspy Seite möglicherweise mit einer Datenstruktur in customData nichts anfangen kann und im Manual auch steht, dass es ein String sein muss. Das ist allerdings eine Interpretationsfrage. Immerhin wird das Ganze ja als JSON(string) an Rhasspy geliefert und der Inhalt von customData ist ein Teilstring von diesem JSON. Zumindest kommt eine Datenstruktur innerhalb von customData bei MQTT Antworten intakt zurück.
Die $old_data sind nur FHEM-Intern (wo auch customData drin stehen können).
Wenn man nach einem Event von Rhasspy in $data->{customData} reinschaut, könnte da (zumindest kurzzeitig) was anderes drin stehen als in $old_data->{customData}

Falls ich mit einem dieser Punkte daneben liege, bitte kurze Info.

Zitat"reopen" mit neuer (aktiv festzulegender) sessionId,
Das passiert im Moment aber noch nicht. Ich habe bei einem Intent mal:
[]{reopenVoiceInput:25} []{customData:xxxxx-----xxxxx}am sentence angehängt. Die customData sind bis zum Ende der Session da, aber wenn die neue Session aufgemacht wird, ist der Inhalt von customData "defhem".

Silent wäre auch noch nötig:
Wenn bei Wake WAV in der Rhasspy-Konfiguration eine WAV Datei angegeben ist, wird die durch den Aufruf der activateVoiceInput() abgespielt.
Das Nutzer-Erlebnis ist damit: starten des Intent->Wake WAV->response (die bei diesem Intent festgelegt ist)
Bei mir ist die Wake WAV ein kurzes "ja" und da ist mir erst nicht aufgefallen, dass aus dem üblichen "Gerne" ein "Ja Gerne" wurde. Bei dem oft verwendeten (default) "Düdelüdüt" als Wake WAV wäre das aber irritierend.
Bei:
hermes/hotword/defhem/detectedgibt es keine Möglichkeit ein Audio oder Stille abzuspielen - soweit ich sehe, kann man es nur generell stumm machen, was an anderer Stelle aber eher ungünstig wäre.
Aber eventuell siehst Du da einen Weg.
Übrigens findet sich da das defhem von oben wieder. Rhasspy scheint generell erst mal das wake word in customData zu schreiben


Beta-User

Zitat von: gregorv am 05 November 2024, 14:37:02Falls ich mit einem dieser Punkte daneben liege, bitte kurze Info.
Das paßt im Großen und Ganzen. customData ist nach meinem Verständnis dazu da, dass man sich selbst Nachrichten innerhalb des Rhasspy-Systems schicken kann - man kann natürlich entscheiden, die irgendwo zu ändern (oder ändern zu lassen), aber per default wird jedenfalls Rhasspy das nicht tun (bzw. nur dann, wenn man "null" sendet).

Zitat von: gregorv am 05 November 2024, 14:37:02Das passiert im Moment aber noch nicht.
Jep. Hatte geglaubt, das auch ausdrücklich geschrieben zu haben, dass da noch was passieren muss (und imo das ganze System insgesamt angepaßt werden muss). Daher sind auch "findings" wie
Zitat von: gregorv am 05 November 2024, 14:37:02wird die durch den Aufruf der activateVoiceInput() abgespielt
vielleicht nur bedingt richtig. Was passiert z.B., wenn man da "text" mitgibt? Würde vermuten, dass dann statt des Tons/der WAV eben eine Textansage erfolgt. Da könnte ich mir "sonst noch was?" u.ä. (oder "pfff") vorstellen.

Zitat von: gregorv am 05 November 2024, 14:37:02Übrigens findet sich da das defhem von oben wieder. Rhasspy scheint generell erst mal das wake word in customData zu schreiben
_Vermutlich_ nur, weil "null" als sessionId vergeben war und auch customData nicht vorbelegt war.

Wie angedeutet: Es gibt da viele Stellschrauben, an denen man drehen kann, und wir stehen da eher am Anfang. Komme aber im Moment nicht dazu, damit weiter zu spielen, und hoffe, wieder ein paar Anregungen geliefert zu haben.
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

Zitatvielleicht nur bedingt richtig. Was passiert z.B., wenn man da "text" mitgibt?
Probiere ich nachher mal.

Zitat_Vermutlich_ nur, weil "null" als sessionId
und auch das - sowohl eine alte, wie auch eine neue.
Ich muss nur erst mal richtig wach werden, war eine lange Nacht  :)

gregorv

#138
@ Beat-User
reopenVoiceInput war heute mein Thema - daher später zu handleIntentSetNumeric()
Bei Test folgende Ergebnisse:
  • activateVoiceInput() mit text in der Payload-> da tut sich gar nichts, die Rhasspy-Doku ist da wohl vollständig.
  • activateVoiceInput() mit explizit angegebener sessionId -> Kein Unterschied, auch nicht, wenn dies die sessionId vom dem Intent ist, der reopenVoiceInput
auslöste.

In beiden Fällen wird eine neue Session aufgemacht - ggf. mit der alten sessionId - aber immer gibt es keinerlei Daten in $hash->helper->.delayed

Ich habe den reopenVoiceInput Code mal umgebaut und in die analyzeMQTTmessage() unter
        } elsif ( $topic =~ m{sessionEnded}x ) {
eingesetzt. Damit ist die alte Session erst mal sicher aufgeräumt, weil wir hinter sessionEnded erst was machen.
An dieser Stelle haben wir aber nur noch die customData, die in der Payload vom Event sessionEnded drin stecken.

Vorläufig habe ich die Daten, die wir benötigen in der respond(), da wo vorher der reopenVoiceInput Code war, mal in das hash geschrieben mit key storedData. Wenn der Event sessionEnded kommt, werden die immer wieder gelöscht.
Aber an der Stelle müssen wir noch überlegen, welche Daten in der Folgesession eventuell noch benötigt werden und wo die optimal gespeichert werden sollten -  $hash->helper->.delayed wäre vielleicht überlegenswert. Die werden zwar auch in dem sessionEnded code gelöscht, aber das könnte man ja davon abhängig machen, ob die sessionId gleich oder anders ist, als die, die gerade beendet wurde (um eine gleiche zu bekommen, muss man die alte nur bei activateVoiceInput() angeben).

An der Stelle, wo der reopenVoiceInput Code jetzt eingebaut ist, wird auch der Ablauf der Audio Ausgabe logischer also erst TTS von der geschlossenen Session und dann 'sonst noch was'. Und da wären wir dann beim nächsten Problem. Wie ober beschrieben get ein Key text nicht. Daher habe ich ein:
IOWrite($hash, 'publish', qq{hermes/tts/say $json});genutzt, um eine Ansage, die mit getResponse(... 'ContinueSession') geholt wird, abzuspielen.

Wenn man die Rhasspy-interne Audio-Datei aus der Config entfernt, funktioniert das an dieser Stelle schon mal sauber - auch im Log.

Allerdings gibt nun natürlich keine Rhasspy-interne Audio Ausgabe, aber die kann man ja in der analyzeMQTTmessage() unter hotword/detected unterbringen. Das baue ich da morgen mal ein einmal für 'initiales' wake word (mein 'Ja') und alternativ für 'sonst noch was' - dann kann dieser Code weiter unten raus.
Optimal wäre allerdings, wenn man da alternativ auch eine WAV Datei abspielen könnte - für die, die den Gong brauchen.




Beta-User

#139
Zitat von: gregorv am 06 November 2024, 23:55:29activateVoiceInput() mit text in der Payload-> da tut sich gar nichts, die Rhasspy-Doku ist da wohl vollständig.
Hab's mir kurz angeschaut, siehe den Oberpunkt "Dialogue Manager" in https://rhasspy.readthedocs.io/en/latest/reference:
ZitatMessages for managing dialogue sessions. These can be initiated by a hotword detected message (or /api/listen-for-command), and manually with a startSession message (or /api/start-recording).
"reopen" wird im Moment über den "hotword"-Mechanismus angeschubst. Was wir imo versuchen sollten, ist das (für den Fall, dass ein "text"-Key in $h übergeben wird) über den anderen Weg zu lösen, also wie in https://rhasspy.readthedocs.io/en/latest/reference/#dialoguemanager_startsession beschrieben.
Mal sehen, ob ich das verknödelt bekomme...
Kommt mir jedenfalls im Moment "sauberer" vor wie überall den Code umzustellen.

PS: Wir könnten auch über sendSpeakCommand() nachdenken, auch da wird (als einzige Stelle im Code) startSession als Topic verwendet.
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

ZitatThese can be initiated by a hotword detected message (or /api/listen-for-command), and manually with a startSession message
das würde das Problem lösen, was ich mit sendSpeakCommand() hatte. Bisher senden wir ja hotword detected und dass initiert ja startSession. Ein anschließender Aufruf von sendSpeakCommand() (das noch einmal startSession schickt) wird deshalb nicht ausgeführt. Wenn das auch geht, wäre das Audio problem gelöst.

Was den Zeitpunkt angeht, an dem das ausgeführt wird, wäre 'nach Event sessionEnded' aber wahrscheinlich nötig - egal ob hotword detected oder startSession verwendet wird um die neue Session zu starten. In respond() konnte ich zumindest die Timing Probleme nicht lösen.
Das weiterführen der alten sessionID wäre damit wohl nicht möglich - was die Frage aufwirft, wo ggf. Daten wie reopenVoiceInput gespeichert werden sollen. $hash->helper->.delayed->sessionId geht nur mit gleicher sessionId (was mit setDialogTimeout() statt sendSpeakCommand() wiederum funktionieren könnte.

Hier mal - nur zur Info - der Code, womit ich zuletzt gearbeitet hatte.
            if ( defined $hash->{storeData}->{reopenVoiceInput} ) {
                $response = getResponse( $hash, 'ContinueSession' );
                $reopenVoiceInput = $hash->{storeData}->{reopenVoiceInput};
                $SilentClosure = $hash->{storeData}->{SilentClosure};           
                activateVoiceInput($hash,[$data->{siteId}]);
                $delay = ReadingsNum($name, "sessionTimeout_$data->{siteId}", $hash->{sessionTimeout} // _getDialogueTimeout($hash));
                $delay = $SilentClosure    if  defined $SilentClosure && looks_like_number($SilentClosure);
                $delay = $reopenVoiceInput if !defined $SilentClosure && defined $reopenVoiceInput && looks_like_number($reopenVoiceInput) && $reopenVoiceInput > 0;
                #Beta-User: timeout function needs review as soon as soon as we store any session data
                resetRegIntTimer( 'testmode_end', time + $delay, \&RHASSPY_reopenVoiceInput_timeout, $hash );
                $sendData = {
                  "text"      => $response,
                  "siteId"    => $siteId,
                  "sessionId" => $hash->{storeData}->{sessionId},
                  "id"        => ($hash->{storeData}->{sessionId} =~ /(?:[^-]+-){2}(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})$/)[0]
                };
                delete $hash->{storeData};
                $json = _toCleanJSON($sendData);
                IOWrite($hash, 'publish', qq{hermes/tts/say $json});
                Log3($hash, 5, "published " . qq{hermes/tts/say $json});
            }

Beta-User

Zitat von: gregorv am 07 November 2024, 10:24:14Was den Zeitpunkt angeht, an dem das ausgeführt wird, wäre 'nach Event sessionEnded' aber wahrscheinlich nötig
Glaube, das Timing-Problem läßt sich durch den init-Key "canBeEnqueued" lösen.

Hier mal der (ungetestete) Versuch, das in Code zu fassen - Achtung, da wird intern die neue _get_filter...() aufgerufen, die muss also auch da sein!
sub activateVoiceInput {
    my $hash    = shift //return;
    my $anon    = shift;
    my $h       = shift;

    my $base = ReadingsVal($hash->{NAME},'siteIds', "$hash->{LANGUAGE}$hash->{fhemId}");
    if ($base =~ m{\b(default|base)(?:[\b]|\Z)}xms) {
        $base = $1;
    } else {
        $base = (split m{,}x, $base)[0];
    }
    my $siteId  = $h->{siteId}  // shift @{$anon} // $base;
    my $hotword = $h->{hotword} // shift @{$anon} // $h->{modelId} // "$hash->{LANGUAGE}$hash->{fhemId}";
    my $modelId = $h->{modelId} // shift @{$anon} // "$hash->{LANGUAGE}$hash->{fhemId}";
   
    my $sendData;
    my $json;

    #use startSession mechanism, see https://rhasspy.readthedocs.io/en/latest/reference/#dialoguemanager_startsession
    if ( defined $h->{text} ) {
        $sendData = {
            init => {
                type                    => $h->{type} // 'action',
                canBeEnqueued           => $h->{canBeEnqueued} // 'true',
                text                    => $h->{text},
                intentFilter            => $h->{intentFilter} // _get_sessionIntentFilter($hash, undef, 1 ),
                sendIntentNotRecognized => $h->{sendIntentNotRecognized} // 'true'
            }
        };
        $sendData->{siteId} = $siteId;
        $sendData->{customData} = $h->{customData} // "$hash->{LANGUAGE}.$hash->{fhemId}";
        $json = _toCleanJSON($sendData);
        return IOWrite($hash, 'publish', qq{hermes/dialogueManager/startSession $json});
    }

    #use default hotword mechanism
    $sendData =  {
        modelId             => $modelId,
        modelVersion        => '',
        modelType           => 'personal',
        currentSensitivity  => '0.5',
        siteId              => $siteId,
        sessionId           => 'null',
        sendAudioCaptured   => 'null',
        customEntities      => 'null'
    };
    $json = _toCleanJSON($sendData);
    return IOWrite($hash, 'publish', qq{hermes/hotword/$hotword/detected $json});
}
ZitatDas weiterführen der alten sessionID wäre damit wohl nicht möglich...
Das ist für diese Funktionalität m.E. nicht erforderlich, wir können alles, was wir ggf. brauchen in customData reinpacken, - unterstellt, der JSON-ized string bleibt erhalten...

Was lt. Doku nicht vorgesehen ist, ist die direkte Vergabe einer sessionId. Die müßten wir wohl aus
Zitathermes/dialogueManager/sessionStarted
    hermes/dialogueManager/sessionQueued
extrahieren (für letzteres gibt es noch kein Abonnement und keinen Code) - falls wir das überhaupt brauchen ;) .

Wenn es klappt, Altdaten einfach über customData durchzureichen, wäre das m.E. der einfachere Weg, und wir könnten ggf. auch da einfach keys reinfieseln, die z.B. anzeigen, wann der Dialog zuzumachen ist.
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

ZitatWenn es klappt, Altdaten einfach über customData durchzureichen, wäre das m.E. der einfachere Weg, und wir könnten ggf. auch da einfach
Jep, das geht.

Hier, das was ich mit continueSession mitgebe:
2024.11.07 11:24:49 5: published hermes/dialogueManager/continueSession {"Device": "AR.Rolladen.Links","SilentClosure":7,"customData":{"goAhead": "yes","name": "rollladen links","prevPos":0,"room": "büro"},"intentFilter":["de.fhem:CancelSession","de.fhem:CancelAction","de.fhem:Blinds"],"noResponse": true,"sendIntentNotRecognized": true,"sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-9d39aab6-82c9-4a22-be27-cbfa47f76e41","sessionTimeout":21,"siteId": "arbeitszimmer","text": "gestartet","what": true}und das, was bei sessionEnded noch da ist.
2024.11.07 11:24:55 5: RHASSPY: [RHASSPY.IF] Parse (IO: RHASSPY.MQTT2): Msg: hermes/dialogueManager/sessionEnded => {"termination": {"reason": "nominal"}, "sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-9d39aab6-82c9-4a22-be27-cbfa47f76e41", "siteId": "arbeitszimmer", "customData": {"goAhead": "yes", "name": "rollladen links", "prevPos": 0, "room": "büro"}}
die customData:
"customData": {"goAhead": "yes", "name": "rollladen links", "prevPos": 0, "room": "büro"}
Vermutlich kann man da auch noch komplexere Strukturen anhängen.

Man müsste aber überlegen, wie man die Daten elegant von einem sentence in customData bekommt. Über den []{key:value} Mechanismus kann das zwar gehen, ist aber schon eine ziemliche Herausforderung.


gregorv

Was lt. Doku nicht vorgesehen ist, ist die direkte Vergabe einer sessionIdWarum denkst Du, dass das erforderlich sein könnte ? Da fehlt mir vermutlich noch das KnowHow - das hing irgendwie mit Aufrämen zusammen??
Das mit dem sessionQueued hatte ich schon gesehen, aber noch nicht durchblickt. Wenn das so etwas ist, wie "mit der neuen Session warten, bis ein bestimmtes Ereignis eintrifft", dann kann man da auf Event sessionEnded warten - ist dann aber wohl von Rhasspy gemanaged.

Beta-User

Zitat von: gregorv am 07 November 2024, 11:57:14Das mit dem sessionQueued [...] wie "mit der neuen Session warten, bis [...]"
Nach meinem Verständnis ist das die Aufforderung an den DialogueManager (von Rhasspy), erst abzuwarten bis die aktuelle Sitzung "ended" ist, um dann mit den Sitzungen in der Warteschlange weiter zu machen.

Den Timer könnte man ggf. auch (erst) starten, indem man in analyzeMQTTmessage() den Code für "sessionStarted" entsprechend aufbohrt. Werde da mal rangehen...
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

#145
Zitat von: Beta-User am 07 November 2024, 13:21:33Werde da mal rangehen...
Ungetestet... (Weiß noch nicht, wann ich dazu komme, das mal im Echtsystem einzuspielen und damit rumzuspielen)
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

#146
Ungetestet... (Weiß noch nicht, wann ich dazu komme, das mal im Echtsystem einzuspielen und damit rumzuspielen)Und ich dachte, ich kenne mich mittlerweile einigermaßen aus - wie Du die Funktion ohne Test so einbauen kannst ist echt klasse.
Einige Kleinigkeiten noch geändert und es läuft bisher ganz gut:
Zeile 3418: } zu };
Zeile 3604 +2:
            if ( defined $data->{customData} &&  ref $data->{customData} eq 'HASH' && (  # @@@ Rhasspy schreibt am Anfang die hotword ID (nur ein String) da rein und dann crasht es
                defined $data->{customData}->{SilentClosure} ||                          # @@@ added $data->
                defined $data->{customData}->{reopenVoiceInput} ) ) {                    # @@@ dito
Zeile 3575:
@candidates = ref $ret eq 'ARRAY' ? $ret : split m{,}x, $ret if defined $ret;            # @@@ added  if defined $ret (warning,split with undef $ret)

Frage: gibt es irgendwie die Möglichkeit bei TTS (bei mir Mimic 3) die Betonung mehr Richtung Frage zu drehen ? Das 'sonst noch was' klingt etwas gelangweilt.
Ach und noch was - hast Du einen Key für das Aktivieren von CancelAction vorgesehen ? (Ich hab den gesamten neuen Code noch nicht durch)

Gleich kommt noch der Code für eine kleine Erweiterung der handleIntentSetNumeric

gregorv

Die handleIntentSetNumeric() habe ich erweitert, damit weitere Kommandos verarbeitet werden können
Die Erweiterung ist so gebaut, dass im sentence als Wert für Change ein beliebiges Kommando einegesetzt werden kann solange es zwei Bedingungen erfüllt.
  • es muss mit cmd starten (aber nicht cmd alleine) z.B. cmdUp
  • es muss in rhasspyMapping einem sinnvollen Kommando für das Device zugeordnet sein z.B. cmdUp=off

Folgende Änderungengen: (oben die geänderte Zeile, darunter die Originalzeile
delete $data->{Change} if defined $change && ($change !~ /^cmd.+/ || ! defined $mapping->{$change});
#delete $data->{Change} if defined $data->{Change} && $data->{Change} ne 'cmdStop';
!defined $change || $change !~ /^cmd.+/ 
#!defined $change || $change ne 'cmdStop' || !defined $mapping->{cmdStop}
und
: analyzeAndRunCmd($hash, $device, $mapping->{$change});
#: analyzeAndRunCmd($hash, $device, $mapping->{cmdStop});

Mein rhasspyMapping sieht z.B. so aus:
SetNumeric:currentVal=position,cmd=pos,minVal=0,maxVal=100,step=10,cmdUp=Auf,cmdDown=Zu,cmdStop=Stop

gregorv

#148
@Beta-User
mir sind noch zwei Dinge aufgefallen, wenn reopenVoiceInput mehr als einmal durchlaufen wird.
Die Msg Logausgabe wird teilweise unleserlich weil in Ausgaben von customData viele \\u0022 erscheinen
Ab da treten auch Duplikate auf.

Hier die sessionStarted Ausgabe beim ersten mal:
Msg: hermes/dialogueManager/sessionStarted => {"sessionId": "6246aebe-6417-42a4-ac2c-0d760704b1e7", "siteId": "arbeitszimmer", "customData": "{\"Device\":\"licht\",\"Value\":\"on\",\"confidence\":1,\"customData\":\"Roberta_de_linux_v3_0_0\",\"input\":\"mach das licht on 25\",\"intent\":\"SetOnOff\",\"lang\":null,\"rawInput\":\"mach das licht an\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"arbeitszimmer-Roberta_de_linux_v3_0_0-c0d43787-2984-4065-ad65-6933cb82707b\",\"siteId\":\"arbeitszimmer\"}", "lang": null}
Das ist m.E. das was Du erreichen wolltest.

und hier nach mehreren Kommandos - das scheint mir nicht gewollt? :
Msg: hermes/dialogueManager/sessionStarted => {"sessionId": "0e51fd42-8d3f-4ca6-812b-957d45788b46", "siteId": "arbeitszimmer", "customData": "{\"Device\":\"licht\",\"Value\":\"on\",\"confidence\":1,\"customData\":\"{\\u0022Device\\u0022:\\u0022licht\\u0022,\\u0022Value\\u0022:\\u0022on\\u0022,\\u0022confidence\\u0022:1,\\u0022customData\\u0022:\\u0022{\\u005cu0022Device\\u005cu0022:\\u005cu0022licht\\u005cu0022,\\u005cu0022Value\\u005cu0022:\\u005cu0022on\\u005cu0022,\\u005cu0022confidence\\u005cu0022:1,\\u005cu0022customData\\u005cu0022:\\u005cu0022{\\u005cu005cu0022Device\\u005cu005cu0022:\\u005cu005cu0022licht\\u005cu005cu0022,\\u005cu005cu0022Value\\u005cu005cu0022:\\u005cu005cu0022on\\u005cu005cu0022,\\u005cu005cu0022confidence\\u005cu005cu0022:1,\\u005cu005cu0022customData\\u005cu005cu0022:\\u005cu005cu0022Roberta_de_linux_v3_0_0\\u005cu005cu0022,\\u005cu005cu0022input\\u005cu005cu0022:\\u005cu005cu0022mach das licht on 25\\u005cu005cu0022,\\u005cu005cu0022intent\\u005cu005cu0022:\\u005cu005cu0022SetOnOff\\u005cu005cu0022,\\u005cu005cu0022lang\\u005cu005cu0022:null,\\u005cu005cu0022rawInput\\u005cu005cu0022:\\u005cu005cu0022mach das licht an\\u005cu005cu0022,\\u005cu005cu0022reopenVoiceInput\\u005cu005cu0022:\\u005cu005cu002225\\u005cu005cu0022,\\u005cu005cu0022requestType\\u005cu005cu0022:\\u005cu005cu0022voice\\u005cu005cu0022,\\u005cu005cu0022sessionId\\u005cu005cu0022:\\u005cu005cu0022arbeitszimmer-Roberta_de_linux_v3_0_0-e128f4e7-7f73-4e5a-845f-1a8117885fe7\\u005cu005cu0022,\\u005cu005cu0022siteId\\u005cu005cu0022:\\u005cu005cu0022arbeitszimmer\\u005cu005cu0022}\\u005cu0022,\\u005cu0022input\\u005cu0022:\\u005cu0022mach das licht on 25\\u005cu0022,\\u005cu0022intent\\u005cu0022:\\u005cu0022SetOnOff\\u005cu0022,\\u005cu0022lang\\u005cu0022:null,\\u005cu0022rawInput\\u005cu0022:\\u005cu0022mach das licht an\\u005cu0022,\\u005cu0022reopenVoiceInput\\u005cu0022:\\u005cu002225\\u005cu0022,\\u005cu0022requestType\\u005cu0022:\\u005cu0022voice\\u005cu0022,\\u005cu0022sessionId\\u005cu0022:\\u005cu00227c6c86fe-70e8-4710-9f28-41cda1236087\\u005cu0022,\\u005cu0022siteId\\u005cu0022:\\u005cu0022arbeitszimmer\\u005cu0022}\\u0022,\\u0022input\\u0022:\\u0022mach das licht on 25\\u0022,\\u0022intent\\u0022:\\u0022SetOnOff\\u0022,\\u0022lang\\u0022:null,\\u0022rawInput\\u0022:\\u0022mach das licht an\\u0022,\\u0022reopenVoiceInput\\u0022:\\u002225\\u0022,\\u0022requestType\\u0022:\\u0022voice\\u0022,\\u0022sessionId\\u0022:\\u002223c7fe3b-e54b-4c5a-8700-486193cf9935\\u0022,\\u0022siteId\\u0022:\\u0022arbeitszimmer\\u0022}\",\"input\":\"mach das licht on 25\",\"intent\":\"SetOnOff\",\"lang\":null,\"rawInput\":\"mach das licht an\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"56587ab6-39b3-486d-b8ca-741d085371a4\",\"siteId\":\"arbeitszimmer\"}", "lang": null}
Die Funktionalität ist aber völlig OK - vermutlich bis die Logausgabe irgendwelche max len überschreitet  :)
Lösungsvorschlag:
In der respond() die Zeile:
        $sendData->{customData} = toJSON($data) if ref $data->{customData} ne 'HASH';
#        $sendData->{customData} = toJSON($data);
statt er auskommentierten Zeile einsetzen.
Dies vermeidet das erneute Kopieren von $data in customData.
Grund: Beim Start eines neuen Dialoges ist m.W. der Key customData immer a) vorhanden und b) mit dem HotworId (String) gesetzt. Durch die o. Zeile wird $data komplett unter customData kopiert, wodurch letztlich die ursprünglichen customData nun als sub Key erscheinen. Es gibt also in customData den Key customData.
Die Änderung prüft, ob $data->{customData} ein hash ist und kopiert nur wenn es noch kein hash ist.
Habe ich geprüft und sehe keine Probleme - aber sicherheitshalber bitte noch einmal durchdenken - zwei Gehirne sehen mehr als eins :)

Allerdings ist das nur die halbe Lösung:
Die bei jedem Durchlauf steigende Anzahl von \uxxxx entsteht, wenn JSON format erneut in JSON format umgewandelt wird.
Ich habe daher getestet ob in der respond() $data innerhalb von sendData->customData überhaupt als JSON benötigt wird und $data als hash in den Key sendData->customData kopiert.
Die Umwandlung zu JSON wird ja in der activateVoiceInput().
Nach der JSON Umwandlung von $sendData dort, sieht $json exakt so aus, wie es sein sollte (meine ich) aber es funktioniert nicht, wie es soll.
An der Stelle hakt es noch. Die Session wird mit hermes/dialogueManager/startSession zwar aufgemacht, aber sofort wieder geschlossen und anschließend kommt hermes/hotword/$hotword/detected.

Problem gefunden und beseitig:
Schlicht zwei fehlende doubleqoutes ". Wenn man ein hash, welches ein Sub-hash (unsere customData) enthält in JSON umwandelt wird dieses natürlich korrekt in den JSON String integriert, aber ist selbst kein String. Der einfachste Weg ist customData vorher in JSON zu wandeln. Damit wird customData zu einem eigenen String, der als Wert für den Key customData von Rhasspy akzeptiert wird.
Damit die Daten in customData bei ggf. mehreren Durchläufen nicht ständig erneut in JSON umgewandelt werden, darf das nur einmal geschehen - sonst funktioniert es zwar, wird im Log allerdings völlig unleserlich.
Zusätzlich habe ich aus dem customData hash vorher alle Keys, die standartmäßig erzeugt werden, gelöscht. Es interessieren ja nur die Sonderkeys die wir selbst für die Steuerung des Ablaufs einsetzen. Beschreibung der Änderungen und neue 10_rhasspy.pm folgen nach weiteren Tests.



In Verbindung mit SilentClosure gibt es auch noch eine Merkwürdigkeit:
Hintergrund: Es gibt Fälle, bei denen Rhasspy ein IntentNotRecognized sendet mit input="", meist irgendwelche Geräusche. Die handleIntentNotRecognized() möchte ich daher so erweitern, dass wie bitte unterdrückt wird, wenn gar nichts erkannt wurde. Also wenn input=leer dann SilentClosure. Das funktioniert auch, aber anschließend ist bis zum sessionTimeout keine Eingabe mehr möglich. Wenn ich aber text = Leerzeichen einstelle, funktioniert es (bis auf das früher schon erwähnte kurze 'pfft')
Vermutlich war das Verhalten vorher schon so.

Im Log sehe ich:
Msg: hermes/hotword/toggleOff => {"siteId": "arbeitszimmer", "reason": "dialogueSession"}
Hintergrund und Lösung:
Ja, war schon vorher, ist aber nicht aufgefallen, weil da die Session ohnehin beendet war.
Etwas ähnliches hatten wir schon bei der Rhasspy Einstellung Error WAV. Dafür hatte ich extra eine leere WAV Datei erzeugt und so eingetragen: ${RHASSPY_PROFILE_DIR}/responses/Nix.wav. Nun, so ist es auch mit der Rhassspy Einstellung Recorded WAV, nur dass jetzt ein 0-Byte Datei auffällt, weil es danach ja weiter gehen soll. Rhasspy braucht also auf jeden Fall etwas abspielbares, wenn text leer ist. Mit einer gültigen WAV Datei mit 16 Bit Stille funktioniert es. Eigentlich ist das aber ein Workaround, weil er soll ja nichts abspielen, und so habe ich weiter experimentiert und die saubere Lösung gefunden. Wenn man bei hermes/dialogueManager/continueSession nicht einen leeren text Parameter übergibt, sondern gar keinen, dann versteht Rhasspy, was gemeint ist. Es kommt zwar immer noch das toggleOff dialogueSession aber sofort anschließend toggleOn  playAudio und er lauscht weiter.

Übriges habe ich auch getestet ob unsere wie bitte Funktion auch bei beliebigen Intents funktioniert. Es klappt prima. Wenn man an den sentence []{reopenVoiceInput:25} und []{RetryIntent:true} anfügt, wird nach einem sonst noch etwas, wenn das nächste Kommando nicht verstanden wurde, das wie bitte ausgegeben und die sessionId  bleibt dann solange erhalten, bis etwas sinnvolles verstanden wurde. Dann folgt wieder sonst noch etwas (wenn der timeout noch nicht abgelaufen ist).
Dafür ist nur eine kleine Änderung in der handleIntentNotRecognized() erforderlich.

Beta-User

#149
Zitat von: gregorv am 07 November 2024, 16:42:59Und ich dachte, ich kenne mich mittlerweile einigermaßen aus
Paßt doch - ich würde mich nicht trauen, ungetestete Versionen hier reinzuwerfen, wenn du am debugging verzweifeln würdest ;D . Und selbst finde ich zwar hin und wieder einen slot, um mal "von ferne" Code-Beschau zu machen, aber testen usw. geht halt deutlich seltener - vermutlich erst wieder Ausgangs der Woche.

Daher anbei die nächste (ungetestete) Iteration :) , die hoffentlich neben den "Kleinigkeiten" noch ein paar Ansätze enthält.

Zitat von: gregorv am 07 November 2024, 16:42:59Zeile 3575:
Vermutlich war die (neue) Zeile 3678 gemeint? Sollte (anders) gefixt sein (defined-or in der Zeile vorher).

Zitat von: gregorv am 07 November 2024, 16:42:59Frage: gibt es irgendwie die Möglichkeit bei TTS (bei mir Mimic 3) die Betonung mehr Richtung Frage zu drehen ? Das 'sonst noch was' klingt etwas gelangweilt.
Hmmm, vielleicht hilft schon die korrekte Interpunktion? ('sonst noch was?'). Sonst könnte man vermutlich noch mit irgendwelchen "Formatierungsanweisungen" (wie "jetzt in english") hantieren, aber das wäre ggf. sehr Mimic3 (oder MaryTTS)-spezifisch.

ZitatAch und noch was - hast Du einen Key für das Aktivieren von CancelAction vorgesehen ? (Ich hab den gesamten neuen Code noch nicht durch)
Wie ist das gemeint? Mit der neuen Funktion _get_filter...() ist es möglich, in einer (continuous session) CancelAction zu aktivieren - und das ist im Moment auch die Regel. Was noch fehlt, ist die Behandlung von "lass gut sein"-Sätzen in handleIntentCancelAction() ;) .

Zitat von: gregorv am 07 November 2024, 17:13:50Die handleIntentSetNumeric() habe ich erweitert, damit weitere Kommandos verarbeitet werden können
Hmmm, bin zwar nicht überzeugt, ob man das wirklich braucht, aber es schadet vermutlich auch nicht (nur der Code-Style war etwas anders als üblich). Was noch fehlt, ist eine sinnvolle Beschreibung/Erwähnung in der commandref ;)

Zitat von: gregorv am 09 November 2024, 17:09:57und hier nach mehreren Kommandos - das scheint mir nicht gewollt?
Nee, nicht gewollt. Das sind solche Dinge, die einem beim Coden eigentlich klar sein könnten, aber dann halt erst beim Debuggen auffallen...

M.E. ist die Prüfung auf HASH (an dieser Stelle, habe die Idee aber an anderer Stelle recycled) nicht der generische Ansatz, weil $data ja jedes Mal anders sein kann - wir machen im Moment nur mit den Daten in customData nichts, von daher scheint das egal zu sein. Ich _glaube_, es ist generischer, jedes Mal (ganz am Ende der Verarbeitung) den (alten) customData-key zu verwerfen und einen neuen zu generieren. Wenn man was "Mitnehmen" will, muss man dann natürlich schauen, dass man alle Daten direkt in $data hat und nicht im Unter-Key "customData". Komisch ist nur, dass Rhasspy anscheinend komisch reagiert, wenn man da (sauberes) JSON drinstehen hat. (Ich frage mich nur, ob der "kaputte" string dann auch wieder in RHASSPY funktioniert oder nicht... Anscheinend schon, sonst wäre die ref-Prüfung nicht funktional?)

Zitat von: gregorv am 09 November 2024, 17:09:57Es gibt Fälle, bei denen Rhasspy ein IntentNotRecognized sendet mit input="", meist irgendwelche Geräusche.
Den Fall gab es in der Vergangenheit auch schon, ich habe dazu mal den Prüfungs-Code (siehe den pod unten bei intentNotRecognized) nach vorne geschoben und aktiviert. Allerdings ist das was da jetzt steht erst mal ein erster Versuch der korrekten Handhabung des Falls. Da ist dein Input schon berücksichtigt, dass man den "text"-Key nicht belegen sollte.

Mal sehen, ob das in die richtige Richtung geht...

Nachtrag: Bin nicht sicher, ob ich alle inputs und Fragen aus den letzten Beiträgen abgearbeitet habe; bitte melden, wenn was wichtiges fehlt!
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