diff --git a/21_HEOSGroup.pm b/21_HEOSGroup.pm index f654468..b9ff0f8 100644 --- a/21_HEOSGroup.pm +++ b/21_HEOSGroup.pm @@ -36,7 +36,11 @@ use warnings; use JSON qw(decode_json); use Encode qw(encode_utf8); -my $version = "0.1.63"; + +my $version = "0.1.68"; + + + # Declare functions sub HEOSGroup_Initialize($); @@ -52,6 +56,9 @@ sub HEOSGroup_GetGroupInfo($); sub HEOSGroup_GetGroupVolume($); sub HEOSGroup_GetGroupMute($); + + + sub HEOSGroup_Initialize($) { my ($hash) = @_; @@ -100,7 +107,7 @@ sub HEOSGroup_Define($$) { return "too few parameters: define HEOSGroup " if( @a < 2 ); my ($name,$gid) = @a; - + $hash->{GID} = $gid; $hash->{VERSION} = $version; $hash->{NOTIFYDEV} = "HEOSPlayer".abs($gid); @@ -117,7 +124,7 @@ sub HEOSGroup_Define($$) { $iodev = $hash->{IODev}->{NAME}; my $code = abs($gid); - + $code = $iodev."-".$code if( defined($iodev) ); my $d = $modules{HEOSGroup}{defptr}{$code}; @@ -218,7 +225,7 @@ sub HEOSGroup_Notify($$) { #my %playerEevents = map { my ( $key, $value ) = split /:\s/; $value =~ s/^\s+//; ( $key, $value ) } @$events; my %playerEevents = map { my ( $key, $value ) = split /:\s/; ( $key, $value ) } @$events; - + foreach my $key ( keys %playerEevents ) { #### playing Infos @@ -236,7 +243,7 @@ sub HEOSGroup_Set($$@) { my $action; my $heosCmd; my $rvalue; - my $favorit; + my $favorit; my $favoritcount = 1; my $string = "gid=$gid"; @@ -410,7 +417,7 @@ sub HEOSGroup_Parse($$) { if( defined($decode_json->{gid}) ) { - $gid = $decode_json->{gid}; + $gid = $decode_json->{gid}; $code = abs($gid); $code = $io_hash->{NAME} ."-". $code if( defined($io_hash->{NAME}) ); @@ -491,7 +498,7 @@ sub HEOSGroup_WriteReadings($$) { if ( ref($decode_json->{payload}{players}) eq "ARRAY" ) { my @members; - + foreach my $player (@{ $decode_json->{payload}{players} }) { readingsBulkUpdate( $hash, 'leader', $player->{name} ) if ( $player->{role} eq "leader" ); @@ -523,21 +530,21 @@ sub HEOSGroup_PreProcessingReadings($$) { my $name = $hash->{NAME}; my $reading; my %buffer; - my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); - - + my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); + + Log3 $name, 4, "HEOSGroup ($name) - preprocessing readings"; if ( $decode_json->{heos}{command} =~ /volume_changed/ or $decode_json->{heos}{command} =~ /set_volume/ or $decode_json->{heos}{command} =~ /get_volume/ ) { my @value = split('&', $decode_json->{heos}{message}); - + $buffer{'volume'} = substr($value[1],6); $buffer{'mute'} = substr($value[2],5) if( $decode_json->{heos}{command} =~ /volume_changed/ ); } elsif ( $decode_json->{heos}{command} =~ /volume_up/ or $decode_json->{heos}{command} =~ /volume_down/ ) { my @value = split('&', $decode_json->{heos}{message}); - + $buffer{'volumeUp'} = substr($value[1],5) if( $decode_json->{heos}{command} =~ /volume_up/ ); $buffer{'volumeDown'} = substr($value[1],5) if( $decode_json->{heos}{command} =~ /volume_down/ ); diff --git a/21_HEOSMaster.pm b/21_HEOSMaster.pm index c3a7a13..f0ca1ba 100644 --- a/21_HEOSMaster.pm +++ b/21_HEOSMaster.pm @@ -41,13 +41,20 @@ package main; use strict; use warnings; - -use JSON qw(decode_json); -use Encode qw(encode_utf8); -use Net::Telnet; use Data::Dumper; -my $version = "0.1.63"; +my $missingModul = ""; +my $missingModulNet = ""; + +eval "use Net::Telnet;1" or $missingModul .= "Net::Telnet "; +eval "use JSON;1" or $missingModul .= "JSON "; +eval "use Encode;1" or $missingModul .= "Encode "; +eval "use IO::Socket::Multicast;1" or $missingModulNet .= "IO::Socket::Multicast "; + + + + +my $version = "0.1.68"; my %heosCmds = ( 'enableChangeEvents' => 'system/register_for_change_events?enable=', @@ -91,9 +98,12 @@ my %heosCmds = ( 'GroupVolumeDown' => 'group/volume_down?', 'getNowPlayingMedia' => 'player/get_now_playing_media?', 'eventChangeVolume' => 'event/player_volume_changed', - 'createGroup' => 'group/set_group?' + 'createGroup' => 'group/set_group?', + 'searchCriteria' => 'browse/get_search_criteria?', + 'search' => 'browse/search?' ); + # Declare functions sub HEOSMaster_Initialize($); sub HEOSMaster_Define($$); @@ -104,7 +114,7 @@ sub HEOSMaster_Close($); sub HEOSMaster_Read($); sub HEOSMaster_Write($@); sub HEOSMaster_Attr(@); -sub HEOSMaster_firstRun($); +sub HEOSMaster_FirstRun($); sub HEOSMaster_ResponseProcessing($$); sub HEOSMaster_WriteReadings($$); sub HEOSMaster_GetPlayers($); @@ -124,6 +134,11 @@ sub HEOSMaster_GetInputs($); sub HEOSMaster_GetMusicSources($); sub HEOSMaster_GetPlaylists($); sub HEOSMaster_GetServers($); +sub HEOSMaster_Hexdump; +sub HEOSMaster_MakePlayLink($$$$) + + + sub HEOSMaster_Initialize($) { @@ -169,6 +184,8 @@ sub HEOSMaster_Define($$) { $hash->{HOST} = $host; $hash->{VERSION} = $version; + return Log3 $name, 3, "Cannot define a HEOS device. Perl modul $missingModul is missing." if ( $missingModul ); + Log3 $name, 3, "HEOSMaster ($name) - defined with host $host"; $attr{$name}{room} = "HEOS" if( !defined( $attr{$name}{room} ) ); @@ -179,11 +196,11 @@ sub HEOSMaster_Define($$) { if( $init_done ) { - HEOSMaster_firstRun($hash); + HEOSMaster_FirstRun($hash); } else { - InternalTimer( gettimeofday()+15, 'HEOSMaster_firstRun', $hash, 0 ) if( ($hash->{HOST}) ); + InternalTimer( gettimeofday()+15, 'HEOSMaster_FirstRun', $hash, 0 ) if( ($hash->{HOST}) ); } $modules{HEOSPlayer}{defptr}{$host} = $hash; @@ -326,7 +343,7 @@ sub HEOSMaster_Set($@) { return "Unknown argument $cmd, choose one of $list"; } - HEOSMaster_Write($hash,$heosCmd,$action); + HEOSMaster_Write($hash,$heosCmd,$action,undef); } sub HEOSMaster_Open($) { @@ -356,13 +373,13 @@ sub HEOSMaster_Open($) { Log3 $name, 4, "HEOSMaster ($name) - Socket Connected"; #hinzugefügt laut Protokoll 2.1.1 Initsequenz - HEOSMaster_Write($hash,'enableChangeEvents','off'); + HEOSMaster_Write($hash,'enableChangeEvents','off',undef); Log3 $name, 4, "HEOSMaster ($name) - set enableChangeEvents off"; #hinzugefügt laut Protokoll 2.1.1 Initsequenz if( defined($user) and defined($password) ) { - HEOSMaster_Write($hash,'signAccountIn',"un=$user&pw=$password"); + HEOSMaster_Write($hash,'signAccountIn',"un=$user&pw=$password",undef); Log3 $name, 4, "HEOSMaster ($name) - sign in"; } @@ -370,6 +387,7 @@ sub HEOSMaster_Open($) { InternalTimer( gettimeofday()+1, 'HEOSMaster_EnableChangeEvents', $hash, 0 ); InternalTimer( gettimeofday()+2, 'HEOSMaster_GetMusicSources', $hash, 0 ); InternalTimer( gettimeofday()+3, 'HEOSMaster_GetGroups', $hash, 0 ); + InternalTimer( gettimeofday()+10, 'HEOSMaster_GetPlayers', $hash, 0 ); } sub HEOSMaster_Close($) { @@ -400,16 +418,22 @@ sub HEOSMaster_ReOpen($) { sub HEOSMaster_Write($@) { - my ($hash,$heosCmd,$value) = @_; + my ($hash,$heosCmd,$value,$blocking) = @_; my $name = $hash->{NAME}; my $string = "heos://$heosCmds{$heosCmd}"; - if( defined($value) ) { $string .= "${value}" if( $value ne '&' ); } + if ( defined $blocking ) { + + my $idx = $blocking->{cl}{LASTACCESS}; + $hash->{helper}{blocking}{$idx} = $blocking; + $string .= "&SEQUENCE=$idx"; + } + $string .= "\r\n"; Log3 $name, 4, "HEOSMaster ($name) - WriteFn called"; @@ -523,36 +547,36 @@ sub HEOSMaster_ResponseProcessing($$) { return Log3 $name, 4, "HEOSMaster ($name) - heos worked" if( $decode_json->{heos}{message} =~ /command\sunder\sprocess/ ); - + if( defined($decode_json->{heos}{result}) or $decode_json->{heos}{command} =~ /^system/ ) { HEOSMaster_WriteReadings($hash,$decode_json); Log3 $name, 4, "HEOSMaster ($name) - call Sub HEOSMaster_WriteReadings"; } - - my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); - - return Log3 $name, 4, "HEOSMaster ($name) - general error ID $message{eid} - $message{text}" - if( defined($decode_json->{heos}{result}) && $decode_json->{heos}{result} =~ /fail/ ); - + + my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); + + return Log3 $name, 4, "HEOSMaster ($name) - general error ID $message{eid} - $message{text}" + if( defined($decode_json->{heos}{result}) && $decode_json->{heos}{result} =~ /fail/ ); + #Quellen neu einlesen if( $decode_json->{heos}{command} =~ /^event\/sources_changed/ ) { - HEOSMaster_Write($hash,'getMusicSources',undef); + HEOSMaster_Write($hash,'getMusicSources',undef,undef); return Log3 $name, 4, "HEOSMaster ($name) - source changed"; } #Player neu einlesen if( $decode_json->{heos}{command} =~ /^event\/players_changed/ ) { - HEOSMaster_Write($hash,'getPlayers',undef); + HEOSMaster_Write($hash,'getPlayers',undef,undef); return Log3 $name, 4, "HEOSMaster ($name) - player changed"; } #User neu einlesen if( $decode_json->{heos}{command} =~ /^event\/user_changed/ ) { - HEOSMaster_Write($hash,'checkAccount',undef); + HEOSMaster_Write($hash,'checkAccount',undef,undef); return Log3 $name, 4, "HEOSMaster ($name) - user changed"; } @@ -560,10 +584,25 @@ sub HEOSMaster_ResponseProcessing($$) { if( $decode_json->{heos}{command} =~ /^event\/groups_changed/ ) { #InternalTimer( gettimeofday()+5, 'HEOSMaster_GetGroups', $hash, 0 ); - HEOSMaster_Write($hash,'getGroups',undef); + HEOSMaster_Write($hash,'getGroups',undef,undef); return Log3 $name, 4, "HEOSMaster ($name) - groups changed"; } + #Queue für Player neu einlesen + if ( $decode_json->{heos}{command} =~ /^event\/player_queue_changed/ ) { + + HEOSMaster_Write($hash,'getQueue',"pid=$message{pid}",undef); + return Log3 $name, 3, "HEOSMaster ($name) - queue changed"; + + } + + #Playlisten neu einlesen da Queue als Playlist gespeichert wurde + if( $decode_json->{heos}{command} =~ /^player\/save_queue/ ) { + + HEOSMaster_Write($hash,'browseSource','sid=1025',undef); + return Log3 $name, 4, "HEOSMaster ($name) - playlist changed"; + } + if( $decode_json->{heos}{command} =~ /^browse\/get_music_sources/ and ref($decode_json->{payload}) eq "ARRAY" and scalar(@{$decode_json->{payload}}) > 0) { #liest nur die Onlinequellen der Rest wird extra eingelesen @@ -606,6 +645,10 @@ sub HEOSMaster_ResponseProcessing($$) { #Onlinedienste push( @{$hash->{helper}{sources}},$payload); Log3 $name, 4, "HEOSMaster ($name) - GetRadioSource {$payload->{name} with sid $payload->{sid}"; + + foreach my $source (@{$hash->{helper}{sources}}) { + HEOSMaster_Write($hash,'searchCriteria','sid='.$source->{sid},undef); + } } } @@ -657,16 +700,47 @@ sub HEOSMaster_ResponseProcessing($$) { } elsif( $message{sid} eq '1027' ) { #Inputs einlesen - push( @{$hash->{helper}{sources}}, map { $_->{name} .= " AUX"; $_ } (@{$decode_json->{payload}}) ); + $hash->{helper}{aux} = [] if ( $message{range} == 0 ); + push( @{$hash->{helper}{aux}}, (@{$decode_json->{payload}}) ); + + foreach my $item (@{$decode_json->{payload}}) { + + #$hash->{helper}{aux}{$item->{sid}} = $item->{name}; + HEOSMaster_Write($hash,'browseSource',"sid=$item->{sid}",undef); + Log3 $name, 3, "HEOSMaster ($name) - call Browser for Input with sid $item->{sid}"; + } } elsif( $message{sid} eq '1024' ) { #Lokal einlesen push( @{$hash->{helper}{sources}}, map { $_->{name} .= " USB" if ( $_->{sid} < 0 ); $_ } (@{$decode_json->{payload}}) ); + + foreach my $source (@{$hash->{helper}{sources}}) { + HEOSMaster_Write($hash,'searchCriteria','sid='.$source->{sid},undef); + Log3 $name, 3, "HEOSMaster ($name) - call Browser for searchCriteria for sid $source->{sid}"; + } + } else { - - #aktuellen Input/Media einlesen + + #AUX Eingang des Player im Player abspeichern + if ( defined $hash->{helper}{aux} && grep( $_->{sid} =~ /$message{sid}/, (@{ $hash->{helper}{aux} }) ) ) { + + my $code = abs($message{sid}); + $code = $hash->{NAME} ."-". $code if( defined($hash->{NAME}) ); + + if( my $phash = $modules{HEOSPlayer}{defptr}{$code} ) { + + $phash->{helper}{aux} = $decode_json->{payload}; + } + + $json = '{"heos": {"command": "event/player_aux_changed", "message": "pid='.$message{sid}.'"}}'; + Dispatch($hash,$json,undef); + Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for AUX Changed"; + + } + + #aktuelle Medien einlesen $hash->{helper}{media} = [] if ( $message{range} == 0 ); push( @{$hash->{helper}{media}}, (@{$decode_json->{payload}}) ); } @@ -675,14 +749,139 @@ sub HEOSMaster_ResponseProcessing($$) { if ( $start < $message{count} ) { - HEOSMaster_Write($hash,'browseSource',"sid=$message{sid}&range=$start,".($start + 100) ); + my $path = "sid=$message{sid}"; + $path .= "&cid=$message{cid}" if ( defined $message{cid} ); + $path .= "&SEQUENCE=$message{SEQUENCE}" if ( defined $message{SEQUENCE} ); + HEOSMaster_Write($hash,'browseSource',"$path&range=$start,".($start + 100),undef); Log3 $name, 3, "HEOSMaster ($name) - call Browser with sid $message{sid} next Range from $message{returned}"; + + } else { + if ( defined $message{SEQUENCE} ) { + + my $idx = $message{SEQUENCE}; + if( defined $hash->{helper}{blocking}{$idx} && $hash->{helper}{blocking}{$idx}{cl}{canAsyncOutput} ) { + + my @list; + my $xcmd; + my $xtext; + my $ret = "$hash->{helper}{blocking}{$idx}{sourcename}\n"; + $ret .= sprintf( "%-35s %-15s %s\n", 'key', 'type', 'title' ); + + if ( $message{sid} eq "1025" ) { + + @list = (@{$hash->{helper}{playlists}}); + + } elsif ( $message{sid} eq "1026" ) { + + @list = (@{$hash->{helper}{history}}); + + } elsif ( $message{sid} eq "1027" ) { + + @list = (@{$hash->{helper}{aux}}); + + } elsif ( $message{sid} eq "1028" ) { + + @list = (@{$hash->{helper}{favorites}}); + + } elsif ( $message{sid} eq "1029" ) { + + my $code = abs($hash->{helper}{blocking}{$idx}{pid}); + $code = $hash->{NAME} ."-". $code if( defined($hash->{NAME}) ); + + if( my $phash = $modules{HEOSPlayer}{defptr}{$code} ) { + + @list = (@{$phash->{helper}{queue}}); + + } + } else { + + @list = (@{$hash->{helper}{media}}); + + } + + my $x = 0; + foreach my $item (@list) { + $ret .= HEOSMaster_MakePlayLink($hash->{helper}{blocking}{$idx}{name}, \%message, $item, ++$x); + } + + $ret .= "\n\n"; + + if( $hash->{helper}{blocking}{$idx}{cl}->{TYPE} eq 'FHEMWEB' ) { + + $ret =~ s/&/&/g; + $ret =~ s/'/'/g; + $ret =~ s/\n/
/g; + $ret = "
$ret
" if( $ret =~ m/ / ); + $ret = "$ret"; + + } else { + + $ret =~ s/]*>//g; + $ret =~ s/<\/a>//g; + $ret =~ s/]*>\n//g; + $ret .= "\n"; + } + + asyncOutput( $hash->{helper}{blocking}{$idx}{cl}, $ret ); + delete $hash->{helper}{blocking}{$idx}; + } + } } return; } } + if( $decode_json->{heos}{command} =~ /^browse\/get_search_criteria/ && ref($decode_json->{payload}) eq "ARRAY" && scalar(@{$decode_json->{payload}}) > 0) { + + push( @{$hash->{helper}{search}{$message{sid}}}, (@{$decode_json->{payload}}) ); + Log3 $name, 3, "HEOSMaster ($name) - call Browser with sid $message{sid}"; + } + + if( $decode_json->{heos}{command} =~ /^browse\/search/ ) { + + Log3 $name, 3, "HEOSMaster ($name) - call search for $message{sid}"; + + if ( defined $message{range} ) { + + $message{range} =~ s/(\d+)\,\d+/$1/; + + } else { + + $message{range} = 0; + $hash->{helper}{searchresult} = []; + } + + my $start = $message{range} + $message{returned}; + push( @{$hash->{helper}{searchresult}}, (@{$decode_json->{payload}}) ); + + if ( $start < $message{count} ) { + + HEOSMaster_Write($hash,"search","sid=$message{sid}&search=$message{search}&scid=$message{scid}",undef); + Log3 $name, 3, "HEOSMaster ($name) - call Search for $message{sid} next Range from $message{returned}"; + + } else { + + if( $hash->{helper}{blocking} && $hash->{helper}{blocking}{cl}{canAsyncOutput} ) { + + my $ret = ''; + + $ret .= sprintf( "%-35s %-10s %s\n", 'Fav', 'type', 'title' ); + foreach my $item (@{ $hash->{helper}{searchresult}}) { + + $ret .= HEOSMaster_MakePlayLink($hash->{helper}{blocking}{name}, 'input', $message{sid}, $item, sprintf( "%-35s %-10s %s\n", "x", $item->{type}, $item->{name} ) ); + } + + $ret .= "\n\n"; + + asyncOutput( $hash->{helper}{blocking}{cl}, $ret ); + delete $hash->{helper}{blocking}; + } + } + + Log3 $name, 4, "HEOSMaster ($name) - call Browser for Search"; + } + if( $decode_json->{heos}{command} =~ /^player/ or $decode_json->{heos}{command} =~ /^event\/player/ or $decode_json->{heos}{command} =~ /^group/ or $decode_json->{heos}{command} =~ /^event\/group/ or $decode_json->{heos}{command} =~ /^event\/repeat_mode_changed/ or $decode_json->{heos}{command} =~ /^event\/shuffle_mode_changed/ ) { if( $decode_json->{heos}{command} =~ /player\/get_players/ ) { @@ -690,6 +889,8 @@ sub HEOSMaster_ResponseProcessing($$) { return Log3 $name, 4, "HEOSMaster ($name) - empty ARRAY received" unless(scalar(@{$decode_json->{payload}}) > 0); + my $filter = "TYPE=HEOSPlayer:FILTER=PID!="; + foreach my $payload (@{$decode_json->{payload}}) { $json = '{"pid": "'; @@ -697,6 +898,16 @@ sub HEOSMaster_ResponseProcessing($$) { $json .= '","heos": {"command": "player/get_player_info"}}'; Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for Players"; + $filter .= $payload->{pid}."|"; + } + + chop($filter); #letztes | wieder abschneiden + + #alle Player ausschalten die nicht mehr im HEOS System existieren + foreach my $dev ( devspec2array($filter) ) { + + my $phash = $defs{$dev}; + readingsSingleUpdate( $phash, "state", "off", 1 ); } } elsif( $decode_json->{heos}{command} =~ /group\/get_groups/ ) { @@ -720,19 +931,12 @@ sub HEOSMaster_ResponseProcessing($$) { chop($filter); #letztes | wieder abschneiden } - #alle Gruppe ausschalten die nicht mehr im HEOS System existieren + #alle Gruppe ausschalten die nicht mehr im HEOS System existieren foreach my $dev ( devspec2array($filter) ) { my $ghash = $defs{$dev}; readingsSingleUpdate( $ghash, "state", "off", 1 ); - } - - } elsif( $decode_json->{heos}{command} =~ /player\/get_queue/ ) { - return Log3 $name, 4, "HEOSMaster ($name) - empty ARRAY received" - unless(scalar(@{$decode_json->{payload}}) > 0); - - Dispatch($hash,$json,undef); - Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for QueueInfo"; + } } elsif( $decode_json->{heos}{command} =~ /player\/get_player_info/ ) { # ist vielleicht verständlicher? @@ -743,7 +947,45 @@ sub HEOSMaster_ResponseProcessing($$) { Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for GroupInfo"; - + + } elsif( $decode_json->{heos}{command} =~ /player\/get_queue/ ) { + + Log3 $name, 3, "HEOSMaster ($name) - call getQueue for player $message{pid}"; + + if ( defined $message{range} ) { + + $message{range} =~ s/(\d+)\,\d+/$1/; + + } else { + + $message{range} = 0; + $hash->{helper}{queue}{$message{pid}} = []; + } + + my $start = $message{range} + $message{returned}; + push( @{$hash->{helper}{queue}{$message{pid}}}, (@{$decode_json->{payload}}) ); + + if ( $start < $message{count} ) { + + HEOSMaster_Write($hash,'getQueue',"pid=$message{pid}&range=$start,".($start + 100),undef); + Log3 $name, 3, "HEOSMaster ($name) - call getQueue for player pid $message{pid} next Range from $message{returned}"; + + } else { + + my $code = abs($message{pid}); + $code = $hash->{NAME} ."-". $code if( defined($hash->{NAME}) ); + + if( my $phash = $modules{HEOSPlayer}{defptr}{$code} ) { + + $phash->{helper}{queue} = $hash->{helper}{queue}; + delete $hash->{helper}{queue}; + } + + $json = '{"heos": {"command": "event/player_queue_changed", "message": "pid='.$message{pid}.'"}}'; + Dispatch($hash,$json,undef); + Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for Queue Changed"; + } + } elsif( defined($message{pid}) or defined($message{gid}) ) { Dispatch($hash,$json,undef); @@ -846,7 +1088,7 @@ sub HEOSMaster_ParseMsg($$) { } } - Log3 $name, 5, "HEOSMaster ($name) - return msg: $msg and tail: $tail"; + #Log3 $name, 5, "HEOSMaster ($name) - return msg: $msg and tail: $tail"; return ($msg,$tail); } @@ -859,7 +1101,7 @@ sub HEOSMaster_PreProcessingReadings($$) { my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); - Log3 $name, 4, "HEOSMaster ($name) - preprocessing readings"; + Log3 $name, 4, "HEOSMaster ($name) - preprocessing readings"; if ( $decode_json->{heos}{command} eq 'system/register_for_change_events' ) { @@ -886,13 +1128,13 @@ sub HEOSMaster_PreProcessingReadings($$) { return \%buffer; } -sub HEOSMaster_firstRun($) { +sub HEOSMaster_FirstRun($) { my $hash = shift; my $name = $hash->{NAME}; - RemoveInternalTimer($hash,'HEOSMaster_firstRun'); + RemoveInternalTimer($hash,'HEOSMaster_FirstRun'); HEOSMaster_Open($hash) if( !IsDisabled($name) ); } @@ -903,7 +1145,7 @@ sub HEOSMaster_GetPlayers($) { RemoveInternalTimer($hash,'HEOSMaster_GetPlayers'); - HEOSMaster_Write($hash,'getPlayers',undef); + HEOSMaster_Write($hash,'getPlayers',undef,undef); Log3 $name, 4, "HEOSMaster ($name) - getPlayers"; } @@ -914,7 +1156,7 @@ sub HEOSMaster_GetGroups($) { RemoveInternalTimer($hash,'HEOSMaster_GetGroups'); - HEOSMaster_Write($hash,'getGroups',undef); + HEOSMaster_Write($hash,'getGroups',undef,undef); Log3 $name, 4, "HEOSMaster ($name) - getGroups"; } @@ -925,7 +1167,7 @@ sub HEOSMaster_EnableChangeEvents($) { RemoveInternalTimer($hash,'HEOSMaster_EnableChangeEvents'); - HEOSMaster_Write($hash,'enableChangeEvents','on'); + HEOSMaster_Write($hash,'enableChangeEvents','on',undef); Log3 $name, 4, "HEOSMaster ($name) - set enableChangeEvents on"; } @@ -936,7 +1178,7 @@ sub HEOSMaster_GetMusicSources($) { RemoveInternalTimer($hash, 'HEOSMaster_GetMusicSources'); - HEOSMaster_Write($hash,'getMusicSources',undef); + HEOSMaster_Write($hash,'getMusicSources',undef,undef); Log3 $name, 4, "HEOSMaster ($name) - getMusicSources"; } @@ -947,7 +1189,7 @@ sub HEOSMaster_GetFavorites($) { RemoveInternalTimer($hash, 'HEOSMaster_GetFavorites'); - HEOSMaster_Write($hash,'browseSource','sid=1028'); + HEOSMaster_Write($hash,'browseSource','sid=1028',undef); Log3 $name, 4, "HEOSMaster ($name) - getFavorites"; } @@ -958,7 +1200,7 @@ sub HEOSMaster_GetInputs($) { RemoveInternalTimer($hash, 'HEOSMaster_GetInputs'); - HEOSMaster_Write($hash,'browseSource','sid=1027'); + HEOSMaster_Write($hash,'browseSource','sid=1027',undef); Log3 $name, 4, "HEOSMaster ($name) - getInputs"; } @@ -969,7 +1211,7 @@ sub HEOSMaster_GetServers($) { RemoveInternalTimer($hash, 'HEOSMaster_GetServers'); - HEOSMaster_Write($hash,'browseSource','sid=1024'); + HEOSMaster_Write($hash,'browseSource','sid=1024',undef); Log3 $name, 4, "HEOSMaster ($name) - getServers"; } @@ -980,7 +1222,7 @@ sub HEOSMaster_GetPlaylists($) { RemoveInternalTimer($hash, 'HEOSMaster_GetPlaylists'); - HEOSMaster_Write($hash,'browseSource','sid=1025'); + HEOSMaster_Write($hash,'browseSource','sid=1025',undef); Log3 $name, 4, "HEOSMaster ($name) - getPlaylists"; } @@ -991,7 +1233,7 @@ sub HEOSMaster_GetHistory($) { RemoveInternalTimer($hash, 'HEOSMaster_GetHistory'); - HEOSMaster_Write($hash,'browseSource','sid=1026'); + HEOSMaster_Write($hash,'browseSource','sid=1026',undef); Log3 $name, 4, "HEOSMaster ($name) - getHistory"; } @@ -1002,7 +1244,7 @@ sub HEOSMaster_CheckAccount($) { RemoveInternalTimer($hash, 'HEOSMaster_CheckAccount'); - HEOSMaster_Write($hash,'checkAccount',undef); + HEOSMaster_Write($hash,'checkAccount',undef,undef); Log3 $name, 4, "HEOSMaster ($name) - checkAccount"; } @@ -1078,14 +1320,84 @@ sub HEOSMaster_ReadPassword($) { } } -################ -### Nur für mich um dem Emulator ein Event ab zu jagen -sub HEOSMaster_send($) { +sub HEOSMaster_MakePlayLink($$$$) { - my $hash = shift; + my ($name, $message, $item, $idx) = @_; + my $xcmd; + my $xtext = $message->{sid}; + + #return $txt if( !$FW_ME ); + + if ( (exists $item->{playable} && $item->{playable} eq "yes") || exists $item->{qid} ) { + $xcmd = 'cmd'.uri_escape('=set '.$name.' input '.$message->{sid}); + + } else { - HEOSMaster_Write($hash,'eventChangeVolume',undef); + $xcmd = 'cmd'.uri_escape('=get '.$name.' ls '.$message->{sid}); + } + + if ( defined $item->{sid} ) { + + $xcmd = 'cmd'.uri_escape('=get '.$name.' ls '.$message->{sid}); + $xcmd .= uri_escape(",".$item->{sid}); + $xtext .= ','.$item->{sid}; + + } elsif ( defined $item->{cid} ) { + if ( $item->{type} eq "album" ) { + + $xcmd = 'cmd'.uri_escape('=get '.$name.' ls '.$message->{sid}); + } + + $xcmd .= uri_escape(",".$item->{cid}); + $xtext .= ','.$item->{cid}; + + } elsif ( defined $item->{mid} ) { + if ( $message->{sid} eq "1028" ) { + + $xcmd .= ','.$idx; + $xtext .= ','.$idx; + + } elsif ( defined $message->{cid} ) { + + $xcmd .= uri_escape(','.$message->{cid}.','.$item->{mid}); + $xtext .= ','.$message->{cid}.','.$item->{mid}; + + } else { + + $xcmd = 'cmd'.uri_escape('=set '.$name.' input 1027'); + $xcmd .= uri_escape(','.$message->{sid}.','.$item->{mid}); + $xtext = '1027,'.$message->{sid}.','.$item->{mid}; + } + } elsif ( defined $item->{qid} ) { + + $xcmd .= ','.$item->{qid}; + $xtext .= ','.$item->{qid}; + } + + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + return '
  • '.sprintf( "%-35s %-15s %s", $xtext, $item->{type}, $item->{name} )."
  • \n"; + #$FW_ME$FW_subdir +} + +sub HEOSMaster_Hexdump { + my $str = ref $_[0] ? ${$_[0]} : $_[0]; + + return "[ZERO-LENGTH STRING]\n" unless length $str; + + # split input up into 16-byte chunks: + my @chunks = $str =~ /([\0-\377]{1,16})/g; + # format and print: + my @print; + for (@chunks) { + my $hex = unpack "H*", $_; + tr/ -~/./c; # mask non-print chars + $hex =~ s/(..)(?!$)/$1 /g; # insert spaces in hex + # make sure our hex output has the correct length + $hex .= ' ' x ( length($hex) < 48 ? 48 - length($hex) : 0 ); + push @print, "$hex $_\n"; + } + wantarray ? @print : join '', @print; } @@ -1095,5 +1407,4 @@ sub HEOSMaster_send($) { - 1; diff --git a/21_HEOSPlayer.pm b/21_HEOSPlayer.pm index 653b2e3..c10f906 100644 --- a/21_HEOSPlayer.pm +++ b/21_HEOSPlayer.pm @@ -35,9 +35,13 @@ use strict; use warnings; use JSON qw(decode_json); use Encode qw(encode_utf8); +use URI::Escape; use Data::Dumper; -my $version = "0.1.63"; +my $version = "0.1.68"; + + + # Declare functions sub HEOSPlayer_Initialize($); @@ -50,11 +54,16 @@ sub HEOSPlayer_Set($$@); sub HEOSPlayer_PreProcessingReadings($$); sub HEOSPlayer_GetPlayerInfo($); sub HEOSPlayer_GetPlayState($); +sub HEOSPlayer_GetQueue($) sub HEOSPlayer_GetNowPlayingMedia($); sub HEOSPlayer_GetPlayMode($); sub HEOSPlayer_GetVolume($); sub HEOSPlayer_Get($$@); sub HEOSPlayer_GetMute($); +sub HEOSPlayer_Hexdump; + + + sub HEOSPlayer_Initialize($) { @@ -217,47 +226,140 @@ sub HEOSPlayer_Get($$@) { my ($cmd, @args) = @aa; my $pid = $hash->{PID}; my $result = ""; - + my $me = {}; - #print "CL ###################################################\n".Dumper($hash->{CL}); - $hash->{helper}{cl} = $hash->{CL} if( ref($hash->{CL}) eq 'HASH' ); - #Leerzeichen müßen für die Rückgabe escaped werden sonst werden sie falsch angezeigt - if( $cmd eq 'playlists' ) { - - #gibt die Playlisten durch Komma getrennt zurück - my @playlists = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{playlists}}); - - $result .= join(",",@playlists) if( scalar @playlists > 0 ); - return $result; - - } elsif( $cmd eq 'channels' ) { - - #gibt die Favoriten durch Komma getrennt zurück - my @channels = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{favorites}}); - - $result .= join(",",@channels) if( scalar @channels > 0 ); - return $result; - - } elsif( $cmd eq 'channelscount' ) { + if( $cmd eq 'channelscount' ) { #gibt die Favoritenanzahl zurück return scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); - } elsif( $cmd eq 'inputs' ) { - - #gibt die Quellen durch Komma getrennt zurück - my @inputs = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{sources}}); - push(@inputs, "Warteschlange"); - $result .= join(",",@inputs) if( scalar @inputs > 0 ); - return $result; + } elsif( $cmd eq 'ls' ) { + + my $param = shift( @args ); + $param = '' if( !$param ); + #$param = substr($param,1) if( $param && $param =~ '^|' ); + + if ( $param eq '' ) { - } elsif( $cmd eq 'search' ) { - - return "usage: search " if( @args != 1 ); + my $xcmd; + my $ret = "Quellen\n"; + $ret .= sprintf( "%-35s %-15s %s\n", 'key', 'type', 'title' ); + + foreach my $item (@{ $hash->{IODev}{helper}{sources}}) { + + $xcmd = 'cmd'.uri_escape('=get '.$hash->{NAME}.' ls '.$item->{sid}); + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + $ret .= '
  • '.sprintf( "%-35s %-15s %s", $item->{sid}, $item->{type}, $item->{name} )."
  • \n"; + } + + if ( defined $hash->{IODev}{helper}{playlists} ) { + + $xcmd = 'cmd'.uri_escape('=get '.$hash->{NAME}.' ls 1025'); + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + $ret .= '
  • '.sprintf( "%-35s %-15s %s", '1025', "heos_service", "Playlist" )."
  • \n"; + } + + if ( defined $hash->{IODev}{helper}{history} ) { + + $xcmd = 'cmd'.uri_escape('=get '.$hash->{NAME}.' ls 1026'); + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + $ret .= '
  • '.sprintf( "%-35s %-15s %s", '1026', "heos_service", "Verlauf" )."
  • \n"; + } + + if ( defined $hash->{IODev}{helper}{aux} ) { + + $xcmd = 'cmd'.uri_escape('=get '.$hash->{NAME}.' ls 1027'); + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + $ret .= '
  • '.sprintf( "%-35s %-15s %s", '1027', "heos_service", "Eingänge" )."
  • \n"; + } + + if ( defined $hash->{IODev}{helper}{favorites} ) { + + $xcmd = 'cmd'.uri_escape('=get '.$hash->{NAME}.' ls 1028'); + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + $ret .= '
  • '.sprintf( "%-35s %-15s %s", '1028', "heos_service", "Favoriten" )."
  • \n"; + } + + if ( defined $hash->{helper}{queue} ) { + + $xcmd = 'cmd'.uri_escape('=get '.$hash->{NAME}.' ls 1029'); + $xcmd = "FW_cmd('/fhem?XHR=1&$xcmd')"; + $ret .= '
  • '.sprintf( "%-35s %-15s %s", '1029', "heos_service", "Warteschlange" )."
  • \n"; + } + + $ret .= "\n\n"; + return $ret; + + } else { + + my @path = split(",", $param); + my $sid = $path[0] if ( scalar @path > 0); + my $cid = $path[1] if ( scalar @path > 1); + + $me->{cl} = $hash->{CL} if( ref($hash->{CL}) eq 'HASH' ); + $me->{name} = $hash->{NAME}; + $me->{pid} = $hash->{PID}; + + if ( $sid eq "1025" ) { + + $me->{sourcename} = "Playlist"; + + } elsif ( $sid eq "1026" ) { + + $me->{sourcename} = "Verlauf"; + + } elsif ( $sid eq "1027" ) { + + $me->{sourcename} = "Eingänge"; + + } elsif ( $sid eq "1028" ) { + + $me->{sourcename} = "Favoriten"; + + } elsif ( $sid eq "1029" ) { + + $me->{sourcename} = "Warteschlange"; + + } else { + + my @sids = map { $_->{name} } grep { $_->{sid} =~ /$sid/i } (@{ $hash->{IODev}{helper}{sources} }); + $me->{sourcename} = $sids[0] if ( scalar @sids > 0); + } + + my $heosCmd = "browseSource"; + my $action; + + if ( defined $sid && defined $cid ) { + if ( $sid eq "1027" ) { + + $action = "sid=$cid"; + + } elsif ( $sid eq "1026" ) { + + $me->{sourcename} .= "/$cid"; + $action = "sid=$sid&cid=$cid"; + + } else { + + my @cids = map { $_->{name} } grep { $_->{cid} =~ /\Q$cid\E/i } (@{ $hash->{IODev}{helper}{media} }); + $me->{sourcename} .= "/".$cids[0] if ( scalar @cids > 0); + $action = "sid=$sid&cid=$cid"; + + } + } else { + + $action = "sid=$sid"; + } + + IOWrite($hash,$heosCmd,$action,$me); + Log3 $name, 4, "HEOSPlayer ($name) - IOWrite: $heosCmd $action IODevHash=$hash->{IODev}"; + + return undef; + } } - - my $list = 'playlists:noArg channels:noArg channelscount:noArg inputs:noArg ls search'; + + my $list = 'channelscount:noArg ls'; return "Unknown argument $cmd, choose one of $list"; } @@ -274,8 +376,7 @@ sub HEOSPlayer_Set($$@) { my $qcount = 1; my $string = "pid=$pid"; - - #print "cmd ###################################################\n".Dumper($cmd); + return undef unless ( ReadingsVal($name, "state", "off") eq "on" ); if( $cmd eq 'getPlayerInfo' ) { return "usage: getPlayerInfo" if( @args != 0 ); @@ -366,8 +467,8 @@ sub HEOSPlayer_Set($$@) { $heosCmd = 'createGroup'; - } elsif( $cmd eq 'clearGroup' ) { - return "usage: clearGroup" if( @args != 0 ); + } elsif( $cmd eq 'groupClear' ) { + return "usage: groupClear" if( @args != 0 ); $heosCmd = 'createGroup'; @@ -383,7 +484,7 @@ sub HEOSPlayer_Set($$@) { } elsif ( $cmd =~ /channel/ ) { - my $favorit = ReadingsVal($name,"channel", 0); + my $favorit = ReadingsVal($name,"channel", 1); $favoritcount = scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); $heosCmd = 'playPresetStation'; @@ -391,247 +492,218 @@ sub HEOSPlayer_Set($$@) { if ( $cmd eq 'channel' ) { return "usage: channel 1-$favoritcount" if( @args != 1 ); - $action = "preset=$args[0]"; + $action = "preset=$args[0]"; } elsif( $cmd eq 'channelUp' ) { - return "usage: channelUp" if( @args != 0 ); + return "usage: $cmd" if( @args != 0 ); $favorit = $favoritcount if ( ++$favorit > $favoritcount ); - $action = "preset=".$favorit; + $action = "preset=".$favorit; } elsif( $cmd eq 'channelDown' ) { - + return "usage: $cmd" if( @args != 0 ); + $favorit = 1 if ( --$favorit <= 0 ); - $action = "preset=".$favorit; + $action = "preset=".$favorit; } + } elsif ( $cmd =~ /Queue/ ) { + + $heosCmd = $cmd; + if ( $cmd eq 'playQueue' ) { + + $qcount = scalar(@{$hash->{helper}{queue}}) if ( defined $hash->{helper}{queue} ); + return "usage: queue 1-$qcount" if( @args != 1 ); + + $action = "qid=$args[0]"; + + } elsif ( $cmd eq 'clearQueue' ) { + #löscht die Warteschlange + return "usage: $cmd" if( @args != 0 ); + + delete $hash->{helper}{queue}; + + } elsif ( $cmd eq 'saveQueue' ) { + + #speichert die aktuelle Warteschlange als Playlist ab + return "usage: saveQueue" if( @args != 1 ); + + $action = "name=$args[0]"; + } + } elsif ( $cmd =~ /Playlist/ ) { - my @cids = map { $_->{cid} } grep { $_->{name} =~ /$args[0]/i } (@{ $hash->{IODev}{helper}{playlists} }); + my $mid; + my $cid = $args[0]; + my @path = split(",", $args[0]) if ( @args != 0 && $args[0] =~ /,/ ); + $cid = $path[0] if ( scalar @path > 0); + $mid = $path[1] if ( scalar @path > 1); - if ( scalar @args == 1 && scalar @cids > 0 ) { - if ( $cmd eq 'playPlaylist' ) { + if ( scalar @args != 0 ) { + + if ( $cid !~ /^-*[0-9]+$/ ) { - $heosCmd = $cmd; - $action = "sid=1025&cid=$cids[0]&aid=4"; + my @cids = map { $_->{cid} } grep { $_->{name} =~ /\Q$cid\E/i } (@{ $hash->{IODev}{helper}{playlists} }); + return "usage: $cmd name" if ( scalar @cids <= 0); + $cid = $cids[0]; + } + + if ( $cmd eq 'playPlaylist' ) { + + $heosCmd = $cmd; + $action = "sid=1025&cid=$cid&aid=4"; + + } elsif ( $cmd eq 'playPlaylistItem' ) { + return "usage: playPlaylistItem name,nr" if ( scalar @path < 2); + + $heosCmd = 'playPlaylist'; + $action = "sid=1025&cid=$cid&mid=$mid&aid=4"; + } elsif ( $cmd eq 'deletePlaylist' ) { $heosCmd = $cmd; - $action = "cid=$cids[0]"; + $action = "cid=$cid"; $string = "sid=1025"; } } else { - - IOWrite($hash,'browseSource','sid=1025'); + my @playlists = map { $_->{name} } (@{ $hash->{IODev}{helper}{playlists}}); - return "usage: $cmd ".join(",",@playlists); + return "usage: $cmd name|id".join(",",@playlists); } + } elsif( $cmd eq 'aux' ) { + return "usage: $cmd" if( @args != 0 ); + + my $auxname = @{ $hash->{helper}{aux} }[0]->{mid}; + $heosCmd = 'playInput'; + $action = "input=$auxname"; + + Log3 $name, 4, "HEOSPlayer ($name) - set aux to $auxname"; + readingsSingleUpdate($hash, "input", $args[0], 1); + } elsif( $cmd eq 'input' ) { - - my @sids; - my $search = $args[0]; + return 'usage: '.$cmd.' sid[,cid][,mid]' if( @args != 1 ); - $search =~ s/\xC2\xA0/ /g; - #$search =~ s/\s+/\ /g; - - if ( $search =~ /Warteschlange/ ) { - - push(@sids, "9999"); - - } else { - - @sids = map { $_->{sid} } grep { $_->{name} =~ /\Q$search\E/i } (@{ $hash->{IODev}{helper}{sources} }); - } - - if ( scalar @args == 1 && scalar @sids > 0 ) { - - readingsSingleUpdate($hash, "input", $args[0], 1); - #sid des Input für Container merken - readingsSingleUpdate($hash, ".input", $sids[0], 1); - #alten Container löschen bei Inputwechsel - readingsSingleUpdate($hash, ".cid", 0, 1); - - if ( $sids[0] eq "9999" ) { - - $heosCmd = 'getQueue'; + my $param = shift( @args ); + my @path = split( ",", $param); + my $sid = $path[0] if ( scalar @path > 0); + my $cid = $path[1] if ( scalar @path > 1); + my $mid = $path[2] if ( scalar @path > 2); + + if ( $sid =~ /^-*[0-9]+$/ ) { + if ( $sid eq "1024" ) { + return 'usage: '.$cmd.' sid,cid[,mid]' unless( defined($cid) && defined($mid) ); + + #Server abspielen + $heosCmd = 'playPlaylist'; + $action = "sid=$sid&cid=$cid&aid=4"; + $action = "sid=$sid&cid=$cid&mid=$mid&aid=4" if ( defined($mid) ); + + } elsif ( $sid eq "1025" ) { + return 'usage: '.$cmd.' sid,cid[,mid]' unless( defined($cid) ); + + #Playlist abspielen + $heosCmd = 'playPlaylist'; + $action = "sid=$sid&cid=$cid&aid=4"; + $action = "sid=$sid&cid=$cid&mid=$mid&aid=4" if ( defined($mid) ); + + } elsif ( $sid eq "1026" ) { + return 'usage: '.$cmd.' sid,cid,mid' unless( defined($cid) ); + + #Verlauf abspielen + if ( $cid eq "TRACKS" ) { + + $heosCmd = 'playPlaylist'; + $action = "sid=$sid&cid=$cid&aid=4"; + $action = "sid=$sid&cid=$cid&mid=$mid&aid=4" if ( defined($mid) ); + + } elsif ( $cid eq "STATIONS" ) { + + $heosCmd = 'playStream'; + $action = "sid=$sid&cid=$cid&mid=$mid"; + } + + } elsif ( $sid eq "1027" ) { + return 'usage: '.$cmd.' sid,spid,mid' unless( defined($cid) ); + + #Eingang abspielen + $heosCmd = 'playInput'; + $action = "input=$mid"; + $action = "spid=$cid&".$action if ( $pid ne $cid ); + + } elsif ( $sid eq "1028" ) { + return 'usage: '.$cmd.' sid,nr' unless( defined($cid) ); + + #Favoriten abspielen + $heosCmd = 'playPresetStation'; + $action = "preset=$cid"; + + } elsif ( $sid eq "1029" ) { + return 'usage: '.$cmd.' sid,qid' unless( defined($cid) ); + + #Warteschlange abspielen + $heosCmd = 'playQueue'; + $action = "qid=$sid"; } else { - - $heosCmd = 'browseSource'; - $action = "sid=$sids[0]"; - - } - - Log3 $name, 4, "HEOSPlayer ($name) - set input with sid $sids[0] and name $args[0]"; - - } else { - - my @inputs = map { $_->{name} } (@{ $hash->{IODev}{helper}{sources}}); - push(@inputs, "Warteschlange"); - return "usage: input ".join(",",@inputs); - } - - } elsif( $cmd eq 'media' ) { - - my @ids; - my $search = $args[0]; - my $sid = ReadingsVal($name,".input", "9999"); - - return "usage: set input first" unless( defined($sid) ); - - if ( scalar @args == 1 ) { - - $search =~ s/\xC2\xA0/ /g; - - if ( $sid eq "9999" ) { - - @ids = grep { $_->{song} =~ /\Q$search\E/i } (@{ $hash->{helper}{queue} }); - - } else { - - @ids = grep { $_->{name} =~ /\Q$search\E/i } (@{ $hash->{IODev}{helper}{media} }); - } - - if ( scalar @ids > 0 ) { - if ( exists $ids[0]{cid} ) { - #hier Container verarbeiten - if ( $ids[0]{playable} eq "yes" ) { + if ( $sid > 0 && $sid < 30 ) { + return 'usage: '.$cmd.' sid,cid,mid' unless( defined($cid) && defined($mid) ); - #alles abspielen - $heosCmd = 'playPlaylist'; - $action = "sid=$sid&cid=$ids[0]{cid}&aid=4"; - #Container merken - readingsSingleUpdate($hash, ".cid", 0, 1); - - } else { + #Radio abspielen + $heosCmd = 'playStream'; + $action = "sid=$sid&cid=$cid&mid=$mid"; - #mehr einlesen - readingsSingleUpdate($hash, ".cid", $ids[0]{cid}, 1); - $heosCmd = 'browseSource'; - $action = "sid=$sid&cid=$ids[0]{cid}"; - } + } else { + return 'usage: '.$cmd.' sid,cid[,mid]' unless( defined($cid) ); - } elsif ( exists $ids[0]{qid} ) { - - $heosCmd = 'playQueue'; - $action = "qid=$ids[0]{qid}"; - - } elsif ( exists $ids[0]{mid} ) { - #hier Medien verarbeiten - if ( $ids[0]{mid} =~ /inputs\// ) { - - #Input abspielen - $heosCmd = 'playInput'; - $action = "input=$ids[0]{mid}"; - - } else { - - #aktuellen Container holen - my $cid = ReadingsVal($name,".cid", undef); - - if ( defined $cid ) { - if ( $ids[0]{type} eq "station" ) { - - #Radio abspielen - $heosCmd = 'playStream'; - $action = "sid=$sid&cid=$cid&mid=$ids[0]{mid}"; - - } else { - - #Song abspielen - $heosCmd = 'playPlaylist'; - $action = "sid=$sid&cid=$cid&mid=$ids[0]{mid}&aid=4"; - } - } - } + #Server abspielen + $heosCmd = 'playPlaylist'; + $action = "sid=$sid&cid=$cid&aid=4"; + $action = "sid=$sid&cid=$cid&mid=$mid&aid=4" if ( defined($mid) ); } } - } else { - my @media; - - if ( $sid eq "9999" ) { - - @media = map { $_->{song} } (@{ $hash->{helper}{queue}}); - - } else { - - @media = map { $_->{name} } (@{ $hash->{IODev}{helper}{media}}); - } - - return "usage: media ".join(",",@media); + return 'usage: '.$cmd.' sid,cid[,mid]'; } - - } elsif ( $cmd eq 'clearQueue' ) { - #löscht die Warteschlange - return "usage: clearQueue" if( @args != 0 ); - - $heosCmd = 'clearQueue'; - delete $hash->{helper}{queue}; - - } elsif ( $cmd eq 'saveQueue' ) { - #speichert die aktuelle Warteschlange als Playlist ab - return "usage: saveQueue" if( @args != 1 ); - - $heosCmd = 'saveQueue'; - $action = "name=$args[0]"; - - } elsif ( $cmd eq 'history' ) { - return "usage: history track,channel" if( @args != 1 ); - - $heosCmd = "browseSource"; - $action = "sid=1026&cid=TRACKS" if ( $args[0] eq "track" ); - $action = "sid=1026&cid=STATIONS" if ( $args[0] eq "channel" ); - } else { - - my @playlists; - my @inputs; - my @media; - my @queue; - my $sid = ReadingsVal($name,".input", "9999"); - my $list = "getPlayerInfo:noArg getPlayState:noArg getNowPlayingMedia:noArg getPlayMode:noArg play:noArg stop:noArg pause:noArg mute:on,off volume:slider,0,5,100 volumeUp:slider,0,1,10 volumeDown:slider,0,1,10 repeat:one,all,off shuffle:on,off channelUp:noArg channelDown:noArg next:noArg prev:noArg history:track,channel "; + + my $list = "getPlayerInfo:noArg getPlayState:noArg getNowPlayingMedia:noArg getPlayMode:noArg play:noArg stop:noArg pause:noArg mute:on,off volume:slider,0,5,100 volumeUp:slider,0,1,10 volumeDown:slider,0,1,10 repeat:one,all,off shuffle:on,off next:noArg prev:noArg input"; + + my @players = devspec2array("TYPE=HEOSPlayer:FILTER=NAME!=$name"); + $list .= " groupWithMember:multiple-strict," . join( ",", @players ) if ( scalar @players > 0 ); + $list .= " groupClear:noArg" if ( defined($defs{"HEOSGroup".abs($pid)}) && $defs{"HEOSGroup".abs($pid)}->{STATE} eq "on" ); - #$list .= "groupWithMember:" . join( ",", devspec2array("TYPE=HEOSPlayer:FILTER=NAME!=$name") ); - $list .= "groupWithMember:multiple-strict," . join( ",", devspec2array("TYPE=HEOSPlayer:FILTER=NAME!=$name") ); #Parameterlisten für FHEMWeb zusammen bauen - $list .= " channel:slider,1,1,".scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); - #$list .= " playQueue:slider,1,1,".scalar(@{$hash->{helper}{queue}}) if ( defined $hash->{helper}{queue} ); + my $favoritcount = scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); + if ( defined $favoritcount && $favoritcount > 0) { + + $list .= " channel:slider,1,1,".scalar(@{$hash->{IODev}{helper}{favorites}}); + $list .= " channelUp:noArg channelDown:noArg" if ( $favoritcount > 1) + } + + if ( defined($hash->{helper}{queue}) && ref($hash->{helper}{queue}) eq "ARRAY" && scalar(@{$hash->{helper}{queue}}) > 0 ) { + $list .= " playQueue:slider,1,1,".scalar(@{$hash->{helper}{queue}}) if ( defined $hash->{helper}{queue} ); + $list .= " clearQueue:noArg saveQueue"; + } + if ( defined $hash->{IODev}{helper}{playlists} ) { - @playlists = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{playlists}}); + my @playlists = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{playlists}}); + #$list .= " playPlaylistItem:slider,1,1,".scalar @playlists; $list .= " playPlaylist:".join(",",@playlists) if( scalar @playlists > 0 ); $list .= " deletePlaylist:".join(",",@playlists) if( scalar @playlists > 0 ); - #$list .= " renamePlaylist:".join(",",@playlists) if( scalar @playlists > 0 ); } - - if ( defined $hash->{IODev}{helper}{sources}) { - @inputs = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{sources}}); - push(@inputs, "Warteschlange"); - $list .= " input:".join(",",@inputs) if( scalar @inputs > 0 ); - } - - if ( $sid eq "9999" ) { - @media = map { my %n; $n{name} = $_->{song}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{helper}{queue}}); - $list .= " clearQueue:noArg saveQueue"; - - } else { - - @media = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{media}}) if ( defined $hash->{IODev}{helper}{media}); - } - - $list .= " media:".join(",",@media) if( scalar @media > 0 ); + + $list .= " aux:noArg" if ( exists $hash->{helper}{aux} ); return "Unknown argument $cmd, choose one of $list"; } - $string .= "&$action" if( defined($action)); - IOWrite($hash,"$heosCmd","$string"); + IOWrite($hash,"$heosCmd","$string",undef); Log3 $name, 4, "HEOSPlayer ($name) - IOWrite: $heosCmd $string IODevHash=$hash->{IODev}"; return undef; } @@ -655,7 +727,7 @@ sub HEOSPlayer_Parse($$) { if( my $hash = $modules{HEOSPlayer}{defptr}{$code} ) { - IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}"); + IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}",undef); Log3 $hash->{NAME}, 4, "HEOSPlayer ($hash->{NAME}) - find logical device: $hash->{NAME}"; Log3 $hash->{NAME}, 4, "HEOSPlayer ($hash->{NAME}) - find PID in root from decode_json"; return $hash->{NAME}; @@ -671,46 +743,19 @@ sub HEOSPlayer_Parse($$) { my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); $pid = $message{pid} if( defined($message{pid}) ); - $pid = $decode_json->{payload}{pid} if( defined($decode_json->{payload}{pid}) ); - + $pid = $decode_json->{payload}{pid} if( ref($decode_json->{payload}) ne "ARRAY" && defined($decode_json->{payload}{pid}) ); + Log3 $name, 4, "HEOSPlayer ($name) PID: $pid"; $code = abs($pid); $code = $io_hash->{NAME} ."-". $code if( defined($io_hash->{NAME}) ); - if( my $hash = $modules{HEOSPlayer}{defptr}{$code} ) { - + if( my $hash = $modules{HEOSPlayer}{defptr}{$code} ) { my $name = $hash->{NAME}; - - if ( $decode_json->{heos}{command} =~ /get_queue/ ) { - - Log3 $name, 3, "HEOSPlayer ($name) - call getQueue for $message{pid}"; - - if ( defined $message{range} ) { - - $message{range} =~ s/(\d+)\,\d+/$1/; - - } else { - - $message{range} = 0; - $hash->{helper}{queue} = []; - } - - my $start = $message{range} + $message{returned}; - push( @{$hash->{helper}{queue}}, (@{$decode_json->{payload}}) ); - - if ( $start < $message{count} ) { - - IOWrite($hash,'getQueue',"pid=$message{pid}&range=$start,".($start + 100)); - Log3 $name, 3, "HEOSMaster ($name) - call getQueue with pid $message{pid} next Range from $message{returned}"; - } - - } else { - - HEOSPlayer_WriteReadings($hash,$decode_json); - Log3 $name, 4, "HEOSPlayer ($name) - find logical device: $hash->{NAME}"; - } - + + HEOSPlayer_WriteReadings($hash,$decode_json); + Log3 $name, 4, "HEOSPlayer ($name) - find logical device: $hash->{NAME}"; + return $hash->{NAME}; } else { @@ -806,8 +851,8 @@ sub HEOSPlayer_PreProcessingReadings($$) { $buffer{'volume'} = $message{level}; $buffer{'mute'} = $message{mute} if( $decode_json->{heos}{command} =~ /volume_changed/ ); if (defined($buffer{'mute'}) && AttrVal($name, 'mute2play', 0) == 1) { - IOWrite($hash,'setPlayState',"pid=$hash->{PID}&state=play") if $buffer{'mute'} eq "off"; - IOWrite($hash,'setPlayState',"pid=$hash->{PID}&state=stop") if $buffer{'mute'} eq "on"; + IOWrite($hash,'setPlayState',"pid=$hash->{PID}&state=play",undef) if $buffer{'mute'} eq "off"; + IOWrite($hash,'setPlayState',"pid=$hash->{PID}&state=stop",undef) if $buffer{'mute'} eq "on"; } } elsif ( $decode_json->{heos}{command} =~ /play_mode/ or $decode_json->{heos}{command} =~ /repeat_mode_changed/ or $decode_json->{heos}{command} =~ /shuffle_mode_changed/ ) { @@ -823,10 +868,10 @@ sub HEOSPlayer_PreProcessingReadings($$) { } elsif ( $decode_json->{heos}{command} =~ /volume_up/ or $decode_json->{heos}{command} =~ /volume_down/ ) { $buffer{'volumeUp'} = $message{step} if( $decode_json->{heos}{command} =~ /volume_up/ ); - $buffer{'volumeDown'} = $message{step} if( $decode_json->{heos}{command} =~ /volume_down/ ); + $buffer{'volumeDown'} = $message{step} if( $decode_json->{heos}{command} =~ /volume_down/ ); } elsif ( $decode_json->{heos}{command} =~ /player_now_playing_changed/ or $decode_json->{heos}{command} =~ /favorites_changed/ ) { - IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}"); + IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}",undef); } elsif ( $decode_json->{heos}{command} =~ /play_preset/ ) { @@ -856,7 +901,7 @@ sub HEOSPlayer_GetPlayerInfo($) { RemoveInternalTimer($hash,'HEOSPlayer_GetPlayerInfo'); - IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}"); + IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}",undef); } sub HEOSPlayer_GetPlayState($) { @@ -865,7 +910,7 @@ sub HEOSPlayer_GetPlayState($) { RemoveInternalTimer($hash,'HEOSPlayer_GetPlayState'); - IOWrite($hash,'getPlayState',"pid=$hash->{PID}"); + IOWrite($hash,'getPlayState',"pid=$hash->{PID}",undef); } sub HEOSPlayer_GetPlayMode($) { @@ -874,7 +919,7 @@ sub HEOSPlayer_GetPlayMode($) { RemoveInternalTimer($hash,'HEOSPlayer_GetPlayMode'); - IOWrite($hash,'getPlayMode',"pid=$hash->{PID}"); + IOWrite($hash,'getPlayMode',"pid=$hash->{PID}",undef); } sub HEOSPlayer_GetNowPlayingMedia($) { @@ -883,7 +928,7 @@ sub HEOSPlayer_GetNowPlayingMedia($) { RemoveInternalTimer($hash,'HEOSPlayer_GetNowPlayingMedia'); - IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}"); + IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}",undef); } sub HEOSPlayer_GetVolume($) { @@ -892,7 +937,7 @@ sub HEOSPlayer_GetVolume($) { RemoveInternalTimer($hash,'HEOSPlayer_GetVolume'); - IOWrite($hash,'getVolume',"pid=$hash->{PID}"); + IOWrite($hash,'getVolume',"pid=$hash->{PID}",undef); } sub HEOSPlayer_GetMute($) { @@ -901,7 +946,7 @@ sub HEOSPlayer_GetMute($) { RemoveInternalTimer($hash,'HEOSPlayer_GetMute'); - IOWrite($hash,'getMute',"pid=$hash->{PID}"); + IOWrite($hash,'getMute',"pid=$hash->{PID}",undef); } sub HEOSPlayer_GetQueue($) { @@ -910,15 +955,32 @@ sub HEOSPlayer_GetQueue($) { RemoveInternalTimer($hash,'HEOSPlayer_GetQueue'); - IOWrite($hash,'getQueue',"pid=$hash->{PID}"); + IOWrite($hash,'getQueue',"pid=$hash->{PID}",undef); +} + +sub HEOSPlayer_Hexdump { + + my $str = ref $_[0] ? ${$_[0]} : $_[0]; + + return "[ZERO-LENGTH STRING]\n" unless length $str; + + # split input up into 16-byte chunks: + my @chunks = $str =~ /([\0-\377]{1,16})/g; + # format and print: + my @print; + for (@chunks) { + my $hex = unpack "H*", $_; + tr/ -~/./c; # mask non-print chars + $hex =~ s/(..)(?!$)/$1 /g; # insert spaces in hex + # make sure our hex output has the correct length + $hex .= ' ' x ( length($hex) < 48 ? 48 - length($hex) : 0 ); + push @print, "$hex $_\n"; + } + wantarray ? @print : join '', @print; } - - - - 1;