From 4c0b8dc31c124e3a787dc9b9d4a403137070a1ec Mon Sep 17 00:00:00 2001 From: drhirn <> Date: Sat, 27 Nov 2021 06:58:20 +0000 Subject: [PATCH] 10_RHASSPY.pm: v.0.5.03 git-svn-id: https://svn.fhem.de/fhem/trunk@25267 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/RHASSPY/10_RHASSPY.pm | 337 +++++++++++++++++++++-------- 1 file changed, 245 insertions(+), 92 deletions(-) diff --git a/fhem/contrib/RHASSPY/10_RHASSPY.pm b/fhem/contrib/RHASSPY/10_RHASSPY.pm index 9443b92dc..ac97165b7 100644 --- a/fhem/contrib/RHASSPY/10_RHASSPY.pm +++ b/fhem/contrib/RHASSPY/10_RHASSPY.pm @@ -1,4 +1,4 @@ -# $Id: 10_RHASSPY.pm 24786 2021-10-17 + Beta-User$ +# $Id: 10_RHASSPY.pm 24786 2021-11-26 + Beta-User$ ########################################################################### # # FHEM RHASSPY module (https://github.com/rhasspy) @@ -59,7 +59,8 @@ my %sets = ( trainRhasspy => [qw(noArg)], fetchSiteIds => [qw(noArg)], update => [qw(devicemap devicemap_only slots slots_no_training language intent_filter all)], - volume => [] + volume => [], + text2intent => [] ); my $languagevars = { @@ -305,6 +306,7 @@ my @topics = qw( hermes/dialogueManager/sessionStarted hermes/dialogueManager/sessionEnded hermes/nlu/intentNotRecognized + hermes/hotword/+/detected ); sub Initialize { @@ -317,7 +319,7 @@ sub Initialize { #$hash->{RenameFn} = \&Rename; $hash->{SetFn} = \&Set; $hash->{AttrFn} = \&Attr; - $hash->{AttrList} = "IODev rhasspyIntents:textField-long rhasspyShortcuts:textField-long rhasspyTweaks:textField-long response:textField-long forceNEXT:0,1 disable:0,1 disabledForIntervals languageFile " . $readingFnAttributes; + $hash->{AttrList} = "IODev rhasspyIntents:textField-long rhasspyShortcuts:textField-long rhasspyTweaks:textField-long response:textField-long rhasspyHotwords:textField-long forceNEXT:0,1 disable:0,1 disabledForIntervals languageFile " . $readingFnAttributes; $hash->{Match} = q{.*}; $hash->{ParseFn} = \&Parse; $hash->{parseParams} = 1; @@ -339,7 +341,7 @@ sub Define { my @unknown; for (keys %{$h}) { - push @unknown, $_ if $_ !~ m{\A(?:baseUrl|defaultRoom|language|devspec|fhemId|prefix|encoding|useGenericAttrs)\z}xm; + push @unknown, $_ if $_ !~ m{\A(?:baseUrl|defaultRoom|language|devspec|fhemId|prefix|encoding|useGenericAttrs|handleHotword|experimental)\z}xm; } my $err = join q{, }, @unknown; return "unknown key(s) in DEF: $err" if @unknown && $init_done; @@ -347,16 +349,22 @@ sub Define { $hash->{defaultRoom} = $defaultRoom; my $language = $h->{language} // shift @{$anon} // lc AttrVal('global','language','en'); - $hash->{MODULE_VERSION} = '0.4.41a'; + $hash->{MODULE_VERSION} = '0.5.03'; $hash->{baseUrl} = $Rhasspy; initialize_Language($hash, $language) if !defined $hash->{LANGUAGE} || $hash->{LANGUAGE} ne $language; $hash->{LANGUAGE} = $language; - $hash->{devspec} = $h->{devspec} // q{room=Rhasspy}; + my $defaultdevspec = defined $h->{useGenericAttrs} && $h->{useGenericAttrs} == 0 ? q{room=Rhasspy} : q{genericDeviceType=.+}; + $hash->{devspec} = $h->{devspec} // $defaultdevspec; $hash->{fhemId} = $h->{fhemId} // q{fhem}; initialize_prefix($hash, $h->{prefix}) if !defined $hash->{prefix} || defined $h->{prefix} && $hash->{prefix} ne $h->{prefix}; $hash->{prefix} = $h->{prefix} // q{rhasspy}; $hash->{encoding} = $h->{encoding} // q{utf8}; $hash->{useGenericAttrs} = $h->{useGenericAttrs} // 1; + + for my $key (qw( experimental handleHotword )) { + delete $hash->{$key}; + $hash->{$key} = $h->{$key} if defined $h->{$key}; + } $hash->{'.asyncQueue'} = []; #Beta-User: Für's Ändern von defaultRoom oder prefix vielleicht (!?!) hilfreich: https://forum.fhem.de/index.php/topic,119150.msg1135838.html#msg1135838 (Rudi zu resolveAttrRename) @@ -383,6 +391,7 @@ sub firstInit { fetchSiteIds($hash) if !ReadingsVal( $name, 'siteIds', 0 ); initialize_rhasspyTweaks($hash, AttrVal($name,'rhasspyTweaks', undef )); + initialize_rhasspyHotwords($hash, AttrVal($name,'rhasspyHotwords', undef )); fetchIntents($hash); delete $hash->{ERRORS}; if ( !defined InternalVal($name, 'IODev',undef) ) { @@ -557,7 +566,8 @@ sub Set { speak => \&sendSpeakCommand, textCommand => \&sendTextCommand, play => \&setPlayWav, - volume => \&setVolume + volume => \&setVolume, + text2intent => \&text2intentRecognition }; return Log3($name, 3, "set $name $command requires at least one argument!") if !@values; @@ -648,6 +658,16 @@ sub Attr { } } + if ( $attribute eq 'rhasspyHotwords' ) { + for ( keys %{ $hash->{helper}{hotwords} } ) { + delete $hash->{helper}{hotwords}{$_}; + } + delete $hash->{helper}{hotwords}; + if ($command eq 'set') { + return initialize_rhasspyHotwords($hash, $value); + } + } + if ( $attribute eq 'languageFile' ) { if ($command ne 'set') { delete $hash->{CONFIGFILE}; @@ -721,7 +741,7 @@ sub initialize_rhasspyTweaks { next; } - if ($line =~ m{\A[\s]*(timeouts|useGenericAttrs|timerSounds|confirmIntents|confirmIntentResponses)[\s]*=}x) { + if ($line =~ m{\A[\s]*(timeouts|useGenericAttrs|timerSounds|confirmIntents|confirmIntentResponses|ignoreKeywords)[\s]*=}x) { ($tweak, $values) = split m{=}x, $line, 2; $tweak = trim($tweak); return "Error in $line! No content provided!" if !length $values && $init_done; @@ -985,6 +1005,7 @@ sub _analyze_rhassypAttr { $specials->{setter} = $vencmd if defined $vencmd; $specials->{device} = $vendev if defined $vendev; $specials->{CustomCommand} = $named->{CustomCommand} if defined $named->{CustomCommand}; + $specials->{stopCommand} = $named->{stopCommand} if defined $named->{stopCommand}; $hash->{helper}{devicemap}{devices}{$device}{venetian_specials} = $specials if defined $vencmd || defined $vendev; } @@ -1050,10 +1071,10 @@ sub _analyze_genDevType { my @rooms; if (!defined AttrVal($device,"${prefix}Room", undef)) { - $attrv = AttrVal($device,'alexaRoom', undef); - push @rooms, split m{,}x, lc $attrv if $attrv; + $attrv = _clean_ignored_keywords( $hash,'rooms', AttrVal($device,'alexaRoom', undef)); + push @rooms, split m{,}x, $attrv if $attrv; - $attrv = AttrVal($device,'room',undef); + $attrv = _clean_ignored_keywords( $hash,'rooms', AttrVal($device,'room',undef)); push @rooms, split m{,}x, lc $attrv if $attrv; $rooms[0] = $hash->{defaultRoom} if !@rooms; } @@ -1067,8 +1088,8 @@ sub _analyze_genDevType { } $hash->{helper}{devicemap}{devices}{$device}->{rooms} = join q{,}, @rooms; - $attrv = AttrVal($device,'group', undef); - $hash->{helper}{devicemap}{devices}{$device}{groups} = lc $attrv if $attrv; + $attrv = _clean_ignored_keywords( $hash,'group', AttrVal($device,'group', undef)); + $hash->{helper}{devicemap}{devices}{$device}{groups} = $attrv if $attrv; my $hbmap = AttrVal($device, 'homeBridgeMapping', q{}); my $allset = getAllSets($device); @@ -1111,7 +1132,7 @@ sub _analyze_genDevType { return; } - if ( $gdt eq 'thermometer' ) { + if ( $gdt eq 'thermometer' || $gdt eq 'HumiditySensor' ) { my $r = $defs{$device}{READINGS}; if($r) { for (sort keys %{$r}) { @@ -1124,7 +1145,7 @@ sub _analyze_genDevType { return; } - if ( $gdt eq 'blind' ) { + if ( $gdt eq 'blind' || $gdt eq 'blinds' || $gdt eq 'shutter' ) { if ( $allset =~ m{\bdim([\b:\s]|\Z)}xms ) { my $maxval = InternalVal($device, 'TYPE', 'unknown') eq 'ZWave' ? 99 : 100; $currentMapping = @@ -1143,6 +1164,9 @@ sub _analyze_genDevType { SetNumeric => { setTarget => { cmd => 'pct', currentVal => 'pct', maxVal => '100', minVal => '0', step => '13', type => 'setTarget'} } }; } + if ( $allset =~ m{\b(stop)([\b:\s]|\Z)}xmsi ) { + $currentMapping->{SetNumeric}->{setTarget}->{cmdStop} = $1; + } $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; return; } @@ -1158,9 +1182,36 @@ sub _analyze_genDevType { $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; } + if ( $gdt eq 'motion' || $gdt eq 'contact' || $gdt eq 'ContactSensor' || $gdt eq 'lock' || $gdt eq 'presence') { + my $r = $defs{$device}{READINGS}; + $gdt = 'contact' if $gdt eq 'ContactSensor'; + if($r) { + for (sort reverse keys %{$r}) { + if ( $_ =~ m{\A(?state|$gdt)\z}x ) { + $currentMapping->{GetState}->{$gdt} = {currentVal => $+{id}, type => '$gdt' }; + } + } + } + if ( $gdt eq 'lock') { + $currentMapping->{SetOnOff} = {cmdOff => 'unlock', type => 'SetOnOff', cmdOn => 'lock'}; + } + $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; + return; + } return; } +sub _clean_ignored_keywords { + my $hash = shift // return; + my $keyword = shift // return; + my $toclean = shift // return; + return lc $toclean if !defined $hash->{helper}->{tweaks} + ||!defined $hash->{helper}->{tweaks}->{ignoreKeywords} + ||!defined $hash->{helper}->{tweaks}->{ignoreKeywords}->{$keyword}; + $toclean =~ s{\A$hash->{helper}->{tweaks}->{ignoreKeywords}->{$keyword}\z}{}gi; + return lc $toclean; +} + sub _analyze_genDevType_setter { my $hash = shift; my $device = shift; @@ -1226,6 +1277,30 @@ sub _analyze_genDevType_setter { return $mapping; } +sub initialize_rhasspyHotwords { + my $hash = shift // return; + my $attrVal = shift // return; + + for my $line (split m{\n}x, $attrVal) { + next if !length $line; + my ($hotword, $values) = split m{=}x, $line, 2; + my($unnamed, $named) = parseParams($values); + for my $site ( keys %{$named} ) { + if ( $named->{$site} =~ m{\A\{.*\}\z}x) { + my $err = perlSyntaxCheck( $named->{$site}, ("%DEVICE"=>"$hash->{NAME}", "%VALUE"=>"test", "%ROOM"=>"room") ); + return "$err in $line, $named->{$site}" if $err && $init_done; + } + } + $hotword = trim($hotword); + next if !$hotword; + if ( keys %{$named} ) { + $hash->{helper}{hotwords}->{$hotword} = $named; + } elsif (@{$unnamed}) { + $hash->{helper}{hotwords}->{$hotword}->{default} = join q{ }, @{$unnamed}; + } + } + return; +} sub perlExecute { my $hash = shift // return; @@ -2151,7 +2226,7 @@ sub Parse { # Name mit IODev vergleichen next if $ioname ne AttrVal($hash->{NAME}, 'IODev', ReadingsVal($hash->{NAME}, 'IODev', InternalVal($hash->{NAME}, 'IODev', 'none'))); next if IsDisabled( $hash->{NAME} ); - my $topicpart = qq{/$hash->{LANGUAGE}\.$hash->{fhemId}\[._]|hermes/dialogueManager|hermes/nlu/intentNotRecognized}; + my $topicpart = qq{/$hash->{LANGUAGE}\.$hash->{fhemId}\[._]|hermes/dialogueManager|hermes/nlu/intentNotRecognized|hermes/hotword/[^/]+/detected}; next if $topic !~ m{$topicpart}x; Log3($hash,5,"RHASSPY: [$hash->{NAME}] Parse (IO: ${ioname}): Msg: $topic => $value"); @@ -2278,6 +2353,15 @@ sub analyzeMQTTmessage { return \@updatedList; } + if ( $topic =~ m{\Ahermes/hotword/([^/]+)/detected}x ) { + return if !$hash->{handleHotword} && !defined $hash->{helper}{hotwords}; + my $hotword = $1; + my $ret = handleHotwordDetection($hash, $hotword, $data); + push @updatedList, $ret if $defs{$ret}; + push @updatedList, $hash->{NAME}; + return \@updatedList; + } + if ($mute) { $data->{requestType} = $message =~ m{${fhemId}.textCommand}x ? 'text' : 'voice'; respond( $hash, $data, q{ } ); @@ -2286,7 +2370,7 @@ sub analyzeMQTTmessage { } if ($topic =~ m{\Ahermes/nlu/intentNotRecognized}x && defined $siteId) { - handleIntentNotRecognized($hash, $data); + handleIntentNotRecognized($hash, $data) if $hash->{experimental}; return; } @@ -2400,26 +2484,6 @@ sub sendTextCommand { return IOWrite($hash, 'publish', qq{$topic $message}); } -=pod -sendSpeakCommand might need review; seems using https://rhasspy.readthedocs.io/en/latest/reference/#dialoguemanager (for details see also https://rhasspy-hermes.readthedocs.io/en/latest/api.html#rhasspyhermes.dialogue.DialogueAction and https://community.rhasspy.org/t/start-conversation-with-tts-and-start-listening/2099/2) with "init" => "type": "notification" is the more generic approach - -hermes/dialogueManager/startSession (JSON) - - Starts a new dialogue session (done automatically on hotword detected) - init: object - JSON object with one of two forms: - Action - type: string = "action" - required - canBeEnqueued: bool - true if session can be queued if there is already one (required) - text: string? = null - sentence to speak using text to speech - intentFilter: [string]? = null - valid intent names (null means all) - sendIntentNotRecognized: bool = false - send hermes/dialogueManager/intentNotRecognized if intent recognition fails - Notification - type: string = "notification" - required - text: string - sentence to speak using text to speech (required) - siteId: string = "default" - Hermes site ID - customData: string? = null - user-defined data passed to subsequent session messages -=cut - # Sprachausgabe / TTS über RHASSPY sub sendSpeakCommand { my $hash = shift; @@ -2450,37 +2514,36 @@ sub sendSpeakCommand { return IOWrite($hash, 'publish', qq{hermes/dialogueManager/startSession $json}); } -=pod -#old version -sub sendSpeakCommand { +# start intent recognition by Rhasspy service, see https://rhasspy.readthedocs.io/en/latest/reference/#nlu_query +sub text2intentRecognition { my $hash = shift; my $cmd = shift; - my $sendData = { - id => '0', - sessionId => '0'#, - #canBeEnqueued => 'true' + my $id = "$hash->{LANGUAGE}.$hash->{fhemId}" . time; + my $sendData = { + intentFilter => 'null', + id => $id, + sessionId => $id }; if (ref $cmd eq 'HASH') { - return 'speak with explicite params needs siteId and text as arguments!' if !defined $cmd->{siteId} || !defined $cmd->{text}; - $sendData->{siteId} = $cmd->{siteId}; - $sendData->{text} = $cmd->{text}; + return 'text2intent with explicite params needs siteId and text as arguments!' if !defined $cmd->{siteId} || !defined $cmd->{text}; + $sendData->{siteId} = $cmd->{siteId}; + $sendData->{input} = $cmd->{text}; + $sendData->{id} = $cmd->{id} // $id; + $sendData->{sessionId} = $cmd->{sessionId} // $id } else { #Beta-User: might need review, as parseParams is used by default...! - my $siteId = 'default'; - my $text = $cmd; my($unnamedParams, $namedParams) = parseParams($cmd); - - if (defined $namedParams->{siteId} && defined $namedParams->{text}) { - $sendData->{siteId} = $namedParams->{siteId}; - $sendData->{text} = $namedParams->{text}; + if (defined $namedParams->{siteId} || defined $namedParams->{text}) { + $sendData->{siteId} = $namedParams->{siteId} // shift @{$unnamedParams}; + $sendData->{input} = lc $namedParams->{text} // lc join q{ }, @{$unnamedParams}; } else { - return 'speak needs siteId and text as arguments!'; + $sendData->{siteId} = shift @{$unnamedParams}; + $sendData->{input} = lc join q{ }, @{$unnamedParams}; } } my $json = _toCleanJSON($sendData); - return IOWrite($hash, 'publish', qq{hermes/tts/say $json}); + return IOWrite($hash, 'publish', qq{hermes/nlu/query $json}); } -=cut # Send all devices, rooms, etc. to Rhasspy HTTP-API to update the slots sub updateSlots { @@ -2537,7 +2600,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)); + my @gdts = (qw(switch light media blind thermostat thermometer lock contact motion presence)); my @aliases = (); my @mainrooms = (); @@ -2546,8 +2609,11 @@ sub updateSlots { my @names = (); my @groupnames = (); my @devs = devspec2array("$hash->{devspec}"); + for my $device (@devs) { - if (AttrVal($device, 'genericDeviceType', '') eq $gdt) { + my $attrVal = AttrVal($device, 'genericDeviceType', ''); + my $gdtmap = { blind => 'blinds|shutter' , thermometer => 'HumiditySensor' , contact => 'ContactSensor'}; + if ($attrVal eq $gdt || defined $gdtmap->{$gdt} && $attrVal =~ m{\A$gdtmap->{$gdt}\z} ) { push @names, split m{,}x, $hash->{helper}{devicemap}{devices}{$device}->{names}; push @aliases, $hash->{helper}{devicemap}{devices}{$device}->{alias}; push @groupnames, split m{,}x, $hash->{helper}{devicemap}{devices}{$device}->{groups} if defined $hash->{helper}{devicemap}{devices}{$device}->{groups}; @@ -2556,10 +2622,10 @@ sub updateSlots { } @names = get_unique(\@names); @names = ('') if !@names && $noEmpty; - $deviceData->{qq(${language}.${fhemId}.Device-${gdt})} = \@names if @names; + $deviceData->{qq(${language}.${fhemId}.Device-$gdt)} = \@names if @names; @groupnames = get_unique(\@groupnames); @groupnames = ('') if !@groupnames && $noEmpty; - $deviceData->{qq(${language}.${fhemId}.Group-${gdt})} = \@groupnames if @groupnames; + $deviceData->{qq(${language}.${fhemId}.Group-$gdt)} = \@groupnames if @groupnames; } @mainrooms = get_unique(\@mainrooms); @mainrooms = ('') if !@mainrooms && $noEmpty; @@ -2736,9 +2802,17 @@ sub RHASSPY_ParseHttpResponse { readingsEndUpdate($hash, 1); return Log3($hash->{NAME}, 1, "JSON decoding error: $@"); } - #my $ref = decode_json($data); - my $siteIds = encode($cp,$ref->{dialogue}{satellite_site_ids}); - readingsBulkUpdate($hash, 'siteIds', $siteIds); + my $siteIds; + for (keys %{$ref}) { + next if !defined $ref->{$_}{satellite_site_ids}; + if ($siteIds) { + $siteIds .= ',' . encode($cp,$ref->{$_}{satellite_site_ids}); + } else { + $siteIds = encode($cp,$ref->{$_}{satellite_site_ids}); + } + } + my @ids = uniq(split q{,},$siteIds); + readingsBulkUpdate($hash, 'siteIds', join q{,}, @ids); } elsif ( $url =~ m{api/intents}ix ) { my $refb; @@ -2758,6 +2832,19 @@ sub RHASSPY_ParseHttpResponse { return; } +sub handleHotwordDetection { + my $hash = shift // return; + my $hotword = shift // return; + my $data = shift; + + my $siteId = $data->{siteId} // return; + + readingsSingleUpdate($hash, 'hotword', "$hotword $siteId", 1); + + return if !defined $hash->{helper}{hotwords} || !defined $hash->{helper}{hotwords}->{$hotword}; + my $command = $hash->{helper}{hotwords}->{$hotword}->{$siteId} // $hash->{helper}{hotwords}->{$hotword}->{default} // return; + return analyzeAndRunCmd($hash, $hash->{NAME}, $command, $hotword, $siteId); +} # Eingehender Custom-Intent sub handleCustomIntent { @@ -3387,7 +3474,9 @@ sub handleIntentSetNumeric { } else { # defined $change # Stellwert um Wert x ändern ("Mache Lampe um 20 heller" oder "Mache Lampe heller") #elsif ((!defined $unit || $unit ne 'Prozent') && defined $change && !$forcePercent) { - if ( ( !defined $unit || !$ispct ) && !$forcePercent ) { + if ( $change eq 'cmdStop' ) { + $newVal = $oldVal; + } elsif ( ( !defined $unit || !$ispct ) && !$forcePercent ) { $newVal = ($up) ? $oldVal + $diff : $oldVal - $diff; } # Stellwert um Prozent x ändern ("Mache Lampe um 20 Prozent heller" oder "Mache Lampe um 20 heller" bei forcePercent oder "Mache Lampe heller" bei forcePercent) @@ -3413,14 +3502,21 @@ sub handleIntentSetNumeric { return $hash->{NAME} if !defined $data->{'.inBulk'} && !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetNumeric' ); # execute Cmd - analyzeAndRunCmd($hash, $device, $cmd, $newVal); + $change ne 'cmdStop' + || !defined $mapping->{cmdStop} + ? analyzeAndRunCmd($hash, $device, $cmd, $newVal) + : analyzeAndRunCmd($hash, $device, $mapping->{cmdStop}); #venetian blind special my $specials = $hash->{helper}{devicemap}{devices}{$device}{venetian_specials}; if ( defined $specials ) { my $vencmd = $specials->{setter} // $cmd; my $vendev = $specials->{device} // $device; - analyzeAndRunCmd($hash, $vendev, defined $specials->{CustomCommand} ? $specials->{CustomCommand} :$vencmd , $newVal) if $device ne $vendev || $cmd ne $vencmd; + if ( $change ne 'cmdStop' ) { + analyzeAndRunCmd($hash, $vendev, defined $specials->{CustomCommand} ? $specials->{CustomCommand} :$vencmd , $newVal) if $device ne $vendev || $cmd ne $vencmd; + } elsif ( defined $specials->{stopCommand} ) { + analyzeAndRunCmd($hash, $vendev, $specials->{stopCommand}); + } } # get response @@ -4147,7 +4243,7 @@ sub handleIntentNotRecognized { $response =~ s{(\$\w+)}{$1}eegx; $data_old->{customData} = 'intentNotRecognized'; - return setDialogTimeout( $hash, $data_old, undef, $response ); # , $data_old->{intentNotRecognized} ); + return setDialogTimeout( $hash, $data_old, undef, $response ); } sub handleIntentCancelAction { @@ -4181,7 +4277,7 @@ sub handleIntentConfirmAction { my $mode = $data->{Mode}; #cancellation case - return handleIntentCancelAction($hash, $data) if $mode ne 'OK' && $mode ne 'Back' && $mode ne 'Next' ; + return handleIntentCancelAction($hash, $data) if !$mode || $mode ne 'OK' && $mode ne 'Back' && $mode ne 'Next'; #confirmed case my $identiy = qq($data->{sessionId}); @@ -4566,7 +4662,7 @@ So all parameters in define should be provided in the key=value form. In

