From 91d73e37b426c214574ffc16386caae5aabbd7f7 Mon Sep 17 00:00:00 2001 From: "michael.winkler" <> Date: Fri, 27 Oct 2023 12:58:08 +0000 Subject: [PATCH] 37_echodevice.pm: bugfixing voice_reading git-svn-id: https://svn.fhem.de/fhem/trunk@28095 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/37_echodevice.pm | 182 +++++++++++++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 17 deletions(-) diff --git a/fhem/FHEM/37_echodevice.pm b/fhem/FHEM/37_echodevice.pm index 215abd569..6808751da 100644 --- a/fhem/FHEM/37_echodevice.pm +++ b/fhem/FHEM/37_echodevice.pm @@ -2,6 +2,15 @@ # ############################################## # +# 2023.10.27 v0.2.22 +# - CHANGE: voice_reading auf neue API umgestellt +# - FEATURE voice_person (Zeigt wer das letzte Voice Command gesagt hat, wenn eingerichtet) +# +# 2023.01.19 v0.2.21 +# - BUG: Refresh Token +# Attribut "fhem_home" +# - FEATURE Neues Reading "parse_error" +# # 2022.11.22 v0.2.20 # - FEATURE: Unterstützung A4ZXE0RM7LQ7A Echo Dot Gen5 # @@ -498,7 +507,7 @@ use lib ('./FHEM/lib', './lib'); use MP3::Info; use MIME::Base64; -my $ModulVersion = "0.2.19"; +my $ModulVersion = "0.2.22"; my $AWSPythonVersion = "0.0.3"; my $NPMLoginTyp = "unbekannt"; my $QueueNumber = 0; @@ -671,7 +680,7 @@ sub echodevice_Get($@) { $usage .= "help:noArg " ; } elsif ($hash->{model} eq "ACCOUNT") { - $usage .= "settings:noArg devices:noArg actions:noArg tracks:noArg help:noArg conversations:noArg html_results:noArg address status:noArg customer-history-records:noArg"; + $usage .= "settings:noArg devices:noArg actions:noArg tracks:noArg help:noArg conversations:noArg html_results:noArg address status:noArg customer-history-records:noArg NPM_check:noArg"; } else { $usage .= "tunein settings:noArg primeplayeigene_albums primeplayeigene_tracks primeplayeigene_artists primeplayeigeneplaylist:noArg help:noArg html_results:noArg "; @@ -818,6 +827,13 @@ sub echodevice_Get($@) { } + if ($command eq "NPM_check") { + + $return = echodevice_NPMCheck($hash); + + return $return; + } + if ($ConnectState ne "connected") { return "$name is not connected. Aborting..."; } @@ -893,7 +909,7 @@ sub echodevice_Set($@) { $usage .= 'AWS_Access_Key AWS_Secret_Key TTS_IPAddress TTS_Filename TTS_TuneIn POM_TuneIn POM_IPAddress POM_Filename AWS_OutputFormat:mp3,ogg_vorbis,pcm textmessage ';# if(defined($hash->{helper}{".COMMSID"})); $usage .= 'config_address_from config_address_to config_address_between mobilmessage '; $usage .= 'login:noArg loginwithcaptcha login2FACode ' if($hash->{LOGINMODE} eq "NORMAL"); - $usage .= 'NPM_install:noArg NPM_login:new,refresh ' if($hash->{LOGINMODE} eq "NPM"); + $usage .= 'NPM_install:noArg NPM_login:new,refresh NPM_check:noArg' if($hash->{LOGINMODE} eq "NPM"); # Einkaufsliste my $ShoppingListe = ReadingsVal($name, "list_SHOPPING_ITEM", ""); @@ -906,7 +922,7 @@ sub echodevice_Set($@) { elsif ($hash->{model} eq "Echo Multiroom" || $hash->{model} eq "Sonos Display" || $hash->{model} eq "Echo Stereopaar") { $usage .= 'volume:slider,0,1,100 play:noArg pause:noArg next:noArg previous:noArg forward:noArg rewind:noArg shuffle:on,off repeat:on,off '; - $usage .= 'tunein primeplaylist primeplaysender primeplayeigene primeplayeigeneplaylist tts tts_translate:textField-long playownmusic:textField-long saveownplaylist:textField-long '; + $usage .= 'tunein primeplaylist primeplaysender primeplayeigene primeplayeigeneplaylist tts tts_translate:textField-long playownmusic:textField-long saveownplaylist:textField-long speak speak_ssml '; if(defined($tracks)) { $tracks =~ s/ /_/g; @@ -1730,8 +1746,16 @@ sub echodevice_SendCommand($$$) { elsif ($type eq "alarmvolume") { $SendUrl .= "/api/device-notification-state?_=".int(time); } - + elsif ($type eq "activities") { + $SendUrl = "https://www.amazon.de/alexa-privacy/apd/rvh/customer-history-records/?startTime=0&endTime=2005090388459&recordType=VOICE_HISTORY&maxRecordSize=100"; + $SendMetode = "GET"; + $SendDataL = "" ; + $SendData = ""; + + } + + elsif ($type eq "activities1") { if (int(AttrVal($name,"intervalvoice",999999)) != 999999) { $SendUrl .= "/api/activities?startTime=&size=10&offset=1&_=".int(time); } @@ -2109,10 +2133,12 @@ sub echodevice_SendCommand($$$) { my $SpeakVolume; $SpeakVolume = int(AttrVal($hash->{IODev}{NAME},"speak_volume",0)); $SpeakVolume = int(AttrVal($name,"speak_volume",0)) if($SpeakVolume == 0); - + if($SpeakVolume > 0){ #if(ReadingsVal($name , "volume", 50) < ReadingsVal($name , "volume_alarm", 50)) { - $SendData = '{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.SerialNode\",\"nodesToExecute\":[{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceSerialNumber\":\"' . $hash->{helper}{".SERIAL"} . '\",\"customerId\":\"' . $hash->{IODev}->{helper}{".CUSTOMER"} .'\",\"locale\":\"de-DE\",\"value\":\"'.$SpeakVolume.'\",\"deviceType\":\"' . $hash->{helper}{DEVICETYPE} . '\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.Speak\",\"operationPayload\":{\"locale\":\"de-DE\",\"deviceSerialNumber\":\"' . $hash->{helper}{".SERIAL"} . '\",\"customerId\":\"' . $hash->{IODev}->{helper}{".CUSTOMER"} .'\",\"deviceType\":\"' . $hash->{helper}{DEVICETYPE} . '\",\"textToSpeak\":\"'.$SendData.'\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceSerialNumber\":\"' . $hash->{helper}{".SERIAL"} . '\",\"customerId\":\"' . $hash->{IODev}->{helper}{".CUSTOMER"} .'\",\"locale\":\"de-DE\",\"value\":\"'.ReadingsVal($name , "volume", 50).'\",\"deviceType\":\"' . $hash->{helper}{DEVICETYPE} . '\"}}]}}","status":"ENABLED"}' + Log3 $name, 3, "[$name] [SpeakVolume] send..."; + $SendData = '{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.SerialNode\",\"nodesToExecute\":[{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceSerialNumber\":\"' . $hash->{helper}{".SERIAL"} . '\",\"customerId\":\"' . $hash->{IODev}->{helper}{".CUSTOMER"} .'\",\"locale\":\"de-DE\",\"value\":\"'.$SpeakVolume.'\",\"deviceType\":\"' . $hash->{helper}{DEVICETYPE} . '\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.Speak\",\"operationPayload\":{\"locale\":\"de-DE\",\"deviceSerialNumber\":\"' . $hash->{helper}{".SERIAL"} . '\",\"customerId\":\"' . $hash->{IODev}->{helper}{".CUSTOMER"} .'\",\"deviceType\":\"' . $hash->{helper}{DEVICETYPE} . '\",\"textToSpeak\":\"'.$SendData.'\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceSerialNumber\":\"' . $hash->{helper}{".SERIAL"} . '\",\"customerId\":\"' . $hash->{IODev}->{helper}{".CUSTOMER"} .'\",\"locale\":\"de-DE\",\"value\":\"'.ReadingsVal($name , "volume", 50).'\",\"deviceType\":\"' . $hash->{helper}{DEVICETYPE} . '\"}}]}}","status":"ENABLED"}'; + Log3 $name, 3, "[$name] [SpeakVolume] send... $SendData"; } else { $SendData = echodevice_getsequenceJson($hash,$type,$SendData); @@ -2213,7 +2239,7 @@ sub echodevice_SendCommand($$$) { sub echodevice_HandleCmdQueue($) { my ($hash, $param) = @_; my $name = $hash->{NAME}; - + return undef if(!defined($hash->{helper}{CMD_QUEUE})); $hash->{helper}{RUNNING_REQUEST} = 0 if(!defined($hash->{helper}{RUNNING_REQUEST})); @@ -2237,7 +2263,7 @@ sub echodevice_HandleCmdQueue($) { # if($hash->{model} eq "ACCOUNT") {$AmazonHeader = "Cookie: ".$hash->{helper}{".COOKIE"}."\r\ncsrf: ".$hash->{helper}{".CSRF"}."\r\nContent-Type: application/json; charset=UTF-8";} # else {$AmazonHeader = "Cookie: ".$hash->{IODev}->{helper}{".COOKIE"}."\r\ncsrf: ".$hash->{IODev}->{helper}{".CSRF"}."\r\nContent-Type: application/json; charset=UTF-8";} - + if($hash->{model} eq "ACCOUNT") {$AmazonHeader = "User-Agent: ". $UserAgent ."\r\nAccept-Language: " . $HeaderLanguage . "\r\nDNT: 1\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nCookie:".$hash->{helper}{".COOKIE"}."\r\ncsrf: ".$hash->{helper}{".CSRF"}."\r\nContent-Type: application/json; charset=UTF-8";} else {$AmazonHeader = "User-Agent: ". $UserAgent ."\r\nAccept-Language: " . $HeaderLanguage . "\r\nDNT: 1\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nCookie:".$hash->{IODev}->{helper}{".COOKIE"}."\r\ncsrf: ".$hash->{IODev}->{helper}{".CSRF"}."\r\nContent-Type: application/json; charset=UTF-8";} @@ -2488,6 +2514,15 @@ sub echodevice_Parse($$$) { Log3 $name, 4, "[$name] [echodevice_Parse] [$msgtype] [$msgnumber] DATA Dumper=" . Dumper(echodevice_anonymize($hash, $data)); } + # Rate exceeded: Too many requests https://forum.fhem.de/index.php/topic,82631.msg1251020.html#msg1251020 + if (Dumper(echodevice_anonymize($hash, $data)) =~ m/Too many requests/ ) { + readingsSingleUpdate ($hash, "parse_error", "Too many requests" ,0); + Log3 $name, 3, "[$name] [echodevice_Parse] [$msgtype] [$msgnumber] Too many requests" ; + } + else { + readingsSingleUpdate ($hash, "parse_error", "OK" ,0); + } + # Nächsten Auftrag starten echodevice_HandleCmdQueue($hash); @@ -2782,8 +2817,79 @@ sub echodevice_Parse($$$) { if (index($data, '{') == -1) {$data = '{"data": "nodata"}';} my $json = eval { JSON->new->utf8(0)->decode($data) }; - + my $Person ; if($msgtype eq "activities") { + if (ref($json) eq "HASH") { + if(defined($json->{customerHistoryRecords}) && ref($json->{customerHistoryRecords}) eq "ARRAY") { + foreach my $recordKey (@{$json->{customerHistoryRecords}}) { + + my @recordKeys = split("#",$recordKey->{recordKey}); + $sourceDeviceIds = @recordKeys[3]; + $Person = 0; + if ($recordKey->{utteranceType} ne "WAKE_WORD_ONLY" and $recordKey->{utteranceType} ne "DEVICE_ARBITRATION") { + + if(defined($modules{$hash->{TYPE}}{defptr}{$sourceDeviceIds})) { + + my $echohash = $modules{$hash->{TYPE}}{defptr}{$sourceDeviceIds}; + my $timestamp = int(ReadingsVal($echohash->{NAME},'voice_timestamp',9999)); + + if($timestamp >= int($recordKey->{voiceHistoryRecordItems}[0]{timestamp})) { + if ($recordKey->{voiceHistoryRecordItems}[0]{personsInfo}[0]{personFirstName} ne ReadingsVal($echohash->{NAME},'voice_person',"unbekannt") and $recordKey->{voiceHistoryRecordItems}[0]{personsInfo}[0]{personFirstName} ne "") { + $Person = 1; + } + else { + next; + } + } + + $echohash->{".updateTimestamp"} = FmtDateTime(int($card->{creationTimestamp}/1000)); + readingsBeginUpdate($echohash); + + if ($Person eq 1) { + readingsBulkUpdate($echohash, "voice_person", $recordKey->{voiceHistoryRecordItems}[0]{personsInfo}[0]{personFirstName}, 1); + } + else { + if ($recordKey->{voiceHistoryRecordItems}[0]{personsInfo}[0]{personFirstName} ne "") { + readingsBulkUpdate($echohash, "voice_person", $recordKey->{voiceHistoryRecordItems}[0]{personsInfo}[0]{personFirstName}, 1); + } + readingsBulkUpdate($echohash, "voice_timestamp", $recordKey->{voiceHistoryRecordItems}[0]{timestamp}, 1); + readingsBulkUpdate($echohash, "voice", $recordKey->{voiceHistoryRecordItems}[0]{transcriptText}, 1); + } + + readingsEndUpdate($echohash,1); + $echohash->{CHANGETIME}[0] = FmtDateTime(int($card->{creationTimestamp}/1000)); + + + } + + #Log3 $name, 3, "[$name] [echodevice_Parse] [" . $echohash->{NAME} . "] Alexatext = ".$sourceDeviceIds . " transcriptText=" . $recordKey->{voiceHistoryRecordItems}[0]{transcriptText} ." Person=" . $recordKey->{voiceHistoryRecordItems}[0]{personsInfo}[0]{personFirstName}; + + } + + + } + } + + # Timer für Realtime Check! + my $IntervalVoice = int(AttrVal($name,"intervalvoice",999999)); + + if ($IntervalVoice != 999999 && $hash->{STATE} eq "connected" && AttrVal($name,"disable",0) == 0) { + Log3 $name, 5, "[$name] [echodevice_Parse] [$msgtype] [$msgnumber] refresh voice command IntervalVoice=$IntervalVoice "; + $hash->{helper}{echodevice_refreshvoice} = 1; + $hash->{helper}{echodevice_refreshvoice_lastdate} = time(); + RemoveInternalTimer($hash, "echodevice_refreshvoice"); + InternalTimer(gettimeofday() + $IntervalVoice , "echodevice_refreshvoice", $hash, 0); + } + else { + $hash->{helper}{echodevice_refreshvoice} = 0; + } + } + else { + Log3 $name, 3, "[$name] [echodevice_Parse] [$msgtype] [$msgnumber] WRONG JSON Type Type=" . ref($json); + } + } + + if($msgtype eq "activities2") { if (ref($json) eq "HASH") { if(defined($json->{activities}) && ref($json->{activities}) eq "ARRAY") { foreach my $card (@{$json->{activities}}) { @@ -3665,7 +3771,7 @@ sub echodevice_Parse($$$) { readingsBulkUpdate($devicehash, "presence", ($device->{online}?"present":"absent"), 1); #readingsBulkUpdate($devicehash, "state", "absent", 1) if(!$device->{online}); readingsBulkUpdate($devicehash, "version", $device->{softwareVersion}, 1); - readingsEndUpdate($devicehash,1); + $devicehash->{helper}{".SERIAL"} = $device->{serialNumber}; $devicehash->{helper}{DEVICETYPE} = $device->{deviceType}; $devicehash->{helper}{NAME} = $device->{accountName}; @@ -3673,6 +3779,19 @@ sub echodevice_Parse($$$) { $devicehash->{helper}{VERSION} = $device->{softwareVersion}; $devicehash->{helper}{".CUSTOMER"} = $device->{deviceOwnerCustomerId}; + # Multiroom + if ($device->{deviceType} eq "A3C9PE6TNYLTCH") { + Log3 $name, 4, "[echodevice_GetSettings] Multiroom Device erkannt" ; + my $EchoMembers = ""; + foreach my $clusterMember (@{$device->{clusterMembers}}) { + if ($EchoMembers eq "") {$EchoMembers = $clusterMember;} else {$EchoMembers = $EchoMembers . "," . $clusterMember;} + } + Log3 $name, 4, "[echodevice_GetSettings] Multiroom Devices $EchoMembers" ; + readingsBulkUpdate($devicehash, "clusterMembers", $EchoMembers, 1); + } + + readingsEndUpdate($devicehash,1); + if ($device->{deviceFamily} eq "ECHO" || $device->{deviceFamily} eq "KNIGHT") { Log3 $name, 4, "[echodevice_GetSettings] SET 5 DEF=" . $devicehash->{DEF} . " TYPE=".$device->{deviceType}. 'SN Hash=' . $devicehash->{helper}{".SERIAL"} . ' SN Result=' . $device->{serialNumber} ; $hash->{helper}{".SERIAL"} = $device->{serialNumber}; @@ -4787,7 +4906,7 @@ sub echodevice_NPMLoginNew($){ } while ($NodeLoop eq "1"); # Prüfen ob das alexa-cookie Mdoul vorhanden ist - if (!(-e "cache/alexa-cookie/node_modules/alexa-cookie2/alexa-cookie.js")) { + if (!(-e $npm_fhem_home . "/cache/alexa-cookie/node_modules/alexa-cookie2/alexa-cookie.js")) { $InstallResult .= '
Das alexa-cookie Modul wurde nicht gefunden. Bitte fuehrt am Amazon Account Device einen set "NPM_install" durch
'; $InstallResult .= '