diff --git a/fhem/contrib/RHASSPY/10_RHASSPY.pm b/fhem/contrib/RHASSPY/10_RHASSPY.pm
index 40af5befd..440e6a2cc 100644
--- a/fhem/contrib/RHASSPY/10_RHASSPY.pm
+++ b/fhem/contrib/RHASSPY/10_RHASSPY.pm
@@ -42,16 +42,14 @@ use List::Util 1.45 qw(max min uniq);
use Scalar::Util qw(looks_like_number);
use Time::HiRes qw(gettimeofday);
use POSIX qw(strftime);
-#use Data::Dumper;
use FHEM::Core::Timer::Register qw(:ALL);
#use FHEM::Meta;
sub ::RHASSPY_Initialize { goto &Initialize }
-#Beta-User: no GefFn defined...?
my %gets = (
- version => q{},
- status => q{}
+ test_file => [],
+ test_sentence => []
);
my %sets = (
@@ -259,10 +257,11 @@ BEGIN {
HttpUtils_NonblockingGet
FmtDateTime
makeReadingName
- FileRead
+ FileRead FileWrite
getAllSets
notifyRegexpChanged setNotifyDev
deviceEvents
+ asyncOutput
trim
) )
};
@@ -288,6 +287,7 @@ sub Initialize {
$hash->{DeleteFn} = \&Delete;
#$hash->{RenameFn} = \&Rename;
$hash->{SetFn} = \&Set;
+ $hash->{GetFn} = \&Get;
$hash->{AttrFn} = \&Attr;
$hash->{AttrList} = "IODev rhasspyIntents:textField-long rhasspyShortcuts:textField-long rhasspyTweaks:textField-long response:textField-long rhasspyHotwords:textField-long rhasspyMsgDialog:textField-long rhasspySTT:textField-long forceNEXT:0,1 disable:0,1 disabledForIntervals languageFile " . $readingFnAttributes; #rhasspyTTS:textField-long
$hash->{Match} = q{.*};
@@ -320,7 +320,7 @@ sub Define {
$hash->{defaultRoom} = $defaultRoom;
my $language = $h->{language} // shift @{$anon} // lc AttrVal('global','language','en');
- $hash->{MODULE_VERSION} = '0.5.20';
+ $hash->{MODULE_VERSION} = '0.5.22';
$hash->{baseUrl} = $Rhasspy;
initialize_Language($hash, $language) if !defined $hash->{LANGUAGE} || $hash->{LANGUAGE} ne $language;
$hash->{LANGUAGE} = $language;
@@ -596,6 +596,67 @@ sub Set {
return;
}
+
+sub Get {
+ my $hash = shift;
+ my $anon = shift;
+ my $h = shift;
+
+ my $name = shift @{$anon};
+ my $command = shift @{$anon} // q{};
+ my @values = @{$anon};
+ return "Unknown argument $command, choose one of "
+ . join(q{ }, map {
+ @{$gets{$_}} ? $_
+ .q{:}
+ .join q{,}, @{$gets{$_}} : $_} sort keys %gets)
+
+ if !defined $gets{$command};
+
+ if ($command eq 'test_file') {
+ return 'provide a filename' if !@values;
+ if ( $values[0] ne 'stop' && !defined $hash->{testline} ) {
+ if($hash->{CL}) {
+ my $start = gettimeofday();
+ my $tHash = { hash=>$hash, CL=>$hash->{CL}, reading=> 'testResult', start=>$start};
+ $hash->{asyncGet} = $tHash;
+ InternalTimer(gettimeofday()+4, sub {
+ asyncOutput($tHash->{CL}, "Test file $values[0] is initiated. See if internal 'testline' is rising and check testResult reading later");
+ delete($hash->{asyncGet});
+ }, $tHash, 0);
+ }
+ return testmode_start($hash, $values[0]);
+ }
+ }
+
+ if ($command eq 'test_sentence') {
+ return 'provide a sentence' if !@values;
+ if ( !defined $hash->{testline} ) {
+ if($hash->{CL}) {
+ my $start = gettimeofday();
+ my $tHash = { hash=>$hash, CL=>$hash->{CL}, reading=> 'testResult', start=>$start};
+ $hash->{asyncGet} = $tHash;
+ InternalTimer(gettimeofday()+4, sub {
+ asyncOutput($tHash->{CL}, "Timeout for test sentence - most likely this is no problem, check testResult reading later, but your system seems to be rather slow...");
+ delete($hash->{asyncGet});
+ }, $tHash, 0);
+ }
+ my $test = join q{ }, @values;
+ $hash->{testline} = 0;
+ $hash->{helper}->{test}->{content} = [$test];
+ $hash->{helper}->{test}->{filename} = 'none';
+ return testmode_next($hash);
+ }
+ }
+
+ delete $hash->{testline};
+ delete $hash->{helper}->{test};
+ readingsSingleUpdate($hash,'testResult','Test mode stopped (might have been running already)',1);
+ return 'Test mode stopped (might have been running already)';
+}
+
+
+
# Attribute setzen / löschen
sub Attr {
my $command = shift;
@@ -2585,6 +2646,125 @@ sub sayFinished {
}
+#Make globally available to allow later use by other functions, esp. handleIntentConfirmAction
+my $dispatchFns = {
+ Shortcuts => \&handleIntentShortcuts,
+ SetOnOff => \&handleIntentSetOnOff,
+ SetOnOffGroup => \&handleIntentSetOnOffGroup,
+ SetTimedOnOff => \&handleIntentSetTimedOnOff,
+ SetTimedOnOffGroup => \&handleIntentSetTimedOnOffGroup,
+ GetOnOff => \&handleIntentGetOnOff,
+ SetNumeric => \&handleIntentSetNumeric,
+ SetNumericGroup => \&handleIntentSetNumericGroup,
+ GetNumeric => \&handleIntentGetNumeric,
+ GetState => \&handleIntentGetState,
+ MediaControls => \&handleIntentMediaControls,
+ MediaChannels => \&handleIntentMediaChannels,
+ SetColor => \&handleIntentSetColor,
+ SetColorGroup => \&handleIntentSetColorGroup,
+ SetScene => \&handleIntentSetScene,
+ GetTime => \&handleIntentGetTime,
+ GetDate => \&handleIntentGetDate,
+ SetTimer => \&handleIntentSetTimer,
+ ConfirmAction => \&handleIntentConfirmAction,
+ CancelAction => \&handleIntentCancelAction,
+ ChoiceRoom => \&handleIntentChoiceRoom,
+ ChoiceDevice => \&handleIntentChoiceDevice,
+ MsgDialog => \&handleIntentMsgDialog,
+ ReSpeak => \&handleIntentReSpeak
+};
+
+
+#reference: https://forum.fhem.de/index.php/topic,124952.msg1213902.html#msg1213902
+sub testmode_start {
+ my $hash = shift // return;
+ my $file = shift // return;
+
+ my ($ret, @content) = FileRead( { FileName => $file, ForceType => 'file' } );
+ return $ret if $ret;
+ return 'file contains no content!' if !@content;
+ $hash->{testline} = 0;
+ $hash->{helper}->{test}->{content} = \@content;
+ $hash->{helper}->{test}->{filename} = $file;
+ return testmode_next($hash);
+}
+
+sub testmode_next {
+ my $hash = shift // return;
+
+ my $line = $hash->{helper}->{test}->{content}->[$hash->{testline}];
+ if ( !$line || $line =~ m{\A\s*[#]}x || $line =~ m{\A\s*\z}x) {
+ $line //= '';
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] = "$line";
+ $hash->{testline}++;
+ return testmode_next($hash) if $hash->{testline} <= @{$hash->{helper}->{test}->{content}};
+ }
+
+ if ( $hash->{testline} < @{$hash->{helper}->{test}->{content}} ) {
+ my @ca_strings = split m{,}, ReadingsVal($hash->{NAME},'intents','');
+ my $sendData = {
+ input => $line,
+ sessionId => "$hash->{siteId}_$hash->{testline}_testmode",
+ id => "$hash->{siteId}_$hash->{testline}",
+ siteId => $hash->{siteId},
+ intentFilter => [@ca_strings]
+ };
+
+ my $json = _toCleanJSON($sendData);
+ return IOWrite($hash, 'publish', qq{hermes/nlu/query $json});
+ }
+
+ my $filename = $hash->{helper}->{test}->{filename};
+ $filename =~ s{[.]txt\z}{}i;
+ $filename = "${filename}_result.txt";
+
+ my $result = $hash->{helper}->{test}->{passed} // 0;
+ my $fails = $hash->{helper}->{test}->{notRecogn} // 0;
+ $result = "tested $result sentences, failed: $fails.";
+
+ if ( $filename ne 'none_result.txt' ) {
+ FileWrite({ FileName => $filename, ForceType => 'file' }, @{$hash->{helper}->{test}->{result}} );
+ $result .= " See $filename for detailed results."
+ } else {
+ $result = $fails ? 'Test failed, ' : 'Test ok, ';
+ $result .= "result is: $hash->{helper}->{test}->{result}->[0]"
+ }
+ readingsSingleUpdate($hash,'testResult',$result,1);
+ if( $hash->{asyncGet} && $hash->{asyncGet}{reading} eq 'testResult' ) {
+ my $duration = sprintf( "%.2f", (gettimeofday() - $hash->{asyncGet}{start})*1);
+ RemoveInternalTimer($hash->{asyncGet});
+ asyncOutput($hash->{asyncGet}{CL}, "test(s) passed successfully. Summary: $result, duration: $duration s");
+ delete($hash->{asyncGet});
+ }
+ delete $hash->{testline};
+ delete $hash->{helper}->{test};
+ return;
+}
+
+sub testmode_parse {
+ my $hash = shift // return;
+ my $intent = shift // return;
+ my $data = shift // return;
+
+ my $line = $hash->{helper}->{test}->{content}->[$hash->{testline}];
+ my $result;
+ $hash->{helper}->{test}->{passed}++;
+ if ( $intent eq 'intentNotRecognized' ) {
+ $result = $line;
+ $hash->{helper}->{test}->{notRecogn}++;
+ } else {
+ my $json = toJSON($data);
+ $result = "$line => $intent $json";
+ }
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] = $result;
+ if (ref $dispatchFns->{$intent} eq 'CODE' && $intent =~m{\AGetOnOff|GetNumeric|GetState|GetTime|GetDate\z}) {
+ $result = $dispatchFns->{$intent}->($hash, $data);
+ return;
+ }
+ $hash->{testline}++;
+ return testmode_next($hash);
+}
+
sub RHASSPY_msgDialogTimeout {
my $fnHash = shift // return;
my $hash = $fnHash->{HASH} // $fnHash;
@@ -2673,7 +2853,6 @@ sub msgDialog_progress {
my $json = _toCleanJSON($sendData);
return IOWrite($hash, 'publish', qq{hermes/nlu/query $json});
- return;
}
sub msgDialog_respond {
@@ -2857,36 +3036,6 @@ sub updateLastIntentReadings {
return;
}
-
-#Make globally available to allow later use by other functions, esp. handleIntentConfirmAction
-my $dispatchFns = {
- Shortcuts => \&handleIntentShortcuts,
- SetOnOff => \&handleIntentSetOnOff,
- SetOnOffGroup => \&handleIntentSetOnOffGroup,
- SetTimedOnOff => \&handleIntentSetTimedOnOff,
- SetTimedOnOffGroup => \&handleIntentSetTimedOnOffGroup,
- GetOnOff => \&handleIntentGetOnOff,
- SetNumeric => \&handleIntentSetNumeric,
- SetNumericGroup => \&handleIntentSetNumericGroup,
- GetNumeric => \&handleIntentGetNumeric,
- GetState => \&handleIntentGetState,
- MediaControls => \&handleIntentMediaControls,
- MediaChannels => \&handleIntentMediaChannels,
- SetColor => \&handleIntentSetColor,
- SetColorGroup => \&handleIntentSetColorGroup,
- SetScene => \&handleIntentSetScene,
- GetTime => \&handleIntentGetTime,
- GetDate => \&handleIntentGetDate,
- SetTimer => \&handleIntentSetTimer,
- ConfirmAction => \&handleIntentConfirmAction,
- CancelAction => \&handleIntentCancelAction,
- ChoiceRoom => \&handleIntentChoiceRoom,
- ChoiceDevice => \&handleIntentChoiceDevice,
- MsgDialog => \&handleIntentMsgDialog,
- ReSpeak => \&handleIntentReSpeak
-};
-
-
# Daten vom MQTT Modul empfangen -> Device und Room ersetzen, dann erneut an NLU übergeben
sub analyzeMQTTmessage {
my $hash = shift;# // return;
@@ -2954,6 +3103,7 @@ sub analyzeMQTTmessage {
}
if ($topic =~ m{\Ahermes/intent/.*[:_]SetMute}x && defined $siteId) {
+ return testmode_parse($hash, 'SetMute', $data) if defined $hash->{testline};
$type = $message =~ m{${fhemId}.textCommand}x ? 'text' : 'voice';
$data->{requestType} = $type;
@@ -3001,10 +3151,13 @@ sub analyzeMQTTmessage {
}
if ($topic =~ m{\Ahermes/nlu/intentNotRecognized}x && defined $siteId) {
+ return testmode_parse($hash, 'intentNotRecognized', $data) if defined $hash->{testline};
handleIntentNotRecognized($hash, $data) if $hash->{experimental};
return;
}
+ return testmode_parse($hash, $data->{intent}, $data) if defined $hash->{testline};
+
my $command = $data->{input};
$type = $message =~ m{${fhemId}.textCommand}x ? 'text' : 'voice';
$data->{requestType} = $type;
@@ -3042,6 +3195,12 @@ sub respond {
my $topic = shift // q{endSession};
my $delay = shift // ReadingsNum($hash->{NAME}, "sessionTimeout_$data->{siteId}", $hash->{sessionTimeout});
+ if ( defined $hash->{testline} ) {
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] .= " => Response: $response";
+ $hash->{testline}++;
+ return testmode_next($hash);
+ }
+
my $type = $data->{requestType} // return;
my $sendData;
@@ -5225,45 +5384,18 @@ __END__
=begin ToDo
-# Farben:
- Warum die Abfrage nach rgb? if ( defined $data->{Colortemp} && defined $mapping->{rgb} && looks_like_number($data->{Colortemp}) ) {
- Gibt auch Lampen, die können nur ct (Beta-User: unklare Frage: der fragliche Zweig wird nur bei "falschem ct" angesteuert, ansonsten wird schon vorher "nativ" ct verwendet)
-
-# Custom Intents
- - Bei Verwendung des Dialouges wenn man keine Antwort spricht, bricht Rhasspy ab. Die voice response "Tut mir leid, da hat etwas zu lange gedauert" wird
- also gar nicht ausgegeben und: (Beta-User: klingt nach "silent cancelation", grade keine Idee).
-
- PERL WARNING: Use of uninitialized value $cmd in pattern match (m//) at fhem.pl line 5868. (Beta-User: nicht mehr zuordenbar)
-
-# Sonstiges, siehe insbes. https://forum.fhem.de/index.php/topic,119447.msg1148832.html#msg1148832
-- kein "match in room" bei GetNumeric
-- "kind" und wie man es füllen könnte (mehr Dialoge)
-- Bestätigungsdialoge - weitere Anwendungsfelder
-- gDT: mehr und bessere mappings?
-- Farbe und Farbtemperatur (fast fertig?)
-- Hat man in einem Raum einen Satelliten aber kein Device mit der siteId/Raum, kann man den Satelliten bei z.B. dem Timer nicht ansprechen, weil der Raum nicht in den Slots ist.
- Irgendwie müssen wir die neue siteId in den Slot Rooms bringen (erl. mit extrarooms?)
-
-# Parameter-Check für define? Anregung DrBasch aus https://forum.fhem.de/index.php/topic,119447.msg1157700.html#msg1157700 (erl.)
-
-# Doku zu den "üblichen Formaten" (z.B. JSON-Keywords beginnen mit Großbuchstaben)? (erl. (?))
-
-=end ToDo
-
-=begin ToClarify
-
-#defaultRoom (JensS):
-- überhaupt erforderlich?
-- Schreibweise: RHASSPY ist raus, Rhasspy scheint der überkommene Raumname für die devspec zu sein => ist erst mal weiter beides drin
+# Continous mode? (Wackelig, mehr oder weniger ungetestet...)
# GetTimer implementieren?
https://forum.fhem.de/index.php/topic,113180.msg1130139.html#msg1130139
-# Wetterdurchsage
-Ist möglich. Dazu hatte ich einen rudimentären Intent in diesem Thread erstellt. Müsste halt nur erweitert werden.
-https://forum.fhem.de/index.php/topic,113180.msg1130754.html#msg1130754 (evtl. ersetzt durch STATE-Abfrage/gDT info?)
+# Rückmeldung zu den AMAD.*-Schnittstellen
+- v.a. auch kontinuierliche Dialoge/Rückfragen, wann Input aufmachen
-=end ToClarify
+# auto-training
+Tests/Rückmeldungen fehlen bisher
+
+=end ToDo
=encoding utf8
=item device
@@ -5445,7 +5577,21 @@ After changing something relevant within FHEM for either the data structure in
Activate a satellite for voice input. siteId, hotword and modelId may be provided (either in order of appearance or as named arguments), otherwise some defaults will be used. +
Activate a satellite for voice input. siteId, hotword and modelId may be provided (either in order of appearance or as named arguments), otherwise some defaults will be used.
+Note: To get test results, RHASSPY's siteId has to be configured for intent recognition in Rhasspy as well.
+Checks the provided text file. Content will be sent to Rhasspy NLU for recognition (line by line), result will be written to the file '<input without ending.txt>_result.txt'. "stop" as filename will stop test mode if sth. goes wrong. No actions will be derived while test mode is active.
+Checks the provided sentence for recognition by Rhasspy NLU. No actions will be derived upon detected content.
Babble_DoIt()
function.
allowed=AMADDev_A
filterFromBabble=tell rhasspy
- <AMAD-device>=wakeword=alexa sessionTimeout=20