diff --git a/fhem/contrib/RHASSPY/10_RHASSPY.pm b/fhem/contrib/RHASSPY/10_RHASSPY.pm
index 6bef856cf..952b7e4bf 100644
--- a/fhem/contrib/RHASSPY/10_RHASSPY.pm
+++ b/fhem/contrib/RHASSPY/10_RHASSPY.pm
@@ -1,7 +1,7 @@
# $Id$
###########################################################################
#
-# FHEM RHASSPY modul (https://github.com/rhasspy)
+# FHEM RHASSPY module (https://github.com/rhasspy)
#
# Originally written 2018 by Tobias Wiedenmann (Thyraz)
# as FHEM Snips.ai module (thanks to Matthias Kleine)
@@ -33,11 +33,13 @@ use strict;
use warnings;
use Carp qw(carp);
use GPUtils qw(:all);
-use JSON;
+use JSON qw(decode_json);
use Encode;
use HttpUtils;
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 FHEM::Core::Timer::Register qw(:ALL);
@@ -56,7 +58,7 @@ my %sets = (
textCommand => [],
trainRhasspy => [qw(noArg)],
fetchSiteIds => [qw(noArg)],
- update => [qw(devicemap devicemap_only slots slots_no_training language all)],
+ update => [qw(devicemap devicemap_only slots slots_no_training language intent_filter all)],
volume => []
);
@@ -92,6 +94,7 @@ my $languagevars = {
'DefaultConfirmationReceived' => "ok will do it",
'DefaultConfirmationNoOutstanding' => "no command is awaiting confirmation",
'DefaultConfirmationRequest' => 'please confirm switching $device $wanted',
+ 'DefaultConfirmationRequestRawInput' => 'please confirm: $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",
@@ -286,15 +289,13 @@ BEGIN {
parseParams
ResolveDateWildcards
HttpUtils_NonblockingGet
- round
- strftime
FmtDateTime
makeReadingName
FileRead
- trim
- looks_like_number
getAllSets
+ trim
))
+ #round
};
@@ -303,6 +304,7 @@ my @topics = qw(
hermes/intent/+
hermes/dialogueManager/sessionStarted
hermes/dialogueManager/sessionEnded
+ hermes/nlu/intentNotRecognized
);
sub Initialize {
@@ -345,7 +347,7 @@ sub Define {
$hash->{defaultRoom} = $defaultRoom;
my $language = $h->{language} // shift @{$anon} // lc AttrVal('global','language','en');
- $hash->{MODULE_VERSION} = '0.4.21';
+ $hash->{MODULE_VERSION} = '0.4.36';
$hash->{baseUrl} = $Rhasspy;
initialize_Language($hash, $language) if !defined $hash->{LANGUAGE} || $hash->{LANGUAGE} ne $language;
$hash->{LANGUAGE} = $language;
@@ -373,17 +375,16 @@ sub firstInit {
# IO
AssignIoPort($hash);
- my $IODev = AttrVal( $name, 'IODev', ReadingsVal( $name, 'IODev', InternalVal($name, 'IODev', undef )));
+ my $IODev = AttrVal( $name, 'IODev', ReadingsVal( $name, 'IODev', InternalVal($name, 'IODev', undef )->{NAME}));
return if !$init_done || !defined $IODev;
RemoveInternalTimer($hash);
deleteAllRegIntTimer($hash);
-
- IOWrite($hash, 'subscriptions', join q{ }, @topics) if InternalVal($IODev,'TYPE',undef) eq 'MQTT2_CLIENT';
fetchSiteIds($hash) if !ReadingsVal( $name, 'siteIds', 0 );
initialize_rhasspyTweaks($hash, AttrVal($name,'rhasspyTweaks', undef ));
- configure_DialogManager($hash);
+ fetchIntents($hash);
+ IOWrite($hash, 'subscriptions', join q{ }, @topics) if InternalVal($IODev,'TYPE',undef) eq 'MQTT2_CLIENT';
initialize_devicemap($hash);
return;
@@ -431,13 +432,13 @@ sub initialize_prefix {
return if defined $old_prefix && $prefix eq $old_prefix;
# provide attributes "rhasspyName" etc. for all devices
- addToAttrList("${prefix}Name");
- addToAttrList("${prefix}Room");
- addToAttrList("${prefix}Mapping:textField-long");
+ addToAttrList("${prefix}Name",'RHASSPY');
+ addToAttrList("${prefix}Room",'RHASSPY');
+ addToAttrList("${prefix}Mapping:textField-long",'RHASSPY');
#addToAttrList("${prefix}Channels:textField-long");
#addToAttrList("${prefix}Colors:textField-long");
- addToAttrList("${prefix}Group:textField");
- addToAttrList("${prefix}Specials:textField-long");
+ addToAttrList("${prefix}Group:textField",'RHASSPY');
+ addToAttrList("${prefix}Specials:textField-long",'RHASSPY');
return if !$init_done || !defined $old_prefix;
my @devs = devspec2array("$hash->{devspec}");
@@ -573,11 +574,15 @@ sub Set {
initialize_devicemap($hash);
return updateSlots($hash);
}
+ if ($values[0] eq 'intent_filter') {
+ return fetchIntents($hash);
+ }
if ($values[0] eq 'all') {
initialize_Language($hash, $hash->{LANGUAGE});
initialize_devicemap($hash);
$hash->{'.needTraining'} = 1;
- return updateSlots($hash);
+ updateSlots($hash);
+ return fetchIntents($hash);
}
}
@@ -704,7 +709,7 @@ sub initialize_rhasspyTweaks {
next;
}
- if ($line =~ m{\A[\s]*(timeouts|useGenericAttrs|timerSounds)[\s]*=}x) {
+ if ($line =~ m{\A[\s]*(timeouts|useGenericAttrs|timerSounds|confirmIntents)[\s]*=}x) {
($tweak, $values) = split m{=}x, $line, 2;
$tweak = trim($tweak);
return "Error in $line! No content provided!" if !length $values && $init_done;
@@ -713,27 +718,53 @@ sub initialize_rhasspyTweaks {
$hash->{helper}{tweaks}{$tweak} = $namedParams;
next;
}
+ if ($line =~ m{\A[\s]*(intentFilter)[\s]*=}x) {
+ ($tweak, $values) = split m{=}x, $line, 2;
+ $tweak = trim($tweak);
+ return "Error in $line! No content provided!" if !length $values && $init_done;
+ my($unnamedParams, $namedParams) = parseParams($values);
+ return "Error in $line! Provide at least one item!" if ( !@{$unnamedParams} && !keys %{$namedParams} ) && $init_done;
+ for ( @{$unnamedParams} ) {
+ $namedParams->{$_} = 'false';
+ }
+ for ( keys %{$namedParams} ) {
+ $namedParams->{$_} = 'false' if $namedParams->{$_} ne 'false' && $namedParams->{$_} ne 'true';
+ }
+ $hash->{helper}{tweaks}{$tweak} = $namedParams;
+ next;
+ }
}
+ return configure_DialogManager($hash) if $init_done;
return;
}
sub configure_DialogManager {
my $hash = shift // return;
- my $siteId = shift;
+ my $siteId = shift // 'null'; #ReadingsVal( $hash->{NAME}, 'siteIds', 'default' ) // return;
my $toDisable = shift // [qw(ConfirmAction CancelAction ChoiceRoom ChoiceDevice)];
my $enable = shift // q{false};
- #return if !$hash->{testing};
+ my $timer = shift;
+
+ #option to delay execution to make reconfiguration last action after everything else has been done and published.
+ if ( defined $timer ) {
+
+ my $fnHash = resetRegIntTimer( $siteId, time + looks_like_number($timer) ? $timer : 0, \&RHASSPY_configure_DialogManager, $hash, 0);
+ $fnHash->{toDisable} = $toDisable;
+ $fnHash->{enable} = $enable;
+ return;
+ }
#loop for global initialization or for several siteId's
- if (!defined $siteId || $siteId =~ m{,}xms) {
- $siteId = ReadingsVal( $hash->{NAME}, 'siteIds', 'default' ) if !defined $siteId;
+ if ( $siteId =~ m{,}xms ) {
my @siteIds = split m{,}xms, $siteId;
for (@siteIds) {
configure_DialogManager($hash, $_, $toDisable, $enable);
}
+ return;
}
+ my @intents = split m{,}xm, ReadingsVal( $hash->{NAME}, 'intents', '' );
my $language = $hash->{LANGUAGE};
my $fhemId = $hash->{fhemId};
@@ -750,24 +781,40 @@ hermes/dialogueManager/configure (JSON)
https://rhasspy-hermes-app.readthedocs.io/en/latest/usage.html#continuing-a-session
=cut
+ my $sId = $siteId eq 'null' ? 'null' : qq("$siteId");
+
my @disabled;
+ my $matches = join q{|}, @{$toDisable};
+ for (@intents) {
+ last if $enable eq 'true';
+ next if $_ =~ m{$matches}ms;
+ my $defaults = {intentId => "$_", enable => 'true'} ;
+ $defaults = {intentId => "$_", enable => $hash->{helper}{tweaks}->{intentFilter}->{$_}} if defined $hash->{helper}->{tweaks} && defined $hash->{helper}{tweaks}->{intentFilter} && defined $hash->{helper}{tweaks}->{intentFilter}->{$_};
+ push @disabled, $defaults;
+ }
for (@{$toDisable}) {
my $id = qq(${language}.${fhemId}:$_);
my $disable = {intentId => "$id", enable => "$enable"};
push @disabled, $disable;
}
- #my $disable = {intentId => [@disabled], enable => "$enable"};
my $sendData = {
siteId => $siteId,
intents => [@disabled]
};
- my $json = toJSON($sendData);
+ my $json = _toCleanJSON($sendData);
IOWrite($hash, 'publish', qq{hermes/dialogueManager/configure $json});
return;
}
+
+sub RHASSPY_configure_DialogManager {
+ my $fnHash = shift // return;
+ return configure_DialogManager( $fnHash->{HASH}, $fnHash->{MODIFIER}, $fnHash->{toDisable}, $fnHash->{enable} );
+}
+
+
sub init_custom_intents {
my $hash = shift // return;
my $attrVal = shift // return;
@@ -946,6 +993,9 @@ sub _analyze_rhassypAttr {
$hash->{helper}{devicemap}{devices}{$device}{intents}{SetScene}->{SetScene} = $combined
: delete $hash->{helper}{devicemap}{devices}{$device}{intents}->{SetScene};
}
+ if ($key eq 'confirm') {
+ $hash->{helper}{devicemap}{devices}{$device}{confirmIntents} = $val;
+ }
}
my @groups;
@@ -1188,15 +1238,12 @@ sub RHASSPY_DialogTimeout {
my $identiy = $fnHash->{MODIFIER};
my $data = shift // $hash->{helper}{'.delayed'}->{$identiy};
- delete $hash->{helper}{'.delayed'}{$identiy};
+ my $siteId = $data->{siteId};
+
deleteSingleRegIntTimer($identiy, $hash, 1);
- my $siteId = $data->{siteId};
- my $toDisable = defined $data->{'.ENABLED'} ? $data->{'.ENABLED'} : [qw(ConfirmAction CancelAction)];
-
- my $response = $hash->{helper}{lng}->{responses}->{DefaultConfirmationTimeout};
- respond ($hash, $data->{requestType}, $data->{sessionId}, $siteId, $response);
- configure_DialogManager($hash, $siteId, $toDisable, 'false');
+ respond( $hash, $data, getResponse( $hash, 'DefaultConfirmationTimeout' ) );
+ delete $hash->{helper}{'.delayed'}{$identiy};
return;
}
@@ -1204,47 +1251,43 @@ sub RHASSPY_DialogTimeout {
sub setDialogTimeout {
my $hash = shift // return;
my $data = shift // return;
- my $timeout = shift;
+ my $timeout = shift // _getDialogueTimeout($hash);
my $response = shift;
my $toEnable = shift // [qw(ConfirmAction CancelAction)];
my $siteId = $data->{siteId};
- $data->{'.ENABLED'} = $toEnable;
+ $data->{'.ENABLED'} = $toEnable; #dialog
my $identiy = qq($data->{sessionId});
- $response = $hash->{helper}{lng}->{responses}->{DefaultConfirmationReceived} if $response eq 'default';
+ $response = getResponse($hash, 'DefaultConfirmationReceived') if $response eq 'default';
$hash->{helper}{'.delayed'}{$identiy} = $data;
resetRegIntTimer( $identiy, time + $timeout, \&RHASSPY_DialogTimeout, $hash, 0);
#interactive dialogue as described in https://rhasspy.readthedocs.io/en/latest/reference/#dialoguemanager_continuesession and https://docs.snips.ai/articles/platform/dialog/multi-turn-dialog
my @ca_strings;
+ $toEnable = split m{,}, $toEnable if ref $toEnable ne 'ARRAY';
for (@{$toEnable}) {
my $id = qq{$hash->{LANGUAGE}.$hash->{fhemId}:$_};
push @ca_strings, $id;
}
-
- #my $ca_part = qq{$hash->{LANGUAGE}.$hash->{fhemId}:ConfirmAction};
- #push @ca_strings, $ca_part;
- #$ca_part = qq{$hash->{LANGUAGE}.$hash->{fhemId}:CancelAction};
- #push @ca_strings, $ca_part;
+
my $reaction = ref $response eq 'HASH'
? $response
: { text => $response,
intentFilter => [@ca_strings],
- #customData => $data
+ sendIntentNotRecognized => 'true', #'false',
+ customData => $data->{customData}
};
- configure_DialogManager($hash, $siteId, $toEnable, 'true');
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $reaction);
-
+ respond( $hash, $data, $reaction );
+
my $toTrigger = $hash->{'.toTrigger'} // $hash->{NAME};
delete $hash->{'.toTrigger'};
return $toTrigger;
}
-#from https://stackoverflow.com/a/43873983, modified...
sub get_unique {
my $arr = shift;
my $sorted = shift; #true if shall be sorted (longest first!)
@@ -1343,7 +1386,8 @@ sub getAllRhasspyNames {
return get_unique(\@devices, 1 );
}
-# Alle Raumbezeichnungen sammeln
+
+# Get all room names with Rhasspy relevance
sub getAllRhasspyRooms {
my $hash = shift // return;
return keys %{$hash->{helper}{devicemap}{rhasspyRooms}} if defined $hash->{helper}{devicemap};
@@ -1409,6 +1453,7 @@ sub getAllRhasspyGroups {
return get_unique(\@groups, 1);
}
+# get a list of all used scenes
sub getAllRhasspyScenes {
my $hash = shift // return;
@@ -1733,6 +1778,41 @@ sub getDevicesByGroup {
return $devices;
}
+sub getNeedsConfirmation {
+ my $hash = shift // return;
+ my $data = shift // return;
+ my $intent = shift // return;
+ my $device = shift;
+
+ my $re = defined $device ? $device : $data->{Group};
+ Log3( $hash, 5, "[$hash->{NAME}] getNeedsConfirmation called, regex is $re" );
+ my $timeout = _getDialogueTimeout($hash);
+ my $response = getResponse($hash, 'DefaultConfirmationRequestRawInput');
+ my $rawInput = $data->{rawInput};
+ $response =~ s{(\$\w+)}{$1}eegx;
+
+ 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)
+ Log3( $hash, 5, "[$hash->{NAME}] getNeedsConfirmation is true for tweak, response is $response" );
+ setDialogTimeout($hash, $data, $timeout, $response);
+ return 1;
+ }
+
+ return if !defined $device;
+
+ 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" );
+ setDialogTimeout($hash, $data, $timeout, $response);
+ return 1;
+ }
+
+ return;
+}
+
# Mappings in Key/Value Paare aufteilen
sub splitMappingString {
@@ -1988,7 +2068,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};
- for my $key (qw(sessionId siteId input rawInput customData)) {
+ for my $key (qw(sessionId siteId input rawInput customData lang)) {
$data->{$key} = $decoded->{$key} if exists $decoded->{$key};
}
@@ -2035,7 +2115,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};
+ my $topicpart = qq{/$hash->{LANGUAGE}\.$hash->{fhemId}\[._]|hermes/dialogueManager|hermes/nlu/intentNotRecognized};
next if $topic !~ m{$topicpart}x;
Log3($hash,5,"RHASSPY: [$hash->{NAME}] Parse (IO: ${ioname}): Msg: $topic => $value");
@@ -2137,6 +2217,15 @@ sub analyzeMQTTmessage {
readingsSingleUpdate($hash, "listening_" . makeReadingName($room), 1, 1);
} elsif ( $topic =~ m{sessionEnded}x ) {
readingsSingleUpdate($hash, 'listening_' . makeReadingName($room), 0, 1);
+ my $identiy = qq($data->{sessionId});
+ my $data_old = $hash->{helper}{'.delayed'}->{$identiy};
+ if (defined $data_old) {
+ $data->{text} = getResponse( $hash, 'DefaultCancelConfirmation' );
+ $data->{intentFilter} = 'null' if !defined $data->{intentFilter}; #dialog II
+ sendTextCommand( $hash, $data );
+ delete $hash->{helper}{'.delayed'}{$identiy};
+ deleteSingleRegIntTimer($identiy, $hash);
+ }
}
push @updatedList, $hash->{NAME};
return \@updatedList;
@@ -2155,10 +2244,15 @@ sub analyzeMQTTmessage {
if ($mute) {
$data->{requestType} = $message =~ m{${fhemId}.textCommand}x ? 'text' : 'voice';
- respond($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, q{ });
+ respond( $hash, $data, q{ } );
#Beta-User: Da fehlt mir der Soll-Ablauf für das "room-listening"-Reading; das wird ja über einen anderen Topic abgewickelt
return \@updatedList;
}
+
+ if ($topic =~ m{\Ahermes/nlu/intentNotRecognized}x && defined $siteId) {
+ handleIntentNotRecognized($hash, $data);
+ return;
+ }
my $command = $data->{input};
$type = $message =~ m{${fhemId}.textCommand}x ? 'text' : 'voice';
@@ -2183,24 +2277,28 @@ sub analyzeMQTTmessage {
push @updatedList, $_ if $defs{$_};
}
+ Log3($hash, 4, "[$name] dispatch result is @updatedList" );
+
return \@updatedList;
}
# Antwort ausgeben
sub respond {
- my $hash = shift // return;
- my $type = shift // return;
- my $sessionId = shift // return;
- my $siteId = shift // return;
- my $response = shift // return;
+ 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 $type = $data->{requestType} // return;
my $topic = q{endSession};
- my $sendData = {
- sessionId => $sessionId,
- siteId => $siteId
- };
+ my $sendData;
+
+ for my $key (qw(sessionId siteId customData lang)) {
+ $sendData->{$key} = $data->{$key} if defined $data->{$key} && $data->{$key} ne 'null';
+ }
if (ref $response eq 'HASH') {
#intentFilter
@@ -2209,10 +2307,12 @@ sub respond {
$sendData->{$key} = $response->{$key};
}
} else {
- $sendData->{text} = $response
+ $sendData->{text} = $response;
+ $sendData->{intentFilter} = 'null';
}
- my $json = toJSON($sendData);
+ my $json = _toCleanJSON($sendData);
+ $response = $response->{text} if ref $response eq 'HASH' && defined $response->{text};
$response = $response->{response} if ref $response eq 'HASH' && defined $response->{response};
readingsBeginUpdate($hash);
$type eq 'voice' ?
@@ -2222,6 +2322,13 @@ sub respond {
readingsEndUpdate($hash,1);
IOWrite($hash, 'publish', qq{hermes/dialogueManager/$topic $json});
Log3($hash->{NAME}, 5, "Response is: $response");
+
+ my $secondAudio = ReadingsVal($hash->{NAME}, "siteId2doubleSpeak_$data->{siteId}",0);
+ sendSpeakCommand( $hash, {
+ siteId => $secondAudio,
+ text => $response} )
+ if $secondAudio;
+
return;
}
@@ -2230,7 +2337,9 @@ sub respond {
sub getResponse {
my $hash = shift;
my $identifier = shift // return 'Code error! No identifier provided for getResponse!' ;
+ my $subtype = shift;
+ return $hash->{helper}{lng}->{responses}->{$identifier}->{$subtype} if defined $subtype;
return getKeyValFromAttr($hash, $hash->{NAME}, 'response', $identifier) // $hash->{helper}{lng}->{responses}->{$identifier};
}
@@ -2242,9 +2351,10 @@ sub sendTextCommand {
my $data = {
input => $text,
- sessionId => "$hash->{fhemId}.textCommand"
+ sessionId => "$hash->{fhemId}.textCommand" #,
+ #canBeEnqueued => 'true'
};
- my $message = toJSON($data);
+ my $message = _toCleanJSON($data);
# Send fake command, so it's forwarded to NLU
# my $topic2 = "hermes/intent/FHEM:TextCommand";
@@ -2253,15 +2363,66 @@ 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;
my $cmd = shift;
+ my $sendData = {
+ init => {
+ type => 'notification',
+ canBeEnqueued => 'true',
+ customData => "$hash->{LANGUAGE}.$hash->{fhemId}"
+ }
+ };
+ 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->{init}->{text} = $cmd->{text};
+ } else { #Beta-User: might need review, as parseParams is used by default...!
+ my($unnamedParams, $namedParams) = parseParams($cmd);
+
+ if (defined $namedParams->{siteId} && defined $namedParams->{text}) {
+ $sendData->{siteId} = $namedParams->{siteId};
+ $sendData->{init}->{text} = $namedParams->{text};
+ } else {
+ return 'speak needs siteId and text as arguments!';
+ }
+ }
+ my $json = _toCleanJSON($sendData);
+ return IOWrite($hash, 'publish', qq{hermes/dialogueManager/startSession $json});
+}
+
+=pod
+#old version
+sub sendSpeakCommand {
+ my $hash = shift;
+ my $cmd = shift;
+
my $sendData = {
id => '0',
- sessionId => '0'
+ sessionId => '0'#,
+ #canBeEnqueued => 'true'
};
if (ref $cmd eq 'HASH') {
return 'speak with explicite params needs siteId and text as arguments!' if !defined $cmd->{siteId} || !defined $cmd->{text};
@@ -2279,9 +2440,10 @@ sub sendSpeakCommand {
return 'speak needs siteId and text as arguments!';
}
}
- my $json = toJSON($sendData);
+ my $json = _toCleanJSON($sendData);
return IOWrite($hash, 'publish', qq{hermes/tts/say $json});
}
+=cut
# Send all devices, rooms, etc. to Rhasspy HTTP-API to update the slots
sub updateSlots {
@@ -2446,6 +2608,16 @@ sub fetchSiteIds {
return _sendToApi($hash, $url, $method, undef);
}
+# Use the HTTP-API to fetch all available siteIds
+sub fetchIntents {
+ my $hash = shift // return;
+ my $url = q{/api/intents};
+ my $method = q{GET};
+
+ Log3($hash->{NAME}, 5, 'fetchIntents called');
+ return _sendToApi($hash, $url, $method, undef);
+}
+
=pod
# Check connection to HTTP-API
# Seems useless, because fetchSiteIds is called after DEF
@@ -2517,6 +2689,9 @@ sub RHASSPY_ParseHttpResponse {
trainRhasspy($hash);
delete $hash->{'.needTraining'};
}
+ if ( $urls->{$url} eq 'training' ) {
+ configure_DialogManager($hash, undef, undef, undef, 5 )
+ }
}
elsif ( $url =~ m{api/profile}ix ) {
my $ref;
@@ -2528,6 +2703,16 @@ sub RHASSPY_ParseHttpResponse {
my $siteIds = encode($cp,$ref->{dialogue}{satellite_site_ids});
readingsBulkUpdate($hash, 'siteIds', $siteIds);
}
+ elsif ( $url =~ m{api/intents}ix ) {
+ my $refb;
+ if ( !eval { $refb = decode_json($data) ; 1 } ) {
+ readingsEndUpdate($hash, 1);
+ return Log3($hash->{NAME}, 1, "JSON decoding error: $@");
+ }
+ my $intents = encode($cp,join q{,}, keys %{$refb});
+ readingsBulkUpdate($hash, 'intents', $intents);
+ configure_DialogManager($hash);
+ }
else {
Log3($name, 3, qq(error while requesting $param->{url} - $data));
}
@@ -2558,7 +2743,7 @@ sub handleCustomIntent {
}
my $subName = $custom->{function};
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'DefaultError')) if !defined $subName;
+ return respond( $hash, $data, getResponse( $hash, 'DefaultError' ) ) if !defined $subName;
my $params = $custom->{args};
my @rets = @{$params};
@@ -2588,7 +2773,7 @@ sub handleCustomIntent {
$timeout = ${$error}[1] if looks_like_number( ${$error}[1] );
return setDialogTimeout($hash, $data, $timeout, ${$error}[0]);
}
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return ${$error}[1]; #comma separated list of devices to trigger
} elsif ( ref $error eq 'HASH' ) {
return setDialogTimeout($hash, $data, $timeout, $error);
@@ -2599,7 +2784,7 @@ sub handleCustomIntent {
$response = getResponse($hash, 'DefaultConfirmation') if !defined $response;
# Antwort senden
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ return respond( $hash, $data, $response );
}
@@ -2617,7 +2802,7 @@ sub handleIntentSetMute {
$response = getResponse($hash, 'DefaultConfirmation');
}
$response = $response // getResponse($hash, 'DefaultError');
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ return respond( $hash, $data, $response );
}
# Handle custom Shortcuts
@@ -2656,7 +2841,7 @@ sub handleIntentShortcuts {
$cmd = qq({$cmd}) if ($cmd !~ m{\A\{.*\}\z}x);
$ret = analyzeAndRunCmd($hash, undef, $cmd, undef, $data->{siteId});
- $device = $ret if $ret && $ret !~ m{Please.define.*first}x;
+ $device = $ret if $ret && $ret !~ m{Please.define.*first}x && !defined $device;
$response = $ret // _replace($hash, $response, \%specials);
} elsif ( defined $shortcut->{fhem} ) {
@@ -2667,7 +2852,7 @@ sub handleIntentShortcuts {
AnalyzeCommand($hash, $cmd);
}
$response = _ReplaceReadingsVal( $hash, $response );
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
# update Readings
#updateLastIntentReadings($hash, $topic,$data);
@@ -2711,7 +2896,7 @@ sub handleIntentSetOnOff {
}
# Send response
$response = $response // getResponse($hash, 'DefaultError');
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $device;
}
@@ -2721,7 +2906,10 @@ sub handleIntentSetOnOffGroup {
Log3($hash->{NAME}, 5, "handleIntentSetOnOffGroup called");
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData')) if !defined $data->{Value};
+ return respond( $hash, $data, getResponse($hash, 'NoValidData') ) if !defined $data->{Value};
+
+ #check if confirmation is required
+ return $hash->{NAME} if !$data->{Confirmation} && getNeedsConfirmation( $hash, $data, 'SetOnOffGroup' );
my $devices = getDevicesByGroup($hash, $data);
@@ -2733,7 +2921,7 @@ sub handleIntentSetOnOffGroup {
} keys %{$devices};
Log3($hash, 5, 'sorted devices list is: ' . join q{ }, @devlist);
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound')) if !keys %{$devices};
+ return respond( $hash, $data, getResponse($hash, 'NoDeviceFound') ) if !keys %{$devices};
my $delaysum = 0;
@@ -2772,7 +2960,7 @@ sub handleIntentSetOnOffGroup {
_sortAsyncQueue($hash) if $init_delay && $needs_sorting;
# Send response
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'DefaultConfirmation'));
+ respond( $hash, $data, getResponse($hash, 'DefaultConfirmation') );
return $updatedList;
}
@@ -2784,7 +2972,7 @@ sub handleIntentSetTimedOnOff {
Log3($hash->{NAME}, 5, "handleIntentSetTimedOnOff called");
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $hash->{helper}{lng}->{responses}->{duration_not_understood})
+ return respond( $hash, $data, getResponse( $hash, 'duration_not_understood' ) )
if !defined $data->{Hourabs} && !defined $data->{Hour} && !defined $data->{Min} && !defined $data->{Sec};
# Device AND Value must exist
@@ -2803,7 +2991,7 @@ sub handleIntentSetTimedOnOff {
$cmd .= "-for-timer";
my $allset = getAllSets($device);
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoTimedOnDeviceFound')) if $allset !~ m{\b$cmd(?:[\b:\s]|\Z)}xms;
+ return respond( $hash, $data, getResponse($hash, 'NoTimedOnDeviceFound') ) if $allset !~ m{\b$cmd(?:[\b:\s]|\Z)}xms;
my $hour = 0;
my $now1 = time;
@@ -2839,7 +3027,7 @@ sub handleIntentSetTimedOnOff {
}
# Send response
$response = $response // getResponse($hash, 'DefaultError');
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $device;
}
@@ -2850,8 +3038,8 @@ sub handleIntentSetTimedOnOffGroup {
Log3($hash->{NAME}, 5, "handleIntentSetTimedOnOffGroup called");
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData')) if !defined $data->{Value};
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $hash->{helper}{lng}->{responses}->{duration_not_understood})
+ return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !defined $data->{Value};
+ return respond( $hash, $data, getResponse( $hash, 'duration_not_understood' ) )
if !defined $data->{Hourabs} && !defined $data->{Hour} && !defined $data->{Min} && !defined $data->{Sec};
my $devices = getDevicesByGroup($hash, $data);
@@ -2864,7 +3052,7 @@ sub handleIntentSetTimedOnOffGroup {
} keys %{$devices};
Log3($hash, 5, 'sorted devices list is: ' . join q{ }, @devlist);
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound')) if !keys %{$devices};
+ return respond( $hash, $data, getResponse($hash, 'NoDeviceFound') ) if !keys %{$devices};
#calculate duration for on/off-timer
my $hour = 0;
@@ -2928,7 +3116,7 @@ sub handleIntentSetTimedOnOffGroup {
_sortAsyncQueue($hash) if $init_delay && $needs_sorting;
# Send response
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'DefaultConfirmation'));
+ respond( $hash, $data, getResponse($hash, 'DefaultConfirmation') );
return $updatedList;
}
@@ -2969,7 +3157,7 @@ sub handleIntentGetOnOff {
}
# Send response
$response = getResponse($hash, 'DefaultError') if !defined $response;
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $device;
}
@@ -3003,7 +3191,7 @@ sub handleIntentSetNumericGroup {
Log3($hash->{NAME}, 5, 'handleIntentSetNumericGroup called');
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData')) if !exists $data->{Value} && !exists $data->{Change};
+ return respond( $hash, $data, getResponse($hash, 'NoValidData') ) if !exists $data->{Value} && !exists $data->{Change};
my $devices = getDevicesByGroup($hash, $data);
@@ -3015,7 +3203,7 @@ sub handleIntentSetNumericGroup {
} keys %{$devices};
Log3($hash, 5, 'sorted devices list is: ' . join q{ }, @devlist);
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound')) if !keys %{$devices};
+ return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !keys %{$devices};
my $delaysum = 0;
@@ -3049,7 +3237,7 @@ sub handleIntentSetNumericGroup {
_sortAsyncQueue($hash) if $init_delay && $needs_sorting;
# Send response
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'DefaultConfirmation'));
+ respond( $hash, $data, getResponse( $hash, 'DefaultConfirmation' ) );
return $updatedList;
}
@@ -3064,7 +3252,7 @@ sub handleIntentSetNumeric {
if ( !defined $device && !isValidData($data) ) {
return if defined $data->{'.inBulk'};
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData'));
+ return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) );
}
my $unit = $data->{Unit};
@@ -3084,10 +3272,10 @@ sub handleIntentSetNumeric {
} elsif ( defined $type && ( $type eq 'volume' || $type eq 'Lautstärke' ) ) {
$device =
getActiveDeviceForIntentAndType($hash, $room, 'SetNumeric', $type)
- // return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoActiveMediaDevice'));
+ // return respond( $hash, $data, getResponse( $hash, 'NoActiveMediaDevice') );
}
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound')) if !defined $device;
+ return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !defined $device;
my $mapping = getMapping($hash, $device, 'SetNumeric', $type, defined $hash->{helper}{devicemap}, 0);
@@ -3096,12 +3284,12 @@ sub handleIntentSetNumeric {
#Beta-User: long forms to later add options to check upper/lower limits for pure on/off devices
return;
} else {
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoMappingFound'));
+ return respond( $hash, $data, getResponse( $hash, 'NoMappingFound' ) );
}
}
# Mapping and device found -> execute command
- my $cmd = $mapping->{cmd} // return defined $data->{'.inBulk'} ? undef : respond($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoMappingFound'));
+ my $cmd = $mapping->{cmd} // return defined $data->{'.inBulk'} ? undef : respond( $hash, $data, getResponse( $hash, 'NoMappingFound' ) );
my $part = $mapping->{part};
my $minVal = $mapping->{minVal};
my $maxVal = $mapping->{maxVal};
@@ -3168,7 +3356,7 @@ sub handleIntentSetNumeric {
}
if ( !defined $newVal ) {
- return defined $data->{'.inBulk'} ? undef : respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoNewValDerived'));
+ return defined $data->{'.inBulk'} ? undef : respond( $hash, $data, getResponse( $hash, 'NoNewValDerived' ) );
}
# limit to min/max (if set)
@@ -3193,7 +3381,7 @@ sub handleIntentSetNumeric {
# send response
$response = getResponse($hash, 'DefaultError') if !defined $response;
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response) if !defined $data->{'.inBulk'};
+ respond( $hash, $data, $response ) if !defined $data->{'.inBulk'};
return $device;
}
@@ -3207,7 +3395,7 @@ sub handleIntentGetNumeric {
Log3($hash->{NAME}, 5, "handleIntentGetNumeric called");
# Mindestens Type oder Device muss existieren
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'DefaultError')) if !exists $data->{Type} && !exists $data->{Device};
+ return respond( $hash, $data, getResponse( $hash, 'DefaultError' ) ) if !exists $data->{Type} && !exists $data->{Device};
my $type = $data->{Type};
my $subType = $data->{subType} // $type;
@@ -3217,7 +3405,7 @@ sub handleIntentGetNumeric {
my $device = exists $data->{Device}
? getDeviceByName($hash, $room, $data->{Device})
: getDeviceByIntentAndType($hash, $room, 'GetNumeric', $type)
- // return respond($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound'));
+ // return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) );
#more than one device
if (ref $device eq 'ARRAY') {
@@ -3226,6 +3414,7 @@ sub handleIntentGetNumeric {
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");
@@ -3233,7 +3422,7 @@ sub handleIntentGetNumeric {
}
my $mapping = getMapping($hash, $device, 'GetNumeric', { type => $type, subType => $subType }, defined $hash->{helper}{devicemap}, 0)
- // return respond($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoMappingFound'));
+ // return respond( $hash, $data, getResponse( $hash, 'NoMappingFound' ) );
# Mapping found
my $part = $mapping->{part};
@@ -3266,9 +3455,9 @@ sub handleIntentGetNumeric {
# Antwort falls Custom Response definiert ist
if ( defined $mapping->{response} ) {
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, _getValue($hash, $device, $mapping->{response}, $value, $location));
+ return respond( $hash, $data, _getValue( $hash, $device, $mapping->{response}, $value, $location ) );
}
- my $responses = $hash->{helper}{lng}->{responses}->{Change};
+ my $responses = getResponse( $hash, 'Change' );
# Antwort falls mappingType oder type matched
my $response =
@@ -3294,7 +3483,7 @@ sub handleIntentGetNumeric {
# Variablen ersetzen?
$response =~ s{(\$\w+)}{$1}eegx;
# Antwort senden
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ return respond( $hash, $data, $response );
}
@@ -3320,7 +3509,7 @@ sub handleIntentGetState {
}
# Antwort senden
$response = getResponse($hash, 'DefaultError') if !defined $response;
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ return respond( $hash, $data, $response );
}
@@ -3375,7 +3564,7 @@ sub handleIntentMediaControls {
}
}
# Send voice response
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $device;
}
@@ -3386,11 +3575,11 @@ sub handleIntentSetScene{
my ($scene, $device, $room, $siteId, $mapping, $response);
Log3($hash->{NAME}, 5, "handleIntentSetScene called");
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData')) if !defined $data->{Scene};
+ return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !defined $data->{Scene};
# Device AND Scene are optimum exist
if ( !exists $data->{Device} ) {
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound'));
+ return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) );
} else {
$room = getRoomName($hash, $data);
$scene = $data->{Scene};
@@ -3414,7 +3603,7 @@ sub handleIntentSetScene{
=cut
# Mapping found?
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData')) if !$device || !defined $mapping;
+ return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !$device || !defined $mapping;
my $cmd = qq(scene $scene);
# execute Cmd
@@ -3422,12 +3611,12 @@ sub handleIntentSetScene{
Log3($hash->{NAME}, 5, "Running command [$cmd] on device [$device]" );
# Define response
- $response = $mapping->{response} // getResponse($hash, 'DefaultConfirmation');
+ $response = $mapping->{response} // getResponse( $hash, 'DefaultConfirmation' );
}
# Send response
$response = $response // getResponse($hash, 'DefaultError');
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $device;
}
@@ -3438,12 +3627,12 @@ sub handleIntentGetTime {
Log3($hash->{NAME}, 5, "handleIntentGetTime called");
(my $sec,my $min,my $hour,my $mday,my $mon,my $year,my $wday,my $yday,my $isdst) = localtime;
- my $response = $hash->{helper}{lng}->{responses}->{timeRequest};
+ my $response = getResponse( $hash, 'timeRequest' );
$response =~ s{(\$\w+)}{$1}eegx;
Log3($hash->{NAME}, 5, "Response: $response");
# Send voice reponse
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ return respond( $hash, $data, $response );
}
@@ -3460,13 +3649,13 @@ sub handleIntentGetDate {
$month = $hash->{helper}{lng}{words}->{$month} if defined $hash->{helper}{lng}{words}->{$month};
my $year = strftime( '%G', localtime );
my $day = strftime( '%e', localtime );
- my $response = $hash->{helper}{lng}->{responses}->{weekdayRequest};
+ my $response = getResponse( $hash, 'weekdayRequest' );
$response =~ s{(\$\w+)}{$1}eegx;
Log3($hash->{NAME}, 5, "Response: $response");
# Send voice reponse
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ return respond( $hash, $data, $response );
}
@@ -3509,7 +3698,7 @@ sub handleIntentMediaChannels {
# Antwort senden
$response = getResponse($hash, 'NoMediaChannelFound') if !defined $response;
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $device;
}
@@ -3528,7 +3717,7 @@ sub handleIntentSetColor {
# At least Device AND Color have to be received
if ( !exists $data->{Color} && !exists $data->{Rgb} &&!exists $data->{Saturation} && !exists $data->{Colortemp} && !exists $data->{Hue} || !exists $data->{Device} && !defined $device) {
return if $inBulk;
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData'));
+ return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) );
}
#if (exists $data->{Color} && exists $data->{Device}) {
@@ -3545,7 +3734,7 @@ sub handleIntentSetColor {
}
return if $inBulk && !defined $device;
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound')) if !defined $device;
+ return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !defined $device;
if ( defined $cmd || defined $cmd2 ) {
$response = getResponse($hash, 'DefaultConfirmation');
@@ -3556,7 +3745,7 @@ sub handleIntentSetColor {
}
# Send voice response
$response = getResponse($hash, 'DefaultError') if !defined $response;
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response) if !$inBulk;
+ respond( $hash, $data, $response ) if !$inBulk;
return $device;
}
@@ -3568,7 +3757,7 @@ sub _runSetColorCmd {
my $color = $data->{Color};
- my $mapping = $hash->{helper}{devicemap}{devices}{$device}{intents}{SetColorParms} // return $inBulk ?undef : respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoMappingFound'));
+ my $mapping = $hash->{helper}{devicemap}{devices}{$device}{intents}{SetColorParms} // return $inBulk ?undef : respond( $hash, $data, getResponse( $hash, 'NoMappingFound' ) );
my $error;
@@ -3586,7 +3775,7 @@ sub _runSetColorCmd {
$error = AnalyzeCommand($hash, "set $device $cmd");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting $device to $cmd");
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $error) if $error;
+ 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);
@@ -3594,7 +3783,7 @@ sub _runSetColorCmd {
$error = AnalyzeCommand($hash, "set $device $mapping->{$_}->{cmd} $value");
return if $inBulk;
Log3($hash->{NAME}, 5, "Setting color to $value");
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $error) if $error;
+ return respond( $hash, $data, $error ) if $error;
return getResponse($hash, 'DefaultConfirmation');
}
}
@@ -3605,7 +3794,7 @@ sub _runSetColorCmd {
$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->{requestType}, $data->{sessionId}, $data->{siteId}, $error) if $error;
+ return respond( $hash, $data, $error ) if $error;
return getResponse($hash, 'DefaultConfirmation');
}
@@ -3647,7 +3836,7 @@ sub _runSetColorCmd {
$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->{requestType}, $data->{sessionId}, $data->{siteId}, $error) if $error;
+ return respond( $hash, $data, $error ) if $error;
return getResponse($hash, 'DefaultConfirmation');
}
@@ -3661,7 +3850,7 @@ sub _runSetColorCmd {
$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->{requestType}, $data->{sessionId}, $data->{siteId}, $error) if $error;
+ return respond( $hash, $data, $error ) if $error;
return getResponse($hash, 'DefaultConfirmation');
}
@@ -3703,7 +3892,7 @@ sub handleIntentSetColorGroup {
Log3($hash->{NAME}, 5, 'handleIntentSetColorGroup called');
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoValidData')) if !exists $data->{Color} && !exists $data->{Rgb} &&!exists $data->{Saturation} && !exists $data->{Colortemp} && !exists $data->{Hue};
+ return respond( $hash, $data, getResponse( $hash, 'NoValidData' ) ) if !exists $data->{Color} && !exists $data->{Rgb} &&!exists $data->{Saturation} && !exists $data->{Colortemp} && !exists $data->{Hue};
my $devices = getDevicesByGroup($hash, $data);
@@ -3715,7 +3904,7 @@ sub handleIntentSetColorGroup {
} keys %{$devices};
Log3($hash, 5, 'sorted devices list is: ' . join q{ }, @devlist);
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'NoDeviceFound')) if !keys %{$devices};
+ return respond( $hash, $data, getResponse( $hash, 'NoDeviceFound' ) ) if !keys %{$devices};
my $delaysum = 0;
my $updatedList;
@@ -3744,7 +3933,7 @@ sub handleIntentSetColorGroup {
_sortAsyncQueue($hash) if $init_delay && $needs_sorting;
# Send response
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, getResponse($hash, 'DefaultConfirmation'));
+ respond( $hash, $data, getResponse( $hash, 'DefaultConfirmation' ) );
return $updatedList;
}
@@ -3759,7 +3948,7 @@ sub handleIntentSetTimer {
Log3($name, 5, 'handleIntentSetTimer called');
- return respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $hash->{helper}{lng}->{responses}->{duration_not_understood})
+ return respond( $hash, $data, getResponse( $hash, 'duration_not_understood' ) )
if !defined $data->{Hourabs} && !defined $data->{Hour} && !defined $data->{Min} && !defined $data->{Sec} && !defined $data->{CancelTimer};
my $room = getRoomName($hash, $data);
@@ -3790,11 +3979,11 @@ sub handleIntentSetTimer {
my $timerRoom = $siteId;
- my $responseEnd = $hash->{helper}{lng}->{responses}->{timerEnd}->{1};
+ my $responseEnd = getResponse( $hash, 'timerEnd', 1);
if ($siteIds =~ m{\b$room\b}ix) {
$timerRoom = $room if $siteIds =~ m{\b$room\b}ix;
- $responseEnd = $hash->{helper}{lng}->{responses}->{timerEnd}->{0};
+ $responseEnd = getResponse( $hash, 'timerEnd', 0);
}
my $roomReading = "timer_".makeReadingName($room);
@@ -3809,7 +3998,7 @@ sub handleIntentSetTimer {
Log3($name, 5, "deleted timer: $roomReading");
$response = getResponse($hash, 'timerCancellation');
$response =~ s{(\$\w+)}{$1}eegx;
- respond ($hash, $data->{requestType}, $data->{sessionId}, $data->{siteId}, $response);
+ respond( $hash, $data, $response );
return $name;
}
@@ -3832,7 +4021,7 @@ sub handleIntentSetTimer {
CommandDefMod($hash, "-temporary $roomReading at +$attime set $name speak siteId=\"$timerRoom\" text=\"$responseEnd\";deletereading $name ${roomReading}$addtrigger");
} else {
$soundoption =~ m{((? This module receives, processes and executes voice commands coming from Rhasspy voice assistent. General Remarks: All parameters in define are optional, but changing them later might lead to confusing results! General Remark: RHASSPY uses parseParams at quite a lot places, not only in define, but also to parse attribute values. Remark: Parameters: Example for defining an MQTT2_CLIENT device and the Rhasspy device in FHEM: RHASSPY
+
+
+
+So don't expect these parts to work if you use other options than Rhasspy's own dialogue management.
See especially attributes languageFile and rhasspyIntents for further reference.Define
define <name> RHASSPY <baseUrl> <devspec> <defaultRoom> <language> <fhemId> <prefix> <useGenericAttrs> <encoding>
-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.
+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.
+
baseUrl=http://127.0.0.1:12101.
Make sure, this is set to correct values (ip and port)devspec=room=Rhasspy, see as a reference, how to e.g. use a comma-separated list of devices or combinations like devspec=room=livingroom,room=bathroom,bedroomlamp.defmod rhasspyMQTT2 MQTT2_CLIENT 192.168.1.122:12183
attr rhasspyMQTT2 clientOrder RHASSPY MQTT_GENERIC_BRIDGE MQTT2_DEVICE
-attr rhasspyMQTT2 subscriptions hermes/intent/+ hermes/dialogueManager/sessionStarted hermes/dialogueManager/sessionEnded
define Rhasspy RHASSPY devspec=room=Rhasspy defaultRoom=Livingroom language=en
Additionals remarks on MQTT2-IOs:
@@ -4274,7 +4517,7 @@ When changing something relevant within FHEM for either the data structure inChoose between one of the following:
+Various options to update settings and data structures used by RHASSPY and/or Rhasspy. Choose between one of the following:
Example: set <rhasspyDevice> update language
Path to the language-config file. If this attribute isn't set, a default set of english responses is used for voice responses.
- The file itself must contain a JSON-encoded keyword-value structure (partly with sub-structures) following the given structure for the mentioned english defaults. As a reference, there's one available in german, or just make a dump of the English structure with e.g. (replace RHASSPY by your device's name): {toJSON($defs{RHASSPY}->{helper}{lng})}, edit the result e.g. using https://jsoneditoronline.org and place this in your own languageFile version. There might be some variables to be used - these should also work in your sentences.
+ The file itself must contain a JSON-encoded keyword-value structure (partly with sub-structures) following the given structure for the mentioned english defaults. As a reference, there's one in the additionals files available in german (note the comments there!), or just make a dump of the English structure with e.g. (replace RHASSPY by your device's name): {toJSON($defs{RHASSPY}->{helper}{lng})}, edit the result e.g. using https://jsoneditoronline.org and place this in your own languageFile version. There might be some variables to be used - these should also work in your sentences.
languageFile also allows combining e.g. a default set of german sentences with some few own modifications by using "defaults" subtree for the defaults and "user" subtree for your modified versions. This feature might be helpful in case the base language structure has to be changed in the future.
Example (placed in the same dir fhem.pl is located):
attr <rhasspyDevice> languageFile ./rhasspy-de.cfg
Not recommended. Use the language-file instead.
+Note: Using this attribute is no longer recommended, use options provided by the languageFile attribute instead.
Optionally define alternative default answers. Available keywords are DefaultError, NoActiveMediaDevice and DefaultConfirmation.
Example:
DefaultError=
@@ -4404,8 +4650,15 @@ DefaultConfirmation=Klaro, mach ich
If a simple text is returned, this will be considered as response.
- For more advanced use of this feature, you may return an array. First element of the array will be interpreted as comma-separated list of devices that may have been modified (otherwise, these devices will not cast any events! See also the "d" parameter in rhasspyShortcuts). The second element is interpreted as response and may either be simple text or HASH-type data. This will keep the dialogue-session open to allow interactive data exchange with Rhasspy. An open dialogue will be closed after some time, default is 20 seconds, you may alternatively hand over other numeric values as third element of the array.
If a simple text is returned, this will be considered as response, if return value is not defined, the default response will be used.
+ For more advanced use of this feature, you may return either a HASH or an ARRAY data structure. If ARRAY is returned:
+
i="what's the time for sunrise" r="at [Astro:SunRise] o'clock" is valid.Currently sets additional settings for timers and slot-updates to Rhasspy. May contain further custom settings in future versions like siteId2room info or code links, allowed commands, confirmation requests etc.
@@ -4468,7 +4721,10 @@ i="i am hungry" f="set Stove on" d="Stove" c="would you like roast pork"<Example:
timeouts: confirm=25 default=30
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. +
Comma-separated "labels" for the device as used when speaking a voice-command. They will be used as keywords by Rhasspy. May contain space or mutated vovels.
Example:
attr m2_wz_08_sw rhasspyName kitchen lamp,ceiling lamp,workspace,whatever
Comma-separated "labels" for the "rooms" the device is located in. Recommended to be unique.
Example:
attr m2_wz_08_sw rhasspyRoom living room
Comma-separated "labels" for the "groups" the device is in. Recommended to be unique.
Example:
attr m2_wz_08_sw rhasspyGroup lights
If automatic detection (gDT) does not work or is not desired, this is the place to tell RHASSPY how your device can be controlled.
Example:
attr lamp rhasspyMapping SetOnOff:cmdOn=on,cmdOff=off,response="All right"
@@ -4519,7 +4775,7 @@ GetState:response=The temperature in the kitchen is at [lamp:temperature] degree
MediaControls:cmdPlay=play,cmdPause=pause,cmdStop=stop,cmdBack=previous,cmdFwd=next
Used to change the channels of a tv, set light-scenes, etc.
key=value line by line arguments mapping command strings to fhem- or Perl commands.
Example:
@@ -4530,7 +4786,7 @@ orf drei=channel 203Note: This attribute is not added to global attribute list by default. Add it using userattr or by editing the global userattr attribute.
Used to change to colors of a light
key=value line by line arguments mapping keys to setter strings on the same device.
Example:
@@ -4541,7 +4797,7 @@ yellow=rgb FFFF00Note: This attribute is not added to global attribute list by default. Add it using userattr or by editing the global userattr attribute. You may consider using rhasspySpecials (colorCommandMap and/or colorForceHue2rgb) instead.
Currently some colour light options besides group and venetian blind related stuff is implemented, this could be the place to hold additional options, e.g. for confirmation requests. You may use several of the following lines.
key:value line by line arguments similar to rhasspyTweaks.
There are some readings you may find usefull to tweak some aspects of RHASSPY's logics: +
setreading siteId2room_mobile_phone1 kitchen will force RHASSPY to link your satellite phone1 kitchen to kitchen as room.
+