diff --git a/21_HEOSGroup.pm b/21_HEOSGroup.pm index 3fba373..c80da45 100644 --- a/21_HEOSGroup.pm +++ b/21_HEOSGroup.pm @@ -36,7 +36,7 @@ use warnings; use JSON qw(decode_json); use Encode qw(encode_utf8); -my $version = "0.1.58"; +my $version = "0.1.60"; # Declare functions sub HEOSGroup_Initialize($); @@ -53,10 +53,12 @@ sub HEOSGroup_GetGroupVolume($); sub HEOSGroup_GetGroupMute($); sub HEOSGroup_Initialize($) { + my ($hash) = @_; $hash->{Match} = '.*{"command":."group.*|.*{"command":."event\/group.*'; + # Provider $hash->{SetFn} = "HEOSGroup_Set"; $hash->{DefFn} = "HEOSGroup_Define"; @@ -69,26 +71,32 @@ sub HEOSGroup_Initialize($) { $readingFnAttributes; foreach my $d(sort keys %{$modules{HEOSGroup}{defptr}}) { + my $hash = $modules{HEOSGroup}{defptr}{$d}; $hash->{VERSION} = $version; } } sub HEOSGroup_Define($$) { + my ( $hash, $def ) = @_; my @a = split( "[ \t]+", $def ); splice( @a, 1, 1 ); my $iodev; my $i = 0; + foreach my $param ( @a ) { if( $param =~ m/IODev=([^\s]*)/ ) { + $iodev = $1; splice( @a, $i, 3 ); last; } + $i++; } + return "too few parameters: define HEOSGroup " if( @a < 2 ); my ($name,$gid) = @a; @@ -97,73 +105,96 @@ sub HEOSGroup_Define($$) { $hash->{VERSION} = $version; $hash->{NOTIFYDEV} = "HEOSPlayer".abs($gid); AssignIoPort($hash,$iodev) if( !$hash->{IODev} ); + if(defined($hash->{IODev}->{NAME})) { + Log3 $name, 3, "HEOSGroup ($name) - I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "HEOSGroup ($name) - no I/O device"; } + $iodev = $hash->{IODev}->{NAME}; my $code = abs($gid); $code = $iodev."-".$code if( defined($iodev) ); my $d = $modules{HEOSGroup}{defptr}{$code}; + return "HEOSGroup device $hash->{GID} on HEOSMaster $iodev already defined as $d->{NAME}." if( defined($d) && $d->{IODev} == $hash->{IODev} && $d->{NAME} ne $name ); Log3 $name, 3, "HEOSGroup ($name) - defined with Code: $code"; + $attr{$name}{room} = "HEOS" if( !defined( $attr{$name}{room} ) ); $attr{$name}{devStateIcon} = "on:10px-kreis-gruen off:10px-kreis-rot" if( !defined( $attr{$name}{devStateIcon} ) ); + if( $init_done ) { + InternalTimer( gettimeofday()+int(rand(2)), "HEOSGroup_GetGroupInfo", $hash, 0 ); InternalTimer( gettimeofday()+int(rand(4)), "HEOSGroup_GetGroupVolume", $hash, 0 ); InternalTimer( gettimeofday()+int(rand(6)), "HEOSGroup_GetGroupMute", $hash, 0 ); + } else { + InternalTimer( gettimeofday()+15+int(rand(2)), "HEOSGroup_GetGroupInfo", $hash, 0 ); InternalTimer( gettimeofday()+15+int(rand(4)), "HEOSGroup_GetGroupVolume", $hash, 0 ); InternalTimer( gettimeofday()+15+int(rand(6)), "HEOSGroup_GetGroupMute", $hash, 0 ); } + readingsBeginUpdate($hash); readingsBulkUpdate($hash, 'state','Initialized'); readingsBulkUpdate($hash, 'volumeUp', 5); readingsBulkUpdate($hash, 'volumeDown', 5); readingsEndUpdate($hash, 1); + $modules{HEOSGroup}{defptr}{$code} = $hash; return undef; } sub HEOSGroup_Undef($$) { + my ( $hash, $arg ) = @_; my $name = $hash->{NAME}; + RemoveInternalTimer($hash); my $code = abs($hash->{GID}); $code = $hash->{IODev}->{NAME} ."-". $code if( defined($hash->{IODev}->{NAME}) ); delete($modules{HEOSGroup}{defptr}{$code}); + Log3 $name, 3, "HEOSGroup ($name) - device $name deleted with Code: $code"; return undef; } sub HEOSGroup_Attr(@) { + my ( $cmd, $name, $attrName, $attrVal ) = @_; my $hash = $defs{$name}; my $token = $hash->{IODev}->{TOKEN}; + if( $attrName eq "disable" ) { if( $cmd eq "set" and $attrVal eq "1" ) { + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); Log3 $name, 3, "HEOSGroup ($name) - disabled"; - } - elsif( $cmd eq "del" ) { + + } elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); Log3 $name, 3, "HEOSGroup ($name) - enabled"; } } + if( $attrName eq "disabledForIntervals" ) { if( $cmd eq "set" ) { + Log3 $name, 3, "HEOSGroup ($name) - enable disabledForIntervals"; readingsSingleUpdate ( $hash, "state", "Unknown", 1 ); - } - elsif( $cmd eq "del" ) { + + } elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); Log3 $name, 3, "HEOSGroup ($name) - delete disabledForIntervals"; } @@ -171,27 +202,33 @@ sub HEOSGroup_Attr(@) { } sub HEOSGroup_Notify($$) { + my ($hash,$dev) = @_; my $name = $hash->{NAME}; - + + return undef if(IsDisabled($name)); + my $events = deviceEvents($dev,1); - - #print "notify ####################################################\n".Dumper($events); - + + #print "notify ####################################################\n".Dumper($events); + return if( !$events ); readingsBeginUpdate($hash); - #my %playerEevents = map { my ( $key, $value ) = split ":"; $value =~ s/^\s+//; ( $key, $value ) } @$events; - my %playerEevents = map { my ( $key, $value ) = split /:\s/; $value =~ s/^\s+//; ( $key, $value ) } @$events; + + my %playerEevents = map { my ( $key, $value ) = split /:\s/; $value =~ s/^\s+//; ( $key, $value ) } @$events; foreach my $key ( keys %playerEevents ) { + #### playing Infos readingsBulkUpdate( $hash, $key, $playerEevents{$key} ) if( grep { $_ =~ /$key/ } ("channel", "currentAlbum", "currentArtist", "currentImageUrl", "currentMedia", "currentMid", "currentQid", "currentSid", "currentStation", "currentTitle", "error", "playStatus", "repeat", "shuffle" ) ); } + readingsEndUpdate( $hash, 1 ); } sub HEOSGroup_Set($$@) { + my ($hash, $name, @aa) = @_; my ($cmd, @args) = @aa; my $gid = $hash->{GID}; @@ -202,110 +239,156 @@ sub HEOSGroup_Set($$@) { my $favoritcount = 1; my $string = "gid=$gid"; + #senden von Befehlen unterdrücken solange state nicht on ist return undef unless ( ReadingsVal($name, "state", "off") eq "on" ); + if( $cmd eq 'getGroupInfo' ) { return "usage: getGroupInfo" if( @args != 0 ); + $heosCmd = $cmd; + } elsif( $cmd eq 'mute' ) { return "usage: mute on/off" if( @args != 1 ); + $heosCmd = 'setGroupMute'; $action = "state=$args[0]"; + } elsif( $cmd eq 'volume' ) { return "usage: volume 0-100" if( @args != 1 ); + $heosCmd = 'setGroupVolume'; $action = "level=$args[0]"; + } elsif( $cmd eq 'volumeUp' ) { return "usage: volumeUp 0-10" if( @args != 1 ); + $heosCmd = 'GroupVolumeUp'; $action = "step=$args[0]"; + } elsif( $cmd eq 'volumeDown' ) { return "usage: volumeDown 0-10" if( @args != 1 ); + $heosCmd = 'groupVolumeDown'; $action = "step=$args[0]"; + } elsif( $cmd eq 'clearGroup' ) { return "usage: clearGroup" if( @args != 0 ); + $heosCmd = 'createGroup'; $string = "pid=$gid"; + } elsif( grep { $_ =~ /\Q$cmd\E/ } ("play", "stop", "pause", "next", "prev", "channel", "channelUp", "channelDown", "playlist" ) ) { + #ab hier Playerbefehle emuliert $string = "pid=$gid"; + if( $cmd eq 'repeat' ) { - return "usage: repeat one,all,off" if( @args != 1 ); - $heosCmd = 'setPlayMode'; - $rvalue = 'on_'.$args[0]; - $rvalue = 'off' if($rvalue eq 'on_off'); - $action = "repeat=$rvalue&shuffle=".ReadingsVal($name,'shuffle','off'); - } elsif( $cmd eq 'shuffle' ) { - return "usage: shuffle on,off" if( @args != 1 ); - $heosCmd = 'setPlayMode'; - $rvalue = 'on_'.ReadingsVal($name,'repeat','off'); - $rvalue = 'off' if($rvalue eq 'on_off'); - $action = "repeat=$rvalue&shuffle=$args[0]"; - } elsif( $cmd eq 'play' ) { + return "usage: repeat one,all,off" if( @args != 1 ); + + $heosCmd = 'setPlayMode'; + $rvalue = 'on_'.$args[0]; + $rvalue = 'off' if($rvalue eq 'on_off'); + $action = "repeat=$rvalue&shuffle=".ReadingsVal($name,'shuffle','off'); + + } elsif( $cmd eq 'shuffle' ) { + return "usage: shuffle on,off" if( @args != 1 ); + + $heosCmd = 'setPlayMode'; + $rvalue = 'on_'.ReadingsVal($name,'repeat','off'); + $rvalue = 'off' if($rvalue eq 'on_off'); + $action = "repeat=$rvalue&shuffle=$args[0]"; + + } elsif( $cmd eq 'play' ) { return "usage: play" if( @args != 0 ); + $heosCmd = 'setPlayState'; $action = "state=$cmd"; + } elsif( $cmd eq 'stop' ) { return "usage: stop" if( @args != 0 ); + $heosCmd = 'setPlayState'; $action = "state=$cmd"; + } elsif( $cmd eq 'pause' ) { return "usage: pause" if( @args != 0 ); + $heosCmd = 'setPlayState'; $action = "state=$cmd"; } elsif( $cmd eq 'next' ) { return "usage: next" if( @args != 0 ); + $heosCmd = 'playNext'; + } elsif( $cmd eq 'prev' ) { return "usage: prev" if( @args != 0 ); + $heosCmd = 'playPrev'; + } elsif ( $cmd eq 'channel' ) { + $favoritcount = scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); return "usage: channel 1-$favoritcount" if( @args != 1 ); + $heosCmd = 'playPresetStation'; $action = "preset=$args[0]"; + } elsif( $cmd eq 'channelUp' ) { return "usage: channelUp" if( @args != 0 ); + $favoritcount = scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); $heosCmd = 'playPresetStation'; $favorit = ReadingsVal($name,"channel", 0) + 1; $favorit = $favoritcount if ( $favorit > $favoritcount); $action = "preset=".$favorit; + } elsif( $cmd eq 'channelDown' ) { return "usage: channelDown" if( @args != 0 ); + $heosCmd = 'playPresetStation'; $favorit = ReadingsVal($name,"channel", 0) - 1; $favorit = 1 if ($favorit <= 0); $action = "preset=".$favorit; + } elsif ( $cmd =~ /Playlist/ ) { - my @cids = map { $_->{cid} } grep { $_->{name} =~ /$args[0]/i } (@{ $hash->{IODev}{helper}{playlists} }); + + my @cids = map { $_->{cid} } grep { $_->{name} =~ /$args[0]/i } (@{ $hash->{IODev}{helper}{playlists} }); - if ( scalar @args == 1 && scalar @cids > 0 ) { - if ( $cmd eq 'playPlaylist' ) { - $heosCmd = $cmd; - $action = "sid=1025&cid=$cids[0]&aid=4"; - } elsif ( $cmd eq 'deletePlaylist' ) { - $heosCmd = $cmd; - $action = "cid=$cids[0]"; - $string = "sid=1025"; - } - } else { - IOWrite($hash,'browseSource','sid=1025'); - my @playlists = map { $_->{name} } (@{ $hash->{IODev}{helper}{playlists}}); - return "usage: $cmd ".join(",",@playlists); - } - } + if ( scalar @args == 1 && scalar @cids > 0 ) { + if ( $cmd eq 'playPlaylist' ) { + + $heosCmd = $cmd; + $action = "sid=1025&cid=$cids[0]&aid=4"; + + } elsif ( $cmd eq 'deletePlaylist' ) { + + $heosCmd = $cmd; + $action = "cid=$cids[0]"; + $string = "sid=1025"; + } + } else { + + IOWrite($hash,'browseSource','sid=1025'); + my @playlists = map { $_->{name} } (@{ $hash->{IODev}{helper}{playlists}}); + return "usage: $cmd ".join(",",@playlists); + } + } } else { + my $list = "getGroupInfo:noArg mute:on,off volume:slider,0,5,100 volumeUp:slider,0,1,10 volumeDown:slider,0,1,10 clearGroup:noArg repeat:one,all,off shuffle:on,off play:noArg stop:noArg pause:noArg next:noArg prev:noArg channelUp:noArg channelDown:noArg "; $list .= " channel:slider,1,1,".scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); + if ( defined $hash->{IODev}{helper}{playlists} ) { + my @playlists = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{playlists}}); $list .= " playlist:".join(",",@playlists) if( scalar @playlists > 0 ); } + return "Unknown argument $cmd, choose one of $list"; } + $string .= "&$action" if( defined($action)); IOWrite($hash,"$heosCmd","$string"); Log3 $name, 4, "HEOSGroup ($name) - IOWrite: $heosCmd $string IODevHash=$hash->{IODev}"; @@ -313,42 +396,58 @@ sub HEOSGroup_Set($$@) { } sub HEOSGroup_Parse($$) { + my ($io_hash,$json) = @_; my $name = $io_hash->{NAME}; my $gid; my $decode_json; - my $code; - + my $code; + + $decode_json = decode_json(encode_utf8($json)); Log3 $name, 4, "HEOSGroup ($name) - ParseFn wurde aufgerufen"; if( defined($decode_json->{gid}) ) { + $gid = $decode_json->{gid}; $code = abs($gid); $code = $io_hash->{NAME} ."-". $code if( defined($io_hash->{NAME}) ); + + if( my $hash = $modules{HEOSGroup}{defptr}{$code} ) { + IOWrite($hash,'getGroupInfo',"gid=$hash->{GID}"); Log3 $hash->{NAME}, 4, "HEOSGroup ($hash->{NAME}) - find logical device: $hash->{NAME}"; Log3 $hash->{NAME}, 4, "HEOSGroup ($hash->{NAME}) - find GID in root from decode_json"; return $hash->{NAME}; + } else { + my $devname = "HEOSGroup".abs($gid); return "UNDEFINED $devname HEOSGroup $gid IODev=$name"; } - } else { - my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); - $gid = $message{pid} if( defined($message{pid}) ); - $gid = $message{gid} if( defined($message{gid}) ); - $gid = $decode_json->{payload}{gid} if( defined($decode_json->{payload}{gid}) ); - Log3 $name, 4, "HEOSGroup ($name) - GID: $gid"; + } else { + + my %message = map { my ( $key, $value ) = split "="; $key => $value } split('&', $decode_json->{heos}{message}); + + $gid = $message{pid} if( defined($message{pid}) ); + $gid = $message{gid} if( defined($message{gid}) ); + $gid = $decode_json->{payload}{gid} if( defined($decode_json->{payload}{gid}) ); + + Log3 $name, 4, "HEOSGroup ($name) - GID: $gid"; + $code = abs($gid); $code = $io_hash->{NAME} ."-". $code if( defined($io_hash->{NAME}) ); + if( my $hash = $modules{HEOSGroup}{defptr}{$code} ) { + HEOSGroup_WriteReadings($hash,$decode_json); Log3 $hash->{NAME}, 4, "HEOSGroup ($hash->{NAME}) - find logical device: $hash->{NAME}"; return $hash->{NAME}; + } else { + my $devname = "HEOSGroup".abs($gid); return "UNDEFINED $devname HEOSGroup $gid IODev=$name"; } @@ -356,9 +455,11 @@ sub HEOSGroup_Parse($$) { } sub HEOSGroup_WriteReadings($$) { + my ($hash,$decode_json) = @_; my $name = $hash->{NAME}; + Log3 $name, 3, "HEOSGroup ($name) - processing data to write readings"; ############################ #### Aufbereiten der Daten soweit nötig (bei Events zum Beispiel) @@ -370,31 +471,44 @@ sub HEOSGroup_WriteReadings($$) { readingsBeginUpdate($hash); ### Event Readings if( ref($readingsHash) eq "HASH" ) { + Log3 $name, 4, "HEOSGroup ($name) - response json Hash back from HEOSGroup_PreProcessingReadings"; my $t; my $v; + while( ( $t, $v ) = each (%{$readingsHash}) ) { + readingsBulkUpdate( $hash, $t, $v ) if( defined( $v ) ); } } + readingsBulkUpdate( $hash, 'state', 'on' ); ### GroupInfos readingsBulkUpdate( $hash, 'name', $decode_json->{payload}{name} ); readingsBulkUpdate( $hash, 'gid', $decode_json->{payload}{gid} ); + 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" ); push( @members, $player->{name}) if ( $player->{role} eq "member" ); } + if ( scalar @members > 1 ) { + readingsBulkUpdate( $hash, 'member', join(",",@members) ); + } else { + readingsBulkUpdate( $hash, 'member', $members[0] ); } } + readingsEndUpdate( $hash, 1 ); + Log3 $name, 5, "HEOSGroup ($name) - readings set for $name"; return undef; } @@ -403,52 +517,68 @@ sub HEOSGroup_WriteReadings($$) { ### my little Helpers sub HEOSGroup_PreProcessingReadings($$) { + my ($hash,$decode_json) = @_; my $name = $hash->{NAME}; my $reading; my %buffer; 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/ ); + } elsif ( $decode_json->{heos}{command} =~ /get_mute/ ) { + my @value = split('&', $decode_json->{heos}{message}); - + $buffer{'mute'} = substr($value[1],6); + } else { + Log3 $name, 3, "HEOSGroup ($name) - no match found"; return undef; } + Log3 $name, 4, "HEOSGroup ($name) - Match found for decode_json"; return \%buffer; } sub HEOSGroup_GetGroupInfo($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSGroup_GetGroupInfo'); IOWrite($hash,'getGroupInfo',"gid=$hash->{GID}"); } sub HEOSGroup_GetGroupVolume($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSGroup_GetGroupVolume'); IOWrite($hash,'getGroupVolume',"gid=$hash->{GID}"); } sub HEOSGroup_GetGroupMute($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSGroup_GetGroupMute'); IOWrite($hash,'getGroupMute',"gid=$hash->{GID}"); } diff --git a/21_HEOSMaster.pm b/21_HEOSMaster.pm index 587b1ac..65b322b 100644 --- a/21_HEOSMaster.pm +++ b/21_HEOSMaster.pm @@ -47,7 +47,7 @@ use Encode qw(encode_utf8); use Net::Telnet; use Data::Dumper; -my $version = "0.1.58"; +my $version = "0.1.60"; my %heosCmds = ( 'enableChangeEvents' => 'system/register_for_change_events?enable=', @@ -126,8 +126,10 @@ sub HEOSMaster_GetPlaylists($); sub HEOSMaster_GetServers($); sub HEOSMaster_Initialize($) { + my ($hash) = @_; + # Provider $hash->{ReadFn} = "HEOSMaster_Read"; $hash->{WriteFn} = "HEOSMaster_Write"; @@ -147,15 +149,18 @@ sub HEOSMaster_Initialize($) { $readingFnAttributes; foreach my $d(sort keys %{$modules{HEOSMaster}{defptr}}) { + my $hash = $modules{HEOSMaster}{defptr}{$d}; $hash->{VERSION} = $version; } } sub HEOSMaster_Define($$) { + my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); + return "too few parameters: define HEOSMaster " if( @a != 3 ); my $name = $a[0]; @@ -173,118 +178,159 @@ sub HEOSMaster_Define($$) { readingsEndUpdate($hash,1); if( $init_done ) { + HEOSMaster_firstRun($hash); + } else { + InternalTimer( gettimeofday()+15, 'HEOSMaster_firstRun', $hash, 0 ) if( ($hash->{HOST}) ); } + $modules{HEOSPlayer}{defptr}{$host} = $hash; return undef; } sub HEOSMaster_Undef($$) { + my ( $hash, $arg ) = @_; my $host = $hash->{HOST}; my $name = $hash->{NAME}; + HEOSMaster_Close($hash); delete $modules{HEOSMaster}{defptr}{$hash->{HOST}}; + Log3 $name, 3, "HEOSPlayer ($name) - device $name deleted"; return undef; } sub HEOSMaster_Attr(@) { + my ( $cmd, $name, $attrName, $attrVal ) = @_; my $hash = $defs{$name}; my $orig = $attrVal; + if( $attrName eq "disable" ) { if( $cmd eq "set" and $attrVal eq "1" ) { + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); Log3 $name, 3, "HEOSMaster ($name) - disabled"; - } - elsif( $cmd eq "del" ) { + + } elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); Log3 $name, 3, "HEOSMaster ($name) - enabled"; } } + if( $attrName eq "disabledForIntervals" ) { if( $cmd eq "set" ) { + Log3 $name, 3, "HEOSMaster ($name) - enable disabledForIntervals"; readingsSingleUpdate ( $hash, "state", "Unknown", 1 ); - } - elsif( $cmd eq "del" ) { + + } elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); Log3 $name, 3, "HEOSMaster ($name) - delete disabledForIntervals"; } } + return undef; } sub HEOSMaster_Get($$@) { + my ($hash, $name, @aa) = @_; my ($cmd, @args) = @aa; my $pid = $hash->{PID}; + if( $cmd eq 'showAccount' ) { + return AttrVal($name,'heosUsername',0) . ":" .HEOSMaster_ReadPassword($hash); } + my $list = 'showAccount:noArg'; return "Unknown argument $cmd, choose one of $list"; } sub HEOSMaster_Set($@) { + my ($hash, $name, $cmd, @args) = @_; my ($arg, @params) = @args; my $action; my $heosCmd; + if($cmd eq 'reopen') { + return "usage: reopen" if( @args != 0 ); HEOSMaster_ReOpen($hash); return undef; + } elsif($cmd eq 'getPlayers') { + return "usage: getPlayers" if( @args != 0 ); $heosCmd = 'getPlayers'; $action = undef; + } elsif($cmd eq 'getGroups') { + return "usage: getGroups" if( @args != 0 ); $heosCmd = 'getGroups'; $action = undef; + } elsif($cmd eq 'enableChangeEvents') { + return "usage: enableChangeEvents" if( @args != 1 ); $heosCmd = $cmd; $action = $args[0]; + } elsif($cmd eq 'checkAccount') { + return "usage: checkAccount" if( @args != 0 ); $heosCmd = $cmd; $action = undef; + } elsif($cmd eq 'signAccount') { + return "usage: signAccountIn" if( @args != 1 ); return "please set account informattion first" if(AttrVal($name,'heosUsername','none') eq 'none'); $heosCmd = $cmd . $args[0]; $action = 'un='. AttrVal($name,'heosUsername','none') . '&pw=' . HEOSMaster_ReadPassword($hash) if($args[0] eq 'In'); + } elsif($cmd eq 'password') { + return "usage: password" if( @args != 1 ); return HEOSMaster_StorePassword( $hash, $args[0] ); + } elsif($cmd eq 'reboot') { + return "usage: reboot" if( @args != 0 ); return HEOSMaster_StorePassword( $hash, $args[0] ); - ################################################### - ### Dieser Menüpunkt ist nur zum testen + ################################################### + ### Dieser Menüpunkt ist nur zum testen + } elsif($cmd eq 'eventSend') { return "usage: eventSend" if( @args != 0 ); HEOSMaster_send($hash); return undef; ################################################### + } else { + my $list = ""; $list .= "reopen:noArg getPlayers:noArg getGroups:noArg enableChangeEvents:on,off checkAccount:noArg signAccount:In,Out password reboot"; return "Unknown argument $cmd, choose one of $list"; } + HEOSMaster_Write($hash,$heosCmd,$action); } sub HEOSMaster_Open($) { + my $hash = shift; my $name = $hash->{NAME}; my $host = $hash->{HOST}; @@ -293,6 +339,7 @@ sub HEOSMaster_Open($) { my $user = AttrVal($name,'heosUsername',undef); my $password = HEOSMaster_ReadPassword($hash); + Log3 $name, 4, "HEOSMaster ($name) - Baue Socket Verbindung auf"; my $socket = new Net::Telnet ( Host=>$host, @@ -314,9 +361,11 @@ sub HEOSMaster_Open($) { #hinzugefügt laut Protokoll 2.1.1 Initsequenz if( defined($user) and defined($password) ) { + HEOSMaster_Write($hash,'signAccountIn',"un=$user&pw=$password"); Log3 $name, 4, "HEOSMaster ($name) - sign in"; } + HEOSMaster_GetPlayers($hash); InternalTimer( gettimeofday()+1, 'HEOSMaster_EnableChangeEvents', $hash, 0 ); InternalTimer( gettimeofday()+2, 'HEOSMaster_GetMusicSources', $hash, 0 ); @@ -324,93 +373,131 @@ sub HEOSMaster_Open($) { } sub HEOSMaster_Close($) { + my $hash = shift; my $name = $hash->{NAME}; + return if( !$hash->{CD} ); + close($hash->{CD}) if($hash->{CD}); delete($hash->{FD}); delete($hash->{CD}); delete($selectlist{$name}); + readingsSingleUpdate($hash, 'state', 'not connected', 1 ); } sub HEOSMaster_ReOpen($) { + my $hash = shift; my $name = $hash->{NAME}; + HEOSMaster_Close($hash); HEOSMaster_Open($hash) if( !$hash->{CD} or !defined($hash->{CD}) ); } sub HEOSMaster_Write($@) { + my ($hash,$heosCmd,$value) = @_; my $name = $hash->{NAME}; my $string = "heos://$heosCmds{$heosCmd}"; + if( defined($value) ) { + $string .= "${value}" if( $value ne '&' ); } + $string .= "\r\n"; Log3 $name, 4, "HEOSMaster ($name) - WriteFn called"; + return Log3 $name, 4, "HEOSMaster ($name) - socket not connected" unless($hash->{CD}); + Log3 $name, 5, "HEOSMaster ($name) - $string"; syswrite($hash->{CD}, $string); + return undef; } sub HEOSMaster_Read($) { + my $hash = shift; my $name = $hash->{NAME}; my $len; my $buf; + Log3 $name, 4, "HEOSMaster ($name) - ReadFn gestartet"; $len = sysread($hash->{CD},$buf,1024); # die genaue Puffergröße wird noch ermittelt + if( !defined($len) || !$len ) { + Log3 $name, 5, "HEOSMaster ($name) - connection closed by remote Host"; HEOSMaster_Close($hash); return; } + unless( defined $buf) { + Log3 $name, 3, "HEOSMaster ($name) - Keine Daten empfangen"; return; } + Log3 $name, 5, "HEOSMaster ($name) - received buffer data, start HEOSMaster_ProcessRead: $buf"; HEOSMaster_ProcessRead($hash,$buf); } sub HEOSMaster_ProcessRead($$) { + my ($hash, $data) = @_; my $name = $hash->{NAME}; my $buffer = ''; + Log3 $name, 4, "HEOSMaster ($name) - process read"; #include previous partial message + if(defined($hash->{PARTIAL}) && $hash->{PARTIAL}) { + Log3 $name, 5, "HEOSMaster ($name) - PARTIAL: " . $hash->{PARTIAL}; $buffer = $hash->{PARTIAL}; + } else { + Log3 $name, 4, "HEOSMaster ($name) - No PARTIAL buffer"; } + Log3 $name, 5, "HEOSMaster ($name) - Incoming data: " . $data; + $buffer = $buffer . $data; + Log3 $name, 5, "HEOSMaster ($name) - Current processing buffer (PARTIAL + incoming data): " . $buffer; + my ($json,$tail) = HEOSMaster_ParseMsg($hash, $buffer); #processes all complete messages + while($json) { + $hash->{LAST_RECV} = time(); Log3 $name, 5, "HEOSMaster ($name) - Decoding JSON message. Length: " . length($json) . " Content: " . $json; my $obj = JSON->new->utf8(0)->decode($json); + if(defined($obj->{heos})) { + HEOSMaster_ResponseProcessing($hash,$json); Log3 $name, 4, "HEOSMaster ($name) - starte HEOSMaster_ResponseProcessing"; + } elsif(defined($obj->{error})) { + Log3 $name, 3, "HEOSMaster ($name) - Received error message: " . $json; } + ($json,$tail) = HEOSMaster_ParseMsg($hash, $tail); } + $hash->{PARTIAL} = $tail; Log3 $name, 5, "HEOSMaster ($name) - Tail: " . $tail; Log3 $name, 5, "HEOSMaster ($name) - PARTIAL: " . $hash->{PARTIAL}; @@ -418,10 +505,12 @@ sub HEOSMaster_ProcessRead($$) { } sub HEOSMaster_ResponseProcessing($$) { + my ($hash,$json) = @_; my $name = $hash->{NAME}; my $decode_json; + Log3 $name, 5, "HEOSMaster ($name) - JSON String: $json"; return Log3 $name, 3, "HEOSMaster ($name) - empty answer received" unless( defined($json)); @@ -436,6 +525,7 @@ sub HEOSMaster_ResponseProcessing($$) { 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"; } @@ -447,24 +537,28 @@ sub HEOSMaster_ResponseProcessing($$) { #Quellen neu einlesen if( $decode_json->{heos}{command} =~ /^event\/sources_changed/ ) { + HEOSMaster_Write($hash,'getMusicSources',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); return Log3 $name, 4, "HEOSMaster ($name) - player changed"; } #User neu einlesen if( $decode_json->{heos}{command} =~ /^event\/user_changed/ ) { + HEOSMaster_Write($hash,'checkAccount',undef); return Log3 $name, 4, "HEOSMaster ($name) - user changed"; } #Gruppen neu einlesen if( $decode_json->{heos}{command} =~ /^event\/groups_changed/ ) { + #InternalTimer( gettimeofday()+5, 'HEOSMaster_GetGroups', $hash, 0 ); HEOSMaster_Write($hash,'getGroups',undef); return Log3 $name, 4, "HEOSMaster ($name) - groups changed"; @@ -478,78 +572,113 @@ sub HEOSMaster_ResponseProcessing($$) { foreach my $payload ( @{$decode_json->{payload}} ) { if( $payload->{sid} eq "1024" ) { + $i += 2; InternalTimer( gettimeofday()+$i, 'HEOSMaster_GetServers', $hash, 0 ); Log3 $name, 4, "HEOSMaster ($name) - GetServers in $i seconds"; + } elsif( $payload->{sid} eq "1025" ) { + $i += 2; InternalTimer( gettimeofday()+$i, 'HEOSMaster_GetPlaylists', $hash, 0 ); Log3 $name, 4, "HEOSMaster ($name) - GetPlaylists in $i seconds"; + } elsif( $payload->{sid} eq "1026" ) { + $i += 2; InternalTimer( gettimeofday()+$i, 'HEOSMaster_GetHistory', $hash, 0 ); Log3 $name, 4, "HEOSMaster ($name) - GetHistory in $i seconds"; + } elsif( $payload->{sid} eq "1027" ) { + $i += 2; InternalTimer( gettimeofday()+$i, 'HEOSMaster_GetInputs', $hash, 0 ); Log3 $name, 4, "HEOSMaster ($name) - GetInputs in $i seconds"; + } elsif( $payload->{sid} eq "1028" ) { + $i += 2; InternalTimer( gettimeofday()+$i, 'HEOSMaster_GetFavorites', $hash, 0 ); Log3 $name, 4, "HEOSMaster ($name) - GetFavorites in $i seconds"; + } else { + #Onlinedienste push( @{$hash->{helper}{sources}},$payload); Log3 $name, 4, "HEOSMaster ($name) - GetRadioSource {$payload->{name} with sid $payload->{sid}"; } } + return Log3 $name, 3, "HEOSMaster ($name) - call Sourcebrowser"; } if( $decode_json->{heos}{command} =~ /^browse\/browse/ and ref($decode_json->{payload}) eq "ARRAY" and scalar(@{$decode_json->{payload}}) > 0) { if ( defined $message{sid} ) { - #my @range = ( 0 ); - #@range = split(',', $message{range}) if ( defined $message{range} ); - if ( defined $message{range} ) { $message{range} =~ s/(\d+)\,\d+/$1/; } - else { $message{range} = 0; } + if ( defined $message{range} ) { + + $message{range} =~ s/(\d+)\,\d+/$1/; + + } else { + + $message{range} = 0; + } + my $start = $message{range} + $message{returned}; if( $message{sid} eq '1028' ) { + #Favoriten einlesen $hash->{helper}{favorites} = [] if ( $message{range} == 0 ); - push( @{$hash->{helper}{favorites}}, (@{$decode_json->{payload}}) ); + push( @{$hash->{helper}{favorites}}, (@{$decode_json->{payload}}) ); + if ( $start >= $message{count} ) { + #Nachricht an die Player das sich die Favoriten geändert haben foreach my $dev ( devspec2array("TYPE=HEOSPlayer") ) { + $json = '{"heos": {"command": "event/favorites_changed", "message": "pid='.$defs{$dev}->{PID}.'"}}'; Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for Favorites Changed"; } } + } elsif( $message{sid} eq '1026' ) { + #History einlesen $hash->{helper}{history} = [] if ( $message{range} == 0 ); push( @{$hash->{helper}{history}}, (@{$decode_json->{payload}}) ); + } elsif( $message{sid} eq '1025' ) { + #Playlisten einlesen $hash->{helper}{playlists} = [] if ( $message{range} == 0 ); - push( @{$hash->{helper}{playlists}}, (@{$decode_json->{payload}}) ); + push( @{$hash->{helper}{playlists}}, (@{$decode_json->{payload}}) ); + } elsif( $message{sid} eq '1027' ) { + #Inputs einlesen push( @{$hash->{helper}{sources}}, map { $_->{name} .= " AUX"; $_ } (@{$decode_json->{payload}}) ); + } elsif( $message{sid} eq '1024' ) { + #Lokal einlesen push( @{$hash->{helper}{sources}}, map { $_->{name} .= " USB" if ( $_->{sid} < 0 ); $_ } (@{$decode_json->{payload}}) ); + } else { + #aktuellen Input/Media einlesen $hash->{helper}{media} = [] if ( $message{range} == 0 ); push( @{$hash->{helper}{media}}, (@{$decode_json->{payload}}) ); } + Log3 $name, 4, "HEOSMaster ($name) - call Browser with sid $message{sid} and $message{returned} items from $message{count} items"; + if ( $start < $message{count} ) { + HEOSMaster_Write($hash,'browseSource',"sid=$message{sid}&range=$start,".($start + 100) ); Log3 $name, 3, "HEOSMaster ($name) - call Browser with sid $message{sid} next Range from $message{returned}"; } + return; } } @@ -557,22 +686,29 @@ sub HEOSMaster_ResponseProcessing($$) { 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/ ) { + return Log3 $name, 4, "HEOSMaster ($name) - empty ARRAY received" unless(scalar(@{$decode_json->{payload}}) > 0); foreach my $payload (@{$decode_json->{payload}}) { + $json = '{"pid": "'; $json .= "$payload->{pid}"; $json .= '","heos": {"command": "player/get_player_info"}}'; Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for Players"; } + } elsif( $decode_json->{heos}{command} =~ /group\/get_groups/ ) { + my $filter = "TYPE=HEOSGroup"; if ( scalar(@{$decode_json->{payload}}) > 0 ) { + $filter .= ":FILTER=GID!="; + foreach my $payload (@{$decode_json->{payload}}) { + $json = '{"gid": "'; $json .= "$payload->{gid}"; $json .= '","heos": {"command": "group/get_group_info"}}'; @@ -580,40 +716,52 @@ sub HEOSMaster_ResponseProcessing($$) { Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for Groups"; $filter .= $payload->{gid}."|"; } + chop($filter); #letztes | wieder abschneiden } + #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( defined($decode_json->{payload}{pid}) ) { + } elsif( $decode_json->{heos}{command} =~ /player\/get_player_info/ ) { # ist vielleicht verständlicher? + Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for PlayerInfo"; - #} elsif( defined($decode_json->{payload}{gid}) and defined($decode_json->{payload}{players}) ) { + } elsif( $decode_json->{heos}{command} =~ /group\/get_group_info/ ) { # ist vielleicht verständlicher? + Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher for GroupInfo"; + } elsif( defined($message{pid}) or defined($message{gid}) ) { + Dispatch($hash,$json,undef); Log3 $name, 4, "HEOSMaster ($name) - call Dispatcher"; } + return; } + Log3 $name, 4, "HEOSMaster ($name) - no Match for processing data"; } sub HEOSMaster_WriteReadings($$) { + my ($hash,$decode_json) = @_; my $name = $hash->{NAME}; + ############################ #### Aufbereiten der Daten soweit nötig my $readingsHash = HEOSMaster_PreProcessingReadings($hash,$decode_json) @@ -629,20 +777,26 @@ sub HEOSMaster_WriteReadings($$) { ### Event Readings if( ref($readingsHash) eq "HASH" ) { + Log3 $name, 4, "HEOSMaster ($name) - response json Hash back from HEOSMaster_PreProcessingReadings"; my $t; my $v; + while( ( $t, $v ) = each (%{$readingsHash}) ) { + readingsBulkUpdate( $hash, $t, $v ) if( defined($v) ); } } + readingsBulkUpdate( $hash, "lastCommand", $decode_json->{heos}{command} ); readingsBulkUpdate( $hash, "lastResult", $decode_json->{heos}{result} ); if( ref($decode_json->{payload}) ne "ARRAY" ) { + readingsBulkUpdate( $hash, "lastPlayerId", $decode_json->{payload}{pid} ); readingsBulkUpdate( $hash, "lastPlayerName", $decode_json->{payload}{name} ); } + readingsEndUpdate( $hash, 1 ); return undef; } @@ -651,6 +805,7 @@ sub HEOSMaster_WriteReadings($$) { ### my little Helpers sub HEOSMaster_ParseMsg($$) { + my ($hash, $buffer) = @_; my $name = $hash->{NAME}; my $open = 0; @@ -658,169 +813,220 @@ sub HEOSMaster_ParseMsg($$) { my $msg = ''; my $tail = ''; + if($buffer) { foreach my $c (split //, $buffer) { if($open == $close && $open > 0) { $tail .= $c; #Log3 $name, 5, "HEOSMaster ($name) - $open == $close && $open > 0"; + } elsif(($open == $close) && ($c ne '{')) { + Log3 $name, 5, "HEOSMaster ($name) - Garbage character before message: " . $c; + } else { + if($c eq '{') { + $open++; + } elsif($c eq '}') { + $close++; } + $msg .= $c; } } + if($open != $close) { + $tail = $msg; $msg = ''; } } + Log3 $name, 5, "HEOSMaster ($name) - return msg: $msg and tail: $tail"; return ($msg,$tail); } sub HEOSMaster_PreProcessingReadings($$) { + my ($hash,$decode_json) = @_; 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, "HEOSMaster ($name) - preprocessing readings"; + if ( $decode_json->{heos}{command} eq 'system/register_for_change_events' ) { + $buffer{'enableChangeEvents'} = $message{enable}; + } elsif ( $decode_json->{heos}{command} eq 'system/check_account' or $decode_json->{heos}{command} eq 'system/sign_in' ) { if ( exists $message{signed_out} ) { + $buffer{'heosAccount'} = "signed_out"; + } else { + $buffer{'heosAccount'} = "signed_in as $message{un}"; HEOSMaster_GetFavorites($hash) if( ReadingsVal($name,"enableChangeEvents", "off") eq "on" ); } + } else { + Log3 $name, 3, "HEOSMaster ($name) - no match found"; return undef; } + Log3 $name, 4, "HEOSMaster ($name) - Match found for decode_json"; return \%buffer; } sub HEOSMaster_firstRun($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash,'HEOSMaster_firstRun'); HEOSMaster_Open($hash) if( !IsDisabled($name) ); } sub HEOSMaster_GetPlayers($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash,'HEOSMaster_GetPlayers'); HEOSMaster_Write($hash,'getPlayers',undef); Log3 $name, 4, "HEOSMaster ($name) - getPlayers"; } sub HEOSMaster_GetGroups($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash,'HEOSMaster_GetGroups'); HEOSMaster_Write($hash,'getGroups',undef); Log3 $name, 4, "HEOSMaster ($name) - getGroups"; } sub HEOSMaster_EnableChangeEvents($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash,'HEOSMaster_EnableChangeEvents'); HEOSMaster_Write($hash,'enableChangeEvents','on'); Log3 $name, 4, "HEOSMaster ($name) - set enableChangeEvents on"; } sub HEOSMaster_GetMusicSources($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_GetMusicSources'); HEOSMaster_Write($hash,'getMusicSources',undef); Log3 $name, 4, "HEOSMaster ($name) - getMusicSources"; } sub HEOSMaster_GetFavorites($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_GetFavorites'); HEOSMaster_Write($hash,'browseSource','sid=1028'); Log3 $name, 4, "HEOSMaster ($name) - getFavorites"; } sub HEOSMaster_GetInputs($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_GetInputs'); HEOSMaster_Write($hash,'browseSource','sid=1027'); Log3 $name, 4, "HEOSMaster ($name) - getInputs"; } sub HEOSMaster_GetServers($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_GetServers'); HEOSMaster_Write($hash,'browseSource','sid=1024'); Log3 $name, 4, "HEOSMaster ($name) - getServers"; } sub HEOSMaster_GetPlaylists($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_GetPlaylists'); HEOSMaster_Write($hash,'browseSource','sid=1025'); Log3 $name, 4, "HEOSMaster ($name) - getPlaylists"; } sub HEOSMaster_GetHistory($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_GetHistory'); HEOSMaster_Write($hash,'browseSource','sid=1026'); Log3 $name, 4, "HEOSMaster ($name) - getHistory"; } sub HEOSMaster_CheckAccount($) { + my $hash = shift; my $name = $hash->{NAME}; + RemoveInternalTimer($hash, 'HEOSMaster_CheckAccount'); HEOSMaster_Write($hash,'checkAccount',undef); Log3 $name, 4, "HEOSMaster ($name) - checkAccount"; } sub HEOSMaster_StorePassword($$) { + my ($hash, $password) = @_; my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; my $key = getUniqueId().$index; my $enc_pwd = ""; + if(eval "use Digest::MD5;1") { + $key = Digest::MD5::md5_hex(unpack "H*", $key); $key .= Digest::MD5::md5_hex($key); } + for my $char (split //, $password) { + my $encode=chop($key); $enc_pwd.=sprintf("%.2x",ord($char)^ord($encode)); $key=$encode.$key; } + my $err = setKeyValue($index, $enc_pwd); return "error while saving the password - $err" if(defined($err)); @@ -828,41 +1034,57 @@ sub HEOSMaster_StorePassword($$) { } sub HEOSMaster_ReadPassword($) { + my ($hash) = @_; my $name = $hash->{NAME}; my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd"; my $key = getUniqueId().$index; my ($password, $err); - Log3 $name, 4, "HEOSMaster ($name) - Read FritzBox password from file"; + + Log3 $name, 4, "HEOSMaster ($name) - Read password from file"; + ($err, $password) = getKeyValue($index); if ( defined($err) ) { - Log3 $name, 4, "HEOSMaster ($name) - unable to read FritzBox password from file: $err"; + + Log3 $name, 4, "HEOSMaster ($name) - unable to read password from file: $err"; return undef; + } + if ( defined($password) ) { if ( eval "use Digest::MD5;1" ) { + $key = Digest::MD5::md5_hex(unpack "H*", $key); $key .= Digest::MD5::md5_hex($key); } + my $dec_pwd = ''; + for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { + my $decode=chop($key); $dec_pwd.=chr(ord($char)^ord($decode)); $key=$decode.$key; } + return $dec_pwd; + } else { - Log3 $name, 4, "HEOSMaster ($name) - No password in file"; - return undef; + + Log3 $name, 4, "HEOSMaster ($name) - No password in file"; + return undef; } } ################ ### Nur für mich um dem Emulator ein Event ab zu jagen sub HEOSMaster_send($) { + my $hash = shift; + + HEOSMaster_Write($hash,'eventChangeVolume',undef); } diff --git a/21_HEOSPlayer.pm b/21_HEOSPlayer.pm index 4fc730c..3af55ab 100644 --- a/21_HEOSPlayer.pm +++ b/21_HEOSPlayer.pm @@ -37,7 +37,7 @@ use JSON qw(decode_json); use Encode qw(encode_utf8); use Data::Dumper; -my $version = "0.1.58"; +my $version = "0.1.60"; # Declare functions sub HEOSPlayer_Initialize($); @@ -57,10 +57,12 @@ sub HEOSPlayer_Get($$@); sub HEOSPlayer_GetMute($); sub HEOSPlayer_Initialize($) { + my ($hash) = @_; $hash->{Match} = '.*{"command":."player.*|.*{"command":."event/player.*|.*{"command":."event\/repeat_mode_changed.*|.*{"command":."event\/shuffle_mode_changed.*|.*{"command":."event\/favorites_changed.*'; + # Provider $hash->{SetFn} = "HEOSPlayer_Set"; $hash->{GetFn} = "HEOSPlayer_Get"; @@ -74,57 +76,73 @@ sub HEOSPlayer_Initialize($) { $readingFnAttributes; foreach my $d(sort keys %{$modules{HEOSPlayer}{defptr}}) { + my $hash = $modules{HEOSPlayer}{defptr}{$d}; $hash->{VERSION} = $version; } } sub HEOSPlayer_Define($$) { + my ( $hash, $def ) = @_; my @a = split( "[ \t]+", $def ); splice( @a, 1, 1 ); my $iodev; my $i = 0; + foreach my $param ( @a ) { if( $param =~ m/IODev=([^\s]*)/ ) { + $iodev = $1; splice( @a, $i, 3 ); last; } + $i++; } + return "too few parameters: define HEOSPlayer " if( @a < 2 ); - + my ($name,$pid) = @a; $hash->{PID} = $pid; $hash->{VERSION} = $version; AssignIoPort($hash,$iodev) if( !$hash->{IODev} ); + if(defined($hash->{IODev}->{NAME})) { + Log3 $name, 3, "HEOSPlayer ($name) - I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "HEOSPlayer ($name) - no I/O device"; } + $iodev = $hash->{IODev}->{NAME}; my $code = abs($pid); - + $code = $iodev."-".$code if( defined($iodev) ); my $d = $modules{HEOSPlayer}{defptr}{$code}; + return "HEOSPlayer device $hash->{pid} on HEOSMaster $iodev already defined as $d->{NAME}." if( defined($d) && $d->{IODev} == $hash->{IODev} && $d->{NAME} ne $name ); - + Log3 $name, 3, "HEOSPlayer ($name) - defined with Code: $code"; $attr{$name}{room} = "HEOS" if( !defined( $attr{$name}{room} ) ); $attr{$name}{devStateIcon} = "on:10px-kreis-gruen off:10px-kreis-rot" if( !defined( $attr{$name}{devStateIcon} ) ); + if( $init_done ) { + InternalTimer( gettimeofday()+int(rand(2)), "HEOSPlayer_GetPlayerInfo", $hash, 0 ); InternalTimer( gettimeofday()+int(rand(4)), "HEOSPlayer_GetPlayState", $hash, 0 ); InternalTimer( gettimeofday()+int(rand(6)), "HEOSPlayer_GetNowPlayingMedia", $hash, 0 ); InternalTimer( gettimeofday()+int(rand(8)), "HEOSPlayer_GetPlayMode", $hash, 0 ); InternalTimer( gettimeofday()+int(rand(10)), "HEOSPlayer_GetVolume", $hash, 0 ); - InternalTimer( gettimeofday()+int(rand(12)), "HEOSPlayer_GetMute", $hash, 0 ); + InternalTimer( gettimeofday()+int(rand(12)), "HEOSPlayer_GetMute", $hash, 0 ); + } else { + InternalTimer( gettimeofday()+15+int(rand(2)), "HEOSPlayer_GetPlayerInfo", $hash, 0 ); InternalTimer( gettimeofday()+15+int(rand(4)), "HEOSPlayer_GetPlayState", $hash, 0 ); InternalTimer( gettimeofday()+15+int(rand(6)), "HEOSPlayer_GetNowPlayingMedia", $hash, 0 ); @@ -132,49 +150,61 @@ sub HEOSPlayer_Define($$) { InternalTimer( gettimeofday()+15+int(rand(10)), "HEOSPlayer_GetVolume", $hash, 0 ); InternalTimer( gettimeofday()+15+int(rand(12)), "HEOSPlayer_GetMute", $hash, 0 ); } + readingsBeginUpdate($hash); readingsBulkUpdate($hash, 'state','Initialized'); readingsBulkUpdate($hash, 'volumeUp', 5); readingsBulkUpdate($hash, 'volumeDown', 5); readingsEndUpdate($hash, 1); + $modules{HEOSPlayer}{defptr}{$code} = $hash; return undef; } sub HEOSPlayer_Undef($$) { + my ( $hash, $arg ) = @_; my $pid = $hash->{PID}; my $name = $hash->{NAME}; + RemoveInternalTimer($hash); my $code = abs($pid); $code = $hash->{IODev}->{NAME} ."-". $code if( defined($hash->{IODev}->{NAME}) ); delete($modules{HEOSPlayer}{defptr}{$code}); + Log3 $name, 3, "HEOSPlayer ($name) - device $name deleted with Code: $code"; return undef; } sub HEOSPlayer_Attr(@) { + my ( $cmd, $name, $attrName, $attrVal ) = @_; my $hash = $defs{$name}; my $token = $hash->{IODev}->{TOKEN}; + if( $attrName eq "disable" ) { if( $cmd eq "set" and $attrVal eq "1" ) { + readingsSingleUpdate ( $hash, "state", "disabled", 1 ); Log3 $name, 3, "HEOSPlayer ($name) - disabled"; - } - elsif( $cmd eq "del" ) { + + } elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); Log3 $name, 3, "HEOSPlayer ($name) - enabled"; } } + if( $attrName eq "disabledForIntervals" ) { if( $cmd eq "set" ) { + Log3 $name, 3, "HEOSPlayer ($name) - enable disabledForIntervals"; readingsSingleUpdate ( $hash, "state", "Unknown", 1 ); - } - elsif( $cmd eq "del" ) { + + } elsif( $cmd eq "del" ) { + readingsSingleUpdate ( $hash, "state", "active", 1 ); Log3 $name, 3, "HEOSPlayer ($name) - delete disabledForIntervals"; } @@ -182,48 +212,58 @@ sub HEOSPlayer_Attr(@) { } sub HEOSPlayer_Get($$@) { + my ($hash, $name, @aa) = @_; my ($cmd, @args) = @aa; my $pid = $hash->{PID}; my $result = ""; - #print "CL ###################################################\n".Dumper($hash->{CL}); - $hash->{helper}{cl} = $hash->{CL} if( ref($hash->{CL}) eq 'HASH' ); - + + #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' ) { + #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 'search' ) { - return "usage: search " if( @args != 1 ); - - - - } + + return "usage: search " if( @args != 1 ); + } + my $list = 'playlists:noArg channels:noArg channelscount:noArg inputs:noArg ls search'; return "Unknown argument $cmd, choose one of $list"; } sub HEOSPlayer_Set($$@) { + my ($hash, $name, @aa) = @_; my ($cmd, @args) = @aa; my $pid = $hash->{PID}; @@ -232,185 +272,270 @@ sub HEOSPlayer_Set($$@) { my $rvalue; my $favoritcount = 1; my $qcount = 1; - my $string = "pid=$pid"; + my $string = ''; + #print "cmd ###################################################\n".Dumper($cmd); if( $cmd eq 'getPlayerInfo' ) { return "usage: getPlayerInfo" if( @args != 0 ); + $heosCmd = $cmd; + } elsif( $cmd eq 'getPlayState' ) { return "usage: getPlayState" if( @args != 0 ); + $heosCmd = $cmd; + } elsif( $cmd eq 'getPlayMode' ) { return "usage: getPlayMode" if( @args != 0 ); + $heosCmd = $cmd; + } elsif( $cmd eq 'getNowPlayingMedia' ) { return "usage: getNowPlayingMedia" if( @args != 0 ); + $heosCmd = $cmd; + } elsif( $cmd eq 'repeat' ) { return "usage: repeat one,all,off" if( @args != 1 ); + $heosCmd = 'setPlayMode'; - $rvalue = 'on_'.$args[0]; - $rvalue = 'off' if($rvalue eq 'on_off'); + $rvalue = 'on_'.$args[0]; + $rvalue = 'off' if($rvalue eq 'on_off'); $action = "repeat=$rvalue&shuffle=".ReadingsVal($name,'shuffle','off'); + } elsif( $cmd eq 'shuffle' ) { return "usage: shuffle on,off" if( @args != 1 ); + $heosCmd = 'setPlayMode'; - $rvalue = 'on_'.ReadingsVal($name,'repeat','off'); - $rvalue = 'off' if($rvalue eq 'on_off'); + $rvalue = 'on_'.ReadingsVal($name,'repeat','off'); + $rvalue = 'off' if($rvalue eq 'on_off'); $action = "repeat=$rvalue&shuffle=$args[0]"; + } elsif( $cmd eq 'play' ) { return "usage: play" if( @args != 0 ); + $heosCmd = 'setPlayState'; $action = "state=$cmd"; + } elsif( $cmd eq 'stop' ) { return "usage: stop" if( @args != 0 ); + $heosCmd = 'setPlayState'; $action = "state=$cmd"; + } elsif( $cmd eq 'pause' ) { return "usage: pause" if( @args != 0 ); + $heosCmd = 'setPlayState'; $action = "state=$cmd"; + } elsif( $cmd eq 'mute' ) { return "usage: mute on/off" if( @args != 1 ); + $heosCmd = 'setMute'; $action = "state=$args[0]"; + } elsif( $cmd eq 'volume' ) { return "usage: volume 0-100" if( @args != 1 ); + $heosCmd = 'setVolume'; $action = "level=$args[0]"; + } elsif( $cmd eq 'volumeUp' ) { return "usage: volumeUp 0-10" if( @args != 1 ); + $heosCmd = $cmd; $action = "step=$args[0]"; + } elsif( $cmd eq 'volumeDown' ) { return "usage: volumeDown 0-10" if( @args != 1 ); + $heosCmd = $cmd; $action = "step=$args[0]"; + } elsif( $cmd eq 'groupWithMember' ) { return "usage: groupWithMember" if( @args != 1 ); + $pid .= ",$defs{$args[0]}->{PID}"; $heosCmd = 'createGroup'; + } elsif( $cmd eq 'clearGroup' ) { return "usage: clearGroup" if( @args != 0 ); + $heosCmd = 'createGroup'; + } elsif( $cmd eq 'next' ) { return "usage: next" if( @args != 0 ); + $heosCmd = 'playNext'; + } elsif( $cmd eq 'prev' ) { return "usage: prev" if( @args != 0 ); + $heosCmd = 'playPrev'; - #} elsif( $cmd eq 'reReadSources' ) { - # return "usage: reReadSources" if( @args != 0 ); - # $heosCmd = 'getMusicSources'; + } elsif ( $cmd =~ /channel/ ) { + my $favorit = ReadingsVal($name,"channel", 0); + $favoritcount = scalar(@{$hash->{IODev}{helper}{favorites}}) if ( defined $hash->{IODev}{helper}{favorites} ); $heosCmd = 'playPresetStation'; + if ( $cmd eq 'channel' ) { return "usage: channel 1-$favoritcount" if( @args != 1 ); + $action = "preset=$args[0]"; + } elsif( $cmd eq 'channelUp' ) { return "usage: channelUp" if( @args != 0 ); + $favorit = $favoritcount if ( ++$favorit > $favoritcount ); $action = "preset=".$favorit; + } elsif( $cmd eq 'channelDown' ) { + $favorit = 1 if ( --$favorit <= 0 ); $action = "preset=".$favorit; } + } elsif ( $cmd =~ /Playlist/ ) { + my @cids = map { $_->{cid} } grep { $_->{name} =~ /$args[0]/i } (@{ $hash->{IODev}{helper}{playlists} }); if ( scalar @args == 1 && scalar @cids > 0 ) { if ( $cmd eq 'playPlaylist' ) { + $heosCmd = $cmd; $action = "sid=1025&cid=$cids[0]&aid=4"; + } elsif ( $cmd eq 'deletePlaylist' ) { + $heosCmd = $cmd; $action = "cid=$cids[0]"; $string = "sid=1025"; } + } else { + IOWrite($hash,'browseSource','sid=1025'); my @playlists = map { $_->{name} } (@{ $hash->{IODev}{helper}{playlists}}); return "usage: $cmd ".join(",",@playlists); } + } elsif( $cmd eq 'input' ) { + my @sids; - my $search = $args[0]; - - $search =~ s/\xC2\xA0/ /g; + my $search = $args[0]; + + $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'; - } else { + + } 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"); + my $search = $args[0]; + my $sid = ReadingsVal($name,".input", "9999"); - return "usage: set input first" unless( defined($sid) ); + return "usage: set input first" unless( defined($sid) ); + if ( scalar @args == 1 ) { - $search =~ s/\xC2\xA0/ /g; + + $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" ) { + #alles abspielen $heosCmd = 'playPlaylist'; $action = "sid=$sid&cid=$ids[0]{cid}&aid=4"; #Container merken readingsSingleUpdate($hash, ".cid", 0, 1); + } else { + #mehr einlesen readingsSingleUpdate($hash, ".cid", $ids[0]{cid}, 1); $heosCmd = 'browseSource'; $action = "sid=$sid&cid=$ids[0]{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"; @@ -419,63 +544,87 @@ sub HEOSPlayer_Set($$@) { } } } + } else { + my @media; if ( $sid eq "9999" ) { - @media = map { $_->{song} } (@{ $hash->{helper}{queue}}); - } else { + + @media = map { $_->{song} } (@{ $hash->{helper}{queue}}); + + } else { + @media = map { $_->{name} } (@{ $hash->{IODev}{helper}{media}}); } + return "usage: media ".join(",",@media); } + } 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' ) { + $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" ); + $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 $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 "; $list .= "groupWithMember:" . 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} ); + if ( defined $hash->{IODev}{helper}{playlists} ) { + @playlists = map { my %n; $n{name} = $_->{name}; $n{name} =~ s/\s+/\ /g; $n{name} } (@{ $hash->{IODev}{helper}{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 ); return "Unknown argument $cmd, choose one of $list"; } + + + $string .= "pid=$pid"; $string .= "&$action" if( defined($action)); IOWrite($hash,"$heosCmd","$string"); Log3 $name, 4, "HEOSPlayer ($name) - IOWrite: $heosCmd $string IODevHash=$hash->{IODev}"; @@ -483,56 +632,82 @@ sub HEOSPlayer_Set($$@) { } sub HEOSPlayer_Parse($$) { + my ($io_hash,$json) = @_; my $name = $io_hash->{NAME}; my $pid; my $decode_json; - my $code; + my $code; + $decode_json = decode_json(encode_utf8($json)); Log3 $name, 4, "HEOSPlayer - ParseFn wurde aufgerufen"; if( defined($decode_json->{pid}) ) { + $pid = $decode_json->{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} ) { + IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}"); 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}; + } else { + my $devname = "HEOSPlayer".abs($pid); return "UNDEFINED $devname HEOSPlayer $pid IODev=$name"; } + } else { - 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}) ); - Log3 $name, 4, "HEOSPlayer ($name) PID: $pid"; + + 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}) ); + 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} ) { + 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} = []; - } + 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}}) ); + 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}"; } + return $hash->{NAME}; + } else { + my $devname = "HEOSPlayer".abs($pid); return "UNDEFINED $devname HEOSPlayer $pid IODev=$name"; } @@ -540,9 +715,11 @@ sub HEOSPlayer_Parse($$) { } sub HEOSPlayer_WriteReadings($$) { + my ($hash,$decode_json) = @_; my $name = $hash->{NAME}; + Log3 $name, 3, "HEOSPlayer ($name) - processing data to write readings"; ############################ #### Aufbereiten der Daten soweit nötig (bei Events zum Beispiel) @@ -554,9 +731,11 @@ sub HEOSPlayer_WriteReadings($$) { readingsBeginUpdate($hash); ### Event Readings if( ref($readingsHash) eq "HASH" ) { + Log3 $name, 4, "HEOSPlayer ($name) - response json Hash back from HEOSPlayer_PreProcessingReadings"; my $t; my $v; + while( ( $t, $v ) = each (%{$readingsHash}) ) { readingsBulkUpdate( $hash, $t, $v ) if( defined( $v ) ); } @@ -587,6 +766,7 @@ sub HEOSPlayer_WriteReadings($$) { my @presets = map { $_->{name} } (@{ $hash->{IODev}{helper}{favorites} }); my $search = ReadingsVal($name,"currentStation" ,undef); my( @index )= grep { $presets[$_] eq $search } 0..$#presets if ( defined $search ); + readingsBulkUpdate( $hash, 'channel', $index[0]+1 ) if ( scalar @index > 0 ); readingsBulkUpdate( $hash, 'state', 'on' ); readingsEndUpdate( $hash, 1 ); @@ -599,15 +779,20 @@ sub HEOSPlayer_WriteReadings($$) { ### my little Helpers sub HEOSPlayer_PreProcessingReadings($$) { + my ($hash,$decode_json) = @_; 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, "HEOSPlayer ($name) - preprocessing readings"; + + if ( $decode_json->{heos}{command} =~ /play_state/ or $decode_json->{heos}{command} =~ /player_state_changed/ ) { + + $buffer{'playStatus'} = $message{state}; - Log3 $name, 4, "HEOSPlayer ($name) - preprocessing readings"; - if ( $decode_json->{heos}{command} =~ /play_state/ or $decode_json->{heos}{command} =~ /player_state_changed/ ) { - $buffer{'playStatus'} = $message{state}; } elsif ( $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}); @@ -617,76 +802,106 @@ sub HEOSPlayer_PreProcessingReadings($$) { 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"; } + } elsif ( $decode_json->{heos}{command} =~ /play_mode/ or $decode_json->{heos}{command} =~ /repeat_mode_changed/ or $decode_json->{heos}{command} =~ /shuffle_mode_changed/ ) { - $buffer{'shuffle'} = $message{shuffle}; + + $buffer{'shuffle'} = $message{shuffle}; $buffer{'repeat'} = $message{repeat}; - $buffer{'repeat'} ==~ s/.*\_(.*)/$1/g; + $buffer{'repeat'} ==~ s/.*\_(.*)/$1/g; + } elsif ( $decode_json->{heos}{command} =~ /get_mute/ ) { + $buffer{'mute'} = $message{state}; + } 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/ ); + } elsif ( $decode_json->{heos}{command} =~ /player_now_playing_changed/ or $decode_json->{heos}{command} =~ /favorites_changed/ ) { IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}"); + } elsif ( $decode_json->{heos}{command} =~ /play_preset/ ) { - $buffer{'channel'} = $message{preset} + + $buffer{'channel'} = $message{preset} + } elsif ( $decode_json->{heos}{command} =~ /play_input/ ) { - $buffer{'input'} = $message{input}; + + $buffer{'input'} = $message{input}; + } elsif ( $decode_json->{heos}{command} =~ /playback_error/ ) { - $buffer{'error'} = $message{error}; + + $buffer{'error'} = $message{error}; + } else { + Log3 $name, 3, "HEOSPlayer ($name) - no match found"; return undef; } + Log3 $name, 4, "HEOSPlayer ($name) - Match found for decode_json"; return \%buffer; } sub HEOSPlayer_GetPlayerInfo($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetPlayerInfo'); IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}"); } sub HEOSPlayer_GetPlayState($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetPlayState'); IOWrite($hash,'getPlayState',"pid=$hash->{PID}"); } sub HEOSPlayer_GetPlayMode($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetPlayMode'); IOWrite($hash,'getPlayMode',"pid=$hash->{PID}"); } sub HEOSPlayer_GetNowPlayingMedia($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetNowPlayingMedia'); IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}"); } sub HEOSPlayer_GetVolume($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetVolume'); IOWrite($hash,'getVolume',"pid=$hash->{PID}"); } sub HEOSPlayer_GetMute($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetMute'); IOWrite($hash,'getMute',"pid=$hash->{PID}"); } sub HEOSPlayer_GetQueue($) { + my $hash = shift; + RemoveInternalTimer($hash,'HEOSPlayer_GetQueue'); IOWrite($hash,'getQueue',"pid=$hash->{PID}"); }