From dc6075d2db9f871e13f9cae271cafe6248be1258 Mon Sep 17 00:00:00 2001 From: Otto123 Date: Mon, 15 Mar 2021 11:17:52 +0000 Subject: [PATCH] 99_sonos2mqttUtils: code and functional improvements git-svn-id: https://svn.fhem.de/fhem/trunk@23978 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- .../AttrTemplate/99_sonos2mqttUtils.pm | 171 ++++++++++++++---- 1 file changed, 134 insertions(+), 37 deletions(-) diff --git a/fhem/contrib/AttrTemplate/99_sonos2mqttUtils.pm b/fhem/contrib/AttrTemplate/99_sonos2mqttUtils.pm index 8e4155515..f3fc465ee 100644 --- a/fhem/contrib/AttrTemplate/99_sonos2mqttUtils.pm +++ b/fhem/contrib/AttrTemplate/99_sonos2mqttUtils.pm @@ -10,11 +10,11 @@ use strict; use warnings; sub -sonos2mqttUtils_Initialize($$) +sonos2mqttUtils_Initialize { - my ($hash) = @_; + my $hash = shift; + return; } - # Enter you functions below _this_ line. ##### Responses for getList and setList commands @@ -38,8 +38,26 @@ if ($NAME eq $bridge){ fhem("setreading $tts text ".ReadingsVal($tts,'text',' ').' '.$text.";sleep 0.4 tts;set $tts tts [$tts:text];sleep $tts:playing:.0 ;set $NAME notifyall [$tts:vol] [$tts:httpName];deletereading $tts text"); } if($cmd eq 'Favorites') {return "$devicetopic/".ReadingsVal((devspec2array('a:model=sonos2mqtt_speaker'))[0],'uuid','').q(/control {"command": "adv-command","input": {"cmd": "GetFavorites","reply": "Favorites"}})} - if($cmd eq 'setplayFav') {sonos2mqtt_mod_list('a:model=sonos2mqtt_speaker','setList','playFav:'.ReadingsVal($NAME,'favlist','').' {sonos2mqtt($NAME,$EVENT)}')} + if($cmd eq 'setplayFav') {sonos2mqtt_mod_list('a:model=sonos2mqtt_speaker','setList','playFav:'.sonos2mqtt_getList($bridge,'Favorites').' {sonos2mqtt($NAME,$EVENT)}')} if($cmd eq 'setjoinGroup') {sonos2mqtt_mod_list('a:model=sonos2mqtt_speaker','setList','joinGroup:'.ReadingsVal($NAME,'grouplist','').' {sonos2mqtt($NAME,$EVENT)}')} + if($cmd eq 'Reply'){ + my $topic = "$devicetopic/".ReadingsVal((devspec2array('a:model=sonos2mqtt_speaker'))[0],'uuid','')."/control"; + my $advc = qq("command": "adv-command","input":); + my $ccmd = qq("cmd": "ContentDirectoryService.BrowseParsed","val":); + my $cval = qq("ObjectID": "SQ:","BrowseFlag": "BrowseDirectChildren","Filter": "*","StartingIndex": 0,"RequestedCount": 0,"SortCriteria": ""); + my $creply = qq("reply": "Reply"); + my $payload = qq( { $advc { $ccmd { $cval },$creply }} ); + if ($arr[1] eq 'Favorites'){ + $ccmd = qq("cmd": "GetFavorites"); + $payload= qq( { $advc { $ccmd ,$creply }} ); + } + if ($arr[1] eq 'Radios'){ + $ccmd = qq("cmd": "GetFavoriteRadioStations"); + $payload= qq( { $advc { $ccmd ,$creply }} ); + } + #Log 1, $EVENT; + return qq($topic $payload); + } return '' } # from here cmds for speaker @@ -47,18 +65,55 @@ if($cmd eq 'sayText') { ($cmd,$text) = split(' ', $EVENT,2)} my $uuid = ReadingsVal($NAME,'uuid','error'); my $topic = "$devicetopic/$uuid/control"; my $payload = $EVENT; +my ($search,$fav); if (@arr == 1){$payload = "leer"} else {$payload =~ s/$cmd //} -# if Radio next Station -my $Input = ReadingsVal($NAME,'Input',''); -if($cmd eq 'next' and $Input eq 'Radio') { - fhem("set $NAME playFav {(Each('$bridge',ReadingsVal('$bridge','favlist','')))}"); +# if Radio next Station from favlist +# no Idea to detect automatically difference between Favorites and Radios +if($cmd eq 'next' and ReadingsVal($NAME,'Input','') eq 'Radio') { + if (ReadingsVal($NAME,'transportState','') eq 'PLAYING'){ fhem("sleep 1;set $NAME play") } + fhem("set $NAME play Favorite {(Each('$bridge',ReadingsVal('$bridge','favlist',sonos2mqtt_getList ('$bridge','Favorites'))))}"); +# fhem("set $NAME play Radio {(Each('$bridge',ReadingsVal('$bridge','favlist',sonos2mqtt_getList ('$bridge','Radios'))))}"); return '' } -my @easycmd = ('stop','play','pause','toggle','volumeUp','volumeDown','next','previous'); +my @easycmd = ('stop','pause','toggle','volumeUp','volumeDown','next','previous'); if (grep { $_ eq $cmd } @easycmd) {return lc( qq($topic { "command": "$cmd" }) )} -if($cmd eq 'volume') {return qq($topic { "command": "volume", "input": $payload })} +# normally set will fading the volume or could set finalVolume startVolume: set volume <0..100> [startValue] +#if($cmd eq 'volume') {return qq($topic { "command": "volume", "input": $payload })} +if($cmd eq 'volume') { + my $vol = ReadingsNum($NAME,'volume',0); + my $d = abs $arr[1] - $vol; + my $s = $arr[1] <=> $vol; + if ($arr[2] or ($d == 1)) { + $payload = $arr[2] || $payload; + if ($arr[2] and ($arr[1] != $arr[2])) {fhem("sleep 2;set $NAME volume $arr[1]")} + return qq($topic { "command": "volume", "input": $payload }) + } else { + for (1..$d) {fhem("sleep $_;set $NAME volume {([$NAME:volume]+$s)}")} + return '' + } +} + +if ($cmd eq 'play') { + if (@arr == 1) { $payload = qq({ "command": "$cmd" }) } + else { + if (grep { $_ eq $arr[1] } ('Radio','Favorite','Playlist')) {$search = (split(' ', $EVENT,3))[2] ;$fav = $arr[1].'s'} + my ($uri,$ItemId,$UpnpClass,$CdUdn)=sonos2mqtt_searchList($search,$fav); + if ($arr[1] eq 'Playlist') { + $payload = qq({"command": "adv-command","input": { "cmd": "AVTransportService.RemoveAllTracksFromQueue" }}); + fhem("set $NAME x_raw_payload $payload"); + fhem("set $NAME input Queue"); + $payload = qq({"UpnpClass": "$UpnpClass","ItemId": "$ItemId","CdUdn": "$CdUdn"}); + $payload = qq({ "InstanceID": 0,"DesiredFirstTrackNumberEnqueued": 0,"EnqueueAsNext": true,"EnqueuedURI":"$uri","EnqueuedURIMetaData": $payload}); + $payload = qq({ "command": "adv-command","input": {"cmd": "AVTransportService.AddURIToQueue","val": $payload}}); + } + else { $payload = qq({ "command": "setavtransporturi", "input": "$uri"}) } + } + #Log 1, qq(play Kommando $topic $payload); + return qq($topic $payload); +} + if($cmd eq 'joinGroup') {return qq($topic { "command": "joingroup", "input": "$payload"})} if($cmd eq 'setAVTUri') {return qq($topic { "command": "setavtransporturi", "input": "$payload"})} if($cmd eq 'notify') {return qq($topic { "command":"notify","input":{"trackUri":"$arr[2]","onlyWhenPlaying":false,"timeout":100,"volume":$arr[1],"delayMs":700}})} @@ -89,27 +144,55 @@ if($cmd eq 'speak') { } } if($cmd eq 'playFav') { - use JSON;use HTML::Entities;use Encode qw(encode decode); - my $enc = 'UTF8';my $uri='';my $search=(split(' ', $EVENT,2))[1]; - $search=~s/[\/()]/./g; - my $dev = (devspec2array('model=sonos2mqtt_bridge'))[0]; - my $decoded = decode_json(ReadingsVal($dev,'Favorites','')); - my @array=@{$decoded->{'Result'}}; - for (@array) { - if (encode($enc, decode_entities($_->{'Title'}))=~/$search/i) - {$uri = $_->{'TrackUri'} } - } - fhem("set $NAME playUri $uri") if ($uri ne ''); + fhem("set $NAME play Favorite $payload"); + fhem("sleep 1;set $NAME play"); } if($cmd eq 'sleep') { $payload = strftime("%H:%M:%S",gmtime($payload*60)); return qq($topic { "command": "sleep", "input": "$payload" }) } -if($cmd eq 'test') {Log 1, "Das Device $NAME hat ausgeloest, die uuid ist >$uuid< der Befehl war >$cmd< der Teil danach sah so aus: $payload"} +# something for playing the queue +if($cmd eq 'queue') { + if ($arr[1] eq 'clear') {return qq($topic {"command": "adv-command","input": { "cmd": "AVTransportService.RemoveAllTracksFromQueue" }})} + if ($arr[1] eq 'add') { + my ($uri,$ItemId,$UpnpClass,$CdUdn)=sonos2mqtt_searchList((split(' ', $EVENT,3))[2],'Playlists'); + $payload = qq({"UpnpClass": "$UpnpClass","ItemId": "$ItemId","CdUdn": "$CdUdn"}); + $payload = qq({ "InstanceID": 0,"DesiredFirstTrackNumberEnqueued": 0,"EnqueueAsNext": true,"EnqueuedURI":"$uri","EnqueuedURIMetaData": $payload}); + $payload = qq({ "command": "adv-command","input": {"cmd": "AVTransportService.AddURIToQueue","val": $payload}}); + return qq($topic $payload); + } +} + +#if($cmd eq 'test') {Log 1, "Das Device $NAME hat ausgeloest, die uuid ist >$uuid< der Befehl war >$cmd< der Teil danach sah so aus: $payload"} +if($cmd eq 'test') {Log 1, (split(' ', $EVENT,3))[2]} if($cmd eq 'x_raw_payload') {return qq($topic $payload)} # if return for other reasons, the response had to be '' 0 or undef return ''; } +# search in Readings of SonosBridge title and return uri and metaclass items +sub sonos2mqtt_searchList +{ +use JSON;use HTML::Entities;use Encode qw(encode decode); +my $regex = shift //return''; +my $list = shift //return''; +my $enc = 'UTF8'; +my $uri = '';my $UpnpClass = '';my $ItemId = '';my $CdUdn = ''; +my $dev = (devspec2array('model=sonos2mqtt_bridge'))[0]; +my $decoded = decode_json(ReadingsVal($dev,$list,'')); +my @array=@{$decoded->{'Result'}}; + $regex=~s/[\/()]/./g; + for (@array) { + if (encode($enc, decode_entities($_->{'Title'}))=~/$regex/i) + { + $uri = $_->{'TrackUri'}; + $ItemId = $_->{'ItemId'} || ''; + $UpnpClass= $_->{'UpnpClass'} || ''; + $CdUdn= $_->{'CdUdn'} || ''; + } + } + return ($uri,$ItemId,$UpnpClass,$CdUdn); +} + ####### devStateIcon sub sonos2mqtt_devStateIcon { @@ -181,6 +264,7 @@ if ($devspec eq 'a:model=sonos2mqtt_bridge'){ sonos2mqtt_mod_list($devspec,'setList','announcementall:textField'.q( {sonos2mqtt($NAME,$EVENT)})); sonos2mqtt_mod_list($devspec,'readingList',AttrVal($bridge,"devicetopic",'sonos').'/RINCON_([0-9A-Z]+)/Favorites:.* Favorites'); sonos2mqtt_mod_list($devspec,'readingList',AttrVal($bridge,"devicetopic",'sonos').'/RINCON_([0-9A-Z]+)/Reply:.* Reply'); + sonos2mqtt_mod_list($devspec,'getList','Reply:Favorites,Radios,Playlists Reply'.q( {sonos2mqtt($NAME,$EVENT)})); return undef } @@ -194,20 +278,19 @@ for ('stop:noArg','play:noArg','pause:noArg','toggle:noArg','volume:slider,0,1,1 my @tv = ("S14","S11","S9"); my @line = ("S5","Z90","ZP120"); # to get the Favorites at this point is only a workaround. Bad for the first player -if (!ReadingsVal($bridge,'favlist',0)) {my $fav = fhem("get $bridge Favorites")} +#if (!ReadingsVal($bridge,'favlist',0)) {my $fav = fhem("get $bridge Favorites")} for (devspec2array($devspec)) { my $mn = ReadingsVal($_,'modelNumber',''); if (grep {/$mn/} @tv) {sonos2mqtt_mod_list($_,'setList','input:Queue,TV'.q( {sonos2mqtt($NAME,$EVENT)}))} if (grep {/$mn/} @line) {sonos2mqtt_mod_list($_,'setList','input:Queue,Line_In'.q( {sonos2mqtt($NAME,$EVENT)}))} sonos2mqtt_mod_list($_,'setList','joinGroup:'.ReadingsVal($bridge,'grouplist','').q( {sonos2mqtt($NAME,$EVENT)})); - sonos2mqtt_mod_list($_,'setList','playFav:'.ReadingsVal($bridge,'favlist','').q( {sonos2mqtt($NAME,$EVENT)})); +# sonos2mqtt_mod_list($_,'setList','playFav:'.ReadingsVal($bridge,'favlist','').q( {sonos2mqtt($NAME,$EVENT)})); fhem("set $_ volumeUp; set $_ volumeDown"); # trick to initiate the userReadings } return '' } #### code for notify for two different triggers: defined and IPAddress is responded -# delete n_configSonos. # defmod n_configSonos notify global:DEFINED.MQTT2_RINCON_[A-Z0-9]+|MQTT2_RINCON_[A-Z0-9]+:IPAddress:.* {sonos2mqtt_nty($NAME,$EVENT)} # for Test use "test $EVENT" sub sonos2mqtt_nty @@ -249,18 +332,7 @@ if ($reading eq 'grouplist'){ return join(',', sort @out) } if ($reading eq 'favlist'){ - use JSON; - use HTML::Entities; - use Encode qw(encode decode); - my $enc = 'UTF8'; - my $decoded = decode_json(ReadingsVal($name,'Favorites','')); - my @arr = @{$decoded->{'Result'}}; - for (@arr) { - my $dec = encode($enc, decode_entities($_->{'Title'})); - $dec =~ s/\s/./g; - if ($_->{'TrackUri'} =~ /x-sonosapi-stream/) {push @out,$dec} - } - return join ',', sort @out +sonos2mqtt_getList ($name,'Favorites') } if ($reading eq 'Input') { @@ -272,6 +344,31 @@ if ($reading eq 'Input') { } } +# return a list of Titles from the favorite json Arrays +# the Favorites Array will be filtered and returns only Radio stations +sub sonos2mqtt_getList +{ +my $name = shift // return ''; +my $reading = shift || 'Favorites'; +my @out; + use JSON; + use HTML::Entities; + use Encode qw(encode decode); + my $enc = 'UTF8'; + my $decoded = decode_json(ReadingsVal($name,$reading,'')); + my @arr = @{$decoded->{'Result'}}; + for (@arr) { + my $dec = encode($enc, decode_entities($_->{'Title'})); + $dec =~ s/\s/./g; + if ($reading eq 'Favorites') { + if ( $_->{'TrackUri'} =~ /x-sonosapi-stream/) { + push @out,$dec + } + } else {push @out,$dec} + } + return join ',', sort @out +} + 1; =pod =begin html