From f3805af574a91596873937dbb6dd4a70191cbbee Mon Sep 17 00:00:00 2001 From: LeonGaultier <LeonGaultier@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:05:19 +0000 Subject: [PATCH] 82_LGTV_WebOS: rewrite and change code, fix bugs of older version git-svn-id: https://svn.fhem.de/fhem/trunk@25432 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/82_LGTV_WebOS.pm | 1336 +++++++++++++++++++----------------- 2 files changed, 710 insertions(+), 627 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 283f525ea..d599318fe 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - bugfix: 82_LGTV_WebOS: rewrite and change code, fix bugs of older version - bugfix: 88_HMCCU: Bugfixes and improvements - bugfix: 73_.*Calculator: bugfix - Correct unit for SymcCounter - bugfix: 82_LGTV_WebOS: fix net socket flapping diff --git a/fhem/FHEM/82_LGTV_WebOS.pm b/fhem/FHEM/82_LGTV_WebOS.pm index 5a77651bd..469142104 100644 --- a/fhem/FHEM/82_LGTV_WebOS.pm +++ b/fhem/FHEM/82_LGTV_WebOS.pm @@ -35,13 +35,6 @@ ## Das JSON Modul immer in einem eval aufrufen # $data = eval{decode_json($data)}; # -# if($@){ -# Log3($SELF, 2, "$TYPE ($SELF) - error while request: $@"); -# -# readingsSingleUpdate($hash, "state", "error", 1); -# -# return; -# } ## ## ## @@ -51,17 +44,24 @@ package main; -my $missingModul = ""; - use strict; use warnings; +use experimental qw /switch/; + use FHEM::Meta; -eval "use MIME::Base64;1" or $missingModul .= "MIME::Base64 "; -eval "use IO::Socket::INET;1" or $missingModul .= "IO::Socket::INET "; -eval "use Digest::SHA qw(sha1_hex);1" or $missingModul .= "Digest::SHA "; -eval "use Encode qw(encode_utf8 decode_utf8);1" or $missingModul .= "Encode "; -eval "use Blocking;1" or $missingModul .= "Blocking "; +my $missingModul = ""; + +eval { require MIME::Base64; 1 } or $missingModul .= "MIME::Base64 "; +eval { require IO::Socket::INET; 1 } or $missingModul .= "IO::Socket::INET "; + +## no critic (Conditional "use" statement. Use "require" to conditionally include a module (Modules::ProhibitConditionalUseStatements)) +eval { use Digest::SHA qw /sha1_hex/; 1 } or $missingModul .= "Digest::SHA "; +eval { use Encode qw /encode_utf8 decode_utf8/; 1 } + or $missingModul .= "Encode "; +## use critic + +eval { require Blocking; 1 } or $missingModul .= "Blocking "; # try to use JSON::MaybeXS wrapper # for chance of better performance + open code @@ -69,15 +69,11 @@ eval { require JSON::MaybeXS; import JSON::MaybeXS qw( decode_json encode_json ); 1; -}; - -if ($@) { - $@ = undef; +} or do { # try to use JSON wrapper # for chance of better performance eval { - # JSON preference order local $ENV{PERL_JSON_BACKEND} = 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' @@ -86,10 +82,7 @@ if ($@) { require JSON; import JSON qw( decode_json encode_json ); 1; - }; - - if ($@) { - $@ = undef; + } or do { # In rare cases, Cpanel::JSON::XS may # be installed but JSON|JSON::MaybeXS not ... @@ -97,10 +90,7 @@ if ($@) { require Cpanel::JSON::XS; import Cpanel::JSON::XS qw(decode_json encode_json); 1; - }; - - if ($@) { - $@ = undef; + } or do { # In rare cases, JSON::XS may # be installed but JSON not ... @@ -108,10 +98,7 @@ if ($@) { require JSON::XS; import JSON::XS qw(decode_json encode_json); 1; - }; - - if ($@) { - $@ = undef; + } or do { # Fallback to built-in JSON which SHOULD # be available since 5.014 ... @@ -119,20 +106,17 @@ if ($@) { require JSON::PP; import JSON::PP qw(decode_json encode_json); 1; - }; - - if ($@) { - $@ = undef; + } or do { # Fallback to JSON::backportPP in really rare cases require JSON::backportPP; import JSON::backportPP qw(decode_json encode_json); 1; - } - } - } - } -} + }; + }; + }; + }; +}; my %lgCommands = ( @@ -206,13 +190,11 @@ my %openApps = ( 'DAZN' => 'dazn', 'SkyQ' => 'com.skygo.app.de.q', 'WaipuTv' => 'tv.waipu.app.waipu-lg', - ); my %openAppsPackageName = reverse %openApps; sub LGTV_WebOS_Initialize { - my ($hash) = @_; # Provider @@ -231,19 +213,22 @@ sub LGTV_WebOS_Initialize { . "wakeOnLanMAC " . "wakeOnLanBroadcast " . "wakeupCmd " + . "keepAliveCheckTime " . $readingFnAttributes; return FHEM::Meta::InitMod( __FILE__, $hash ); } sub LGTV_WebOS_Define { - my ( $hash, $def ) = @_; - - my @a = split( "[ \t][ \t]*", $def ); + my $version; return $@ unless ( FHEM::Meta::SetInternals($hash) ); - use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + + $version = FHEM::Meta::Get( $hash, 'version' ); + our $VERSION = $version; + + my @a = split( "[ \t][ \t]*", $def ); return "too few parameters: define <name> LGTV_WebOS <HOST>" if ( @a != 3 ); return @@ -260,7 +245,7 @@ sub LGTV_WebOS_Define { $hash->{helper}{device}{registered} = 0; $hash->{helper}{device}{runsetcmd} = 0; - Log3 $name, 3, "LGTV_WebOS ($name) - defined with host $host"; + Log3( $name, 3, "LGTV_WebOS ($name) - defined with host $host" ); $attr{$name}{devStateIcon} = 'on:10px-kreis-gruen:off off:10px-kreis-rot:on' if ( !defined( $attr{$name}{devStateIcon} ) ); @@ -278,57 +263,57 @@ sub LGTV_WebOS_Define { "LGTV_WebOS_TimerStatusRequest", $hash ); } + $hash->{helper}->{lastResponse} = + int( gettimeofday() ); # Check Socket KeepAlive + return; } sub LGTV_WebOS_Undef { - my ( $hash, $arg ) = @_; my $host = $hash->{HOST}; my $name = $hash->{NAME}; RemoveInternalTimer($hash); - - LGTV_WebOS_Close($hash); delete $modules{LGTV_WebOS}{defptr}{ $hash->{HOST} }; - - Log3 $name, 3, "LGTV_WebOS ($name) - device $name deleted"; + Log3( $name, 3, "LGTV_WebOS ($name) - device $name deleted" ); return; } sub LGTV_WebOS_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" ) { + if ( $cmd eq "set" && $attrVal eq "1" ) { RemoveInternalTimer($hash); readingsSingleUpdate( $hash, "state", "disabled", 1 ); $hash->{PARTIAL} = ''; - Log3 $name, 3, "LGTV_WebOS ($name) - disabled"; + Log3( $name, 3, "LGTV_WebOS ($name) - disabled" ); } elsif ( $cmd eq "del" ) { readingsSingleUpdate( $hash, "state", "active", 1 ); - Log3 $name, 3, "LGTV_WebOS ($name) - enabled"; + Log3( $name, 3, "LGTV_WebOS ($name) - enabled" ); LGTV_WebOS_TimerStatusRequest($hash); } } if ( $attrName eq "disabledForIntervals" ) { if ( $cmd eq "set" ) { - Log3 $name, 3, "LGTV_WebOS ($name) - enable disabledForIntervals"; + Log3( $name, 3, + "LGTV_WebOS ($name) - enable disabledForIntervals" ); readingsSingleUpdate( $hash, "state", "Unknown", 1 ); } elsif ( $cmd eq "del" ) { readingsSingleUpdate( $hash, "state", "active", 1 ); - Log3 $name, 3, "LGTV_WebOS ($name) - delete disabledForIntervals"; + Log3( $name, 3, + "LGTV_WebOS ($name) - delete disabledForIntervals" ); } } @@ -336,7 +321,6 @@ sub LGTV_WebOS_Attr { } sub LGTV_WebOS_TimerStatusRequest { - my $hash = shift; my $name = $hash->{NAME}; @@ -344,19 +328,19 @@ sub LGTV_WebOS_TimerStatusRequest { readingsBeginUpdate($hash); - if ( !IsDisabled($name) - and $hash->{CD} - and $hash->{helper}{device}{registered} == 1 ) + if ( !IsDisabled($name) + && $hash->{CD} + && $hash->{helper}{device}{registered} == 1 ) { - Log3 $name, 4, "LGTV_WebOS ($name) - run get functions"; + Log3( $name, 4, "LGTV_WebOS ($name) - run get functions" ); LGTV_WebOS_Presence($hash) if ( AttrVal( $name, 'pingPresence', 0 ) == 1 ); - if ( $hash->{helper}{device}{channelguide}{counter} > 2 - and AttrVal( $name, 'channelGuide', 0 ) == 1 - and ReadingsVal( $name, 'launchApp', 'TV' ) eq 'TV' ) + if ( $hash->{helper}{device}{channelguide}{counter} > 2 + && AttrVal( $name, 'channelGuide', 0 ) == 1 + && ReadingsVal( $name, 'launchApp', 'TV' ) eq 'TV' ) { LGTV_WebOS_GetChannelProgramInfo($hash); @@ -407,66 +391,68 @@ sub LGTV_WebOS_TimerStatusRequest { readingsEndUpdate( $hash, 1 ); - LGTV_WebOS_Open($hash) if ( !IsDisabled($name) and not $hash->{CD} ); + LGTV_WebOS_Open($hash) if ( !IsDisabled($name) && !$hash->{CD} ); $hash->{helper}{device}{channelguide}{counter} = $hash->{helper}{device}{channelguide}{counter} + 1; InternalTimer( gettimeofday() + 10, "LGTV_WebOS_TimerStatusRequest", $hash ); + LGTV_WebOS_SocketKeepAlive($hash) + if ( AttrVal( $name, 'keepAliveCheckTime', 0 ) > 0 ) + ; # Check Socket KeepAlive + return; } -sub LGTV_WebOS_Set { - +sub LGTV_WebOS_Set +{ ## no critic (Subroutine "LGTV_WebOS_Set" with high complexity score) my ( $hash, $name, $cmd, @args ) = @_; my ( $arg, @params ) = @args; my $uri; my %payload; - my $inputs; - my @inputs; - if ( $cmd eq 'connect' ) { - return "usage: connect" if ( @args != 0 ); + given ($cmd) { + when ('connect') { + return "usage: connect" if ( @args != 0 ); - LGTV_WebOS_Open($hash); + LGTV_WebOS_Open($hash); - return undef; + return; - } - elsif ( $cmd eq 'clearInputList' ) { - return "usage: clearInputList" if ( @args != 0 ); + } + when ('clearInputList') { + return "usage: clearInputList" if ( @args != 0 ); - delete $hash->{helper}{device}{inputs}; - delete $hash->{helper}{device}{inputapps}; + delete $hash->{helper}{device}{inputs}; + delete $hash->{helper}{device}{inputapps}; - return undef; + return; - } - elsif ( $cmd eq 'pairing' ) { - return "usage: pairing" if ( @args != 0 ); + } + when ('pairing') { + return "usage: pairing" if ( @args != 0 ); - LGTV_WebOS_Pairing($hash); + LGTV_WebOS_Pairing($hash); - return undef; + return; - } - elsif ( $cmd eq 'screenMsg' ) { - return "usage: screenMsg <message>" if ( @args < 1 ); + } + when ('screenMsg') { + return "usage: screenMsg <message>" if ( @args < 1 ); - my $msg = join( " ", @args ); - $payload{ $lgCommands{$cmd}->[1] } = decode_utf8($msg); - $uri = $lgCommands{$cmd}->[0]; + my $msg = join( " ", @args ); + $payload{ $lgCommands{$cmd}->[1] } = decode_utf8($msg); + $uri = $lgCommands{$cmd}->[0]; - } - elsif ( $cmd eq 'on' or $cmd eq 'off' ) { - return "usage: on/off" if ( @args != 0 ); + } + when ('off') { + return "usage: on/off" if ( @args != 0 ); - if ( $cmd eq 'off' ) { $uri = $lgCommands{powerOff}; } - elsif ( $cmd eq 'on' ) { + when ('on') { if ( AttrVal( $name, 'wakeOnLanMAC', 'none' ) ne 'none' ) { LGTV_WebOS_WakeUp_Udp( $hash, @@ -477,15 +463,22 @@ sub LGTV_WebOS_Set { } elsif ( AttrVal( $name, 'wakeupCmd', 'none' ) ne 'none' ) { my $wakeupCmd = AttrVal( $name, 'wakeupCmd', 'none' ); - if ( $wakeupCmd =~ s/^[ \t]*\{|\}[ \t]*$//g ) { - Log3 $name, 4, - "LGTV_WebOS executing wake-up command (Perl): $wakeupCmd"; - eval $wakeupCmd; + if ( $wakeupCmd =~ s/^[ \t]*\{|\}[ \t]*$//xg ) { + Log3( $name, 4, +"LGTV_WebOS executing wake-up command (Perl): $wakeupCmd" + ); + eval { $wakeupCmd } or do { + Log3( $name, 2, +"LGTV_WebOS executing wake-up command (Perl): $wakeupCmd failed" + ); + return; + }; return; } else { - Log3 $name, 4, - "LGTV_WebOS executing wake-up command (fhem): $wakeupCmd"; + Log3( $name, 4, +"LGTV_WebOS executing wake-up command (fhem): $wakeupCmd" + ); fhem $wakeupCmd; return; } @@ -494,210 +487,217 @@ sub LGTV_WebOS_Set { $uri = $lgCommands{powerOn}; } } + when ('3D') { + return "usage: 3D on/off" if ( @args != 1 ); - } - elsif ( $cmd eq '3D' ) { - return "usage: 3D on/off" if ( @args != 1 ); - - if ( $args[0] eq 'off' ) { - $uri = $lgCommands{'3DOff'}; - } - elsif ( $args[0] eq 'on' ) { - $uri = $lgCommands{'3DOn'}; - } - - } - elsif ( $cmd eq 'mute' ) { - return "usage: mute" if ( @args != 1 ); - - if ( $args[0] eq 'off' ) { - - $uri = $lgCommands{volumeDown}->[0]; + if ( $args[0] eq 'off' ) { + $uri = $lgCommands{'3DOff'}; + } + elsif ( $args[0] eq 'on' ) { + $uri = $lgCommands{'3DOn'}; + } } - elsif ( $args[0] eq 'on' ) { + when ('mute') { + return "usage: mute" if ( @args != 1 ); - $payload{ $lgCommands{$cmd}->[1] } = 'true'; + if ( $args[0] eq 'off' ) { + + $uri = $lgCommands{volumeDown}->[0]; + + } + elsif ( $args[0] eq 'on' ) { + + $payload{ $lgCommands{$cmd}->[1] } = 'true'; + $uri = $lgCommands{$cmd}->[0]; + } + + } + when ('volume') { + return "usage: volume" if ( @args != 1 ); + + $payload{ $lgCommands{$cmd}->[1] } = int( join( " ", @args ) ); $uri = $lgCommands{$cmd}->[0]; + } + when ('launchApp') { + return "usage: launchApp" if ( @args != 1 ); - } - elsif ( $cmd eq 'volume' ) { - return "usage: volume" if ( @args != 1 ); + $payload{ $lgCommands{$cmd}->[1] } = + $openApps{ join( " ", @args ) }; + $uri = $lgCommands{$cmd}->[0]; - $payload{ $lgCommands{$cmd}->[1] } = int( join( " ", @args ) ); - $uri = $lgCommands{$cmd}->[0]; + } + when ('input') { + return "usage: input" if ( @args != 1 ); - } - elsif ( $cmd eq 'launchApp' ) { - return "usage: launchApp" if ( @args != 1 ); + my $inputLabel = join( " ", @args ); + $payload{ $lgCommands{launchApp}->[1] } = + $hash->{helper}{device}{inputs}{$inputLabel}; + $uri = $lgCommands{launchApp}->[0]; - $payload{ $lgCommands{$cmd}->[1] } = $openApps{ join( " ", @args ) }; - $uri = $lgCommands{$cmd}->[0]; + } + when ('volumeUp') { + return "usage: volumeUp" if ( @args != 0 ); - } - elsif ( $cmd eq 'input' ) { - return "usage: input" if ( @args != 1 ); + $uri = $lgCommands{$cmd}->[0]; - my $inputLabel = join( " ", @args ); - $payload{ $lgCommands{launchApp}->[1] } = - $hash->{helper}{device}{inputs}{$inputLabel}; - $uri = $lgCommands{launchApp}->[0]; + } + when ('volumeDown') { + return "usage: volumeDown" if ( @args != 0 ); - } - elsif ( $cmd eq 'volumeUp' ) { - return "usage: volumeUp" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('channelDown') { + return "usage: channelDown" if ( @args != 0 ); - } - elsif ( $cmd eq 'volumeDown' ) { - return "usage: volumeDown" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('channelUp') { + return "usage: channelUp" if ( @args != 0 ); - } - elsif ( $cmd eq 'channelDown' ) { - return "usage: channelDown" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('channel') { + return "usage: channel" if ( @args != 1 ); - } - elsif ( $cmd eq 'channelUp' ) { - return "usage: channelUp" if ( @args != 0 ); + $payload{ $lgCommands{openChannel}->[1] } = join( " ", @args ); + $uri = $lgCommands{openChannel}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('getServiceList') { + return "usage: getServiceList" if ( @args != 0 ); - } - elsif ( $cmd eq 'channel' ) { - return "usage: channel" if ( @args != 1 ); + $uri = $lgCommands{$cmd}->[0]; - $payload{ $lgCommands{openChannel}->[1] } = join( " ", @args ); - $uri = $lgCommands{openChannel}->[0]; + } + when ('getChannelList') { + return "usage: getChannelList" if ( @args != 0 ); - } - elsif ( $cmd eq 'getServiceList' ) { - return "usage: getServiceList" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('getAppList') { + return "usage: getAppList" if ( @args != 0 ); - } - elsif ( $cmd eq 'getChannelList' ) { - return "usage: getChannelList" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('getExternalInputList') { + return "usage: getExternalInputList" if ( @args != 0 ); - } - elsif ( $cmd eq 'getAppList' ) { - return "usage: getAppList" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('play') { + return "usage: play" if ( @args != 0 ); - } - elsif ( $cmd eq 'getExternalInputList' ) { - return "usage: getExternalInputList" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('stop') { + return "usage: stop" if ( @args != 0 ); - } - elsif ( $cmd eq 'play' ) { - return "usage: play" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('fastForward') { + return "usage: fastForward" if ( @args != 0 ); - } - elsif ( $cmd eq 'stop' ) { - return "usage: stop" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('rewind') { + return "usage: rewind" if ( @args != 0 ); - } - elsif ( $cmd eq 'fastForward' ) { - return "usage: fastForward" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; + } + when ('pause') { + return "usage: pause" if ( @args != 0 ); - } - elsif ( $cmd eq 'rewind' ) { - return "usage: rewind" if ( @args != 0 ); + $uri = $lgCommands{$cmd}->[0]; - $uri = $lgCommands{$cmd}->[0]; - - } - elsif ( $cmd eq 'pause' ) { - return "usage: pause" if ( @args != 0 ); - - $uri = $lgCommands{$cmd}->[0]; - - } - else { - my $list = ""; - $list .= + } + default { + my $list = ""; + $list .= 'connect:noArg pairing:noArg screenMsg mute:on,off volume:slider,0,1,100 volumeUp:noArg volumeDown:noArg channelDown:noArg channelUp:noArg getServiceList:noArg on:noArg off:noArg'; - $list .= + $list .= ' 3D:on,off stop:noArg play:noArg pause:noArg rewind:noArg fastForward:noArg clearInputList:noArg channel'; - $list .= ' launchApp:' . join( ',', => map qq{$_} => keys %openApps ); - $list .= ' input:' - . join( ',', - => map qq{$_} => keys %{ $hash->{helper}{device}{inputs} } ) - if ( defined( $hash->{helper}{device}{inputs} ) - and ref( $hash->{helper}{device}{inputs} ) eq "HASH" ); + $list .= + ## no critic (Expression form of map. See page 169 of PBP) + ' launchApp:' . join( ',', => map qq{$_} => keys %openApps ); + $list .= ' input:' . join( + ',', + ## no critic (Expression form of map. See page 169 of PBP) + => map qq{$_} => keys %{ $hash->{helper}{device}{inputs} } + ) + if ( defined( $hash->{helper}{device}{inputs} ) + && ref( $hash->{helper}{device}{inputs} ) eq "HASH" ); - return "Unknown argument $cmd, choose one of $list"; + return "Unknown argument $cmd, choose one of $list"; + } } $hash->{helper}{device}{runsetcmd} = $hash->{helper}{device}{runsetcmd} + 1; - LGTV_WebOS_CreateSendCommand( $hash, $uri, \%payload ); - - return; + return LGTV_WebOS_CreateSendCommand( $hash, $uri, \%payload ); } sub LGTV_WebOS_Open { - my $hash = shift; my $name = $hash->{NAME}; my $host = $hash->{HOST}; my $port = 3000; my $timeout = 0.1; - Log3 $name, 4, "LGTV_WebOS ($name) - Baue Socket Verbindung auf"; + Log3( $name, 4, "LGTV_WebOS ($name) - Baue Socket Verbindung auf" ); - my $socket = new IO::Socket::INET( - PeerHost => $host, - PeerPort => $port, - Proto => 'tcp', - Timeout => $timeout + my $socket = IO::Socket::INET->new( + PeerHost => $host, + PeerPort => $port, + Proto => 'tcp', + KeepAlive => 1, + Timeout => $timeout ) - or return Log3 $name, 4, - "LGTV_WebOS ($name) Couldn't connect to $host:$port"; # open Socket + or return Log3( $name, 4, + "LGTV_WebOS ($name) Couldn't connect to $host:$port" ); # open Socket $hash->{FD} = $socket->fileno(); $hash->{CD} = $socket; # sysread / close won't work on fileno $selectlist{$name} = $hash; - Log3 $name, 4, "LGTV_WebOS ($name) - Socket Connected"; + $hash->{helper}->{lastResponse} = + int( gettimeofday() ); # Check Socket KeepAlive + + Log3( $name, 4, "LGTV_WebOS ($name) - Socket Connected" ); LGTV_WebOS_Handshake($hash); - Log3 $name, 4, "LGTV_WebOS ($name) - start Handshake"; + Log3( $name, 4, "LGTV_WebOS ($name) - start Handshake" ); return; } sub LGTV_WebOS_Close { - my $hash = shift; my $name = $hash->{NAME}; return if ( !$hash->{CD} ); + delete( $hash->{PARTIAL} ); + close( $hash->{CD} ) if ( $hash->{CD} ); - delete( $hash->{FD} ); delete( $hash->{CD} ); + delete( $selectlist{$name} ); + delete( $hash->{FD} ); readingsSingleUpdate( $hash, 'state', 'off', 1 ); - Log3 $name, 4, "LGTV_WebOS ($name) - Socket Disconnected"; + Log3( $name, 4, "LGTV_WebOS ($name) - Socket Disconnected" ); return; } @@ -707,30 +707,47 @@ sub LGTV_WebOS_Write { my ( $hash, $string ) = @_; my $name = $hash->{NAME}; - Log3 $name, 4, "LGTV_WebOS ($name) - WriteFn called"; + Log3( $name, 4, "LGTV_WebOS ($name) - WriteFn called" ); - return Log3 $name, 4, "LGTV_WebOS ($name) - socket not connected" + return Log3( $name, 4, "LGTV_WebOS ($name) - socket not connected" ) unless ( $hash->{CD} ); - Log3 $name, 4, "LGTV_WebOS ($name) - $string"; + Log3( $name, 4, "LGTV_WebOS ($name) - $string" ); syswrite( $hash->{CD}, $string ); + return; +} + +sub LGTV_WebOS_SocketKeepAlive { + my $hash = shift; + my $name = $hash->{NAME}; + + if ( + int( gettimeofday() ) - int( $hash->{helper}->{lastResponse} ) > + AttrVal( $name, 'keepAliveCheckTime', 0 ) ) + { + LGTV_WebOS_SocketClosePresenceAbsent( $hash, 'absent' ); + Log3( $name, 4, +"LGTV_WebOS ($name) - KeepAlive It looks like there no Data more response" + ); + } return; } sub LGTV_WebOS_Read { - my $hash = shift; my $name = $hash->{NAME}; my $len; my $buf; - Log3 $name, 4, "LGTV_WebOS ($name) - ReadFn started"; + Log3( $name, 4, "LGTV_WebOS ($name) - ReadFn started" ); + $hash->{helper}->{lastResponse} = + int( gettimeofday() ); # Check Socket KeepAlive $len = sysread( $hash->{CD}, $buf, 10240 ); - if ( !defined($len) or !$len ) { + if ( !defined($len) || !$len ) { LGTV_WebOS_Close($hash); @@ -738,31 +755,36 @@ sub LGTV_WebOS_Read { } unless ( defined $buf ) { - Log3 $name, 3, "LGTV_WebOS ($name) - no data received"; + Log3( $name, 3, "LGTV_WebOS ($name) - no data received" ); return; } - if ( $buf =~ /(\{"type":".+}}$)/ ) { + if ( $buf =~ /(\{"type":".+}}$)/x ) { - $buf =~ /(\{"type":".+}}$)/; + $buf =~ /(\{"type":".+}}$)/x; + ## no critic (Capture variable used outside conditional. See page 253 of PBP) $buf = $1; + ## use critic - Log3 $name, 4, -"LGTV_WebOS ($name) - received correct JSON string, start response processing: $buf"; + Log3( $name, 4, +"LGTV_WebOS ($name) - received correct JSON string, start response processing: $buf" + ); LGTV_WebOS_ResponseProcessing( $hash, $buf ); } - elsif ( $buf =~ /HTTP\/1.1 101 Switching Protocols/ ) { + elsif ( $buf =~ /HTTP\/1.1 101 Switching Protocols/x ) { - Log3 $name, 4, -"LGTV_WebOS ($name) - received HTTP data string, start response processing: $buf"; + Log3( $name, 4, +"LGTV_WebOS ($name) - received HTTP data string, start response processing: $buf" + ); LGTV_WebOS_ResponseProcessing( $hash, $buf ); } else { - Log3 $name, 4, -"LGTV_WebOS ($name) - coruppted data found, run LGTV_WebOS_ProcessRead: $buf"; + Log3( $name, 4, +"LGTV_WebOS ($name) - coruppted data found, run LGTV_WebOS_ProcessRead: $buf" + ); LGTV_WebOS_ProcessRead( $hash, $buf ); } @@ -770,31 +792,30 @@ sub LGTV_WebOS_Read { } sub LGTV_WebOS_ProcessRead { - my ( $hash, $data ) = @_; my $name = $hash->{NAME}; my $buffer = ''; - Log3 $name, 4, "LGTV_WebOS ($name) - process read"; + Log3( $name, 4, "LGTV_WebOS ($name) - process read" ); - if ( defined( $hash->{PARTIAL} ) and $hash->{PARTIAL} ) { + if ( defined( $hash->{PARTIAL} ) && $hash->{PARTIAL} ) { - Log3 $name, 5, "LGTV_WebOS ($name) - PARTIAL: " . $hash->{PARTIAL}; + Log3( $name, 5, "LGTV_WebOS ($name) - PARTIAL: " . $hash->{PARTIAL} ); $buffer = $hash->{PARTIAL}; } else { - Log3 $name, 4, "LGTV_WebOS ($name) - No PARTIAL buffer"; + Log3( $name, 4, "LGTV_WebOS ($name) - No PARTIAL buffer" ); } - Log3 $name, 5, "LGTV_WebOS ($name) - Incoming data: " . $data; + Log3( $name, 5, "LGTV_WebOS ($name) - Incoming data: " . $data ); $buffer = $buffer . $data; - Log3 $name, 5, + Log3( $name, 5, "LGTV_WebOS ($name) - Current processing buffer (PARTIAL + incoming data): " - . $buffer; + . $buffer ); my ( $json, $tail ) = LGTV_WebOS_ParseMsg( $hash, $buffer ); @@ -802,46 +823,45 @@ sub LGTV_WebOS_ProcessRead { $hash->{LAST_RECV} = time(); - Log3 $name, 5, - "LGTV_WebOS ($name) - Decoding JSON message. Length: " - . length($json) - . " Content: " - . $json; - Log3 $name, 5, - "LGTV_WebOS ($name) - Vor Sub: Laenge JSON: " - . length($json) - . " Content: " - . $json - . " Tail: " - . $tail; + Log3( $name, 5, + "LGTV_WebOS ($name) - Decoding JSON message. Length: " + . length($json) + . " Content: " + . $json ); + Log3( $name, 5, + "LGTV_WebOS ($name) - Vor Sub: Laenge JSON: " + . length($json) + . " Content: " + . $json + . " Tail: " + . $tail ); LGTV_WebOS_ResponseProcessing( $hash, $json ) - unless ( not defined($tail) and not($tail) ); + if ( defined($tail) && ($tail) ); ( $json, $tail ) = LGTV_WebOS_ParseMsg( $hash, $tail ); - Log3 $name, 5, - "LGTV_WebOS ($name) - Nach Sub: Laenge JSON: " - . length($json) - . " Content: " - . $json - . " Tail: " - . $tail; + Log3( $name, 5, + "LGTV_WebOS ($name) - Nach Sub: Laenge JSON: " + . length($json) + . " Content: " + . $json + . " Tail: " + . $tail ); } $tail = '' if ( length($tail) > 30000 ); $hash->{PARTIAL} = $tail; - Log3 $name, 4, "LGTV_WebOS ($name) - PARTIAL lenght: " . length($tail); + Log3( $name, 4, "LGTV_WebOS ($name) - PARTIAL lenght: " . length($tail) ); - Log3 $name, 5, "LGTV_WebOS ($name) - Tail: " . $tail; - Log3 $name, 5, "LGTV_WebOS ($name) - PARTIAL: " . $hash->{PARTIAL}; + Log3( $name, 5, "LGTV_WebOS ($name) - Tail: " . $tail ); + Log3( $name, 5, "LGTV_WebOS ($name) - PARTIAL: " . $hash->{PARTIAL} ); return; } sub LGTV_WebOS_Handshake { - my $hash = shift; my $name = $hash->{NAME}; my $host = $hash->{HOST}; @@ -860,25 +880,24 @@ sub LGTV_WebOS_Handshake { $hash->{helper}{wsKey} = $wsKey; - Log3 $name, 4, "LGTV_WebOS ($name) - send Handshake to WriteFn"; + Log3( $name, 4, "LGTV_WebOS ($name) - send Handshake to WriteFn" ); LGTV_WebOS_TimerStatusRequest($hash); - Log3 $name, 4, "LGTV_WebOS ($name) - start timer status request"; + Log3( $name, 4, "LGTV_WebOS ($name) - start timer status request" ); LGTV_WebOS_Pairing($hash); - Log3 $name, 4, "LGTV_WebOS ($name) - start pairing routine"; + Log3( $name, 4, "LGTV_WebOS ($name) - start pairing routine" ); return; } sub LGTV_WebOS_ResponseProcessing { - my ( $hash, $response ) = @_; my $name = $hash->{NAME}; ######################## ### Response has HTML Header - if ( $response =~ /HTTP\/1.1 101 Switching Protocols/ ) { + if ( $response =~ /HTTP\/1.1 101 Switching Protocols/x ) { my $data = $response; my $header = LGTV_WebOS_Header2Hash($data); @@ -888,7 +907,7 @@ sub LGTV_WebOS_ResponseProcessing { if ( defined( $header->{'Sec-WebSocket-Accept'} ) ) { my $keyAccept = $header->{'Sec-WebSocket-Accept'}; - Log3 $name, 5, "LGTV_WebOS ($name) - keyAccept: $keyAccept"; + Log3( $name, 5, "LGTV_WebOS ($name) - keyAccept: $keyAccept" ); my $wsKey = $hash->{helper}{wsKey}; my $expectedResponse = trim( @@ -905,313 +924,362 @@ sub LGTV_WebOS_ResponseProcessing { if ( $keyAccept eq $expectedResponse ) { - Log3 $name, 3, -"LGTV_WebOS ($name) - Sucessfull WS connection to $hash->{HOST}"; + Log3( $name, 3, +"LGTV_WebOS ($name) - Sucessfull WS connection to $hash->{HOST}" + ); readingsSingleUpdate( $hash, 'state', 'on', 1 ); } else { LGTV_WebOS_Close($hash); - Log3 $name, 3, -"LGTV_WebOS ($name) - ERROR: Unsucessfull WS connection to $hash->{HOST}"; + Log3( $name, 3, +"LGTV_WebOS ($name) - ERROR: Unsucessfull WS connection to $hash->{HOST}" + ); } } - return undef; + return; } - elsif ( $response =~ m/^{"type":".+}}$/ ) { + elsif ( $response =~ m/^{"type":".+}}$/x ) { - return Log3 $name, 4, "LGTV_WebOS ($name) - garbage after JSON object" - if ( $response =~ m/^{"type":".+}}.+{"type":".+/ ); + return Log3( $name, 4, + "LGTV_WebOS ($name) - garbage after JSON object" ) + if ( $response =~ m/^{"type":".+}}.+{"type":".+/x ); - Log3 $name, 4, - "LGTV_WebOS ($name) - JSON detected, run LGTV_WebOS_WriteReadings"; + Log3( $name, 4, + "LGTV_WebOS ($name) - JSON detected, run LGTV_WebOS_WriteReadings" + ); my $json = $response; - Log3 $name, 4, "LGTV_WebOS ($name) - Corrected JSON String: $json" + Log3( $name, 4, "LGTV_WebOS ($name) - Corrected JSON String: $json" ) if ($json); - if ( not defined($json) or not($json) ) { + if ( !defined($json) || !($json) ) { - Log3 $name, 4, "LGTV_WebOS ($name) - Corrected JSON String empty"; + Log3( $name, 4, + "LGTV_WebOS ($name) - Corrected JSON String empty" ); return; } my $decode_json = eval { decode_json( encode_utf8($json) ) }; if ($@) { - Log3 $name, 3, "LGTV_WebOS ($name) - JSON error while request: $@"; + Log3( $name, 3, + "LGTV_WebOS ($name) - JSON error while request: $@" ); return; } LGTV_WebOS_WriteReadings( $hash, $decode_json ); - return undef; + return; } - Log3 $name, 4, "LGTV_WebOS ($name) - no Match found"; + Log3( $name, 4, "LGTV_WebOS ($name) - no Match found" ); return; } -sub LGTV_WebOS_WriteReadings { +sub LGTV_WebOS_WriteServiceReadings { + my $hash = shift; + my $decode_json = shift; - my ( $hash, $decode_json ) = @_; - - my $name = $hash->{NAME}; - my $mute; - my $response; - my %channelList; - - Log3 $name, 4, "LGTV_WebOS ($name) - Beginn Readings writing"; - - readingsBeginUpdate($hash); - - if ( ref( $decode_json->{payload}{services} ) eq "ARRAY" - and scalar( @{ $decode_json->{payload}{services} } ) > 0 ) - { - foreach my $services ( @{ $decode_json->{payload}{services} } ) { - - readingsBulkUpdateIfChanged( - $hash, - 'service_' . $services->{name}, - 'v.' . $services->{version} - ); - } + for my $services ( @{ $decode_json->{payload}{services} } ) { + readingsBulkUpdateIfChanged( + $hash, + 'service_' . $services->{name}, + 'v.' . $services->{version} + ); } - elsif ( ref( $decode_json->{payload}{devices} ) eq "ARRAY" - and scalar( @{ $decode_json->{payload}{devices} } ) > 0 ) - { + return; +} - foreach my $devices ( @{ $decode_json->{payload}{devices} } ) { +sub LGTV_WebOS_WriteDeviceReadings { + my $hash = shift; + my $decode_json = shift; - if ( - not - defined( $hash->{helper}{device}{inputs}{ $devices->{label} } ) - or not defined( - $hash->{helper}{device}{inputapps}{ $devices->{appId} } - ) - ) - { + for my $devices ( @{ $decode_json->{payload}{devices} } ) { - $hash->{helper}{device}{inputs}{ $devices->{label} } = - $devices->{appId}; - $hash->{helper}{device}{inputapps}{ $devices->{appId} } = - $devices->{label}; - } + if ( !defined( $hash->{helper}{device}{inputs}{ $devices->{label} } ) + || ! + defined( $hash->{helper}{device}{inputapps}{ $devices->{appId} } ) ) + { - readingsBulkUpdateIfChanged( - $hash, - 'extInput_' . $devices->{label}, - 'connect_' . $devices->{connected} - ); + $hash->{helper}{device}{inputs} + { makeDeviceName( $devices->{label} ) } = $devices->{appId}; + $hash->{helper}{device}{inputapps}{ $devices->{appId} } = + makeDeviceName( $devices->{label} ); } + + readingsBulkUpdateIfChanged( + $hash, + 'extInput_' . makeDeviceName( $devices->{label} ), + 'connect_' . $devices->{connected} + ); } - elsif ( ref( $decode_json->{payload}{programList} ) eq "ARRAY" - and scalar( @{ $decode_json->{payload}{programList} } ) > 0 ) - { - use Date::Parse; - my $count = 0; - foreach my $programList ( @{ $decode_json->{payload}{programList} } ) { + return; +} - if ( - str2time( +sub LGTV_WebOS_WriteProgramlistReadings { + my $hash = shift; + my $decode_json = shift; + + require Date::Parse; + my $count = 0; + for my $programList ( @{ $decode_json->{payload}{programList} } ) { + + if ( + str2time( + LGTV_WebOS_FormartStartEndTime( $programList->{localEndTime} ) + ) > time() + ) + { + if ( $count < 1 ) { + + readingsBulkUpdateIfChanged( $hash, 'channelCurrentTitle', + $programList->{programName} ); + readingsBulkUpdateIfChanged( + $hash, + 'channelCurrentStartTime', + LGTV_WebOS_FormartStartEndTime( + $programList->{localStartTime} + ) + ); + readingsBulkUpdateIfChanged( + $hash, + 'channelCurrentEndTime', LGTV_WebOS_FormartStartEndTime( $programList->{localEndTime} ) - ) > time() - ) - { - if ( $count < 1 ) { + ); - readingsBulkUpdateIfChanged( $hash, 'channelCurrentTitle', - $programList->{programName} ); - readingsBulkUpdateIfChanged( - $hash, - 'channelCurrentStartTime', - LGTV_WebOS_FormartStartEndTime( - $programList->{localStartTime} - ) - ); - readingsBulkUpdateIfChanged( - $hash, - 'channelCurrentEndTime', - LGTV_WebOS_FormartStartEndTime( - $programList->{localEndTime} - ) - ); - - } - elsif ( $count < 2 ) { - - readingsBulkUpdateIfChanged( $hash, 'channelNextTitle', - $programList->{programName} ); - readingsBulkUpdateIfChanged( - $hash, - 'channelNextStartTime', - LGTV_WebOS_FormartStartEndTime( - $programList->{localStartTime} - ) - ); - readingsBulkUpdateIfChanged( - $hash, - 'channelNextEndTime', - LGTV_WebOS_FormartStartEndTime( - $programList->{localEndTime} - ) - ); - } - - $count++; - return if ( $count > 1 ); } + elsif ( $count < 2 ) { + + readingsBulkUpdateIfChanged( $hash, 'channelNextTitle', + $programList->{programName} ); + readingsBulkUpdateIfChanged( + $hash, + 'channelNextStartTime', + LGTV_WebOS_FormartStartEndTime( + $programList->{localStartTime} + ) + ); + readingsBulkUpdateIfChanged( + $hash, + 'channelNextEndTime', + LGTV_WebOS_FormartStartEndTime( + $programList->{localEndTime} + ) + ); + } + + $count++; + return if ( $count > 1 ); } } - elsif (defined( $decode_json->{payload}{'mute'} ) - or defined( $decode_json->{payload}{'muted'} ) ) + return; +} + +sub LGTV_WebOS_WriteMuteReadings { + my $hash = shift; + my $decode_json = shift; + + if ( + defined( $decode_json->{payload}{'mute'} ) + && ( $decode_json->{payload}{'mute'} eq 'true' + || $decode_json->{payload}{'mute'} == 1 ) + ) { - if ( - defined( $decode_json->{payload}{'mute'} ) - and ( $decode_json->{payload}{'mute'} eq 'true' - or $decode_json->{payload}{'mute'} == 1 ) - ) - { + readingsBulkUpdateIfChanged( $hash, 'mute', 'on' ); - readingsBulkUpdateIfChanged( $hash, 'mute', 'on' ); - - } - elsif ( defined( $decode_json->{payload}{'mute'} ) ) { - if ( $decode_json->{payload}{'mute'} eq 'false' - or $decode_json->{payload}{'mute'} == 0 ) - { - - readingsBulkUpdateIfChanged( $hash, 'mute', 'off' ); - } - } - - if ( - defined( $decode_json->{payload}{'muted'} ) - and ( $decode_json->{payload}{'muted'} eq 'true' - or $decode_json->{payload}{'muted'} == 1 ) - ) - { - - readingsBulkUpdateIfChanged( $hash, 'mute', 'on' ); - - } - elsif ( - defined( $decode_json->{payload}{'muted'} ) - and ( $decode_json->{payload}{'muted'} eq 'false' - or $decode_json->{payload}{'muted'} == 0 ) - ) + } + elsif ( defined( $decode_json->{payload}{'mute'} ) ) { + if ( $decode_json->{payload}{'mute'} eq 'false' + || $decode_json->{payload}{'mute'} == 0 ) { readingsBulkUpdateIfChanged( $hash, 'mute', 'off' ); } } - elsif ( defined( $decode_json->{payload}{status3D}{status} ) ) { - if ( $decode_json->{payload}{status3D}{status} eq 'false' - or $decode_json->{payload}{status3D}{status} == 0 ) - { + if ( + defined( $decode_json->{payload}{'muted'} ) + && ( $decode_json->{payload}{'muted'} eq 'true' + || $decode_json->{payload}{'muted'} == 1 ) + ) + { - readingsBulkUpdateIfChanged( $hash, '3D', 'off' ); + readingsBulkUpdateIfChanged( $hash, 'mute', 'on' ); - } - elsif ($decode_json->{payload}{status3D}{status} eq 'true' - or $decode_json->{payload}{status3D}{status} == 1 ) - { + } + elsif ( + defined( $decode_json->{payload}{'muted'} ) + && ( $decode_json->{payload}{'muted'} eq 'false' + || $decode_json->{payload}{'muted'} == 0 ) + ) + { - readingsBulkUpdateIfChanged( $hash, '3D', 'on' ); - } - - readingsBulkUpdateIfChanged( $hash, '3DMode', - $decode_json->{payload}{status3D}{pattern} ); + readingsBulkUpdateIfChanged( $hash, 'mute', 'off' ); } - elsif ( defined( $decode_json->{payload}{appId} ) ) { + return; +} - if ( - ( - $decode_json->{payload}{appId} =~ /com.webos.app.externalinput/ - or $decode_json->{payload}{appId} =~ /com.webos.app.hdmi/ - ) - and defined( - $hash->{helper}{device}{inputapps} - { $decode_json->{payload}{appId} } - ) - and +sub LGTV_WebOS_Write3dReadings { + my $hash = shift; + my $decode_json = shift; + + if ( $decode_json->{payload}{status3D}{status} eq 'false' + || $decode_json->{payload}{status3D}{status} == 0 ) + { + + readingsBulkUpdateIfChanged( $hash, '3D', 'off' ); + + } + elsif ($decode_json->{payload}{status3D}{status} eq 'true' + || $decode_json->{payload}{status3D}{status} == 1 ) + { + + readingsBulkUpdateIfChanged( $hash, '3D', 'on' ); + } + + readingsBulkUpdateIfChanged( $hash, '3DMode', + $decode_json->{payload}{status3D}{pattern} ); + + return; +} + +sub LGTV_WebOS_WriteAppIdReadings { + my $hash = shift; + my $decode_json = shift; + + if ( + ( + $decode_json->{payload}{appId} =~ /com.webos.app.externalinput/x + || $decode_json->{payload}{appId} =~ /com.webos.app.hdmi/x + ) + && defined( $hash->{helper}{device}{inputapps}{ $decode_json->{payload}{appId} } - ) + ) + && $hash->{helper}{device}{inputapps}{ $decode_json->{payload}{appId} } + ) + { + + readingsBulkUpdateIfChanged( $hash, 'input', + $hash->{helper}{device}{inputapps}{ $decode_json->{payload}{appId} } + ); + readingsBulkUpdateIfChanged( $hash, 'launchApp', '-' ); + + } + elsif ( defined( $openAppsPackageName{ $decode_json->{payload}{appId} } ) + && $openAppsPackageName{ $decode_json->{payload}{appId} } ) + { + + readingsBulkUpdateIfChanged( $hash, 'launchApp', + $openAppsPackageName{ $decode_json->{payload}{appId} } ); + readingsBulkUpdateIfChanged( $hash, 'input', '-' ); + } + + return; +} + +sub LGTV_WebOS_WriteTypeReadings { + my $hash = shift; + my $decode_json = shift; + + my $response; + + if ( $decode_json->{type} eq 'registered' + && defined( $decode_json->{payload}{'client-key'} ) ) + { + + $hash->{helper}{device}{registered} = 1; + + } + elsif ( + ( + $decode_json->{type} eq 'response' + && ( $decode_json->{payload}{returnValue} eq 'true' + || $decode_json->{payload}{returnValue} == 1 ) + ) + || ( $decode_json->{type} eq 'registered' ) + && defined( $decode_json->{payload}{'client-key'} ) + ) + { + + $response = 'ok'; + readingsBulkUpdateIfChanged( $hash, 'pairing', 'paired' ); + $hash->{helper}{device}{runsetcmd} = + $hash->{helper}{device}{runsetcmd} - 1 + if ( $hash->{helper}{device}{runsetcmd} > 0 ); + + } + elsif ( $decode_json->{type} eq 'error' ) { + + $response = "error - $decode_json->{error}" + if ( $decode_json->{error} ne '404 no such service or method' ); + + if ( $decode_json->{error} eq '401 insufficient permissions' + || $decode_json->{error} eq + '401 insufficient permissions (not registered)' ) { - readingsBulkUpdateIfChanged( $hash, 'input', - $hash->{helper}{device}{inputapps} - { $decode_json->{payload}{appId} } ); - readingsBulkUpdateIfChanged( $hash, 'launchApp', '-' ); - - } - elsif ( - defined( $openAppsPackageName{ $decode_json->{payload}{appId} } ) - and $openAppsPackageName{ $decode_json->{payload}{appId} } ) - { - - readingsBulkUpdateIfChanged( $hash, 'launchApp', - $openAppsPackageName{ $decode_json->{payload}{appId} } ); - readingsBulkUpdateIfChanged( $hash, 'input', '-' ); + readingsBulkUpdateIfChanged( $hash, 'pairing', 'unpaired' ); } + $hash->{helper}{device}{runsetcmd} = + $hash->{helper}{device}{runsetcmd} - 1 + if ( $hash->{helper}{device}{runsetcmd} > 0 ); + } + + return $response; +} + +sub LGTV_WebOS_WriteReadings { + my ( $hash, $decode_json ) = @_; + + my $name = $hash->{NAME}; + my $response; + + Log3( $name, 4, "LGTV_WebOS ($name) - Beginn Readings writing" ); + + readingsBeginUpdate($hash); + + if ( ref( $decode_json->{payload}{services} ) eq "ARRAY" + && scalar( @{ $decode_json->{payload}{services} } ) > 0 ) + { + LGTV_WebOS_WriteServiceReadings( $hash, $decode_json ); + } + elsif ( ref( $decode_json->{payload}{devices} ) eq "ARRAY" + && scalar( @{ $decode_json->{payload}{devices} } ) > 0 ) + { + LGTV_WebOS_WriteDeviceReadings( $hash, $decode_json ); + } + elsif ( ref( $decode_json->{payload}{programList} ) eq "ARRAY" + && scalar( @{ $decode_json->{payload}{programList} } ) > 0 ) + { + LGTV_WebOS_WriteProgramlistReadings( $hash, $decode_json ); + } + + if ( defined( $decode_json->{payload}{'mute'} ) + || defined( $decode_json->{payload}{'muted'} ) ) + { + LGTV_WebOS_WriteMuteReadings( $hash, $decode_json ); + } + elsif ( defined( $decode_json->{payload}{status3D}{status} ) ) { + LGTV_WebOS_Write3dReadings( $hash, $decode_json ); + } + elsif ( defined( $decode_json->{payload}{appId} ) ) { + LGTV_WebOS_WriteAppIdReadings( $hash, $decode_json ); } if ( defined( $decode_json->{type} ) ) { - - if ( $decode_json->{type} eq 'registered' - and defined( $decode_json->{payload}{'client-key'} ) ) - { - - $hash->{helper}{device}{registered} = 1; - - } - elsif ( - ( - $decode_json->{type} eq 'response' - and ( $decode_json->{payload}{returnValue} eq 'true' - or $decode_json->{payload}{returnValue} == 1 ) - ) - or ( $decode_json->{type} eq 'registered' ) - and defined( $decode_json->{payload}{'client-key'} ) - ) - { - - $response = 'ok'; - readingsBulkUpdateIfChanged( $hash, 'pairing', 'paired' ); - $hash->{helper}{device}{runsetcmd} = - $hash->{helper}{device}{runsetcmd} - 1 - if ( $hash->{helper}{device}{runsetcmd} > 0 ); - - } - elsif ( $decode_json->{type} eq 'error' ) { - - $response = "error - $decode_json->{error}" - if ( $decode_json->{error} ne '404 no such service or method' ); - - if ( $decode_json->{error} eq '401 insufficient permissions' - or $decode_json->{error} eq - '401 insufficient permissions (not registered)' ) - { - - readingsBulkUpdateIfChanged( $hash, 'pairing', 'unpaired' ); - } - - $hash->{helper}{device}{runsetcmd} = - $hash->{helper}{device}{runsetcmd} - 1 - if ( $hash->{helper}{device}{runsetcmd} > 0 ); - } + $response = LGTV_WebOS_WriteTypeReadings( $hash, $decode_json ); } readingsBulkUpdateIfChanged( $hash, 'lgKey', @@ -1257,13 +1325,10 @@ sub LGTV_WebOS_WriteReadings { } sub LGTV_WebOS_Pairing { - my $hash = shift; my $name = $hash->{NAME}; - my $lgKey; - - Log3 $name, 4, "LGTV_WebOS ($name) - HASH handshakePayload"; + Log3( $name, 4, "LGTV_WebOS ($name) - HASH handshakePayload" ); my %handshakePayload = ( "pairingType" => "PROMPT", @@ -1334,19 +1399,17 @@ sub LGTV_WebOS_Pairing { $usedHandshake->{'client-key'} = $key if ( defined($key) ); LGTV_WebOS_CreateSendCommand( $hash, undef, $usedHandshake, 'register' ); - Log3 $name, 4, "LGTV_WebOS ($name) - Send pairing informations"; + Log3( $name, 4, "LGTV_WebOS ($name) - Send pairing informations" ); return; } sub LGTV_WebOS_CreateSendCommand { - my ( $hash, $uri, $payload, $type ) = @_; my $name = $hash->{NAME}; - my $err; - $type = 'request' if ( not defined($type) ); + $type = 'request' if ( !defined($type) ); my $command = {}; $command->{'client-key'} = ReadingsVal( $name, 'lgKey', '' ) @@ -1356,11 +1419,11 @@ sub LGTV_WebOS_CreateSendCommand { $command->{uri} = $uri if ($uri); $command->{payload} = $payload if ( defined($payload) ); -#Log3 $name, 5, "LGTV_WebOS ($name) - Payload Message: $command->{payload}{message}"; +#Log3( $name, 5, "LGTV_WebOS ($name) - Payload Message: $command->{payload}{message}" ); my $cmd = encode_json($command); - Log3 $name, 5, "LGTV_WebOS ($name) - Sending command: $cmd"; + Log3( $name, 5, "LGTV_WebOS ($name) - Sending command: $cmd" ); LGTV_WebOS_Write( $hash, LGTV_WebOS_Hybi10Encode( $cmd, "text", 1 ) ); @@ -1368,38 +1431,35 @@ sub LGTV_WebOS_CreateSendCommand { } sub LGTV_WebOS_Hybi10Encode { - - my ( $payload, $type, $masked ) = @_; - - $type //= "text"; - $masked //= 1; + my $payload = shift; + my $type = shift // 'text'; + my $masked = shift // 1; my @frameHead; my $frame = ""; my $payloadLength = length($payload); - if ( $type eq "text" ) { + given ($type) { + when ('text') { - # first byte indicates FIN, Text-Frame (10000001): - $frameHead[0] = 129; + # first byte indicates FIN, Text-Frame (10000001): + $frameHead[0] = 129; + } + when ('close') { - } - elsif ( $type eq "close" ) { + # first byte indicates FIN, Close Frame(10001000): + $frameHead[0] = 136; + } + when ('ping') { - # first byte indicates FIN, Close Frame(10001000): - $frameHead[0] = 136; + # first byte indicates FIN, Ping frame (10001001): + $frameHead[0] = 137; + } + when ('pong') { - } - elsif ( $type eq "ping" ) { - - # first byte indicates FIN, Ping frame (10001001): - $frameHead[0] = 137; - - } - elsif ( $type eq "pong" ) { - - # first byte indicates FIN, Pong frame (10001010): - $frameHead[0] = 138; + # first byte indicates FIN, Pong frame (10001010): + $frameHead[0] = 138; + } } # set mask and payload length (using 1, 3 or 9 bytes) @@ -1419,7 +1479,7 @@ sub LGTV_WebOS_Hybi10Encode { if ( $frameHead[2] > 127 ) { #$this->close(1004); - return undef; + return; } } @@ -1469,12 +1529,12 @@ sub LGTV_WebOS_Hybi10Encode { } sub LGTV_WebOS_GetAudioStatus { - my $hash = shift; my $name = $hash->{NAME}; - Log3 $name, 4, "LGTV_WebOS ($name) - LGTV_WebOS_GetAudioStatus: " - . $hash->{helper}{device}{runsetcmd}; + Log3( $name, 4, + "LGTV_WebOS ($name) - LGTV_WebOS_GetAudioStatus: " + . $hash->{helper}{device}{runsetcmd} ); LGTV_WebOS_CreateSendCommand( $hash, $lgCommands{getAudioStatus}, undef ) if ( $hash->{helper}{device}{runsetcmd} == 0 ); @@ -1482,13 +1542,13 @@ sub LGTV_WebOS_GetAudioStatus { } sub LGTV_WebOS_GetCurrentChannel { - my $hash = shift; my $name = $hash->{NAME}; RemoveInternalTimer( $hash, 'LGTV_WebOS_GetCurrentChannel' ); - Log3 $name, 4, "LGTV_WebOS ($name) - LGTV_WebOS_GetCurrentChannel: " - . $hash->{helper}{device}{runsetcmd}; + Log3( $name, 4, + "LGTV_WebOS ($name) - LGTV_WebOS_GetCurrentChannel: " + . $hash->{helper}{device}{runsetcmd} ); LGTV_WebOS_CreateSendCommand( $hash, $lgCommands{getCurrentChannel}, undef ) if ( $hash->{helper}{device}{runsetcmd} == 0 ); @@ -1496,13 +1556,13 @@ sub LGTV_WebOS_GetCurrentChannel { } sub LGTV_WebOS_GetForgroundAppInfo { - my $hash = shift; my $name = $hash->{NAME}; RemoveInternalTimer( $hash, 'LGTV_WebOS_GetForgroundAppInfo' ); - Log3 $name, 4, "LGTV_WebOS ($name) - LGTV_WebOS_GetForgroundAppInfo: " - . $hash->{helper}{device}{runsetcmd}; + Log3( $name, 4, + "LGTV_WebOS ($name) - LGTV_WebOS_GetForgroundAppInfo: " + . $hash->{helper}{device}{runsetcmd} ); LGTV_WebOS_CreateSendCommand( $hash, $lgCommands{getForegroundAppInfo}, undef ) if ( $hash->{helper}{device}{runsetcmd} == 0 ); @@ -1511,13 +1571,13 @@ sub LGTV_WebOS_GetForgroundAppInfo { } sub LGTV_WebOS_GetExternalInputList { - my $hash = shift; my $name = $hash->{NAME}; RemoveInternalTimer( $hash, 'LGTV_WebOS_GetExternalInputList' ); - Log3 $name, 4, "LGTV_WebOS ($name) - LGTV_WebOS_GetExternalInputList: " - . $hash->{helper}{device}{runsetcmd}; + Log3( $name, 4, + "LGTV_WebOS ($name) - LGTV_WebOS_GetExternalInputList: " + . $hash->{helper}{device}{runsetcmd} ); LGTV_WebOS_CreateSendCommand( $hash, $lgCommands{getExternalInputList}, undef ) if ( $hash->{helper}{device}{runsetcmd} == 0 ); @@ -1526,13 +1586,13 @@ sub LGTV_WebOS_GetExternalInputList { } sub LGTV_WebOS_Get3DStatus { - my $hash = shift; my $name = $hash->{NAME}; RemoveInternalTimer( $hash, 'LGTV_WebOS_Get3DStatus' ); - Log3 $name, 4, "LGTV_WebOS ($name) - LGTV_WebOS_Get3DStatus: " - . $hash->{helper}{device}{runsetcmd}; + Log3( $name, 4, + "LGTV_WebOS ($name) - LGTV_WebOS_Get3DStatus: " + . $hash->{helper}{device}{runsetcmd} ); LGTV_WebOS_CreateSendCommand( $hash, $lgCommands{get3DStatus}, undef ) if ( $hash->{helper}{device}{runsetcmd} == 0 ); @@ -1540,12 +1600,12 @@ sub LGTV_WebOS_Get3DStatus { } sub LGTV_WebOS_GetChannelProgramInfo { - my $hash = shift; my $name = $hash->{NAME}; - Log3 $name, 4, "LGTV_WebOS ($name) - LGTV_WebOS_GetChannelProgramInfo: " - . $hash->{helper}{device}{runsetcmd}; + Log3( $name, 4, + "LGTV_WebOS ($name) - LGTV_WebOS_GetChannelProgramInfo: " + . $hash->{helper}{device}{runsetcmd} ); LGTV_WebOS_CreateSendCommand( $hash, $lgCommands{getChannelProgramInfo}, undef ) if ( $hash->{helper}{device}{runsetcmd} == 0 ); @@ -1557,67 +1617,67 @@ sub LGTV_WebOS_GetChannelProgramInfo { ### my little Helper sub LGTV_WebOS_ParseMsg { + my $hash = shift; + my $buffer = shift; - my ( $hash, $buffer ) = @_; - - my $name = $hash->{NAME}; - my $open = 0; - my $close = 0; - my $msg = ''; - my $tail = ''; + my $name = $hash->{NAME}; + my $jsonopen = 0; + my $jsonclose = 0; + my $msg = ''; + my $tail = ''; if ($buffer) { - foreach my $c ( split //, $buffer ) { - if ( $open == $close && $open > 0 ) { + for my $c ( split //, $buffer ) { + if ( $jsonopen == $jsonclose && $jsonopen > 0 ) { $tail .= $c; - Log3 $name, 5, - "LGTV_WebOS ($name) - $open == $close && $open > 0"; + Log3( $name, 5, +"LGTV_WebOS ($name) - $jsonopen == $jsonclose && $jsonopen > 0" + ); } - elsif ( ( $open == $close ) && ( $c ne '{' ) ) { + elsif ( ( $jsonopen == $jsonclose ) && ( $c ne '{' ) ) { - Log3 $name, 5, - "LGTV_WebOS ($name) - Garbage character before message: " - . $c; + Log3( $name, 5, + "LGTV_WebOS ($name) - Garbage character before message: " + . $c ); } else { if ( $c eq '{' ) { - $open++; + $jsonopen++; } elsif ( $c eq '}' ) { - $close++; + $jsonclose++; } $msg .= $c; } } - if ( $open != $close ) { + if ( $jsonopen != $jsonclose ) { $tail = $msg; $msg = ''; } } - Log3 $name, 5, "LGTV_WebOS ($name) - return msg: $msg and tail: $tail"; + Log3( $name, 5, "LGTV_WebOS ($name) - return msg: $msg and tail: $tail" ); return ( $msg, $tail ); } sub LGTV_WebOS_Header2Hash { - my $string = shift; my %hash = (); - foreach my $line ( split( "\r\n", $string ) ) { + for my $line ( split( "\r\n", $string ) ) { my ( $key, $value ) = split( ": ", $line ); next if ( !$value ); - $value =~ s/^ //; + $value =~ s/^ //x; $hash{$key} = $value; } @@ -1625,7 +1685,6 @@ sub LGTV_WebOS_Header2Hash { } sub LGTV_WebOS_FormartStartEndTime { - my $string = shift; my @timeArray = split( ',', $string ); @@ -1636,7 +1695,6 @@ sub LGTV_WebOS_FormartStartEndTime { ############ Presence Erkennung Begin ################# sub LGTV_WebOS_Presence { - my $hash = shift; my $name = $hash->{NAME}; @@ -1649,27 +1707,26 @@ sub LGTV_WebOS_Presence { } sub LGTV_WebOS_PresenceRun { - my $string = shift; my ( $name, $host ) = split( "\\|", $string ); my $tmp; my $response; - $tmp = qx(ping -c 3 -w 2 $host 2>&1); + $tmp = qx(ping -c 3 -w 2 $host 2>&1); ## no critic (Backtick operator used) - if ( defined($tmp) and $tmp ne "" ) { + if ( defined($tmp) && $tmp ne "" ) { chomp $tmp; - Log3 $name, 5, - "LGTV_WebOS ($name) - ping command returned with output:\n$tmp"; - $response = "$name|" - . ( - ( - $tmp =~ /\d+ [Bb]ytes (from|von)/ - and not $tmp =~ /[Uu]nreachable/ - ) ? "present" : "absent" - ); + Log3( $name, 4, + "LGTV_WebOS ($name) - ping command returned with output:\n$tmp" ); + $response = $name . '|' . ( + $tmp =~ + /\d+ [Bb]ytes (from|von)/ ## no critic (Regular expression without "/x") + && $tmp !~ /[Uu]nreachable/x + ? 'present' + : 'absent' + ); } else { @@ -1677,56 +1734,58 @@ sub LGTV_WebOS_PresenceRun { $response = "$name|Could not execute ping command"; } - Log3 $name, 4, -"Sub LGTV_WebOS_PresenceRun ($name) - Sub finish, Call LGTV_WebOS_PresenceDone"; + Log3( $name, 4, +"Sub LGTV_WebOS_PresenceRun ($name) - Sub finish, Call LGTV_WebOS_PresenceDone" + ); + return $response; } sub LGTV_WebOS_PresenceDone { - - my ($string) = @_; + my $string = shift; my ( $name, $response ) = split( "\\|", $string ); my $hash = $defs{$name}; delete( $hash->{helper}{RUNNING_PID} ); - Log3 $name, 4, + Log3( $name, 4, "Sub LGTV_WebOS_PresenceDone ($name) - Helper is disabled. Stop processing" - if ( $hash->{helper}{DISABLED} ); + ) if ( $hash->{helper}{DISABLED} ); return if ( $hash->{helper}{DISABLED} ); readingsSingleUpdate( $hash, 'presence', $response, 1 ); LGTV_WebOS_SocketClosePresenceAbsent( $hash, $response ); - Log3 $name, 4, "Sub LGTV_WebOS_PresenceDone ($name) - presence done"; + Log3( $name, 4, "Sub LGTV_WebOS_PresenceDone ($name) - presence done" ); return; } sub LGTV_WebOS_PresenceAborted { - - my ($hash) = @_; + my $hash = shift; my $name = $hash->{NAME}; delete( $hash->{helper}{RUNNING_PID} ); readingsSingleUpdate( $hash, 'presence', 'pingPresence timedout', 1 ); - Log3 $name, 4, -"Sub LGTV_WebOS_PresenceAborted ($name) - The BlockingCall Process terminated unexpectedly. Timedout!"; + Log3( $name, 4, +"Sub LGTV_WebOS_PresenceAborted ($name) - The BlockingCall Process terminated unexpectedly. Timedout!" + ); return; } sub LGTV_WebOS_SocketClosePresenceAbsent { - my ( $hash, $presence ) = @_; + my $hash = shift; + my $presence = shift; my $name = $hash->{NAME}; LGTV_WebOS_Close($hash) - if ( $presence eq 'absent' and not IsDisabled($name) and $hash->{CD} ) + if ( $presence eq 'absent' && !IsDisabled($name) && $hash->{CD} ) ; # https://forum.fhem.de/index.php/topic,66671.msg694578.html#msg694578 # Sobald pingPresence absent meldet und der Socket noch steht soll er geschlossen werden, da sonst FHEM nach 4-6 min für 10 min blockiert @@ -1734,27 +1793,27 @@ sub LGTV_WebOS_SocketClosePresenceAbsent { } sub LGTV_WebOS_WakeUp_Udp { - my ( $hash, $mac_addr, $host, $port ) = @_; my $name = $hash->{NAME}; - $port = 9 if ( !defined $port || $port !~ /^\d+$/ ); + $port = 9 if ( !defined $port || $port !~ /^\d+$/x ); - my $sock = new IO::Socket::INET( Proto => 'udp' ) or die "socket : $!"; + my $sock = IO::Socket::INET->new( Proto => 'udp' ) or warn "socket : $!\n"; if ( !$sock ) { - Log3 $name, 3, - "Sub LGTV_WebOS_WakeUp_Udp ($name) - Can't create WOL socket"; + Log3( $name, 3, + "Sub LGTV_WebOS_WakeUp_Udp ($name) - Can't create WOL socket" ); return 1; } my $ip_addr = inet_aton($host); my $sock_addr = sockaddr_in( $port, $ip_addr ); - $mac_addr =~ s/://g; + $mac_addr =~ s/://xg; my $packet = pack( 'C6H*', 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, $mac_addr x 16 ); - setsockopt( $sock, SOL_SOCKET, SO_BROADCAST, 1 ) or die "setsockopt : $!"; - send( $sock, $packet, 0, $sock_addr ) or die "send : $!"; + setsockopt( $sock, SOL_SOCKET, SO_BROADCAST, 1 ) + or warn "setsockopt : $!\n"; + send( $sock, $packet, 0, $sock_addr ) or warn "send : $!\n"; close($sock); return 1; @@ -1853,6 +1912,12 @@ sub LGTV_WebOS_WakeUp_Udp { current state of ping presence from TV. create a reading presence with values absent or present. </ul> </ul> + <ul> + <ul> + <li>keepAliveCheckTime</li> + value in seconds - keepAliveCheck is check read data input from tcp socket and prevented FHEM freeze. + </ul> + </ul> <ul> <ul> <li>wakeOnLanMAC</li> @@ -1865,7 +1930,7 @@ sub LGTV_WebOS_WakeUp_Udp { Broadcast Address of the Network - wakeOnLanBroadcast <network>.255 </ul> </ul> - <ul> + <ul> <ul> <li>wakeupCmd</li> Set a command to be executed when turning on an absent device. Can be an FHEM command or Perl command in {}. @@ -1978,14 +2043,31 @@ sub LGTV_WebOS_WakeUp_Udp { </ul> </ul> </ul> - <ul> + <ul> + <ul> + <ul> + <li>pingPresence</li> + Mögliche Werte: 0 => presence via ping deaktivert, 1 => presence via ping aktiviert + </ul> + </ul> + </ul> + <ul> + <ul> + <ul> + <li>keepAliveCheckTime</li> + Wert in Sekunden - keepAliveCheckTime + kontrolliert in einer bestimmten Zeit ob noch Daten über die TCP Schnittstelle kommen und verhindert somit FHEM Freezes + </ul> + </ul> + </ul> + <ul> <ul> <ul> <li>wakeupCmd</li> Befehl zum Einschalten des LG TV. Möglich ist ein FHEM Befehl oder Perl in {}. </ul> </ul> - </ul> + </ul> <p><br /><br /><strong>Generierte Readings/Events:</strong></p> <ul> <ul> @@ -2032,7 +2114,7 @@ sub LGTV_WebOS_WakeUp_Udp { ], "release_status": "stable", "license": "GPL_2", - "version": "v3.3.0", + "version": "v3.4.2", "author": [ "Marko Oldenburg <fhemdevelopment@cooltux.net>" ],