From b8977d3d660fd2d68b07aeabb724e6133aae6f1a Mon Sep 17 00:00:00 2001
From: Beta-User Send WAV file to Rhasspy. Examples: Voice output over TTS. Example: Send a text command to Rhasspy. Example: Sets volume of given siteId between 0 and 1 (float) Example: Creates a new - or overwrites an existing slot - in Rhasspy 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. Exports a "classical" rhasspyMapping attribute value for the provided device. You may find this usefull to adopt that further to your individual needs. Checks the provided sentence for recognition by Rhasspy NLU. No actions will be derived upon detected content. 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 commands will be executed towards FHEM devices while test mode is active. Checks the provided sentence for recognition by Rhasspy NLU. No commands to be executed as well. By default, RHASSPY will use a minimum confidence level of 0.66, otherwise no command will be executed. You may change this globally (key: default) or more granular for each intent specified.
' : q{\n};
+
+ my $mapping = $hash->{helper}{devicemap}{devices}{$device}{intents};
+ my $result;
+
+ for my $key ( keys %{$mapping} ) {
+ my $map = $mapping->{$key};
+ my @tokens;
+ if ( defined $mapping->{$key}->{$key} ) {
+ $map = $mapping->{$key}->{$key};
+ delete $map->{type};
+ $result .= $nl if $result;
+ $result .= "${key}:";
+ @tokens = ();
+ for my $skey ( keys %{$map} ) {
+ push @tokens, "${skey}=$map->{$skey}";
+ }
+ $result .= join q{,}, @tokens;
+ } else {
+ for my $skey ( keys %{$map} ) {
+ $result .= $nl if $result;
+ $result .= "${key}:";
+ @tokens = ();
+ for my $sskey ( keys %{$map->{$skey}} ) {
+ push @tokens, "${sskey}=$map->{$skey}->{$sskey}";
+ }
+ $result .= join q{,}, @tokens;
+ }
+ }
+ }
+ return $result;
+}
+
# Cmd von Attribut mit dem Format value=cmd pro Zeile lesen
sub getKeyValFromAttr {
@@ -2334,6 +2427,10 @@ sub analyzeAndRunCmd {
if ($cmd =~ m{\A\s*\{.*\}\s*\z}x) { #escaping closing bracket for editor only
# CMD ausführen
Log3($hash->{NAME}, 5, "$cmd is a perl command");
+ if ( defined $hash->{testline} ) {
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] .= " => Perl: $cmd";
+ return;
+ }
return perlExecute($hash, $device, $cmd, $val,$siteId);
}
@@ -2361,16 +2458,23 @@ sub analyzeAndRunCmd {
}
# FHEM Command oder CommandChain
elsif (defined $cmds{ (split m{\s+}x, $cmd)[0] }) {
- #my @test = split q{ }, $cmd;
Log3($hash->{NAME}, 5, "$cmd is a FHEM command");
+ if ( defined $hash->{testline} ) {
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] .= " => Command(s): $cmd";
+ return;
+ }
$error = AnalyzeCommandChain($hash, $cmd);
$returnVal = (split m{\s+}x, $cmd)[1];
}
# Soll Command auf anderes Device umgelenkt werden?
elsif ($cmd =~ m{:}x) {
$cmd =~ s{:}{ }x;
- $cmd = qq($cmd $val) if defined($val);
+ $cmd = qq($cmd $val) if defined $val;
Log3($hash->{NAME}, 5, "$cmd redirects to another device");
+ if ( defined $hash->{testline} ) {
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] .= " => Redirected command: $cmd";
+ return;
+ }
$error = AnalyzeCommand($hash, "set $cmd");
$returnVal = (split q{ }, $cmd)[1];
}
@@ -2379,7 +2483,7 @@ sub analyzeAndRunCmd {
$cmd = qq($device $cmd);
$cmd = qq($cmd $val) if defined $val;
Log3($hash->{NAME}, 5, "$cmd is a normal command");
- $error = AnalyzeCommand($hash, "set $cmd");
+ $error = _AnalyzeCommand($hash, "set $cmd");
$returnVal = (split q{ }, $cmd)[1];
}
Log3($hash->{NAME}, 1, $_) if defined $error;
@@ -2456,7 +2560,7 @@ sub parseJSONPayload {
# Standard-Keys auslesen
($data->{intent} = $decoded->{intent}{intentName}) =~ s{\A.*.:}{}x if exists $decoded->{intent}{intentName};
- $data->{probability} = $decoded->{intent}{confidenceScore} if exists $decoded->{intent}{confidenceScore};
+ $data->{confidence} = $decoded->{intent}{confidenceScore} // 0.75;# if exists $decoded->{intent}{confidenceScore};
for my $key (qw(sessionId siteId input rawInput customData lang)) {
$data->{$key} = $decoded->{$key} if exists $decoded->{$key};
}
@@ -2693,9 +2797,11 @@ 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) {
+ if ( !$line || $line =~ m{\A\s*[#]}x || $line =~ m{\A\s*\z}x || $line =~ m{\A\s*(?:DIALOGUE|WAKEWORD)[:]}x ) {
$line //= '';
$hash->{helper}->{test}->{result}->[$hash->{testline}] = "$line";
+ $hash->{helper}->{test}->{isInDialogue} = 1 if $line =~ m{\A\s*DIALOGUE[:](?!END)}x;
+ delete $hash->{helper}->{test}->{isInDialogue} if $line =~ m{\A\s*DIALOGUE[:]END}x;
$hash->{testline}++;
return testmode_next($hash) if $hash->{testline} <= @{$hash->{helper}->{test}->{content}};
}
@@ -2720,11 +2826,14 @@ sub testmode_next {
my $result = $hash->{helper}->{test}->{passed} // 0;
my $fails = $hash->{helper}->{test}->{notRecogn} // 0;
- $result = "tested $result sentences, failed: $fails.";
+ my $failsInDialogue = $hash->{helper}->{test}->{notRecognInDialogue} // 0;
+ $result = "tested $result sentences, failed total: $fails, amongst these in dialogues: $failsInDialogue.";
if ( $filename ne 'none_result.txt' ) {
+ my $duration = '';
+ $duration = sprintf( " Testing time: %.2f seconds.", (gettimeofday() - $hash->{asyncGet}{start})*1) if $hash->{asyncGet} && $hash->{asyncGet}{reading} eq 'testResult';
FileWrite({ FileName => $filename, ForceType => 'file' }, @{$hash->{helper}->{test}->{result}} );
- $result .= " See $filename for detailed results."
+ $result .= "$duration See $filename for detailed results."
} else {
$result = $fails ? 'Test failed, ' : 'Test ok, ';
$result .= "result is: $hash->{helper}->{test}->{result}->[0]"
@@ -2752,12 +2861,21 @@ sub testmode_parse {
if ( $intent eq 'intentNotRecognized' ) {
$result = $line;
$hash->{helper}->{test}->{notRecogn}++;
+ $hash->{helper}->{test}->{notRecognInDialogue}++ if defined $hash->{helper}->{test}->{isInDialogue};
} else {
my $json = toJSON($data);
+ $line = "$line => Confidence not sufficient!" if !_check_minimumConfidence($hash, $data, 1);
$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}) {
+ if (ref $dispatchFns->{$intent} eq 'CODE' && $intent =~m{\ASetOnOffGroup|SetColorGroup|SetNumericGroup|SetTimedOnOffGroup\z}) {
+ my $devices = getDevicesByGroup($hash, $data);
+ $result = ref $devices ne 'HASH' || !keys %{$devices} ?
+ q{can't identify any device in group and room}
+ : join q{,}, keys %{$devices};
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] .= " => Devices in group and room: $result";
+ } elsif (ref $dispatchFns->{$intent} eq 'CODE' && $intent =~m{\AGetOnOff|GetNumeric|GetState|GetTime|GetDate|MediaControls|SetNumeric|SetOnOff|SetTimedOnOff|SetScene|SetColor\z}) {
+ #missing: MediaChannels SetTimer
$result = $dispatchFns->{$intent}->($hash, $data);
return;
}
@@ -2765,6 +2883,17 @@ sub testmode_parse {
return testmode_next($hash);
}
+sub _isUnexpectedInTestMode {
+ my $hash = shift // return;
+ my $data = shift // return;
+
+ return if !defined $hash->{testline};
+ $hash->{helper}->{test}->{result}->[$hash->{testline}] .= " => Unexpected call of $data->{intent} routine!";
+ $hash->{testline}++;
+ return 1;
+}
+
+
sub RHASSPY_msgDialogTimeout {
my $fnHash = shift // return;
my $hash = $fnHash->{HASH} // $fnHash;
@@ -2988,7 +3117,6 @@ sub ttsDialog_progress {
my $json = _toCleanJSON($sendData);
return IOWrite($hash, 'publish', qq{hermes/nlu/query $json});
- return;
}
sub ttsDialog_respond {
@@ -3166,6 +3294,8 @@ sub analyzeMQTTmessage {
# update Readings
updateLastIntentReadings($hash, $topic,$data);
+ return [$hash->{NAME}] if !_check_minimumConfidence($hash, $data);
+
# Passenden Intent-Handler aufrufen
if (ref $dispatchFns->{$intent} eq 'CODE') {
$device = $dispatchFns->{$intent}->($hash, $data);
@@ -3191,7 +3321,7 @@ sub analyzeMQTTmessage {
sub respond {
my $hash = shift // return;
my $data = shift // return;
- my $response = shift // return;
+ my $response = shift // getResponse( $hash, 'NoValidResponse' );
my $topic = shift // q{endSession};
my $delay = shift // ReadingsNum($hash->{NAME}, "sessionTimeout_$data->{siteId}", $hash->{sessionTimeout});
@@ -3406,7 +3536,7 @@ sub updateSlots {
my $overwrite = defined $tweaks && defined $tweaks->{overwrite_all} ? $tweaks->{useGenericAttrs}->{overwrite_all} : 'true';
$url = qq{/api/slots?overwrite_all=$overwrite};
- my @gdts = (qw(switch light media blind thermostat thermometer lock contact motion presence));
+ my @gdts = (qw(switch light media blind thermostat thermometer lock contact motion presence info));
my @aliases = ();
my @mainrooms = ();
@@ -3650,6 +3780,29 @@ sub RHASSPY_ParseHttpResponse {
return;
}
+sub _check_minimumConfidence {
+ my $hash = shift // return;
+ my $data = shift;
+ my $noResponse = shift;
+
+ my $intent = $data->{intent};
+ #check minimum confidence levels
+ my $minConf = 0.66;
+ if ( defined $hash->{helper}{tweaks}{confidenceMin} ) {
+ $minConf = $hash->{helper}{tweaks}{confidenceMin}->{$intent} // $hash->{helper}{tweaks}{confidenceMin}->{default} // $minConf;
+ }
+ if ( $minConf > $data->{confidence} ) {
+ return if $noResponse;
+ my $probability = _round($data->{confidence}*10)/10;
+ my $response = getResponse( $hash, 'NoMinConfidence' );
+ $response =_shuffle_answer($response);
+ $response =~ s{(\$\w+)}{$1}eegx;
+ respond( $hash, $data, $response );
+ return;
+ }
+ return 1;
+}
+
sub handleHotwordDetection {
my $hash = shift // return;
my $hotword = shift // return;
@@ -3810,7 +3963,7 @@ sub handleIntentShortcuts {
Log3($hash->{NAME}, 5, "FHEM shortcut identified: $cmd, device name is $name");
$cmd = _replace($hash, $cmd, \%specials);
$response = _replace($hash, $response, \%specials);
- AnalyzeCommand($hash, $cmd);
+ _AnalyzeCommand($hash, $cmd);
}
$response = _ReplaceReadingsVal( $hash, $response );
respond( $hash, $data, $response );
@@ -3874,6 +4027,7 @@ sub handleIntentSetOnOffGroup {
return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetOnOffGroup' );
my $devices = getDevicesByGroup($hash, $data);
+ return testmode_next($hash) if _isUnexpectedInTestMode($hash, $data);
#see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference
my @devlist = sort {
@@ -4002,6 +4156,7 @@ sub handleIntentSetTimedOnOffGroup {
return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetTimedOnOffGroup' );
my $devices = getDevicesByGroup($hash, $data);
+ return testmode_next($hash) if _isUnexpectedInTestMode($hash, $data);
#see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference
my @devlist = sort {
@@ -4083,41 +4238,30 @@ sub handleIntentSetTimedOnOffGroup {
sub handleIntentGetOnOff {
my $hash = shift // return;
my $data = shift // return;
- my $device;
- my $response;
Log3($hash->{NAME}, 5, "handleIntentGetOnOff called");
- # Device AND Status must exist
- if ( exists $data->{Device} && exists $data->{State} ) {
- my $room = getRoomName($hash, $data);
- $device = getDeviceByName($hash, $room, $data->{Device});
- my $deviceName = $data->{Device};
- my $mapping;
- $mapping = getMapping($hash, $device, 'GetOnOff') if defined $device;
- my $status = $data->{State};
+ return respond( $hash, $data, getResponse($hash, 'NoValidData') ) if !defined $data->{State} || !defined $data->{Device};
- # Mapping found?
- if ( defined $mapping ) {
- # Device on or off?
- my $value = _getOnOffState($hash, $device, $mapping);
+ my $response;
- # Define reponse
- if ( defined $mapping->{response} ) {
- $response = _getValue($hash, $device, _shuffle_answer($mapping->{response}), $value, $room);
- $response //= _shuffle_answer($response);
- }
- else {
- my $stateResponseType = $internal_mappings->{stateResponseType}->{$status};
- $response = _shuffle_answer($hash->{helper}{lng}->{stateResponses}{$stateResponseType}->{$value});
- $response =~ s{(\$\w+)}{$1}eegx;
- }
- }
+ my $room = getRoomName($hash, $data);
+ my $device = getDeviceByName($hash, $room, $data->{Device}) // return respond( $hash, $data, getResponse($hash, 'NoDeviceFound') );
+
+ my $deviceName = $data->{Device};
+ my $mapping = getMapping($hash, $device, 'GetOnOff') // return respond( $hash, $data, getResponse($hash, 'NoMappingFound') );
+
+ my $value = _getOnOffState($hash, $device, $mapping);
+
+ # Define reponse
+ if ( defined $mapping->{response} ) {
+ $response = _getValue($hash, $device, _shuffle_answer($mapping->{response}), $value, $room);
+ } else {
+ my $stateResponseType = $internal_mappings->{stateResponseType}->{$data->{State}};
+ $response = _shuffle_answer($hash->{helper}{lng}->{stateResponses}{$stateResponseType}->{$value});
+ $response =~ s{(\$\w+)}{$1}eegx;
}
- # Send response
- $response //= getResponse($hash, 'DefaultError');
- respond( $hash, $data, $response );
- return $device;
+ return respond( $hash, $data, $response );
}
@@ -4149,6 +4293,7 @@ sub handleIntentSetNumericGroup {
return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetNumericGroup' );
my $devices = getDevicesByGroup($hash, $data);
+ return testmode_next($hash) if _isUnexpectedInTestMode($hash, $data);
#see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference
my @devlist = sort {
@@ -4214,6 +4359,7 @@ sub handleIntentSetNumeric {
my $type = $data->{Type};
if ( !defined $type && defined $change ){
$type = $internal_mappings->{Change}->{$change}->{Type};
+ $data->{Type} = $type if defined $type;
}
my $value = $data->{Value};
my $room = getRoomName($hash, $data);
@@ -4226,10 +4372,26 @@ sub handleIntentSetNumeric {
$device =
getActiveDeviceForIntentAndType($hash, $room, 'SetNumeric', $type)
// return respond( $hash, $data, getResponse( $hash, 'NoActiveMediaDevice') );
+ } else {
+ $device = getDeviceByIntentAndType($hash, $room, 'SetNumeric', $type);
}
return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !defined $device;
+ #more than one device
+ if ( ref $device eq 'ARRAY' ) {
+ #until now: only extended test code
+ my $first = $device->[0];
+ my $response = $device->[1];
+ my $all = $device->[2];
+ my $choice = $device->[3];
+ $data->{customData} = $all;
+ my $toActivate = $choice eq 'RequestChoiceDevice' ? [qw(ChoiceDevice CancelAction)] : [qw(ChoiceRoom CancelAction)];
+ $device = $first;
+ Log3($hash->{NAME}, 5, "More than one device possible, response is $response, first is $first, all are $all, type is $choice");
+ return setDialogTimeout($hash, $data, _getDialogueTimeout($hash), $response, $toActivate);
+ }
+
my $mapping = getMapping($hash, $device, 'SetNumeric', $type);
if ( !defined $mapping ) {
@@ -4376,7 +4538,7 @@ sub handleIntentGetNumeric {
// return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) );
#more than one device
- if (ref $device eq 'ARRAY') {
+ if ( ref $device eq 'ARRAY' ) {
#until now: only extended test code
my $first = $device->[0];
my $response = $device->[1];
@@ -4455,15 +4617,61 @@ sub handleIntentGetNumeric {
sub handleIntentGetState {
my $hash = shift // return;
my $data = shift // return;
- my $device = $data->{Device} // return respond( $hash, $data, getResponse($hash, 'NoValidData'));
+ my $device = $data->{Device} // q{RHASSPY};
my $response;
Log3($hash->{NAME}, 5, 'handleIntentGetState called');
my $room = getRoomName($hash, $data);
+
+ my $type = $data->{Type} // $data->{type};
+ if ($device eq 'RHASSPY') {
+ $type //= 'generic';
+ return respond( $hash, $data, getResponse($hash, 'NoValidData')) if $type !~ m{\Ageneric|control|info|scenes|rooms\z};
+ $response = getResponse( $hash, 'getRHASSPYOptions', $type );
+ my $roomNames = '';
+ if ( $type eq 'rooms' ) {
+ my @rooms = getAllRhasspyMainRooms($hash);
+ $roomNames = _array2andString( $hash, \@rooms);
+ }
+
+ my @names; my @scenes;
+ my @intents = qw(SetNumeric SetOnOff GetNumeric GetOnOff MediaControls GetState SetScene);
+ @intents = [] if $type eq 'rooms';
+ @intents = qw(GetState GetNumeric) if $type eq 'info';
+ @intents = qw(SetScene) if $type eq 'scenes';
+
+ my @devsInRoom = values %{$hash->{helper}{devicemap}{rhasspyRooms}{$room}};
+ return respond( $hash, $data, getResponse($hash, 'NoDeviceFound')) if !@devsInRoom;
+ @devsInRoom = get_unique(\@devsInRoom);
+
+ for my $intent (@intents) {
+ for my $dev (@devsInRoom) {
+ next if !defined $hash->{helper}{devicemap}{devices}{$dev}->{intents}->{$intent};
+ push @names, $hash->{helper}{devicemap}{devices}{$dev}->{alias};
+ if ($intent eq 'SetScene') {
+ @scenes = (@scenes, (values %{$hash->{helper}{devicemap}{devices}{$dev}{intents}{SetScene}->{SetScene}}));
+ }
+ }
+ }
+
+ return respond( $hash, $data, getResponse($hash, 'NoDeviceFound')) if !@names;
+
+ @names = uniq(@names);
+ @scenes = uniq(@scenes) if @scenes;
+
+ my $deviceNames = _array2andString( $hash, \@names );
+ my $sceneNames = !@scenes ? '' : _array2andString( $hash, \@scenes );
+ $response =~ s{(\$\w+)}{$1}eegx;
+ return respond( $hash, $data, $response);
+ }
+
my $deviceName = $device;
+ my $intent = 'GetState';
+
$device = getDeviceByName($hash, $room, $device);
- my $mapping = getMapping($hash, $device, 'GetState') // return respond( $hash, $data, getResponse($hash, 'NoMappingFound') );
+ $type //= 'GetState';
+ my $mapping = getMapping($hash, $device, 'GetState', $type) // return respond( $hash, $data, getResponse($hash, 'NoMappingFound') );
if ( defined $data->{Update} ) {
my $cmd = $mapping->{update} // return respond( $hash, $data, getResponse($hash, 'DefaultError') );
@@ -4474,9 +4682,9 @@ sub handleIntentGetState {
} elsif ( defined $mapping->{response} ) {
$response = _getValue($hash, $device, _shuffle_answer($mapping->{response}), undef, $room);
$response = _ReplaceReadingsVal($hash, _shuffle_answer($mapping->{response})) if !$response; #Beta-User: case: plain Text with [device:reading]
- } elsif ( defined $data->{Type} ) {
+ } elsif ( defined $data->{type} || $data->{Type} ) {
my $reading = $data->{Reading} // 'STATE';
- $response = getResponse( $hash, 'getStateResponses', $data->{Type} );
+ $response = getResponse( $hash, 'getStateResponses', $type ) // getResponse( $hash, 'NoValidIntentResponse') ;
$response =~ s{(\$\w+)}{$1}eegx;
$response = _ReplaceReadingsVal($hash, $response );
} else {
@@ -4495,40 +4703,39 @@ sub handleIntentGetState {
sub handleIntentMediaControls {
my $hash = shift // return;
my $data = shift // return;
- my $command, my $device, my $room;
- my $mapping;
- my $response;
Log3($hash->{NAME}, 5, "handleIntentMediaControls called");
# At least one command has to be received
- return respond( $hash, $data, getResponse($hash, 'DefaultError') ) if !exists $data->{Command};
+ return respond( $hash, $data, getResponse($hash, 'NoValidData') ) if !exists $data->{Command};
- $room = getRoomName($hash, $data);
- $command = $data->{Command};
+ my $room = getRoomName($hash, $data);
+ my $command = $data->{Command};
+
+ my $device;
# Search for matching device
if (exists $data->{Device}) {
$device = getDeviceByName($hash, $room, $data->{Device});
} else {
- $device = getActiveDeviceForIntentAndType($hash, $room, 'MediaControls', undef);
- $response = getResponse($hash, 'NoActiveMediaDevice') if !defined $device;
+ $device = getActiveDeviceForIntentAndType($hash, $room, 'MediaControls', undef)
+ // return respond( $hash, $data, getResponse($hash, 'NoActiveMediaDevice') );
}
- $mapping = getMapping($hash, $device, 'MediaControls');
+ my $mapping = getMapping($hash, $device, 'MediaControls') // return respond( $hash, $data, getResponse($hash, 'NoMappingFound') );
+
+ return respond( $hash, $data, getResponse($hash, 'NoMappingFound') ) if !defined $mapping->{$command};
+
+ #check if confirmation is required
+ return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'MediaControls' );
+ my $cmd = $mapping->{$command};
+ # Execute Cmd
+ analyzeAndRunCmd($hash, $device, $cmd);
+ # Define voice response
+ my $response = defined $mapping->{response} ?
+ _getValue($hash, $device, _shuffle_answer($mapping->{response}), $command, $room)
+ : getResponse($hash, 'DefaultConfirmation');
- if ( defined $device && defined $mapping && defined $mapping->{$command} ) {
- #check if confirmation is required
- return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'MediaControls' );
- my $cmd = $mapping->{$command};
- # Execute Cmd
- analyzeAndRunCmd($hash, $device, $cmd);
- # Define voice response
- $response = defined $mapping->{response} ?
- _getValue($hash, $device, _shuffle_answer($mapping->{response}), $command, $room)
- : getResponse($hash, 'DefaultConfirmation');
- }
- $response //= getResponse($hash, 'DefaultError');
# Send voice response
respond( $hash, $data, $response );
return $device;
@@ -4538,7 +4745,6 @@ sub handleIntentMediaControls {
sub handleIntentSetScene{
my $hash = shift // return;
my $data = shift // return;
- my ($scene, $device, $room, $siteId, $mapping, $response);
Log3($hash->{NAME}, 5, "handleIntentSetScene called");
return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !defined $data->{Scene};
@@ -4547,10 +4753,10 @@ sub handleIntentSetScene{
return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !exists $data->{Device};
- $room = getRoomName($hash, $data);
- $scene = $data->{Scene};
- $device = getDeviceByName($hash, $room, $data->{Device});
- $mapping = getMapping($hash, $device, 'SetScene');
+ my $room = getRoomName($hash, $data);
+ my $scene = $data->{Scene};
+ my $device = getDeviceByName($hash, $room, $data->{Device});
+ my $mapping = getMapping($hash, $device, 'SetScene');
# restore HUE scenes
$scene = qq([$scene]) if $scene =~ m{id=.+}xms;
@@ -4567,7 +4773,7 @@ sub handleIntentSetScene{
Log3($hash->{NAME}, 5, "Running command [$cmd] on device [$device]" );
# Define response
- $response = _shuffle_answer($mapping->{response}) // getResponse( $hash, 'DefaultConfirmation' );
+ my $response = _shuffle_answer($mapping->{response}) // getResponse( $hash, 'DefaultConfirmation' );
respond( $hash, $data, $response );
return $device;
@@ -4688,7 +4894,7 @@ sub handleIntentSetColor {
$response = _runSetColorCmd($hash, $device, $data, $inBulk);
}
# Send voice response
- $response = getResponse($hash, 'DefaultError') if !defined $response;
+ $response //= getResponse($hash, 'DefaultError');
respond( $hash, $data, $response ) if !$inBulk;
return $device;
}
@@ -4716,7 +4922,7 @@ sub _runSetColorCmd {
my $specialmapping = $hash->{helper}{devicemap}{devices}{$device}{color_specials}{$kw};
if (defined $data->{$kw} && defined $specialmapping && defined $specialmapping->{$data->{$kw}}) {
my $cmd = $specialmapping->{$data->{$kw}};
- $error = AnalyzeCommand($hash, "set $device $cmd");
+ $error = _AnalyzeCommand($hash, "set $device $cmd");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting $device to $cmd");
return respond( $hash, $data, $error ) if $error;
@@ -4724,7 +4930,7 @@ sub _runSetColorCmd {
} elsif ( defined $data->{$kw} && defined $mapping->{$_} ) {
my $value = _round( ( $mapping->{$_}->{maxVal} - $mapping->{$_}->{minVal} ) * $data->{$kw} / ( $kw eq 'Hue' ? 360 : 100 ) ) ;
$value = min(max($mapping->{$_}->{minVal}, $value), $mapping->{$_}->{maxVal});
- $error = AnalyzeCommand($hash, "set $device $mapping->{$_}->{cmd} $value");
+ $error = _AnalyzeCommand($hash, "set $device $mapping->{$_}->{cmd} $value");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting color to $value");
return respond( $hash, $data, $error ) if $error;
@@ -4735,7 +4941,7 @@ sub _runSetColorCmd {
#shortcut: Rgb field is used or color is in HEX value and rgb is a possible command
if ( ( defined $data->{Rgb} || defined $color && $color =~ m{\A[[:xdigit:]]\z}x ) && defined $mapping->{rgb} ) {
$color = $data->{Rgb} if defined $data->{Rgb};
- $error = AnalyzeCommand($hash, "set $device $mapping->{rgb}->{cmd} $color");
+ $error = _AnalyzeCommand($hash, "set $device $mapping->{rgb}->{cmd} $color");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting rgb-color to $color");
return respond( $hash, $data, $error ) if $error;
@@ -4777,7 +4983,7 @@ sub _runSetColorCmd {
return "mapping problem in Hue2rgb" if !defined $rgb;
my $rgbcmd = $mapping->{rgb}->{cmd};
$rgb = lc $rgb if $rgbcmd eq 'hex';
- $error = AnalyzeCommand($hash, "set $device $rgbcmd $rgb");
+ $error = _AnalyzeCommand($hash, "set $device $rgbcmd $rgb");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting rgb-color to $rgb using hue");
return respond( $hash, $data, $error ) if $error;
@@ -4791,7 +4997,7 @@ sub _runSetColorCmd {
my $rgb = uc sprintf( "%2.2X%2.2X%2.2X", $r, $g, $b );
return "mapping problem in _ct2rgb" if !defined $rgb;
- $error = AnalyzeCommand($hash, "set $device $mapping->{rgb}->{cmd} $rgb");
+ $error = _AnalyzeCommand($hash, "set $device $mapping->{rgb}->{cmd} $rgb");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting color-temperature to $ct");
return respond( $hash, $data, $error ) if $error;
@@ -4842,6 +5048,7 @@ sub handleIntentSetColorGroup {
return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetColorGroup' );
my $devices = getDevicesByGroup($hash, $data);
+ return testmode_next($hash) if _isUnexpectedInTestMode($hash, $data);
#see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference
my @devlist = sort {
@@ -4940,7 +5147,6 @@ sub handleIntentSetTimer {
my $response;
if (defined $data->{CancelTimer}) {
CommandDelete($hash, $roomReading);
- #readingsSingleUpdate( $hash,$roomReading, 0, 1 );
readingsDelete($hash, $roomReading);
Log3($name, 5, "deleted timer: $roomReading");
$response = getResponse($hash, 'timerCancellation');
@@ -4974,10 +5180,8 @@ sub handleIntentSetTimer {
CommandDefMod($hash, "-temporary $roomReading at +$attime set $name play siteId=\"$timerRoom\" path=\"$file\" repeats=$repeats wait=$duration id=${roomReading}$addtrigger");
}
- #readingsSingleUpdate($hash, $roomReading, 1, 1);
readingsSingleUpdate($hash, $roomReading, $readingTime, 1);
- #Log3($name, 5, "Created timer: $roomReading at +$attime");
Log3($name, 5, "Created timer: $roomReading at $readingTime");
my ($range, $minutes, $hours, $minutetext);
@@ -5297,8 +5501,8 @@ sub _ReplaceReadingsVal {
if($s && $s =~ m{:d|:r|:i}x && $val =~ m{(-?\d+(\.\d+)?)}x) {
$val = $1;
$val = int($val) if $s eq ':i';
- my $n = defined $1 ? $1 : 1;
- $val = sprintf("%.${n}f",$val) if $s =~ m{\A:r(\d)?}x;
+ my $nn = defined $1 ? $1 : 1;
+ $val = sprintf("%.${nn}f",$val) if $s =~ m{\A:r(\d)?}x;
}
return $val;
};
@@ -5332,6 +5536,9 @@ sub _readLanguageFromFile {
return $ret, undef;
}
my @cleaned = grep { $_ !~ m{\A\s*[#]}x } @content;
+ for (@cleaned) {
+ $_ =~ s{\A\s+}{}gmxsu;
+ };
return 0, join q{ }, @cleaned;
}
@@ -5377,6 +5584,25 @@ sub _shuffle_answer {
return $arr[ rand @arr ];
}
+sub _array2andString {
+ my $hash = shift // return;
+ my $arr = shift // return;
+
+ return $arr if ref $arr ne 'ARRAY';
+
+ my $and = $hash->{helper}{lng}->{words}->{and} // 'and';
+
+ my @all = @{$arr};
+ my $fin = pop @all;
+ while (@all && !$fin) {
+ $fin = pop @all;
+ }
+ return $fin if !@all;
+ my $text = join q{, }, @all;
+ $text .= " $and $fin";
+ return $text;
+}
+
1;
__END__
@@ -5394,7 +5620,16 @@ https://forum.fhem.de/index.php/topic,113180.msg1130139.html#msg1130139
- v.a. auch kontinuierliche Dialoge/Rückfragen, wann Input aufmachen
# auto-training
-Tests/Rückmeldungen fehlen bisher
+Tests/Rückmeldungen fehlen bisher; sieht nicht funktional aus...
+
+# probability:
+Minimum Level (pro Intent?) festlegen können. (muss getestet werden)
+
+# Testsuite:
+- "Kenner" Dialoge etc. einbauen (vorl. erledigt)
+- Mehr Info zu adressierten Geräten (getDevicesByGroup?)
+-- OK für Gruppen;
+-- Nacharbeit erforderlich für Eizel-Intents (vorbereitet).
=end ToDo
@@ -5518,9 +5753,9 @@ After changing something relevant within FHEM for either the data structure in
- siteId and path are required!
+ siteId and path and filename are required!
You may optionally add a number of repeats and a wait time in seconds between repeats. wait defaults to 15, if only repeats is given.
set <rhasspyDevice> play siteId="default" path="/opt/fhem/test.wav"
@@ -5529,7 +5764,7 @@ After changing something relevant within FHEM for either the data structure in
Both arguments (siteId and text) are required!
@@ -5537,7 +5772,7 @@ After changing something relevant within FHEM for either the data structure in
set <rhasspyDevice> textCommand turn the light on
Both arguments (siteId and volume) are required!
@@ -5567,7 +5802,7 @@ After changing something relevant within FHEM for either the data structure in
Provide slotname, slotdata and (optional) info, if existing data shall be overwritten and training shall be initialized immediately afterwards.
First two arguments are required, third and fourth are optional.
@@ -5587,12 +5822,16 @@ After changing something relevant within FHEM for either the data structure in
@@ -5757,6 +5996,11 @@ i="i am hungry" f="set Stove on" d="Stove" c="would you like roast pork"<
Example: extrarooms= barn,music collection,cooking recipies
Note: Only do this in case you really know what you are doing! Additional rooms only may be usefull in case you have some external application knowing what to do with info assinged to these rooms!
+ Example: confidenceMin= default=0.6 SetMute=0.4 SetOnOffGroup=0.8 SetOnOff=0.8
Explanation:
If set, the label provided will be sent to Rhasspy instead of the tech names (derived from available setters). Keyword none will delete the scene from the internal list, setting the combination all=none will exclude the entire device from beeing recognized for SetScene.
+attr weather rhasspySpecials blacklistIntents:MediaControls
Explanation: +
If set, the blacklisted intents will be deleted after automated mapping analysis.
+