Parameters:

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

-

Example for defining an MQTT2_CLIENT device and the Rhasspy device in FHEM:

-

defmod rhasspyMQTT2 MQTT2_CLIENT 192.168.1.122:12183
+

Examples for defining an MQTT2_CLIENT device and the Rhasspy device in FHEM: +

Additionals remarks on MQTT2-IOs:

Using a separate MQTT server (and not the internal MQTT2_SERVER) is highly recommended, as the Rhasspy scripts also use the MQTT protocol for internal (sound!) data transfers. Best way is to either use MQTT2_CLIENT (see above) or bridge only the relevant topics from mosquitto to MQTT2_SERVER (see e.g. http://www.steves-internet-guide.com/mosquitto-bridge-configuration for the principles). When using MQTT2_CLIENT, it's necessary to set clientOrder to include RHASSPY (as most likely it's the only module listening to the CLIENT it could be just set to attr <m2client> clientOrder RHASSPY)

Furthermore, you are highly encouraged to restrict subscriptions only to the relevant topics:

@@ -4593,7 +4701,8 @@ attr rhasspyMQTT2 subscriptions hermes/intent/+ hermes/dialogueManager/sessionSt

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

+hermes/nlu/intentNotRecognized
+hermes/hotword/+/detected

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

@@ -4780,6 +4889,7 @@ i="i am hungry" f="set Stove on" d="Stove" c="would you like roast pork"< @@ -4823,10 +4933,19 @@ i="i am hungry" f="set Stove on" d="Stove" c="would you like roast pork"< Example:

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

+ +

To execute any action requiring confirmation, you have to send an Mode:OK value by the ConfirmAction intent. Any other Mode key sent to ConfirmAction intent will be interpretad as cancellation request. For cancellation, you may alternatively use the CancelAction intent. Example:
+ [de.fhem:ConfirmAction]
+ ( yes, please do it | go on | that's ok | yes, please ){Mode:OK}
+ ( don't do it after all ){Mode}
+ [de.fhem:CancelAction]
+ ( let it be | oh no | cancel | cancellation ){Mode:Cancel} +

+

  • 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. +

    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.

  • @@ -4834,10 +4953,25 @@ i="i am hungry" f="set Stove on" d="Stove" c="would you like roast pork"<
  • 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.

  • + +
  • ignoreKeywords +

    You may have also some technically motivated settings in the attributes RHASSPY uses to generate slots, e.g. MQTT, alexa, homebridge or googleassistant in room attribute. The key-value pairs will sort the given value out while generating the content for the respective slot for key (atm. only rooms and group are supported). value will be treated as (case-insensitive) regex with need to exact match.
    + Example:

    ignoreKeywords=room=MQTT|alexa|homebridge|googleassistant|logics-.*
    + Note: requires restart to take full effect, will only affect content from general room, group or alexaRoom attributes.

    +
  • - - +
  • + rhasspyHotwords +

    Define custom reactions as soon as a specific hotword is detected. This does not require any specific configuration on any other FHEM device.
    + One hotword per line, syntax is either a simple and an extended version.

    + Examples:
    +

    bumblebee_linux = set amplifier2 mute on
    + porcupine_linux = livingroom="set amplifier mute on" default={Log3($DEVICE,3,"device $DEVICE - room $ROOM - value $VALUE")}

    +

    First example will execute the command for all incoming messages for the respective hotword, second will decide based on the given siteId keyword; $DEVICE is evaluated to RHASSPY name, $ROOM to siteId and $VALUE to the hotword.
    + default is optional. If set, this action will be executed for all siteIds without match to other keywords.
    + Additionally, if either rhasspyHotwords ia set or key handleHotword in DEF is activated, the reading hotword will be filled with hotword plus siteId to also allow arbitrary event handling.
    NOTE: As all hotword messages are sent to a common topic structure, you may need additional measures to distinguish between several RHASSPY instances, e.g. by restricting subscriptions and/or using different entries in this attribute.

    +
  • forceNEXT

    If set to 1, RHASSPY will forward incoming messages also to further MQTT2-IO-client modules like MQTT2_DEVICE, even if the topic matches to one of it's own subscriptions. By default, these messages will not be forwarded for better compability with autocreate feature on MQTT2_DEVICE. See also clientOrder attribute in MQTT2 IO-type commandrefs; setting this in one instance of RHASSPY might affect others, too.

    @@ -4930,6 +5064,8 @@ yellow=rgb FFFF00

    If set, the slat target position will be set to the same level than the main device.

  • @@ -4971,29 +5107,46 @@ yellow=rgb FFFF00

    Intents

    -

    The following intents are directly implemented in RHASSPY code: +

    The following intents are directly implemented in RHASSPY code and the keywords used by them in sentences.ini are as follows: