diff --git a/fhem/contrib/RHASSPY/10_RHASSPY.pm b/fhem/contrib/RHASSPY/10_RHASSPY.pm index 5ea1b8c81..73f6c0626 100644 --- a/fhem/contrib/RHASSPY/10_RHASSPY.pm +++ b/fhem/contrib/RHASSPY/10_RHASSPY.pm @@ -106,6 +106,7 @@ my $languagevars = { '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', + 'RequestChoiceGeneric' => 'there are several options, choose between $options', 'DefaultChoiceNoOutstanding' => "no choice expected", 'NoMinConfidence' => 'minimum confidence not given, level is $confidence', 'timerSet' => { @@ -243,7 +244,7 @@ BEGIN { Log3 defs attr cmds modules L DAYSECONDS HOURSECONDS MINUTESECONDS - init_done + init_done fhem_started InternalTimer RemoveInternalTimer AssignIoPort @@ -334,7 +335,7 @@ sub Define { $hash->{defaultRoom} = $defaultRoom; my $language = $h->{language} // shift @{$anon} // lc AttrVal('global','language','en'); - $hash->{MODULE_VERSION} = '0.5.25'; + $hash->{MODULE_VERSION} = '0.5.26a'; $hash->{baseUrl} = $Rhasspy; initialize_Language($hash, $language) if !defined $hash->{LANGUAGE} || $hash->{LANGUAGE} ne $language; $hash->{LANGUAGE} = $language; @@ -875,7 +876,7 @@ sub initialize_rhasspyTweaks { sub configure_DialogManager { my $hash = shift // return; my $siteId = shift // 'null'; #ReadingsVal( $hash->{NAME}, 'siteIds', 'default' ) // return; - my $toDisable = shift // [qw(ConfirmAction CancelAction ChoiceRoom ChoiceDevice)]; + my $toDisable = shift // [qw(ConfirmAction CancelAction Choice ChoiceRoom ChoiceDevice)]; my $enable = shift // q{false}; my $timer = shift; my $retArr = shift; @@ -989,7 +990,7 @@ sub init_custom_intents { sub initialize_devicemap { my $hash = shift // return; - + Log3($hash->{NAME}, 5, "initialize_devicemap called"); my $devspec = $hash->{devspec}; delete $hash->{helper}{devicemap}; @@ -1002,6 +1003,7 @@ sub initialize_devicemap { _analyze_genDevType($hash, $_) if $hash->{useGenericAttrs}; _analyze_rhassypAttr($hash, $_); } + InternalTimer(time+125, \&initialize_devicemap, $hash ) if $fhem_started + 90 > time; return; } @@ -1326,11 +1328,16 @@ sub _analyze_genDevType { } if ( $gdt eq 'info' ) { - my $r = $defs{$device}{READINGS}; $currentMapping->{GetState}->{$gdt} = {currentVal => 'STATE', type => 'STATE' }; $currentMapping = _analyze_genDevType_setter( $hash, $device, $allset, $currentMapping ); $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; } + + if ( $gdt eq 'scene' ) { + $currentMapping = _analyze_genDevType_setter( $hash, $device, $allset, $currentMapping ); + $hash->{helper}{devicemap}{devices}{$device}{intents} = $currentMapping; + } + return; } @@ -1372,10 +1379,12 @@ sub _analyze_genDevType_setter { my $mapping = shift // {}; my $allValMappings = { - MediaControls => { + MediaControls => { cmdPlay => 'play', cmdPause => 'pause' ,cmdStop => 'stop', cmdBack => 'previous', cmdFwd => 'next', chanUp => 'channelUp', chanDown => 'channelDown' }, - GetState => { + GetState => { update => 'reread|update|reload' }, + SetScene => { + cmdBack => 'previousScene', cmdFwd => 'nextScene' } }; for my $okey ( keys %{$allValMappings} ) { my $ikey = $allValMappings->{$okey}; @@ -1420,7 +1429,6 @@ sub _analyze_genDevType_setter { my $clscene = $scname; # cleanup HUE scenes if ($clscene =~ m{[#]}xms) { - #next if $clscene =~ m{[#]\[id}xms; $clscene = (split m{[#]\[id}xms, $clscene)[0] if $clscene =~ m{[#]\[id}xms; $clscene =~ s{[#]}{ }gxm; $scname =~ s{.*[#]\[(id=.+)]}{$1}xms if $scname =~ m{[#]\[id}xms; @@ -1914,7 +1922,7 @@ sub getAllRhasspyScenes { push @names, split m{,}x, $hash->{helper}{devicemap}{devices}{$device}->{names}; my $scenes = $hash->{helper}{devicemap}{devices}{$device}{intents}{SetScene}->{SetScene}; for (keys %{$scenes}) { - push @sentences, qq{( $scenes->{$_} ){Scene:$_}}; + push @sentences, qq{( $scenes->{$_} ){Scene:$_}} if $_ ne 'cmdBack' && $_ ne 'cmdFwd' ; } } @@ -2773,10 +2781,13 @@ my $dispatchFns = { GetTime => \&handleIntentGetTime, GetDate => \&handleIntentGetDate, SetTimer => \&handleIntentSetTimer, + GetTimer => \&handleIntentGetTimer, + Timer => \&handleIntentSetTimer, ConfirmAction => \&handleIntentConfirmAction, CancelAction => \&handleIntentCancelAction, ChoiceRoom => \&handleIntentChoiceRoom, ChoiceDevice => \&handleIntentChoiceDevice, + Choice => \&handleIntentChoice, MsgDialog => \&handleIntentMsgDialog, ReSpeak => \&handleIntentReSpeak }; @@ -3379,7 +3390,7 @@ sub respond { } elsif ( $delay ) { $sendData->{text} = $response; $topic = 'continueSession'; - my @ca_strings = configure_DialogManager($hash,$data->{siteId}, [qw(ConfirmAction ChoiceRoom ChoiceDevice)], 'false', undef, 1 ); + my @ca_strings = configure_DialogManager($hash,$data->{siteId}, [qw(ConfirmAction Choice ChoiceRoom ChoiceDevice)], 'false', undef, 1 ); $sendData->{intentFilter} = [@ca_strings]; } else { $sendData->{text} = $response; @@ -3463,7 +3474,7 @@ sub sendSpeakCommand { my $hash = shift; my $cmd = shift; - my $sendData = { + my $sendData = { init => { type => 'notification', canBeEnqueued => 'true', @@ -3472,13 +3483,13 @@ sub sendSpeakCommand { }; 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->{siteId} = _getSiteIdbyRoom($hash, $cmd->{siteId}); $sendData->{init}->{text} = $cmd->{text}; } else { my($unnamedParams, $namedParams) = parseParams($cmd); if (defined $namedParams->{siteId} && defined $namedParams->{text}) { - $sendData->{siteId} = $namedParams->{siteId}; + $sendData->{siteId} = _getSiteIdbyRoom($hash, $namedParams->{siteId}); $sendData->{init}->{text} = $namedParams->{text}; } else { return 'speak needs siteId and text as arguments!'; @@ -3488,12 +3499,25 @@ sub sendSpeakCommand { return IOWrite($hash, 'publish', qq{hermes/dialogueManager/startSession $json}); } +sub _getSiteIdbyRoom { + my $hash = shift // return; + my $siteId = shift // return; + + my $siteIdList = ReadingsVal($hash->{NAME}, 'siteIds', $siteId); + my $siteId2 = ReadingsVal($hash->{NAME}, "room2siteId_$siteId", $siteId); + for my $id ($siteId2, $siteId) { + return $1 if $siteIdList =~ m{\b($id)(?:[,]|\Z)}xmsi; + return $1 if $siteIdList =~ m{\b($id[^,]+)(?:[,]|\Z)}xmsi; + } + return $siteId; +} + # start intent recognition by Rhasspy service, see https://rhasspy.readthedocs.io/en/latest/reference/#nlu_query sub msgDialog { my $hash = shift; my $cmd = shift; - readingsSingleUpdate($hash,"enableMsgDialog", $cmd eq 'enable' ? 1 : 0 ,1); + readingsSingleUpdate($hash,'enableMsgDialog', $cmd eq 'enable' ? 1 : 0 ,1); return initialize_msgDialog($hash) if $cmd eq 'enable'; return disable_msgDialog($hash); @@ -4416,7 +4440,7 @@ sub handleIntentSetNumeric { my $all = $device->[2]; my $choice = $device->[3]; $data->{customData} = $all; - my $toActivate = $choice eq 'RequestChoiceDevice' ? [qw(ChoiceDevice CancelAction)] : [qw(ChoiceRoom CancelAction)]; + my $toActivate = $choice eq 'RequestChoiceDevice' ? [qw(ChoiceDevice Choice CancelAction)] : [qw(ChoiceRoom Choice 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); @@ -4575,7 +4599,7 @@ sub handleIntentGetNumeric { my $all = $device->[2]; my $choice = $device->[3]; $data->{customData} = $all; - my $toActivate = $choice eq 'RequestChoiceDevice' ? [qw(ChoiceDevice CancelAction)] : [qw(ChoiceRoom CancelAction)]; + my $toActivate = $choice eq 'RequestChoiceDevice' ? [qw(ChoiceDevice Choice CancelAction)] : [qw(ChoiceRoom Choice 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); @@ -4655,6 +4679,7 @@ sub handleIntentGetState { my $room = getRoomName($hash, $data); my $type = $data->{Type} // $data->{type}; + my @scenes; my $deviceNames; my $sceneNames; if ($device eq 'RHASSPY') { $type //= 'generic'; return respond( $hash, $data, getResponse($hash, 'NoValidData')) if $type !~ m{\Ageneric|control|info|scenes|rooms\z}; @@ -4663,11 +4688,12 @@ sub handleIntentGetState { if ( $type eq 'rooms' ) { my @rooms = getAllRhasspyMainRooms($hash); $roomNames = _array2andString( $hash, \@rooms); + $response =~ s{(\$\w+)}{$1}eegx; + return respond( $hash, $data, $response); } - my @names; my @scenes; + my @names; 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'; @@ -4690,8 +4716,9 @@ sub handleIntentGetState { @names = uniq(@names); @scenes = uniq(@scenes) if @scenes; - my $deviceNames = _array2andString( $hash, \@names ); - my $sceneNames = !@scenes ? '' : _array2andString( $hash, \@scenes ); + $deviceNames = _array2andString( $hash, \@names ); + $sceneNames = !@scenes ? '' : _array2andString( $hash, \@scenes ); + $response =~ s{(\$\w+)}{$1}eegx; return respond( $hash, $data, $response); } @@ -4699,7 +4726,18 @@ sub handleIntentGetState { my $deviceName = $device; my $intent = 'GetState'; - $device = getDeviceByName($hash, $room, $device); + $device = getDeviceByName($hash, $room, $device) // return respond( $hash, $data, getResponse($hash, 'NoDeviceFound') ); + + if ( $type eq 'scenes' ) { + $response = getResponse( $hash, 'getRHASSPYOptions', $type ); + @scenes = values %{$hash->{helper}{devicemap}{devices}{$device}{intents}{SetScene}->{SetScene}}; + @scenes = uniq(@scenes) if @scenes; + $sceneNames = !@scenes ? '' : _array2andString( $hash, \@scenes ); + $deviceNames = $deviceName; + $response =~ s{(\$\w+)}{$1}eegx; + return respond( $hash, $data, $response); + } + $type //= 'GetState'; my $mapping = getMapping($hash, $device, 'GetState', $type) // return respond( $hash, $data, getResponse($hash, 'NoMappingFound') ); @@ -4712,7 +4750,7 @@ 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} || $data->{Type} ) { + } elsif ( defined $data->{type} || defined $data->{Type} ) { my $reading = $data->{Reading} // 'STATE'; $response = getResponse( $hash, 'getStateResponses', $type ) // getResponse( $hash, 'NoValidIntentResponse') ; $response =~ s{(\$\w+)}{$1}eegx; @@ -4777,7 +4815,7 @@ sub handleIntentSetScene{ my $data = shift // return; Log3($hash->{NAME}, 5, "handleIntentSetScene called"); - return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !defined $data->{Scene}; + return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !defined $data->{Scene} && (!defined $data->{Get} || $data->{Get} ne 'scenes'); # Device AND Scene are optimum exist @@ -4787,6 +4825,22 @@ sub handleIntentSetScene{ my $scene = $data->{Scene}; my $device = getDeviceByName($hash, $room, $data->{Device}); my $mapping = getMapping($hash, $device, 'SetScene'); + + #Welche (Szenen | Szenarien | Einstellungen){Get:scenes} (kennt|kann) [(der | die | das)] $de.fhem.Device-scene{Device} + if ( defined $data->{Get} && $data->{Get} eq 'scenes' ) { + delete $data->{Get}; + my $response = getResponse( $hash, 'RequestChoiceGeneric' ); + my @scenes = values %{$hash->{helper}{devicemap}{devices}{$device}{intents}{SetScene}->{SetScene}}; + @scenes = uniq(@scenes) if @scenes; + my $options = !@scenes ? '' : _array2andString( $hash, \@scenes ); + $response =~ s{(\$\w+)}{$1}eegx; + + #until now: only extended test code + $data->{customData} = join q{,}, @scenes; + my $toActivate = [qw(Choice CancelAction)]; + return setDialogTimeout($hash, $data, _getDialogueTimeout($hash), $response, $toActivate); + } + # restore HUE scenes $scene = qq([$scene]) if $scene =~ m{id=.+}xms; @@ -4797,6 +4851,7 @@ sub handleIntentSetScene{ return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetScene' ); my $cmd = qq(scene $scene); + $cmd = $scene if $scene eq 'cmdBack' || $scene eq 'cmdFwd'; # execute Cmd analyzeAndRunCmd($hash, $device, $cmd); @@ -5123,8 +5178,8 @@ sub handleIntentSetColorGroup { -# Handle incoming SetTimer intents -sub handleIntentSetTimer { +# Handle incoming Timer, SetTimer and GetTimer intents +sub handleIntentTimer { my $hash = shift; my $data = shift // return; my $siteId = $data->{siteId} // return; @@ -5256,6 +5311,23 @@ sub handleIntentSetTimer { return $name; } +sub handleIntentGetTimer { + my $hash = shift; + my $data = shift // return; + my $siteId = $data->{siteId} // return; + $data->{GetTimer} = 'redirected from intent GetTimer'; + return handleIntentTimer($hash, $data); +} + +sub handleIntentSetTimer { + my $hash = shift; + my $data = shift // return; + my $siteId = $data->{siteId} // return; + $data->{'.remark'} = 'redirected from intent SetTimer'; + return handleIntentTimer($hash, $data); +} + + sub handleIntentNotRecognized { my $hash = shift // return; @@ -5381,11 +5453,11 @@ sub handleIntentConfirmAction { return $device; } -sub handleIntentChoiceRoom { +sub handleIntentChoice { my $hash = shift // return; my $data = shift // return; - Log3($hash->{NAME}, 5, 'handleIntentChoiceRoom called'); + Log3($hash->{NAME}, 5, 'handleIntentChoice called'); my $identity = qq($data->{sessionId}); my $data_old = $hash->{helper}{'.delayed'}->{$identity}; @@ -5394,10 +5466,9 @@ sub handleIntentChoiceRoom { return respond( $hash, $data, getResponse( $hash, 'DefaultChoiceNoOutstanding' ) ) if !defined $data_old; - $data_old->{siteId} = $data->{siteId}; - $data_old->{sessionId} = $data->{sessionId}; - $data_old->{requestType} = $data->{requestType}; - $data_old->{Room} = $data->{Room}; + for ( qw( siteId sessionId requestType Room Device Scene ) ) { + $data_old->{$_} = $data->{$_} if defined $data->{$_}; + } my $intent = $data_old->{intent}; my $device = $hash->{NAME}; @@ -5410,34 +5481,23 @@ sub handleIntentChoiceRoom { return $device; } + +sub handleIntentChoiceRoom { + my $hash = shift // return; + my $data = shift // return; + + Log3($hash->{NAME}, 5, 'handleIntentChoiceRoom called'); + + return handleIntentChoice($hash, $data); +} + sub handleIntentChoiceDevice { my $hash = shift // return; my $data = shift // return; Log3($hash->{NAME}, 5, 'handleIntentChoiceDevice called'); - #my $data_old = $data->{customData}; - my $identity = qq($data->{sessionId}); - my $data_old = $hash->{helper}{'.delayed'}->{$identity}; - delete $hash->{helper}{'.delayed'}{$identity}; - deleteSingleRegIntTimer($identity, $hash); - - return respond( $hash, $data, getResponse( $hash, 'DefaultChoiceNoOutstanding' ) ) if ! defined $data_old; - - $data_old->{siteId} = $data->{siteId}; - $data_old->{sessionId} = $data->{sessionId}; - $data_old->{requestType} = $data->{requestType}; - $data_old->{Device} = $data->{Device}; - - my $intent = $data_old->{intent}; - my $device = $hash->{NAME}; - - # Passenden Intent-Handler aufrufen - if (ref $dispatchFns->{$intent} eq 'CODE') { - $device = $dispatchFns->{$intent}->($hash, $data_old); - } - - return $device; + return handleIntentChoice($hash, $data); } @@ -5463,7 +5523,7 @@ sub setPlayWav { return 'playWav needs siteId and path to file as parameters!' if !defined $cmd->{siteId} || !defined $cmd->{path}; - my $siteId = $cmd->{siteId}; + my $siteId = _getSiteIdbyRoom($hash, $cmd->{siteId}); my $filename = $cmd->{path}; my $repeats = $cmd->{repeats}; my $encoding = q{:raw :bytes}; @@ -5708,7 +5768,12 @@ So all parameters in define should be provided in the key=value form. In
genericDeviceType
(switch, light, thermostat, thermometer, blind and media), so it will add genericDeviceType
to the global attribute list and activate RHASSPY's feature to estimate appropriate settings - similar to rhasspyMapping. useGenericAttrs=0
will deactivate this. (do not set this unless you know what you are doing!). Note: homebridgeMapping
atm. is not used as source for appropriate mappings in RHASSPY.genericDeviceType
(switch, light, thermostat, thermometer, blind, media, scene and info), so it will add genericDeviceType
to the global attribute list and activate RHASSPY's feature to estimate appropriate settings - similar to rhasspyMapping. useGenericAttrs=0
will deactivate this. (do not set this unless you know what you are doing!). Notes:
+ homebridgeMapping
atm. is not used as source for appropriate mappings in RHASSPY.encoding=cp-1252
. Do not set this unless you experience encoding problems!setreading siteId2room_mobile_phone1 kitchen
will force RHASSPY to link your satellite phone1 kitchen to kitchen as room.
+