diff --git a/fhem/contrib/RHASSPY/10_RHASSPY.pm b/fhem/contrib/RHASSPY/10_RHASSPY.pm index 952b7e4bf..a1c5d0f15 100644 --- a/fhem/contrib/RHASSPY/10_RHASSPY.pm +++ b/fhem/contrib/RHASSPY/10_RHASSPY.pm @@ -1,4 +1,4 @@ -# $Id$ +# $Id: 10_RHASSPY.pm 24786 2021-09-21 + Beta-User$ ########################################################################### # # FHEM RHASSPY module (https://github.com/rhasspy) @@ -40,7 +40,7 @@ use utf8; use List::Util 1.45 qw(max min uniq); use Scalar::Util qw(looks_like_number); use POSIX qw(strftime); -use Data::Dumper; +#use Data::Dumper; use FHEM::Core::Timer::Register qw(:ALL); sub ::RHASSPY_Initialize { goto &Initialize } @@ -88,13 +88,14 @@ my $languagevars = { 'NoActiveMediaDevice' => "Sorry no active playback device", 'NoMediaChannelFound' => "Sorry but requested channel seems not to exist", 'DefaultConfirmation' => "OK", + 'DefaultConfirmationBack' => "So once more", 'DefaultConfirmationTimeout' => "Sorry too late to confirm", 'DefaultCancelConfirmation' => "Thanks aborted", 'SilentCancelConfirmation' => "", 'DefaultConfirmationReceived' => "ok will do it", 'DefaultConfirmationNoOutstanding' => "no command is awaiting confirmation", - 'DefaultConfirmationRequest' => 'please confirm switching $device $wanted', 'DefaultConfirmationRequestRawInput' => 'please confirm: $rawInput', + 'DefaultChangeIntentRequestRawInput' => 'change command to $rawInput', 'RequestChoiceDevice' => 'there are several possible devices, choose between $first_items and $last_item', 'RequestChoiceRoom' => 'more than one possible device, please choose one of the following rooms $first_items and $last_item', 'DefaultChoiceNoOutstanding' => "no choice expected", @@ -347,7 +348,7 @@ sub Define { $hash->{defaultRoom} = $defaultRoom; my $language = $h->{language} // shift @{$anon} // lc AttrVal('global','language','en'); - $hash->{MODULE_VERSION} = '0.4.36'; + $hash->{MODULE_VERSION} = '0.4.40'; $hash->{baseUrl} = $Rhasspy; initialize_Language($hash, $language) if !defined $hash->{LANGUAGE} || $hash->{LANGUAGE} ne $language; $hash->{LANGUAGE} = $language; @@ -709,7 +710,7 @@ sub initialize_rhasspyTweaks { next; } - if ($line =~ m{\A[\s]*(timeouts|useGenericAttrs|timerSounds|confirmIntents)[\s]*=}x) { + if ($line =~ m{\A[\s]*(timeouts|useGenericAttrs|timerSounds|confirmIntents|confirmIntentResponses)[\s]*=}x) { ($tweak, $values) = split m{=}x, $line, 2; $tweak = trim($tweak); return "Error in $line! No content provided!" if !length $values && $init_done; @@ -945,9 +946,13 @@ sub _analyze_rhassypAttr { for my $line (@lines) { my ($key, $val) = split m{:}x, $line, 2; next if !$val; - + + if ($key eq 'colorForceHue2rgb') { + $hash->{helper}{devicemap}{devices}{$device}{color_specials}{forceHue2rgb} = $val; + } + + my($unnamed, $named) = parseParams($val); if ($key eq 'group') { - my($unnamed, $named) = parseParams($val); my $specials = {}; my $partOf = $named->{partOf} // shift @{$unnamed}; $specials->{partOf} = $partOf if defined $partOf; @@ -956,19 +961,13 @@ sub _analyze_rhassypAttr { $hash->{helper}{devicemap}{devices}{$device}{group_specials} = $specials; } - if ($key eq 'colorForceHue2rgb') { - $hash->{helper}{devicemap}{devices}{$device}{color_specials}{forceHue2rgb} = $val; - } if ($key eq 'colorCommandMap') { - my($unnamed, $named) = parseParams($val); $hash->{helper}{devicemap}{devices}{$device}{color_specials}{CommandMap} = $named if defined $named; } if ($key eq 'colorTempMap') { - my($unnamed, $named) = parseParams($val); $hash->{helper}{devicemap}{devices}{$device}{color_specials}{Colortemp} = $named if defined $named; } if ($key eq 'venetianBlind') { - my($unnamed, $named) = parseParams($val); my $specials = {}; my $vencmd = $named->{setter} // shift @{$unnamed}; my $vendev = $named->{device} // shift @{$unnamed}; @@ -979,12 +978,10 @@ sub _analyze_rhassypAttr { $hash->{helper}{devicemap}{devices}{$device}{venetian_specials} = $specials if defined $vencmd || defined $vendev; } if ($key eq 'priority') { - my($unnamed, $named) = parseParams($val); $hash->{helper}{devicemap}{devices}{$device}{prio}{inRoom} = $named->{inRoom} if defined $named->{inRoom}; $hash->{helper}{devicemap}{devices}{$device}{prio}{outsideRoom} = $named->{outsideRoom} if defined $named->{outsideRoom}; } if ( $key eq 'scenes' && defined $hash->{helper}{devicemap}{devices}{$device}{intents}{SetScene} ) { - my($unnamed, $named) = parseParams($val); my $combined = _combineHashes( $hash->{helper}{devicemap}{devices}{$device}{intents}{SetScene}->{SetScene}, $named); for (keys %{$combined}) { delete $combined->{$_} if $combined->{$_} eq 'none' || defined $named->{all} && $named->{all} eq 'none'; @@ -994,7 +991,12 @@ sub _analyze_rhassypAttr { : delete $hash->{helper}{devicemap}{devices}{$device}{intents}->{SetScene}; } if ($key eq 'confirm') { - $hash->{helper}{devicemap}{devices}{$device}{confirmIntents} = $val; + #my($unnamed, $named) = parseParams($val); + $hash->{helper}{devicemap}{devices}{$device}{confirmIntents} = join q{,}, (@{$unnamed}, keys %{$named}); + $hash->{helper}{devicemap}{devices}{$device}{confirmIntentResponses} = $named if $named; + } + if ($key eq 'confirmValueMap') { + $hash->{helper}{devicemap}{devices}{$device}{confirmValueMap} = $named if $named; } } @@ -1083,8 +1085,10 @@ sub _analyze_genDevType { } $currentMapping = _analyze_genDevType_setter( $hash, $device, $allset, $currentMapping ); $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; + return; } - elsif ( $gdt eq 'thermostat' ) { + + if ( $gdt eq 'thermostat' ) { my $desTemp = $allset =~ m{\b(desiredTemp)([\b:\s]|\Z)}xms ? $1 : 'desired-temp'; my $measTemp = InternalVal($device, 'TYPE', 'unknown') eq 'CUL_HM' ? 'measured-temp' : 'temperature'; $currentMapping = @@ -1093,9 +1097,10 @@ sub _analyze_genDevType { SetNumeric => {'desired-temp' => { cmd => $desTemp, currentVal => $desTemp, maxVal => '28', minVal => '10', step => '0.5', type => 'temperature'}} }; $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; + return; } - elsif ( $gdt eq 'thermometer' ) { + if ( $gdt eq 'thermometer' ) { my $r = $defs{$device}{READINGS}; if($r) { for (sort keys %{$r}) { @@ -1105,9 +1110,10 @@ sub _analyze_genDevType { } } $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; + return; } - elsif ( $gdt eq 'blind' ) { + if ( $gdt eq 'blind' ) { if ( $allset =~ m{\bdim([\b:\s]|\Z)}xms ) { my $maxval = InternalVal($device, 'TYPE', 'unknown') eq 'ZWave' ? 99 : 100; $currentMapping = @@ -1127,6 +1133,7 @@ sub _analyze_genDevType { }; } $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; + return; } if ( $gdt eq 'media' ) { #genericDeviceType media @@ -1785,16 +1792,24 @@ sub getNeedsConfirmation { my $device = shift; my $re = defined $device ? $device : $data->{Group}; + return if !defined $re; + my $target = defined $device ? $data->{Device} : $data->{Group}; Log3( $hash, 5, "[$hash->{NAME}] getNeedsConfirmation called, regex is $re" ); my $timeout = _getDialogueTimeout($hash); - my $response = getResponse($hash, 'DefaultConfirmationRequestRawInput'); + my $response; my $rawInput = $data->{rawInput}; - $response =~ s{(\$\w+)}{$1}eegx; + my $Value = $data->{Value}; + $Value = $hash->{helper}{lng}->{words}->{$Value} if defined $Value && defined $hash->{helper}{lng}->{words} && defined $hash->{helper}{lng}->{words}->{$Value}; if (defined $hash->{helper}{tweaks} && defined $hash->{helper}{tweaks}{confirmIntents} && defined $hash->{helper}{tweaks}{confirmIntents}{$intent} - && $hash->{helper}{tweaks}{confirmIntents}{$intent} =~ m{\b$re(?:[,]|\Z)}i ) { ##no critic qw(RequireExtendedFormatting) + && $re =~ m{\A($hash->{helper}{tweaks}{confirmIntents}{$intent})\z}m ) { + $response = defined $hash->{helper}{tweaks}{confirmIntentResponses} + && defined $hash->{helper}{tweaks}{confirmIntentResponses}{$intent} ? $hash->{helper}{tweaks}{confirmIntentResponses}{$intent} + : getResponse($hash, 'DefaultConfirmationRequestRawInput'); + + $response =~ s{(\$\w+)}{$1}eegx; Log3( $hash, 5, "[$hash->{NAME}] getNeedsConfirmation is true for tweak, response is $response" ); setDialogTimeout($hash, $data, $timeout, $response); return 1; @@ -1804,8 +1819,18 @@ sub getNeedsConfirmation { my $confirm = $hash->{helper}{devicemap}{devices}{$device}->{confirmIntents}; return if !defined $confirm; - if ( $confirm->{$intent} =~ m{\b$intent(?:[,]|\Z)}i ) { ##no critic qw(RequireExtendedFormatting) - Log3( $hash, 5, "[$hash->{NAME}] getNeedsConfirmation is true on device level" ); + if ( $confirm =~ m{\b$intent(?:[,]|\Z)}i ) { ##no critic qw(RequireExtendedFormatting) + $response = defined $hash->{helper}{devicemap}{devices}{$device}->{confirmIntentResponses} + && defined $hash->{helper}{devicemap}{devices}{$device}->{confirmIntentResponses}{$intent} + ? $hash->{helper}{devicemap}{devices}{$device}->{confirmIntentResponses}{$intent} + : defined $hash->{helper}{tweaks} + && defined $hash->{helper}{tweaks}{confirmIntentResponses} + && defined $hash->{helper}{tweaks}{confirmIntentResponses}{$intent} ? $hash->{helper}{tweaks}{confirmIntentResponses}{$intent} + : getResponse($hash, 'DefaultConfirmationRequestRawInput'); + my $words = $hash->{helper}{devicemap}{devices}{$device}->{confirmValueMap} // $hash->{helper}{lng}->{words} // {}; + $Value = $words->{$data->{Value}} // $Value; + $response =~ s{(\$\w+)}{$1}eegx; + Log3( $hash, 5, "[$hash->{NAME}] getNeedsConfirmation is true on device level, response is $response" ); setDialogTimeout($hash, $data, $timeout, $response); return 1; } @@ -2288,12 +2313,10 @@ sub respond { my $hash = shift // return; my $data = shift // return; my $response = shift // return; - #former call: respond( $hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse( $hash, 'DefaultCancelConfirmation' ) ); + my $topic = shift // q{endSession}; my $type = $data->{requestType} // return; - my $topic = q{endSession}; - my $sendData; for my $key (qw(sessionId siteId customData lang)) { @@ -2306,6 +2329,9 @@ sub respond { for my $key (keys %{$response}) { $sendData->{$key} = $response->{$key}; } + } elsif ($topic eq 'continueSession') { + $sendData->{text} = $response; + $sendData->{intentFilter} = 'null'; } else { $sendData->{text} = $response; $sendData->{intentFilter} = 'null'; @@ -2877,6 +2903,8 @@ sub handleIntentSetOnOff { # Mapping found? if ( defined $device && defined $mapping ) { + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetOnOff', $device ); my $cmdOn = $mapping->{cmdOn} // 'on'; my $cmdOff = $mapping->{cmdOff} // 'off'; my $cmd = $value eq 'on' ? $cmdOn : $cmdOff; @@ -2985,6 +3013,7 @@ sub handleIntentSetTimedOnOff { # Mapping found? if ( defined $device && defined $mapping ) { + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetTimedOnOff', $device ); my $cmdOn = $mapping->{cmdOn} // 'on'; my $cmdOff = $mapping->{cmdOff} // 'off'; my $cmd = $value eq 'on' ? $cmdOn : $cmdOff; @@ -3042,6 +3071,9 @@ sub handleIntentSetTimedOnOffGroup { return respond( $hash, $data, getResponse( $hash, 'duration_not_understood' ) ) if !defined $data->{Hourabs} && !defined $data->{Hour} && !defined $data->{Min} && !defined $data->{Sec}; + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetTimedOnOffGroup' ); + my $devices = getDevicesByGroup($hash, $data); #see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference @@ -3087,7 +3119,7 @@ sub handleIntentSetTimedOnOffGroup { # Mapping found? next if !defined $mapping; - + my $cmdOn = $mapping->{cmdOn} // 'on'; my $cmdOff = $mapping->{cmdOff} // 'off'; my $cmd = $value eq 'on' ? $cmdOn : $cmdOff; @@ -3193,6 +3225,9 @@ sub handleIntentSetNumericGroup { return respond( $hash, $data, getResponse($hash, 'NoValidData') ) if !exists $data->{Value} && !exists $data->{Change}; + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetNumericGroup' ); + my $devices = getDevicesByGroup($hash, $data); #see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference @@ -3336,7 +3371,7 @@ sub handleIntentSetNumeric { $newVal = $value; #$newVal = 0 if ($newVal < 0); #$newVal = 100 if ($newVal > 100); - $newVal = round((($newVal * (($maxVal - $minVal) / 100)) + $minVal), 0); + $newVal = _round(($newVal * (($maxVal - $minVal) / 100)) + $minVal); } } else { # defined $change # Stellwert um Wert x ändern ("Mache Lampe um 20 heller" oder "Mache Lampe heller") @@ -3349,7 +3384,7 @@ sub handleIntentSetNumeric { elsif ( ( $ispct || $forcePercent ) && $checkMinMax ) { #$maxVal = 100 if !looks_like_number($maxVal); #Beta-User: Workaround, should be fixed in mapping (tbd) #my $diffRaw = round((($diff * (($maxVal - $minVal) / 100)) + $minVal), 0); - my $diffRaw = round(($diff * ($maxVal - $minVal) / 100), 0); + my $diffRaw = _round($diff * ($maxVal - $minVal) / 100); $newVal = ($up) ? $oldVal + $diffRaw : $oldVal - $diffRaw; $newVal = max( $minVal, min( $maxVal, $newVal ) ); } @@ -3363,6 +3398,9 @@ sub handleIntentSetNumeric { $newVal = max( $minVal, $newVal ) if defined $minVal; $newVal = min( $maxVal, $newVal ) if defined $maxVal; + #check if confirmation is required + return $hash->{NAME} if !defined $data->{'.inBulk'} && !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetNumeric' ); + # execute Cmd analyzeAndRunCmd($hash, $device, $cmd, $newVal); @@ -3437,7 +3475,7 @@ sub handleIntentGetNumeric { my @tokens = split m{\s+}x, $value; $value = $tokens[$part] if @tokens >= $part; } - $value = round( ($value * ($maxVal - $minVal) / 100 + $minVal), 0) if $forcePercent; + $value = _round($value * ($maxVal - $minVal) / 100 + $minVal) if $forcePercent; my $isNumber = looks_like_number($value); # replace dot by comma if needed @@ -3539,6 +3577,8 @@ sub handleIntentMediaControls { $mapping = getMapping($hash, $device, 'MediaControls', undef, defined $hash->{helper}{devicemap}, 0); if (defined $device && defined $mapping) { + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'MediaControls' ); my $cmd = $mapping->{$command}; #Beta-User: backwards compability check; might be removed later... @@ -3604,6 +3644,10 @@ sub handleIntentSetScene{ # Mapping found? return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !$device || !defined $mapping; + + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetScene' ); + my $cmd = qq(scene $scene); # execute Cmd @@ -3690,6 +3734,8 @@ sub handleIntentMediaChannels { #$cmd = (split m{=}x, $cmd, 2)[1]; if ( defined $device && defined $cmd ) { + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'MediaChannels' ); $response = getResponse($hash, 'DefaultConfirmation'); # Cmd ausführen analyzeAndRunCmd($hash, $device, $cmd); @@ -3736,6 +3782,9 @@ sub handleIntentSetColor { return if $inBulk && !defined $device; return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !defined $device; + #check if confirmation is required + return $hash->{NAME} if !defined $data->{'.inBulk'} && !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetColor' ); + if ( defined $cmd || defined $cmd2 ) { $response = getResponse($hash, 'DefaultConfirmation'); # Execute Cmd @@ -3778,7 +3827,7 @@ sub _runSetColorCmd { return respond( $hash, $data, $error ) if $error; return getResponse($hash, 'DefaultConfirmation'); } elsif ( defined $data->{$kw} && defined $mapping->{$_} ) { - my $value = round( ($mapping->{$_}->{maxVal} - $mapping->{$_}->{minVal}) * $data->{$kw} / ($kw eq 'Hue' ? 360 : 100) , 0); + 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"); return if $inBulk; @@ -3894,6 +3943,9 @@ sub handleIntentSetColorGroup { return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !exists $data->{Color} && !exists $data->{Rgb} &&!exists $data->{Saturation} && !exists $data->{Colortemp} && !exists $data->{Hue}; + #check if confirmation is required + return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetColorGroup' ); + my $devices = getDevicesByGroup($hash, $data); #see https://perlmaven.com/how-to-sort-a-hash-of-hashes-by-value for reference @@ -4076,9 +4128,10 @@ sub handleIntentNotRecognized { my $identiy = qq($data->{sessionId}); my $data_old = $hash->{helper}{'.delayed'}->{$identiy}; return if !defined $data_old; - $hash->{helper}{'.delayed'}->{$identiy}->{intentNotRecognized} = $data; + return if !defined $data->{input} || length($data->{input}) < 12; #Beta-User: silence chuncks or single words, might later be configurable + $hash->{helper}{'.delayed'}->{$identiy}->{intentNotRecognized} = $data->{input}; Log3( $hash->{NAME}, 5, "data_old is: " . toJSON( $hash->{helper}{'.delayed'}->{$identiy} ) ); - my $response = getResponse($hash, 'DefaultConfirmationRequestRawInput'); + my $response = getResponse($hash, 'DefaultChangeIntentRequestRawInput'); my $rawInput = $data->{input}; $response =~ s{(\$\w+)}{$1}eegx; $data_old->{customData} = 'intentNotRecognized'; @@ -4114,9 +4167,10 @@ sub handleIntentConfirmAction { my $data = shift // return; Log3($hash->{NAME}, 5, 'handleIntentConfirmAction called'); + my $mode = $data->{Mode}; #cancellation case - return handleIntentCancelAction($hash, $data) if $data->{Mode} ne 'OK' && $data->{Mode} ne 'Back' && $data->{Mode} ne 'Next' ; + return handleIntentCancelAction($hash, $data) if $mode ne 'OK' && $mode ne 'Back' && $mode ne 'Next' ; #confirmed case my $identiy = qq($data->{sessionId}); @@ -4130,14 +4184,38 @@ sub handleIntentConfirmAction { }; #continued session after intentNotRecognized - if ( defined $data_old->{intentNotRecognized} && ( $data->{Mode} eq 'OK' || $data->{Mode} eq 'Back') ) { - Log3($hash->{NAME}, 5, "ConfirmAction in $data->{Mode}"); - #$hash->{helper}{'.delayed'}->{$identiy}->{intentNotRecognized} = $data; - #respond( $hash, $data, getResponse( $hash, 'DefaultConfirmationNoOutstanding' ) ); - #return configure_DialogManager( $hash, $data->{siteId}, undef, undef, 1 ); #global intent filter seems to be not working!; - #atm no idea, how to continue... - return; + if ( defined $data_old->{intentNotRecognized} + && ( $mode eq 'OK' + || $mode eq 'Back' + || $mode eq 'Next' ) ) { + Log3($hash->{NAME}, 5, "ConfirmAction in $data->{Mode} after intentNotRecognized"); + if ($mode eq 'Back') { + delete $hash->{helper}{'.delayed'}->{$identiy}->{intentNotRecognized}; + return respond( $hash, $data, {text => getResponse( $hash,'DefaultConfirmationBack')} ); + } + + if ( $mode eq 'Next' + || $mode eq 'OK' && $data->{intent} =~ m{Choice}gxmsi ) { + #new nlu request with stored rawInput + my $topic = q{hermes/nlu/query}; + my $sendData; + for my $key (qw(sessionId siteId customData lang)) { + $sendData->{$key} = $data->{$key} if defined $data->{$key} && $data->{$key} ne 'null'; + } + $sendData->{input} = $data_old->{intentNotRecognized}; #input: string - text to recognize intent from (required) + $sendData->{intentFilter} = 'null'; #intentFilter: [string]? = null - valid intent names (null means all) - back to global FHEM defaults? + #id: string? = null - unique id for request (copied to response messages) + #siteId: string = "default" - Hermes site ID + #sessionId: string? = null - current session ID + #asrConfidence: float? = null + my $json = _toCleanJSON($sendData); + delete $hash->{helper}{'.delayed'}->{$identiy}; + IOWrite($hash, 'publish', qq{$topic $json}); + return respond( $hash, $data, {text => getResponse( $hash,'DefaultConfirmation')} ); + } + #return; }; + return handleIntentCancelAction($hash, $data) if $mode ne 'OK'; #modes 'Back' or 'Next' in non-dialogical context $data_old->{siteId} = $data->{siteId}; $data_old->{sessionId} = $data->{sessionId}; @@ -4324,7 +4402,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'; - $val = round($val, defined $1 ? $1 : 1) if $s =~ m{\A:r(\d)?}x; + my $n = defined $1 ? $1 : 1; + $val = sprintf("%.${n}f",$val) if $s =~ m{\A:r(\d)?}x; } return $val; }; @@ -4388,6 +4467,7 @@ sub _toCleanJSON { return $json; } +sub _round { int( $_[0] + ( $_[0] < 0 ? -.5 : .5 ) ); } 1; @@ -4467,23 +4547,24 @@ https://svn.fhem.de/trac/browser/trunk/fhem/contrib/RHASSPY">svn contrib.

Define

define <name> RHASSPY <baseUrl> <devspec> <defaultRoom> <language> <fhemId> <prefix> <useGenericAttrs> <encoding>

-

All parameters in define are optional, but changing them later might lead to confusing results!

+

All parameters in define are optional, most will not be needed (!), but keep in mind: changing them later might lead to confusing results for some of them! Especially when starting with RHASSPY, do not set any other than the first three (or four if your language is neither english nor german) of these at all!

Remark:
RHASSPY uses parseParams at quite a lot places, not only in define, but also to parse attribute values.
So all parameters in define should be provided in the key=value form. In other places you may have to start e.g. a single line in an attribute with option:key="value xy shall be z" or identifier:yourCode={fhem("set device off")} anotherOption=blabla form.

Parameters:

RHASSPY needs a MQTT2_CLIENT device connected to the same MQTT-Server as the voice assistant (Rhasspy) service.

@@ -4500,17 +4581,18 @@ attr rhasspyMQTT2 subscriptions hermes/intent/+ hermes/dialogueManager/sessionSt

In case you are using the MQTT server also for other purposes than Rhasspy, you have to set subscriptions manually to at least include the following topics additionally to the other subscriptions desired for other purposes.

hermes/intent/+
hermes/dialogueManager/sessionStarted
-hermes/dialogueManager/sessionEnded

+hermes/dialogueManager/sessionEnded
+hermes/nlu/intentNotRecognized

Important: After defining the RHASSPY module, you are supposed to manually set the attribute IODev to force a non-dynamic IO assignement. Use e.g. attr <deviceName> IODev <m2client>.

-

Note: RHASSPY consolidates a lot of data from different sources. The final data structure RHASSPY uses at runtime can be viewed using the list command. It's highly recommended to have a close look at this data structure, especially when starting with RHASSPY or in case something doesn't work as expected!
-When changing something relevant within FHEM for either the data structure in

+

Note: RHASSPY consolidates a lot of data from different sources. The final data structure RHASSPY uses at runtime will be shown by the list command. It's highly recommended to have a close look at this data structure, especially when starting with RHASSPY or in case something doesn't work as expected!
+After changing something relevant within FHEM for either the data structure in

-

these changes must be get to known to RHASSPY and (often, but not allways) to Rhasspy. See the different versions provided by the update command.

+

you have to make sure these changes are also updated in RHASSPYs internal data structure and (often, but not always) to Rhasspy. See the different versions provided by the update command.

Set

@@ -4721,6 +4803,21 @@ i="i am hungry" f="set Stove on" d="Stove" c="would you like roast pork"<

Example:

timeouts: confirm=25 default=30

+ +
  • confirmIntents +

    This key may contain <Intent>=<regex> pairs beeing +

    + Example:

    confirmIntents=SetOnOffGroup=light|blinds SetOnOff=blind.*

    +
  • + +
  • confirmIntentResponses +

    By default, the answer/confirmation request will be some kind of echo to the originally spoken sentence ($rawInput as stated by DefaultConfirmationRequestRawInput key in responses). You may change this for each intent specified using $target, ($rawInput) and $Value als parameters. + Example:

    confirmIntentResponses=SetOnOffGroup="really switch group $target $Value" SetOnOff="confirm setting $target $Value"

    + $Value may be translated with defaults from a words key in languageFile, for more options on $Value and/or more specific settings in single devices see also confirmValueMap key in rhasspySpecials.

    +
  • intentFilter

    Atm. Rhasspy will activate all known intents at startup. As some of the intents used by FHEM are only needed in case some dialogue is open, it will deactivate these intents (atm: ConfirmAction, CancelAction, ChoiceRoom and ChoiceDevice(including the additional parts derived from language and fhemId))) at startup or when no active filtering is detected. You may disable additional intents by just adding their names in intentFilter line or using an explicit state assignment in the form intentname=true (Note: activating the 4 mentionned intents is not possible!). For details on how configure works see Rhasspy documentation. @@ -4802,7 +4899,7 @@ yellow=rgb FFFF00

    key:value line by line arguments similar to rhasspyTweaks.