CustomIntent mit Dialog

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

Vorheriges Thema - Nächstes Thema

gregorv

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.

gregorv

#16
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.

Beta-User

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...
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

gregorv

#18
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

gregorv

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

gregorv

#20
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.

Beta-User

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!!!
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

gregorv

@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)

Beta-User

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?
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

gregorv

|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.IFsehe.
Gruß
Gregor


Beta-User

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.
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

gregorv

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' :)

Beta-User

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 ;) .
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

gregorv

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.

Beta-User

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...
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors