Ich experimentiere seit einiger Zeit mit Rhasspy und fhem. Da ich ohnehin viele Funktionalität in 99_myUtils.pm ausgelagert habe, bin ich dazu übergegangen CustomIntents zu erstellen. Falls da jemand einen Link zu umfangreicherer Dokumentation hat, bitte sagen, wo das zu finden ist.
Derzeit habe ich meine gesamten RollladenIntens in zwei CustomIntents gepackt mit Funktionen wie Auf, Zu, halb oder beliebige pos. Wenn der Intent zum fahren der Rollladen gestartet wird, lasse ich den Dialog offen und der Benutzer kann in dieser Zeit 'Stop' (oder ähnliches sagen, damit der Rollladen anhält.
Das funktioniert prima, aber nicht ganz so, wie ich mir das vorstelle.
Info:In der BlindStop sub wird nur ein String "Rollladen angehalten" zurückgeliefert.
Hier der return Wert, der in der sub "Blinds" zum Starten der Rolladen benutzt wird:
return {
text => "gestarted",
intent => {
name => "Blinds",
confidence => $data->{confidence}
},
sendIntentNotRecognized => 0,
sessionTimeout => 30,
customData => {
action => "blindMoving",
device => $name
}
};
Folgende Probleme konnte ich noch nicht lösen:
1. "sendIntentNotRecognized => 0": Wenn ich nach dem Start kein Stopp Kommando spreche, wird nach ca. 15 Sek. Intent not recognized gemeldet und die zugehörige Ansage wird gespielt. Wie kann ich erreichen, dass der Dialog sauber geschlossen wird ? (Falls das nicht geht, die Fehleransage nicht kommt)
2. "sessionTimeout => 30": Der Parameter wird ignoriert, Intent not Recognized kommt immer nach ca. 15 Sek. Wie kann ich den Zeitraum, in dem auf ein eventuelles Stop gewartet wird, verändern idealerweise auf den Wert, der für "drive-up-time-to-open" beim entsprechenden Rolladen eingestellt ist ?
3. Wenn wärend dieser Wartezeit ein Geräusch aufgenommen wird (z.B. vorbeifahrendes Auto), kommt 'Intent not recognized' und kein Stop ist mehr möglich. Gut wäre, wenn alles was nicht Stop (oder den im Intent angegebenen Alternativen - nein, halt...) ist, ignoriert wird und den Dialog nicht beendet.
Wäre super, wenn mir da jemand Hinweise geben könnte.
Allgemeine Fragen zum Rückgabewert:
Das Hash Format ist ja "key" => "wert" -> müssen numerische Werte auch in " eingeschlossen sein? Habe ich getestet, kein Unterschied (aber gerade die Werte werden ja auch ignoriert).
Sollte der Rückgabewert in VAR1 => {...} eingeschlossen sein ? (Habe ich noch nicht getestet)
Ist mein Syntax im Code Block überhabt richtig? (das meiste funktioniert ja)
Bei einem Array als Rückgabewert können nur alternative responses angegeben werden, von denen eine zufällig ausgewähl wird - oder hat man da auch Möglichkeiten der Steuerung?
Danke, Gregor
Hallo, willkommen bei den RHASSPY-Usern!
Ist leider schon sehr lange her, seit ich mich das letzte Mal mit diesen Sachen beschäftigt habe, von daher tappe ich auch ziemlich im Dunkeln rum. Weiter hatte ich bei der Entwicklung eher im Auge, die Dinge, die ich als "Basisfunktionalität" ansehe, eher direkt in den Code zu integrieren, CustomIntent war nicht mein Fokus, daher bitte die folgenden Anmerkungen mit einer gewissen Vorsicht genießen:
- Wenn ich das Beispiel in https://svn.fhem.de/trac/browser/trunk/fhem/contrib/RHASSPY/99_RHASSPY_Utils_Demo.pm (DialogueTest) richtig verstanden habe, wird der default timeout nur geändert, wenn ein ARRAY type per return zurückgegeben wird.
- ggf. kann man das ändern, indem man (ungetestet!) im RHASSPY-Code eine Zeile 4226 neu einfügt:
$timeout = $error->{sessionTimeout} if defined $error->{sessionTimeout} && looks_like_number( $error->{sessionTimeout} );
.
Das "Grundproblem" dürfte aber weiter bleiben, dass nach dem Abwarten des timeouts eine Info "angesagt" wird, dass der timeout um ist, weil eigentlich halt auf eine weitere Ansage gewartet wird. Möglicherweise (!) gibt es irgendwo noch ein Beispiel mit einer "silent" Rückgabe? Mir dämmert dunkel, damit auch rumexperimentiert zu haben, wenn dazu was zu finden ist, dann ggf. in dem sehr langen Entwicklungstread zu RHASSPY (ziemlich hinten).
Zu dem konkreten Problem mit dem Anhalten der Rollläden würde ich allerdings sowieso eher zu einer generischen Lösung tendieren, die auch dann funktioniert, wenn der Fahrbefehl von "irgendwoher" kam, also z.E. auch von einem Taster, einer Automatik, whatever.
Hier ein Auszug aus meiner sentences/SetNumeric, bei der auch solche "Spezialfälle" wie "Halb" oder eben das Anhalten mit abgedeckt sind, erläuterungen müßten in einem Thread mit "best practices", Lösungsansätze oder so zu finden sein:
( stoppe | stop ){Change:cmdStop} [<den>] $de.fhem.Device-blind{Device} [<rooms>]
( halte | halt ){Change:cmdStop} [<den>] $de.fhem.Device-blind{Device} [<rooms>] an
( <cmddrive> | <cmdmulti> ) [<den>] $de.fhem.Device-blind{Device} [<de.fhem:SetOnOff.rooms>] (halb (auf|zu)){Value:49.5}
( <cmddrive> | <cmdmulti> ) [<den>] $de.fhem.Device-blind{Device} [<de.fhem:SetOnOff.rooms>] (<etwas> | ein [(kleines{Factor:0.75} | großes{Factor:2} ) ] Stück{Value:15}) [weiter] (auf{Change:setUp} | zu{Change:setDown} )
(öffne{Change:setUp} | schließe{Change:setDown} ) [<den>] $de.fhem.Device-blind{Device} [<de.fhem:SetOnOff.rooms>] (<etwas> | ein [(kleines{Factor:0.75} | großes{Factor:2} ) ] Stück{Value:15})
Zitat von: gregorv am 03 Oktober 2024, 10:54:24Bei einem Array als Rückgabewert können nur alternative responses angegeben werden, von denen eine zufällig ausgewähl wird - oder hat man da auch Möglichkeiten der Steuerung?
Sorry, hatte ich irgendwie überlesen:
Soweit mir das in Erinnerung ist, wird bei jeder "response" geschaut, ob da was "pipe-separiertes" kam, und wenn ja, wird daraus zufällig einer dieser Blöcke ausgegeben. Diesen Teil kann man (unterstellt, das stimmt so) also nur dahingehend steuern dass man RHASSPY eine passende Auswahl vor die Füße wirft.
Dementsprechend kann man das schon "steuern", indem man eben im myUtils-Code selbst die Vorauswahl trifft oder eben nur die einen passende Antwort zuläßt.
Trifft das die Frage?
Hallo Beta-User,
vielen Dank für die ausführliche Antwort. Damit habe ich erstmal neues 'Futter' für weitere Recherchen und Tests. Ich schau mir die 10_RHASSPY.pm mal genauer an.
Bei Deinem sentence für die Rollladensteuerung (nehme ich an) wird der Dialog nicht offengehalten? Falls doch, wäre toll, wenn Du mir die zugehörige 'rhasspyMappings' und ggf. 'rhasspySpecials'. Noch zeigen könnntest. Ich hatte zuerst die generische Variante implementiert, konnte aber Stop nur realisieren, wenn das KeyWort erneut gesprochen wird, was einerseits lästig ist und außerdem ist die Rollladenfahrt ggf. schon beendet bevor man das geschafft hat. Deshalb hatte ich schon die ausführlichere response zu "gestartet" geschrumpft. "sag stop, um den Rolladen anzuhalten" geht gar nicht ohne 'sleep' im fhem Kommando sonst bleibt keine Zeit zum Stop
Weißt Du, ob man Rhasspy so einstellen kann, dass er keine Nachricht an FHEM schickt, wenn er zwar was erkennt, aber keinen intent findet ?
Bezüglich der Frage zum Array als return Wert ging es eher darum, ob man damit auch z.B. den Dialog offen halten kann (ich mag keine hashes).
Danke Gregor
Zitat von: gregorv am 03 Oktober 2024, 14:31:26Bei Deinem sentence für die Rollladensteuerung (nehme ich an) wird der Dialog nicht offengehalten?
Nein.
Und meine Devices (CUL_HM und ZWave) verstehen nativ ein "stop"-Kommando, "problematisch" ist da nur das Einstellen der Lamellendrehung, dafür sind dann rhasspySpecials definiert.
Wenn das Problem aber das Schließen des Dialogs nach "getaner Arbeit" ist, könntest du mal den sessionTimeout-Parameter im define testen bzw. mal schauen, ob es dazu irgendwelches feedback im "Mega-Thread" gibt.
Zitat von: gregorv am 03 Oktober 2024, 14:31:26Weißt Du, ob man Rhasspy so einstellen kann, dass er keine Nachricht an FHEM schickt, wenn er zwar was erkennt, aber keinen intent findet ?
Dazu ist mir nichts in Erinnerung, bei mir ist Rhasspy so konfiguriert, dass er immer irgendeinen Intent findet. Ich benutze aber praktisch nur die App und dort den "Knopf", kein Hotword.
Ggf. könntest du dich im "Mega-Thread" mal einklinken, da lesen dann eher die anderen mit, die sonst noch mit Rhasspy arbeiten. Eventuell liest auch der eine oder andere eher mit, wenn du den Threadtitel im ersten Beitrag so änderst, dass klarer wird, dass es hier um RHASSPY geht.
Zitat von: gregorv am 03 Oktober 2024, 14:31:26Bezüglich der Frage zum Array als return Wert ging es eher darum, ob man damit auch z.B. den Dialog offen halten kann (ich mag keine hashes).
Das geht, aber egal bei welcher Variante muss man strukturiert aufbereitete returns liefern, damit die Datenstrukturen im Großen und Ganzen schon zu dem passen, was hinterher als JSON vercodet werden muss. Hatte mich damals an dem orientiert, was lt. Doku bei Rhasspy möglich war, weniger an "user-friendlyness" im Vercoden innerhalb der myUtils. (PS: da war mir vieles von dem zu "frickelig", das damals vorzufinden war, als wir damit angefangen haben. Am Ende sind weniger "echte" Intents übrig geblieben bei größerer Funktionalität, weil eben viel mehr im Hauptcode gelandet ist...)
Danke, den Mega-Thread kenne ich und habe auch schon viele Anregungen daraus bezogen. Wenn man gerade auf der Suche nach etwas bestimmten ist, überliest man natürlich was gerade nicht aktuell ist; mit der Folge, dass ich schon mehrfach von Seite 1 angefangen habe - halt bis dahin, wo ich fündig wurde. Ist halt recht zeitaufwändig, aber macht Spass.
An dem Timeout Parameter bin ich gerade dran - wenn ich schaffe, die sub 'setSpeechDialogTimeout' mit der erwarteten Parametern zu füllen oder wenn ich zur richtigen Zeit $hash->{helper}->{SpeechDialog}->{config}->{$device}->{sessionTimeout} überschreiben kann (nämlich bevor die sub 'SpeechDialog_open' aufgrufen wird), könnte es klappen.
Alternativ hattest Du ja erwähnt, dass es mit einem Array als return geht. Werde ich auch testen.
Ich hatte mir heute mal OpenVoiceOS angeschaut aber mein erster Eindruck war, dass das noch viel komplexer wird. Meinst Du, das wäre echt eine Alternative zu Rhasspy ? Ich müsste dann auch meinen ESP-C3 Rhasspy Satelliten umschreiben. Solange keiner sagt, dass wäre DIE "Goldrand"- Lösung bleibe ich wohl erst mal bei Rhasspy.
Ist zwar schon eine Weile her aber ich denke, dass alle Anforderungen aus dem ersten Post bereits im Modul intergriert sind.
Ich versuche abends ein Beispiel zu frickeln.
Gruß Jens
ich arbeite gerade die 10_RHASSPY.pm durch und versuche zu verstehen, wie was funktioniert. Wäre super, wenn Du mir ein Beispiel senden könntest.
Wo wird eigentlich der SpeechDialog timer gesetzt ? Default ist wohl 20 Sekunden, ich sehe bei mir (nach der Ansage immer etwa 15 Sekunden. Die Änderung, die Beta-User vorgeschlagen hat fürht zwar zum Aufruf der entsprechenden sub und der Wert wird auch richtig übergeben, ergibt aber keine Änderung - hier mein Print dazu:
"starting: setDialogTimeout, timeout: 30"
.
Danke, Gregor
Zitat von: gregorv am 03 Oktober 2024, 21:09:40Ich hatte mir heute mal OpenVoiceOS angeschaut aber mein erster Eindruck war, dass das noch viel komplexer wird. Meinst Du, das wäre echt eine Alternative zu Rhasspy ? Ich müsste dann auch meinen ESP-C3 Rhasspy Satelliten umschreiben. Solange keiner sagt, dass wäre DIE "Goldrand"- Lösung bleibe ich wohl erst mal bei Rhasspy.
Vielleicht schaue ich mir das über den Winter mal an, bisher kenne ich das nicht.
Prinzipiell ist mir etwas zu wenig Bewegung auf der Rhasspy-Seite, und wenn, scheint es eher HomeAssistant-support zu betreffen. Ich würde mir wünschen, dass wenigstens die bekannten (kleinen!) Mängel in der Installation der letzten Version beseitigt werden. Aber es funktioniert ja, wenn man die Kniffe kennt...
Falls OpenVoiceOS die grundlegenden Dinge nicht substantiell anders macht (also im Kern Konfigurationen anzunehmen, und key-value-Infos auszutauschen), sollte es auch nicht allzuviel Aufwand sein, das Modul zu portieren.
Zitat von: gregorv am 03 Oktober 2024, 21:09:40An dem Timeout Parameter bin ich gerade dran - wenn ich schaffe, die sub 'setSpeechDialogTimeout' mit der erwarteten Parametern zu füllen oder wenn ich zur richtigen Zeit $hash->{helper}->{SpeechDialog}->{config}->{$device}->{sessionTimeout} überschreiben kann (nämlich bevor die sub 'SpeechDialog_open' aufgrufen wird), könnte es klappen.
Das ist nur bedingt richtig: Diese sub ist nur dafür da, wenn NICHT Rhasspy für die Ein- und Ausgabe genutzt wird, sondern ein Messenger oder AMAD oä.. Die "Musik" in der Kommunikation mit Rhasspy ist nämlich das hier in der "sub respond":
IOWrite($hash, 'publish', qq{hermes/dialogueManager/$topic $json});
Zitat von: gregorv am 05 Oktober 2024, 16:50:44Wo wird eigentlich der SpeechDialog timer gesetzt ? Default ist wohl 20 Sekunden, ich sehe bei mir (nach der Ansage immer etwa 15 Sekunden. Die Änderung, die Beta-User vorgeschlagen hat fürht zwar zum Aufruf der entsprechenden sub und der Wert wird auch richtig übergeben, ergibt aber keine Änderung - hier mein Print dazu:
Der steckt (für den Rhasspy-dialogmanager) in "$json". Kann durchaus sein, dass da was in der Kommunikation zu Rhasspy verbesserungsfähig ist, ich habe das damals mit den "kontinuierlichen Eingabemöglichkeiten" auch eher schnell dann wieder gelassen, weil irgendwas nicht funktioniert hat und mir dann das mit der Musikauswahl per Sprache wichtiger war. Ggf. müßte man das mal im Rhasspy-Code (oder Forum) verifizieren, wie das zu sein hat und ob es überhaupt jemals auf der Rhasspy-Seite funktioniert hatte, aber s.o..
Jedenfalls hatte "damals" kaum jemand bei Rhasspy viel mit Dialogen gemacht (oder ich habe es nicht gefunden), die FHEM-Lösung ist afaik sowas wie der "Gold-Standard", was solche Dinge angeht 8) .
Zitat von: gregorv am 05 Oktober 2024, 16:50:44Die Änderung, die Beta-User vorgeschlagen hat
Danke für die Rückmeldung, dann checke ich das bei Gelegenheit mal ein (es fehlt vielleicht noch etwas Doku dazu).
Hallo Beta-User,
etwas weiter, aber noch nicht fertig.
return setDialogTimeout($hash, $data, $timeout, $error);
hat deshalb nicht funktioniert, weil $error ein hash ist und setDialogTimeout einen Sting erwartet (hat allerdings keinen fehler ausgegeben)
$timeout = $error->{sessionTimeout} if defined $error->{sessionTimeout} && looks_like_number( $error->{sessionTimeout} );
return setDialogTimeout($hash, $data, $timeout, $error->{'text'});
setzt den DialogTimeout nun aud den Wert von Timeout.
Allerdings gibt es nur einen halben Erfolg:
1.Wenn ich das 'Stop' Kommando spreche, wird der Intent 'BlindStop' nicht mehr erkannt. Das hängt aber mit meinem Intent-Filter zusammen - lasse ich den weg, geht es wieder (aber alle anderen Intents natürlich auch). Ändere ich
return setDialogTimeout($hash, $data, $timeout, $error->{'text'});
zu
return setDialogTimeout($hash, $data, $timeout, $error->{'text'}, '[\"CancelAction\"]');
klappt es mit dem Timer UND ohne Fehleransage! - allerdings wird der Intent Filter ignoriert :-\
Auch noch nicht gut:
Wenn ich die Rolladenfahrt starte und nichts sage (oder der Intent nicht erkannt wird) kommt nach dem Timeout eine Fehleransage.
Da werde ich mir mal die handleIntentNotRecognized anschauen und das timeout handling.
Weiterhin ist zwar gut, dass ich nun den DialogTimeout einstellen kann, aber es kommt (vermutlich) vom Rhasspy Server immer noch ein IntentNotRecognized Event nach ca. 15 Sekunden.
Falls einer dazu eine Idee hat ?
Wieder ein Stück weiter:
ZitatWeiterhin ist zwar gut, dass ich nun den DialogTimeout einstellen kann, aber es kommt (vermutlich) vom Rhasspy Server immer noch ein IntentNotRecognized Event nach ca. 15 Sekunden.
Das kommt, wenn die Dauer von Stille den Wert überschreibt, der bei STT Kaldi unter "Maximum Duration:" eingestellt ist (default ist 30 Sekunden). Da stand bei mir 20 Sekunden. In Rhasspy gibt es an dieser Stelle offenbar einen Fehler. Im Log war zu sehen, das nach exakt 10 Sekunden (also nicht 20!!)
rhasspyasr_kaldi_hermes: -> AsrRecordingFinished(site_id=...
kommt. Bei Erhöhung des Wertes auf 30 waren es exakt 15 Sekunden und bei 60 dann 30 Sekunden. Der effektive Wert ist also immer die Hälfte von dem, was eingestellt wird.
Gut: Wenn in FHEM mit setDialogTimeout ein Wert < dem (echten) Timeout durch "Maximum Duration:", hat FHEM die Kontrolle über den DialogTimeout.
Und noch ein Stück:
Zitat- allerdings wird der Intent Filter ignoriert
Ich hatte in 'handleCustomIntent'
setDialogTimeout($hash, $data, $timeout, $error->{'text'}, '[\"CancelAction\"]');
Das CancelAction setzt den IntentFilter auf de.fhem:CancelAction (im hash unter .ENABLED)
Damit muss noch etwas geändert werden, um einen bestimmten Intent zu aktivieren:
my $intentFilter = $error->{intentFilter} if defined $error->{intentFilter} && ref $error->{intentFilter} eq 'ARRAY';
$timeout = $error->{sessionTimeout} if defined $error->{sessionTimeout} && looks_like_number( $error->{sessionTimeout} );
return setDialogTimeout($hash, $data, $timeout, $error->{'text'}, $intentFilter);
@Beta-User Damit sind die ersten beiden Zeilen komplett zusätzlich erforderlich und in der dritten Zeile statt
$error); nun
$error->{'text'}, $intentFilter);Interresant ist auch, dass der intentFilter im return hash des CustomIntent "intentFilter"->["
BlindStop"] sein muss und nicht "intentFilter"->["
de.fhem:BlindStop"], was aber dann funktioniert, wenn die sub setDialogTimeout mit einem ungültigen (3. oder 4.) Parameter aufgerufen wird.
Habe zugegebenermaßen gewisse Schwierigkeiten, mich in das Thema wieder einzudenken. Ist wirklich schon lange her, seit ich damit rumexperimentiert hatte. Lese aber einstweilen interessiert mit!
Das finding, dass der timeout über Kaldi definiert wird, ist z.B. ein nettes Detail, klingt danach, als wäre das gar nicht über MQTT abweichend parametrierbar?
Wenn du patches einreichen willst, übernehme ich das gerne, bitte aber darum, dass sich der Code-Style möglichst am RHASSPY-Code zu orienteren. Falls dazu Fragen sind, bitte melden.
Zitattimeout über Kaldi definiert wird
Die Einstellung des timeout über setDialogTimeout hat zumindest keinerlei Effekt auf den Zeitpunkt an dem Rhasspy den Dialog mitIntentNotRecognized beendet.
Sehe ich aber kein Problem, weil man den ja so groß einstellen kann, dass er nie vor dem SessionTimeout von FHEM zuschlägt - im Gegenteil, letzterer ermöglicht auch eine Unterscheidung zwischen timeout und IntentNotRecognized - der Kaldi timeout liefert ja immer (zumindest bei mir) IntentNotRecognized.
Das mit den Patches könnte für andere interessant sein. Aber da müsste erst noch jemand drüberschauen - ich hab in den 10_RHASSPY.pm code vor ein paar Tagen erstmalig gesehen. Wann welches Stück durchlaufen wird, weiß ich noch lange nicht.
Gerade hänge ich an dem nächsten Punkt: Wenn der Dialog auf das Stop wartet, soll ein IntentNotRecognized den Dialog nicht schließen sondern nur 'wie bitte?' sagen.
wieder ein Stück weiter:
ZitatWenn der Dialog auf das Stop wartet, soll ein IntentNotRecognized den Dialog nicht schließen sondern nur 'wie bitte?' sagen.
Hier habe ich die sub
handleIntentNotRecognized() am ende erweitert:
return; #Beta-User: End of recent changes...
durch
$data->{requestType} = 'voice'; # required, otherwise session open but no voice input possible
my $intentFilter = $data_old->{'.ENABLED'} if defined $data_old->{'.ENABLED'} && ref $data_old->{'.ENABLED'} eq 'ARRAY'; # get intent filter(s)
my @ca_strings; # just copied from setDialogTimeout
$intentFilter = split m{,}xms, $intentFilter if ref $intentFilter ne 'ARRAY';
if (ref $intentFilter eq 'ARRAY') {
for (@{$intentFilter}) {
my $id = qq{$hash->{LANGUAGE}.$hash->{fhemId}:$_};
push @ca_strings, $id;
}
}
my $response = getResponse( $hash, 'RetryIntent'); # get retry response
my $reaction = { text => $response, # hash to control respond() behaviour
intentFilter => [@ca_strings],
sendIntentNotRecognized => 'true', #'false',
customData => $data->{customData}
};
respond( $hash, $data, $reaction); # keep session open and continue listening
return $hash->{NAME};
ersetzt.
Damit wird nun der Dialog nicht unterbrochen, wenn ein Geräusch oder gesprochene Worte zu einem IntentNotRecognized führen.
In der Datei
rhasspy-de.cfg ist unter user->responses noch folgedes eingefügt:
"RetryIntent": "wiebitte? | was? | bitte nochmal",
Das hat sich allerdings noch nicht aktualisert - damit hatte ich schon mal Probleme.
update->language liest zwar die Datei neu ein, aber damit ist die Response noch lange nicht im hash. Selbst ein Neustart des FHEM Raspi und des Rhasspy Containers helfen da nicht. Irgendwann ist es dann da. Getestet habe ich deshalb mit
$response = "wiebitte?".
Die Änderung wird nur durchlaufen, wenn in
$hash->{helper}{'.delayed'}->{$identity} Daten vorhanden sind und wirkt daher möglicherweise auch bei Rückfragen nach Device oder Room, wenn da ein IntentNotRecognized käme (noch nicht getestet). Allerdings würde ich das eher als Feature statt als Problem sehen.
Jetzt bleibt noch die Beseitigung der DialogTimeout Ansage, falls ein Rolladen nicht gestoppt werden soll.
Da fällt mir gerade noch ein: Der Timeout von Kaldi MUSS jetzt größer eingestellt sein, als der DialogTimeout, weil sonst zwischendrin mehrere IntentNotRecognized kommen. Habe mal Maximum Duration: auf 10 eingestellt da kommt dann alle 5 (!!siehe oben Rhasspy Fehler) Sekunden 'wie bitte'. Man kann danach immer noch Stop sagen und es geht (falls es nicht zufällig mit dem Rhasspy timeout zusammentrifft) - da wurde Stop zwar ausgeführt, aber die Timeout Fehleransage kam trotzdem.
Genau so funktionieren auch mehrere Geräusche, die jeweils IntentNotRecognized auslösen und jedesmal mit 'wie bitte' quittiert werden. Ein Stop(halt,anhalten) ist danach noch möglich.
Ansonsten könnte man auch überlegen, ob diese 'wie bitte' Mimik auch an anderer Stelle Sinn macht.
Ich bin da nicht ganz sicher. Ich hatte mal ein YouTube Video über Roboter gesehen und dabei schmerzlich erfahren, dass mein KeyWort 'Roberta' seine Schwächen hat. Dauernd hat Rhasspy nachgefragt, was ich denn will. In so einem Fall kämen vermutlich 'wie bitte' Rückfragen ohne Ende und die Gefahr, dass Rhaspy aus dem Video-Sound irgendwas gültiges erkennt ist stark erhöht.
Zitat von: gregorv am 08 Oktober 2024, 00:00:08update->language liest zwar die Datei neu ein, aber damit ist die Response noch lange nicht im hash. Selbst ein Neustart des FHEM Raspi und des Rhasspy Containers helfen da nicht. Irgendwann ist es dann da.
Glaube ich nicht... In der Doku steht zutreffend:
ZitatDie Struktur muss dabei mit dem obigen Bereich übereinstimmen, wobei nur "Keywords" zugelassen sind, die vom Modul vorgegeben werden, und nur die Variablen aufgelöst werden können, die auch im (englischen) Ausgangstext vorgesehen sind. Er kann in diesem Rahmen entsprechend der nachfolgenden Muster im Prinzip beliebig erweitert werden.
Man müßte also erst sowas in der Struktur im Modul vorsehen ($languagevars, dort m.E. direkt bei den ersten paar "responses".
Generell scheint mir eine allgemeine "wie bitte"-Logik sinnvoll, es bleibt dann aber die Frage, wann es genug ist, sonst landet man uU. in einer ungewollten Dauerschleife, wenn es zu viel Hintergrundgeräusch (oder Probleme mit dem Mikro usw.) gibt.
Wenn wir grade bei sturkuturellen Änderungen an der $languagevars sind: Eigentlich hatte ich irgendwann noch im Plan, GetDate und GetTime in einem Intent zusammenzuführen, mit der man dann auch verschiedene Ausgaben haben kann (Zeit, Datum, Datum mit Zeit, langes Datum, langes Datum mit Zeit, ...)
Beides sollte man aber ggf. im "Mega-Thread" ankündigen, nicht, dass bisher zufriedene User plötzlich ungewohnte Effekte haben...
Hallo Beta-User,
Zitatwobei nur "Keywords" zugelassen sind, die vom Modul vorgegeben werden
Danke für den Hinweis, jetzt klappt es. Ich habe im Modul
10_RHASSPY.pm im hash
$languagevars unter
'responses' => { noch den Eintrag :
'RetryIntent' => 'Please try again',
An passender alphabetischer Stelle eingefügt. Das wurde allerdings erst nach 'shutdown restart' aktiv. Anschließende Änderungen in der
rhasspy-de.cfg mit
update->language werden sofort aktiv. Mein Fehler, dass die keyword im Modul konfiguriert sind, hatte ich übersehen.
Zitatsonst landet man uU. in einer ungewollten Dauerschleife
Ja, das stimmt zwar, aber spätestens durch den DialogTimeout wird die auf jeden Fall beendet (der Timeout wird nicht jedes mal neu gestartet - macht auch keinen Sinn, weil nach Ablauf des timeout der Rolladen eh am Ende angekommen ist; wird bei mir durch
$timeout = AttrNum($name,'drive-up-time-to-open', 30); gesetzt).
Sollte ein Timeout restart bei einer anderen Anwendung Sinn machen, muss man das natürlich beachten und könnte einen counter (maxRetry) einbauen.
Danke, Gregor
Wenn ich während Rhasspy auf ein Stop-Kommando wartet, gar nichts sage, soll am Ende keine Meldung kommen. Die geeignete Stelle dafür ist in der sub RHASSPY_DialogTimeout. Wenn ich mir dort den hash anschaue, sehe ich keine Möglichkeit diesen speziellen Fall zu erkennen.
Die ursprünglichen CustomData sind mit der Bezeichnung meines 'wake word' überschrieben (warum eigentlich?).
Ich kann zwar feststellen, dass der Intent ein CustomIntent ist, aber ich will ja nicht für alle CustomIntents den timeout stumm machen.
Frage:
Kann ich im Custom Intent noch eine Info mitgeben, die bis zum timeout überlebt ?
Oder gibt es eine Alternative ?
Danke Gregor
Ich hatte den Modul code durchsucht um die Stelle zu finden, an der customData überschrieben wird ->nichts gefunden ABER festgestellt, dass meine sub für den CustomIntent zwar die customData als hash liefert, die aber im Modul gar nicht in $data eingesetzt werden. Damit bleibt der Inhalt der vorher schon drin war erhalten.
Wenn der Custom Intent im Modul aufgerufen wird, landen die return Werte in $error(aus sub handleCustomIntent:
my $error = AnalyzePerlCommand($hash, $cmd);
deshalb habe ich in diese sub noch eine weitere Zeile eingefügt, sodass die gesamte Änderung da nun so aussieht:
$data->{customData} = $error->{customData}; #<------- NEU
my $intentFilter = $error->{intentFilter} if defined $error->{intentFilter} && ref $error->{intentFilter} eq 'ARRAY';
$timeout = $error->{sessionTimeout} if defined $error->{sessionTimeout} && looks_like_number( $error->{sessionTimeout} );
return setDialogTimeout($hash, $data, $timeout, $error->{'text'}, $intentFilter);
Die Änderung in der sub RHASSPY_DialogTimeout ist nun kein Problem.
Da habe ich VOR der Zeile
respond( $hash, $data, getResponse( $hash, 'DefaultConfirmationTimeout' ) );
die Zeilen:
my $respondFlag = $data->{customData}->{action} if defined $data->{customData}->{action} // 'NA' ;
( $respondFlag eq 'noTimeoutVoice') ? # if true
respond( $hash, $data, '') # no response
: # else, what already exists
Also die darunter stehende respond(...) Zeile wird nur ausgeführt, wenn in customData->{action} nicht 'noTimeoutVoice' steht - in dem Fall wird ein leerer response string übergeben.
Funktioniert und sollte keine Auswirkung für andere haben - außer jemand gibt liefert diese Daten explizit.
Werde morgen noch etwas testen und meinen CustomIntent erweitern - falls jemand inzwischen noch Anmerkungen hat bitte melden. Ich werde die gesamten nötigen Schritte im ersten Post anfügen, eingeschlossen weiterer Anregungen.
Zitat von: gregorv am 09 Oktober 2024, 01:10:46Werde morgen noch etwas testen und meinen CustomIntent erweitern - falls jemand inzwischen noch Anmerkungen hat bitte melden. Ich werde die gesamten nötigen Schritte im ersten Post anfügen, eingeschlossen weiterer Anregungen.
Anmerkung: m.E. sollte es reichen, erst mal die "diff -u" zu machen, also einfach alle Änderungen in Code/myUtils (Demo)/language-cfg als "patches" anzufügen. Wenn dann dazu Fragen von meiner Seite sind, werde ich mich schon melden.
Anregungen/Fragen natürlich gerne, wie schon mal angemerkt habe ich den Teil mit den Dialogen irgendwann dann nicht mehr im Detail weiter verfolgt. Da gibt es sicher manches, was man besser machen (oder auch nur dokumentieren) könnte.
PS: Hut ab, es ist m.E. eine große Leistung, sich in diese Dinge so schnell so weit einzuarbeiten!!!
@Beta-User,
Danke für das Lob, hat mich gleich animiert, noch was zu bauen.
Bei den Tests war mir aufgefallen, dass wenn ich den Rollladen gestartet hatte, aber gar nicht vorhabe Stop zu sagen, der Dialog per Design nun ja jedes weitere Kommando bis zum DialogTimeout sperrt (wegen Intent Filter). Deshalb habe ich nun eine Erweiterung eingebaut.
Funktion: wenn, ich während der DialogTimeout noch läuft, mein hotword spreche, wird der Dialog silent gestoppt und ein neuer Dialog gestartet. Mein hotword ist einfach als optionales Wort im Intent konfiguriert:
[de.fhem:BlindStop]
\[bitte] (stop|halt|anhalten|roberta)<--- mein Hotword
und im Modul, in der sub handleCustomIntent
statt der Zeile:
$response = $error; # if $error && $error !~ m{Please.define.*first}x;
folgede Erweiterung eingefügt:
my ($hotword,$siteId) = split(' ', $hash->{READINGS}->{hotword}->{VAL}); # read hotword Id and siteId from hash
if ($hotword =~ /$data->{input}/i ) { # if the hotword ID contains the input value
handleIntentCancelAction($hash, $data); # stop the dialog
activateVoiceInput($hash,[$siteId,$hotword]); # start new session
return;
}else{ # if hotword does not match do old stuff
$response = $error; # if $error && $error !~ m{Please.define.*first}x;
}
Funktioniert hervorragend! Durch den Start des neuen Dialog wird sogar der in Rhasspy konfigurierte 'Wake WAV' sound abgespielt - bei mir ein fragendes 'Ja?'
Jetzt brauche ich doch noch etwas Zeit zum Testen - ich habe im Modul Code schon wieder aufgeräumt und alle Änderungen mit einem Kommentar versehen. Suche nach '@@@' findet alle relevanten Stellen.
Ich habe da noch eine ganz verrückte Idee für eine alternative hotword Erkennung - muss aber noch darüber nachdenken und ist dann ein anderer thread.
Eigentlich sollte es möglich sein, dass man beim Start von fhem bereits eine session mit einem IntentFilter [de.fhem:Hotword] startet und auf ein hotword wartet. Bei [de.fhem:Hotword] beliebiges hotword einträgt (oder mehrere) und wenn erkannt, wird der Intent-Filter gelöscht. Wenn der SpeechDialogTimeout abgelaufen ist oder eine Session erfolgreich abgeschlossen ist, wird einfach eine neue session mit dem Intent-Filter gestartet.
Derzeit benutze ich Porcupine und da gefällt mir die offenbar gelegentlich stattfindende Überprüfung des Lizenz-Schlüssel nicht. Alles ist auf 'Offline' ausgelegt, aber da ist er dann doch online. Außerdem ist es aufwändig das hotword zu generieren und noch aufwändiger den Rhasspy Container (2.5.11) mit der neuen Porcupine Version zu aktualisieren. Das hat viel Zeit gekostet. (Falls einer das braucht, ich habe alles dokumentiert)
Zitat von: gregorv am 10 Oktober 2024, 01:09:53Funktioniert hervorragend!
Generell finde ich den Ansatz cool, hatte aber eine Idee für eine andere, generalisierte Lösung, die eventuell gleich die "Hotword"-Intent-Idee abfackelt:
Man könnte einen "resetInput" als vorrangigen Key vorsehen. Das (optionale) "hotword" in jedem Intent konfigurieren:
[de.fhem:BlindStop]
\[bitte] (stop|halt|anhalten|roberta{resetInput})
Dann müßte man halt in der ersten Abzweigung vorab prüfen, ob der Key im JSON war und könnte dann diesen Input resetten, und dabei ggf. auch berücksichtigen, ob es Voice (mit "activateVoiceInput" oder Text (ein messenger oä.) ist.
Meinung(en) dazu?
|roberta{resetInput})
das hört sich gut an. Da tun sich ja endlose Möglichkeiten auf.
Mir ist die Funktionsweise von xxxx{yyy} im sentence zwar noch nicht ganz klar, aber das schau ich mir mal an.
Die Mimik dafür müsste dann schon in die sub analyzeMQTTmessage() oder ?
Vermutlich bist Du durch meine Aktivitäten wieder richtig im Modul-Code drin.
Ich habe noch einen Fehler gefunden:
respond( $hash, $data, getResponse( $hash,'SilentCancelConfirmation' ) )
funktioniert nicht, wie es soll, weil $idenifier im Falle von SilentCancelConfirmation undef ist und letztlich die Ansage 'NoValidResponse' kommt. Mit einem space geht es zwar aber mein Satellit sagt dann 'pffft' und es wäre auch nicht sauber.
Der gefundene Fehler ist in der sub handleIntentCancelAction:
respond( $hash, $data, getResponse( $hash, 'SilentCancelConfirmation' ), undef, 0 );
und das kann bisher kaum richtig funktioniert haben. Ich habe das deshalb durch:
respond( $hash, $data, '' , undef, 0 );
ersetzt. Dass das bisher nicht aufgefallen ist, liegt wohl daran, dass es irgendwo noch eine andere Möglichkeit gibt eine response zu unterdrücken - vermutlich zufällig, weil ich in so einem Fall im log den Fehler:
[RHASSPY.IF] Parse: internal error: onmessage returned an unexpected value: RHASSPY.IF
sehe.
Gruß
Gregor
Zitat von: gregorv am 10 Oktober 2024, 13:02:41Vermutlich bist Du durch meine Aktivitäten wieder richtig im Modul-Code drin.
Leider nein, im Moment beantworte ich deine Fragen auf Basis einiger Blicke in den Code und dem, was mir noch in Erinnerung ist, wie es in etwa funktioniert...
Zitat von: gregorv am 10 Oktober 2024, 13:02:41Die Mimik dafür müsste dann schon in die sub analyzeMQTTmessage() oder ?
Anscheinend ist der Code so, dass man sich halbwegs orientieren kann => denke, dass das paßt (vor/nach "if ($mute)").
Zitat von: gregorv am 10 Oktober 2024, 13:02:41Dass das bisher nicht aufgefallen ist, liegt wohl daran, dass es irgendwo noch eine andere Möglichkeit gibt eine response zu unterdrücken - vermutlich zufällig, weil ich in so einem Fall im log den Fehler:
...meine Vermutung ist eher, dass bisher keiner so weit in das Thema eingestiegen ist... Wie gesagt: ich habe da einiges gemacht und vorbereitet, aber letztlich ist das Thema Dialoge bzw. "continuous session" dann doch ein "funktionierender stub" geblieben, und der Code ist an der Stelle auch nicht einheitlich: der $mute-Fall sollte eigentlich auch auf "silentCancelation" umgestellt werden, aber da ist das Leerzeichen hart im Code verankert ;) .
Zitat von: gregorv am 10 Oktober 2024, 13:02:41Mir ist die Funktionsweise von xxxx{yyy} im sentence zwar noch nicht ganz klar, aber das schau ich mir mal an.
Es gibt ein sehr gutes Video (im Wiki verlinkt?) zu der sentences.ini und den dortigen Möglichkeiten.
Mein erster Post auszugsweise:
[(kleines{Factor:0.75} | großes{Factor:2} ) ] Stück{Value:15}) [weiter] (auf{Change:setUp} ... ]
Damit werden optionale Zusatzinfos möglich (eckige Klammer außen):
"Factor" ist ein Multiplikator für einen Basiswert, der mit "15" in "Value" übergeben wird, wobei eben "großes" in "2" übersetzt wird, die Richtungsinfo kommt über "Change" usw. (die Keys sind in der commandref erläutert?).
Ergo landet bei unserem Beispiel "roberta{resetInput}" eben "roberta" im Key "resetInput". Ob man den Inhalt "roberta" dort braucht, sei dahingestellt, weil wir ja wissen, über welche siteId das kam und genau die ja wieder auf den default gestellt werden soll.
ZitatErgo landet bei unserem Beispiel "roberta{resetInput}" eben "roberta" im Key "resetInput
Stimmt, habe mal das {resetInput} eingesetzt und im log genau das gesehen. Den value roberta habe ich für die andere Variante nur gebraucht, um zu prüfen, ob das Gehörte auch wirklich das hotword ist.
Zitatanscheinend ist der Code so, dass man sich halbwegs orientieren kann
Stimmt auch, ich habe schon pms gesehen, wo ich nur 'Bahnhof' verstehe. Ich brauchte nur die Bestätigung. Danke für den Hinweis auf eine geeignete Stelle.
Zitataber da ist das Leerzeichen hart im Code verankert
man könnte auch:
$languagevars=>{responses}=>{SilentCancelConfirmation}
verwenden - ist aber irgendwie 'von hinten durchs Knie geschossen' :)
Zitat von: gregorv am 10 Oktober 2024, 14:35:01Danke für den Hinweis auf eine geeignete Stelle.
Gerne!
ZitatStimmt auch, ich habe schon pms gesehen, wo ich nur 'Bahnhof' verstehe. Ich brauchte nur die Bestätigung.
Ich auch... Daher auch der Versuch, manches halbwegs "lesbar" zu machen und die Teile, an denen ich auch noch am Grübeln war mir passenden Kommentaren zu versehen. Scheint zumindest bei uns beiden halbwegs zu funktionieren 8) .
Zitat von: gregorv am 10 Oktober 2024, 14:35:01... verwenden - ist aber irgendwie 'von hinten durchs Knie geschossen' :)
Na ja, ein "q{ }" hat vielleicht dieselbe Auswirkung, ist aber m.E. erheblich schwerer zu erfassen, wenn man einen schnellen Blick in den Code wirft... Daher würde ich die paar Byte und ms Rechenzeit mehr in Kauf nehmen ;) .
Zitat"...|roberta{resetInput}"
DONE!
Vereinfacht einiges. In sub
analyzeMQTTmessage hinter
if ($mute) eingefügt:
# @@@ added by GV start - THIS ends the current session if the wake word was spoken and starts a new session. Useful while waiting for choice or stop ...
if ( defined $data->{resetInput} && defined $data->{siteId}) { # if intent contains: ...command|hotword{resetInput}) and hotword was recognized
handleIntentCancelAction($hash, $data); # stop the dialog
activateVoiceInput($hash,[$data->{siteId}]); # start new session
return;
}
# @@@ added by GV end
und die Änderung in der sub
handleIntentCancelAction überarbeitet:
# @@@ changed by GV next two lines - THIS makes the cancel silent in case a new session shall be started
if ( !defined $data_old || defined $data->{resetInput}) { # a new dialogue is requested
respond( $hash, $data, '' , undef, 0 ); # respond silent
Damit kann man jede offene session durch Sprechen des hotword abbrechen und gleich eine neue starten (für andere Kommandos).
Aus der sub
handleCustomIntent habe ich die entsprechende Änderung wieder herausgeholt.
Ich muss jetzt mal überlegen, was man damit alls noch so machen kann. Gefällt mir jedenfalls.
Zitat von: gregorv am 10 Oktober 2024, 18:50:29DONE!
:)
Anmerkungen:
- analyzeMQTTmessage() wird zunehmend eine Sammlung von diversen Kasuistiken, glaube, da sollte man mal aufräumen :-*
- "SilentCancelation" würde ich gerne als Keyword beibehalten (s.o.). Man kann "nichts" natürlich auch jedes Mal am Zeilenende kommentieren, mir wäre es aber lieber, dass wir da, wo es möglich ist, "selbsterklärenden" Code verwenden (siehe auch unten).
- "return" sollte bei den "Endfunktionen" was zurückgeben (damit die fhem-Event-loop sauber angesteuert wird). Von daher ist der "$mute"-Fall auch nicht "sauber" beendet (aber da ist man ja durch den Kommentar gewarnt, dass es ein stub ist).
- Ob man bei "nichts" dann überhaupt irgendwas an die siteId sendet, wäre die Frage. Es musss halt "endSession" gesendet werden.
- Da es verschiedene site-Typen gibt (text/voice), muss man das auch berücksichtigen (siehe Punkt 1).
Mal sehen, ob ich den ganzen Input hier soweit zusammenfrickeln kann, falls du nicht schneller bist...
Zitatfalls du nicht schneller bist.
vermutlich nicht - in den nächsten paar Tagen.
Ich möchte heute erst mal die geänderten Dateien zusammenfassen, meine 99_myUtils.pm aufräumen und eventuell aus den zwei CustomIntents (Blinds,BlindStopp) nur einen machen und ggf. ein paar screenshots machen. Bis zum Nachmittag häng ich die Dateien mal an.
Ich schau mir das mit
SilentCancelation und auch
SilentTimeout mal an. Das sollte man so ändern können, dass es über den 'normalen' Weg geht.
Hmmm,
bin jetzt nochmal etwas intensiver über den Code gegangen und hab' noch den einen oder anderen Wackler gefunden.
M.E. müßte das angehängte (ungetestet, also nur, falls es sich laden läßt...) im Großen und Ganzen die Problemchen hier adressieren, allerdings etwas anders, als das hier diskutiert war, z.B., was das Durchreichen von "customData" angeht.
Mal sehen, ob ich über das WE zum Testen komme.
Hallo Beta-User,
ist ja schon fast alles drin! Aber es gibt leider noch einen Fehler (mag aber sein dass mein intent noch was liefern muss).
wenn ich die Rolladen Fahrt starte (Intent Blinds), dann hört Rhaspy zwar die Antworten, aber meldet zu allem IntentNotRecognized - selbst bei dem hotword, was ja keinen Intent startet - und beendet die session.
Nach dem Start von meinem Intent sehe ich im log mehrere Fehlermeldungen, wie:
ERROR: >ARRAY(0x61344a0)< returned by the RHASSPY ParseFn is invalid, notify the module maintainer
Logauszug angehängt.
Ansonsten habe ich noch optional silent timeout in RHASSPY_DialogTimeout eingebaut.
statt:
respond( $hash, $data, getResponse( $hash, 'DefaultConfirmationTimeout' ) );
eingefügt:
my $response = ( defined $data->{customData} && defined $data->{customData}->{silent} ) ? 'SilentCancelConfirmation' : 'DefaultConfirmationTimeout';
respond( $hash, $data, getResponse( $hash, $response ), undef, 0 );
und die WieBitte? Funktion hätte ich noch gerne drin (bei IntentNotRecognized). Sollte nur dann kommen, wenn z.B. ein spezielles customData vorhanden ist.
Könnte aber sein, dass das schon geht - komme ich wegen des Fehlers aber nicht hin.
Hier ist ganz schön was los. Ich hatte leider keinen Ansatz für das timeout in der Referenz gefunden.
Kann mir bitte jemand mit einer beispielhaften MQTT-Anweisung auf die Sprünge helfen?
Zitat von: JensS am 11 Oktober 2024, 19:30:40Ich hatte leider keinen Ansatz für das timeout in der Referenz gefunden.
Kann mir bitte jemand mit einer beispielhaften MQTT-Anweisung auf die Sprünge helfen?
Hmmm, das mit dem timeout ist m.E. mehrdeutig, und ich hatte heute morgen dann auch keine Zeit mehr, auch dazu noch was zu suchen bzw. zu schreiben. Jedenfalls:
- RHASSPY kennt eigene timeouts für "seine" Dialoge und verwaltet die auch intern. Das war das, was im ursprünglichen Code für CustomIntents mit "timeout" gemeint gewesen war.
- Wie das an Rhasspy ggf. _auch noch_ weitergegeben wird: Aktuell eine Wissenslücke bei mir... Kann sein, dass es jetzt funtioniert, weil/wenn man im return-HASH (-Teil) dann auch noch was weitergibt.
Bitte ggf. selbst mal schauen, ich komme zumindest an diesem WE eher nicht dazu.
Zitat von: gregorv am 11 Oktober 2024, 14:56:25Nach dem Start von meinem Intent sehe ich im log mehrere Fehlermeldungen, wie:
Hängt vermutlich an der Notation der Initialisierung des Arrays, müßte ggf. der Einfachheit halber auf die push-Variante umgestellt werden?
Zitat von: gregorv am 11 Oktober 2024, 14:56:25Ansonsten habe ich noch optional silent timeout in RHASSPY_DialogTimeout eingebaut.
Klingt sinnvoll! Allerdings meine ich mich zu entsinnen, dass "customData" flacher Text sein muss, so dass der Code entweder nicht funktioniert oder nur dann, wenn man da was manipuliert. Von daher würde ich auf die Schnelle dazu neigen, customData mit "silentTimeout" (oder so) zu vergleichen, so dass man den Dialog damit wie beabsichtigt schließen kann, ohne was "regelwidrig" zu verbiegen. Da der Dialog dann fertig ist, wird der originale Inhalt ja nicht mehr benötigt, oder liege ich da falsch? (=Bitte mal im Code recherchieren)
Zitat von: gregorv am 11 Oktober 2024, 14:56:25und die WieBitte? Funktion hätte ich noch gerne drin (bei IntentNotRecognized). Sollte nur dann kommen, wenn z.B. ein spezielles customData vorhanden ist.
Könnte aber sein, dass das schon geht - komme ich wegen des Fehlers aber nicht hin.
Ist mir durchgerutscht, sorry.
Ein "Wie bitte" finde ich generell richtig, wenn grade ein Dialog mit dem Satelliten offen ist. Hintergrund (wieder lt. meiner evtl. falschen Erinnerung!): Da die anderen Intents deaktiviert sind, werden die Sätze zwar uU. erkannt, aber eben ohne passenden Intent geliefert, so dass das ggf. ein sogar recht häufiger Fall ist.
Dann war mir noch irgendwas im Kopf, das ich aus der Diskussion hier versehentlich nicht eingearbeitet hatte. Leider habe ich zwischenzeitlich vergessen, was das war :-[ ...
Bewußt noch nicht drin ist der generalisierte "GetDate"-Intent, dafür war einfach keine Zeit. Falls jemand verstanden hat, wie ich das meine und Lust hat, Code zu liefern: Her damit! ;D
@Jens
ZitatHmmm, das mit dem timeout ist m.E. mehrdeutig
Soweit ich in den Logs gesehen habe, kommt von Rhasspy ein
IntentNotRecognized, wenn die bei Rhasspy unter Kaldi eingestellte
Maximum Duration: abgelaufen ist (!! der timeout kommt immmer nach der Hälfte der eingestellten Sekunden -> eingestellt 60 -> Timeout nach 30s). In
fhem ist es eher störend, wenn der zuschlägt.
fhem hat einen eigenen timeout nachdem auch als Grund für die Beendigung der session
reason timeout gemeldet wird (
und nicht IntentNotRecognized) Dieser
fhem timeout wird mit der sub
setDialogTimeout und da mit
resetRegIntTimer (im Modul) eingestellt das installiert die Callback Funktion
RHASSPY_DialogTimeout. Daher sollte der Rhasspy Kaldi timeout möglicht nicht 'reinpfuschen' -> also so groß, wie möglich einstellen werden (habe noch nicht gefunden, was das Maximum ist. Und NEIN, ein
MQTT wird durch
setDialogTimeout gar nicht gesendet, das ist
fhem intern.
@Beta-User
ZitatHängt vermutlich an der Notation der Initialisierung des Arrays, müßte ggf. der Einfachheit halber auf die push-Variante umgestellt werden?
Da überforderst Du mich - so tief stecke ich in Perl nicht drin - eventuell weiß Jens das ?
entsinnen, dass "customData" flacher Text sein muss
Kann wohl beides sein. Beim Start einer session steht da immer die Bezeichnung der
hotword Datei (Dateiname in /profiles/de/porcupine) als flacher Text drin. Zumindest bei mir.
Ich sende da mit meinem
CustomIntent ein hash (innerhalb vom
customData hash) und der wird von
fhem klaglos bis zum Ende der Session behalten und man kann ihn beliebig verwenden.
Das habe ich übrigens von perplexity, der KI Suchmaschine, erfahren und ich hab keine Ahnung, wo die das her hat. Der kann man zwar nicht alles glauben, aber gibt ganz gute Tips und sogar fhem code Beispiele, aus denen man allerdings nur die Ideen übernehmen sollte. Bei Fehlern erklärt sie immer
Tut mir leid... eingentlich wäre es logisch, dass die library existiert.
Alternativ wäre so etwas wie
{xx:yy} im sentence möglich, aber dam muss man dann aufpassen wohin man das setzt, sonst macht
fhem? aus
yy den
input Wert.
ZitatIst mir durchgerutscht, sorry.
Kein Problem. Hier noch mal der Code dazu:
# @@@ added by GV start - THIS keeps the session open speaking "please try again" after IntentNotRecognized - can repeatedly happen, but terminates with dialogue timeout
$data->{requestType} = 'voice'; # required, otherwise session open but no voice input possible
my $intentFilter = $data_old->{'.ENABLED'} if defined $data_old->{'.ENABLED'} && ref $data_old->{'.ENABLED'} eq 'ARRAY'; # get intent filter(s)
my @ca_strings; # just copied from setDialogTimeout
$intentFilter = split m{,}xms, $intentFilter if ref $intentFilter ne 'ARRAY';
if (ref $intentFilter eq 'ARRAY') {
for (@{$intentFilter}) {
my $id = qq{$hash->{LANGUAGE}.$hash->{fhemId}:$_};
push @ca_strings, $id;
}
}
my $response = getResponse( $hash, 'RetryIntent'); # get retry response
my $reaction = { text => $response, # hash to control respond() behaviour
intentFilter => [@ca_strings],
sendIntentNotRecognized => 'true', #'false',
customData => $data->{customData}
};
respond( $hash, $data, $reaction); # keep session open and continue listening
return $hash->{NAME};
# @@@ added by GV end
Vermutlich wird jetzt der Code einfacher. Wenn DATA jetzt komplett ist kann man den Intent Filter da rausholen und ggf. ein silent auch. silent hatte ich deshalb genommen, weil das für timeout
und für cancel benötigt wird - um das ganz logisch zu machen, kann man auch silentCancel/silentRetry und silendTimeout einführen - wird Dir wahrscheinlich besser gefallen.
Beispiel in rhasspy-de.cfg:
"RetryIntent": "wiebitte?|wass?|bitte nochmal|hää?",
Gruß Gregor
Zitat von: gregorv am 12 Oktober 2024, 00:13:03ein MQTT wird durch setDialogTimeout gar nicht gesendet, das ist fhem intern
Gut erklärt; genau da könnte man ggf. ja mal versuchsweise ansetzen.
Zitat von: gregorv am 12 Oktober 2024, 00:13:03Da überforderst Du mich - so tief stecke ich in Perl nicht drin - eventuell weiß Jens das ?
Ein einfaches diff hätte dir da vermutlich schon weiter geholfen (oder ein Texteditor für Win.* wie notepad++, falls diff nicht in den Bordmitteln ist):
#my @updatedList = [$hash->{NAME}];
my @updatedList;
push @updatedList $hash->{NAME};
Zitat von: gregorv am 12 Oktober 2024, 00:13:03um das ganz logisch zu machen, kann man auch silentCancel/silentRetry und silendTimeout
Habe das jetzt einfach so umbenannt, dass man einheitlich erkennen kann, dass mit "silence" geschlossen werden soll...
In der Funktion war wieder zwangsweise "voice" drin. Bitte sowas nicht machen, es gibt text-Type Satelliten, und es wäre angenehmer, wenn ich nicht ständig darauf hinweisen müsste... (Mir ist allerdings nicht klar, warum man das überhaupt noch braucht, weil "analyzeMQTT.." das jetzt per default und immer macht. Unterschiedliche Versionen? Bitte diff nutzen ;) .)
Hab's nochmal versucht zu konsolidieren, aber jetzt muss ich dringend was anderes machen ;D .
Danke für die Erläuterungen. CustumData muss tatsächlich "flacher Text" sein. Ein Array oder Hash müsste dann zerlegt werden.
Hier ein altes Beispiel, bei dem ich ein Wort buchstabiere und an eine Wiki-Sub schicke.
rhasspyIntents:
WIKI=WIKI(DATA)
Wikipedia=Wikipedia(DATA,Begriff)
ABC=ABC(DATA,Ch)
intentFilter=de.fhem:ABC='false'
sentences.ini:
[de.fhem:WIKI]
(bitte| ) schlage in der wikipedia nach
[de.fhem:ABC]
($de.fhem.ABC){Ch}
(suche){Ch}
(abbruch){Ch}
[font=Verdana, Arial, Helvetica, sans-serif]sub ABC{[/font]
sub ABC{
my $data = shift // return;
my $Buchstabe = shift // return;
my $SessionJSON = decode_json($data);
my $SessionID = $SessionJSON->{'sessionId'};
my $SiteID = $SessionJSON->{'siteId'};
my $CustomData = $SessionJSON->{'customData'}.$Buchstabe;
if ($Buchstabe ne "suche"){
my $ReturnHash = {
sendIntentNotRecognized => q{true},
intentFilter => [qq{de.fhem:ABC}],
customData => $CustomData,
text => $Buchstabe
};
return $ReturnHash;
}
else{
$CustomData =~ s/WIKI de.fhem:ABC //g;
$CustomData =~ s/suche//g;
Wikipedia($data,$CustomData);
}
}
sub WIKI{
my $data = shift // return;
my $SessionJSON = decode_json($data);
my $SessionID = $SessionJSON->{'sessionId'};
my $SiteID = $SessionJSON->{'siteId'};
my $ReturnHash = {
sendIntentNotRecognized => q{true},
intentFilter => [qq{de.fhem:ABC}],
customData => qq{WIKI de.fhem:ABC },
text => q{bitte buchstabiere}
};
return $ReturnHash;
}
sub Wikipedia{
my $data = shift // return;
my $Begriff = shift // return;
my $SessionJSON = decode_json($data);
#my $SessionID = $SessionJSON->{'sessionId'};
my $SiteID = $SessionJSON->{'siteId'};
#my $CustomData = $SessionJSON->{'customData'};
#my $Readingsstring = "$SiteID $SessionID $CustomData";
#fhem("setreading Rhasspy SessionID $Readingsstring");
$Begriff =~ s/ /_/g;
$Begriff =~ s/ä/%C3%A4/g;
$Begriff =~ s/ö/%C3%B6/g;
$Begriff =~ s/ü/%C3%BC/g;
$Begriff =~ s/ß/%E1%BA%9E/g;
my $ua = LWP::UserAgent->new();
my $response = $ua->get("https://de.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=$Begriff");
my $content = $response->content;
my $ref = JSON->new->decode($content);
my ($jsonHash) = values %{ $ref->{'query'}->{'pages'} };
my $Response = $jsonHash->{'extract'};
$Response =~ s/\(/ /g;
$Response =~ s/\)/ /g;
$Response =~ s/\;/ /g;
$Response =~ s/\'/ /g;
$Response =~ s/\./ - /g;
$Response =~ s/\:/ /g;
$Response =~ s/\,/ /g;
$Response =~ s/\'/ /g;
$Response = lc $Response;
$Response = encode('utf-8',$Response);
return $Response;
}
Gern würde ich mich wieder mehr reinknien aber aktuell habe ich zuviele andere Verpflichtungen.
Gruß Jens
ZitatGut erklärt; genau da könnte man ggf. ja mal versuchsweise ansetzen.
Zumindest könnnte man, wenn es denn geht, sicherstellen, dass der
DialogTimeout nicht größer als die Kaldi Maximum Duration ist. Muss man aber vorsichtig sein, weil ein zu großer Wert für Maximum Duration den Timout plötzlich auf 15 Sekunden zurückspringen läßt (ich hatte mal 120 Sek. probiert - werde das Thema mal im Rhaspy Forum unterbringen)
ZitatEin einfaches diff hätte dir da vermutlich schon weiter geholfen
Das hatte ich sogar zuerst gamacht und diese Änderungen auch gesehen, aber nicht mit
ZitatInitialisierung des Arrays, müßte ggf. der Einfachheit halber auf die push-Variante
in Verbindung gebracht. Probiere ich mal.
überhaupt noch braucht, weil "analyzeMQTT.."
Mein Lösungsansatz war noch der aus der früheren Modul Version und da ging es ohne das nicht. Teste ich auch.
@Jens
Zitatmuss tatsächlich "flacher Text" sein. Ein Array oder Hash müsste dann zerlegt werden
Mhhhh Bei customData sind, soweit ich weiß, die möglichen Werte ja flexibel. Wer das verwendet, muss daher vor dem Lesen immer erst prüfen, was da drin
defined ist. Demnach sollte ein hash als Wert grundsätzlich keine Probleme machen.
Als Parameter, der im Modul abgefragt wird, hast Du aber Recht. Die beste Lösung wäre, dass ein Parameter auf root Ebene.
Ich habe daher den Parameter
silent in die sub
Define unter:
for (keys %{$h}) {
... | |
...sessionTimeout|silent|handleHotword|noChangeover|experimental|Babble|autoTraining)\z}xm;
eingetragen und kann den damit im return hash für CustomIntents verwenden. Die Abfragen im Modul Code ändere ich entspechend.
@BetaUser
Da ist mal wieder bestätigt, warum ich hashes nicht mag - ich weiß nie so genau, was Perl daraus macht - bevorzuge eindeutig JSON aber das ist mit Perl ja kaum zu handhaben.
ZitatHängt vermutlich an der Notation der Initialisierung des Arrays, müßte ggf. der Einfachheit halber auf die push-Variante umgestellt werden?
Nee, war am Anfang, wo Du das erste Element in
@updatedList reinschreibst. Das muss
@updatedList = $hash->{NAME}; sein, also ohne
[]. push macht offenbar aus einer Variablen automatisch ein Array mit zwei Elementen. Hätte ich auch mit [] initialisiet.
weiter unten war noch ein
push @updatedList, $hash->{NAME}; , wohl nur ein Schönheitsfehler - ich hab es sicherheitshalber auskommentiert
Die Fehlermeldung ist jetzt jedenfalls weg und die Funktion wieder da.
Test ABBRUCH!
In der Modulversion verschwinden offenbar session data - nach längerer Suchzeit habe ich aufgegeben.
Verhalten ist: sobald die Stop session offen ist kommt auf alle Antworten (inclusive hotword) ein IntentNotRecognized und die Session wird silent geschlossen.
resetInput ist nicht mehr da und in $data fehlen Daten aus dem ursprünglichen return hash (sind auch im haupt hash nicht mehr da) und der Intenfilter ist auf ["ConfirmAction","CancelAction"] geändert.
Mit der alten Modulversion werde ich weitermachen und dann nach und nach die Änderungen einbauen - mal sehen, ab wann es dann klemmt.
Hab mir nochmal session_timeout angeschaut und keine Möglichkeit zur Änderung während der Laufzeit gefunden.
https://github.com/rhasspy/rhasspy-dialogue-hermes/blob/0d697cb1631880688fd484177f1cdf50351f4a3e/rhasspydialogue_hermes/__init__.py#L130 (https://github.com/rhasspy/rhasspy-dialogue-hermes/blob/0d697cb1631880688fd484177f1cdf50351f4a3e/rhasspydialogue_hermes/__init__.py#L130)
Um das umzusetzten, müsste einiges in Rhasspy selbst angepasst werden. In customData könnte man ein session_timeout unterbringen und in https://github.com/rhasspy/rhasspy-dialogue-hermes/blob/0d697cb1631880688fd484177f1cdf50351f4a3e/rhasspydialogue_hermes/__init__.py#L368 (https://github.com/rhasspy/rhasspy-dialogue-hermes/blob/0d697cb1631880688fd484177f1cdf50351f4a3e/rhasspydialogue_hermes/__init__.py#L368) in eine neue Variable schreiben.
Dann müsste man nur noch die Variable als zusätzlichen Parameter bis zu https://github.com/rhasspy/rhasspy-dialogue-hermes/blob/0d697cb1631880688fd484177f1cdf50351f4a3e/rhasspydialogue_hermes/__init__.py#L773 (https://github.com/rhasspy/rhasspy-dialogue-hermes/blob/0d697cb1631880688fd484177f1cdf50351f4a3e/rhasspydialogue_hermes/__init__.py#L773) schleusen und fertig. 8)
Meine Erfahrungen mit Codevorschlägen bei https://community.rhasspy.or (https://community.rhasspy.org/)g sind aber eher ernüchternd...
Gruß Jens
p.s. Die einzige Möglichkeit zur Änderung von session_timeout liegt wohl aktuell darin, den Wert in supervisord.conf vor dem Programmaufruf anzupassen.
@Jens
ZitatcustomData könnte man ein session_timeout
ist schon drin und im Modul angepasst und funktioniert. Man sollte nur den Timeout in Rhasspy größer einstellen, sonst kommt nach dessen Ablauf IntentNotRecogized und die session wird früher beendet.
Mit der auch impementierten 'wiebitte?' Funktion im Modul wird die Session nicht beendet und nach dem 'wieBitte?' weiter auf ein gültiges Kommando gelauscht - bis eben zum fhem timeout.
Sorry, wo genau?
@Beta-User
Gibt's eine Möglichkeit in rasppySpezials ein customData mitzuschicken?
Teste gerade eine simple Implementation im Code.
Gruß Jens
@Beta-User
MEIN ENTWURF
Ich habe Deinen Code weitgehend eingearbeitet und habe die Bereiche, wo noch ToDos sind markiert '@@@@'. Sonstige Änerungen von mir sind mit @@@ gekennzeichnet. Die Funktion ist soweit in Ordnung (Schöheitsfehler im Log, die Ansage für DefaultCancelConfirmation kommt nicht und 'SilentCancelConfirmation' => q{}, fuktioniert doch nicht. Da die Funktionalität aber störungsfrei genutzt werden kann, habe ich die 10_RHASSPY.pm und die rhasspy-de.cfg mal zum Testen angehängt.
Neue Funktionen:
Ein CustomIntent kann nach Absetzen eines FHEM Befehls auf weitere Anweisungen warten.
Anweisungen (auch Geräusche), die nicht zum IntentFilter passen, werden mit 'wie bitte?' oder so quittert und die Anweisung kann korrekt wiederholt werden.
Der Dialog wird so lange offen gehalten bis ein einstellbarer sessionTimeout abgelaufen ist.
Sagt man währen der Wartezeiten ein beliebiges 'hotword' wird sofort eine neue Session gestartet und ein anderes Kommando kann abgesetzt werden.
Die 'Warte-Session' wird, je nach Einstellung, ohne Ansage beendet.
Steuerung:
Abbrechen eines bestehenden Dialges und sofort neues Kommando aktivieren (ist für alle Dialoge möglich):
Im betreffenden Sentence in Rhasspy das optionale Wort [porcupine:{resetInput}] einfügen. Oder als zusätzliches Word (der|die|das|porcupine:{resetInput}) anhängen. Dieses 'Hotword' kann ein beliebiges Wort sein, auch unterschiedlich für jeden Sentence
Auch ist das bei jedem Sentence möglich, bei dem eine Rückfrage erfolgen könnte. Es wird dadurch kein neuer Intent gestartet, sondern auf 'höherer' Ebene abgefangen.
Weiteres funktioniert nur bei einem CustomIntent, weil Daten zur Steuerung im 'return' als hash übergeben werden müssen:
Maximale Dauer des Dialoges: "sessionTimeout"=> int, Int ist ein numerischer Wert z.B. AttrNum($name,'drive-up-time-to-open', 30) , also die Zeit, die bestimmter ein Rollladen zum Hochfahren benötigt.
Wichtig ist, die in Rhasspy eingestellte Zeit unter Maximum Duration: (unter STT) auf einen großen Wert einzustellen UND ! oben ist dazu ein Fehler beschrieben.
'WieBitte' Funktion, falls Rhasspy in der Dialog Zeit ein IntentNotRecognized erkennt (Geräusch, falsches Kommando, ...)
Dafür sind folgende Einstellungen nötig:
° "what"=>"true", aktiviert die Rückfrage (die man ignorieren kann) und der Dialog bleibt offen. Das kann auch mehrfach (während der Dialogzeit) vorkommen.
In der Datei rhasspy-de.cfg kann man eigene Wünsche konfigurieren z.B. "RetryIntent": "wiebitte?|waaas:|bitte nochmal|häääh:",
° "intentFilter"=>["BlindStop"], muss nicht sein, ist aber sehr sinnvoll. Sonst kann man ggf. die Rollladen-Fahrt nicht mehr anhalten. BlindStop kann natürlich ein
beliebiger Intent sein und es können auch mehrere Intents angegeben werden.
° "silent"=>int, - wobei int ein binär kodierter Wert ist, mit dem eine stumme Ausgabe an mehreren Stellen eingestellt werden kann.
Im Moment funktioniert ohnehin nur 2, was SilentTimeout entspricht und dafür sorgt, dass die Ansage nach Ablauf des Dialoges unterdrückt wird.
Mögliche andere Werte sind SilentIntentNotRecognized = 1 und SilentCancel = 4 bzw Kombinationen davon durch Addition (z.B SilentTimeout und SilentCancel entspricht 6)
° mit dem bereits bekannten Parameter "text":"gestartet" legt man die Antwort fest. Ich erwähne das hier nur, weil die so kurz wie möglich sein sollte
sonst ist der Rollladen schon unten, bevor man Stop sagen kann
Beispiel beim Einsatz als Rollladensteuerung (da werde ich noch ein BeispielIntent zu liefern):
Sage 'Rollladen im Arbeitszummer zu' -> Antwort:'gestartet'. Nun gibt mehere Möglichkeiten:
1. Sage 'Stop', -> Antwort:'Rollladen angehalten'
2. Es fällt der Schlüssel auf den Boden man Hüstelt oder sag Mülleimer -> Antwort 'Wie bitte' -> anschließend sage 'Anhalten' -> Antwort 'Rollladen angehalten'
3. Sage das Hotword -> Rhasspy reagiert als habe er das 'Wake Word' gehört (mit Abspielen der wav Datei unter Rhasspy Wake WAV) und ein beliebiges anderes Kommando kann gegeben werden.
ZitatrasppySpezials ein customData
Das würde mich auch interessieren - eventuell auch die anderen Optionen, die beim CustomIntent im return hash möglich sind?
@Jens
ZitatSorry, wo genau?
Ist eben erst als Beta angehängt zum Testen.
Zitat von: gregorv am 13 Oktober 2024, 22:37:49@Beta-User
MEIN ENTWURF
Thx, hoffe, ich komme dazu, mir das zeitnah mal näher anzusehen. Falls du je ein unified diff zu meiner letzten Version von hier und zur "offiziellen" machen könntest, wäre das ggf. hilfreich, dann kann ich ggf. meine Kommentare in das diff reinfrickeln?
Ich habe den Verdacht, dass da ggf. auch was an den kleinen Änderungen verloren gegangen war, die in meiner letzten Version drinne waren*.
Zitat von: JensS am 13 Oktober 2024, 21:19:53@Beta-User
Gibt's eine Möglichkeit in rasppySpezials ein customData mitzuschicken?
Ich habe vermutlich die Frage nicht verstanden...
Anmerkungen zu customData:
- lt. Definition ist es flacher Text, was anderes geht ja auch per MQTT nicht über den Äther...
- Man _könnte_ ggf. jede Art Daten da mitsenden, wenn man sie serialisiert (z.B. mit toJSON).
- Dunkle Erinnerung sagt mir: Ich habe mich damals dagegen entschieden, alles mögliche Ein- und wieder Auszupacken und das Ganze stattdessen so konzipiert, dass halt mit jedem Satelliten parallel immer nur ein Dialog laufen kann (?). Das hat den Vorteil, dass man intern (über ~ identifier und oldData) alles mögliche unkompliziert zwischenspeichern kann. Es ist nur nicht wirklich dokumentiert, was man damit in myUtils-Code anstellen könnte.
Aber an der Stelle könnte man z.B. auch die Info speichern, ob "wiebitte?" schon (x) mal durchlaufen war.
*Generell würde ich gerne ggf. eine "ungefährliche" Version als Zwischenversion (mit cfg und myUtils) einchecken, damit wir nicht zu viele Änderungen gleichzeitig nachziehen müssen, wenn wir jetzt weiter feilen.
PS: Es sollte alles unfallfrei durch perlcritic (onlne: http://perlcritic.com/) auf Stufe 3 laufen. Vermutlich muss dann "use constant" durch readonly ersetzt werden (wenn man die Werte überhaupt zentral braucht).
Zitat von: JensS am 12 Oktober 2024, 23:43:05Hab mir nochmal session_timeout angeschaut und keine Möglichkeit zur Änderung während der Laufzeit gefunden.
Thx für die Analyse.
Prinzipiell gehe ich nicht davon aus, dass synesthesiam noch irgendwas an Rhasspy 2.x schrauben wird, und wir wenn, dann eher den update auf 3.x in FHEM nachziehen müssen (das wird dann aber vermutlich was größeres, da weg von MQTT).
Es bleibt daher erst mal bei dem, was gregorv angemerkt hatte: Man muss halt (wenn in Verwendung) in Kaldi den Wert dauerhauft anpassen, wenn man längere Werte haben will.
Zitat von: gregorv am 12 Oktober 2024, 21:08:25Test ABBRUCH!
In der Modulversion verschwinden offenbar session data - nach längerer Suchzeit habe ich aufgegeben.
Habe vermutlich noch nicht alles verstanden, was da passiert. Vermutlich ist da irgendeine Schleife drin und/oder der Rhasspy-timeout schlägt dann doch irgendwann zu?
Vielleicht noch was zu "IntentNotRecognized":
Allgemein wird das auch ausgegeben, wenn ein Intent an sich erkannt werden würde, aber dieser grade deaktiviert ist. Von daher müßte auch was im "input"-Feld zu finden sein, wenn es einer dieser "bekannten Sätze" war?
Vielleicht könnte man das auch nutzen, wenn eine Sitzung offen ist und "Schleifengefahr" zu bannen ist?
Und: Zu kurze Sätze sollte man m.E. vermeiden, weil sonst die große Gefahr besteht, dass Geräusche usw. als Eingabe missverstanden werden. Irgendwo müßte aus diesem Grund die Empfehlung zu finden sein, dass der kürzeste mögliche Satz "Nein" (oder so) sein sollte, damit im Zweifel "CancelAction" durchlaufen wird und alles resettet wird...
@Beta-User,
die von mir gesandte Version basiert auf der Original-Version. Da habe ich meine Änderungen und anschließend Deine Änderungen nach und nach eingebaut und zwischendrin getestet. Die von Deinen Änderungen, die zu Fehlfunktionen (@@@@) geführt haben sind auskommentiert, aber alle drin. Ein anschließender compare zeigt nur noch die @@@@ Stellen und die Funktionserweiterungen, @@@ die in Deiner Version noch nicht drin waren.
Ich schick Dir aber noch ein unified diff.Sowas habe ich aber noch nie als Datei benötigt und bräuchte ich einen Tipp, womit man das macht. Google zeigt ja viele Möglichkeiten, aber Du möchtest ja sicher ein bestimmtes Datei-Format.
Zitathttp://perlcritic.com
mault tatsächlich über die Verwendung von
use constant und das wäre auch unabhängig davon, ob das global oder in einer sub plaziert wird. Grund: es funktioniert nicht, wenn es in Gänsefüßchen verwendet wird - in einem Log3 z.B. Eigentlich wollte ich aber explizit solche #defines haben, die nicht wie eine Variable aussehen und global sollten die auch sein.
Ansonsten gibt es nur zwei Stelllen, bei denen ich eine Variable mit
my innerhalb einer
condition definiere - das kommt sicher, weil ich viel mit Arduino und ESP mache, wo jedes Bit zählt), ach ja, und Tonnen von
too complex Meldungen, die aber schon vorher da gewesen sein müssen.
ZitatVielleicht noch was zu "IntentNotRecognized":
Das wäre interressant -
wiebitte? muss ja nicht kommen, wenn es nur ein Geräusch war - das schau ich mir mal genauer an. Im
input habe ich bisher nur sinnlose Worte gesehen.
ZitatUnd: Zu kurze Sätze sollte man m.E. vermeiden ...
Ja, das ist korrekt. Ich habe viel mit den
Kaldi settings 'Speech Before:', 'Minimum Duration:' ... herumgespielt bevor mein Feuerzeug nicht mehr als Eingabe erkannt wurde und
ja, Nein . trotzdem funktioniert.
Zitatoffen ist und "Schleifengefahr" zu bannen
In der (Test-)Praxis hat das bisher nicht gestört, kam da recht selten vor - zumindest wäre m.E ein Zähler nicht erforderlich, schon gar nicht, wenn man Geräusche noch ausfiltern könnte. Wenn ich es gezielt das
wieBitte? so oft, wie möglich hören wollte, habe ich es innerhalb von 21 Sekunden nur 4-5 mal geschafft.
Frage:Weißt Du, wie lang der Timeout (von fhem) sein kann ?
Gruß
Gregor
Noch was vergessen:
Zitatder Rhasspy-timeout schlägt dann doch irgendwann zu?
Das wäre kein Problem - kommt ja
IntentNotRecogized, was mit
wieBitte? quittiert wird.
Und noch zur Frage von Jens (und mir) bzgl. der customData:
Bei CustomIntents kann man die ja im Code als return Wert anhängen - geht das eventuell auch bei anderen Intents z.B SetOnOff durch irgendwelch Kniffe (RhasspySpecials oder so)?
Zitat von: gregorv am 14 Oktober 2024, 11:22:33Ich schick Dir aber noch ein unified diff. Sowas habe ich aber noch nie als Datei benötigt und bräuchte ich einen Tipp, womit man das macht.
Kommt auf's Umfeld an, unter Linux einfach in eine Datei umleiten. notepad++ sollte auch diff ausgeben können, da habe ich das aber bisher nie gemacht. Das sind am Ende simple text-Files, kann man als txt kennzeichnen oder z.B. als .patch. Ist egal ;) .
Zitat von: gregorv am 14 Oktober 2024, 11:22:33Eigentlich wollte ich aber explizit solche #defines haben, die nicht wie eine Variable aussehen und global sollten die auch sein.
Das ist C-Style und da auch völlig ok. Für Perl heißt das Stichwort "Readonly", was nichts anderes sind wie normale Variablen, nur halt nicht änderbar.
Zitat von: gregorv am 14 Oktober 2024, 11:22:33Ansonsten gibt es nur zwei Stelllen, bei denen ich eine Variable mit my innerhalb einer condition definiere - das kommt sicher, weil ich viel mit Arduino und ESP mache, wo jedes Bit zählt), ach ja, und Tonnen von too complex Meldungen, die aber schon vorher da gewesen sein müssen.
M.E. sind die Definitionen innerhalb einer Condition immer ein Fehler, den man halt in C/C++ nicht machen kann, weil die Deklaration einer Variablen (samt Type) zwingend vorab erfolgen muss...
"Too complex" läßt sich leider kaum verhindern, der Code ist schon einigermaßen refactored, das war früher noch viel übler ;D ...
Zitat von: gregorv am 14 Oktober 2024, 11:22:33Im input habe ich bisher nur sinnlose Worte gesehen.
Habe den Teil bisher nicht angesehen, aber bei nochmaligem Nachdenken den Verdacht, dass es ggf. auch von der STT-Engine abhängt und es von daher ggf. gefährlich sein könnte, irgendwas von dieser Info abhängig zu machen.
Zitat von: gregorv am 14 Oktober 2024, 11:22:33Weißt Du, wie lang der Timeout (von fhem) sein kann ?
Im Prinzip beliebig. Er wird aber afair jedesmal erneuert, wenn eine Antwort gesendet wird, von daher weiß ich nicht, ob das "innerhalb 21 Sekunden" die "korrekte Denke" zu dem Thema ist (muss ggf. selbst nochmal in den Code sehen).
Zitat von: gregorv am 14 Oktober 2024, 11:30:59Bei CustomIntents kann man die ja im Code als return Wert anhängen - geht das eventuell auch bei anderen Intents z.B SetOnOff durch irgendwelch Kniffe (RhasspySpecials oder so)?
Soweit ich mich entsinne, spielt sonst "customData" keine Rolle und mir wäre auch nicht klar, für was man das benötigen würde. Wie vorhin geschrieben: RHASSPY ist an der Stelle anders gestrickt, mutmaßlich hatte ich Schwierigkeiten mit (von Rhasspy geänderten?) customData, weiß es aber auch nicht mehr genau...
Wenn ihr eigenen Code schreibt, wäre es m.E. zielführender, die Identifizierungsmerkmale aus oldData zu holen, wie z.B. das in ConfirmAction vercodet ist.
Angehängt die diffs:
10_RHASSPY.pm release-Beta-User.txt offizielles Release zu Deiner Version 11.10.2024
10_RHASSPY.pm Beta-User-Gregor.txt Deine Version 11.10.2024 zu meiner Version 13.10.2024
10_RHASSPY.pm release-Gregor.txt offizielles Release zu meiner Version 13.10.2024
ZitatSoweit ich mich entsinne, spielt sonst "customData" keine Rolle
Noch nicht! - ich hatte zumindest daran gedacht mit vorhandenen
fhem Bordmitteln beim
Device sowas wie
"what"=>"true" anzuhängen um damit ggf. die
wieBitte? Funktion auch für 'nicht-CustomIntents' nutzen zu können.
Zitat von: gregorv am 14 Oktober 2024, 16:46:1910_RHASSPY.pm release-Beta-User.txt offizielles Release zu Deiner Version 11.10.2024
Thx, dachte mir schon, dass die Zwischenversion vom 12. untergegangen war. Bin jetzt aber zuhause und kann selbst diffs machen.
Frage zu den Keys im define: Welchen Sinn machen die? Wenn es nur darum geht, zusätzliche Infos an der RHASSPY-Instanz unterzubringen, würde ich das lieber über setreading oder userAttr lösen wollen.
Feedback zu den Commandref-Aktualisierungen und Anmerkungen in der myUtils wären auch willkommen, wobei ich mir mit dem customData-Ding schon wieder nicht sicher bin, ob das wirklich funktioniert und man nicht besser empfehlen sollte, die oldData-Referenz unterhalb $hash->{helper} zu verwenden...
Was neue Keys in der language-cfg angeht, wäre meine Bitte, sowas künftig gleich "ordentlich" mit zu pflegen, also ein etwas weniger lässiges "Wie Bitte?" bei den Sätzen oben (nicht "user") reinzumachen - jedenfalls dann, wenn es wie hier einigermaßen wahrscheinlich ist, dass der key auf die eine oder andere Weise kommt.
Mal sehen, ob ich es schaffe, einen Satz an eincheckbaren Dateien zeitnah hier einzustellen (zum Testen).
Zitat von: gregorv am 14 Oktober 2024, 16:57:15Noch nicht! - ich hatte zumindest daran gedacht mit vorhandenen fhem Bordmitteln beim Device sowas wie "what"=>"true" anzuhängen um damit ggf. die wieBitte? Funktion auch für 'nicht-CustomIntents' nutzen zu können.
Hmmm, bin mal gespannt, wie das aussehen soll. Die hartcodierten Intents fangen jedenfalls afair nichts mit den Infos aus customData an, und im Moment sehe ich auch nicht, dass es an sowas fehlt.
Zitat von: Beta-User am 14 Oktober 2024, 09:22:57Prinzipiell gehe ich nicht davon aus, dass synesthesiam noch irgendwas an Rhasspy 2.x schrauben wird, und wir wenn, dann eher den update auf 3.x in FHEM nachziehen müssen (das wird dann aber vermutlich was größeres, da weg von MQTT).
Bin mir sicher, dass 3.x nicht der Renner ist . ;D
Meine Idee ist, für die 2.x ein paar modifizierte Dateien inkl. Installationsanleitung zur Verfügung zu stellen.
Deaktivierte Intents und Neustartinfo per MQTT könnten z.B. auch mit rein.
Gruß Jens
ZitatBin mir sicher, dass 3.x nicht der Renner ist .
das sehe ich genau so - ist seit langer Zeit nur eingeschränkt funktionsfähig ;D
ZitatThx, dachte mir schon, dass die Zwischenversion vom 12. untergegangen
Oh, sorry, das habe ich tatsächlich nicht gesehen.
ZitatFrage zu den Keys im define: Welchen Sinn machen die?
Damit soll man die Audio Ausgabe allgemein für ein oder mehrere Resultat(e) einstellen können und es ergibt selbstsprechenden Code z.B. '
if defined silent && silent & CancelSilent ...
Im return hash beim CustomIntent übergibt man z.B.
'... , "silent" => '.CancelSilent + TimeoutSilent.', ... '
. Das '
use constant' Problem bei Perl ist bei
#define in C übrigens auch da, nur kenne ich keinen, der das als Problem sieht.
ZitatBitte, sowas künftig gleich "ordentlich" mit zu pflegen
ist oben schon ganz gesittet drin :) und JA, kannst Du beliebig umformulieren, sollte nur kurz sein (also nicht wie oben - sonst ist der Rolladen schon unten bevor man
Stop sagen kann
Zitatsetreading oder userAttr lösen wollen
gute Idee! daran hatte ich noch gar nicht gedacht (obwohl ich selbst schon
$timeout = AttrNum($name,'drive-up-time-to-open', 30); im CustomIntent verwende
Zitatunterhalb $hash->{helper}
kann man da durch irgendwelche settings am fhem device Infos reinschreiben?
Noch zu setDialogTimeout:
wenn der timeout gesetzt ist, bleibt er erhalten, egal wie viele Antworten kommen.
Nur ein neues setDialogTimeout startet den timeout neu oder eine neue session wird gestartet.
Beispiel: Rhaspy wartet auf Stop und man sagt das hotword. Dann startet der Timer mit default Timeout (30 Sekunden)
Zitat von: gregorv am 14 Oktober 2024, 23:29:40kann man da durch irgendwelche settings am fhem device Infos reinschreiben?
Na ja, direktes "Rumgepfusche" im Device-Hash ist immer schwierig, weil man das Risiko hat, dass man beim Löschen was übersieht und damit ein Speicherloch aufreißt. Würde tendenziell immer zu den "offiziellen" Methoden (attr und Reading) raten. Die Ausnahme wäre ggf., wenn man im laufenden Dialog irgendwas an den "Altdaten" ergänzen muss/will, die dann durch das Schließen auch wieder sauber entsorgt werden. Da war "nur" das Problem, dass man nicht mit Kopien arbeiten darf, sondern besser mit der Referenz auf's Original (so jedenfalls meine Erinnerung).
Zitat von: gregorv am 14 Oktober 2024, 23:29:40. Das 'use constant' Problem bei Perl ist bei #define in C übrigens auch da, nur kenne ich keinen, der das als Problem sieht.
"constant" ist in Perl Scheiße, wenn du dir manche komischen Klimmzüge in MYSENSORS.* ansiehst ("+CONSTANTNAME"), verstehst du vielleicht, warum ich diese critic voll teile. Andere Programmiersprachen haben das Problem ggf. nicht bzw. anders gelöst. In "meinen" Code kommt sowas jedanfalls nicht neu rein :P , und schon gleich nicht, wenn man eine "Vorratsvariable" an genau einer Stelle "braucht" (Bitgeschubse war bisher nicht meins, von daher finde ich persönlich den Code eher "unleserlich").
Würde sagen, wer den Key in seinem return-Hash setzt, weiß was er tut und dabei kann man es auch bewenden lassen bzw. ggf. den nächsten timeout damit setzen, falls es "nicht 0" ist. Das ist imo KISS - oder übersehe ich was wichtiges?
Ansonsten bin ich immer noch der Ansicht, dass jeder optionale Key im define auch in der commandref beschrieben sein sollte und tendenziell für eine Mehrzahl von Usern interessant, also in diesem Sinne "wichtig". Wer myUtils-code (und CustomIntent) verwendet, kann RHASSPY auch gleich passend beliefern, das ist also eher "exotisch"
Zitat von: gregorv am 15 Oktober 2024, 00:04:56Noch zu setDialogTimeout:
Thx für den Hinweis, war mir nicht mehr bewußt, dass es "vor" "respond" noch eine Abzweigung gab, die man explizit wählen muss. Bin halt immer noch nicht wieder komplett im Code drin.
Review dauert noch etwas, allerdings finde ich den Teil "# just copied from setDialogTimeout" an dieser Stelle (unabhängig vom berechtigten percritic-Punkt) nicht in sich logisch. An der Originalstelle kann es sein, dass die Funktion entweder mit einer kommaseparierten Liste oder einem Array aufgerufen wird, hier verwenden wir entweder das, was schon gesetzt war (~oldata - warum eigentlich? Rhasspy wird ja in den seltensten Fällen neu gestartet...), oder wir MÜSSEN die Variable sinnvoll füllen, wenn da nichts steht (oder den Teil überspringen, wenn das nicht geht?).
Zitat# just copied from setDialogTimeout
die Kommentare sind hauptsächlich nur für Dich und sollten raus- dieser speziell war markiert, weil man da eine sub draus machen könnte. Ich meine, dass die Mimik noch öfter vorkommt.
Du kennst Dich besser im Code aus, ich hatte hauptsächlich die Funktion im Auge. Fühl Dich frei alles umzuwerfen.
Ich habe inzwischen mal meine Intents aufgeräumt. Die subs sind angehängt
(Blind custom intents.pm) am einfachsten kopiert man die 'as is' in die
99_myUtils.pm, weil dann die zugehörige
rhasspyIntents Einstellung passt (in
Einstellungen.txt). Da ist auch der Auszug von
Sentences.ini und ein Dump von einem Rolladen Device (ich habe
Dooya).
Im Code und Sentences wäre da für eine HM Steuerung nur
pos duch
pct (und zum Lesen
position duch
pct) zu ersetzen. und in den Sentences die entsprechenden Kommandos für Auf,Zu,Stop.
Ich selbst habe für die Tests allerdings die
fhem("set ... Zeilen auskommentiert. Sonst machen sich die Nachbarn Gedanken um meinen Gesundheitszustand :)
Zitat von: gregorv am 16 Oktober 2024, 15:41:17die Kommentare sind hauptsächlich nur für Dich
Na ja, das war von meiner Seite nur wegen der Code-Stelle, auf die sich das bezog. Das kann jedenfalls so nicht "sauber" funktionieren, auch wenn das Ergebnis ggf. so aussehen sollte als ob ;) ...
Zitat von: gregorv am 16 Oktober 2024, 15:41:17war markiert, weil man da eine sub draus machen könnte. Ich meine, dass die Mimik noch öfter vorkommt.
_Vielleicht_ gibt es dazu schon eine sub - configure_DialogManager().
Aber vielleicht nochmal ein Schritt zurück:
- Die CustomIntents-Schnittstelle ist funktional, aber "eigentlich" hat sich bisher keiner so richtig für die Funktionalität im Detail interessiert. An sich _sollte_ die Rückgabe vollen Zugriff auf die RHASSPY/Rhasspy-Funktionalität erlauben. Da habe ich im Moment große Zweifel, würde andererseits aber auch nicht mehr dazu "raten", notfalls selbst im RHASSPY-Hash rumzumalen oder Funktionen wie configure_DialogManager() direkt aufzurufen. Geht zwar alles, bringt aber Probleme mit sich, falls (!) wir doch mal an den Punkt kommen, irgendein neues Interface für "Rhasspy 4.x" (oder was anderes ?) zu basteln.
- Ich bin "damals" den Weg gegangen, alle gewünschten Funktionalitäten möglichst direkt im Modul unterzubringen, weswegen der Code im Prinzip auch schon so angelegt ist, dass diverse Dinge modular aufgerufen werden können, je nach Bedarf. Ist sicher nicht 100% fertig, wie wir jetzt sehen, aber es ging wohl nach meinem jetzigen Gefühl deutlich in diese Richtung.
- ergo sollten wir mAn. nun versuchen, die CustomIntent-Schnittstelle so aufzubohren, dass man mit entsprechenden Schlüsseln auch alle Tore so aufbekommt, dass das auch noch paßt, wenn wir größer renovieren müssen.
Etwas andere Baustelle:
Der eigentliche Wunsch war ja, nach (z.B.) einem "setNumeric" den Dialog offen zu halten. Die eigentlichen Kommandos, die für die Dooya erforderlich wären, scheint RHASSPY ja zu erkennen, auch "stop" oder so sollte (außer dem Zeitproblem) ohne weiteres umsetzbar sein.
Daraus könnte man auch den Schluss ziehen, dass man statt eines "lasse generell das Mikro noch eine Zeitlang offen" ein "mache in speziellen Fällen das Mikro nicht aus" direkt im Modul vorsieht. Lösen liese sich das ggf. über zwei Wege:
- einen "continueDialogue"-Key, den man (wie den anderen neuen Key) überall setzen kann, und der dann (optional) einen Wert für die Dauer enthalten könnte, und/oder
- ein rhasspySpecial, der das für einzelne Geräte/Kommandos (?) konfigurierbar macht.
Vermutlich ließe sich der erste Ansatz mit dem key "relativ leicht" lösen (abgesehen von den vielen Detailfragen, die ich im Moment vermutlich übersehe..., und der Zeit, die das ggf. kostet, die ich im Moment nicht wirklich aufbringen kann).
Meinungen dazu?
Zitatnun versuchen, die CustomIntent-Schnittstelle so aufzubohren, dass man mit entsprechenden Schlüsseln auch alle Tore so aufbekommt
Sehr gut, so habe ich mir das vorgestellt.
Keys für
Dialog offen lassen, optional
Dauer, optional
Response abschalten ...
Für die möglichen Parameter könnte man noch Ideen anderer FHEM-Rhasspy Nutzer sammeln.
ZitatEtwas andere Baustelle:
Das wäre sogar noch besser, wenn die zusätzlichen Parameter auch bei Standard Intents machbar wären.
Das sollte am Device konfigurierbar sein, weil man so ggf.
Attribute oder
Readings mit übergeben kann. Ich nutze z.B. das Attribut
drive-up-time-to-open für die Dialog Dauer und das Reading
position um z.B mit dem Kommando
weiter die Fahrrichtung zu ermitteln und eine neue Position x% höher oder tiefer anzufahren. (Readings müssten während der Dialog Dauer aktuell sein.)
Mehrfach hintereinander (ohne vorher jedes mal das Wake Word) Lauter / Leiser wäre ein anderes Beispiel oder mehrfach hintereinander Programmwahl - so zu sagen: Zappen ...
Ich glaube kaum, dass HA oder Alexa da mitkommen :o
Zitat von: gregorv am 17 Oktober 2024, 11:21:19Ich glaube kaum, dass HA oder Alexa da mitkommen :o
Weiß nicht, habe keine Erfahrung damit. Meine persönliche Intention war, die Gadgets in meiner Hausautomatisierung einigermaßen flexibel per Sprachsteuerung steuern bzw. abfragen zu können, irgendwas "externes" (Wetter in Haithi etc.) war nie mein Fokus. Und das Ganze sollte ohne cloud laufen.
Den Job macht Rhasspy/RHASSPY (abgesehen von ein paar Kleinigkeiten, an denen man noch feilen könnte) zu meiner vollen Zufriedenheit, und ich kann mir auch nicht vorstellen, dass eine externe Lösung das "stressfreier" (oder mit weniger Missverständnissen) hinbekommen könnte.
Von daher ist das vermutlich bereits heute so, ohne, dass wir den Code nochmal anfassen 8) .
Zitat von: gregorv am 03 Oktober 2024, 21:09:40Ich hatte mir heute mal OpenVoiceOS angeschaut aber mein erster Eindruck war, dass das noch viel komplexer wird. Meinst Du, das wäre echt eine Alternative zu Rhasspy ?
Hatte jetzt auch mal die Tage einen (theoretischen) Blick darauf geworfen. Nach dem letzten statement von dkreutz ist er bis dato nicht dazu gekommen, seinen skill auf OpenVoiceOSS anzupassen. Was in der mycroft-Lösung an Optionen angeboten (bzw. dokumentiert) ist, bleibt - zumindest für mich und auf den ersten Blick - deutlich hinter dem zurück, was mit RHASSPY geht.
Von daher lohnt es sich m.E., RHASSPY weiter zu entwickeln, allerdings eben so, dass wir uns nach Möglichkeit nicht irgendwelche Eigentore schießen, falls wir künftig irgendwann einen anderen Unterbau dazu verwenden könnten ;) .
Zum eigentlichen:
Zitat von: gregorv am 17 Oktober 2024, 11:21:19Das wäre sogar noch besser, wenn die zusätzlichen Parameter auch bei Standard Intents machbar wären.
Danke für die spontane Rückmeldung!
ZitatDas sollte am Device konfigurierbar sein, weil man so ggf. Attribute oder Readings mit übergeben kann. Ich nutze z.B. das Attribut drive-up-time-to-open für die Dialog Dauer und das Reading position um z.B mit dem Kommando weiter die Fahrrichtung zu ermitteln und eine neue Position x% höher oder tiefer anzufahren. (Readings müssten während der Dialog Dauer aktuell sein.)
Puh, das klingt schon wieder nach "zu viel Auswertung", und ich habe im Moment Zweifel, ob sich dieser Aufwand wirklich lohnt.
Aber bevor wir darüber diskutieren, sollten wir m.E. mal definieren, wie wir vorgehen wollen. Mein Vorschlag:
- Das mit "resetInput" fertig machen und einchecken. Dazu müßte man m.E. die angehängten Dateien checken, ob das Zusammenspiel soweit paßt, dass bisherige User keine Probleme haben.
- Danach könnte man sich mit einem globalen (sentences-) Key für "führe den Dialog weiter" befassen. Ziel auch hier: Zwischenversion einchecken.
- Dann eventuell checken, ob man "specials" findet (Zwischenversion...)
- zuletzt wäre das mit myUtils dran.
Vermutlich entspricht diese Liste nicht deinen persönlichen Präferenzen, aber so herum bekommen wir es in (hoffentlich) handhabbare Häppchen aufgedröselt und können ggf. gleich entscheinden, welche generischen Code-Brocken wir brauchen (bzw. schon haben), und wie man die am besten einsetzt... (feedback welcome)
Grundsätzlich fehlt mir grade v.a. die Zeit zum Testen, von daher werde ich mal im Hauptthread darauf hinweisen, dass wir hier am diskutieren sind. Vielleicht findet sich zumindest jemand, der testet, dass es mit diesen Zwischenversionen keine Probleme mit seiner bisherigen Funktionalität gibt?!?
Zitat von: gregorv am 17 Oktober 2024, 11:21:19Für die möglichen Parameter könnte man noch Ideen anderer FHEM-Rhasspy Nutzer sammeln.
Dann geht es m.E. nämlich nicht nur um "mögliche Parameter", sondern v.a. auch um Ideen, welche Funktionalitäten ggf. noch wie (im Detail) umsetzbar wären (ohne sich irgendwie "tot konfigurieren" zu müssen...).
EDIT: Fehlerkorrektur in RHASSPY.pm
Zitatneue 10_RHASSPY.pm
werde gleich mit intensiveren Tests starten, schon mal vorab, damit das Modul lädt:
Korrektur Zeile 106
':' zu
'=>'Gibt aber wohl noch ein paar Dinge:
resetInput geht nicht, weil der CustomIntent BlindStop nicht mehr aufgerufen wird und damit
resetInput nicht da ist. Ich versuch das mal einzugrenzen.
Unabhängig von den neuen Funktionen:
Falls das Wake-Word erkannt wurde in nichts gesagt wird, gibt es ja den
timeout. Die
session wird jetzt sauber (und still) beendet. vorher war: response = "Da hat etwas zu lange gedauert" im Log zu sehen mit einer anschließenden Fehlermeldung.
ZitatDanach könnte man sich mit einem globalen (sentences-) Key für "führe den Dialog weiter" befassen
In Rhasspy ist der Key:
sendIntentNotRecognized (warum auch immer so heißt) - jedenfalls habe ich bei Tests gesehen, dass
sendIntentNotRecognized = "true" die
session offen hält und
sendIntentNotRecognized -> 0 (oder nicht vorhanden) die
session sofort schließt. In der sub
setDialogTimeout ist das ja mit
"true" 'hart verdrahtet', was da auch Sinn macht. Also offen beibt er jetzt schon und erkennt auch Kommandos - quittiert aber alle mit IntentNotRecognized.
Melde mich später noch einmal.
Danke, Gregor
Zitat von: gregorv am 17 Oktober 2024, 18:49:46Korrektur Zeile 106 ':' zu '=>'
Thx, Datei im post angepaßt.
Zitat von: gregorv am 17 Oktober 2024, 18:49:46resetInput geht nicht, weil der CustomIntent BlindStop nicht mehr aufgerufen wird und damit resetInput nicht da ist. Ich versuch das mal einzugrenzen.
Na ja, das sollte ja vom design her in jedem Intent funktionieren.
Generell würde ich gerne v.a. den (Einzel-) Intent setNumeric (und evtl. setOnOff) zum Testen und Weiterentwickeln verwenden, von daher würde es sich m.E. anbieten, das da mal testweise reinzuknödeln.
Da es ja aber mal funktioniert hat, sollten wir Schritt 1 eigentlich relativ schnell abhaken können, oder? Zumal die Änderungen marginal sind.
Die allgemeine "Warnung" vor updates ist ja raus, von daher kann sich keiner beschweren, falls wir dann doch ein Problem finden O:-) .
Für den 2. Schritt habe ich mal in den Code geschaut, und da v.a. alles rund um die sub respond(). Da ist ja schon vorgesehen, dass man ein $delay mit übergeben kann. Genutzt wird das intern aber selten, eigentlich nur von handleIntentCancelAction(), und es ist der Funktion (bis auf den Ausnahmefall, dass es sich nicht um einen generischen Rhasspy-Satelliten handelt (v.a. AMAD)) eigentlich auch egal, welcher Wert (außer 0) da übergeben wird...
Komisch kommt mir grade nur vor, dass da in handleIntentCancelAction() erst "respond" mit $delay aufgerufen wird, dort intern intentFilter gesetzt wird (die Formatierung macht mittels des letzten Parameters configure_DialogManager() ;) ), und dann nochmal configure_DialogManager() direkt aufgerufen. Letzteres macht eigentlich keinen Sinn, aber mir dünkt, dass ggf. sonst das "pure" "intentFilter"-Feld in $sendData nichts bewirkt? Aber dann wäre das mit dem CustomIntent bei dir auch schief gegangen...?!? Oder war da was mit einem session-intentFilter und einem globalen intentFilter (per Satellit?)? Jedenfalls sind die deaktivierten Intents etwas unterschiedlich, default deaktivert auch "Cancel", mit $delay ist das nicht mit drin.
Hmmm, da müssen wir wohl noch das eine oder andere austesten.
Generell scheint es mir sinnvoll, auch die zu deaktivierenden Intents mit übergeben zu können. Da muss ich aber auch noch etwas rumhirnen, eine kommaseparierte Liste wäre hier m.E. das einfachste.
Was CustomIntents angeht: Da sollte man einen Schalter haben, anhand dessen entschieden werden kann, ob setDialogTimeout() aufgerufen wird oder das ganze (default) in respond() gehen soll.
Erstere Funktion sehe ich als "Fortsetzungsfunktion" zu einem Thema, respond() ist entweder "nur" eine Ausgabefunktion, oder eben (mit $delay) eine universelle Möglichkeit, ggf. auch direkt einen neuen Dialog zu beginnen (oder den bestehenden fortzuführen). Mit
Zitat von: Beta-User am 17 Oktober 2024, 13:17:39führe den Dialog weiter"
wäre demnach eher "mach unmittelbar einen neuen Dialog auf" gemeint, ohne irgendwelche bereits gesetzten Filter usw.. Dafür gibt es - soweit ich das erkennen kann - im Moment keinen Code.
Zitat von: gregorv am 17 Oktober 2024, 18:49:46Also offen beibt er jetzt schon und erkennt auch Kommandos - quittiert aber alle mit IntentNotRecognized.
Das scheint mir die Folge des session-intentFilters zu sein. Falls da was falsch gesetzt sein sollte, kommt halt schlicht nichts mehr durch - falls mich meine Erinnerung nicht trügt.
Wollte nur mal meine Gedanken erst mal sortieren und hoffe, das ist soweit verständlich und nachvollziehbar?
Nachdem die (v.a. bzgl. Doku/commandref noch etwas abgeänderten) Dateien "unfallfrei" geladen werden können, habe ich das ganze eben eingecheckt. Mal sehen, wann sich der erste beschwert ;D O:-) ...
@Beta-User
ich habe den Fehler gefunden und außerdem noch den Code noch optimiert.
In der sub
handleIntentNotRecognized ist der Parameter intentFilter gar nicht erforderlich, damit wird das da erheblich kürzer:
#@@@
$data->{requestType} //= $data_old->{requestType} // 'voice';
my $response = getResponse( $hash, 'RetryIntent');
my $reaction = {
text => $response,
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
respond( $hash, $data, $reaction);
return $hash->{NAME};
ZitatDas scheint mir die Folge des session-intentFilters zu sei
Das Problem hängt tatsächlich mit dem IntentFilter zusammen. In der sub
handleCustomIntent stehen die von der sub
CustomIntent (bei mir
Blinds) angehängten Daten in
$error (hash, komplett) und das kann nicht als
response (4.) Parameter an die sub
setDialogTimeout übergeben werden, die erwartet da einen oder mehr strings und kein hash. Jeden falls habe ich da nichts gefunden, was den hash auseinander nimmt, auswertet oder weiterleitet. Damit ist auch der IntentFilter weg, obwohl der in
$error drin steht.
statt:
return setDialogTimeout($hash, $data, $timeout, $error);
wie folgt:
return setDialogTimeout($hash, $data, $timeout, $resonse, $intentFilter);
Der Aufruf der sub
setDialogTimeout mit obigen Daten liefert das erwartete Ergebnis.
Da die Rückgabewerte der sub
CustomIntent aber nicht in
$data stehen gehen die verloren. Es bleibt nur die Möglichkeit die Daten aus
$error in
$data zu übertragen. Oder siehst Du da eine andere Möglichkeit ?
Ich habe daher den Code in der sub
handleCustomIntent:
$timeout = $error->{sessionTimeout} if defined $error->{sessionTimeout} && looks_like_number( $error->{sessionTimeout} );
return setDialogTimeout($hash, $data, $timeout, $error);
wie folgt geändert:
# @@@
$data->{customData} = $error->{customData} // undef ;
$data->{silent} = $error->{silent} // undef ;
my $resonse = $error->{text} // getResponse($hash, 'DefaultConfirmation');
my $intentFilter = $error->{intentFilter} if( ref $error->{intentFilter} eq 'ARRAY') // [qw(CancelAction)];
$timeout = $error->{sessionTimeout} if defined $error->{sessionTimeout} && looks_like_number( $error->{sessionTimeout} );
return setDialogTimeout($hash, $data, $timeout, $resonse, $intentFilter);
Eigentlich wäre das besser in der sub
setDialogTimeout aufgehoben (reines Bauchgefühl). Ich hab es nur nicht gemacht, weil ein Fehler da sich auch auf andere Intents auswirkt.
Da die obigen Änderungen sich nur bei CustomIntents auswirken können und da auch nur bei denen, die ein hash als return liefern, dürfte das für alle Nutzer die keine solchen CustomIntents verwenden ohne jegliche Auswirkung sein.
ZitatErstere Funktion sehe ich als "Fortsetzungsfunktion" zu einem Thema, respond() ist entweder "nur" eine Ausgabefunktion, oder eben (mit $delay)
Das hatte ich in meinem vorherigen Code schon drin. Das war der Parameter "what"=>"true". Abgefragt hatte ich aber nur, ob der existiert. In sub
handleIntentNotRecognized hatte ich dafür das Codestock ganz oben in:
if ($data_old->{what} // undef ) {
...
}
eingefasst.
Ich habe es mal wieder eingebaut, wie der Parameter benannt werden soll, kann man ja noch ändern und auch, ob man den am Device konfigurieren sollte.
Die neue Version hänge ich noch nicht dran, weil da noch eine Änderung nötig ist:
In der sub RHASSPY_DialogTimeout klemmt es noch mit 'SilentClosure' obwohl da ein Leerzeichen drin ist. Ich meine das ging früher.
Die
getResponse liefert als return einen Leerstring. Der wiederum wird von der sub
respond durch die Fehlermeldung ersetzt.
Moin,
kurze Antwort, sorry, heute ist wenig bis keine Zeit dafür...
Inhaltlich schaue ich mir deine Antwort bei Gelegenheit näher an, die Änderungen in der commandref bedeuten aber: Ich habe arge Zweifel, dass es richtig ist, wenn nach handleCustomIntent() dann zwangsweise setDialogTimeout() aufgerufen werden muss, sobald komplexe Daten kommen. Ich meine nach der gestrigen Analyse, dass es besser wäre, diesen default zu ändern, und den User aufzufordern, es explizit mitzuteilen, wenn er diese Funktion haben will und nicht das "einfache" respond()...
Eben weil respond() schon besser mit HASH-Type Rückgaben umgehen kann.
Nett war irgendwie, wieder über den [0.5...]-Thread zu stolpern. Dein Thread hier knüpft ja unmittelbar an die dortigen noch offenen "Wackeligkeiten" an.
ZitatEben weil respond() schon besser mit HASH-Type Rückgaben ...
Interesant, hatte heute morgen die gleiche Idee, da habe ich mir nämlich die
respond() mal genauer angeschaut (wegen
SilentClosure Problem).
Dafür habe ich inzwischen eine Lösung.
Das Problem ist, dass die
getResponse() den Antwort Text aus
$hash->{helper}{lng} holt (weil
getKeyValFromAttr mangels
response Attribut am Device nichts liefert. Damit der Text aber aus
$hash->{helper}{lng} geholt werden kann, muss
SilentClosure in der Datei
rhasspy-xx.cfg drin sein. Also kommt wieder ein Leerstring heraus und die
respond() steigt aus.
Ich habe mal folgende Änderung eingebaut:
In der
getResponse() am Anfang die Zeile:
if ( $identifier eq 'SilentClosure' ) { return 'silent'; }
eingefügt.
Damit steigt die
respond() schon mal nicht mehr aus.
In der
respond() dann auch am Anfang die Zeile:
if ( $response eq 'silent' ) { $response = q{}; }
eingefügt.
Ab der Stelle scheint ein Leerstring als Antworttext sauber zu funktionieren (auch im Log). Auch stellt dieser Lösungsansatz sicher, dass die
'NoValidResponse' response noch funtioniert, für den Fall, dass in der Language Datei mal ein
respons Identifier fehlen sollte
2024.10.19 09:43:21 1: XXX->getResponse starting identifier = :SilentClosure
2024.10.19 09:43:21 1: XXX->respond starting response = :silent
2024.10.19 09:43:21 5: published hermes/dialogueManager/endSession {"customData":{"name": "links","prevPos": "0","room": "arbeitszimmer"},
"intentFilter": null,"sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-99f18e55-0308-4d4b-a497-dad59175ecd7","siteId": "arbeitszimmer","text": ""} <----- !
Unschön sind die Ifs, weil 'Sonderlocke', die man vermeiden sollte, und die Inkonsistenz für den Identifier, weil der in den
$languagevars nun gar nicht mehr drin sein muss.
Wenn Dir was besseres einfällt - die Problem-Analyse steht ja oben. Ein Leerzeichen als Antworttext quittiert mein Satellit übrigens mit der leisen, aber deutlich hörbaren Audio Ausgabe
'pffft'. In
hermes/dialogueManager/endSession ... sollte der Value von
text ein echter Leerstring sein (siehe Log oben).
Als Alternative wäre die Lösung 'SilentClosure' => " ", also eher nicht geeignet - abgesehen davon dass dieser Parameter da wenig Sinn macht.
Ich werde mich mit dem Thema
respond() statt
setDialogTimeout() mal etwas beschäftigen. Und mir den
[0.5...]-Thread mal anschauen.
ZitatIch werde mich mit dem Thema respond() statt setDialogTimeout()...
Das war eine harte Nuss!
response() statt
setDialogTimeout() wäre zwar möglich, aber dann muss man fast alles nachbauen, was in
setDialogTimeout() schon drin ist. (
setDialogTimeout() von
response() aufrufen geht nicht, weil
response() wieder
setDialogTimeout() aufruft -> Endlos-Schleife!)
Die gefundene Variante erweitert
setDialogTimeout(), damit es mit dem
response() Parameter (
$error, von der
CustomIntent()), was anfangen kann.
nötig ist zwischen die Zeilen:
my $siteId = $data->{siteId};
und:
$data->{'.ENABLED'} = $toEnable; #dialog
folgende Erweiterung:
my $sendIntentNotRecognized = "true";
if ( ref $response eq 'HASH' ) {
$sendIntentNotRecognized = $response->{sendIntentNotRecognized};
$toEnable = $response->{intentFilter} if $toEnable ~~ [qw(ConfirmAction CancelAction)];
$timeout = $response->{sessionTimeout} if defined $response->{sessionTimeout} && looks_like_number( $response->{sessionTimeout} ) && $timeout == _getDialogueTimeout($hash) ;
delete $response->{intent};
delete $response->{intentFilter};
for my $key (keys %{$response}) {
$data->{$key} = $response->{$key};
}
}
*1 my $sendIntentNotRecognized = "true"; -> damit es wie vorher funktioniert, falls die
CustomIntent() das nicht abschaltet.
falls $ response ein hash ist
*2 $sendIntentNotRecognized = $response->{sendIntentNotRecognized}; -> das ist "true" oder nicht vorhanden - meine
CustmIntenet() (Blinds() muss es also als "true" mitliefern
*3 $toEnable = $response->{intentFilter} -> wird später mit dem alten Code weiterbearbeitet, übergebener Parameter
$toEnable hat Vorrang.
*4 $timeout = $response->{sessionTimeout} -> wird später mit dem alten Code weiterbearbeitet, übergebener Parameter
$timeout hat Vorrang
*5 delete $response->{intent}; und delete $response->{intentFilter}; löscht diese Keys aus dem
$response array (intent würde sonst den alten richtigen Wert überschreiben)
*6 for my $key (keys %{$response}) {... kopiert den Rest von
$response in
$data damit es später noch zur Verfügung steht
zusätzlich wird die Zeile:
sendIntentNotRecognized => 'true', #'false',
zu:
sendIntentNotRecognized => $sendIntentNotRecognized,
geändert. (siehe *1)
In der
handleCustomIntent werden sämtlich Änderungen wieder entfernt. Also nur noch:
return setDialogTimeout($hash, $data, $timeout, $error);
Vorteile gegenüber der vorherigen Variante in
handleCustomIntent:
* die Bearbeitung von erweiterten Daten kann von allen Intents genutzt werden.
* zusätzliche
keys im
return hash können frei hinzugefügt werden und sind über die Laufzeit der
session verfügbar.
Nachteil: Die Änderung erfolgt in einer sub, die von anderen Intent auch benutzt wird - das Risiko einer Störung schätze ich als sehr gering ein. Notfalls kann man die Bedingung:
if ( ref $response eq 'HASH' )
um
if ( ref $response eq 'HASH' && defined $response->{SezifischerKey} )
erweitern.
Viel Spass beim Testen
10_RHASSPY.pm angehängt und alle Änderungen mit
@@@ markiert.
@Beta-User
Für eine Funktionserweiterung meines CustomIntent habe ich den Group Parameter in $data gesetzt und RHASSPY::handleIntentSetOnOffGroup() aufgefufen. Das funktioniert soweit, aber mir ist aufgefallen, dass die handleIntentSetOnOffGroup() und auch die handleIntentSetOnOff() nur 'on' und 'off' kennt. Ich habe mal folgendes probiert:
in handleIntentSetOnOffGroup() hinter der Zeile:
my $cmdOff = $mapping->{cmdOff} // 'off';
die Zeile:
#my $cmd = $value eq 'on' ? $cmdOn : $cmdOff;
auskommentiert, und folgende Zeilen hinzugefügt:
my $cmdAny = $mapping->(cmdAny) // $value;
my $cmd;
if ( $value eq 'on' ) { $cmd = $cmdOn;
}elsif ( $value eq 'off' ) { $cmd = $cmdOff;
}else { $cmd = $cmdAny; }
Damit ist es möglich einen beliebigen Wert für $Value zu nutzen. Es greifen da die original Steuer Befehle für ein Device und auch (!!!) die im eventMap Attribut konfigurierten Übersetzungen z.B.:
on:Zu off:Auf stop:Stop open:Offen closed:Geschlossen
Damit kann man auch Befehle wie 'hoch/runter' oder 'Auf/Zu' (case Sensitiv) direkt im Rhasspy Sentence verwenden, wenn das in eventMap passend zugewiesen wird.
* $cmdAny = $mapping->(cmdAny) // $value; -> als weiteren Parameter in Attribut rhasspyMapping, falls man den $value mit situtionsabhängigen Werten überschreiben will, falls nicht ist es der pure Inhalt von $value.
* if ( $value eq 'on' )... -> hier gibt es nun drei Alternativen, die mit elsif der Variablen $cmd zugewiesen werden.
Bitte schau Dir das mal an, ob das gefahrlos so eingebaut werden kann - ich weiß nicht ob es nicht an anderer Stelle stört. Es muss ja einen Grund gegeben haben, warum mit rhasspyMapping ein eigenes Attribut geschaffen wurde. Für FHEM scheint es mir nicht nötig ???
Zitat von: gregorv am 23 Oktober 2024, 14:02:37Bitte schau Dir das mal an, ob das gefahrlos so eingebaut werden kann - ich weiß nicht ob es nicht an anderer Stelle stört. Es muss ja einen Grund gegeben haben, warum mit rhasspyMapping ein eigenes Attribut geschaffen wurde. Für FHEM scheint es mir nicht nötig ???
Lustige Sichtweise:
rhasspyMapping war "immer schon" da, ich fand das nur ziemlich unnötig und habe das so umgebaut, dass es in der Regel ohne geht 8) .
Deinen Vorschlag schaue ich mir bei Gelegenheit mal intensiver an, für den ersten Eindruck wehrt sich da ziemlich viel in mir. Kurzform:
- Der Versuch mit OnOff war, das absolut (Sprach-) neutral zu gestalten (=> Prinzipverstoß)
- eventMap ist ein Attribut, das ich hasse. Ist nur Kosmetik und mAn. effektiv ziemlich verwirrend (ist emotional, ist aber halt so). Finde "sprechende" Icons für das Web-Interface besser.
- Wenn man schon ein mapping braucht, kann man doch auch gleich die onOff-Werte (auf der FHEM-Seite) passend setzen. Hat es funktionale Vorteile, die Optionen zu doppeln (oder ist es gar nicht doppelt)?
- eventMap kennt mehrere Facetten (siehe HASH-Variante!). Vielleicht geht es bei dir direkt, wenn du die "richtige" Variante wählst?
Hier noch mein onOff-Intent:
[de.fhem:SetOnOff]
rooms=([(im|in dem|auf dem|in der|auf der)] $de.fhem.Room{Room})
morerooms=(und [(im|in dem|auf dem|in der|auf der)] $de.fhem.Room{Room1})
devSetOnOff=($de.fhem.Device-SetOnOff{Device})
onOff=((an|ein){Value:on}|aus{Value:off})
an=((an|ein){Value:on})
aus=(aus{Value:off})
den=(den|die|das)
cmdmulti=(schalte|schalt|mache|mach|stelle|stell)
cmddrive=(fahre|fahr)
cmdopenclose=(öffne{Value:on}|(schließe|schließ){Value:off})
openclose=((hoch|auf){Value:on} | (zu|runter){Value:off})
grpMark=<de.fhem:SetOnOffGroup.grpMark>
(<cmdmulti>|starte) [<den>] <devSetOnOff> [und [<den>] $de.fhem.Device-SetOnOff{Device1}] [<rooms> [<morerooms>]] <onOff>
<cmddrive> [<den>] $de.fhem.Device-blind{Device} [und [<den>] $de.fhem.Device-blind{Device1}] [<rooms> [<morerooms>]] <openclose>
<cmdopenclose>[<den>] $de.fhem.Device-blind{Device} [und [<den>] $de.fhem.Device-blind{Device1}] [<rooms>[<morerooms>]]
<cmdmulti> [<den>] $de.fhem.Device-blind{Device} [und [<den>] $de.fhem.Device-blind{Device1}] [<rooms> [<morerooms>]] <openclose>
Zitat- Der Versuch mit OnOff war, das absolut (Sprach-) neutral zu gestalten (=> Prinzipverstoß)
stimmt schon, passt eigentlich eher zu einem Intent Generic oder so. Ich hatte cmdAny nur vermisst, weil mein CustomIntent ja Auf/Zu kann. Der Aufruf einer vorhandenen
handle...() von
99_myUtils aus sollte das auch können und noch ein weiterer Punkt ist aufgefallen. Wenn man z.B. die
handleIntentSetOnOffGroup() aufruft wird ja auch der Dialog mit eigener Ansage beendet wegen
respond() am Ende, weshalb ich den Aufruf noch an eine Bedingung geknüpft habe.:
respond( $hash, $data, getResponse($hash, 'DefaultConfirmation') ) if ( ! defined $data->{silent});
Es ist sehr hilfreich, wenn man aus den selbstgebauten Intent die
handleIntentSetOnOffGroup(), oder ggf. auch andere, aufrufen kann. Eventuell könnte man die Kernfunktionen von
handleIntentSetOnOffGroup() in eine
helper...() Funktion auslagern, die von
handleIntentSetOnOffGroup() aufgerufen wird. Ein Custom Intent könnte dann die
helper...() aufrufen.
So kann meine
Blinds() nun auch Gruppen von Rollläden und im Code muss man nur die Gruppe ermitteln, bzw. prüfen ob das was in
$data->{Device} steht, auch wirklich eine Gruppe ist und die
handleIntentSetOnOffGroup() mit:
if (defined $group){
$data->{Group} = $group;
$data->{silent} = 1;
RHASSPY::handleIntentSetOnOffGroup($hash, $data);
$response = "alle rolläden gestartet";
}else{
Group erwartet die Funktion und
silent, damit sie nicht mit
respond und mit ihrer eigenen Ansage den Dialog beendet.
Das
cmdAny ist da hauptsächlich Kosmetik, weil ich sonst in meinem Rhasspy Intent im
sentence einmal
{Value:Zu} und für Gruppen
{Value:on} hätte, gar nicht zu reden davon, dass mein letzter
FS20 Rollladen
on/off genau umgekehrt versteht, weshalb ich da
eventMap verwenden musste.
Im übrigen - zum Thema
eventMap - habe ich einige FHEM Devices für die ich mehrere Infos in mehren Zeilen anzeige (die zwei devices im Bild).
fhem screen keller.jpg
Für die Umwälzpumpe gibt es z.B. kein on/off Zustand-Icon und die Standart Glühlampe statt
An ist eher unpassend. Dazu kommt noch, dass ich Devices habe wie das von der Heizung, wo ich mal länger nicht hinschaue und dann mit einem schnellen Blick nicht sofort weiß, dass das Icon Brenner in dieser Farbe auch Status An bedeutet - mit dem Text
An ist das gleich klar.
Uff, das war ja jetzt ein richtiges Plädoyer für
eventMap. Das ist aber gerade, was ich an FHEM so gut finde, nämlich, dass man sehr flexibel in der Gestaltung für verschiedenste persönliche Vorlieben ist. Das
look&feel von
HA gefällt mir einfach nicht.
Zitatzu doppeln (oder ist es gar nicht doppelt)?
Nein, ist es nicht, für meine Rollläden habe gar kein
rhasspyMapping gesetzt - nur
eventMap.
ZitatVielleicht geht es bei dir direkt, wenn du die "richtige" Variante wählst
Mhhh -das wäre aber Zufall, ich nutze das eigentlich nur so:
on:Zu off:Auf stop:Stop open:Offen closed:Geschlossen
Offenbar ist mir die andere Variante noch nicht über den Weg gelaufen. :)
ZitatSetOnOffGroup.grpMark>
ist das wirklich das, was ich vermute, nämlich dass Rhasspy damit automatisch
$data->{Group} füllt - das hätte mir eine Menge Zeit erspart in meiner
Blinds(), wo ich mühselig durch die Group Einträge loope. Na ja, was am meisten Zeit brauchte war die Fehlersuche, dauernde FHEM restarts, bis ich herausgefunden hatte, dass man in
perl eine
loop nicht mit
exit beendet sondern mit
last - das ist nun wirklich
unique >:(
Zitat von: gregorv am 23 Oktober 2024, 23:56:55ist das wirklich das, was ich vermute
Nope, das ist einfach ein Fehler :o ....
Diese Variable wird in diesem Intent dann gar nicht genutzt, sondern nur im Ausgangs-Gruppen-Intent:
[de.fhem:SetOnOffGroup]
grpRooms=( (überall|im ganzen Haus){Room:global} | <de.fhem:SetOnOff.rooms> )
grpMark=( die | alle | sämtliche | (überall|im ganzen Haus){Room:global} )
Zitat von: gregorv am 23 Oktober 2024, 23:56:55hatte cmdAny nur vermisst, weil mein CustomIntent ja Auf/Zu kann. Der Aufruf einer vorhandenen handle...() von 99_myUtils aus sollte das auch können
Bin ehrlich gesagt bisher nicht auf den Gedanken gekommen, dass man die internen Funktionen auch aus CustomIntent-Code heraus aufrufen könnte, aber na ja, das macht uU. Sinn. Als Bedingung würde ich nur was wählen wollen, was "selbsterklärend" ist; "noResponse"?
Aber "cmdAny" mit eigenem Code zu begründen, ist m.E. ein Zirkelschluss :)) .
Zitat von: gregorv am 23 Oktober 2024, 23:56:55Zitatzu doppeln (oder ist es gar nicht doppelt)?
Nein, ist es nicht, für meine Rollläden habe gar kein rhasspyMapping gesetzt - nur eventMap.
Mit "doppeln" war gemeint: Gibt es bereits heute eine Möglichkeit, das gewünschte Ergebnis zu erzielen (ohne sich zu verrenken)? Da _glaube_ ich immer noch, dass es "einfacher" ist, neben eventMap dann eben auch rhasspyMapping zu setzen. Mit "einfacher" ist gemeint:
- Man kann es jedem User erklären. Wer mappt, muss es halt konsequent machen ;) . Ist zwar oft nicht nötig, aber andererseits auch nicht verboten, eigene rhasspyMappings zu setzen :) .
- Der Code bleibt klar und eindeutig und ich muss mir nicht überlegen, was sich jemand bei dem Zusatzcode gedacht hat, wenn mich jemand anderes in ein paar Monaten oder Jahren fragt, wie das im Detail funktionieren soll. Dazu müßte man es dokumentieren, und ich _vermute_ (nach meinen bisherigen Erfahrungen mit sowas), dass mehr Erklärungstext am Ende nur zu mehr Verwirrung führen wird.
Daher die Bitte: Es gibt eine einfache Option, das bestehende rhasspyMapping pro Device zu exportieren (get <RHASSPY> export_mapping <device>) => nehmen, ändern und gut ist?
Wenn das nicht klappt, schaust du dir an, wie das mit komplexeren eventMaps geht ("The explicit variant...").
Was den "umgekehrten" FS20-Aktor angeht: Weiß nicht mehr, ob es irgendwo eine Option gab, die Funktion in Software umzubiegen (also auch die level zu invertieren), aber wieso drehst du nicht die Leitungen (bzw. den Aktor in der Dose) einfach um?
Für den Code brauche ich noch etwas...
@Beta-User,
danke für die Hinweise.
ZitatgrpMark=( die ...
Ich habe das inzwischen sogar gelöst! Immerhin gab mir das den Anstoß nochmal genauer bei Sentences nachzuforschen.
Nur:
... | alle | sämtliche ) $de.fhem.Group-SetOnOff{Group}
im
sentence einbauen und viola landet einer der Werte von
de.fhem.Group-SetOnOff in Groups.
Dabei ist mir noch etwas anderes aufgefallen. In der
99_RHASSPY_Intents_Demo.pm wird ja mit:
if ( !eval { $data = decode_json($rawd) ; 1 } ) {
das JSON Format wieder in ein hash umgewandelt. Wenn man dann aber $data an eine sub wie
handleIntentSetOnOffGroup() übergibt, knallt es bei den Umlauten - zumindest bei mir, obwohl die sonst keine Probleme machen. Um das zu lösen, muss man die o.Zeile in:
if (defined $rawd && !eval { $data = decode_json(encode_utf8($rawd)) ; 1 } ) {
umbauen.
Sollte man eventuell in der
99_RHASSPY_Intents_Demo.pm ändern.
Zitat von: gregorv am 24 Oktober 2024, 13:19:49das zu lösen, muss man die o.Zeile in:
Code Auswählen Erweitern
if (defined $rawd && !eval { $data = decode_json(encode_utf8($rawd)) ; 1 } ) {
umbauen.
Kannst du bitte mal mit
JSON->new->decode($rawd)
testen? Diese ganzen encoding-Anweisungen haben sich in der Vergangenheit immer als üble Sackgassen erwiesen.
EDIT: Siehe auch https://forum.fhem.de/index.php?topic=126088.0 (Unicode first aid).
Das mit "defined" sollte man ggf. direkt im Eingangsbereich lösen und dann eben auch direkt passende Antworten liefern? Im Zusammenhang:
sub DataTest {
my $name = shift;
my $rawd = shift // return RHASSPY::getResponse($hash, 'NoValidData');
my $hash = $defs{$name} // return RHASSPY::getResponse($hash, 'NoDeviceFound');
my $data;
if ( !eval { $data = JSON->new->decode($rawd) ; 1 } ) {
ZitatDiese ganzen encoding-Anweisungen haben sich in der Vergangenheit immer als üble Sackgassen erwiesen
Kann ich nur bestätigen Perl hat allerdings da wohl besondere Feinheiten! und
JA,
JSON->new->decode($rawd)
es funktioniert ??? - überlebt sogar
shutdown restart. Bei
Perl und
encoding weiß man das nie so genau.
Ich hätte gewettet, dass
JSON->new->decode() das gleiche ist, wie
decode_json() - irgendwo hatte ich gesehen:
Zitat$perl_scalar = decode_json $json_text
or
$perl_scalar = JSON->new->utf8->decode($json_text)
ZitatDas mit "defined" sollte man ggf. direkt im Eingangsbereich lösen
Stimmt, sonst macht er mit nix weiter und es hagelt unpassende Fehlermeldungen.
Frage:
warum wurde da überhaupt JSON genommen?, ein hash kann doch auch Argument sein.
Zitatmy $rawd = shift // return RHASSPY::getResponse($hash, 'NoValidData');
funktioniert (zumindest bei meiner 99_myUtils ) nur, wenn man
$hash durch
$defs{'RHASSPY.IF'} ersetzt (RHASSPY.IF = Bezeichnung meines RHASSPY Devices).
$hash ist zu dem Zeitpunkt noch nicht da.
Bei den Demos ist das vermutlich auch so.
Zitat von: gregorv am 24 Oktober 2024, 17:21:07warum wurde da überhaupt JSON genommen?, ein hash kann doch auch Argument sein.
Na ja, wie du an vielen Stellen gemerkt hast, ist es "schillernd", wenn man es mit HASHes (oder nur Referenzen auf solche...?!?) zu tun hat. Z.B. hätte deine Rückreferenzierung über customData nach meinem Verständnis nicht funktionieren dürfen, weil über MQTT eben nur plain text übermittelt wird. Dass Perl anscheinend (!?!) da wieder eine Referenz draus ableitet, ist hochgradig irritierend und zumindest gefühlt komplett fehleranfällig...
Ergo war die Entscheidung, da JSON in die Richtung des Users weiterzugeben nur folgerichtig, denn da kann man "nur" die Konvertierung falsch machen mit der Handhabung der Daten. Mit Referenzen (oder eben nicht...) crasht man schnell das System (oder verändert die Ausgangsdaten, was uU. auch unsupportbare Probleme macht.
Genau das macht aber nun leider decode_json() => ergo kommt es bei nächster Gelegenheit aus der Demo raus, ist einfach ein Rest aus der Zeit vor der Umstellung in RHASSPY selbst (siehe auch meine letzten Kommentare im verlinkten Thread).
Dass sich die Katze in den Schwanz beißt, wenn man $hash braucht, hatte ich schlicht übersehen, in die Demo kommt daher folgerichtigerweise direkt Text; den kann dann jeder nach Belieben anpassen...
Zitatmeinem Verständnis nicht funktionieren dürfen, weil über MQTT eben nur plain text übermittelt
Ja, das ist auch richtig, hashes dürften für
MQTT ein Problem sein - in meinen ESP Projekten verwende ich auch nur
JSON als
payload.
Aber jetzt dämmert es mir langsam, weshalb Du schon öfter
MQTT in diesem Thread erwähnt hast und ich mich immer nur gewundert hatte, weil da
MQTT an keiner Stelle eine Rolle spielt.
Der
Rhasspy Server spricht ja niemals direkt mit der
Blinds().
Dafür gibt es ja die
handleCustomIntent() und die bekommt ihre Daten letztlich aus der
MQTT Nachricht, die im
RHASSPY Modul ankommen und das veranlasst letztlich auch ein
publish an den
Rhasspy Server. In der
handleCustomIntent() (und da ist schon alles im hash Format) wird die
Blinds() aufgerufen und die kann daher natürlich auch hashes empfangen und zurück liefern, sie ist ja nur eine Unterfunktion von
handleCustomIntent(). Alles danach führt über deren
return und diverse Funktionsaufrufe innerhalb des
RHASSPY Moduls wieder zu einer Nachricht (
publish) mit
JSON als paload an den
Rhasspy Server.
Zumindest habe ich den Ablauf im Code so verstanden - oder übersehe ich da was? Ich halte das für eine ziemlich geniale Lösung um eigene Wünsche relisieren zu können.
Zitat von: gregorv am 24 Oktober 2024, 21:14:44Zumindest habe ich den Ablauf im Code so verstanden - oder übersehe ich da was?
Was den Ablauf beüglich Intent-Handling angeht, stimmt das soweit: Der Austausch zwischen RHASSPY/FHEM und Rhasspy (Dienst) erfolgt da via MQTT, deswegen ist das (primäre) Interface auch MQTT2_CLIENT (MQTT2_SERVER ist ausdrücklich nicht empfohlen, da Rhasspy intern auch MQTT verwendet - u.a. auch für den Austausch von Audiodaten...)
RHASSPY kommuniziert allerdings auch (wegen anderer Dinge) per HTTP-Post mit Rhasspy, z.B. um die slots zu generieren.
Zitat von: gregorv am 24 Oktober 2024, 21:14:44Ich halte das für eine ziemlich geniale Lösung um eigene Wünsche relisieren zu können.
Ob "genial" die passende Kategorie ist? Es ist jedenfalls eine sehr flexible Lösung, mit der sich FHEM und Sprachsteuerungsbefehle sehr eng verzahnen und an die eigenen Wünsche anpassen lassen.
Das mit den "Intents" verleitet allerdings - grade am Anfang - dazu, für jeden (vermeintlichen) Zweck einen eigenen Intent anzulegen. Das ist - jedenfalls nach meinem derzeitigen Meinungsstand - nicht optimal. Natürlich gibt es "Großkategorien" wie "OnOff", aber wenn man sich da die Entwicklung anschaut, war es irgendwann so, dass mich die Unterscheidung zwischen "Einzeldevice" und "Gruppe" eher behindert hat als das es (gefühlt) hilfreich war. Zwischenzeitlich bevorzuge ich es, anhand des (Nicht-) Vorhandenseins von bestimmten Keys zu unterscheiden, was eigentlich gewollt ist. Macht natürlich andererseits den Code komplexer...
Na ja, jedenfalls muss ich manchmal schmunzeln wenn ich sehe, welche Klimmzüge man machen muss, um anderen Sprachsteuerungen bestimmte Aktionen zu "entlocken" und denke bei mir, dass das mit RHASSPY bestimmt sehr viel einfacher ginge 8) .
ZitatDas mit den "Intents" verleitet allerdings - grade am Anfang...
Mhhh - ich sehe es eher als persönliche Vorliebe. Dazu zählen sicher auch meine vielen anderen subs in der
99_myUtils wo alle möglichen events gesammelt werden, um passende Aktionen zu veranlassen. Ich weiß, dass das auch mit
DOIFs geht, aber eine
DOIF Variante, die - um bei den Rolläden zu beiben - diese herunterfährt, wenn es dunkel wird, aber die für die Terrassentür nicht, wenn die offen ist, bei schwerem Sturm aber alle, selbst, wenn es hell ist... und das ganze auch noch abhängig von einstellbaren Leveln für Helligkeit, Zeit oder Windgeschwindigkeit ... Beim Hochfahren ist es noch viel komplexer und spätestens da habe ich die
DOIF erst mal ignoriert und über all nur noitfy benutzt um eigene subs aufzurufen.
Sicher, man kann es auch übertreiben - aber es macht Spass.
Ich suche gerade noch die Ursache für ein Problem - wir hatten ja das Thema Codierung schon angesprochen damit scheint es wieder zu klemmen.
Das
JSON->new->decode($rawd) hatte ich ja eingesetzt und das Problem ist auch unabhängig davon.
Getestet hatte ich mit der Gruppe "Rollläden", aber wenn ich diese Gruppe auf einem Raum beschränke funktioniert das nur, wenn der Raum keine Umlaute hat - Küche und Büro geht nicht.
Das Problem liegt in der
getDevicesByGroup in der Zeile:
next if $room ne 'global' && $allrooms !~ m{\b$room(?:[\b:\s]|\Z)}i; ##no critic qw
Ich habe in die
loop mal ein Log eingebaut und an dieser Stelle sollte die Bedingung zuschlagen:
------------------>allrooms=arbeitszimmer,büro room=büro
Tut es aber nicht und läuf durch alle Devices -> Keine Devices gefunden und Ansage Fehlermeldung.
Falls Du dazu gerade eine zündende Idee hast ... - ansonsten knie ich mich da mal rein.
Hinweis:
Im
$hash unter
rhasspyRooms ist das Büro: "
b\303\274ro" (Octet Schreibweise) in
$data dagegen "
b\x{c3}\x{bc}ro" (HEX Schreibweise) Beide Werte sind gleich.
Zitat von: gregorv am 25 Oktober 2024, 09:56:18Falls Du dazu gerade eine zündende Idee hast ... - ansonsten knie ich mich da mal rein.
Sieht komisch aus, aber im Moment habe ich dazu auch keine Idee und auch keine Zeit zu testen.
Zitat von: gregorv am 25 Oktober 2024, 09:56:18Mhhh - ich sehe es eher als persönliche Vorliebe. Dazu zählen sicher auch meine vielen anderen subs in der 99_myUtils [...] dass das auch mit DOIFs geht, aber
[...]
Sicher, man kann es auch übertreiben - aber es macht Spass.
Na ja, m.E. ist das was substantiell anderes, weil durch die Intents eben gerade eine Vor-Kategorisierung stattfindet, aus der man schwer wieder raus kommt. Gutes Beispiel ist das mit "sage die Zeit an" und "sage das Datum an". Warum sind das zwei Intents? Nur, weil jemand irgendwann mal gedacht hat, man bräuchte dafür getrennte Intents. Heute würde ich das anders lösen: Es ist die Frage nach "irgendeiner" Zeitangabe, und je nachdem, wie man fragt, erwartet man eine andere Antwort. Es ist aber programmtechnisch einfacher zu lösen, wenn man alle diese Varianten in eine Routine reinwirft und dann schaut, was der passende Antwortsatz ist, oder?
DOIF verwende ich bei mir gar nicht, ich verstehe das Modul nicht (und habe weiter den Eindruck, dass sehr viele (auch damit erfahrene) Nutzer ) Probleme haben, das Modul "richtig" zu verwenden... Ist aber nicht unser Thema!
Rollladenautomatisierung ist sowieso ein Spezialthema, das hier AutoShuttersControl erledigt. Da war ich bei der Entwicklung recht intensiv dabei, so dass das meine Bedarfe sehr gut abdeckt.
Bingo, schon gelöstIch habe die Zeile:
next if $room ne 'global' && $allrooms !~ m{\b$room(?:[\b:\s]|\Z)}i;
durch
next if $room ne 'global' && $allrooms !~ m{(?<!\p{L})$room(?:(?!\p{L})|\Z)}i;
Jetzt geht es. Sollte bei standard-
SetOnOffGroup() nicht stören obwohl ich vermute, dass es da nie ein Problem mit Umlaut Codierung gab.
Der Grund ist wahrscheinlich, dass bei Einem CustomIntent die Daten in $data erst mal in von hash in JSON und anschließen wieder in hash convertiert werden.
ZitatDOIF verwende ich bei mir gar nicht, ich verstehe das Modul nicht
Volltreffer, geht mir genau so. :)
Ich könnte mich ja mit fremden Federn schmücken aber im Anhang mal, woher ich das habe.
Perplexity.jpg
Insgesamt bestimmt5 weitere Vorschläge - den zweiten fand ich passend.
Zitatweil durch die Intents eben gerade eine Vor-Kategorisierung stattfindet
Das denke ich auch - ich will meine Blinds() ja auch nicht in das RHASSPY Modul einbauen. Und behalten will die nur so lange, bis die handleSetNumric() das auch kann (oder wie auch immer ein General-Intent heißen wird.
@Beta-User
Frage: wo ist eigentlich die andere sub Parse()?
Im RHASSPY Modul ist zwar eine drin und da ist auch eine Fehlermeldung, die ich sehe, aber wenn ich da eine Log Ausgabe einfüge, sehe ich zwar die Fehlermeldung, aber nicht meine Logausgabe. Das passiert in folgendem Fall: Ich spreche das Wake Word und erzwinge dann ein IntentNotRecognized. -> Keine Audio Ausgabe und die Fehlermeldung:
2024.10.25 17:37:58 5: RHASSPY: [RHASSPY.IF] Parse: internal error: onmessage returned an unexpected value: RHASSPY.IF
Das Problem war schon in der ursprünglichen Version, die FHEM etwa im Sommer aktualisiert hatte und ich wollte mal schauen wodurch das zustande kommt.
mehr Details:
2024.10.25 17:37:58 5: RHASSPY: [RHASSPY.IF] Parse (IO: RHASSPY.MQTT2): Msg: hermes/nlu/intentNotRecognized => {"input": "stop terrassentür auf dem kochstudio zu", "siteId": "arbeitszimmer", "id": null, "customData": "Roberta_de_linux_v3_0_0", "sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-e12a5432-aad5-4b07-9160-7176497e2ce6"}
2024.10.25 17:37:58 5: Parsed value: 0.75 for key: confidence
2024.10.25 17:37:58 5: Parsed value: arbeitszimmer for key: siteId
2024.10.25 17:37:58 5: Parsed value: stop terrassentür auf dem kochstudio zu for key: input
2024.10.25 17:37:58 5: Parsed value: arbeitszimmer-Roberta_de_linux_v3_0_0-e12a5432-aad5-4b07-9160-7176497e2ce6 for key: sessionId
2024.10.25 17:37:58 5: Parsed value: Roberta_de_linux_v3_0_0 for key: customData
2024.10.25 17:37:58 5: [RHASSPY.IF] handleIntentNotRecognized called, input is stop terrassentür auf dem kochstudio zu
2024.10.25 17:37:58 5: published hermes/dialogueManager/endSession {"customData": "Roberta_de_linux_v3_0_0","intentFilter": null,"sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-e12a5432-aad5-4b07-9160-7176497e2ce6","siteId": "arbeitszimmer","text": "Das habe ich leider nicht verstanden"}
2024.10.25 17:37:58 5: RHASSPY: [RHASSPY.IF] Parse: internal error: onmessage returned an unexpected value: RHASSPY.IF
2024.10.25 17:37:58 5: RHASSPY: [RHASSPY.IF] Parse (IO: RHASSPY.MQTT2): Msg: hermes/dialogueManager/sessionEnded => {"termination": {"reason": "intentNotRecognized"}, "sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-
Die Ansage wird erkannt und auch mit 'gepublished' und auch die MQTT payload sieht hat genau die gleichen Elemente wie für Fälle in denen es funktioniert, z.B. "licht im büro an". Aber trotzdem kommt die Fehlermeldung internal error: onmessage returned an unexpected value: RHASSPY.IF.
Das funktionierende Gegenstück:
2024.10.22 12:43:44 5: published hermes/dialogueManager/endSession {"customData": "Roberta_de_linux_v3_0_0","intentFilter": null,"sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-fce5d7e4-3bc2-4176-852d-6a7d65f56848","siteId": "arbeitszimmer","text": "oke"}
2024.10.22 12:43:44 4: [RHASSPY.IF] dispatch result is RHASSPY.IF AR.Licht
2024.10.22 12:43:44 5: RHASSPY: [RHASSPY.IF] Parse (IO: RHASSPY.MQTT2): Msg: hermes/dialogueManager/endSession => {"customData": "Roberta_de_linux_v3_0_0","intentFilter": null,"sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-fce5d7e4-3bc2-4176-852d-6a7d65f56848","siteId": "arbeitszimmer","text": "oke"}
Wenn Du mir sagen kannst, wo die Parse() ist, kann ich da mal weiter tracen
Danke, Gregor
Zitat von: gregorv am 25 Oktober 2024, 18:29:10Frage: wo ist eigentlich die andere sub Parse()?
Welche "andere"? Es gibt nur die eine {ParseFn} = \&Parse.
Das "Problem" ist, dass die ein Array erwartet und eben (veraltet) meckert, wenn analyzeMQTTmessage was anderes zurückgibt wie erwartet, also kein Array. Beende deine handleIntentNotRecognized() mal mit:
return ($hash->{NAME});
Vermutlich taucht das Problem noch an anderen Stellen auf, in der angehängten Version (sie lädt, viel mehr kann ich im Moment nicht testen) ist das schon geändert und das eine oder andere von deinem inupt (verändert...) eingeflossen.
Deine abgeänderte regex (Thx!) ist noch nicht eingepflegt, sie scheint aber zu funktionieren, von daher hätte ich keine Bedenken, das insgesamt umzustellen.
Wegen der Anpassung deines myUtils-Codes anbei auch ein diff, damit sollte es sich leichter feststellen lassen, was zu ändern wäre (es sind einige Codes geändert).
Falls gewünscht mache ich noch ein paar Kommentare dazu, aber das wird mir frühestens am Sonntag reichen.
ZitatWelche "andere"? Es gibt nur die eine {ParseFn} = \&Parse.
Ops, war mein Fehler, namlich eine fehlerhafte Log3 Anweisung. Das erschien nicht im Log weshalb ich annahm, dass die Stelle nicht durchlaufen wird.
Vermutlich taucht das Problem noch an anderen Stellen auf
ja, war mir auch schon aufgefallen - auch bei einer Standard-Ansage. Deshalb habe ich nach einer besseren Stelle als in
handleIntentNotRecognized() gesucht. Da wird ja der
return von
respond() als
return übergeben und deshalb habe ich in der
respond() am Ende die Zeile:
my $secondAudio = ReadingsVal($hash->{NAME}, "siteId2doubleSpeak_$data->{siteId}",undef) // return [$hash->{NAME}];
geändert (return in []) -> Die Fehlermeldung ist jetzt weg, aber leider nicht das Problem, dass die Ansage nicht kommt. Muss also weitersuchen.
Nun sieht das Log so aus:
2024.10.26 11:15:27 5: [RHASSPY.IF] handleIntentNotRecognized called, input is
2024.10.26 11:15:27 5: published hermes/dialogueManager/endSession {"customData": "Roberta_de_linux_v3_0_0","intentFilter": null,"sessionId": "arbeitszimmer-Roberta_de_linux_v3_0_0-6e2b78ac-5f07-43f9-97ba-f67f727a3aaf","siteId": "arbeitszimmer","text": "Das habe ich leider nicht verstanden"}
2024.10.26 11:15:27 5: Parsed value: arbeitszimmer-Roberta_de_linux_v3_0_0-6e2b78ac-5f07-43f9-97ba-f67f727a3aaf for key: sessionId
Negative Auswirkungen auf
respond() Aufrufe habe ich bisher nicht festgestellt.
Zitat...viel mehr kann ich im Moment nicht testen...
Ich werde die Version mal aktivieren - bei meinen Spielereinen werden mir eventuelle Problem auffallen. Was die Kommentare angeht, habe ich in meiner Version schon überall Links auf die Posts hier eingebaut, sonst verliere ich selbst den Überblick. Wenn du das als Kommentare haben möchtest, braucht Du das nicht machen.
ZitatProblem, dass die Ansage nicht kommt. Muss also weitersuchen.
Gelöst! Ist ein Rhasspy Problem.
Wenn man in der
Rhasspy Konfiguration bei
Error WAV keinen Eintrag hat, wird nicht nur keine WAV abgespielt, sondern eine Ansage komplett unterdrückt.
Lösung: Im Verzeichnis
responses eine leere WAV Datei z.B.
Nix.wav erstellen und in Rhasspy
Error WAV ${RHASSPY_PROFILE_DIR}/responses/Nix.wav
einfügen.
Wenn man will kann man da auch eine WAV mit
Ping angeben, dann hört man
Ping UND die FHEM Ansage von Id '
NoIntentRecognized'
Ich bin bestimmt nicht der erste, der die Ansage
NoIntentRecognized vermisst hat.
@Beta-User
Die neue Version ist prima, bis auf den Punkt cmdAny (https://forum.fhem.de/index.php?msg=1323259), wo Du ja noch überlegst, wie man das besser gestalten könnte - ist aber kein Problem.
Die letzten Änderungen:
$allrooms !~ m{(?<!\p{L})$room(?:(?!\p{L})|\Z)}i
und
return [$hash->{NAME}];
habe ich noch eingebaut, letzteres allerdings, wie oben beschrieben in der respond(). Falls Du da Bedenken hast, kannst Du es ja noch in die handleIntentNotRecognized verschieben - es funktioniert ja an beiden Stellen, an letzterer halt nur für IntentNotRecognized.
Gelöscht habe ich noch den Kommentar: # Vorschlag GV: my $reaction... das war in der Tat ein Überbleibsel, weil ich erst das undef als 3. Argument bei Aufruf der respond() vermeiden wollte. Später war mir das zu riskant, weil es ja sein könnte, dass jemand als response Argument ein hash übergeben könnte UND ein timeout Argument. Offenbar gibt es solche Fälle aber nicht, weil Du den Code so geändert, dass timeout und response definitv vom hash kommen, falls respone ein hash ist und diese Werte da geliefert werden.
Anmerkung:
ich hatte noch einen Schalter vorgesehen, mit dem man die WieBitte Funktion abschalten kann, sodass zwar der Dialog offen blebt aber die nächste Erkennung den Dialog beendet:
if ( defined $data_old->{what} ) {...
s.a. what (https://forum.fhem.de/index.php?msg=1322924) - der muss nicht sein, aber Du hattest so etwas erwähnt, wenn ich richtig gelesen hatte.
Ich habe zwei Versionen angehängt, eine mit nur den drei Änderungen und eine, mit zusätzlichen Kommentaren, Links zu den entsprechenden Beiträgen in diesem Thread (alles mit @@@ markiert).
Ich finde, die Zusammenarbeit funktioniert hervorragend und macht Spass.
Mal sehen, was ich sonst noch so (er)finde.
Was hälst Du davon, wenn ich im ersten Post mal eine Zusammenstellung(Beschreibung der neuen Möglichkeiten einbaue - Mit Hinweisen auf die gefundenen Rhasspy Problemchen?
Gruß
Gregor
Danke, Gregor
Zitat von: gregorv am 26 Oktober 2024, 17:01:40Ich finde, die Zusammenarbeit funktioniert hervorragend und macht Spass.
Kann mich dem nur anschließen, Danke! Wobei du die Hauptarbeit machst ;D ...
Zitat von: gregorv am 26 Oktober 2024, 17:01:40wo Du ja noch überlegst, wie man das besser gestalten könnte
Ich meine mich entsinnen zu können, dass ich um Tests gebeten habe, ob nicht ein rhasspyMapping dasselbe bewirken würde ;) .
Zitat von: gregorv am 26 Oktober 2024, 17:01:40der muss nicht sein,
Beim drüber nachdenken bin ich dann auch irgendwann zu dem Schluss gekommen. Falls wir da doch einen "Hosenträger" brauchen, können wir ihn immer noch einbauen, wobei das ggf. auch der "experimental"-Schalter sein könnte - je nachdem, wie tatsächlich die Problemlage bei einer Mehrzahl von Usern ggf. ist => ich würde das erst mal ohne Sicherung einchecken, die Warnung ist ja raus O:-) .
Zitat von: gregorv am 26 Oktober 2024, 17:01:40Was hälst Du davon, wenn ich im ersten Post mal eine Zusammenstellung(Beschreibung der neuen Möglichkeiten einbaue - Mit Hinweisen auf die gefundenen Rhasspy Problemchen?
Doku ist prinzipiell immer eine gute Idee - ich habe daher (eigentlich schon immer) versucht, die commandref jeweils mit zu aktualisieren. Ggf. könnte man das auch direkt im Wiki irgendwie verwursteln? Darfst gerne einen Zugang beantragen und in "meinen" Artikeln ergänzen (oder korrigieren, wenn dir was auffällt).
Ansonsten wäre ggf. auch der "0.5"-Thread kein schlechter Ort für eine Zusammenfassung? As you like!
Code schaue ich mir frühestens morgen an, wollte v.a. nochmal auf den ausstehenden rhasspyMapping-Test hinweisen ;D .
Zitatauf den ausstehenden rhasspyMapping-Test hinweisen
Oh, hatte ich vergessen zu erwähnen. Der Wunsch war ja, dass im
sentence solche Dinge, wie An,Aus,Auf,Zu in value schreiben wollte.
Das kommt ja in der
handleIntentSetOnOffGroup() an. Im rhasspyMapping muss man das sozusagen noch mal bestätigen cmdOn=Zu und cmdOff=Auf, aber bei:
$cmd = $value eq 'on' ? $cmdOn : $cmdOff
gehts dann aber schief, da
$value eben nicht
on ist. Laut Doku kann man für
SetOnOff auch nur
on oder
off verwenden und an der Stelle sieht man den Grund - das einzige was da durchkommt ist der Wert von
cmdOff, das
Auf.
Auf das
cmdAny und die Zeile
my $cmdAny = $mapping->{cmdAny} // $value;
kann man natürlich verzichten - ein Mapping dafür wäre ohnehin recht sinnfrei.
Die Änderung jetzigen Codezeile oben (mit
eq 'on')zu:
if ( $value eq 'on' ) { $cmd = $cmdOn;
}elsif ( $value eq 'off' ) { $cmd = $cmdOff;
}else { $cmd = $value; }
Also auf
on oder
off prüfen und sonst kein Mapping.
Da käme dann natürlich alles durch, also auch stop oder pos x, aber ich meine, das schadet ja nicht.
Ich hab zur Sicherheit noch mal in die
getMapping() und in das hash geschaut und dabei erstaunt festgestellt, dass da die default Mappings so stehen, wie ich es mit meinem
eventMap konfiguriert habe - ist ja wirklich intelligent. Ich hätte gewettet, dass da nur die Standard Befehle für ein Device drin stehen.
Falls Du es noch einbauen willst, bitte auch bei
handleIntentSetOnOff(), sonst wäre es inkonsistent (nach meinem Bauchgefühl).
Zitatder "0.5"-Thread kein schlechter Ort
Das hört sich gut an, mach ich da mal zuerst.
Gruß
Gregor
Zitat von: gregorv am 26 Oktober 2024, 23:12:17Ich hab zur Sicherheit noch mal in die getMapping() und in das hash geschaut und dabei erstaunt festgestellt, dass da die default Mappings so stehen, wie ich es mit meinem eventMap konfiguriert habe - ist ja wirklich intelligent. Ich hätte gewettet, dass da nur die Standard Befehle für ein Device drin stehen.
8)
Demnach müßte dein Rollladen ja komplett funktionieren (ganz ohne weitere Anpassung...), wenn du es machst wie in der Doku beschrieben und eben "on" oder "off" in deinen sentences übergibst, oder?
Mein Punkt ist der:
Zitat von: gregorv am 26 Oktober 2024, 23:12:17Da käme dann natürlich alles durch, also auch stop oder pos x, aber ich meine, das schadet ja nicht.
Genau da bin ich - nach meinen bisherigen support-Erfahrungen - anderer Meinung. Es gibt nämlich auf der Strecke zwischen Rhasspy und FHEM-Devices so viele Stellen, wo man irgendwas anders machen könnte (und früher teils auch konnte!), und genau das machen dann die User: Sie konfigurieren "irgendwas", sagen es aber nicht explizit (meist, weil sie nicht wissen, dass das wichtig wäre), und man (=meinereiner) "sucht sich den Wolf" im Code. Also weiter: Wenn es so funktioniert wie in der Anleitung beschrieben, ändern wir nichts im Code und verstecken keine features, die "jemandem" irgendwann auf die Füße fallen können ;) . Wer was (für einzelne Devices) "umbiegen" muss, soll das in rhasspyMapping machen. Da kann man alles mögliche verwursteln, wenn ich es richtig im Kopf habe ::) .
Zitat von: gregorv am 26 Oktober 2024, 23:12:17Oh, hatte ich vergessen zu erwähnen. Der Wunsch war ja, dass im sentence solche Dinge, wie An,Aus,Auf,Zu in value schreiben wollte.
Das mit dem Wunsch hatte ich schon verstanden, den Code mit $mapping->{cmdAny} nicht. Meine "Vorgabe" damals (bei der Umsetzung der "Sprach-Agnostik") an alle User war: Stellt eure onOff-intents so um, dass genau nur on und off als Befehle kommen. Vorher war das (gefühlt) ein nicht handhabbarer Wildwuchs ;) .
Das einzige Argument, das man ins Feld führen könnte, wären ggf. "sprachliche Ähnlichkeiten", wenn also Rhasspy nicht zuverlässig erkennen würde, was gewünscht ist, weil die Sätze zu ähnlich sind. Dann würde ich aber eher dazu tendieren, andere Keys zu verwenden, um einen absichtlichen Intent-Wechsel in RHASSPY vorzunehmen. Sowas kann man dann im log leichter nachvollziehen ;) . Allerdings: Bisher hat kein User Bedarf in diese Richtung angemeldet 8) .
PS: Hab's nicht nochmal im Code nachvollzogen, aber "eigentlich" sollte auch setNumeric einheitlich für FS20 und den Rest funktionieren, wenn klar ist, wo "oben" und "unten" ist. Da einfach auch die Ausgaben von getMapping vergleichen und ggf. anpassen.
Bin nochmal über den Code drüber und habe noch das eine oder andere gefunden, was mir "anders logischer" vorgekommen ist, v.a. was die return-Werte/Array-Geschichte angeht.
Bin geneigt, das einzuchecken, komme allerdings heute voraussichtlich nicht mehr zum Testen.
diff ist wieder beigefügt zwecks besserem Überblick über die Änderungen.
@ Beta-User
ZitatDemnach müßte dein Rollladen ja komplett funktionieren (ganz ohne weitere Anpassung...), w
Das ist korrekt.
Zitat...Stellen, wo man irgendwas anders machen könnte (und früher teils auch konnte!),
Ha, jetzt hab ich Dich erwischt :) Das Übergeben beliebiger Values funktioniert ja noch heute - eben nur bei
cmdOff:
$cmd = $value eq 'on' ? $cmdOn : $cmdOff
Aber im Ernst, ich sehe den Punkt. Ich hatte Überlegt ob beim Check auf
on nicht eventuell auf ein Element in einer Liste prüft, die man aus dem language file unter z.B
SetOnOff->ValidForOn {on|An|Zu,...} holen könnte...
Aber egal ich mach das Mapping von
An zu
on in meiner
Blinds(), damit
handleIntentSetOnOffGroup zufrieden ist (falls Du wg. meiner Anmerkung oben auch auf
off-Prüfung umstellst, kann ich
cmdOff im Custom Intent natürlich auch mappen).
aber "eigentlich" sollte auch setNumeric
Da hatte ich auch schon dran gedacht, aber bin ich noch nicht ganz durchgestiegen - das Andere schien mir erst mal einfacher.
Die neue Version werde ich gleich mal testen, ich hatte bisher nur mal in die
diff geschaut und sehe den key $data->{
SilentClosure} aber auch noch $data->{
noResponse}
Zitat von: gregorv am 27 Oktober 2024, 09:50:01Das Übergeben beliebiger Values funktioniert ja noch heute - eben nur bei cmdOff:
? Wenn es ein passendes (implizites oder explizites) rhasspyMapping gibt, funktionieren alle (im Prinzip denkbaren) Befehle, jeweils für cmdOn und cmdOff. Ohne eben nicht bzw. ohne gültigen "value" es ist eben dann immer das, was auf "off" gemappt ist.
Zitat von: gregorv am 27 Oktober 2024, 09:50:01Aber im Ernst, ich sehe den Punkt. Ich hatte Überlegt ob beim Check auf on nicht eventuell auf ein Element in einer Liste prüft, die man aus dem language file unter z.B SetOnOff->ValidForOn {on|An|Zu,...} holen könnte...
Im Ernst: Ich will wirklich bei dem simplen Prinzip bleiben, also: "Wer mappt, soll halt mappen, aber dann an beiden Enden." (also eventMap und rhasspyMapping). Der OnOff-Intent ist der "Einsteiger-Intent", und da ist es m.E. zwingend, dass man nicht zu viele Möglichkeiten schafft (die dann schiefgehen können), sondern den Pfad ganz geradlinig durchläuft. Ich kann jedenfalls keinen Mehrwert erkennen, wenn man dem User nicht klar macht, dass er eben "standardisierte" Werte (hier: on/off) zu übergeben hat. Ist ja bei "rot" und "grün" auch nicht anders: Da braucht es Farbkreiswerte (oder rgb). Ist immer dasselbe, also KISS.
Zitat von: gregorv am 27 Oktober 2024, 09:50:01Da hatte ich auch schon dran gedacht, aber bin ich noch nicht ganz durchgestiegen - das Andere schien mir erst mal einfacher.
Müßte auch suchen, wie das im Detail ist. Vermutlich steht dazu was im Wiki... Jedenfalls rechnet RHASSPY bei allen diesen Anweisungen (auch bei den HUE-Color-Dingen) immer ausgehend von dem, was das Device an Spanne kann und ermittelt daraus den Zielwert. Scheint zu funktionieren, jedenfalls wäre mir keine diesbezügliche Beschwerde bekannt (oder es haben alle 100%=offen-Type Rollläden?!?).
Zitat von: gregorv am 27 Oktober 2024, 09:50:01Die neue Version werde ich gleich mal testen, ich hatte bisher nur mal in die diff geschaut und sehe den key $data->{SilentClosure} aber auch noch $data->{noResponse}
Ich meine, diese Unterscheidung absichtlich gelassen zu haben; irgendwas war da auch in der Kommunikation zu Rhasspy unterschiedlich... (gelegentlich sollte man die Doku in der Demo anpassen, wenn beide Optionen so bleiben).
Zitat? Wenn es ein passendes (implizites oder explizites) rhasspyMapping gibt, funktionieren
Das war nicht ganz, was ich meinte - es funktioniert mit der gegewärtigen Prüfung in der
handleIntentSetOnOff()/Group() ein
sentence wie dieser:
\[bitte] [(mach|mache|schalte|schalt)] [<den>] $de.fhem.Device{Device} [<rooms>] ( (an|ein|einschalten){Value:on} | (aus|ausschalten){Value:AUS} )
also
AUS statt
off - eben alles, was nicht
on ist, ist gültig für
off. In
handleIntentSetOnOff...() wird es dann zu
off, oder was anderem, wenn es ein Mapping gibt.
Zitat von: gregorv am 27 Oktober 2024, 10:39:31es funktioniert mit der gegewärtigen Prüfung in der handleIntentSetOnOff()/Group() ein sentence wie dieser:
\[bitte] [(mach|mache|schalte|schalt)] [<den>] $de.fhem.Device{Device} [<rooms>] ( (an|ein|einschalten){Value:on} | (aus|ausschalten){Value:AUS} )
Na ja, das sieht mir schon fast wie ein "absichtlicher Fehler" aus :o ... Im Moment sehe ich keine Veranlassung, da auch noch eine explizite Gültigkeitsprüfung einzubauen (mit der Konseqenz, den User auf den Fehler hinzuweisen ;D ). Dein "AUS" wird im Moment halt einfach ausgefiltert bzw. ignoriert, gemacht wird halt "das Gegenteil von Einschalten". Kommt mir weiter logisch vor...
Ist ja bei "Bestätigen" auch so: alles, was nicht "OK" ist, bedeutet "abbrechen".
ZitatIch meine, diese Unterscheidung absichtlich gelassen zu haben
Ich habe mir das angesehen und festgestellt, dass es gestern schon drin war, aber mir nicht aufgefallen ist. Ist aber OK.
Dabei bin ich über das neue Codestück in der
response gestolpert:
#new reopen or sessionTimeout variant: Close the old session and reopen a new one:
if ( $topic ne 'continueSession' && $type eq 'voice' && ( defined $data->{reopenVoiceInput} || defined $hash->{sessionTimeout} ) ) {
activateVoiceInput($hash,[$data->{siteId}]);
$delay = ReadingsNum($name, "sessionTimeout_$data->{siteId}", $hash->{sessionTimeout} // _getDialogueTimeout($hash));
$delay = $data->{SilentClosure};// if defined $data->{SilentClosure} && looks_like_number($data->{SilentClosure});
resetRegIntTimer( 'testmode_end', time + $delay, \&RHASSPY_testmode_timeout, $hash ); #Beta-User: needs different timeout function
}
Sieht mir ähnlich aus, wie mit dem
resetInput, allerdings hier durch den
response() Aufruf und dass da ein spezifischer
sessionTimeout eingestellt werde kann.
Was für ein Anwendungsfall steckt hinter
reopenVoiceInput?
Die ersten Tests sind alle erfolgreich. Geht aber noch weiter.
Ich denke mal bis 18:00 habe ich die Version soweit getestet, dass sie bereitgestellt werden kann.
Zitat von: gregorv am 27 Oktober 2024, 13:10:38Sieht mir ähnlich aus, wie mit dem resetInput, allerdings hier durch den response() Aufruf und dass da ein spezifischer sessionTimeout eingestellt werde kann.
resetInput wirkt global.
Bei meiner letzten Runde der Befassung mit RHASSPY war ich bei "kontinuierliche Eingabe-Optionen" stecken geblieben und habe das dann auch nicht weiter vertieft, u.a. auch, weil es niemanden sonst interessiert hatte. Jetzt bis du gekommen und hattest ein ähnliches Thema - nur halt beschränkt auf einen bestimmten Anwendungfall. Bei der Durchsicht des Codes ist mir dann aufgegangen, dass ich einen grundsätzlichen Denkfehler drin hatte: Wenn man einen echten Dialog hat, muss man sich Dinge merken. Nicht aber, wenn man einfach das Thema wechseln (können) will... Dann muss man einfach "alles auf Anfang" stellen und das Mikro wieder einschalten. Genau das soll (irgendwann mal) dann das Ergebnis dieser Codestelle hier sein. Daher der Pseudo-Aufruf von RHASSPY_testmode_timeout() am timer-Ende. Da muss was hin, das einfach unauffällig das Mikro wieder schließt, wenn man nichts gesagt hat.
Danke für's testen, bei mir im log waren noch ein paar "uninitialized"-Warnings für split (=> Vorbelegung mit "nichts"=q{}), dahingehend überarbeitete Fassung anbei.
Zitatkontinuierliche Eingabe-Optionen...
Das hört sich gut an, da grübel ich auch noch. Eine Möglichkeit könnte sein dass man schon bei Start einen Dialog öffnet und das
Wake Word über
STT erkannt, nur noch alle
Intents scharf schaltet - dann wird man das
Porcupine o.äh. los. Passiert längere Zeit nichts, werden alle
Intents gesperrt, außer dem Wake Word.
Den Dialog neu Starten nach Intent Bearbeitung ist schon mal gut. Man könnte einen Dialog auch neu Starten nach Ablauf des Timeout - und dann nur den Wake Word Intent aktivieren.
Ein Problem sehe ich allerdings einmal in
Rhasspy und einmal bei
Satteliten.
Bei
Rhasspy kommt irgendwann der dort eingestellte
Timeout (hab den Max.Wert immer noch nicht herausgefunden) dummerweise kommt der nicht als
Timeout sondern als
IntentNotRecognized. Soweit ich gesehen habe, kann man den nicht von den
'normalen' IntentNotRecognized unterscheiden.
Bei Satelliten müste Audio die ganze Zeit auf Sendung sein. Das kann man zwar steuern, aber macht bei vielen Satteliten das LAN/WiFi dicht. Deshalb will ich meinen Satelliten so umbauen, dass er bei Stille nichts sendet. Alle komerziellen tun das aber - zumindest soweit ich weiß, außer wenn die Wake Word Erkennung im Satelliten selbst ist.
Was man auch untersuchen müsste, wäre, wie das den Rhasspy Server belastet, wenn 4-10 Dialoge offen sind - falls der das überhaupt kann.
ist schon noch was zu forschen...
Die neue Version habe ich aktiviert und teste weiter.
Danke Gregor
Das war etwas anders gemeint:
Wakeword (oder "Knopf") => Rhasspy lauscht, was man haben möchte
Intent wird erkannt => FHEM führt das aus
NEU: Mikro wird ggf. wieder eingeschaltet (über den key im define oder über eine Einzelanweisung im CustomIntent; für letzteres ist der neue key gedacht!)
Dann überwacht FHEM einen timout und macht das Mikro wieder zu, wenn man Rhasspy nichts mehr zu sagen hat...
Dazwischen könnte man sowas machen wie die Ansage: "Sonst noch was?"
Es wird also nichts grundsätzliches am Ablauf verändert, außer, dass man eben hintereinander mehrere Einzelanweisungen absetzen kann, ohne Rhasspy jedes Mal explizit aufzurufen. That's all...
Ansonsten kann Rhasspy vermutlich viele Dialoge offen halten, das "Problem" dürfte eher sein, ggf. parallel viele Audiodaten auszuwerten mit dem STT-Modul. Text in Intent umzuwandeln dürfte dagegen kaum Rechenleistung erfordern und geht imo auch deutlich schneller wie man sprechen kann.
ZitatNEU: Mikro wird ggf. wieder eingeschaltet
Das ist auch schon super.
Danke für die Info, Tests laufen gut.
Erweitere gerade meine
Blinds() und da ich eher der
Try& Error Typ bin fallen da noch irre viele Tests an.
Und die Erfahrungen wachsen auch, heute z.B. dass die erforderlichen 'muss' Wörter im
sentence nicht zu knapp gehalten werden sollten. Eben habe ich mir ein regelrechtes Wortgefecht mit meiner
Roberta geliefert, als ich knapp sage:
Licht an und sie keck antwortet
das Licht ist an - beim 3. Versuch musste ich dann doch etwas deutlicher werden:
mach das Licht an!. Da hat sie dann mit einem gelangweilten
OK nachgegeben.
Gruß
Gregor
Zitat von: gregorv am 27 Oktober 2024, 20:51:45Danke für die Info, Tests laufen gut.
Thx für die Rückmeldung, habe die Version zwischenzeitlich eingecheckt.
Zitat von: gregorv am 27 Oktober 2024, 20:51:45Erweitere gerade meine Blinds() und da ich eher der Try& Error Typ bin fallen da noch irre viele Tests an.
Apropos Tests: Testsuite für Sprachsteuerung (https://forum.fhem.de/index.php?topic=126864.0) und [RHASSPY] - Einstellungen optimieren mit Hilfe der Testsuite (https://forum.fhem.de/index.php?topic=126913.0) kennst du?
Zitat von: gregorv am 27 Oktober 2024, 20:51:45Und die Erfahrungen wachsen auch, heute z.B. dass die erforderlichen 'muss' Wörter im sentence nicht zu knapp gehalten werden sollten. Eben habe ich mir ein regelrechtes Wortgefecht mit meiner Roberta geliefert, als ich knapp sage: Licht an und sie keck antwortet das Licht ist an - beim 3. Versuch musste ich dann doch etwas deutlicher werden: mach das Licht an!. Da hat sie dann mit einem gelangweilten OK nachgegeben.
Hatte eigentlich gedacht, dass es einen thread gäbe mit "best practices" zu RHASSPY, in dem ein gewisser Erfahrungsaustausch zu solchen (allgemeinen) Themen gäbe, aber anscheinend habe ich den entweder nicht angefangen oder finde ihn nicht mehr...
Mal ein aktualisierter Zwischenstand:
Zitat von: Beta-User am 17 Oktober 2024, 13:17:39Aber bevor wir darüber diskutieren, sollten wir m.E. mal definieren, wie wir vorgehen wollen. Mein Vorschlag:
- Das mit "resetInput" fertig machen und einchecken. Dazu müßte man m.E. die angehängten Dateien checken, ob das Zusammenspiel soweit paßt, dass bisherige User keine Probleme haben.
- Danach könnte man sich mit einem globalen (sentences-) Key für "führe den Dialog weiter" befassen. Ziel auch hier: Zwischenversion einchecken.
- Dann eventuell checken, ob man "specials" findet (Zwischenversion...)
- zuletzt wäre das mit myUtils dran.
Den globalen key zum zweiten Punkt gibt es jetzt, vermutlich macht RHASSPY auch das Mikro wieder auf. Was fehlt ist das "saubere Ende" nach timeout (und ggf. der Check, wie lange der timeout sein soll).
Für myUtils fehlt m.E. (fast nur?) noch etwas Doku zu den neuen bzw. geänderten keys in $response. Ansonsten fehlt in handleCustomIntent noch eine Verzweigung für den neuen "reopenVoiceInput"-Key. Da frage ich mich grade, ob man eine sessionId vergeben sollte, damit man ggf. auch den (session-) IntentFilter gleich setzen kann. Aber das ist im Moment noch "terra incognita", und für Tests fehlt mir die Zeit. (Hoffe, das überhaupt verständlich formuliert zu haben).
Version zwischenzeitlich eingecheckt
Ist OK, Stand jetzt ist nichts aufgefallen
ZitatTestsuite für Sprachsteuerung und [RHASSPY] - Einstellungen optimieren
Stimmt, irgendwo mal gelesen - danke für die Links. Ich sagte ja, dass ich mit Rhasspy noch Anfänger bin - die Informationen und Threads, die es da gibt, habe ich noch lang nicht durch.
Mit FHEM bin ich aber schon lange unterwegs - so lange, das zwischendrin schon mein Forum-Zugang verschwunden war. Die alten Beiträge sind noch da - im Grund bin ich also Doppel Junior.
Zitatvermutlich macht RHASSPY auch das Mikro wieder auf. Was fehlt ist das "saubere Ende"
Teste ich nachher auch mal - nach dem Code sollte neue
session und
timeout funktionieren
Zitatfehlt in handleCustomIntent noch eine Verzweigung für den neuen "reopenVoiceInput"-Key
Ist wohl nicht nötig, es reicht, den Key
noResponse zu entfernen, was ich in meiner
Blind() z.B. beim Kommando
stop machen
könnte werde (dann kann ich das gleich da testen). Das
stop ist das einzige Kommando, was keine Verlängerung des Dialoges braucht. Da habe ich ohnehin schon die Audio-Ausgabe gelöscht, weil man ja kaum Stop sagen wird, wenn man das Resultat nicht sieht. Die entferne ich bei
mehr, weinger... und so auch noch.
Ein Info noch...Derzeit habe ich die Möglichkeit den
value ohne Mapping durchzuschleifen
(temporär und nur für mich) in die
handleIntentSetOnOffGroup doch wieder eingebaut, damit ich testen kann. Wenn ich mit Aufräumen fertig bin, schau ich mal wie es mit der
handleSetNumeric()/(Group()) gehen könnte.
Uff... gerade ein Fehler - die Keys:
"reopenVoiceInput"=>"true",
"noResponse"=>"true",
"SilentClosure"=>$newTimeout, <---- Wert 10 (auch 10.0 getestet)
sind gesetzt und der Dialog wird gestartet.
Dann den
sessionTimeout ablaufen lassen, oder
Stop sagen).
Nach etwa 20 Sekunden (das könnte mein ursprünglicher sessionTimeout (21) sein) wird eine neue
session gestartet und exakt 10 Sekunden später ($newTime oben ??) kommt folgende Meldung:
Can't use an undefined value as an ARRAY reference at /opt/fhem/FHEM/10_RHASSPY.pm line 3117.
2024.10.28 14:43:18 1: PERL WARNING: Prototype mismatch: sub main::ctime: none vs (;$) at /usr/local/share/perl/5.32.1/Exporter.pm line 66.
gefolgt von einem FHEM Restart.
Die Zeile
3117 ist in der
testmode_end():
$result .= join q{ => }, @{$hash->{helper}->{test}->{result}->{0}};
Log dazu angehängt
Nachtrag:
das Problem tritt auch auf, wenn ich bei einem sentence im SetOnOff Intent noch {reopenVoiceInput:true} anhänge.
Wieder was gelernt: mit []{reopenVoiceInput:true} []{SilentClosure:20}im sentence kann man auch mehrere Parameter aus dem Sentence in $data übernehmen
Zitat von: gregorv am 28 Oktober 2024, 15:35:10Die Zeile 3117 ist in der testmode_end():
Ups, dann wird es wohl dringend, dass eine neue timeout-Routine dazukommt O:-) ...
(es sollte eigentlich für's erste reichen, über den timer eine dummy-Funktion aufzurufen, die dann schlicht ein "return" ausführt. Falls du verstehst, wie ich das meine)...
Sorry!
Falls du verstehst, wie ich das meine)..
noch nicht so ganz... help please
meinst Du, in RHASSPY_DialogTimeout()
Nein, eine neue Funktion.
testmode_end() wird (indirekt?) aufgerufen, Quelle ist an der Stelle, wo der Key geprüft wird. Da steht auch ein Kommentar von mir dazu.
DONE, funktioniert:
- roberta ->ja?
- mach das licht an -> licht geht an -> ok
- mach das lich aus -> licht geht aus -> zu diensten
- nach 30 Sekunden -> Dialog Ende
SUPER!
Zitat von: gregorv am 28 Oktober 2024, 16:27:17SUPER
8)
Anbei eine Fassung, die (hoffentlich) etwas mehr macht wie nur return (aufräumen...) und den timeout uU. auch aus dem reopenVoiceInput-Key holt, falls nicht SilentClosure explizit gesetzt ist. Werde das vermutlich spätestens morgen früh einchecken, falls nicht noch (größere oder andere) Probleme auftauchen...
Was mir gedanklich mindestens noch fehlt, ist das "Abräumen" für den Fall, dass tatsächlich eine Ansage gemacht wird. Da im Moment aber sowieso bei timer-Ende nichts weiter passiert wie Aufräumen, ist das nicht weiter tragisch, aber sobald da eine Ansage stattfinden soll, irgendwelche Sitzungsdaten gespeichert sind etc. pp. könnte es "komisch" werden. War vermutlich einer der Gründe, warum ich gestern dann nicht weiter an dem Code gebastelt hatte.
Wie du vermutlich im Code gesehen hast, läßt sich das feature auch über das RHASSPY-define (global, also für alle Intents und alle Satelliten) aktivieren, so dass man nicht zwingend über einen CustomIntent kommen muss.
Zitat von: gregorv am 28 Oktober 2024, 15:44:47Wieder was gelernt: mit []{reopenVoiceInput:true} []{SilentClosure:20}im sentence kann man auch mehrere Parameter aus dem Sentence in $data übernehmen
Das ist ein cooles finding!
Es erleichtert es ggf. sehr, das feature sehr gezielt einzusetzen.
"Eigentlich" sollten wir damit dein eigentliches Anliegen weitestgehend gelöst haben, oder? Es gibt dann zwar keinen Rollladen-spezifischen Timer für den onOff-Intent, und du kannst auch "alles" sagen, solange das Mikro offen ist, aber setNumeric (?) mit "stop" sollte klappen. Hier meine sentences dazu:
( stoppe | stop ){Change:cmdStop} [<den>] $de.fhem.Device-blind{Device} [<rooms>]
( halte | halt ){Change:cmdStop} [<den>] $de.fhem.Device-blind{Device} [<rooms>] an
Schadet ja nicht, wenn das auch funktioniert, wenn man mal eine Taste gedrückt hat...
Zitat...irgendwelche Sitzungsdaten gespeichert...
ich sehe zwar im Code beim Beenden der Sitzung:
delete $hash->{helper}{'.delayed'}{$identity};
ABER könnnte das überhaupt passieren, dass wenn eine neue sessionId vergeben wurde der Inhalt von der alten sessionId unter der neuen auch erscheint ? Das müsste dann doch irgendwo explizit kopiert worden sein - oder ?
Ich baue ja gerade meine
Blinds() um, da benutze ich:
my $data_old = $defs{'RHASSPY.IF'}->{helper}->{".delayed"}->{$data->{sessionId}} // undef ; # get old data if exist - means that a dialog is open
und
if (defined $data_old) {
um zu prüfen, ob die
session neu ist, oder schon existiert. Wenn da irgendwie Daten von einer vorherigen Session drin sein könnten, ist die Prüfung nicht zuverlässig.
Bereits getestet hatte ich: Dialog starten und während der läuft, auf dem
Rhasspy WEB Interface einen anderen Intent starten. Funktioniert, einwandfrei, im Log taucht temporär eine neue
sessionId auf und ein anschließendes
stop geht auch noch.
Ich habe mal das Log genauer geprüft und sehe unter der neuen
sessionId tatsächlich einen Wert (aber nur einen) von meinem
customData hash. Der betreffende Wert ist da drin der
letzte. Ich mach mal einen Log Dump vom
hash bevor der neue Intent bearbeitet wird würde ich erwarten, dass kein
old_data da ist. Sind auch sonst keine alten
keys drin.
ZitatDas ist ein cooles finding!
Ist einer der Vorteile von
try&error :) Das steht vermutlich nicht einmal in einer Rhasspy
Best PracticeZitat"Eigentlich" sollten wir damit dein eigentliches Anliegen weitestgehend gelöst haben
Ja, absolut - sogar mehr als das.
Rundum erfolreicher
Sprint, sogar ohne dauerde Telko's und umfangreichen
RequestForChange.
Hole mir jetzt mal die neue, aber heute werde ich wohl nicht mehr so viel machen.
Aber sicher werde ich noch Prüfen,ob der
CancelIntent jetzt wirklich noch alles beendet.
Meine Frau kam gerade ins Zimmer, als ein Dialog offen war und sich dann empört, dass Roberta dauernd dazwischen geredet hat.
Dabei ist mir aufgefallen, dass
reopenVoiceInput den
sessionTimer immer neu setzt - da kommt dann echt wieder der Zähler ins Spiel, ggf. nur bei
IntentNotRecognized. Das erhöht den
WAF-Faktor.
Info:
mit reopenVoiceInput ist offenbar der Intent CancelAction NICHT aktiv.
Es kommt IntentNotRecognized aber immerhin ist dann der Dialog beendet (ich meine, das war mit der vorherigen Version nicht so).
Jetzt ist die sessionTimeout Einstellung für reopenVoiceInput OK (auch wenn der Wert dafür mit reopenVoiceInput übergeben wird .
habe noch einen Fehler gefunden. Der IntentFilter wurde beim publish nicht gesetzt.
Der erscheint zwar im hash, aber muss mit dem prefix "de.fhem:" auch an die respond() weitergegeben werden.
In der setDialogTimeout() bei:
my @ca_strings;
$toEnable = split m{,}xms, $toEnable if ref $toEnable ne 'ARRAY';
if (ref $toEnable eq 'ARRAY') {
for (@{$toEnable}) {
my $id = qq{$hash->{LANGUAGE}.$hash->{fhemId}:$_};
push @ca_strings, $id;
}
$response->{intentFilter} = [@ca_strings];
}
das ist jetzt noch die Zeile 1741 $response->{intentFilter} = [@ca_strings]; dazugekommen.
Keine Ahnung, wann das untergegangen ist, vermutlich bei meiner Verschiebung der Code Teile von handleCustomIntent() zu setDialogTimeout().
Für reopenVoiceInput wird CancelAction dadurch leider nicht aktiv.
Sorry, Gregor
Sorry für die Stille; im Moment komme ich nicht dazu, mir das näher anzusehen, es wird mir allerdings immer deutlicher, dass wir ein gewisses session-Management auch für den Teil brauchen.
Was das mit dem intentFilter angeht, bin ich mehrfach "vorirritiert"; irgendwie müßte man das insgesamt anders lösen (und insbesondere die Möglichkeit lassen, den Filter nicht zu ändern), aber im Moment fürchte ich Nebenwirkungen, weil das intern auf eine bestimmte Weise genutzt wird.... Und das Bilden des/der Array-Strings könnte man auch in eine eigene (separate) Funktion auslagern (ist aber gefahrgeneigt). Unrund...
@Beta-User
Ich denke, dass noch eine Funktion ganz hilfreich wäre. Sowohl bei
reopenVoiceInput wie auch bei
wieBitte wäre es sinnvoll, die die
session mit einem Sprachkommando stoppen zu können.
CancelAction wäre geeignet, wenn man das
- beim Start einer sesssion aktivieren könnte (habe ich noch nicht herausgefunden)
- die Standard Audio Ausgabe 'habe abgebrochen' dynamisch ändern könnte
Situation: eine
session wartet gerade auf Eingaben, aber das Telefon klingelt.
Damit Rhasspy nicht dauernd dazwischen reded, sollte ein
sei still möglich sein.
ich habe derzeit einen CustomIntent
CancelSession() wie folgt, eingesetzt.
[de.fhem:CancelSession]
\[bitte][ich] ((möchte | will) nichts [mehr] | nicht mehr zuhören | fertig | sei still | ruhe jetzt) {text:okeey}
# terminate listen mode, terminate session
sub CancelSession($) {
my ($rawd) = @_;
my $data;
if (defined $rawd && !eval { $data = JSON->new->decode($rawd) ; 1 } ) { # convert JSON data
Log 1, "JSON decoding error, $rawd seems not to be valid JSON data: $@"; # error handling
return "Error! $rawd seems not to be valid JSON data!";
}
return $data->{text};
}
ZitatSorry für die Stille
Kein Problem ich hab noch ne Menge zu testen bei meinem Umbau.
ZitatWas das mit dem intentFilter angeht, bin ich mehrfach "vorirritiert";
ja, ich auch - gerade kam ein Fehler,
genau an dieser Stelle.
Can't use string ("Du hast widersprüchliche Anga"...) as a HASH ref while "strict refs" in use at /opt/fhem/FHEM/10_RHASSPY.pm line 1741.
Problem:
$response kann auch ein String sein, daher Umbau:
$response->{intentFilter} = [@ca_strings] if ref $response eq 'HASH' ;
Außerdem habe ich die Zeile als 1742 eingefügt, unmittelbar vor
my $reaction = ref ...Jetzt geht es - irritation Ende (meine).
Das Problem mit dem fehlenden
IntentFilter scheint mir aber sauber gelöst, der darunter stehende Teil, wo
$reaction hash zusammengebaut wird, wird ja nur durchlaufen, wenn
$response kein hash ist, daher fehlte der
IntentFilter im hash Fall.
Die Zeile 1719:
delete $response->{intentFilter};
kann damit raus, weil
intentFilter weiter unten (mit Präfix) überschrieben wird.
Zitat von: gregorv am 30 Oktober 2024, 14:48:19Das Problem mit dem fehlenden IntentFilter
Weiß nicht recht, ob wir über dasselbe reden.
Mein Gedanke, ausgehend von
my $toEnable = shift // [qw(ConfirmAction CancelAction)];
Ist es richtig, dass es immer und zwangsweise einen (geänderten) intentFilter gibt?
Und (im HASH-Fall): Ist es richtig, dass diese Defaults ggf. das überschreiben, was der (CustomIntent-) Code explizit geliefert hat?
Anders gesagt: die Logik muss vom Ergebnis her gedacht so bleiben, dass im "es steht nirgendwo was"-Fall genau das rauskommt, was jetzt rauskommt (die defaults), aber es muss anders geprüft werden, ob der Code an anderer Stelle bereits mit anderen Vorgaben aufgerufen wurde. Wurde was in $toEnable übergeben, hat das Vorrang, stand was im HASH, dann das...
Und es muss (?) eine Möglichkeit geben, den bereits (in einer vorherigen Runde) gesetzten Filter nicht zu ändern.
ZitatIst es richtig, dass es immer und zwangsweise einen (geänderten) intentFilter gibt?
Nein, der kann auch bleiben. Prinzipiell ist es aber möglich bei jedem Aufruf eines CustomIntent den IntenFilter zu ändern.
ZitatUnd (im HASH-Fall): Ist es richtig, dass diese Defaults ggf. das überschreiben, was der (CustomIntent-) Code explizit geliefert hat?
Kommt drauf an, was Du meinst:
Nein, wenn der
CustomIntent im
hash (oder
ToEnable) was liefert wird, kann das nicht durch die Defaults überschrieben werden.
ABER
Ja, alle IntentFilter müssen vor dem
publish verändert werden weil
Rhasspy die
Intents nur mit einem
Präfix (de.fhem:) erkennt und
FHEM die nur ohne Präfix bearbeitet. Das ist sicher so gemacht, weil das
de.fhem: ja bei jedem anders sein könnte. Also
de.fhem:ConfirmAction statt
ConfirmAction.
Deshalb wird auch die Zeile
$response->{intentFilter} = [@ca_strings] if ref $response eq 'HASH' ;
benötigt, da stehen in
@ca_strings die Intents mit
Präfix.
Zitatwas jetzt rauskommt (die defaults)
Die default kommen nur, wenn im Argument
ToEnable nichts drin steht
UND im
return hash der
CustomIntent() (z.b. Blinds()) keinen
Key intentFilter (mit gültigen Intents) hat.
Vermutlich kommst Du da drauf, weil das in der Fehlenmeldung stand - das war ein beliebiger Intent der mehrdeutig war. Rhasspy hatte verstanden '
mach das licht im Keller an' da gibt es aber derzeit nur die Heizung -> also Rückfrage und dabei
setDialogTimeout() mit
response als
string, was natürlich schief geht (mächtig sogar - restart FHEM), wenn ich
$response->{xxx} irgendwas zuweise.
Im Falle von Blinds() wird im hash der Key
intentFilter mit
["Blinds", "CancelSession"] übergeben. Die
setDialogTimeout() überschreibt damit die default Werte
ODER auch Werte die ggf. mit Argument
ToEnable übergeben werden. Das hatte ich mal so gebaut, dass Argument
ToEnable vorrangig wäre, hast Du aber entfernt. Ich nehme daher an, das kommt ohnehin nicht vor, dass
ToEnable UND ein hash mit
key intentFilter als Argument
response übergeben werden. Genau so übrigens auch mit
sessionTimeout, das steckt auch im hash - könnte natürlich auch im
timeout Argument übergeben werden. Wollte man
ToEnable und
timeout explizit mit den Argumenten übergeben, müsste man die Werte in der
handleCustomIntent() erst aus
$error holen. Das hast Du sogar mit dem
timeout so gemacht, wird aber nicht benötigt - das macht die
setDialogTimeout(). Ich hatte als
timeout Argument deshalb
undef übergeben, aber es schadet ja nicht.
Und es muss (?) eine Möglichkeit geben, den bereits (in einer vorherigen Runde)
Nein, das muss nicht, so, wie es ist, geht es aber.
In Falle der Blinds() ist es so, dass die so beleiben können. Da ich mittlerweile die BlindStop() fast vollständig in die Blinds() mit eingebaut habe, wird die nur noch alleine, aber oft mehrfach aufgerufen. Beispiel die Sonne blendet -> stell den Rolladen auf halb -> Sonne blendet immer noch -> weiter -> weiter -> stop...
Für alle Kommandos wird die Blinds() aufgerufen und immer mit dem gleichen hash beendet. Also werden die IntentFilter auch immer neu gesetzt. Die Blinds geht ja direkt zur
b]setDialogTimeout()[/b].
Muss ich drüber nachdenken, im Moment hängt da für mich was schief und wir reden in Teilen aneinander vorbei.
Zitat von: Beta-User am 30 Oktober 2024, 13:47:19das Bilden des/der Array-Strings könnte man auch in eine eigene (separate) Funktion auslagern (ist aber gefahrgeneigt).
Bin jetzt mal über den Code und die Doku bei Rhasspy. Dabei ist mir der Unterschied zwischen dem session-intentFilter (JSON-Array mit flachem Text) und dem globalen "configure" (JSON-Array mit Objekten, die an- oder ausgeschaltet sind) wieder deutlich geworden.
Für das globale "configure" gibt es Code, für die "einfache" Liste hier mal ein (ungetesteter) Vorschlag, der mit allem denkbaren input klarkommen sollte:
sub _get_sessionIntentFilter {
my $hash = shift // return;
my $intents = shift;
my $enableCancel = shift;
my @allIntents = split m{,}xm, ReadingsVal( $hash->{NAME}, 'intents', '' );
my @sessionIntents;
for (@allIntents) {
next if $_ =~ m{ConfirmAction|CancelAction|Choice|ChoiceRoom|ChoiceDevice};
push @sessionIntents, $_ if
!defined $hash->{helper}->{tweaks} ||
!defined $hash->{helper}{tweaks}->{intentFilter} ||
!defined $hash->{helper}{tweaks}->{intentFilter}->{$_} ||
defined $hash->{helper}{tweaks}->{intentFilter}->{$_} && $hash->{helper}{tweaks}->{intentFilter}->{$_} eq 'true';
}
my $id = qq($hash->{LANGUAGE}.$hash->{fhemId}:);
push @sessionIntents, "${id}CancelAction" if $enableCancel;
my @addIntents;
if ( ref $intents eq 'ARRAY' ) {
@addIntents = @{$intents};
} else {
@addIntents = split m{,}xm, $intents;
}
for (@addIntents) {
if ( $_ =~ m{\a${id}} ) {
push @sessionIntents, $_;
} else {
push @sessionIntents, "${id}$_";
}
}
return \@sessionIntents;
}
Nachzusehen, ob er funktioniert und wo/wie er angesteuert werden sollte, reicht es heute nicht mehr. Eine Anmerkungen zum Verständnis des ganzen noch:
1.
Zitat von: gregorv am 30 Oktober 2024, 18:48:56Das ist sicher so gemacht, weil das de.fhem: ja bei jedem anders sein könnte.
Das kann sogar in einer Installation für mehrere Instanzen unterschiedlich sein, etwa wenn man einen deutschen und einen spanischen Rhasspy parallel betreibt. Da sollte eigentlich der myUtils-Code so auslegbar sein, dass der User, der es konfiguriert nicht groß drüber nachdenken muss, wie da die Details in seiner Installation eigentlich sind... Er gibt einfach die Intents ohne die Präfixe zurück und gut ist ;) .
2. Bin mir nicht sicher, ob es clever ist, die defaults wiederherzustellen. Für die reopenVoiceInput-Option paßt es so, ansonsten könnte man noch eine Option vorsehen, eine Liste mit zu deaktivierenden (Standard-) Intents vorzugeben?
Zitatdas Bilden des/der Array-Strings könnte man auch in eine eigene (separate) Funktion auslagern
ich hatte schon versucht, das über einen Einzeiler mit regex zu machen - bisher aber nicht erfolgreich :(
ZitatBin mir nicht sicher, ob es clever ist, die defaults wiederherzustellen. Für die reopenVoiceInput-Option paßt es so..
Wenn eine neue
session aufgemacht wird, werden derzeit (egal ob über
reopenVoiceInput oder über
resetInput alle Intents scharf gemacht, außer solchen, die eine eine offene
session erwarten (Cancel,Choice...).
Bei
resetInput sieht man das sogar explizit im Log, da werden alle
de.fhem: Intents aufgelistet und mit
enable=true oder
false gekennzeichnet.
... {"intentId": "de.fhem:CancelAction","enable": false} ..
Warum das bei
reopenVoiceInput nicht so im Log erscheint, habe ich nicht untersucht - das Verhalten bezüglich gültiger Intents ist jedenfall genau so.
Für
reopenVoiceInput und
resetInput wäre es aber praktisch, den Intent
de.fhem:CancelAction auch aktiviert zu haben.
Szenario: Hotword -> Kommando 1 -> Kommando2 ... -> fertig ('oh nein' passt wenig). Aber die Worte 'sei still' oder 'fertig' ... kann man ja noch im Intent einfügen und die
response 'habe abgebrochen' so formulieren, dass es auf beide Szenarien passt.
Nachtreg: hab die neue sub mal angeschaut da ist ja schon drin, dass man den
CancelAction aktivieren kann.
Ich habe das derzeit über einen eigenen Intent
CancelSession umgesetzt.
Zitathier mal ein (ungetesteter) Vorschlag
Habe ich mir mal genauer angesehen.
Die Möglichkeit, den Intent
CancelAction zu aktivieren, wäre hier global einstellbar. Ggf. wäre es zu überlegen, ob man
CancelAction nur in den Fällen aktiviert, in denen der
key reopenVoiceInput oder
resetInput gesetzt ist.
Ich habe in der Doku zu
RhasspyTweaks unter
intentFilter folgenden Hinweis gesehen:
(Note: activating the 4 mentionned intents is not possible!). For details on how configure works see Rhasspy documentation.
Das könnte darauf hindeuten, dass es ggf. mal Probleme mit
CancelAction gab, wenn der Intent nach der Wake Word-Erkennung ausgeführt wurde.
Hinweis: die
initialize_rhasspyTweaks müsste auch noch angepasst werden damit der neue
key enableCancel nicht verschluckt wird.
Zitat von: gregorv am 01 November 2024, 11:11:46Die Möglichkeit, den Intent CancelAction zu aktivieren, wäre hier global einstellbar. Ggf. wäre es zu überlegen, ob man CancelAction nur in den Fällen aktiviert, in denen der key reopenVoiceInput oder resetInput gesetzt ist.
"global" ist m.E. etwas zu weitgehend, man kann es eben (über diesen Code) aus jeder beliebigen Antwort heraus aktivieren (zumindest, wenn er funktioniert wie gedacht).
Zitat von: gregorv am 01 November 2024, 11:11:46ggf. mal Probleme mit CancelAction gab, wenn der Intent nach der Wake Word-Erkennung ausgeführt wurde
Weiß auch nicht mehr genau, was die Ursache war, das in der Form so "hart" zu deaktivieren. Jedenfalls dann, wenn schon irgendeine Art von "Unterhaltung" stattgefunden hatte, ist es nicht mehr so schlimm, wenn der Dialog erkennbar geschlossen wird, und über einen weiteren "key" könnte man auch das Antwortverhalten in CancelAction ja nochmal anpassen (und z.B. silent schließen oder auf "halt die Klappe" mit "gerne" reagieren...). (Diese Art der Unterscheidung nach "Sub-Keys" ist im übrigen Code übrigens erst nach der grundlegenden "Bitte bestätigen"-Funktionalität dazu gekommen, für die man CancelAction braucht; über den Teil hat also imo noch keiner intensiver nachgedacht...).
Jedenfalls Danke, dass du mit mir erst mal gedanklich "von hinten nach vorne" gehst, ich komme leider grade (und vorassichtlich auch nicht vor Mitte nächster/Anfang übernächster Woche) dazu, das code-mäßig deutlich weiter zu treiben oder auszutesten. Also falls du konkrete Vorschläge hast, schaue ich mir das gerne an.
Zitat von: gregorv am 01 November 2024, 11:11:46ob man CancelAction nur in den Fällen aktiviert, in denen der key reopenVoiceInput oder resetInput gesetzt ist.
Ich habe jedenfalls den Eindruck, dass wir gedanklich jetzt einigermaßen beieinander sind, wie das (funktional) im Großen und Ganzen aussehen sollte :) .
@Beta-User
ich habe mir heute mal die handleSetNumeric() angeschaut.
Hintergrund: ich will mit meiner Blinds() ja nicht das Rad neu erfinden, sondern das nutzen, was da ist. Momentan schrumpft sie zu einem Mantel für den Aufruf bereits vorhandener Modul Funktionen und stellt nur zusätzliche Funktionalität und hoch flexible situationsangepasste responses zur verfügung
Frage:
Wenn ich die handleSetNumeric() nutze, kann die (für Rollläden) ja Befehle wie Stop und anfahren bestimmter Positionen behandeln, was ich vermisse, ist, dass beim Anfahren von relativen Positionen 'höher und tiefer' geht, aber das i-Tüpfelchen wäre noch so ein Kommando wie setMore, womit man abhängig von der vorherigen Bewegungsrichtung ein Stück weiter fahren kann - z.B. mit Sprachkommando 'weiter'. In meiner Blinds() ist das drin und recht bequem. Für Lautstärke Einstellungen sicher auch nicht uninteressant.
Soll ich mal schauen, wie das in der handleSetNumeric() umgesetzt werden könnte.
Noch eine grundsätzliche Frage:
Da ich mit meiner Blinds() auch die Rollläden auf und zu machen kann, müsste ich - Stand jetzt, die handleSetOnOff() aufrufen, wobei ich vorher natürlich prüfen muss, ob das Kommando 'on/off' oder 'stop/pos x' ist.
Einfacher wäre es natürlich, wenn cmdOn und cmdOff von der handleSetNumeric() behandelt werden könnten. Über die rhasspyMappings bekommt man die schon jetzt in den SetNumeric Bereich im hash rein (devicemap->devices->device->Intents).
Wäre sicher kein besonders riskanter Eingriff - Das Problem sehe ich eher in dem Punkt, dass handleSetOnOff damit wohl überflüssig wird (aber natürlich bleiben kann). Als möglicher Change Wert wäre cmdOn und cmdOff denkbar
Was denkst Du ?
Schnellantwort:
ad "weiter" - setzt für Rollläden etc. halt voraus, dass man sich irgendwo die Richtung merkt. Wenn es dafür eine allgemein sinnvolle Lösung gibt: Warum nicht?
ad "cmdOn" in setNumeric:
Hatte ja schon geschrieben, dass ich zwischenzeitlich eher dazu tendiere, weniger Intents besser zu finden statt mehr... Von daher finde ich es zumindest auf den ersten Blick ok, aus "numeric" "ausbrechen" zu können bzw. das ggf. als "multi"-Kommand zu behandeln (je nachdem). Kannst ja mal schauen (auch im changelog), ob das nicht über die Multi-Geschichte ggf. sogar relativ einfach umzusetzen wäre.
Auch eine "Umleitung" nach (Gruppen-) onOff finde ich ok, wenn nur der eine Key drin ist. Man müßte halt ggf. $data "umverpacken"?
So viel (ohne Blick in den Code) auf die Schnelle dazu :) .
...dass man sich irgendwo die Richtung merkt...
Bisher habe ich die alte Position (vor der Rolladenfahrt) in CustomData untergebracht. Beim nächsten Durchlauf vergleicht er die mit
currentVal und weiß in welcher Richtung er unterwegs war. Das könnte über den bereits bekannten Key
Value in
$old_data gehen.
(Ich denke, Du weißt, was ich mit
$old_data meine - schreibt sich schneller als
hash->{helper}->{.delayed}->{$sessionId}Zitatob das nicht über die Multi-Geschichte ggf. sogar relativ einfach umzusetzen wäre
Schau ich mir auch mal an - gut, dass ich gerne lerne...
ZitatMan müßte halt ggf. $data "umverpacken"
Zumindest muss man sehen, dass nichts verloren geht, habe ich auch schon festgestellt. Da ein Kommando wie 'ein wenig runter' keine
Device,
Group und
Room Keys mehr hat, muss man die aus
$old_data wieder in
$data kopieren. Klar könnte man die erneut sprechen, fände ich aber keine natürliche Kommunikation und hab die deshalb optional im
sentence.
Gruß Gregor
Zitat von: gregorv am 02 November 2024, 00:36:57Bisher habe ich die alte Position (vor der Rolladenfahrt) in CustomData untergebracht. Beim nächsten Durchlauf vergleicht er die mit currentVal und weiß in welcher Richtung er unterwegs war. Das könnte über den bereits bekannten Key Value in $old_data gehen.
Hmmm, prinzipiell gefällt mir der Gedanke gut, das via customData zu lösen.
Problem ist: Wenn man eigentlich fertig war und dann $old_data hat, könnte es weird werden, wenn die aktuelle Anweisung - die ja ganz anders sein kann - vorrangig (?) aus diesem Datensatz abgeleitet werden würde. Außerdem muss man das irgendwann löschen. Der Umweg über customData ist da m.E. eine gute Option. Da JSON-encoded alles rein, was zur letzten Sitzung gehört hatte und "gut ist", dann kann man es nutzen, wenn es paßt, und es ist weg, wenn man sonst klar kommt. (könnte nur ein (lösbares) Problem beim parsen der Message geben, weil zu weit ausgepackt wird (?)).
Mit "weiter" habe ich dann noch weitere "Hänger":
- es ist (gefühlt: zu?) kurz
- Das mit dem Positionsvergleich ist mehrfach gefahrgeneigt: Wo ist oben und unten? (ROLLO vs. CUL_HM-Style). Wann wird die Position wirklich aktualisiert? CUL_HM macht es fortlaufend, ZWave erst dann, wenn der Rollladen wieder steht, bei MQTT2_DEVICE ist es von der Konfiguration des Users und der konkreten Hardware abhängig...
Jedenfalls für das im Modul vercodete Verhalten würde ich dazu neigen, vom Sprechenden wenigstens zu verlangen, dass er eine Richtungsangabe macht ("weiter runter"); das ist dann (hoffentlich) auch "lang genug".
Puh, und dann fürchte ich, dass sich noch ein paar weitere Folgefragen stellen werden, aber m.E. müßten wir das erst mal so weit vercoden.
Würde allerdings dann gerne erst mal die bisher aufgerissenen Baustellen zu machen, bevor wir da mehr content in die Funktionalität rein bringen. Anders gesagt: Das "reopen" mit den "normalen" sentences muss erst mal reibungslos klappen, oder?
Zitates ist (gefühlt: zu?) kurz
Im Prinzip richtig, aber deshalb habe ich den IntentFilter gesetzt. In dem anschließend verlängerten Dialog kommen nur noch Kommandos vom CustomIntent und Cancel durch (und natürlich das
resetInput - ist ja unabhängig von IntentFiltern) - das klärt auch den Punkt:
ZitatProblem ist: Wenn man eigentlich fertig war und dann $old_data...
Das erzwingt nämlich vor einem anderen Intent noch mal das Hotword - wodurch eine neue
session gestartet wird, wo es noch kein
$old_data gibt - abgesehen davon, dass in der alten
session $old_data ohnehin gelöscht wird.
ZitatWann wird die Position wirklich aktualisiert?
Für so was ist gut, dass Du darüberschaust. Bisher habe ich das so gebaut, dass mindestens
drive-up-time-to-open gesetzt sein muss, damit man solche Kommandos starten kann. Wenn die d
rive-xxx Attribute passend gesetzt werden, macht FHEM (nach meinem Verständnis) das Update der Position (fast) live. Die Dooya liefern auch nur am Ende ein closed/opened. HM kommt aber ohne die
drive-xxx Attribute aus, sollte aber trotzdem relative
position Kommandos können. FS20 kann nicht mal Stop ... - es gibt auch Rollladensteuerungen, wo oben 100 ist und nicht 0 und möglicherweise kein
inverse Attribut...
Immerhin ist es egal, wenn die aktuelle Position erst am Ende kommt, ein
weiter wird man kaum veranlassen, wenn der Rollladen noch fährt - falls doch, wird es ignoriert.
Trotzdem zu viele
? für eine sichere Implementation. Da müssten wir noch weitere Infos bekommen - eine Zusammenstellung von Features aller FHEM Rollladensteuerungen gibt es wohl nicht. Gar nicht zu reden von Lautstärke-Einstellungen o.a.
Zitat von: gregorv am 02 November 2024, 11:53:21es gibt auch Rollladensteuerungen,
Es gibt zu AutoShuttersControl einen Thread mit "spezieller Hardware", da kann man in etwa erkennen, wie bunt die Welt sein kann...
Ad "CancelActeion" und "Nicht-Aktivierbarkeit" ist mir noch eingefallen dass der wahre Grund für diese Besonderheit ein anderer ist: Daran erkennt RHASSPY, dass der (default) Intentfilter verloren gegangen ist und erneuert werden muss, z.B. nach einem restart von Rhasspy. Kann man vielleicht auch anders lösen, für den Moment werde ich mal bei Gelegenheit schauen, ob man das nicht macht, wenn spezielle Keys enthalten sind.
Ansonsten noch zu $old_data: Das ist sinnvoll, wenn man irgendeine Art von echter Interaktion hat, aber sobald man eigentlich "alles mögliche" sagen könnte, sind die Altdaten imo tendenziell eher verwirrend.
Zitat von: gregorv am 02 November 2024, 11:53:21Das erzwingt nämlich vor einem anderen Intent noch mal das Hotword
Die Idee hinter einer "continous session" ist eben, nach einem Hotword dann noch eine ganze Zeit weitere Anweisungen absetzen zu können, und zwar eben im Prinzip "Intent-offen". Das erfordert aber eine etwas andere Struktur wie sie jetzt besteht.
Komme aber wie gesagt im Moment nicht dazu, da was weiter dran zu drehen...
ZitatDie Idee hinter einer "continous session" ist eben ...
Das müssen wir prüfen. Aber ich habe mir noch nicht genau angeschaut, wie Du
reopenVoiceInput realisiert hast. Derzeit ist es da jedenfalls so, dass nach jedem Kommando eine neue Session (mit anderer
sessionId) aufgemacht wird. Damit gibt es da jedenfalls keinerlei Konfliktpotential mit
old_data.
Aber was die
sessionIds in diesem Szenario angeht bin ich im Log über unerwartete Events gestolpert, hatte nur noch nicht die Zeit, das zu analysieren. Da war im Log zu sehen, dass nach einem Kommando eine neue Session aufgemacht wird. Anschließend wurde laut Log die neue
sessionId mit
Msg: hermes/dialogueManager/sessionEnded beendet (erwartet hatte ich das für die alte).
In der Folge wurde aber mit der, laut Log beendeten.
SessionId alles weitere gemacht und die alte
sessionId taucht nicht mehr auf. Es scheint so, als wäre die richtige Session beendet und nur der zugehörige Logeintrag mit der falschen
sessionId versehen. Beim Verhalten gab es auch bei bisherigen Tests nichts Auffälliges.
Da kommt mir aber gerade ein Gedanke.
Wenn man
reopenVoiceInput mit
continous session realisieren wollte, müsste man das so machen, wie für den
CustomIntent - das ist es ja eine
continous session, möglicherweise sogar der bessere Ansatz. Da müsste man natürlich auf die
$old_data aufpassen, was aber relativ einfach gelöst werden kann. Ist das Kommando ein neuer Intent, ist
$data->{Intent} != old_data->{Intent}, was man nutzen kann um
$old_data komplett zu löschen. Das bedeutet allerdings, dass beim mehreren
CustomIntents man auch nicht mehr auf die Daten eines anderen
CustomIntent zugreifen kann (ist ohnehin konsistenter).
Dieser Ansatz bedeutet aber mehr Arbeit weil der meiste Code für eine
continous session beim
CustomIntent ja in
handleSetDialogTimeout drin ist und dann doch besser in
response() aufgehoben wäre. Da wird ja nur aufgrund der Tatsache, dass
$reatction bzw.
$response ein
hash ist das
topic continueSession gesetzt.
Ansonsten entdecke ich immer noch neues in der
handleIntentSetNumeric() und man da kann auch viel lernen, wie
rhasspyMapping funktioniert. Dazu kommt aber morgen mehr - gibt da ganz interessante Möglichkeiten.
Zitat von: gregorv am 05 November 2024, 00:34:13ZitatDie Idee hinter einer "continous session" ist eben ...
wie Du reopenVoiceInput realisiert hast.
Na ja, das ist im Moment halt eine erste Version, ggf. müssen wir das anpassen - ich glaube aber nicht, dass das ein grundsätzlich falscher Ansatz ist, vielleicht abgesehen davon, dass eben keine (neue) sessionId vergeben wird, grade um Probleme mit $old_data zu vermeiden.
ZitatDa war im Log zu sehen, dass nach einem Kommando eine neue Session aufgemacht wird. Anschließend wurde laut Log die neue sessionId mit Msg: hermes/dialogueManager/sessionEnded beendet (erwartet hatte ich das für die alte).
Ich eigentlich auch. Wenn es ums FHEM-log geht: wenn man kein msec-Log macht, werden Events gepuffert und uU. "umgekehrt herum" weggeschrieben.
ZitatIst das Kommando ein neuer Intent, ist $data->{Intent} != old_data->{Intent}, was man nutzen kann um $old_data komplett zu löschen. Das bedeutet allerdings, dass beim mehreren CustomIntents man auch nicht mehr auf die Daten eines anderen CustomIntent zugreifen kann (ist ohnehin konsistenter).
Ähm, im Moment ist grade der Ansatz, dass man in Dialogen (andere) Intents aktivieren kann und dann eben ggf. Inhalt ergänzen usw.. Genau dafür wird $old_data gespeichert. Für "irgendwas" braucht man (im Prinzip) eher nichts aus der vorherigen Anweisung. Und wenn, könnte man das via customData zur Verfügung stellen. Da wird Nacharbeit erforderlich sein.
Zitatin handleSetDialogTimeout drin ist und dann doch besser in response() aufgehoben wäre.
Weiß noch nicht genau, aber im Moment glaube ich nicht, dass da grundsätzlich was umzustellen ist. Das sind dann eher vorzuschaltende Funktionen.
ZitatAnsonsten entdecke ich immer noch neues in der handleIntentSetNumeric() und man da kann auch viel lernen, wie rhasspyMapping funktioniert. Dazu kommt aber morgen mehr - gibt da ganz interessante Möglichkeiten.
Freut mich, dass du da Freude dran hast :) .
Zitat... dass eben keine (neue) sessionId vergeben wird ...
Bin etwas verunsichert - ist das Ziel, dass für
reopenVoiceInput die
sessionId behalten wird oder nicht ?
Derzeit ist das ja nicht der Fall - weiter unten sagst Du aber, dass man bei neuen Intents auch die Möglichkeit haben möchtest, dass man auf Daten eines
(anderen) vorherigen zugreifen kann. Im Code wird
activateVoiceInput() aufgerufen, was eine neue Session (mit anderer
sessionId aufmacht - zumindest, soweit ich das bisher gesehen habe).
Wenn die
sessionId bis zu
timeout oder
CancelAction beibehalten werden soll
UND auf Daten des vorherigen Intent zugreifbar sein sollen, darf man die beim Start eines anderen Intent natürlich nicht löschen. Da würde tatsächlich die Frage entstehen, wie man sicherstellt, dass es keine ungewollten Störungen duch
$old_data gibt.
Weißt Du welche Keys da stören könnten ?
Dazu eine Frage betreffend
$old_data:
Bisher kopiere ich ja Keys, die im nächsten Intent benötigt werden in
$data und damit erscheinen sie beim nächsten Intent in
$old_data.
Was ich bisher nicht probiert habe, ist diese Daten einfach zum
publish mit durchzureichen a) weil ich nicht weiß, was Rhasspy mit Daten macht, die nicht zum Topic passen und b) weil ich nicht weiß, ob die dann auch später in
$old_data drin sind.
Wenn, wie Du sagst:
Zitat...dass man in Dialogen (andere) Intents aktivieren kann und dann eben ggf. Inhalt ergänzen...
UND das eine Möglichkeit ist, die
Rhasspy bereitstellt, müsste es ja in
Rhasspy einen 'offiziellen' Weg geben, solche Daten zu
Rhasspy selbst zu transportieren.
An einfachsten eben dadurch dass man die einfach mit einem
publich mitsendet.
Jetzt endlich die Frage: ist das möglich, eventuell sogar der 'offizielle' Weg ?
Zitat von: gregorv am 05 November 2024, 09:49:44Bin etwas verunsichert - ist das Ziel, dass für reopenVoiceInput die sessionId behalten wird oder nicht ?
Ziel ist erst mal, nach Abschluss einer Aktion direkt eine neue "ansagen" zu können (eventuell mit einem konfigurierbaren intentFilter oä.).
Allerdings ist mir zwischenzeitlich aufgegangen, dass man das nur dann "geordnet" wieder abgeräumt bekommt (mit dem intern definierten timeout, falls der kürzer ist als der in Rhasspy), wenn man aktiv eine neue SessionId vergibt. Das fehlt im Moment (für reopen).
ZitatJetzt endlich die Frage: ist das möglich, eventuell sogar der 'offizielle' Weg ?
Der "offizielle" Weg, innerhalb einer Sitzung Daten in Rhasspy durchzuschleusen ist "customData". Da mir das aber zu kompliziert erschienen ist, alles mitzuschicken und wieder auszupacken, habe ich damals das mit $old_data reingeknödelt. Da erfolgt das "Verknüpfen" der neuen mit den alten Daten über die sessionId.
Ergo würde ich weiter zwei getrennte Wege vorsehen:
- Fortsetzungsdialoge unter der alten sessionId mit $old_data
- "reopen" mit neuer (aktiv festzulegender) sessionId, Weitergabe des "Datenmülls" per customData (?), falls (!) man die braucht, FHEM-interner Timer-Verwaltung zum Schließen und der Option, direkt einen anderen als den üblichen IntentFilter zu setzen (insbes. CancelAction für "sei still").
Hoffe, das Bild ist jetzt etwas klarer?
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/detected
gibt 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
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.
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 :)
@ 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->.delayedIch 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.
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.
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});
}
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.
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.
Was lt. Doku nicht vorgesehen ist, ist die direkte Vergabe einer sessionId
Warum 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.
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...
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)
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
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
@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.
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!
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.
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.
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
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?).
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).
@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.
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.
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 ;) .
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.
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...
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.
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.
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!!!
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?
@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
Moin. In der Tat testest du viel rum, mit "Tests" war aber ausdrücklich auch pah's Testsuite gemeint, die ich vor längerem hier mal verlinkt hatte; meine Empfehlung ist wirklich, sich mal zurückzulehnen und mit diesem Hilfsmittel anzuschauen, was dein Rhasspy damit so zurückgibt :) .
Dein jüngster input ist vielschichtig, ich würde das gerne erst mal "auseinandernehmen" und erst mal einen Teil der Punkte einzeln betrachten:
Zitat von: gregorv am 17 November 2024, 00:52:35Das wäre ja nötig, wenn man timedOn aus gdt herausnimmt.
Habe jetzt mal in handleIntentSetTimedOnOff() geschaut - das macht die "on-for-timer"-Prüfung at runtime, und vielleicht hilft dir ein Blick in die Hilfsfunktion _getSecondsfromData() ;) .
my $cmd = $value eq 'on' ? $cmdOn : $cmdOff;
$cmd .= "-for-timer";
my $allset = getAllSets($device);
return respond( $hash, $data, getResponse($hash, 'NoTimedOnDeviceFound') ) if $allset !~ m{\b$cmd(?:[\b:\s]|\Z)}xms;
my (undef , undef, $secsfromnow) = _getSecondsfromData($data);
$cmd .= " $secsfromnow";
Bin übrigens immer noch dagagen, diese Funktionalität in setNumeric auszulagern :P .
Zitat von: gregorv am 17 November 2024, 00:52:35ähnlich wie gdt das mit timedOn oder brightness macht
Das Stichwort "timedOn" finde ich im aktuellen Code nur in dem oben genannten Zusammenhang, und das Parsing der rhasspyMappings macht intern splitMappingString(). Das sieht mir eher nach einfachen key-value-Paaren aus. Die komplexere Struktur (mit den "Doppelungen" mancher Stichworte) kommt aber zum Teil nach meiner Erinnerung auch davon, dass das rhasspyMapping am Anfang zu flach war, um alle Fälle abzufangen und ich daher die eher einfacheren Fälle stärker strukturiert habe als eigentlich notwendig, damit man immer dieselbe Logik zur Auswertung hernehmen kann.
Ergo dürfte es ausreichen, die Zeile mit "option" zu beginnen und dann die drei Werte "flach" reinzuwerfen (Factor=60,RetryIntent=true,reopenVoiceInput=25)
(Ich schreibe das nur, um die sachliche Frage zu beantworten, mit dem konkreten Vorschlag bin ich aber weiter nicht einverstanden...)
Zu den einzelnen Keys:
"Factor" Da ist mir im Moment unklar, für was der sein soll. Diskutiert hatten wir das im Zusammenhang mit timern, dazu hatte ich oben was geschrieben, und ansonsten fallen mir grade nur Schrittweiten bei setNumeric ein. Da gibt es aber schon einen setztbaren Parameter, soweit ich mich entsinne.
"RetryIntent" würde ich im Moment noch zurückstellen, weil mir nicht klar ist, wann dieser Parameter konkret zur Anwendung kommen soll. Es klingt ein wenig danach: Der User spricht ein Kommando zu einem bestimmten Device, das kann aber so nicht ausgeführt werden. Der User soll eine Rückmeldung erhalten, dass es so nicht geht, und kann es direkt nochmal versuchen?
Frage: Warum soll das nicht das default-Verhalten (für solche Fälle) sein/werden?
Im Zweifel könnte man für bestimmte Fälle/devices Ausnahmen vorsehen, in denen dann doch das Mikro zugemacht wird. Klingt insgesamt nach "rhasspyTweaks" (oder "specials").
"reopenVoiceInput" ist imo ein sehr klarer Kandidat für solche "specials" (eher wie als "tweak"). Falls dir nicht klar ist, was ich damit meine, versuche mal den Pfad für "ConfirmAction" nachzuvollziehen (und die Beschreibung zu "rhasspySpecials"- "confirm" und "confirmValueMap".
Als Ablauf beschrieben: Du willst nicht global reopenVoiceInput haben (das wäre "continuos session" aktivieren), sondern entweder bei allen Kommandos auf ein bestimmtes Device (=>Tweak) oder bei bestimmten Kommandos auf konkrete Devices das Mikro weiter öffnen, um eine weitere Benutzersteuerung zu ermöglichen?
Also "Schalte das Radio ein" - "Gerne" - "stelle auf SWR27" - "wird erledigt" - "mach leiser" - "ok" (und Schluss).
Soweit nachvollziehbar?
Da habe ich noch einiges Nachzuarbeiten und wieder was gelernt. Ich beschäftige mich heute mal mit der Testsuite.
Aber vorab noch ein paar Kommentare:
ZitatFrage: Warum soll das nicht das default-Verhalten (für solche Fälle) sein/werden?
Da hätte ich im Prinzip eigentlich nichts dagegen.
Allerdings habe ich da auch die User im Auge, die auf das neue, vmtl. unerwartete
'wie bitte?' so reagieren, wie Sie es gewohnt sind, nämlich das
Wake-Word sprechen um dann das Kommando zu wiederholen.
Schlecht ist dann, das die erwartete Reaktion auf das
Wake-Word ausbleibt und statt dessen so lange mit
'wie bitte?' quittiert wird, bis der
sessionTimeout abgelaufen ist. Damit inerhalb vom
sessionTimeout das
Wake-Word funktioniert, muss es einen
sentence mein-wake-word{resetInput} geben.
An dieser Stelle habe ich noch einen Fehler im Code gefunden.
Die Zeile
if ( !$data->{input} || defined $data->{customData}->{RetryIntent} ) { in der
handleIntentNotRecognized()muss durch
if ( !$data->{input} || (ref $data->{customData} eq 'HASH' && defined $data->{customData}->{RetryIntent} ) ) {
ausgetauscht werden, sonst chrasht es wenn
IntentNotRecognized direkt nach dem
Wake-Word kommt (
customData ist dann ein Text).
Als Key hatte ich
RetryIntent gewählt, weil es exakt mit der
Id für die
responses im
language file übereinstimmt. Es sollte als Key allerdings mit
lower case starten, also
retryIntent - falls es nicht immer aktiv werden soll.
ZitatHabe jetzt mal in handleIntentSetTimedOnOff() geschaut...
Ich jetzt auch - ein paar interessante Anregungen drin, aber mit diesem Intent bin ich nicht so recht zufrieden.
- timedOnOff (on/off-for-timer) ist m.E. sehr nah verwandt mit Type brightness. Insofern ist es nach meinem Empfinden inkonsistent das einerseits brightness in der handleIntentSetNumeric() behandelt wird, aber für on/off-for-timer ein besonderer Intent (handleIntentSetTimedOnOff()) gewählt wurde. Warum wurde beides unterschiedlich realisiert ?
- Zudem denke ich das die Performance ungünstiger ist. Was getAllSets() genau macht (in welcher pm ist das?), weiß ich zwar nicht, aber ich bin fast sicher, dass es aufwändiger ist, als im gdt mapping nachzusehen - ganz abgesehen davon dass dann auch noch das Resultat nach cmd durchsucht wird - das sind bei mir 34KB.
- Weiterhin ist mir aufgefallen, dass die Prüfung nach cmd nicht notwendigerweise sicherstellt (hat brightness auch), dass das Device auch on-for-timer kann. Selbst wenn man nach on-for-timer suchen würde, ist nicht sicher ob es auch off-for-timer gibt.
Zumindest denke ich, dass man sich für
gtd Mapping (wird nur beim Start ausgeführt) oder die
runtime Variante mit
getAllSets() entscheiden sollte.
Das ist aber nur mein erster Eindruck - subjektiv gefärbt. Im übrigen denke ich bei meinen Ideen für die
handleIntentSetNumeric() eher an eine mögliche
handleIntentGeneric(), wo aber die
handleIntentSetNumeric() eine gute Vorlage sein könnte.
Kurz zu timedOnOff: das getAll... wird u.a. von FHEMWEB verwendet, und es wird auch erkannt, was das Device wirklich kann (off-for...).
M.E. gibt es wirklich keinen Grund, das anders abzufackeln. Historisch gewachsenen, ja. Aber OK. Fertig dazu.
Hast du mal deinen aktuellen Code im Zusammenhang (pm als Anhang)? Ist vermutlich einfacher, wenn ich deine pm gegen svn/meinen Code von neulich automatisiert vergleichen kann...
Zitat von: gregorv am 17 November 2024, 12:55:43Da hätte ich im Prinzip eigentlich nichts dagegen.
Allerdings habe ich da auch die User im Auge, die auf das neue, vmtl. unerwartete 'wie bitte?' so reagieren, wie Sie es gewohnt sind, nämlich das Wake-Word sprechen um dann das Kommando zu wiederholen.
Na ja, Usergewohnheiten kann man ändern ;) . In der Regel kommen die recht gut damit klar, wenn es nachvollziehbar ist und eher einfacher geworden.
Den Teil würde ich daher per default aktivieren, wir müßten dann eben nur eine Option (Tweak?) vorsehen, um das bisherige Verhalten beizubehalten. Vorgewarnt sind die (FHEM konfigurierenden) User ja ;) .
Zitat von: gregorv am 17 November 2024, 12:55:43timedOnOff (on/off-for-timer) ist m.E. sehr nah verwandt mit Type brightness.
Wo siehst du die Parallele? Historisch entwickelt hat sich der Intent aus einer Mischung von "OnOff" und "SetTimer", und ich finde den "Ausgangspunkt" (also eine gesprochene Anweisung eines Nutzers) hinreichend klar abgegrenzt, dass man dafür durchaus weiter einen eigenen Intent bereithalten kann. Verbessern vielleicht (v.a. dahingehend: "Mach das Licht um 12 Uhr für 10 Minuten an"), aber prinzpiell ist das m.E. ansonsten wirklich "fertig" und bedarf auch (aus mehreren Gründen, die hier fast* alle schon genannt waren) keiner weiteren Code-Optimierung. *Die meisten Devices, die ich hier als OnOff-Devices habe, kennen auch (verschieden implementierte) on-for-timer-Anweisungen. Von daher reicht m.E. auch die "Eingangsabgrenzung" auf diese Gerätegruppe in meinen sentences.ini, um nicht allzu viele "false positives" zu generieren.
Eine Anmerkung noch zur Frage "at runtime" oder "vorab": Zum einen "brauchen" wir für die slots in Rhasspy jeweils sowas wie eine Liste, und zum anderen kann es wegen der diversen Mechanismen recht schwierig sein festzustellen, welches "mapping"-Attribut (gdt/rhasspyMapping/Tweaks/Specials) im Endeffekt greift und wie berücksichtigt wird. Von daher finde ich die Konsolidierung am Anfang schon sinnvoll, selbst wenn es effektiv (Sprachanweisungen sind ja eher selten!) bei der Umsetzung von Anweisungen in Aktionen praktisch kaum einen fühlbaren Performance-Vorteil bieten dürfte. Andererseits will ich den RHASSPY-Hash nicht wegen "seltener Fälle" unnötig weiter aufblähen.
ZitatDen Teil würde ich daher per default aktivieren
Auch OK, allerdings sollte das dann immer funktionieren. Unmittelbar nach einem Wake-Word geht es derzeit nicht.
Wenn die Bedingung
RetryIntent nicht mehr da ist, wird die sub ja immer an dieser Stelle beendet - wahlweise silent oder mit Ansage RetryIntent (wenn in Input was drin steht).
Eventuell kannst Du mal checken, unter welchen Bedingungen die sub weiterlaufen muss.
Ich musste mein Modul gerade noch etwas aufräumen:
ZitatWo siehst du die Parallele? Historisch...
Oh... ich wollte die
handleIntentSetTimedOnOff() nicht abschaffen. Die Behandlung von dem 'echten'
on/off-for timer ist da ja eigentlich nur eine Nebenfunktion - der Name war für mich etwas irreführend
Die Verwandtschaft sehe ich in dem Commandset für
brightness und
timedOn und der Bearbeitung numerischer Werte in beiden.
Zitatwird auch erkannt, was das Device wirklich kann (off-for...).
Ja, das stimmt, aber
handleIntentSetTimedOnOff() schaut nur nach
cmd und setzt dann voraus, dass die eigentlichen Kommandos funktionieren.
Kannst Du mir sagen in welchem modul die
rhasspyXxx Optionen gelesen werden, dann schau ich mal wie man erreichen kann, dass z.B.
reopenVoiceInput in
$data übertragen kann.
Zitat von: gregorv am 18 November 2024, 12:38:00Kannst Du mir sagen in welchem modul die rhasspyXxx Optionen gelesen werden, dann schau ich mal wie man erreichen kann, dass z.B. reopenVoiceInput in $data übertragen kann.
Deswegen hatte ich direkt die Schlüsselwörter genannt
Zitat von: Beta-User am 17 November 2024, 09:15:10"ConfirmAction" [...] "rhasspySpecials" - "confirm" und "confirmValueMap"
Eingelesen werden die über Attr() bzw. _analyze_rhassypAttr() (in dener Fassung ab Zeile 1097), ausgewertet dann jeweils an der erforderlichen Stelle.
Zitat von: gregorv am 18 November 2024, 12:38:00Ja, das stimmt, aber handleIntentSetTimedOnOff() schaut nur nach cmd und setzt dann voraus, dass die eigentlichen Kommandos funktionieren.
$cmd wird erweitert. Allerings habe ich grade gemerkt, dass das dann schwierig wird, wenn man unbeding "An" oder "Aus" als xxCmd haben will: "An-for-timer" wird in der Regel tatsächlich nicht exisiteren...
Zitat von: gregorv am 18 November 2024, 12:38:00Die Verwandtschaft sehe ich in dem Commandset für brightness und timedOn und der Bearbeitung numerischer Werte in beiden.
Na ja, das kann man zwar so sehen, aber bei Timern sind "numerische Werte" eigentlich immer Vielfaches von einer ganz speziellen Basisgröße, nämlich Sekunden. Und zum Umgang mit dieser Art von Werten gibt es eine spezielle Hilfsfunktion, um daraus was "maschinenlesbares" (aka Sekunden) zu machen...
Mir ist schon klar, dass das unglaublich viel Code ist, den du da versuchst auf einmal zu verstehen, und du machst das auch sehr aber gut! Aber offen gesagt irritiert es mich, wenn ich mir doppelt die Mühe machen muss, sowas wie das mit "from my side: no changes in timedOn" mehrfach zu erklären oder (abgesehen von Kleinigkeiten wie dem "Auf-for-timer") zu verteidigen.
Bin mal über deine pm drüber.
Ein paar Sachen sollten wir klären, in order of appearance in code:
- "pos" - Was sind das für Devices (TYPE)? Das sieht mir auch irgendwie "generisch" aus, und ich befürchte Verwechslungen (können wir uns dann drum kümmern, wenn das der Fall ist). Kommt aber erst nach pct, dann wird das vorrangig verwendet, falls beides geht. Schwierigkeiten damit?
- zu "defined $data->{customData}->{reopenVoiceInput} ) ) { # GV: ich würde vorschlagen, [...]" Da ist mir der Grund noch unklar, warum der timeout nicht in "SilentClosure" stehen dürfte? Anmerkung: Der Zweig ist insgesamt noch nicht "ausgereift", m.E. sollten wir erst klären, wann woher welche Keys (und Werte) kommen können/sollen. Dass das aus den sentences.ini kommt, dürfte künftig eher die Ausnahme sein.
- das activateVoiceInput() direkt danach stammte aus der Überlegung, dass uU. das Mikro nicht automatisch vom SessionManager aufgemacht werden könnte. Wenn wir ohne das direkt was sprechen können, ist es ok, wenn wir das an der Stelle raus nehmen.
- 'my $json = toJSON($sendData); # @@@ error with "true"' müssen wir klären: _toCleanJSON() gibt es nämlich deswegen, weil Rhasspy Probleme mit puren "toJSON()"-Anweisungen hatte. Ergo müssen wir das m.E. auf der RHASSPY-Empfangsseite klären, wie wir das mit der "1" abgefangen bekommen. In dem Zusammenhang kann es uU. auch einen großen Unterschied machen, welcher JSON-Decodierer de facto aufgerufen wird mit JSON->new->decode().
- zu
if ( ! defined $mapping->{$change} ) { # @@@
$cmd = $mapping->{cmd} // return defined $data->{'.inBulk'} ? undef : respond( $hash, $data, getResponse( $hash, 'NoMappingFound' ) );
}
Zum einen meine ich, dass man die Eingangsbedingung erweitern müßte:
if ( !defined $change || !defined $mapping->{$change} ) {
(das führt sonst uU. dazu, dass es ab der 2. Anfrage definiert ist... (Edit: Wir arbeiten an der Stelle mit einer Kopie und nicht mit dem Original (ref), das sollte also effektiv kein Thema sein))
Aber wichtiger: Mir ist nicht klar,
a) was bei "seltsamen" change-Anweisungen passieren würde und
b) wie der Code mit ".inBulk"-Fällen umgeht.
Hast du dazu Infos, bevor ich mir da theoretisch was zusammenfieseln muss?
Trockenübungs-Revision anbei ;D .
Zitat"pos" - Was sind das für Devices (TYPE)?
Das sind Dooya Rolladen Steuerungen als Type ist ja SetTarget angegeben - dass habe ich aus
pct übernommen.
- zu "defined $data->{customData}->{reopenVoiceInput} )
SilentClosure hieß anfangs
silent und ist gedacht, um bei einem CustomIntent, der den Dialog offen hält, die Fehleransage nach dem Ablauf des
sessionTimeout (schaltbar zu unterdrücken).
M.E. kann das aber ganz entfallen. Zumindest hast Du für
reopenInputVoice den
timeout Ablauf ja generell stumm gemacht.
Aber es hatte ohnehin nichts mit
reopenInputVoice zu tun - insofern denke ich, dass der
sessionTimeout für
reopenInputVoice besser nur da auch eingestellt wird.
Dass ich ursprünglich bei
silent binäre Werte übergeben hatte, sollte dazu dienen, die Stellen, die stumm sein sollen unterschiedlich einstellen zu können.
Zitatdas activateVoiceInput() direkt danach stammte aus der Überlegung, dass uU. das Mikro nicht automatisch
Ja,geht. Der Aufruf von
activateVoiceInput() an dieser Stelle bewirkt das die durch den Zweig
#use default hotword mechanism läuft und dann sind alle Daten weg und offenbar kommt Rhasspy durcheinender, weil keine Spracheingabe mehr angenommen wird (vermutlich weil während einer Session mit einer bestimmten
siteId keine neue Session mit der gleichen
siteId gestartet werden kann).
Zitatmy $json = toJSON($sendData); # @@@ error with "true"' müssen wir klären
Jep, das denke ich auch. Das Problem liegt nicht bei Rhasspy sondern entsteht beim
encoden - so wie ich das auf die Schnelle gemacht hatte ist es ein Workaround, keine Lösung
ZitatAber wichtiger: Mir ist nicht klar, ...
Die Frage hatte ich erwartet - ich weiß es nicht mehr. Ich hatte viel mit Optionen und Types experimentiert, aber das kläre ich noch. Mit der Erweiterung hast Du recht - das müsste noch rein.
Thema Optionen via Specials.
Ich habe für rhasspySpecials mal den Key options eingeführt.
if ($key eq 'options') { # @@@
$hash->{helper}{devicemap}{devices}{$device}{options} = $named if $named;
Damit landet die Einstellung rhasspySpecials -> options:reopenVoiceInput=25 RetryIntent=true so im hash (print von device)
$VAR1 = {
"alias" => "licht",
"group_specials" => {
"async_delay" => "0.5"
},
"groups" => "lampen",
"intents" => {
"GetOnOff" => {
"GetOnOff" => {
"currentVal" => "state",
"type" => "GetOnOff",
"valueOff" => "off"
}
},
"SetNumeric" => {
"timedOn" => {
"cmd" => "on-for-timer",
"currentVal" => "timedOn",
"type" => "timedOn"
}
},
"SetOnOff" => {
"SetOnOff" => {
"cmdOff" => "off",
"cmdOn" => "on",
"type" => "SetOnOff"
}
}
},
"names" => "licht,lampe,b\303\274ro licht",
"options" => {
"RetryIntent" => "true",
"reopenVoiceInput" => 25
},
"rooms" => "arbeitszimmer,b\303\274ro"
};
(ganz unten in der Sektion options)
In handleIntent muss der Inhalt dann in $data geschrieben werden:
my $options = $hash->{helper}->{devicemap}->{devices}->{$device}->{options}; # @@@
for my $key (keys %{$options}) {
$data->{$key} = $options->{$key};
}
Da der Device Name (zumindest bei handleIntentSetNumeric()) erst da drin ermittelt wird, kann man die options erst danach lesen und in $data anhängen.
Vermutlich kennst Du eine Lösung, wie man das schon in der analyzeMQTTmessage() machen kann - dann braucht man die handleIntent...() Prozeduren nicht mit redundantem Code füllen)
Was mir aber noch fehlt, wäre die Möglichkeit das bei dem Rhasspy Modul zu setzen, damit das gleich für alle einschalten kann - rhasspySpecials funktioniert da offenbar nicht. Jedenfalls sehe ich im hash keine Spur von meinen versuchten Settings (für async_delay wäre das auch nicht schlecht, wenn man das da setzen könnte). Hast du da eine Idee?
Ein Dummy device mit Namen 'options' wäre zwar möglich, würde mir aber gar nicht gefallen.
Zitat von: gregorv am 18 November 2024, 22:48:02Thema Optionen via Specials.
Wir kommen voran :) .
Zum Verständnis: "specials" und "tweaks" in meiner Terminologie sind nach meinem bisherigen Verständnis dasselbe wie das, was du unter "options" verstehst. Anders gesagt: Jedes "special" ist bereits eine "option" auf Device-Ebene, ein "tweak" ist das, was du ggf. notfalls in einem Dummy (bäh) unterbringen wolltest ;) . (Nachtrag: Es braucht m.E. daher auch keine weitere Abstufung im RHASSPY-Hash nach "options" oä.)
Als Ziel würde ich für "specials" bzw. "tweaks" in etwa so eine Notation sehen:
attr blind1 rhasspySpecials reopenVoiceInput:SetOnOff=50 SetNumeric:30
RetryIntent:SetNumeric
attr rhasspy rhasspyTweaks reopenVoiceInput=SetOnOff=blinds:35
Das Beispiel sollte - erst mal nur bezüglich der reopen-Angaben, "retry" würde ich erst mal noch zurückstellen - in etwa das bewirken, was du mit deinem "blinds"-Intent vorhattest: Wenn man nur "auf" oder "zu" anweist (über SetOnOff), geht das Mikro wieder allgemein für 30 Sekunden auf (tweak), es sei denn, es gäbe dazu was auf Device-Ebene, z.B. weil "blind1" eine Jalousie ist, die sehr viel längere Laufzeiten hat wie die übrigen Rollläden. Für setNumeric würde dann eine etwas kürzere Zeit gelten.
Zitat von: gregorv am 18 November 2024, 22:48:02wie man das schon in der analyzeMQTTmessage() machen kann - dann braucht man die handleIntent...() Prozeduren nicht mit redundantem Code füllen)
Sowas geht (leider?) nur, wenn man es (weiter erst mal nur bzgl. reopen) innerhalb der Intent-Verarbeitung abarbeitet, von daher würde ich das halt in eine eigene sub auslagern - ganz analog zu dem, was mit "confirm" bereits vercoded ist, nur dass man vermutlich besser die Referenz auf den $data-Hash weitergibt und den innerhalb der sub ergänzt (gefahrgeneigt!).
Ist jetzt klarer, wie es von meiner Seite gedacht ist?
Zitat von: gregorv am 18 November 2024, 22:48:02async_delay wäre das auch nicht schlecht, wenn man das da setzen könnte
Im Moment bin ich der Ansicht, dass wir nicht noch weitere Baustellen aufreißen sollten. Ich fahre hier ein ziemlich gemischtes setup, und was "gleichzeitig" geht, ist sehr individuell. Wenn man es "global" aktivieren könnte, fürchte ich, dass potentielle User genau das machen - und dann nicht mehr darauf achten, was eigentlich individuell sinnvoll wäre ;) . Das ist nämlich oft was ganz anderes, wie man vielleicht zuerst denken würde...
Zitat von: gregorv am 18 November 2024, 22:15:43Das sind Dooya
Habe zum Code an sich - bis auf die Reihenfolge im Code - keine Einwände, es kommt mir nur "speziell" vor, so dass ich gerne im Code vermerken würde, wo es herkommt und in der Form paßt. Dann kann man ggf. anhand des TYPE weiter verzweigen, wenn jemand mit was anderem kommt ;) .
Zitat von: gregorv am 18 November 2024, 22:15:43SilentClosure hieß anfangs silent und ist gedacht, um bei einem CustomIntent, der den Dialog offen hält, die Fehleransage nach dem Ablauf des sessionTimeout (schaltbar zu unterdrücken).
OK, dann muss das "Kind", das da in meinem Hinterkopf rumspukt vermutlich anders heißen: In dem Moment, in dem wir den default dahingehend ändern, dass das Mikro eher wieder für weitere Ansagen offen bleiben soll, braucht es eine Option, das Verhalten aktiv abzuschalten. Das kann per expliziter Ansage erfolgen (=>sentences.ini-Key, insbes. für "CancelAction"), oder eben pro Intent oder Gerät (tweak / special)?
Das mit toXyJSON() muss ich mir gesondert auch auf der MQTT-Ebene mal ansehen. Wird dauern.
Rückfrage zur Funktionsweise:
attr rhasspy rhasspyTweaks reopenVoiceInput=SetOnOff=blinds:35
Auswirkung: setzt reopenVoiceInput=35, aber nur, wenn gerade ein SetOnOff Intent behandelt wurde und das Device zur Gruppe blinds gehörte.
Alternativ aber auch möglich:
attr rhasspy rhasspyTweaks reopenVoiceInput:35
um das sozusagen global (für diese RHASSPY Instanz) zu aktivieren
Oder auch Einschränkungen wie nur für blinds oder nur für SetOnOff Intents:
[code]attr rhasspy rhasspyTweaks reopenVoiceInput=blinds:35
Im hash sollte das dann so aussehen:
"tweaks" => {
"reopenVoiceInput" => {
"SetOnOffGroup" => {
"blinds" => 35
...
oder auch alternativ:
"tweaks" => {
"reopenVoiceInput" => 35
...
Ziel ist am Ende, dass, unter Berücksichtigung eventueller Einschränkungen (blinds ond/oder SetOnOff), "reopenVoiceInput" => 35 in $data steht und wie bisher geprüft wird.
reopenVoiceInput ist da natürlich ein unpassendes Beispiel weil man das in der Regel wohl immer aktiv haben will oder eben nicht, aber ich möchte nur wissen, ob ich das Funktionsprinzip von Tweaks richtig verstanden habe.
Zitat von: gregorv am 19 November 2024, 12:01:59möchte nur wissen, ob ich das Funktionsprinzip von Tweaks richtig verstanden habe.
Das ist ziemlich genau das, was ich mir vorgestellt hatte. Über die konkrete Funktionsweise bzw. in welchen Fällen was ggf. sinnvoll ist, sollte natürlich vorab Klarheit herrschen, das ist jetzt erst mal "ins Unreine" formuliert, um einen Eindruck von den Möglichkeiten zu geben, die sich da ggf. eröffnen könnten.
Anmerkungen noch:
- "tweak" und "special" haben in vielen Fällen denselben Zweck (so wäre es auch hier), und eine (funktionsgleiche) Angabe in "special" hat dann ggf. Vorrang.
- wenn etwas "immer" gelten soll, steht in tweak/special in der Regel irgendwo ein "all". Anders gesagt: defaults schreibt man ggf. nach "all", und schaut dann, ob es was spezielleres gibt oder "all" gelten soll.
Also in der Richtung:
"tweaks" => {
"reopenVoiceInput" => {
"all" => 35
...
(kann sein, dass man ein "doppeltes all" braucht, um bestimmte Kombinationen abzubilden, mal sehen.)
Ich fasse hier mal zusammen, was es für neue Parameter gibt und deren Funktion
- reActiateVoiceInput = nn: -> sonst noch was? Funktion
Einstellung: in der
Rhasspy Konfiguration am Ende eines
sentence []{reActiateVoiceInput=nn} anhängen (gilt dann nur für dieses Kommando).
Besser Alternativ: in
FHEM rhasspyTweaks die Zeile
reActiateVoiceInput=all=nn (gilt immer). Statt
all sind auch andere Bedingungen einstellbar. SetOnOff bewirkt z.B., dass '
sonst noch was' nur für SetOnOff Intents eingeschaltet ist. Welche Bedingungen einstellbar sind wird später noch beschrieben.
bewirkt, dass nach der Behandlung eines Kommandos die Session für die Dauer von
nn Sekunden offen bleibt.
Nach jedem Kommando wird der Timer neu gestartet. Erfolgt kein Kommando mehr, wird die Session nach Ablauf der Zeit beendet still beendet.
Wenn ein Kommando nicht verstanden wurde, wird die Session beendet - außer man kombiniert mit der
wie bitte? Funktion.
Szenario: Wenn man ohne erneutes
Wake-Word noch andere Kommandos geben will.
Zusätzlich: Es wurde die
responseId ContinueSession in der
rhassp-de.cfg hinzugefügt, unter der man Alternativen für den Text "Sonst noch was?" einstellen kann.
- retryInput = true -> wie bitte? Funktion
Einstellung: in der
Rhasspy Konfiguration am Ende eines
sentence []{retryInput=true} anhängen (gilt dann nur für dieses Kommando).
Besser Alternativ: in
FHEM rhasspyTweaks die Zeile
retryInput=all=true (gilt immer). Statt
all sind auch andere Bedingungen einstellbar.
bewirkt, dass wenn ein Kommando nicht verstanden wurde, statt der Standardansage die Ansage
wie bitte? und der Dialog offen bleibt, damit man das fehlerhafte Kommando ohne erneutes
Wake-Word korrigieren kann. Die Dauer, die der Dialog offen bleibt wird von dem Timeout bestimmt, der beim Start einer Session gesetzt wurde und verlängert sich durch
wie bitte? nicht.
Zusätzlich: Es wurde die
responseId RetryInput in der
rhassp-de.cfg hinzugefügt, unter der man Alternativen für den Text "wie bitte?" einstellen kann.
- noResponse = true: -> Standard Antwort eines Intent unterdrücken, nur sinnvoll, wenn reActiateVoiceInput oder retryInput aktiv ist.
bewirkt, dass nach Abarbeitung eines Intent keine Ansage erfolgt.
Einstellung: in der
Rhasspy Konfiguration am Ende des betreffenden
sentence für
etwas höher, leiser, heller ... noch
[]{noResponse:true} anhängen.
Szenario Bei einer Steuerung von Lampen oder Rollläden können so Kommandos wie '
etwas heller' oder '
etwas runter' / '
Stop' ggf. mehrfach hintereinander gegeben werden, ohne dass man auf das Ende einer Ansage warten muss. Wenn diese Art Kommandos gegeben werden, sieht/hört man ja das Ergebnis.
- closeSession ->sei still Funktion, nur sinnvoll, wenn reActiateVoiceInput oder retryInput aktiv ist.
Einstellen: in der
Rhasspy Konfiguration einen weiteren
sentence z.B. (fertig|sei still){closeSession:true} unter
CancelAction einfügen.
Funktionserweiterung für
CancelAction bewirkt, dass die laufende Session mit einer anderen Ansage (
responseId CloseSession) beendet wird.
Szenario: es ist gerade eine Session offen z.B. durch
reopenVoiceInput und das Telefon klingelt.
Rhasspy hört dann gerade auf ein Kommando und wird ständig dazwichen reden, während man sich unterhält. Mit dem Kommando '
sei still' kann man
Rhasspy zum schweigen bringen als Antwort erhält man nicht das unpassende '
habe abgebrochen' sondern z.B. ein kurzes '
OK'
Zusätzlich: Es wurde die
responseId CloseSession in der
rhassp-de.cfg hinzugefügt, unter der man Alternativen für den Text "OK" einstellen kann.
- resetInput -> Alles auf Anfang
Einstellen: in der
Rhasspy Konfiguration den
sentence Wake-Word{resetInput} bei irgend einem Intent einfügen.
bewirkt, dass sofort eine neue Session gestartet wird, wenn das
Wake-Word gesprochen wird. Man vermeidet ein "
Das habe ich nicht verstanden" oder "
wie bitte", wenn man während einer laufenden Session versehentlich das
Wake-Word spricht.
Szenario: es ist gerade eine Session offen z.B. durch
reopenVoiceInput und das
Wake-Word wird gesprochen. Wegen der offenen Sesssion ist z.B.
Porcupine nicht aktiv und
Rhasspy würde vergeblich versuchen einen passenden Intent zu finden. Durch
resetInput kann man sein
Wake-Word bei einem Intent unterbringen und
Rhasspy versteht das
Wake-Word als Kommando. Damit gibt es kein
IntentNotRecognized mehr. Intern wird der betreffende Intent dann nicht ausgeführt, und statt dessen - wie man ja nach einem
Wake-Word erwartet - eine Neue Session gestartet.
- silentClosure = true -> Session Ende erfolgt ohne Ansage
Einstellung: In einem hash als
return eines CustomIntent übergeben.
Hauptsächlich gedacht für CustomIntents.
Wenn ein CustomIntent eine Session für eine angegebene Zeit offen hält, wird am Ende der Session die Standard Antwort unterdrückt.
Zusätzlich: es wurde die
responseId SilentClosure eingeführt, die man auch als
response übergeben kann und eine stille Ausführung eines Kommandos bewirkt.
Hinweis für CustomIntents:
Wenn
reActiateVoiceInput eingeschaltet ist, erfolgt anschließend die Frage
sonst noch was?. In dem obigen Fall möchte man das eventuell nicht haben. Bei einem CustomIntent kann man im
return hash den Key
reActiateVoiceInput mit dem Wert
undef übergeben, um den Start einer neuen Session zu vermeiden.
Allgemein:
Alle obigen Einstellungen, die in
rhasspyTweaks möglich sind, können auch als
Device Einstellung unter
rhasspySpecials vorgenommen werden und gelten dann nur für dieses
Device. Bitte das nötige Specials-Format beachten: die Tweaks Einstellung
retryInput=all=true muss bei Specials
retryInput:all=true geschrieben werden (der '
:').
Specials sind vorrangig vor
Tweaks.
Bei der Einstellung unter
rhasspyTweaks können statt
all Bedingungen wie SetOnOff angegeben werden, auch kombiniert in der Form SetOnOff|SetNumeric wober die betreffende Einstellung nur für die Intents SetOnOff, bzw. SetOnOff oder SetNumeric gilt.
Mögliche Bedingungen (derzeit) sind:
Intent, Group, gdt, Type, Name, Device, RoomBeispiel:
reActiateVoiceInput=light=15 ->
sonst noch was? für 15 Sekunden, gilt nur für Geräte deren
attr GenericDeviceType light ist.
Oder:
reActiateVoiceInput=SetOnOff=32 Lampen|Rollläden=25 -> für Intent
SetOnOff gilt
sonst noch was? für 32 Sekunden, ansonsten für die
Gruppen Lampen und Rolläden eben 25 Sekunden.
Die erste Bedingung (von linkst nach rechts), die zutrifft, bestimmt die Zeiteinstellung.
Wer eigene Daten in als
customData an
Rhasspy übertragen will, kann die mit
Tweaks so übergeben:
customData=myKey100=myValue100 myKey200=myValue200oder mit Specials so:
customData:myKey1=myValue1 myKey2=myValue2Diese Daten werden dann als
JSON - String gepackt an
Rhasspy weitergeleitet.
Zu Tweaks/Specials:
Prima, dann werde ich gleich mal an die nötigen hash Einträge für Tweaks/Specials machen und mir schon mal die Funktion überlegen, die das dann in $data schreibt.
bzgl. all wäre es zumindest für die Funktion nicht nötig das mehrfach zu verschachteln - oder gibt es da schon eine Funktion, die das so erwartet ?
Zitat von: gregorv am 19 November 2024, 13:13:04ToDo: Derzeit ist kein Abbruch mit CancelAction möglich.
Weiß nicht, ob das noch stimmt; hatte dazu was in intentCancelAction ergänzt, aber sentences.ini muss was liefern.
Zitat von: gregorv am 19 November 2024, 13:13:04Wenn ein Kommando nicht verstanden wurde, wird die Session beendet.
Bin nicht sicher, ob das der Standard bleiben sollte. Tendiere dazu, das zu ändern und lieber die Gegenausnahme explizit definieren zu lassen.
Zitat von: gregorv am 19 November 2024, 13:13:04noResponse = true: -> Standard Antwort eines Intent unterdrücken
Hmmm, hatten wir sowas diskutiert? Bin grundsätzlich der Ansicht, dass eine Sprachsteuerung _immer_ eine Rückmeldung geben sollte, wenn man was von ihr will und sie das kapiert hat (ggf. sogar bei/nach "Schluss jetzt", und das sollte eigentlich sogar schon so in CancelAction vorbereitet sein).
Zitat von: gregorv am 19 November 2024, 13:13:04RetryInput = true
Guter neuer Name. Über das gewünschte Verhalten (und auch das Coding drumrum) würde ich erst abschließend nachdenken, wenn wir ansonsten soweit sind.
Zitat von: gregorv am 19 November 2024, 13:13:04-> Session Ende erfolgt ohne Ansage (nur für Custom Intents)
Ähm, also: nach reopen...() wird nach timer-Ablauf still (?) geschlossen. Unterscheidungsmerkmal wäre ggf., dass der key defined ist, aber nicht irgendwie belegt.
Ansonsten kann ich mir vorstellen, dass wir da schon irgendwie einen numerischen Wert (0=> mach es gleich) mitgeben könnten, um einen timeout zu definieren, aber im Moment ist mir das zugegebenermaßen auch noch zu abstrakt, um dazu was qualifizierteres sagen zu können.
Aus einem CustomIntent heraus wäre es jedenfalls auch wichtig unterscheiden zu können, ob man die alte Sitzung fortführen will oder schlicht ein "reopen" haben möchte. Ist aber auch noch sehr abstrakt...
Zitat von: gregorv am 19 November 2024, 13:21:58bzgl. all wäre es zumindest für die Funktion nicht nötig das mehrfach zu verschachteln - oder gibt es da schon eine Funktion, die das so erwartet ?
Was "nötig" ist, werden wir sehen; im Moment gehe ich davon aus, dass wir eine mehrdimensionale Matrix haben werden, die zu durchsuchen ist und bin daher darauf gekommen, dass das uU. eine tiefere Gliederung erforderlich machen könnte. Mal schauen - die Funktion, um das auszuwerten gibt es nämlich auch noch nicht, du bist also im Prinzip "frei". Meine Bitte ist in dem Zusammenhang nur: Belege möglichst wenige keys, damit das ganze einigermaßen übersichtlich bleibt. Anders gesagt: wir brauchen nicht sowas wie ein bmp, sondern ein svg (hoffe, das "Bild" ist einigermaßen klar ;D )...
Heute mal die Tweaks
rhasspyTweaks -> reopenVoiceInput=SetOnOff=blinds=xxx=yyy=zzz=35 macht im tweaks hash das:
"reopenVoiceInput" => {
"SetOnOff" => {
"blinds" => {
"xxx" => {
"yyy" => {
"zzz" => 35
}
}
}
}
und bei rhasspyTweaks -> reopenVoiceInput=all=35 kommt das raus:
"reopenVoiceInput" => {
"all" => 35
}
Ich hoffe das '=' auch vor dem Wert, statt ':' ist akzeptabel, sonst muss ich noch was dazu bauen.
In habe ich dafür das eingebaut:
if ($line =~ m{\A[\s]*(reopenVoiceInput)[\s]*=}x) {
($tweak, $values) = split m{=}x, $line, 2;
$tweak = trim($tweak);
return "Error in $line! No content provided!" if !length $values && $init_done;
my @parts = split /=/, $values;
my $value = pop @parts;
my $ref = \%path;
foreach my $key (@parts) {
if ($key eq $parts[-1]) {
$ref->{$key} = $value;
} else {
$ref->{$key} = {} unless exists $ref->{$key};
$ref = $ref->{$key};
}
}
$hash->{helper}{tweaks}{$tweak} = \%path;
next;
}
Für rhasspySpecials Änderung in der _analyze_rhassypAttr()
rhasspySpecials -> reopenVoiceInput:all=25
wird im Device hash zu:
"reopenVoiceInput" => {
"all" => "25 "
},
mit folgender Codeänderung (einfacher Voeschlag) hat nun jede Option ihr eigenes Segment
if ($key =~ m{reopenVoiceInput|RetryIntent}x ){ # @@@
my ($subkey, $val) = split m{\=}x, $val, 2;
$hash->{helper}{devicemap}{devices}{$device}{$key}{$subkey} = $val;
}
auf Device-Ebene ist, denke ich, maximal ein SubKey ausreichend (all oder intent oder group. Ich glaube auf Device Ebene wird wohl immer nur all benötigt.
Wenn es, wie bei Tweaks beliebig viele Ebenen sein sollen, kann ich den Tweaks Code in eine sub auslagern und auch bei Specials verwenden.
Die unterschiedliche Schreibweise bei Tweaks und Specials mit der vertauschen Anwendung von : und = ist etwas gewöhnungsbedüftig.
ZitatWeiß nicht, ob das noch stimmt;
Stimmt, ich meine,dass Du sowas gesagt hast - ich schau mir das mal an.
ZitatTendiere dazu, das zu ändern und lieber die Gegenausnahme explizit definieren zu lassen
Kein Problem, was soll die Bezeichnung für diesen Schalter sein ?
ZitatHmmm, hatten wir sowas diskutiert?
Auch Hmmm, Du fandest es sogar so gut, dass Du es in jedem Intent schon eingebaut hast. Für mich ist nur wichtig, dass es im CustomIntent drin bleibt.
In dem Szenario: Sonne blendet -> Kommando:Rolladen Zu -> Kommando:Stop -> doch etwas zu früh Stop gesagt -> Kommando:etwas runter ... würden mich ständige Audio Bestätigungen ziemlich stören - ich sehe es ja. Bei '
etwas lauter' sollte Rhasspy auch nicht dazwisch reden. Auch ist das Micro währen der Response ja zu und man muss warten bis eine Ansage fertig ist.
noResponse ist übrigens eine Option die nur auf Ebene
sentence Sinn macht. Das muss man dann in
Rhasspy machen oder ginge das auch im
RHASPY Modul ?
ZitatÄhm, also: nach reopen...() wird nach timer-Ablauf still (?) geschlossen
Jep - nicht einstellbar. Die Ansage 'Da hat etwas zu lange gedauert' wäre auch sehr unpassend - die war ja der Grund warum ich
silent eingeführt hatte.
Zitatda schon irgendwie einen numerischen Wert (0=> mach es gleich)
Du meinst aber nicht den
sessionTimeout der mit
reopen gesetzt werden kann ? Da fehlt mir eine Verknüpfung :)
ZitatWas "nötig" ist, werden wir sehen; im Moment gehe ich davon aus, dass wir eine mehrdimensionale Matrix haben werden
Mit dem Code, den ich ausgeknobelt (ein Post früher) habe kann es hunderte von SubKeys geben und mehrdimensional ist es schon dadurch, dass das für jede Option gilt.
Die Funktion, die da draus dann
$data keys macht könnte allerdings sehr auffwändig werden. Die muss ja immer prüfen, ob die SubKeys mit irgendwas gerade matchen um zu entscheiden ob der
Key in
$data auftaucht.
Zitat von: gregorv am 19 November 2024, 23:31:33Kein Problem, was soll die Bezeichnung für diesen Schalter sein ?
"deactivateVoiceInput"? Vielleicht sollte man "reopenVoiceInput" auch zu "reactivateVoiceInput" umbenennen? Das würde jedenfalls besser zum setter-Namen passen...
ad "noResponse" - ok, als gezielte Aktion macht es uU. Sinn, und wir bauen das auch nicht wieder aus dem Code aus. Aber für den Moment würde ich erst mal keinen Aufwand in "tweaks"/"specials" dazu reinstecken wollen, erst müßte das Prinzip klarer sein. Also kommt der Key bis auf weiteres entweder aus CustomIntent-Verarbeitung oder aus der sentences.ini, nicht aus der Code-Verarbeitung direkt in RHASSPY.
Zitat von: gregorv am 19 November 2024, 23:31:33Zitatda schon irgendwie einen numerischen Wert (0=> mach es gleich)
Du meinst aber nicht den sessionTimeout der mit reopen gesetzt werden kann ? Da fehlt mir eine Verknüpfung :)
Der (eventuell noch nicht zu Ende gedachte) Gedanke war, den Timer zu löschen bzw. mit einem anderen Timer zu überschreiben. Geht irgendwie auch um sauberes "cleanup".
Zitat von: gregorv am 19 November 2024, 23:31:33Mit dem Code, den ich ausgeknobelt (ein Post früher) habe kann es hunderte von SubKeys geben und mehrdimensional ist es schon dadurch, dass das für jede Option gilt.
Nun ja, der Code "kann" beliebig gestuft. Das ist aber nicht unbedingt das, was ich mit "mehrdimensional" gemeint hatte. Die "Dimensionen", die mir im Moment dazu auf die Schnelle einfallen: "intent", "gdt", "device (-regex)", vielleicht sogar "value" ("on" könnte anders zu behandeln sein wie "off"?).
Ich meine, dass "confirm" schon in etwa in diese Richtung geht, und dass der Code sehr viel komplexer sein müßte. Schau dir dazu auch an, wie die Gegenrichtung in etwa funktioniert ("needsConfirmation"?) - wir müssen das, was wir vorne zusammenbasteln ja auch hinten wieder durchsuchen...
Ad coding: über den Style sollten wir mal reden... "Folklore" wie "foreach" baue ich jedenfalls nicht in den Code ein ;D , und warum du einmal "perlcritic"-optimierte Regexe übernimmst, um dann 3 Zeilen später dem Compiler keine genaueren Anweisungen zu übergeben, erkläre ich mir erst mal durch die Uhrzeit, zu der das entstanden ist :P .
ZitatDas ist aber nicht unbedingt das, was ich mit "mehrdimensional" gemeint hatte
Eher so (für die Tweaks)?
"reopenVoiceInput" => {
"Deckenl*" => 39,
"Lampen" => 38,
"SetNumeric" => 36,
"SetOnOff" => 35,
"blinds" => 37
}
Das gefällt mir ,ehrlich gesagt, auch besser und macht auch die Auswertung einfacher.
Man könnte sich auch so etwas vorstellen - oder gemischt.
"reopenVoiceInput" => {
"(SetOnOff|SetNumeric|light|Lampen|Deckenl*)" => 35
}
ZitatAd coding: über den Style sollten wir mal reden...
Schon klar, ich bin happy, wenn es funktioniert, Schönheit kommt beim Aufräumen.
Man könnte sich auch so etwas vorstellen;
"reopenVoiceInput" => {
"(SetOnOff|SetNumeric|blinds|Lampen|Deckenl*)" => 35,
"blinds" => "(drive-up-time-to-open|35)"
}
Also bei gdt blind soll er sich den Wert aus attr drive-up-time-to-open holen und 35 als default.
Sorry für die etwas knappe Antwort:
- Zugriffe auf "fremde" Attribute oder Readings sollten wir vermeiden. Der User soll das erforderlichenfalls halt "doppelt" konfigurieren, die Welt ist m.E. zu bunt, um das (mit vertretbarem Aufwand) per Coding abzufangen.
- Wie soll man das auswerten, wenn alles "vermischt" ist? Meine Idee bei vielen Dingen im RHASSPY-Hash war, das "lesbar" zu machen, wie RHASSPY die Angaben interpretiert. Und da muss es ggf. eine Unterscheidung geben zwischen "blinds" als gdt, Gruppenname und Devicename, und "blinds" kann unterschiedliche Zeiten bedeuten für verschiedliche Intents*... Kann also sein, dass sowohl die Notation der Attribut-Zeile in tweaks/specials sehr komplex werden kann, wie auch der Code zur Analyse (Attribut=>RHASSPY-Hash) und Auswertung (RHASSPY-Hash=>konkrete Aktion nach Spracheingabe).
Hoffe, es wird jetzt noch klarer, was mit "mehrdimensional" gemeint ist.
*Insgesamt angemerkt: Eine zu starke Fragmentierung der Intents hatte ich als "wenig hilfreich" empfunden und das weitgehend rückgebaut, so dass es jetzt einige wenige Grundtypen gibt (teils mit Übergangsmöglichkeiten vom einen zum anderen). Aber grade für sowas wie das, was wir grade zusammenfrickeln, ist es sinnvoll, zwischen den verschiedenen Intents unterscheiden zu können. Auch für die Strukturierung der sentences.ini über die passend gefüllten slots wurde "gefühlt" die Trefferquote deutlich erhöht, was an effektiv gesprochenen Anweisungen "umsetzbar"war - schlicht, weil die erkennbaren "Zusammenhänge" trennschärfer formuliert werden können...
@Beta-User
Folgende Überlegung für die sub zur Auswertung:
Ziel ist es die entsprechende Option als Key mit einem passenden Wert
zurückzuliefern in
$data zu schreiben.
Glossar:
- Option ist das, was später als Key zurückgeliefert wird
- Bedingungen sind die Keys, die unter Option im hash stehen
- Werte ist das was bei diesen Keys als value im hash steht
Ablauf:
Es wird geprüft, ob die Option z.B.
reopenVoiceInput im
tweak hash vorhanden ist > damit hat man auch gleich den
key für das
returnAus
$data werden, soweit vorhanden einige Werte gelesen und als
list zusammengestellt. Andere Werte, wie z.B.
$gdt werden vorher ermittelt.
die Liste sieht z.B so aus:
my @condition_values = split(' ', "all $data->{intent} $data->{Group} $gdt $data->{device} $device $data->{Room} $data->{Type}");
Reihenfolge und welche Bedingungen müssen noch festgelegt werden das wären auch gleichzeitig die Elemente, die in rhasspyTweaks verwendet werden können.
Für jeden Bedingungen in dieser Liste werden alle
keys aus
hash->helper->tweaks->option überprüft, ob es ein Match gibt.
Falls ja, wird der zugehörige Wert ausgelesen und als
value für
return gespeichert und die
loops beendet.
Damit hat man das
key-value Paar für den
return Wert. Weiterhin erfolgt dann noch die
rhasspySpecials Prüfung, die den Wert ggf. überschreibt.
Der Key/Values im
hash kann entweder ein einzelnes
key-value Paar sein z.B.
SetOnOff=35 oder wenn keine unterschiedlichen Werte für mehrere Bedingungen benötigt werden
SetNumeric|light|Lampen=36.
Die Key Varianten können 1 zu 1 im Such-Regex verwendet werden z.B.
if ( $condition =~ m{$condition_line}x ) {
Im Klartext:
SetOnOff =~ m{SetNumeric|light|Lampen}xFragen:
- Wenn ein Treffer gefunden wurde, wird die weitere Suche beendet. M.E. ist das ausreichend - wenn man für eine bestimmte Gruppe was anderes möchte, kann man das über die rhasspySpecials einstellen. Wie siehst Du das?
- Wenn im o.Beispiel erst ein Treffer bei light erfolgt, bleibt SetNumeric unbeachtet, auch falls der nächste Key für SetNumeric den Wert 20 liefern würde. Das scheint mir kompliziert, weil die Regel für eine irgendwie gewollte Priorisierung fehlt.
- Wenn z.B. rhasspyGroup identisch ist mit gdt, soll das unterschieden werden?
Und noch was zur Lesbarkeit: Bei einer Einstellung wie
reopenVoiceInput=SetOnOff=15 SetNumeric=16 light=17 Lampen=18 Deckenl*=19 blind=25 weiß man bei Intents sofort, was gemeint ist,
light ist auch recht sicher
gdt, bei allen anderen ist das schon schwierig. Man könnte das verbessern, indem man statt
Lampen Group:Lampen einsetzt oder statt
Deckenl* name:Deckenl* wenn, sollte das bei allen Bedingungen angegeben werden und macht auch eine Unterscheidung von gleich benannten Bedingungen möglich. Sogar eine Bedingung, die in der Liste der sub noch gar nicht vorgesehen ist - wie irgend ein Wert aus
$data (rawInput:) oder sogar dem
$hash ($device->{alias}:). Man könnte auf die Liste sogar ganz verzichten.
Ups... jetzt bin ich doch von der Lesbarkeit abgeschweift, ja, ich weiß, 'zu bunt'.
ZitatWeiß nicht, ob das noch stimmt; hatte dazu was in intentCancelAction ergänzt, aber sentences.ini muss was liefern.
Habe ich mir angeschaut und die Zeile:
if ( !defined $data_old || defined $data->{resetInput}) {
gefunden, also den
$data->key resetInput. Den hatte ich in meiner Aufstellung nicht aufgeführt, weil er nie über die
Tweaks gesetzt wird, sondern als
Wake-Word{resetInput} in einem
sentence.
Er bewirkt ja, dass wenn das
Wake-Word während eines offenen Dialoges gesprochen wird die nächste
MQTT Nachricht von
Rhasspy die Session schließt und eine neue aufmacht. In
handleIntentCancelAction() bewirkt er, dass das ganze still passiert.
Der andere
$data-key ist
closeSession, der in der Zeile:
if ( defined $data->{closeSession} ) {
der künftig mal bestimmen wird, welche
responseId hier gespielt werden soll (shut-up)
Wenn er gesetzt wird, bewirkt er aber, dass nach der Behandlung eines Intent gar keine neue Session aufgemacht wird.
In
respond() die Zeile:
if ( $topic ne 'continueSession' && $type eq 'voice' && !defined $data->{closeSession} && ( defined $data->{reopenVoiceInput} || defined $hash->{sessionTimeout} ) ) {
Ein gesetztes $data->{closeSession} sollte nicht eine neue Session blockieren, sondern, wenn diese aufgemacht wird, den Intent CancelAktion aktivieren.
Zitat von: gregorv am 21 November 2024, 11:07:24Ein gesetztes $data->{closeSession} sollte nicht eine neue Session blockieren, sondern, wenn diese aufgemacht wird, den Intent CancelAktion aktivieren.
Nope. "closeSession" ist gedacht für "habe fertig und will jetzt nicht mehr mit Rhasspy quatschen!". Ergo darf in dem Fall auch keine neue Sitzung aufgemacht werden, und imo sollte dazu eine (positive) Bestätigung kommen.
Zitat von: gregorv am 21 November 2024, 11:07:24gefunden, also den $data->key resetInput. Den hatte ich in meiner Aufstellung nicht aufgeführt, weil er nie über die Tweaks gesetzt wird, sondern als Wake-Word{resetInput} in einem sentence.
Er bewirkt ja, dass wenn das Wake-Word während eines offenen Dialoges gesprochen wird die nächste MQTT Nachricht von Rhasspy die Session schließt und eine neue aufmacht. In handleIntentCancelAction() bewirkt er, dass das ganze still passiert.
Zum einen: Er kann (wie prinzipiell alle keys) "überall" gesetzt werden, und ich sehe auch keinerlei (sachlich notwendige) Begrenzung auf ein Wakeword in sentences.ini.
Und der key ist schlicht dafür gedacht, alles "auf Anfang" zu stellen, also insbesondere auch alle Intent-Filter (auf den default) zurückzusetzen.
Ob eine neue Sitzung geöffnet werden soll, ist (zumindest im Moment) damit nicht gesagt; so jedenfalls mein aktuelles Verständnis der End-Sektion in sub respond().
Nebeneffekt: Wenn man "CancelAction" aktivieren will, muss man einen anderen key verwenden und das entsprechend ansteuern, CancelAction ist nicht Teil des per default aktivierten Intent-sets.
Nebenbemerkung:
Wenn das das gemeinsame Verständnis der keys ist, müssen wir das demnächst mal in der commandref niederscheiben, damit wir nicht "versehentlich" wieder die Bedeutung ändern, sondern uns selbst an das halten, was wir mal festgelegt haben...
Zitat von: gregorv am 20 November 2024, 23:44:56Folgende Überlegung für die sub zur Auswertung:
imo ist das (bei weitem) "zu flach", das ganze wird ziemlich sicher noch sehr viel komplexer wie die sub getNeedsConfirmation(), und wir sollten auch "andersum" anfangen zu suchen. Also: Wenn "specials" was liefert, sind wir bereits fertig, "tweaks" ist allgemeiner, und die weitere Suche wäre überflüssig/ineffektiv. Das gilt umso mehr für den Fall, dass "specials" auch die Gegenausnahme liefern kann (unter einem anderen "specials"-key).
Wir können das nur im Moment nicht vercoden (bzw. nur einen Platzhalter vorsehen), weil wir für "specials" noch nichts näheres "wissen".
Ich komme aber im Moment bis auf weiteres auch nicht dazu, Code beizusteuern...
ZitatNope. "closeSession" ist gedacht für "habe fertig und will jetzt ...
Vollkommen richtig, allerdings macht die
CancelAktion ja schon die Session zu und räumt auf. Danach gibt es den
$data->reopenVoiceInput nicht mehr und es wird somit keine neue Session aufgemacht.
Das Problem ist, dass die
CancelAktion nicht aktiv ist und
closeSession gar nicht erst kommen kann.
Ich habe das so gelöst.
Wenn wir eine neue Session aufmachen (
sonst noch was?) dann wird
CancelAktion aus der
disabled Liste entfernt und somit kann man via
CancelAktion z.B. mit '
fertig' oder '
sei still' die Session beenden (mit einer passenden Ansage).
Folgende Code Änderung in der
respond():
In Zeile:
if ( $topic ne 'continueSession' && $type eq 'voice' && ( defined $data->{reopenVoiceInput} || defined $hash->{sessionTimeout} ) ) {
das
closeSession entfernt, weil nicht nötig.
Unmittelbar darunter zwei neue Zeilen eingefügt:
$toDisable = [qw(ConfirmAction Choice ChoiceRoom ChoiceDevice)];
configure_DialogManager($hash, $data->{siteId}, $toDisable);
um
CancelAktion aus der disabled Liste zu entfernen. Das weiter oben schn stehende
my $toDisable habe ich aus dem
ifelse ...herausgezogen
Und in der
handleIntentCancelActionDie Zeile:
if ( (!defined $data_old || defined $data->{resetInput}) && $data->{intent} ne "CancelAction") {
das
$data->{intent} ne "CancelAction" mit eingesetzt, damit, falls wirklich der Intent gestartet wurde, die Session auch beendet wird (die
handleIntentCancelAction kann ja auch von einer anderen sub aus aufgerufen werden)
Die anderen Ansagen werden so bestimmt.
my $response = ( defined $data->{closeSession} ) ? 'CloseSession' : 'DefaultCancelConfirmation'; # @@@
$response = getResponse( $hash, $response );
Hier habe ich eine neue
responseId eingeführt, weil die möglichen Ansagen von
DefaultConfirmation unpassend sein können z.B. '
wird erledigt' klingt in dieser Situation etwas unpassend.
Unter
CancelAction kann mit dem
sentence z.B.
(fertig|sei still){closeSession:true} die Session mit einer anderen Antwort beendet werden.
ZitatZum einen: Er kann (wie prinzipiell alle keys) "überall" gesetzt werden ...
In einem beliebigen
sentence: JA über
Tweaks: oder so NEIN und JA, er macht eine neue Session auf. Ich vermute Du verwechselst da etwas ? In der
response() spielt er jedenfalls gar keine keine Rolle - nur in der
analyzeMQTTmessage() führt er dazu, dass bei jeder MQTT Nachricht egal welche eine neue Session aufgemacht wird. Damit darf
resetInput nur mal kurzzeitig gesetzt sein. Da wird übrigens auch die
handleIntentCancelAction() aufgerufen, obwohl kein Intent
CancelAction vorliegt, um die alte Session sauber zu beenden.
Zitatsollten auch "andersum" anfangen zu suchen
Stimmt, ist effizienter.
ZitatIch komme aber im Moment bis auf weiteres auch nicht dazu, Code beizusteuern
Kein Problem, Ich bau erst mal die
getOptions() so, dass die Keys, die für die Einstellungen der neuen Funktionalität erforderlich sind gesetzt werden können, damit man das nicht nur über den Trick
[]{option:wert} im
sentence machen kann.
Hmmm, bitte, wir gehen erst nochmal wieder einen oder zwei Schritte zurück, bevor wir alles mögliche wieder ausbauen, was da mit/für
sub _get_sessionIntentFilter vorbereitet war:
Zitat von: Beta-User am 12 November 2024, 11:04:39Da 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).
Theoretischer Hintergrund in Kurzform:
Zitat von: Beta-User am 31 Oktober 2024, 15:43:38Bin jetzt mal über den Code und die Doku bei Rhasspy. Dabei ist mir der Unterschied zwischen dem session-intentFilter (JSON-Array mit flachem Text) und dem globalen "configure" (JSON-Array mit Objekten, die an- oder ausgeschaltet sind) wieder deutlich geworden.
Ich komme im Moment nicht zum Testen und muss mich auf theoretische Hinweise beschränken, sorry...
Zitatbevor wir alles mögliche wieder ausbauen, was da mit/für sub _get_sessionIntentFilter vorbereitet war
Da hast Du mich abgehängt - ich habe die
_get_sessionIntentFilter noch nie geändert und Du hast sie auch noch nicht erwähnt.
ZitatWenn es so jetzt nicht aktiviert wird,...
.
Was sollte denn aktiviert werden ? Ich teste das gerne, aber ich müsste wissen was zu testen ist, und welches Resultat erwartet wird.
ZitatUnterschied zwischen dem session-intentFilter (JSON-Array mit flachem Text) und ...
In den Logs habe ich zumindest noch nie etwas anderes gesehen, als flachen text für ein
intentFilter (ein array in doublequotes).
Rhasspy Dialog Manager erwartet auch nur für
configure eine Struktur.
Wenn für
startSession intentFilter übergeben wird, werden ALLE intents für diese Session deaktiviert, außer denen im Array. Ein Aktivieren vom Intents, die per
configure disabled wurden, ist damit nicht möglich.
Für die nächste Session ist wieder das gültig. was vor mal in
configure als Struktur gesendet wurde.
Die einzige Möglichkeit das (sozusagen die default Einstellung) zu ändern, ist erneut
configure zu senden, was unsere
configure_DialogManager() macht. Die baut die Struktur aber selbst zusammen, wir übergeben in
toEnable oder
toDisable nur ein Array wie für
startSession and diese Funktion.
Das tritt z.B. beim Trainig auf, oder wenn man
configure_DialogManager() zur
runtime benutzt:
2024.11.22 10:31:24 5: RHASSPY: [RHASSPY.IF] Parse (IO: RHASSPY.MQTT2): Msg: hermes/dialogueManager/configure => {"intents":[{"intentId": "de.fhem:GetTime","enable": true},{"intentId": "de.fhem:Blinds","enable": true},{"intentId": "de.fhem:SetNumeric","enable": true},{"intentId": "de.fhem:ReSpeak","enable": true},{"intentId": "de.fhem:Shortcuts","enable": true},{"intentId": "de.fhem:SetOnOff","enable": true},{"intentId": "de.fhem:DeviceState","enable": true},{"intentId": "de.fhem:GetNumeric","enable": true},{"intentId": "de.fhem:DayNight","enable": true},{"intentId": "de.fhem:GetDate","enable": true},{"intentId": "de.fhem:ConfirmAction","enable": false},{"intentId": "de.fhem:CancelAction","enable": true},{"intentId": "de.fhem:Choice","enable": false},{"intentId": "de.fhem:ChoiceRoom","enable": false},{"intentId": "de.fhem:ChoiceDevice","enable": false}],"siteId": null}
hier z.B das resultat von dem
configure_DialogManager() Aufruf, den ich in der
respond() eingebaut habe (
{"intentId": "de.fhem:CancelAction","enable": true}Da fällt mir übigens noch ein, dass ich am Ende der Session wieder in den ursprünglichen Zustand herstellen sollte - gerade erledigt. Obwohl es eigentlich ganz schick wäre, wenn man unmittelbar nach dem
Wake-Word auch '
oh nein' sagen könnte - da gab es aber ein Aufräum Problem ?.
Zitat von: gregorv am 22 November 2024, 12:03:14Wenn für startSession intentFilter übergeben wird, werden ALLE intents für diese Session deaktiviert, außer denen im Array. Ein Aktivieren vom Intents, die per configure disabled wurden, ist damit nicht möglich.
Das würde dem widersprechen, was wir bisher bei CustomIntents im Code hatten.
Habe jetzt doch einen Kurztest gemacht, folgender Mitschnitt aus mosquitto_sub:
hermes/dialogueManager/sessionStarted {"sessionId": "motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047", "siteId": "motox", "customData": "manual", "lang": null}
hermes/nlu/query {"input": "wie spät ist es", "siteId": "motox", "id": null, "intentFilter": ["de.fhem:SetColor", "de.fhem:SetColorGroup", "de.fhem:SetTimedOnOffGroup", "de.fhem:GetState", "de.fhem:GetNumeric", "de.fhem:SetOnOffGroup", "de.fhem:GetDate", "de.fhem:SetOnOff", "de.fhem:SetTimedOnOff", "de.fhem:siteId2room", "de.fhem:MediaControls", "de.fhem:Shortcuts", "de.fhem:SetScene", "de.fhem:ReSpeak", "de.fhem:SetTimer", "de.fhem:GetOnOff", "de.fhem:SetNumericGroup", "de.fhem:GetTime", "de.fhem:SetNumeric"], "sessionId": "motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047", "wakewordId": "manual", "lang": null, "customData": "manual", "asrConfidence": 1.0, "customEntities": null}
hermes/nlu/intentParsed {"input": "wie spät ist es 25", "intent": {"intentName": "de.fhem:GetTime", "confidenceScore": 1.0}, "siteId": "motox", "id": null, "slots": [{"entity": "reopenVoiceInput", "value": {"kind": "Unknown", "value": "25"}, "slotName": "reopenVoiceInput", "rawValue": "", "confidence": 1.0, "range": {"start": 16, "end": 18, "rawStart": 16, "rawEnd": 15}}], "sessionId": "motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047"}
hermes/intent/de.fhem:GetTime {"input": "wie spät ist es 25", "intent": {"intentName": "de.fhem:GetTime", "confidenceScore": 1.0}, "siteId": "motox", "id": null, "slots": [{"entity": "reopenVoiceInput", "value": {"kind": "Unknown", "value": "25"}, "slotName": "reopenVoiceInput", "rawValue": "", "confidence": 1.0, "range": {"start": 16, "end": 18, "rawStart": 16, "rawEnd": 15}}], "sessionId": "motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047", "customData": "manual", "asrTokens": [[{"value": "wie", "confidence": 1.0, "rangeStart": 0, "rangeEnd": 3, "time": null}, {"value": "spät", "confidence": 1.0, "rangeStart": 4, "rangeEnd": 8, "time": null}, {"value": "ist", "confidence": 1.0, "rangeStart": 9, "rangeEnd": 12, "time": null}, {"value": "es", "confidence": 1.0, "rangeStart": 13, "rangeEnd": 15, "time": null}, {"value": "25", "confidence": 1.0, "rangeStart": 16, "rangeEnd": 18, "time": null}]], "asrConfidence": 1.0, "rawInput": "wie spät ist es", "wakewordId": "manual", "lang": null}
hermes/dialogueManager/endSession {"customData": "manual","intentFilter": null,"sessionId": "motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047","siteId": "motox","text": "Es ist 12 Uhr 43"}
hermes/dialogueManager/startSession {"customData": "{\u0022confidence\u0022:1,\u0022customData\u0022:\u0022manual\u0022,\u0022input\u0022:\u0022wie spät ist es 25\u0022,\u0022intent\u0022:\u0022GetTime\u0022,\u0022lang\u0022:null,\u0022rawInput\u0022:\u0022wie spät ist es\u0022,\u0022reopenVoiceInput\u0022:\u002225\u0022,\u0022requestType\u0022:\u0022voice\u0022,\u0022sessionId\u0022:\u0022motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047\u0022,\u0022siteId\u0022:\u0022motox\u0022}","init":{"canBeEnqueued": true,"intentFilter": null,"sendIntentNotRecognized": true,"text": " Weitere Wünsche?","type": "action"},"siteId": "motox"}
hermes/dialogueManager/sessionQueued {"sessionId": "2a1187e2-2a63-4ad3-9a6b-9cfebb7677ed", "siteId": "motox", "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047\",\"siteId\":\"motox\"}"}
hermes/dialogueManager/sessionEnded {"termination": {"reason": "nominal"}, "sessionId": "motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047", "siteId": "motox", "customData": "manual"}
hermes/dialogueManager/sessionStarted {"sessionId": "2a1187e2-2a63-4ad3-9a6b-9cfebb7677ed", "siteId": "motox", "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047\",\"siteId\":\"motox\"}", "lang": null}
hermes/nlu/query {"input": "ciao", "siteId": "motox", "id": null, "intentFilter": ["de.fhem:SetColor", "de.fhem:SetColorGroup", "de.fhem:SetTimedOnOffGroup", "de.fhem:GetState", "de.fhem:GetNumeric", "de.fhem:SetOnOffGroup", "de.fhem:GetDate", "de.fhem:SetOnOff", "de.fhem:SetTimedOnOff", "de.fhem:siteId2room", "de.fhem:MediaControls", "de.fhem:Shortcuts", "de.fhem:SetScene", "de.fhem:ReSpeak", "de.fhem:SetTimer", "de.fhem:GetOnOff", "de.fhem:SetNumericGroup", "de.fhem:GetTime", "de.fhem:SetNumeric"], "sessionId": "2a1187e2-2a63-4ad3-9a6b-9cfebb7677ed", "wakewordId": "", "lang": null, "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047\",\"siteId\":\"motox\"}", "asrConfidence": 0.49159699999999995, "customEntities": null}
hermes/nlu/intentNotRecognized {"input": "ciao", "siteId": "motox", "id": null, "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-6119e0a7-f231-4789-aab6-f399e0fc8047\",\"siteId\":\"motox\"}", "sessionId": "2a1187e2-2a63-4ad3-9a6b-9cfebb7677ed"}
Interessant ist eigentlich vor allem diese Zeile:
Zitathermes/dialogueManager/startSession {"customData": "{bla}","init":{"canBeEnqueued": true,"intentFilter": null,"sendIntentNotRecognized": true,"text": " Weitere Wünsche?","type": "action"},"siteId": "motox"}
"null" bedeutet in dem Zusammenhang: Nimm den default aus configure. Genau das wollten wir aber eigentlich nicht.
Soweit also das, was nicht funktioniert hat. Ein paar kleine Code-Eingriffe später sieht das jetzt so aus:
hermes/dialogueManager/endSession {"customData": "manual","intentFilter": null,"sessionId": "motox-manual-4df43557-f6ab-4ba1-8eec-bd2fb01a785c","siteId": "motox","text": "Es ist 13 Uhr 14"}
hermes/dialogueManager/startSession {"customData": "{\u0022confidence\u0022:1,\u0022customData\u0022:\u0022manual\u0022,\u0022input\u0022:\u0022wie spät ist es 25\u0022,\u0022intent\u0022:\u0022GetTime\u0022,\u0022lang\u0022:null,\u0022rawInput\u0022:\u0022wie spät ist es\u0022,\u0022reopenVoiceInput\u0022:\u002225\u0022,\u0022requestType\u0022:\u0022voice\u0022,\u0022sessionId\u0022:\u0022motox-manual-4df43557-f6ab-4ba1-8eec-bd2fb01a785c\u0022,\u0022siteId\u0022:\u0022motox\u0022}","init":{"canBeEnqueued": true,"intentFilter":["de.fhem:SetColor","de.fhem:SetColorGroup","de.fhem:SetTimedOnOffGroup","de.fhem:GetState","de.fhem:GetNumeric","de.fhem:SetOnOffGroup","de.fhem:GetDate","de.fhem:SetOnOff","de.fhem:SetTimedOnOff","de.fhem:siteId2room","de.fhem:MediaControls","de.fhem:Shortcuts","de.fhem:SetScene","de.fhem:ReSpeak","de.fhem:SetTimer","de.fhem:GetOnOff","de.fhem:SetNumericGroup","de.fhem:GetTime","de.fhem:SetNumeric","de.fhem:CancelAction"],"sendIntentNotRecognized": true,"text": "Sonst noch was? ","type": "action"},"siteId": "motox"}
hermes/dialogueManager/sessionQueued {"sessionId": "224aecb0-9029-4272-8f06-a9084a16fe5b", "siteId": "motox", "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-4df43557-f6ab-4ba1-8eec-bd2fb01a785c\",\"siteId\":\"motox\"}"}
hermes/dialogueManager/sessionEnded {"termination": {"reason": "nominal"}, "sessionId": "motox-manual-4df43557-f6ab-4ba1-8eec-bd2fb01a785c", "siteId": "motox", "customData": "manual"}
hermes/dialogueManager/sessionStarted {"sessionId": "224aecb0-9029-4272-8f06-a9084a16fe5b", "siteId": "motox", "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-4df43557-f6ab-4ba1-8eec-bd2fb01a785c\",\"siteId\":\"motox\"}", "lang": null}
hermes/nlu/query {"input": "ciao", "siteId": "motox", "id": null, "intentFilter": ["de.fhem:SetColor", "de.fhem:SetColorGroup", "de.fhem:SetTimedOnOffGroup", "de.fhem:GetState", "de.fhem:GetNumeric", "de.fhem:SetOnOffGroup", "de.fhem:GetDate", "de.fhem:SetOnOff", "de.fhem:SetTimedOnOff", "de.fhem:siteId2room", "de.fhem:MediaControls", "de.fhem:Shortcuts", "de.fhem:SetScene", "de.fhem:ReSpeak", "de.fhem:SetTimer", "de.fhem:GetOnOff", "de.fhem:SetNumericGroup", "de.fhem:GetTime", "de.fhem:SetNumeric", "de.fhem:CancelAction"], "sessionId": "224aecb0-9029-4272-8f06-a9084a16fe5b", "wakewordId": "", "lang": null, "customData": "{\"confidence\":1,\"customData\":\"manual\",\"input\":\"wie spät ist es 25\",\"intent\":\"GetTime\",\"lang\":null,\"rawInput\":\"wie spät ist es\",\"reopenVoiceInput\":\"25\",\"requestType\":\"voice\",\"sessionId\":\"motox-manual-4df43557-f6ab-4ba1-8eec-bd2fb01a785c\",\"siteId\":\"motox\"}", "asrConfidence": 0.99798579, "customEntities": null}
hermes/nlu/intentParsed {"input": "Cancel", "intent": {"intentName": "de.fhem:CancelAction", "confidenceScore": 1.0}, "siteId": "motox", "id": null, "slots": [{"entity": "Mode", "value": {"kind": "Unknown", "value": "Cancel"}, "slotName": "Mode", "rawValue": "ciao", "confidence": 1.0, "range": {"start": 0, "end": 6, "rawStart": 0, "rawEnd": 4}}, {"entity": "closeSession", "value": {"kind": "Unknown", "value": ""}, "slotName": "closeSession", "rawValue": "", "confidence": 1.0, "range": {"start": 7, "end": 6, "rawStart": 5, "rawEnd": 4}}], "sessionId": "224aecb0-9029-4272-8f06-a9084a16fe5b"}
Aktualisierte Fassung anbei, jetzt muss ich mich aber dringlich wieder anderen Dingen zuwenden :) .
ZitatDas würde dem widersprechen, was wir bisher bei CustomIntents im Code hatten.
Genau so hatte ich das bisher als gewollt gesehen.
Zitat"null" bedeutet in dem Zusammenhang: Nimm den default aus configure
und das auch.
Ich bin auch ziemlich sicher, dass das schon immer so war - weiß ich aber nicht genau. Werde ich mal mit der Version von vor unserm Start testen.
"
null" bedeutet also jetzt: Alle existierenden Intents werden aktiv ?
Für unseren Fall
reopenVoiceInput ist
configure_DialogManager() mit
$toDisable = [qw(ConfirmAction Choice ChoiceRoom ChoiceDevice)] einfacher, um
CancelAction zu aktivieren. Oder spricht etwas dagegen
Wenn wir das mit
intentFilter machen, müssten wir erst mal alle aktiven Intents mit
/nlu/query hole und dann
CancelAction in das
intentFilter Array
pushen. Oder hast Du das an anderer Stelle schon so gemacht?
ZitatOder spricht etwas dagegen
Ja.
Ich versuche die ganze Zeit schon zu vermitteln, dass wir die nämliche neue Funktion brauchen.
Jetzt funktioniert sie prinzipiell, es ist aber noch Arbeit "drum herum" zu machen.
Ich erkläre es ggf. ein anderes Mal, für heute ist mein Rhasspy-Zeitbudget schon überschritten.
Erst mal nur wieder kurz:
M.E. brauchen wir 2 Funktionalitäten:
a) Wenn Rhasspy neu startet oder sich (möglicherweise) irgendwas in RHASSPY geändert hat, brauchen wir den "objektorientierten" JSON für den "configure"-Topic. Dieser Teil steckt in "configure_DialogManager()", und wurde bisher fälschlicherweise auch aufgerufen, wenn einer der internen Dialog-Intents (confirm, cancel, choice) angeschalten werden sollte.
b) Für "normale Dialoge" (oder eben den expliziten Start einer session mit einer bestimmten Auswahl von Intents ist "eigentlich" vorgesehen, die "flache Liste" für das enqueue/start/continue-session-Topic zu verwenden.
Genau das macht neuerdings die neue Funktion _get_sessionIntentFilter(), und die soll künftig immer dann verwendt werden, wenn es eben nicht um dauerhafte Einstellungen gehen soll (die dann aktiv sind, wenn eine Sitzung mit "null" gestartet wird), sondern "nur" um irgendwas, was sitzungsbezogen ist.
Wie man an dem bereits geposteten MQTT-Verkehr sehen kann, stört sich die nlu-Analyse überhaupt nicht daran, dass ein bestimmter Intent per default deaktiviert ist, aber eben sitzungsbezogen (oder besser: bezogen auf den aktuellen Stand in der session) grade aktiv.
Dementsprechend sind jetzt erst mal ein paar Aufräumarbeiten erforderlich, um (mindestens)
- in CancelAction die Unterscheidung reinzuknödeln, ob das "versehentlich" oder absichtlich ist, dass der Intent aktiv ist und
- den/die "falschen" Aufruf/e von configure_DialogManager() umzustellen, und
- die Weitergabe der gewünschten (aktivierten) Intents aus handleCustomIntent() zu vereinfachen (es sollte am Ende ohne "de.fhem:" übergeben werden können und das RHASSPY-intern passend ergänzt).
OK, jetzt wird das für mich klar, ich schau mir das mal an. Wird aber etwas dauern, weil ich gerade noch auf einer anderen Baustelle bin.
Übrigens hast Du gestern die beiden Änderungen in einer älteren Version gemacht (12.11.) statt in Deiner aktuellen vom 18.11. Die von gestern darf keine Basis für künftige Versionen werden, sonst muss da einiges Andere wieder eingebaut werden.
Zitat von: gregorv am 23 November 2024, 09:17:45Übrigens hast Du gestern die beiden Änderungen in einer älteren Version gemacht (12.11.) statt in Deiner aktuellen vom 18.11.
Ups, dachte, ich hätte die Fassung vom 18.11. auf github gehabt. Sind aber nur ein paar Zeilen anders, von daher sollte sich das relativ schnell übereinander schieben lassen. Aber nicht mehr heute...
OK, bis auf ein
Zitat2024.11.24 15:00:28 1: PERL WARNING: Use of uninitialized value $intents in split at ./FHEM/10_RHASSPY.pm line 1788.
scheint die Version anbei erst mal wieder zu funktionieren; dabei ist neben der Einarbeitung der Version vom 18. auch schon "configue" weitgehend umgestellt, aber noch nicht intensiv getestet, v.a. mit CustomIntents nicht. Da sollte man jetzt auch einfach z.B. "CancelAction" angeben können.
Gibt bestimmt wieder viele Feinheiten zum Nacharbeiten, und die Doku/commandref fehlt auch noch...
Zitatdie Doku/commandref fehlt auch noch...
Heute habe ich hier Beschreibung der Optionen (https://forum.fhem.de/index.php?msg=1325607) noch etws weiter gamacht.
Das wird eine Art Vorlage für die CommandRef, ist aber noch nicht komplett.
@Beta-User
Eine Frage:
Ich habe die getOptions() mit der man bestimmte Keys aus Specials oder Tweaks auslesen und an $data anhängen kann soweit, dass sie das macht, was wir derzeit brauchen.
Eigentlich müsste die in jedem Intent ausgerufen werden, damit das bei beliebigen Intents funktioniert.
Statt dessen möcht ich sie gerne an zentraler Stelle aufrufen.
In der analyzeMQTTmessage(), unmittelbar unter der Zeile:
return [$hash->{NAME}] if !_check_minimumConfidence($hash, $data);
getOptions($hash, $data); # @@@
schein es passend. Irgendwelche Einwände ?
Die getOptions() selbst habe ich so erweitert, dass sie sich $room und $device selbst aus $data holen kann:
# this will insert specified keys into $data which are stored in $device hash or tweaks hash # @@@
sub getOptions{
my $hash = shift // return;
my $data = shift // return;
my $room = shift // getRoomName($hash,$data);
my $device = shift // getDeviceByName($hash, $room, $data->{Device});
Noch eine kleine Erweiterung da drin, da sie ja ohnehin schon in $data schreibt:
Ich habe als möglichen Key noch customData erlaubt. Damit kann man auch in $data->{customData} key-values unterbringen.
Tweaks: customData=MeinKey=MeinValue oder Specials: customData:MeinKey=MeinValue
Zitat von: gregorv am 24 November 2024, 16:37:51Heute habe ich hier Beschreibung der Optionen (https://forum.fhem.de/index.php?msg=1325607) noch etws weiter gamacht.
Danke! Muss aber mal darüber nachdenken, wie und wo man das sinnvollerweise unterbringen könnte. Tendiere dazu, das in einem eigenen (Unter-Abschnitt) unterzubringen, der dann erst mal unter "Tweaks" anzusiedeln wäre (obwohl es dazu noch nichts gibt).
Zitat von: gregorv am 25 November 2024, 17:39:13Irgendwelche Einwände ?
Für den Moment erst mal keine echten "Einwände", aber vom Bauchgefühl her neige ich dazu, das (zumindest zum Teil) in die Intents zu verteilen.
Muss aber vermutlich etwas ausholen:
Letztlich geht es doch darum, zum einen die Dialogsteuerung zu ergänzen, und zum anderen ggf. fehlende Infos aus den Altdaten zu holen. Für den ersten Teil kann (!) es sinnvoll sein, das zentral zu erledigen, für den Rest _glaube_ ich, dass es sinnvoll wäre, erst mal zu checken, wie "treffsicher" die bereits vorhandenen Infos aus der nlu für sich genommen sind. Wenn wir das da schon vermengen, können wir es hinterher nicht mehr bewerten. Beispiel: Es ist kein Device angegeben, wir holen das aus customData* (so wie wir das auf den Weg gebracht hatten: einfach die Alt-$data verpacken). Plötzlich sieht der gesamte Datensatz eindeutig aus, obwohl er es vorher nicht war...
Ich neige dazu, in solchen Fällen den "confidence"-Level zu ändern und nochmal zu checken, ob das noch ausreichend ist (falls du die Testsuite "durch" hast, verstehst du vielleicht besser, wie sich das eventuell auswirkt).
Konkret müßte ich das ganze aber auch erst mal für ein paar Grenzfälle durchspielen.
Zitat von: gregorv am 25 November 2024, 17:39:13Ich habe als möglichen Key noch customData erlaubt.
*Spontan finde ich das nicht gut. "customData" sind in meiner aktuellen Gedankenwelt Infos zum vorherigen Dialog. Irgendwas in der Konfiguration hat damit (erst mal) nichts zu tun und sollte m.E. auch anders benannt werden, auch, damit man das gg. anderen Usern halbwegs transparent erklären kann.
Habe übrigens noch einen logischen Wackler in der neuen Funktion (bzw. dem Aufruf anstelle von "configure") gefunden, muss das aber auch erst noch testen.
ZitatBeispiel: Es ist kein Device angegeben, wir holen das aus customData* ...
Der Einbau an dieser Stelle schließt nicht aus, dass er auch aus einem Intent heraus aufgrufen werden kann. Dafür sind die zusätzlichen Parameter
$room und
$device gedacht. Wenn bei einem erneuten Aufruf ein Key-Value Paar dazukommt, oder ein Wert sich ändert, bleiben alle sonstigen Daten, die vorher geschrieben wurden, erhalten.
Zitat"customData" sind in meiner aktuellen Gedankenwelt Infos zum vorherigen Dialog
Ja dazu haben wir es gemacht. Es ist nach wie vor die - meine ich - einzige Möglichkeit Daten zu Rhasspy zu transportieren - falls man die dort auswerten wollte.
JensS hatte hier customData in Rhasspy auswerten (https://forum.fhem.de/index.php?msg=1322376) dazu mal eine Möglichkeit beschrieben. Einige Posts früher hatte er mal erwähnt, dass er
customData nutzt (genutzt hat) und dabei einfach einen String da reinschreibt. Wenn jemand das innerhalb vom Modul macht macht sind unsere Daten weg. Wenn ein String in
customData von der
NLU kommt (das kann z.B. mit einem CustomIntent gemacht werden), wird er derzeit gelöscht. Besser wäre, den in einen key von customData umzuwandeln, damit er erhalten bleibt. Log3... "$customData succesfully converted into \$customData->{$customData}".
Zitat von: gregorv am 25 November 2024, 22:23:33Der Einbau an dieser Stelle schließt nicht aus, dass er auch aus einem Intent heraus aufgrufen werden kann. Dafür sind die zusätzlichen Parameter $room und $device gedacht. Wenn bei einem erneuten Aufruf ein Key-Value Paar dazukommt, oder ein Wert sich ändert, bleiben alle sonstigen Daten, die vorher geschrieben wurden, erhalten.
Wie gesagt: ich habe da im Moment einfach nur ein Bauchgefühl, über Details wird man sich unterhalten müssen und können. Wann "multifunktionale" Codes sinnvoll sind und wann nicht, ist auch so eine Frage, tendenziell würde ich im Moment eher davon ausgehen, dass es einfacher ist, gleich die Trennung vorzusehen, aber wir werden sehen...
Zitat von: gregorv am 25 November 2024, 22:23:33Es ist nach wie vor die - meine ich - einzige Möglichkeit Daten zu Rhasspy zu transportieren - falls man die dort auswerten wollte.
Das mit dem Beispiel von JensS aus https://forum.fhem.de/index.php?topic=139337.msg1322321#msg1322321 ist ein guter Hinweis.
Muss mal nachschauen, dass wir sowas nicht kaputt machen, im Moment _glaube_ ich, dass wir customData nur anfassen (sollten), wenn wir nach einer abgeschlossenen Sitzung eine neue aufmachen; für den bisherigen Fall der "echten Dialoge" via "continueSession" speichern wir die Altdaten anders und verändern (hoffentlich) customData nicht.
Ob es "Altdaten" sind oder irgendwas anderes, sollte das "eval{-decode}" bereits jetzt (indirekt) aussortieren, und wir bringen eventuell was durcheinander, wenn wir da plötzlich einen Hash generieren?
Ad "zu Rhasspy transportieren": Jedenfalls so, wie wir Rhasspy nutzen, ist das nur ein Weg, uns selbst Infos zu schicken. Rhasspy (also eigentlich: die "üblicherweise genutzten Module innerhalb des Rhasspy-Universums") läßt die nach meiner bisherigen Erfahrung unverändert, wenn man da was reinschiebt (und nicht die Sitzung mit "null" startet). "customData" ist echt "user-sphere".
Zitatdass wir sowas nicht kaputt machen
so, wie es jetzt ist, kann etwas Wichtiges in
customData nur durch einen
CustomIntent gesetzt werden - ansonsten ist da nur die
wake-word-Id drin.
Erst, wenn nach
sessionEnded eine neue Session mit
sonst noch was aufgemacht wird überschreiben wir einen eventuell im
CustomIntent gesetzten String.
Dürfte also soweit keine Beeinträchtigung geben - oder kennst Du eine andere Möglichkeit
customData außerhalb vom Modul zu setzen?
Zitat von: gregorv am 26 November 2024, 16:31:16oder kennst Du eine andere Möglichkeit customData außerhalb vom Modul zu setzen
Na ja, im Prinzip kann "jeder", der da per MQTT mit dran hängt, auch beliebige Parameter mit einschleusen... Ergo: In der Theorie ja, in der Praxis dürfte das für unsere Anwendung keine Rolle spielen.
Anbei meine - leider immer noch ungetestete - aktualisierte Fassung, v.a. wegen der commandref. Aus "reopenVoiceInput" ist jetzt "reActivateVoiceInput" geworden, irgendeine Option aus dem "Text-Dialog-Modus" ist auch entfallen.
Achtung: Da der timer-Name geändert ist, sollte FHEM nach dem Austausch der Datei neu gestartet werden, ich hoffe, dass kein bug drin ist und das Modul wenigstens geladen werden kann.
(Sehr) vielleicht komme ich morgen wenigstens zu einem Kurztest und kann ggf. selbst debuggen.
So, kleines update mit einigen wenigen Fixes...
@Beta-User
kannst Du Dir das mal bitte anschauen. Ich denke das ist einfacher für den Aufruf.
Args:
$hash, $data, [qw(IntentA IntentB)],
- enable: enabled einen oder mehrere Intents (alle anderen Intents behalten ihren Status).
- disable: disabled einen oder mehrere Intents (alle anderen Intents behalten ihren Status).
- default: stellt die default Intents wieder her. (return = undef)
- prefix: ergänzt nur den Präfix (fhem.de:), bei denen, wo es noch nicht da ist
sub _set_sessionIntentFilter {
my $hash = shift // return;
my $data = shift // return;
my $intents = shift;
my $action = shift // 'default' ; # possible values: default: reset to global intents, enable: enable the intent(s) in $intents, disable: disable the intent(s) in $intents, prefix: just add prefix
my @allIntents = split m{,}xm, ReadingsVal( $hash->{NAME}, 'intents', '' );
my @sessionIntents;
for (@allIntents) {
next if $_ =~ m{ConfirmAction|CancelAction|Choice|ChoiceRoom|ChoiceDevice};
push @sessionIntents, $_ if
!defined $hash->{helper}->{tweaks} ||
!defined $hash->{helper}{tweaks}->{intentFilter} ||
!defined $hash->{helper}{tweaks}->{intentFilter}->{$_} ||
defined $hash->{helper}{tweaks}->{intentFilter}->{$_} && $hash->{helper}{tweaks}->{intentFilter}->{$_} eq 'true';
}
return undef if $action eq 'default';
my $id = qq($hash->{LANGUAGE}.$hash->{fhemId}:);
my @tmp;
my @actionIntents;
if ( ref $intents eq 'ARRAY' ) {
@tmp = @$intents;
} else {
@tmp = split m{,}xm, $intents;
}
for (@tmp) {
if ( $_ =~ m{\a${id}} ) {
push @actionIntents, $_;
} else {
push @actionIntents, ${id}.$_;
}
}
my @intentFilter;
@tmp = ();
@tmp = @{ $data->{intentFilter} } if defined $data->{intentFilter};
@tmp = @sessionIntents if !defined $data->{intentFilter};
for my $element (@tmp) {
if ( $element =~ m{$id}x ) {
push @intentFilter, $element;
} else {
push @intentFilter, ${id}.$element;
}
}
@sessionIntents = ();
if ( $action eq 'enable' ) {
for my $element (@actionIntents) {
push @intentFilter, $element unless grep { $_ eq $element } @intentFilter ;
}
@sessionIntents = @intentFilter;
}elsif ( $action eq 'disable' ) {
for my $element (@intentFilter) {
push @sessionIntents, "$element" if ! grep( /^$element$/, @actionIntents ) ;
}
}elsif ( $action eq 'prefix' ) {
for my $element (@intentFilter) {
push @sessionIntents, "$element" ;
}
}
return \@sessionIntents;
}
Für unseren Fall wäre das
_set_sessionIntentFilter($hash, $data, 'CancelAction', 'enable') )die aktuell enabled Intents holt er sich aus $data - wenn das leer ist - oder nicht existiert basieren die Ergebnisse für enable/disable auf den default Intents.
Hmmm, werde zumindest heute nicht groß Zeit finden, intensiver darüber nachzudenken, aber kurz zu
- "return undef":
a) Logik:
Warum soll man die Funktion überhaupt aufrufen, wenn man ein ganz bestimmtes Ergebnis haben will? Und warum dann vorab überhaupt irgendwelche Operationen durchführen?
b) Formal:
Schau mal in die Perl-Ecke: https://forum.fhem.de/index.php?topic=109592.0. ("unless" ist auch "bäh", und an der Stelle wollte ich bei Gelegenheit "any" einbauen...)
Sonst:
- "all" fehlt. Wäre ggf. hilfreich fürs Testen von Sätzen ;) . (ist imo jetzt in meinem Code vorbereitet, wird aber noch nicht genutzt)
- Die Idee, bei "ergänze CancelAction" auf $data zurückzugreifen finde ich gut!
Zitat- "return undef":
Jep, war noch nicht getestet - muss
return "null" sein. Und klar, nach oben.
Weiterhin
unless beseitigt,
all eingebaut und - damit es vollständig ist - noch
only damit sind nur die als arg übergebenen Intents enabled.
Neue Varianten (inl. default) getestet - wird aber weiter getestet.
sub _set_sessionIntentFilter {
my $hash = shift // return;
my $data = shift // return;
my $intents = shift;
my $action = shift // 'default' ;
# possible values: default: reset to global intents, enable: enable the intent(s) in $intents, disable: disable the intent(s) in $intents, prefix: just add prefix, all: enable all intents, only: enables only intents from arg
return "null" if $action eq 'default';
my @allIntents = split m{,}xm, ReadingsVal( $hash->{NAME}, 'intents', '' );
my @sessionIntents;
for (@allIntents) {
next if $_ =~ m{ConfirmAction|CancelAction|Choice|ChoiceRoom|ChoiceDevice};
push @sessionIntents, $_ if
!defined $hash->{helper}->{tweaks} ||
!defined $hash->{helper}{tweaks}->{intentFilter} ||
!defined $hash->{helper}{tweaks}->{intentFilter}->{$_} ||
defined $hash->{helper}{tweaks}->{intentFilter}->{$_} && $hash->{helper}{tweaks}->{intentFilter}->{$_} eq 'true';
}
if ( $action eq 'all') {
@sessionIntents = @allIntents;
}
my $id = qq($hash->{LANGUAGE}.$hash->{fhemId}:);
my @tmp;
my @actionIntents;
if ( ref $intents eq 'ARRAY' ) {
@tmp = @$intents;
} else {
@tmp = split m{,}xm, $intents;
}
for (@tmp) {
if ( $_ =~ m{\a${id}} ) {
push @actionIntents, $_;
} else {
push @actionIntents, ${id}.$_;
}
}
my @intentFilter;
@tmp = ();
@tmp = @{ $data->{intentFilter} } if defined $data->{intentFilter};
@tmp = @sessionIntents if !defined $data->{intentFilter};
for my $element (@tmp) {
if ( $element =~ m{$id}x ) {
push @intentFilter, $element;
} else {
push @intentFilter, ${id}.$element;
}
}
@sessionIntents = ();
if ( $action eq 'enable' ) {
for my $element (@actionIntents) {
push @intentFilter, $element if not grep { $_ eq $element } @intentFilter ;
}
@sessionIntents = @intentFilter;
}elsif ( $action eq 'disable' ) {
for my $element (@intentFilter) {
push @sessionIntents, "$element" if ! grep( /^$element$/, @actionIntents ) ;
}
}elsif ( $action =~ m{(prefix|all)}x ) {
for my $element (@intentFilter) {
push @sessionIntents, "$element" ;
}
}elsif ( $action eq 'only') {
@sessionIntents = @actionIntents;
}else{
return "null";
}
return \@sessionIntents;
}
Anbei meine aktuelle 10_RHASSPY.pm
Basiert auf Deiner vom 29.11. da noch kleine Korrekturen in CommandRef.
Die zwei Keys retryIntent und closeSession geändert auf lower case für den 1. Buchstaben
und einige Änderungen, die ich inzwischen gemacht hatte hauptsächlich neu getOptions() und _set_sessionIntentFilter() und geändert handleIntentSetNumeric() handleIntentNotRecognized()
Puh, anbei mal das diff.
Ich komme mal zurück auf unser "Arbeitsprogramm":
Zitat von: Beta-User am 17 Oktober 2024, 13:26:12- Das mit "resetInput" fertig machen und einchecken. Dazu müßte man m.E. die angehängten Dateien checken, ob das Zusammenspiel soweit paßt, dass bisherige User keine Probleme haben.
- Danach könnte man sich mit einem globalen (sentences-) Key für "führe den Dialog weiter" befassen. Ziel auch hier: Zwischenversion einchecken.
- Dann eventuell checken, ob man "specials" findet (Zwischenversion...)
Zumindest nach meinen bisherigen (eher wenigen!) Tests sind wir relativ nahe dran, (auch) den zweiten Punkt abhaken zu können. Ich würde weiter gerne erst mal eine "saubere" Zwischenversion haben wollen, bevor wir mit noch mehr Optionen weiter machen.
In dem diff sind einige Dinge drin, die im Sinne dieser "Sauberkeit" sind, aber auch ein paar Sachen, über die ich mir erst mal vertiefter Gedanken machen muss, und bei denen mir vermutlich auch zum Teil der Kontext fehlt (z.B.:
+ respond( $hash, $data, getResponse( $hash, defined $data->{customData}->{silentClosure} ? 'SilentClosure' : 'DefaultConfirmationTimeout' ) ); # @@@ changed to lower case
lower case ist klar, aber warum steht an der Stelle der key "eine Ebene tiefer"?
Anders gesagt: Das bekomme ich in der Fülle nicht kurzfristig verarbeitet...
PS: "all" für die IntentFilter-Anfrage brauchen wir (vermutlich) doch nicht. Wenn, dann ist das für die Satz-Testung relevant, und da ist schon "alle Intents" aktiv...
Zitatlower case ist klar, aber warum steht an der Stelle der key "eine Ebene tiefer"?
habe ich geändert, steht auch in
$dataDa war ich etwas irritiert, weil wir hier einen ähnlichen Fall haben. In der Tat geht es da aber gar nicht anders, weil von unseren alten Daten nach einem
IntentNotRecognized nur noch die
customData intakt sind.
if ( !$data->{input} || (ref $data->{customData} eq 'HASH' && defined $data->{customData}->{retryIntent} ) ) { # @@@ retry.. lower case
Ich neige dazu, dass es besser wäre, wenn wir in der
handleIntentNotRecognized() die alten Daten aus customData wieder herstellen (in $reaction kopieren) - dazu müsste dann auch der key
intentFilter in
customData drin sein. Derzeit ist es so, dass wir den nur aus
$hash->{helper}->{".delayed"}->{$data->{sessionId}}->{intentFilter} wieder herstellen können (falls das existiert).
Wenn wir nach einem
IntentNotRecognized wichtige Keys nur noch in customData haben, mag es auch an anderen Stellen klemmen.
An der Stelle habe ich auch noch etwas ergänzt. Wenn nämlich
reActivateVoiceInput zusammen mit
retryInput benutzt wird ist
CancelAction nach
IntentNotRecognized wieder
disabled und wird mit default intentFilter fortgesetzt (da gibt es ja keine oldData). Ich habe den CancelAction da neu aktiviert.
Sieht nun so aus:
if ( !$data->{input} || (ref $data->{customData} eq 'HASH' && defined $data->{customData}->{retryIntent} ) ) { # @@@ retry.. lower case
$data->{intentFilter} = $hash->{helper}->{".delayed"}->{$data->{sessionId}}->{intentFilter} if !defined $data->{intentFilter} // "null"; # @@@ NLU IntentNotRecognized does not (allways??)return intentFilter
$response = $data->{input} ? getResponse( $hash, 'RetryIntent') : 'SilentClosure';
$reaction = {
text => $response,
sendIntentNotRecognized => 'true',
intentFilter => _set_sessionIntentFilter($hash, $data, 'CancelAction', 'enable' ), <--- add CancelAction
customData => $data->{customData} #Beta-User: toJSON? # @@@ not required
Zitat von: gregorv am 01 Dezember 2024, 14:32:43Wenn wir nach einem IntentNotRecognized wichtige Keys nur noch in customData haben, mag es auch an anderen Stellen klemmen.
Wenn der intent nicht erkannt wird, gibt es imo "nichts" (also keine neuen keys), und mir ist im Moment auch nicht klar, wie der Ablauf für diese "retryIntent"-Sache genau sein soll.
Vermutlich brauchen wir da irgend eine Art von Zwischenschritt, angefangen damit, dass einfach nur "reActivateVoiceInput" aktiv wird, wenn überhaupt ein "input" da ist.
ABER:
Mir ist das im Moment insgesamt zu viel an Änderungen, ich würde gerne erst mal "nur" diese beiden in der commandref bereits (halbwegs) sauber beschriebenen Keys ("reActivateVoiceInput" und "closeSession") einchecken (und den Rest gar nicht erst zeigen). Dazu braucht es eine "saubere" .pm, mal sehen, ob wir das bis Ende der Woche hinbekommen...
Oder gibt es inhaltlich was, das ich übersehen habe und erst mal noch zwingend erforderlich wäre?
Zitatich würde gerne erst mal "nur" diese beiden in der commandref bereits ...
Die
getOptions() wäre schon noch interessant, damit man die Option
reActivateVoiceInput auch einstellen kann (statt es in jedem
sentence machen zu müssen)
Zitatwie der Ablauf für diese "retryIntent"-Sache genau sein soll
Das ist relativ simpel: Wenn ein
IntentNotRecognized kommt soll die Session offen bleiben.
Und da gibt es zwei Fälle die sich nur durch die
response unterscheiden - entweder wurde kein
input geliefert ->
silent weiter machen, ansonsten nach
wie bitte? weiter machen.
Zitat von: gregorv am 02 Dezember 2024, 13:33:22Die getOptions() wäre schon noch interessant, damit man die Option reActivateVoiceInput auch einstellen kann (statt es in jedem sentence machen zu müssen)
getOptions() ist mir im Moment noch zu diffus, und wir bekommen da nicht auf die Schnelle die Komplexität hin, die mir nach dem Motto "wenn schon, denn schon!" vorschwebt. Da jetzt aber eine (vereinfachte?) Syntax zu definieren und die dann wieder über den Haufen zu werfen, ist vermutlich für andere User kein wirklich guter Weg. Ergo geht es im Moment imo darum, das (übergangsweise) per sentence einstellen zu können oder global einen "sessionTimeout" zu definieren. Dafür gibt es schon einen key im define. Dann wäre die (künftig imo zwingend weiter bestehende) Option "closeSession" halt die einzige Möglichkeit, vorzeitig das Mikro zu schließen. Für erstes Testen sollte das ok sein und wir bekommen ggf. wenigstens Rückmeldung, wenn es irgendwo unvorhergesehenerweise klemmen sollte.
Zitat von: gregorv am 02 Dezember 2024, 13:33:22Zitatwie der Ablauf für diese "retryIntent"-Sache genau sein soll
Das ist relativ simpel: Wenn ein IntentNotRecognized kommt soll die Session offen bleiben.
Und da gibt es zwei Fälle die sich nur durch die response unterscheiden - entweder wurde kein input geliefert -> silent weiter machen, ansonsten nach wie bitte? weiter machen.
OK, bin eben auch über diese Code-Stelle gestolpert. Nach kürzerem Nachdenken jetzt ist mir aber immer noch nicht klar, wie der Zusammenhang zwischen $device (specials) oder $intent (tweak) und dieser Option sein soll. Würde es (zumindest für's erste) nicht reichen, auch hier zu schauen, ob wir entweder den globalen key "sessionTimeout" aktiviert haben oder in customData der reActivate-key finden können?
Wie gesagt: Für's erste sollte es eine einfache Möglichkeit geben, die neuen features einigermaßen ungefährdet zu nutzen, feiner einstellbar können wir das später noch machen.
Ansonsten stellt sich ggf. die Frage, ob das "not recognized" schlicht daran lag, dass ein "falscher" intentFilter aktiv war und wie man ggf. damit umgehen kann/soll. Aber das ist imo auch wieder ein Ast, den wir später betrachten sollten...
Hmmm, wenn ich das jetzt auf die Schnelle richtig zusammengepuzzelt habe, braucht es doch in der .pm hier dann nur noch eine Abgfrage in handleIntentNotRecognized()?
Also statt
if ( !$data->{input} || (ref $data->{customData} eq 'HASH' && defined $data->{customData}->{RetryIntent} ) ) {
if ( !$data->{input} || (ref $data->{customData} eq 'HASH' && defined $data->{customData}->{RetryIntent} ) || defined $hash->{sessionTimeout} ) {
bzw. (erst mal ohne "getOptions")
if ( !$data->{input} || defined $hash->{sessionTimeout} ) {
Oder übersehe ich im Moment was?
Ahh, jetzt verstehe ich Deine Anmerkung im Code, als ich vorgeschlagen hatte die closeSession Bedingung und das Auslesen eines numerischen Wertes daraus als $delay zu streichen. closeSession macht da wirklich keinen Sinn, aber wenn man an sessionTimeout denkt, passt das wieder.
Aber das ist noch etwas komplexer. Wir haben ja zwei Möglichkeiten, einen Dialog offen zu halten. Einmal über respond(), wie es in handleIntentNotRecognized() gemacht wird und außerdem über setDialogTimeout(), wie es in handleCustomIntent gemacht wird. Das hatte ich heute noch einmal probiert,ob man da nicht doch respond() einsetzen kann. - aber wegen vieler Unpässlichkeiten letztlich wieder aufgegeben.
Der große Unterschied zwischen beiden Varianten ist der, dass einmal die Alt-Daten in customData stehen und bei setDialogTimeout() im hash ($hash->{helper}->{".delayed"}->{$data->{sessionId}}->). Das mit dem customData hatten wir ja gemacht, damit die Alt-Daten in eine neue Session übertragen werden können, während das beim schlichten Verlängern einer Session nicht erforderlich ist, weil das hash erhalten bleibt (sessionId bleibt gleich).
Aber ich werde morgen (vmtl. später Vormittag) etwas detaillierter dazu antworten.
Zunächst mal zu
IntentNotRecognizedWie ich gestern abend geschrieben habe gibt es da zwei Varianten um auf die Alt-Daten (hier insbesondere
intentFilter und
retryIntent) zugreifen zu können.
Für die folgenden Szenarien ist
reActivateVoiceInput = Dauer und
retryInput = 'yes' gesetzt (ist egal was da drin steht 'true' führt aber noch zu JSON Fehler)
Zunächst mal die Szenarien:
- 1. Wake-Word -> falsches Kommando -> Session wird mit Standard-Ansage beendet:
Grund: bis dahin wurde noch nicht
respond() durchlaufen und daher ist in
customData kein
retryIntent, sondern nur die Wake-Word Id.
- 2. Wake-Word -> Geräusch -> Session bleibt offen
Grund: derzeit ist die Geräusch-Behandlung unabhängig vom Schalter
retryIntent (ist m.E. auch OK so.)
- 3. Wake-Word -> Kommando -> Response -> sonst noch, was? -> falsches Kommando oder Geräusch -> Session wird fortgesetzt mit wie bitte? oder Stille
Grund: Da
sonst noch, was? durchlaufen wurde, gibt es nun Alt-Daten in
customData und wegen
retryIntent kommt
wie bitte?Die Optionen
reActivateVoiceInput = Dauer und
retryInput = 'yes' werden - egal, ob sie im
sentence oder in
tweaks configuriert sind - immer erst gesetzt, wenn das Gesamtsystem weiß, welcher Intent behandelt werden soll. (
IntentNotRecognized ist da eine Ausnahme). Insbesondere ist das für Szenario 1. interessant, weil man das weder über
sentence noch über
getOptions ändern kann
Deine Frage, ob man die
tweak/specials nicht besser direkt da auslesen sollte, wo sie benötigt werden hat da übrigens Vorteile, weil
hash->tweaks ja unabhängig von irgendwelchen
$data Inhalten ist. In Szenario 1. könnte man damit
wie bitte? auch über
hash->tweaks->retryIntent aktivieren.
Ein Problem sehe ich aber in den ganzen Bedingungen, die bisher nur die
getOption berücksichtigen kann - außer man baut so eine Bedingungsprüfung überall, wo man es benötigt.
Ein paar Prints, die aus
handleIntentNotRecognized()Hier $data von Fall 1
2024.12.03 10:32:59 5: [RHASSPY.IF] handleIntentNotRecognized data=
$VAR1 = {
"confidence" => "0.75",
"customData" => "Roberta_de_linux_v3_0_0",
"input" => "mach das keller fenster auf",
"requestType" => "voice",
"sessionId" => "arbeitszimmer-Roberta_de_linux_v3_0_0-0bd974b3-a460-4b34-bb25-2d607ce30a18",
"siteId" => "arbeitszimmer"
};
customData hat keine Alt-Daten weder retryIntent noch intentFilter
Hier $data von Fall 2.
2024.12.03 10:34:06 5: [RHASSPY.IF] handleIntentNotRecognized data=
$VAR1 = {
"confidence" => "0.75",
"customData" => "Roberta_de_linux_v3_0_0",
"input" => "",
"requestType" => "voice",
"sessionId" => "arbeitszimmer-Roberta_de_linux_v3_0_0-bb98e1f2-f619-4519-bb88-a785649beae2",
"siteId" => "arbeitszimmer"
};
also identisch mit Fall 1 ABER unsere Sonderbehandlung wird durchlaufen und
respond() wird mit $reaction:
2024.12.03 10:34:06 5: [RHASSPY.IF] handleIntentNotRecognized reaction=
$VAR1 = {
"customData" => "Roberta_de_linux_v3_0_0",
"intentFilter" => [
"de.fhem:Shortcuts",
"de.fhem:Blinds",
"de.fhem:ReSpeak",
"de.fhem:DayNight",
"de.fhem:GetNumeric",
"de.fhem:DeviceState",
"de.fhem:SetOnOff",
"de.fhem:GetDate",
"de.fhem:GetTime",
"de.fhem:CancelAction"
],
"sendIntentNotRecognized" => "true",
"text" => "SilentClosure"
};
aufgerufen. Da kein
intentFilter verfügbar ist, (wie vermutlich immer am Anfang einer Session) werden die default Intents +
CancelAction aktiviert.
Anders sieht $data im Fall 3. (falsches Kommando) aus
2024.12.03 11:38:47 5: [RHASSPY.IF] handleIntentNotRecognized data=
$VAR1 = {
"confidence" => "0.75",
"customData" => {
"Device" => "licht",
"Value" => "on",
"confidence" => 1,
"input" => "mach das licht on",
"intent" => "SetOnOff",
"lang" => undef,
"rawInput" => "mach das licht an",
"reActivateVoiceInput" => 25,
"requestType" => "voice",
"retryIntent" => "true",
"sessionId" => "arbeitszimmer-Roberta_de_linux_v3_0_0-4e1c97ff-f091-4c6c-9a73-cca89e23a398",
"siteId" => "arbeitszimmer"
},
"input" => "mach das keller fenster auf",
"requestType" => "voice",
"sessionId" => "c7ecad5c-97c1-4525-880d-6b1e6f9aec29",
"siteId" => "arbeitszimmer"
};
Hier haben wir (und das gilt auch für alle zukünftigen
IntentNotRecognized, die während eine Sitzung noch kommen mögen) in CustomData unsere Alt-Daten.
intentFilter wieder auf default, um
CancelAction ergänzt. Da haben wir möglicherweise ein Problem. Das beim Start noch kein intentFilter da ist, ist ja OK, aber sollte durch eine vorherige Intent Behandlung ein
intentFilter gesetzt werden, geht der verloren. Hier in diesem Beispiel wird bereits früher
CancelAction (in
sonst noch was?) gesetzt, kommt in
customData aber nicht an. Solange default OK ist wird
CancelAction bei
IntentNotRecognized ja neu gesetzt, aber wenn der
intentFilter verändert wurde, wäre das nicht OK.
Hier noch
$reaction für Fall 3
2024.12.03 11:38:47 5: [RHASSPY.IF] handleIntentNotRecognized reaction=
$VAR1 = {
"customData" => {
"Device" => "licht",
"Value" => "on",
"confidence" => 1,
"input" => "mach das licht on",
"intent" => "SetOnOff",
"lang" => undef,
"rawInput" => "mach das licht an",
"reActivateVoiceInput" => 25,
"requestType" => "voice",
"retryIntent" => "true",
"sessionId" => "arbeitszimmer-Roberta_de_linux_v3_0_0-4e1c97ff-f091-4c6c-9a73-cca89e23a398",
"siteId" => "arbeitszimmer"
},
"intentFilter" => [
"de.fhem:Shortcuts",
"de.fhem:Blinds",
"de.fhem:ReSpeak",
"de.fhem:DayNight",
"de.fhem:GetNumeric",
"de.fhem:DeviceState",
"de.fhem:SetOnOff",
"de.fhem:GetDate",
"de.fhem:GetTime",
"de.fhem:CancelAction"
],
"sendIntentNotRecognized" => "true",
"text" => "waas: ??"
};
Jetzt kommt ein CustomIntent ( meine
Blinds() ) mit einem engen intentFilter. Bei einem CustomIntent wird, wie gestern Abend schon erwähnt die Session mit
setDialogTimeout() verlängert.
wieder die drei Fälle von oben (Fall 1. und 2. ist identisch - ist ja noch kein Intent bekannt)
Fall 3 sieht hier etwas anders aus:
- Wake-Word -> Kommando -> Response -> falsches Kommando oder Stille -> Session wird fortgesetzt entweder mit wie bitte? oder Stille (also kein sonst noch was?)
Die Prints sehen nun anders aus:
$data
$VAR1 = {
"confidence" => "0.75",
"customData" => {
"goAhead" => "yes",
"name" => "rollladen links",
"prevPos" => 0,
"retryIntent" => "yes",
"room" => "b\x{c3}\x{83}\x{c2}\x{bc}ro"
},
"input" => "mach das keller fenster auf",
"requestType" => "voice",
"sessionId" => "arbeitszimmer-Roberta_de_linux_v3_0_0-b1809ff1-98da-41da-9b31-b2b1553692f4",
"siteId" => "arbeitszimmer"
};
in
customData stehen nur die Daten, die ich im CustomIntent selbst anhänge
intentfilter ist nicht vorhanden, obwohl früher bereits gesetzt. Hier ist der Grund aber ein anderer. In
customData kann der nicht drin stehen, weil
sonst noch was? gar nicht durchlaufen wurde (und auch nicht soll). Da ich den aber brauche, muss ich die Alt-Daten woanders her holen. Bei einer Session, die mit
setDialogTimeout() verlängert wurde stehen die im hash (meist $old_data genannt).
Damit sieht
$reaction dann so aus
2024.12.03 12:20:13 5: [RHASSPY.IF] handleIntentNotRecognized reaction=
$VAR1 = {
"customData" => {
"goAhead" => "yes",
"name" => "rollladen links",
"prevPos" => 0,
"retryIntent" => "yes",
"room" => "b\x{c3}\x{83}\x{c2}\x{bc}ro"
},
"intentFilter" => [
"de.fhem:CancelAction",
"de.fhem:Blinds"
],
"sendIntentNotRecognized" => "true",
"text" => "wiebitte ??"
};
retryIntent kann abgefragt werden, weil ich das explizit schon in
customData übergeben hatte.
intentFilter, weil es im hash steht.
Klar könnte man auch
retryIntent aus dem hash lesen oder
intentFilter könnte als
customData schon im CustomIntent angehängt werden Aber einem User ist es sicher nur schwer vermittelbar, dass ein in einem CustomIntent falls er
intentFilter benötigt den zwei mal setzen soll (also zusätzlich auf ebene
customData)
Übrigens hier noch die hash Einträge für die Optionen tweak und specials
tweaks: "tweaks" => {
"gdt2groups" => {
"blind" => "rolll\303\244den"
},
"reActivateVoiceInput" => { <-----------
"all" => 35
},
"retryIntent" => { <-----------
"all" => "true"
}
}
und
specials "AR.Licht" => {
"alias" => "licht",
"group_specials" => {
"async_delay" => "0.5"
},
"groups" => "lampen",
"intents" => {
"GetOnOff" => {
... },
"names" => "licht,lampe,deckenlampe,deckenlicht,b\303\274ro licht",
"reActivateVoiceInput" => { <-----------
"all" => 25
},
"rooms" => "arbeitszimmer,b\303\274ro"
},
Kurze Zwischeninfo: Habe mir auch den Code-Teil handleIntentNotRecognized() nochmal angesehen. "Ursprünglich" stand der (aktuell im svn noch deaktivierte) "Stille"-Verarbeitungscode noch am Ende der Funktion, und das war kein (kompletter) Zufall: Wenn ich was deaktiviere bzw. auskommentiere, dann in der Regel an der Stelle, an der ich ihn zuletzt aktiv hatte.
Beim Drübersehen jetzt bin ich dann auch wieder drauf gestoßen, dass wir imo die beiden "Grundtypen" (nach) "continue" (Fall 1) und "reactivate" auch an der Stelle getrennt betrachten müssen. "continue" hat immer Altdaten ($old_data), "reactivate" dann, wenn schon mal eine "Schleife" durch war (Fälle 2a bzw. 2b).
Bei "continue" haben wir dann auch in der Regel einen eingeschränkten Satz an aktivierten Intents, der eigentlich schon dadurch aktiviert bleiben sollte, dass wir innerhalb der session bleiben und keinen neuen setzen. (to be verified)
Fall 2a sollte eigentlich auf "default+cancel" stehen (und auch (immer?) so bleiben?!?), bei 2b stellt sich die Frage, ob wir den verstandenen Inhalt (also nicht "leer") in eine erweiterte Erkennung geben sollten, bei zusätzlich aktivierten Intents (welche?!?).
Imo sollte die Abarbeitung im Code im Großen und Ganzen auch (wieder) dieser Logik folgen, es kann allerdings sein, dass "Stille" der einfachste Fall ist und wir das tatsächlich vorab "abfrühstücken" können (für Stille brauchen wir imo auch keine Fehlerausgabe, das war ein "Abfallprodukt" aus der Test-Suite), mit der aktuellen Logik haben wir also die Ausnahme zur Regel gemacht :o .
2b ist der komplexeste Fall, und im Moment sehe ich auch keine einfache Vorgabe, wie man damit umgehen sollte (eventuell: Erkennung ohne Vorgabe starten und dann eine Bestätigung dieser Aktion anfordern?) Ist aber was für einen Tag...
Und bevor wir irgendwelche "tweaks" oder so einbauen, müssen wir imo diese paar Grundtypen sauber haben, was m.E. am einfachsten geht, wenn wir "retryIntent" einfach global (de-) aktivieren (können). Was spricht dagegen, das (erst mal) über den globalen def-key "sessionTimeout" zu machen?
(Das ist übrigens nicht (direkt) abhängig von "reActivate", das aber über den obigen key halt im Moment zusätzlich auch als "global aktiv" gilt).
(Ich gehe davon aus, dass jede zusätzliche Option schlicht die Komplexität verdoppelt, deswegen will ich mich erst mal nicht mit mehr Optionen belasten als unbedingt nötig...)
der eigentlich schon dadurch aktiviert bleiben sollte, dass wir innerhalb der session bleiben
habe ich gerade geprüft, stimmt. Bei continous bleibt der intentFilter erhalten, wenn der key intentFilter in $reaction nicht mitgegeben wird.
nein, funktioniert doch nicht. Ich hatte offenbar kein reload gemacht, als ich das getestet habe. Wenn intentFilter in $reaction nicht mitgegeben wird, gilt der default.
Was spricht dagegen, das (erst mal) über den globalen def-key "sessionTimeout" zu machen?
werde ich mal testen. Bisher war ich nicht sicher, ob man sich darauf verlassen kann, ob der nicht im Verlauf einer Session entstehen oder verschwinden kann. Speziell für einen CustomIntent ist das ja kein muss-key - wenn sessionTimeout nicht übergeben wird, gilt ja default. Ich werde das mal überdenken.
Bisher habe ich sessionTimeout nur als Variable betrachtet, um die Dauer eines Dialoges setzen zu können, als key weiß ich zwar, dass der existieren kann ($hash->{helper}->{msgDialog}->{config}->{sessionTimeout}), hatte damit bisher aber nichts zu tun und habe ich auch noch nie im hash gesehen. Für mich sah das eher so aus, als gehört das zum Bereich text-dialog. Darf man den so einfach erstellen? - es fehlt auch die gesamte Kette davor (->{msgDialog}->{config}). Falls ja, wäre es sicher auch sinnvoll die anderen Parameter da hinein zu schreiben - hat ja alles irgendwie mit config für einen Dialog zu tun.
bei 2b stellt sich die Frage, ob wir den verstandenen Inhalt (also nicht "leer")
Diesen Codeteil habe ich zwar schon gesehen, aber nicht so angeschaut, dass ich das beurteilen könnte, wie man das hier nutzen kann. Mir scheint aber, dass diese Funktion ohnehin schon aktiv ist. Wenn ich nicht ein völlig blödsinniges Kommando gebe, sondern z.B. mach das Bürolicht im Flur an dann kommt kein wie bitte? sondern ein Hinweis über widersprüchliche Angaben.
2024.12.04 10:40:12 5: Parsed value: flur for key: Room
2024.12.04 10:40:12 5: Parsed value: SetOnOff for key: intent
2024.12.04 10:40:12 5: Parsed value: mach das büro licht in dem flur an for key: rawInput
2024.12.04 10:40:12 5: Parsed value: on for key: Value
2024.12.04 10:40:12 5: Parsed value: büro licht for key: Device
...
2024.12.04 10:40:12 5: Device selected (by hash, using only name): AR.Licht
2024.12.04 10:40:12 5: [RHASSPY.IF] getOptions called
2024.12.04 10:40:12 5: handleIntentSetOnOff called
2024.12.04 10:40:12 5: [RHASSPY.IF] getNeedsClarification called, regex is büro licht
2024.12.04 10:40:12 5: [RHASSPY.IF] resetting Timer: RHASSPY.IF_63ce7db8-8753-4091-8d30-54b817592b52
Es kommt in diesem Fall nicht zu einem IntentNotRecognized
Aber dazu habe ich ohnehin noch eine Frage - wozu gibt es den Key .ENABLED ?
"customData" => {
".ENABLED" => [
"Choice",
"CancelAction"
],
ich hätte statt .ENABLED eher intentFilter erwartet.
Fall 2a sollte eigentlich auf "default+cancel" stehen (und auch (immer?) so bleiben?!?)
Das wäre OK. Es gibt da FÜR und WIDER. Bei versehentlicher Aktivierung ausgelöst durch z.B. Fernseher, Radio, ... ist es sicher besser wenn die Session beendet wird, bei Betrachtung einer sonst noch etwas?-Session ist es nicht ganz konsistent wenn die wie bitte? Rückfrage nicht schon am Anfang funktioniert.
Vorab mal zu "sessionTimeout": Das ist vorrangig mal ein globaler key, der im RHASSPY-define angegeben werden kann - der fehlt nur "oben" in der Liste, habe ich grade festgestellt... "Unten" (Parameters) ist er als "experimental" gelistet.
RHASSPY liest den im Moment nur aus, ändert da aber im laufenden Betrieb nichts.
Es gibt dann für "textbasierte" Dialoge noch die Option, den separat zu setzen, aber ausgewertet wird dieser Teil dann nur, wenn auf der anderen Seite auch ein Messenger oä. vorhanden ist; dieser Wert geht dann (und nur dann) vor.
(Zu guter letzt kann man dann auch noch per Reading (pro siteId) dynamisch reagieren, wenn man will...)
Zitat von: gregorv am 04 Dezember 2024, 11:58:28Diesen Codeteil habe ich zwar schon gesehen, aber nicht so angeschaut, dass ich das beurteilen könnte, wie man das hier nutzen kann. Mir scheint aber, dass diese Funktion ohnehin schon aktiv ist. Wenn ich nicht ein völlig blödsinniges Kommando gebe, sondern z.B. mach das Bürolicht im Flur an dann kommt kein wie bitte? sondern ein Hinweis über widersprüchliche Angaben.
Muss vielleicht ausholen:
Wenn ein Intent erkannt wird, wird auch versucht, den auszuführen, und wenn das nicht klappt, gibt es eben eine Rückfrage (über die Qualität bzw. Verbesserungsmöglichkeiten können wir uns ggf. auch noch austauschen).
"Not recognized" kommt ggf. aber dann zustande, wenn der gesprochene (und von der STT-Komponente korrekt "decodierte") Satz zwar bekannt ist, aber grade der zugehörige Intent nicht aktiviert ist.
Letztlich bin ich bei meinen "damaligen" Versuchen dann irgendwo in dieser Ecke etwas ratlos gestrandet, und da es auch niemanden gab, der an einer (wie auch immer gearteten) Lösung dieser Konstellation Interesse gezeigt hatte, habe ich dann eben den Teil auskommentiert, soweit er nicht für die "Basisunktionalität" erforderlich war.
Aus diesen Versuchen stammt übrigens auch der "experimental-key" "sessionTimeout" (für voice-Dialoge)...
Zitat von: gregorv am 04 Dezember 2024, 11:58:28wozu gibt es den Key .ENABLED ?
Gute Frage. Müßte auch im Code schauen, ob der überhaupt irgendwo benutzt wird, und wenn ja, ob man das nicht anderweitig "besser" lösen könnte (es kommt ja der aktive intentFilter von Rhasspy zurück, allerdings "individualisiert").
Ist vermutlich (!) auch noch ein Rest von dem damaligen "Versuchs-Code" und könnte eventuell entfallen, wenn wir uns an die Bereinigung machen.
Diese ganzen "Unsauberkeiten" (?) sind übrigens mit der Grund, warum ich gerne zuerst einen funktionalen Basis-Code hätte, bevor wir anfangen, das wieder komplizierter zu machen ;D ...
Zitat von: gregorv am 04 Dezember 2024, 11:58:28Das wäre OK. Es gibt da FÜR und WIDER. Bei versehentlicher Aktivierung ausgelöst durch z.B. Fernseher, Radio, ... ist es sicher besser wenn die Session beendet wird, bei Betrachtung einer sonst noch etwas?-Session ist es nicht ganz konsistent wenn die wie bitte? Rückfrage nicht schon am Anfang funktioniert.
? Ich glaube, das habe ich noch nicht durchschaut, wie es gemeint ist.
Habe jetzt nochmal die "not recognized"-Funktion durchgesehen, und würde mal folgendes in den Raum werfen (wie üblich: ungetestet...):
sub handleIntentNotRecognized {
my $hash = shift // return;
my $data = shift // return;
Log3( $hash, 5, "[$hash->{NAME}] handleIntentNotRecognized called, input is $data->{input}" );
my $reaction;
if ( !$data->{input} ) { # just listened to "noise" or silence
$data->{requestType} //= 'voice'; # we need "voice" to make sure we reopen the microphone if session is voice type
$reaction = {
text => 'SilentClosure',
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
return respond( $hash, $data, $reaction); # continue session silently
}
my $identity = qq($data->{sessionId});
my $siteId = $hash->{siteId};
my $msgdev = (split m{_${siteId}_}x, $identity,3)[0];
if ($msgdev && $msgdev ne $identity) {
$data->{text} = getResponse( $hash, 'NoIntentRecognized' );
return handleTtsMsgDialog($hash,$data);
}
#return $hash->{NAME} if !$hash->{experimental};
my $data_old = $hash->{helper}{'.delayed'}->{$identity};
if ( !defined $data_old ) {
return handleCustomIntent($hash, 'intentNotRecognized', $data) if defined $hash->{helper}{custom} && defined $hash->{helper}{custom}{intentNotRecognized};
my $entry = qq([$data->{siteId}] $data->{input});
readingsSingleUpdate($hash, 'intentNotRecognized', $entry, 1);
#Beta-User: silence chuncks or single words, might later be configurable
#if ( !defined $data->{input} || length($data->{input}) < 12 ) {
#if ( !$data->{input} || (ref $data->{customData} eq 'HASH' && defined $data->{customData}->{RetryIntent} ) ) {
if ( defined $hash->{sessionTimeout} ) { #this might be the place to extend for "getOption" keys
$data->{requestType} //= 'voice'; # we need "voice" to make sure we reopen the microphone if session is voice type
$reaction = {
text => getResponse( $hash, 'RetryIntent'),
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
return respond( $hash, $data, $reaction); # continue listening
}
$data->{requestType} //= 'text';
return respond( $hash, $data, getResponse( $hash, 'NoIntentRecognized' ));
}
#$data_old is given, continue dialogue
$data->{requestType} //= $data_old->{requestType} // 'voice'; # required, otherwise session open but no voice input possible
$reaction = {
text => getResponse( $hash, 'RetryIntent'),
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
respond( $hash, $data, $reaction); # keep session open and continue listening
return $hash->{NAME};
}
Ich habe die
handleIntentNotRecognized() mal mit Deiner Version getauscht. Zwei Dinge fallen auf:
Bei einem CustomIntent kommt nach
IntentNotRecognized die Nachfrage
wie bitte?. Ab da ist der
intentfilter auf default.
Bei einem normalen Intent kommt nach
IntentNotRecognized nur die Default Ansage und die Session ist beendet (das
wie bitte? da, ist erst als Zukunftsfeature geplant, wenn ich Dich richtig verstanden habe?).
(in beiden Fällen
reActivateVoiceInput und
retryIntent aktiv und
IntentNotRecognized mit
input)
Bei einem CustomIntent oder normalen Intent wird
IntentNotRecognized ohne
input still übergangen und die Session bleibt offen. Der
intentFilter ist anschließend default (u.A.
CancelAction weg)
Ich teste aber noch weiter und verfolge erst mal den Weg, der im Code durchlaufen wird.
Zitat? Ich glaube, das habe ich noch nicht durchschaut, wie es gemeint ist.
Das galt für den Fall, dass
reActivateVoiceInput und
retryIntent aktiv sind.
retryIntent bewirkt ja (zumindest in meiner Variante), dass bei einem
IntentNotRecognized mit
input != leer nicht die Default Ansage kommt, sondern die Nachfrage
wie bitte?.
Wenn die Aktivierung (erkanntes
Wake-Word) unbeabsichtigt ist, weil das
Wake-Word zufällig z.B. aus dem Radio erkannt wurde, ist es besser, wenn ein anschließendes
IntentNotRecognized zum Ende der Session führt. Das ist aber nicht konsistent mit einer
sonst noch was? Session, weil da erst NACH dem ersten
sonst noch was? ein
IntentNotRecognized das
wie bitte? auslöst. Somit gibt es beim Start der Session (also nach
Wake-Word) ein anderes Verhalten auf
intentNotRecognized, als nach einem
sonst noch was?. Ich neige aber dazu, dieses unterschiedliche Verhalten als Feature zu verkaufen, weil man sonst eventuell länger dauernde Unterhaltungen zwischen Radio und Rhasspy provoziert.
ZitatVorab mal zu "sessionTimeout": Das ist vorrangig mal ein globaler key ...
Wenn ich das richtig verstehe, wäre das nicht ganz störungssicher, weil es sein könnte, dass jemand den Key gesetzt hat.
Du hattest ja vorgeschlagen mit
sessionTimeout den Key
retryIntent abzulösen - oder irre ich mich da?
Wenn das so wäre ist bei RHASSPY Instanzen wo der Key
sessionTimeout im
define steht nach einem Update plötzlich
wie bitte? aktiv.
Willst Du
reActivateVoiceInput auch da unterbringen ?
Oder bringe ich da jetzt was ganz Anderes durcheinander? :(
Zitat von: gregorv am 04 Dezember 2024, 17:01:50Wenn ich das richtig verstehe, wäre das nicht ganz störungssicher, weil es sein könnte, dass jemand den Key gesetzt hat.
Dieser key ist ausdrücklich als "experimental" gekennzeichnet, also: Wer den gesetzt hat, weiß, auf was er sich eingelassen hat...
Im Ernst: Das ist niemand, und wenn, geht ja nicht gleich die Welt unter :P .
Zitat von: gregorv am 04 Dezember 2024, 17:01:50Du hattest ja vorgeschlagen mit sessionTimeout den Key retryIntent abzulösen - oder irre ich mich da?
Mein Vorschlag war eher so zu verstehen: Bitte lass uns nicht gleich in irgendwelche Details bei tweaks/specials gehen, sondern den dafür ursprünglich vorgesehenen experimentellen Weg der Aktivierung einer neuen Funktionalität via define gehen. Dieser "alte" Weg sah schon immer die Antwort aus "retryIntent" vor. Wir "lösen" also nichts "ab", sondern machen erst mal fertig, was schon vorgesehen war, und dann sehen wir weiter...
Klarer jetzt?
Zitat von: gregorv am 04 Dezember 2024, 16:25:36Zwei Dinge fallen auf:
Das muss ich mir bei Gelegenheit mal im MQTT-Verlauf ansehen; vielleicht kann ich morgen noch eine (kommentierte) Version liefern, aus der besser zu erkennen ist, was im Ergebnis bewirkt werden soll.
Es kann sein, dass die "alte" Logik auch deswegen nicht mehr (ohne weiteres) funktioniert, weil wir in respond() einige Änderungen vorgenommen haben. Das Ganze ist mir aber für heute abend zu komplex.
Zitat von: gregorv am 04 Dezember 2024, 17:01:50Willst Du reActivateVoiceInput auch da unterbringen ?
Dazu fehlte noch eine Antwort - Jein!
Also nochmal anders erklärt: Hinter "sessionTimeout" steckt "ursprünglich" der Gedanke, jede Sitzung schlicht immer weiterzuführen, bis der darin konfigurierte timeout (nach dem letzten Input) abgelaufen ist. Also immer dann, wenn entweder ein sinnvoller input kommt (also ein Intent erkannt) oder eben "irgendwas" gesagt wurde (not recognized). "Eigentlich" auch, wenn fälschlicherweise "silence" (vor Timerablauf) erkannt wurde (da aber - soweit ich mich entsinne) dann ohne Verlängerung/Erneuerung des timeouts.
Das ganze war/ist aber wackelig und unrund (deswegen gibt es m.E. niemanden, der den Key aktiv hat ;) ), und zwischenzeitlich glaube ich auch, die Ursache gefunden zu haben, nämlich das mit dem zu häufigen "configure" für den DialogManager/intentFilter (siehe meine bisherigen Ausführen dazu, die jetzt vielleicht doch noch irgendwie verständlich werden).
Ergo geht es jetzt darum, diese "pauschale Option" zum Laufen zu bringen, bevor wir anfangen, für eine differenziertere Funktionalität mehr/andere Keys "zu verstreuen".
Und die "korrekte" Funktionalität von "handleIntentNotRecognized()" ist m.E. das verbleibende Bausteinchen, bis wir hinter das Thema einen Haken machen können. Jetzt schaue ich mal, ob ich das mit den Kommentaren im Code hinbekomme :) .
PS:
Um das nachzuvollziehen vielleicht den alten Code unter https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/10_RHASSPY.pm?rev=27324 - dort "sessionTimeout" bemühen. Da führt ein gesetzter Key immer zu $delay und damit zu einer "falschen" Erneuerung des intentFilter (mit enable/disable-Angaben).
Zitat von: gregorv am 04 Dezember 2024, 11:58:28Bei continous bleibt der intentFilter erhalten, wenn der key intentFilter in $reaction nicht mitgegeben wird.
Habe eben erst die Änderung dieses Teils (nach "stimmt gar nicht") gesehen...
Vermutlich ist das schon die Ursache, warum der Code nicht funktioniert hat (=> eventuell reicht es, einfach überall vor respond auch noch den intentFilter aus $data nach $sendData zu kopieren), aber wie dem auch sei, hier mal meine kommentierte Fassung (ohne Übertrag):
sub handleIntentNotRecognized {
my $hash = shift // return;
my $data = shift // return;
Log3( $hash, 5, "[$hash->{NAME}] handleIntentNotRecognized called, input is $data->{input}" );
my $response;
# Teil 1: Umgang mit "silence", also gar kein {input}
# Klären:
# a) (https://forum.fhem.de/index.php?msg=1326892): müssen wir zwingend immer den intentFilter mit angeben?
#b) brauchen wir eine Unterscheidungen nach "unmittelbar" nach (nur) dem Wakeword (b1), einer "echten Sitzung" (Info oder Bestätigung nachgefordert, b2), oder einfach einer "weiterlaufenden" Sitzung (b3, nach (z.B.) reActivateVoiceInput)
if ( !$data->{input} ) { # just listened to "noise" or silence
$data->{requestType} //= 'voice'; # we need "voice" to make sure we reopen the microphone if session is voice type
$response = {
text => 'SilentClosure',
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
return respond( $hash, $data, $response); # continue session silently
} # Ende Teil 1
#text-basierte Dialoge, nicht unser aktuelles Thema
my $identity = qq($data->{sessionId});
my $siteId = $hash->{siteId};
my $msgdev = (split m{_${siteId}_}x, $identity,3)[0];
if ($msgdev && $msgdev ne $identity) {
$data->{text} = getResponse( $hash, 'NoIntentRecognized' );
return handleTtsMsgDialog($hash,$data);
}
# Ende text-basierte Dialoge
#return $hash->{NAME} if !$hash->{experimental};
my $data_old = $hash->{helper}{'.delayed'}->{$identity};
# Teil 2: {input} ist da, es wurde also (vermutlich) was gesagt, aber der input wurde nicht als Intent erkannt (z.B. falscher Filter?)
# Teil 2a - wir sind in einer neuen (2a1) oder "weiterlaufenden", aber "sauberen" Sitzung (z.B. nach reActivateVoiceInput, 2a2)
if ( !defined $data_old ) {
# user-definierte Sonderbehandlung hat Vorrang und ist abschließend!
return handleCustomIntent($hash, 'intentNotRecognized', $data) if defined $hash->{helper}{custom} && defined $hash->{helper}{custom}{intentNotRecognized};
my $entry = qq([$data->{siteId}] $data->{input});
readingsSingleUpdate($hash, 'intentNotRecognized', $entry, 1);
if ( defined $hash->{sessionTimeout} ) { #this might be the place to extend for "getOption" keys
$data->{requestType} //= 'voice'; # we need "voice" to make sure we reopen the microphone if session is voice type
$response = {
text => getResponse( $hash, 'RetryIntent'),
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
return respond( $hash, $data, $response); # continue listening, as $response is HASH type
}
$data->{requestType} //= 'text';
return respond( $hash, $data, getResponse( $hash, 'NoIntentRecognized' ));
}
# Teil 2b - irgendwas hat mit der weiteren Eingabe nicht geklappt (könnte v.a. auch ein ganz anderer Satz gewesen sein - es ist häufig (choice!) nur ein sehr eingeschränkter intentFilter aktiv!)
#$data_old is given, continue dialogue
$data->{requestType} //= $data_old->{requestType} // 'voice'; # required, otherwise session open but no voice input possible
$response = {
text => getResponse( $hash, 'RetryIntent'),
sendIntentNotRecognized => 'true',
customData => $data->{customData}
};
respond( $hash, $data, $response); # keep session open and continue listening
return $hash->{NAME};
}
neue
handleIntentNotRecognized() getestet
wieder
reActivateVoiceInput und
retryIntent gesetzt
kein
input normaler Intent:
- Wake-Word -> Geräusch -> silent continue -> Kommando -> Bestätigung -> sonst noch was? -> fertig -> OK -> Session closed <---- OK
- Wake-Word -> Kommando -> Bestätigung -> sonst noch was? -> Geräusch -> silent continue -> fertig -> nicht verstanden -> Session closed <---- falsche Ansage (vmtl. intentFilter weg)
customIntent
- Wake-Word -> Geräusch -> silent continue -> Kommando -> Bestätigung -> fertig -> OK <---- OK
- Wake-Word -> Kommando -> Bestätigung -> Geräusch -> silent continue -> fertig -> wie bitte? <---- intentFilter weg
->> wenn Nach einem erfolgreichen Kommando der Fall
kein Input auftritt, ist
CancelAction nicht mehr möglich.
input != ''
normaler Intent:
- Wake-Word -> falsches Kommando -> nicht verstanden -> Session closed <---- OK
- Wake-Word -> Kommando -> Bestätigung -> sonst noch was? -> falsches Kommando -> nicht verstanden -> Session closed <---- wenn retryIntent aktiv ist, sollte hier wenn wie bitte? kommen und die Session fortgeführt werden - künftig erst??) AUSSERDEM kommt nach sessionEnded später noch (18 sec im Log) endSession ersteres bei IntentNotRecognized, letzteres wenn der sessionTimeout abgelaufen ist - siehe IntentNotRecognized sessionTimeout.txt. Falls in der Zwischenzeit (zwischen sessionEnded und endSession) nach erneutem Wake-Word andere Kommandos gesprochen werden, ist das Ergebnis nicht nicht vorhersehbar z.B. nach einem deutlich gesprochenen Kommando wird der Choice-Intent gestartet - zumindest nach der Ansage zu urteilen - reagiert aber auf nein mit wie bitte? oder nach nicht verstanden kommt noch mal sonst noch was?
customIntent
- Wake-Word -> falsches Kommando -> nicht verstanden -> Session closed <---- OK
- Wake-Word -> Kommando -> Bestätigung -> falsches Kommando -> wie bitte? -> fertig -> wie bitte? <---- intentFilter weg
->> wenn Nach einem erfolgreichen Kommando der Fall
input != '' auftritt, ist
CancelAction nicht mehr möglich.
Zitat von: gregorv am 05 Dezember 2024, 17:25:19wieder reActivateVoiceInput und retryIntent gesetzt
Das sind nach wie vor nicht die erwarteten Keys. Erwartet wird sessionTimeout im define, sonst wird die Sitzung halt einfach nach "not recognized" geschlossen.
Ansonsten ist der Code nur kommentiert, nicht irgendwie gegenüber gestern geändert. Hatte kurz angefangen mit Testen, bin aber nicht besonders weit gekommen (außer, dass die erwartete "normale" Funktionalität (=> immer neue Sitzung aufmachen) scheinbar funktioniert).
Eventuell morgen mehr, jetzt ist mein Zeitbudget dafür aus...
@Beta-User
nach längeren Tests mit der letzten Version, insbesondere auch nur mit global sessionTimeout habe ich es nicht geschafft, die Dialoge so zu bekommen, wie ich es eingentlich haben will. Nach einer Session, die nur verlängert wurde z.B. um einen Rolladen anhalten, etwas höher/tiefer zu fahren sollte nicht automatisch eine neue Session starten. Auch der noch laufende Timer nach IntentNotRecognized stört und führt zu unvorhersehbaren Resultaten, wenn vor dem Ablauf eine neue Session gestartet wird. Nach vielen Versuchen das doch zum Laufen zu bringen, habe ich mich heute entschlossen, wieder auf meiner Version vom 30.11. aufzusetzen. Da funktioniert nun alles so, wie ich es mir vorgestellt hatte - zumindest solange man nicht den globalen sessionTimeout aktiviert.
die Dokumentation habe ich auf den neuesten Stand gebracht Doku (https://forum.fhem.de/index.php?msg=1325607).
Hmmm, Danke erst mal für die Rückmeldung zu deinen Tests.
Ich bin die Tage leider selbst nicht wirklich zum Testen gekommen, hatte wenn, dann aber auch wieder einige seltsame Effekte.
Nach wie vor glaube ich aber, dass es möglich sein sollte, das erst mal global zufriedenstellend zum Laufen zu bekommen (vermutlich nicht so granular, wie du das im Moment hast), denn ich gehe nach wie vor davon aus, dass die Probleme eigentlich von einem grundlegenden Verständnisproblem (meinerseits) in der Interaktion zwischen Rhasspy und RHASSPY herkommen. Das muss erst sauber sein, bevor man wieder komplizierter werden darf.
Ich hoffe, am WE irgendwann sowas wie einen Vergleich unserer Versionen hinzubekommen und/oder vielleicht sogar die Probleme doch testweise besser nachvollziehen zu können, aber versprechen kann ich nichts, habe zu viel andere dringendere Baustellen...
Trotzdem eine Frage:
Zitat von: gregorv am 12 Dezember 2024, 17:31:27Nach einer Session, die nur verlängert wurde z.B. um einen Rolladen anhalten, etwas höher/tiefer zu fahren sollte nicht automatisch eine neue Session starten.
Warum nicht?
Meine Denke dazu: Der ursprüngliche Befehl ist abgearbeitet. Was danach kommt, könnte alles sein. Die Schwierigkeit liegt ggf. "nur" darin, festzustellen, was der "Bezugspunkt" des Sprechenden ist. Das ist ggf. in der Tat eine größere (programmiertechnische (und konfigurationsmäßige?)) Herausforderung.
Klar könnte man das anders wollen und dann übergangsweise nur bestimmte Intents zulassen, aber dann ist man wirklich bei CustomIntent. Oder übersehe ich was?
Inwieweit der laufende Timer in Konflikt kommt mit einer neuen session ist mir auch noch nicht klar, aber das werde ich vermutlich selbst sehen, wenn ich (endlich) mal in Ruhe zum Testen komme.
(Vielleicht kannst du noch schreiben, in welche Richtung du was modifiziert hattest, wir brauchen ja nicht doppelt dieselben Fehler machen...)