diff --git a/21_HEOSMaster.pm b/21_HEOSMaster.pm index 2649170..2418a8d 100644 --- a/21_HEOSMaster.pm +++ b/21_HEOSMaster.pm @@ -45,17 +45,25 @@ use JSON; use Net::Telnet; -my $version = "0.1.36"; +my $version = "0.1.42"; my %heosCmds = ( 'enableChangeEvents' => 'system/register_for_change_events?enable=', + 'checkAccount' => 'system/check_account', + 'signAccountIn' => 'system/sign_in?', + 'signAccountOut' => 'system/sign_out', + 'reboot' => 'system/reboot', 'getPlayers' => 'player/get_players', 'getPlayerInfo' => 'player/get_player_info?', 'getPlayState' => 'player/get_play_state?', + 'getPlayMode' => 'player/get_play_mode?', 'setPlayState' => 'player/set_play_state?', + 'setPlayMode' => 'player/set_play_mode?', 'setMute' => 'player/set_mute?', 'setVolume' => 'player/set_volume?', + 'volumeUp' => 'player/volume_up?', + 'volumeDown' => 'player/volume_down?', 'getNowPlayingMedia' => 'player/get_now_playing_media?', 'eventChangeVolume' => 'event/player_volume_changed' ); @@ -79,6 +87,8 @@ sub HEOSMaster_WriteReadings($$); sub HEOSMaster_PreResponseProsessing($$); sub HEOSMaster_GetPlayers($); sub HEOSMaster_EnableChangeEvents($); +sub HEOSMaster_PreProcessingReadings($$); +sub HEOSMaster_ReOpen($); @@ -91,9 +101,9 @@ sub HEOSMaster_Initialize($) { $hash->{ReadFn} = "HEOSMaster_Read"; $hash->{WriteFn} = "HEOSMaster_Write"; $hash->{Clients} = ":HEOSPlayer:"; - $hash->{MatchList} = { "1:HEOSPlayer" => '.*{"command":."player.*|.*{"command":."event\/player.*' }; + $hash->{MatchList} = { "1:HEOSPlayer" => '.*{"command":."player.*|.*{"command":."event\/player.*|.*{"command":."event\/repeat_mode_changed.*|.*{"command":."event\/shuffle_mode_changed.*' }; + - # Consumer $hash->{SetFn} = "HEOSMaster_Set"; #$hash->{GetFn} = "HEOSMaster_Get"; @@ -101,6 +111,7 @@ sub HEOSMaster_Initialize($) { $hash->{UndefFn} = "HEOSMaster_Undef"; $hash->{AttrFn} = "HEOSMaster_Attr"; $hash->{AttrList} = "disable:1 ". + "heosUsername ". $readingFnAttributes; @@ -203,7 +214,8 @@ sub HEOSMaster_Attr(@) { sub HEOSMaster_Set($@) { my ($hash, $name, $cmd, @args) = @_; - my ($arg, @params) = @args; + my ($arg, @params) = @args; + my $name = $hash->{NAME}; my $action; my $heosCmd; @@ -211,8 +223,7 @@ sub HEOSMaster_Set($@) { if($cmd eq 'reopen') { return "usage: reopen" if( @args != 0 ); - HEOSMaster_Close($hash); - HEOSMaster_Open($hash) if( !$hash->{CD} or !defined($hash->{CD}) ); + HEOSMaster_ReOpen($hash); return undef; @@ -222,23 +233,48 @@ sub HEOSMaster_Set($@) { $heosCmd = 'getPlayers'; $action = undef; - return undef; - } elsif($cmd eq 'enableChangeEvents') { return "usage: enableChangeEvents" if( @args != 1 ); $heosCmd = $cmd; $action = $args[0]; - } elsif($cmd eq 'eventSend') { - return "usage: eventSend" if( @args != 0 ); + } elsif($cmd eq 'checkAccount') { + return "usage: checkAccount" if( @args != 0 ); - HEOSMaster_send($hash); - return undef; + $heosCmd = $cmd; + $action = undef; + } elsif($cmd eq 'signAccount') { + return "usage: signAccountIn" if( @args != 1 ); + + $heosCmd = $cmd . $args[0]; + $action = 'un='. AttrVal($name,'heosUsername',0) . '&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 + } elsif($cmd eq 'eventSend') { + return "usage: eventSend" if( @args != 0 ); + + HEOSMaster_send($hash); + return undef; + ################################################### + + } else { my $list = ""; - $list .= "reopen:noArg getPlayers:noArg enableChangeEvents:on,off"; + $list .= "reopen:noArg getPlayers:noArg enableChangeEvents:on,off checkAccount:noArg signAccount:In,Out password reboot"; return "Unknown argument $cmd, choose one of $list"; } @@ -289,6 +325,16 @@ sub HEOSMaster_Close($) { 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) = @_; @@ -335,14 +381,8 @@ sub HEOSMaster_Read($) { return; } - if( $buf !~ m/^[\[{].*[}\]]$/ ) { - Log3 $name, 4, "HEOSMaster ($name) - invalid json detected. start preprocessing"; - HEOSMaster_PreResponseProsessing($hash,$buf); - return; - } - - Log3 $name, 5, "HEOSMaster ($name) - Daten: $buf"; - HEOSMaster_ResponseProcessing($hash,$buf); + Log3 $name, 4, "HEOSMaster ($name) - received buffer data, start preprocessing"; + HEOSMaster_PreResponseProsessing($hash,$buf); } sub HEOSMaster_PreResponseProsessing($$) { @@ -351,7 +391,7 @@ sub HEOSMaster_PreResponseProsessing($$) { my $name = $hash->{NAME}; - Log3 $name, 4, "HEOSMaster ($name) - pre processing respone data"; + Log3 $name, 4, "HEOSMaster ($name) - pre processing response data"; my $len = length($response); my @letterArray = split("",$response); @@ -411,9 +451,10 @@ sub HEOSMaster_ResponseProcessing($$) { HEOSMaster_WriteReadings($hash,$decode_json); Log3 $name, 4, "HEOSMaster ($name) - call Sub HEOSMaster_WriteReadings"; + } - if( $decode_json->{heos}{command} =~ /^player/ or $decode_json->{heos}{command} =~ /^event\/player/ ) { + if( $decode_json->{heos}{command} =~ /^player/ or $decode_json->{heos}{command} =~ /^event\/player/ or $decode_json->{heos}{command} =~ /^event\/repeat_mode_changed/ or $decode_json->{heos}{command} =~ /^event\/shuffle_mode_changed/ ) { if( ref($decode_json->{payload}) eq "ARRAY" and scalar(@{$decode_json->{payload}}) > 0) { foreach my $payload (@{$decode_json->{payload}}) { @@ -454,13 +495,37 @@ sub HEOSMaster_WriteReadings($$) { my $value; - readingsBeginUpdate($hash); - if ( $decode_json->{heos}{command} =~ /register_for_change_events/ ) { - my @value = split('=', $decode_json->{heos}{message}); - $value = $value[1]; - readingsBulkUpdate( $hash, 'enableChangeEvents', "$value" ); - } + ############################ + #### Aufbereiten der Daten soweit nötig + my $readingsHash = HEOSMaster_PreProcessingReadings($hash,$decode_json) + if( $decode_json->{heos}{command} eq 'system/register_for_change_events' + or $decode_json->{heos}{command} eq 'system/check_account' + or $decode_json->{heos}{command} eq 'system/sign_in' + or $decode_json->{heos}{command} eq 'system/sign_out' ); + + + ############################ + #### schreiben der Readings + + readingsBeginUpdate($hash); + + ### 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 ) { + if( defined( $v ) ) { + + readingsBulkUpdate( $hash, $t, $v ); + } + } + } + + readingsBulkUpdate( $hash, "lastCommand", $decode_json->{heos}{command} ); readingsBulkUpdate( $hash, "lastResult", $decode_json->{heos}{result} ); @@ -477,6 +542,45 @@ sub HEOSMaster_WriteReadings($$) { ################### ### my little Helpers +sub HEOSMaster_PreProcessingReadings($$) { + + my ($hash,$decode_json) = @_; + my $name = $hash->{NAME}; + + my $reading; + my %buffer; + + + Log3 $name, 4, "HEOSMaster ($name) - preprocessing readings"; + + + if ( $decode_json->{heos}{command} eq 'system/register_for_change_events' ) { + + my @value = split('=', $decode_json->{heos}{message}); + $buffer{'enableChangeEvents'} = $value[1]; + + } elsif ( $decode_json->{heos}{command} eq 'system/check_account' or $decode_json->{heos}{command} eq 'system/sign_in' ) { + + my @value = split('&', $decode_json->{heos}{message}); + if( $decode_json->{heos}{message} eq 'signed_out' ) { + + $buffer{'heosAccount'} = $value[0]; + + } else { + + $buffer{'heosAccount'} = $value[0] . ' as ' . substr($value[1],3); + } + + } 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; @@ -509,9 +613,84 @@ sub HEOSMaster_EnableChangeEvents($) { RemoveInternalTimer($hash); HEOSMaster_Write($hash,'enableChangeEvents','on'); - Log3 $name, 3, "HEOSMaster ($name) - set enableChangeEvents on"; + Log3 $name, 4, "HEOSMaster ($name) - set enableChangeEvents on"; } +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)); + + return "password successfully saved"; +} + +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"; + + ($err, $password) = getKeyValue($index); + + if ( defined($err) ) { + + Log3 $name, 4, "HEOSMaster ($name) - unable to read FritzBox 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; + } +} + + + + ################ ### Nur für mich um dem Emulator ein Event ab zu jagen sub HEOSMaster_send($) { diff --git a/21_HEOSPlayer.pm b/21_HEOSPlayer.pm index b6e8241..784f44b 100644 --- a/21_HEOSPlayer.pm +++ b/21_HEOSPlayer.pm @@ -33,7 +33,7 @@ use warnings; use JSON; -my $version = "0.1.36"; +my $version = "0.1.42"; @@ -50,6 +50,7 @@ sub HEOSPlayer_PreProcessingReadings($$); sub HEOSPlayer_GetPlayerInfo($); sub HEOSPlayer_GetPlayState($); sub HEOSPlayer_GetNowPlayingMedia($); +sub HEOSPlayer_GetPlayMode($); @@ -59,7 +60,7 @@ sub HEOSPlayer_Initialize($) { my ($hash) = @_; - $hash->{Match} = '.*{"command":."player.*|.*{"command":."event/player.*'; + $hash->{Match} = '.*{"command":."player.*|.*{"command":."event/player.*|.*{"command":."event\/repeat_mode_changed.*|.*{"command":."event\/shuffle_mode_changed.*'; # Provider $hash->{SetFn} = "HEOSPlayer_Set"; @@ -105,7 +106,7 @@ sub HEOSPlayer_Define($$) { $hash->{PID} = $pid; $hash->{VERSION} = $version; - $hash->{STATE} = 'Initialized'; + AssignIoPort($hash,$iodev) if( !$hash->{IODev} ); @@ -133,19 +134,29 @@ sub HEOSPlayer_Define($$) { Log3 $name, 3, "HEOSPlayer ($name) - defined with Code: $code"; - $attr{$name}{room} = "HEOS" if( !defined( $attr{$name}{room} ) ); + $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(5)), "HEOSPlayer_GetPlayerInfo", $hash, 0 ); + InternalTimer( gettimeofday()+int(rand(10)), "HEOSPlayer_GetPlayState", $hash, 0 ); + InternalTimer( gettimeofday()+int(rand(15)), "HEOSPlayer_GetNowPlayingMedia", $hash, 0 ); + InternalTimer( gettimeofday()+int(rand(20)), "HEOSPlayer_GetPlayMode", $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 ); + InternalTimer( gettimeofday()+15+int(rand(5)), "HEOSPlayer_GetPlayerInfo", $hash, 0 ); + InternalTimer( gettimeofday()+15+int(rand(10)), "HEOSPlayer_GetPlayState", $hash, 0 ); + InternalTimer( gettimeofday()+15+int(rand(15)), "HEOSPlayer_GetNowPlayingMedia", $hash, 0 ); + InternalTimer( gettimeofday()+15+int(rand(20)), "HEOSPlayer_GetPlayMode", $hash, 0 ); } - + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, 'state','Initialized'); + readingsBulkUpdate($hash, 'volumeUp', 5); + readingsBulkUpdate($hash, 'volumeDown', 5); + readingsEndUpdate($hash, 1); + + return undef; } @@ -207,23 +218,61 @@ sub HEOSPlayer_Set($$@) { my $pid = $hash->{PID}; my $action; my $heosCmd; + my $rvalue; my $string = "pid=$pid"; if( $cmd eq 'getPlayerInfo' ) { return "usage: getPlayerInfo" if( @args != 0 ); - $heosCmd = 'getPlayerInfo'; + $heosCmd = $cmd; } elsif( $cmd eq 'getPlayState' ) { return "usage: getPlayState" if( @args != 0 ); - $heosCmd = 'getPlayState'; + $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 = 'getNowPlayingMedia'; + $heosCmd = $cmd; + + } elsif( $cmd eq 'repeat' ) { + return "usage: repeat" if( @args != 1 ); + + $heosCmd = 'setPlayMode'; + + if($args[0] eq 'all') { + + $rvalue = 'on_all'; + + } elsif($args[0] eq 'one') { + + $rvalue = 'on_one'; + } + + $action = "repeat=$rvalue&shuffle=" . ReadingsVal($name,'shuffle','off'); + + } elsif( $cmd eq 'shuffle' ) { + return "usage: shuffle" if( @args != 1 ); + + $heosCmd = 'setPlayMode'; + + if(ReadingsVal($name,'repeat','off') eq 'all') { + + $rvalue = 'on_all'; + + } elsif(ReadingsVal($name,'repeat','off') eq 'one') { + + $rvalue = 'on_one'; + } + + $action = "repeat=$rvalue&shuffle=$args[0]"; } elsif( $cmd eq 'play' ) { return "usage: play" if( @args != 0 ); @@ -254,9 +303,21 @@ sub HEOSPlayer_Set($$@) { $heosCmd = 'setVolume'; $action = "level=$args[0]"; + + } elsif( $cmd eq 'volumeUp' ) { + return "usage: volumeUp 0-10" if( @args != 1 ); + + $heosCmd = 'volumeUp'; + $action = "step=$args[0]"; + + } elsif( $cmd eq 'volumeDown' ) { + return "usage: volumeDown 0-10" if( @args != 1 ); + + $heosCmd = 'volumeDown'; + $action = "step=$args[0]"; } else { - my $list = "getPlayerInfo:noArg getPlayState:noArg getNowPlayingMedia:noArg play:noArg stop:noArg pause:noArg mute:on,off volume:slider,0,5,100"; + 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"; return "Unknown argument $cmd, choose one of $list"; } @@ -264,7 +325,7 @@ sub HEOSPlayer_Set($$@) { $string .= "&$action" if( defined($action)); IOWrite($hash,"$heosCmd","$string"); - Log3 $name, 4, "HEOSPlayer ($name) - IOWrite: $heosCmd${string} IODevHash=$hash->{IODev}"; + Log3 $name, 4, "HEOSPlayer ($name) - IOWrite: $heosCmd $string IODevHash=$hash->{IODev}"; return undef; } @@ -290,15 +351,18 @@ sub HEOSPlayer_Parse($$) { my $code = abs($pid); $code = $io_hash->{NAME} ."-". $code if( defined($io_hash->{NAME}) ); + if( my $hash = $modules{HEOSPlayer}{defptr}{$code} ) { + my $name = $hash->{NAME}; - IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}"); - IOWrite($hash,'getPlayState',"pid=$hash->{PID}"); - IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}"); + #IOWrite($hash,'getPlayerInfo',"pid=$hash->{PID}"); + #IOWrite($hash,'getPlayState',"pid=$hash->{PID}"); Erst mal schauen ob es ohne das geht, wenn nicht wieder aktivieren + #IOWrite($hash,'getNowPlayingMedia',"pid=$hash->{PID}"); Log3 $name, 4, "HEOSPlayer ($name) - find logical device: $hash->{NAME}"; - + Log3 $name, 5, "HEOSPlayer ($name) - PID direkt im root von decode_json gefunden"; + return $hash->{NAME}; } else { @@ -317,6 +381,7 @@ sub HEOSPlayer_Parse($$) { $pid = $decode_json->{payload}{pid}; } elsif ( $decode_json->{heos}{message} =~ /^pid=/ ) { + my @pid = split('&', $decode_json->{heos}{message}); $pid = substr($pid[0],4); Log3 $name, 4, "HEOSPlayer ($name) - PID[0]: $pid[0] and PID: $pid"; @@ -420,7 +485,6 @@ sub HEOSPlayer_PreProcessingReadings($$) { my $name = $hash->{NAME}; my $reading; - my $value; my %buffer; @@ -431,16 +495,57 @@ sub HEOSPlayer_PreProcessingReadings($$) { my @value = split('&', $decode_json->{heos}{message}); $buffer{'playStatus'} = substr($value[1],6); - } elsif ( $decode_json->{heos}{command} =~ /set_volume/ ) { + } elsif ( $decode_json->{heos}{command} =~ /volume_changed/ or $decode_json->{heos}{command} =~ /set_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_changed/ ) { + } elsif ( $decode_json->{heos}{command} =~ /play_mode/ ) { my @value = split('&', $decode_json->{heos}{message}); - $buffer{'volume'} = substr($value[1],6); - $buffer{'mute'} = substr($value[2],5); + if(substr($value[1],7) eq 'on_all') { + + $buffer{'repeat'} = 'all'; + + } elsif (substr($value[1],7) eq 'on_one') { + + $buffer{'repeat'} = 'one'; + + } else { + + $buffer{'repeat'} = substr($value[1],7); + } + + $buffer{'shuffle'} = substr($value[2],8); + + } 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} =~ /repeat_mode_changed/ ) { + + my @value = split('&', $decode_json->{heos}{message}); + + if(substr($value[1],7) eq 'on_all') { + + $buffer{'repeat'} = 'all'; + + } elsif (substr($value[1],7) eq 'on_one') { + + $buffer{'repeat'} = 'one'; + + } else { + + $buffer{'repeat'} = substr($value[1],7); + } + + } elsif ( $decode_json->{heos}{command} =~ /shuffle_mode_changed/ ) { + + my @value = split('&', $decode_json->{heos}{message}); + $buffer{'shuffle'} = substr($value[1],8); } elsif ( $decode_json->{heos}{command} =~ /player_now_playing_changed/ ) { @@ -449,6 +554,7 @@ sub HEOSPlayer_PreProcessingReadings($$) { } else { Log3 $name, 3, "HEOSPlayer ($name) - no match found"; + return undef; } @@ -474,6 +580,15 @@ sub HEOSPlayer_GetPlayState($) { } +sub HEOSPlayer_GetPlayMode($) { + + my $hash = shift; + + RemoveInternalTimer($hash,'HEOSPlayer_GetPlayMode'); + IOWrite($hash,'getPlayMode',"pid=$hash->{PID}"); + +} + sub HEOSPlayer_GetNowPlayingMedia($) { my $hash = shift;