diff --git a/fhem/FHEM/70_STV.pm b/fhem/FHEM/70_STV.pm index d0d57abbc..7ee3b0293 100644 --- a/fhem/FHEM/70_STV.pm +++ b/fhem/FHEM/70_STV.pm @@ -29,21 +29,34 @@ ############################################################################## package main; +use strict; +use warnings; use IO::Socket::INET; use Sys::Hostname; use MIME::Base64; +use DevIo; my @gets = ('dummy'); sub STV_Initialize($) { -my ($hash) = @_; - $hash->{DefFn} = "STV_Define"; - $hash->{StateFn} = "STV_SetState"; - $hash->{SetFn} = "STV_Set"; - $hash->{AttrFn} = "STV_Attr"; - $hash->{AttrList} = "MAC"; + my ($hash) = @_; + $hash->{DefFn} = "STV_Define"; + $hash->{UndefFn} = "STV_Undefine"; + $hash->{StateFn} = "STV_SetState"; + $hash->{SetFn} = "STV_Set"; + $hash->{AttrFn} = "STV_Attr"; + $hash->{ReadFn} = "STV_Read"; + $hash->{ReadyFn} = "STV_Ready"; + $hash->{AttrList} = "MAC fork:enable,disable setWhenOffline:execute,ignore " . $readingFnAttributes;; +} + +sub STV_Undefine($$) +{ + my ($hash,$arg) = @_; + DevIo_CloseDev($hash); + return undef; } sub @@ -61,37 +74,93 @@ STV_SetState($$$$) { my ($hash, $tim, $vt, $val) = @_; $val = $1 if($val =~ m/^(.*) \d+$/); - return "Undefined value $val" if(!defined($it_c2b{$val})); +# return "Undefined value $val" if(!defined($it_c2b{$val})); return undef; } sub getIP() { - my $host = hostname(); - my $address = inet_ntoa(scalar gethostbyname(hostname() || 'localhost')); - return "$address"; + my $host = hostname(); + my $address = inet_ntoa(scalar gethostbyname(hostname() || 'localhost')); + return "$address"; +} + +sub STV_Ready($) +{ + my ($hash) = @_; + if(AttrVal($hash->{NAME},'fork','disable') eq 'enable') { + if($hash->{CHILDPID} && !(kill 0, $hash->{CHILDPID})) { + $hash->{CHILDPID} = undef; + return DevIo_OpenDev($hash, 1, "STV_Init"); + } + elsif(!$hash->{CHILDPID}) { + return if($hash->{CHILDPID} = fork); + my $ppid = getppid(); + + ### Copied from Blocking.pm + foreach my $d (sort keys %defs) { # Close all kind of FD + my $h = $defs{$d}; + TcpServer_Close($h) if($h->{SERVERSOCKET}); + if($h->{DeviceName}) { + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + DevIo_CloseDev($h,1); + } + } + ### End of copied from Blocking.pm + + while(kill 0, $ppid) { + DevIo_OpenDev($hash, 1, "STV_ChildExit"); + sleep(5); + } + exit(0); + } + } else { + return DevIo_OpenDev($hash, 1, "STV_Init"); + } + return undef; +} + +sub STV_Read($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + #we dont really expect data here. Its just to gracefully close the device if the connection was closed + my $buf = DevIo_SimpleRead($hash); +} + +sub STV_Init($) +{ + my ($hash) = @_; + return undef; +} + +sub STV_ChildExit($) +{ + exit(0); } sub STV_Define($$) { - my ($hash, $def) = @_; - my @args = split("[ \t]+", $def); + my ($hash, $def) = @_; + DevIo_CloseDev($hash); + my @args = split("[ \t]+", $def); - if (int(@args) < 3) - { - return "Define: not enough arguments. Usage:\n" . + if (int(@args) < 3) + { + return "Define: not enough arguments. Usage:\n" . "define STV "; - } + } - $hash->{Host} = $args[2]; - if (defined $args[3]) { - $hash->{Port} = $args[3] - } else { - $hash->{Port} = 52235; - $hash->{".validcommands"} = "mute volume call sms date"; - } + $hash->{Host} = $args[2]; + if (defined $args[3]) { + $hash->{Port} = $args[3] + } else { + $hash->{Port} = 52235; + $hash->{".validcommands"} = "mute volume call sms date"; + } - if ( $hash->{Port} eq 55000 ){ + if ( $hash->{Port} eq 55000 ){ $hash->{".validcommands"} = "0 1 2 3 4 5 6 7 8 9 UP DOWN LEFT RIGHT ENTER ". "MENU PRECH GUIDE INFO RETURN CH_LIST EXIT ". "SOURCE AD PICTURE_SIZE VOLUP VOLDOWN MUTE ". @@ -99,72 +168,77 @@ sub STV_Define($$) "RSS MTS SRS CAPTION TOPMENU SLEEP ESAVING ". "PLAY PAUSE REWIND FF REC STOP ". "TV HDMI PIP_ONOFF ASPECT EXT20"; - my $system = $^O; - if($system =~ m/Win/) { - $result = `ipconfig /all`; - my @myarp=split(/\n/,$result); - foreach (@myarp){ - if ( /([0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2})$/i ) - { - $result = $1; - $result =~ s/-/:/g; - } - } + my $system = $^O; + my $result; + if($system =~ m/Win/) { + $result = `ipconfig /all`; + my @myarp=split(/\n/,$result); + foreach (@myarp){ + if ( /([0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2})$/i ) + { + $result = $1; + $result =~ s/-/:/g; + } + } + } + if($system eq "linux") { + $result = `ifconfig -a`; + my @myarp=split(/\n/,$result); + foreach (@myarp){ + if ( /^(lan|eth0) .*(..:..:..:..:..:..) .*$/ ) + { + $result = $2; + } + } + } + # Fritzbox "? (192.168.0.1) at 00:0b:5d:91:fc:bb [ether] on lan" + # debian "192.168.0.1 ether c0:25:06:1f:3c:14 C eth0" + #$result = "? (192.168.0.1) at 00:0b:5d:91:fc:bb [ether] on lan"; + + $hash->{MAC} = $result; + $hash->{MyIP} = getIP(); + + $hash->{DeviceName} = $hash->{Host} . ":" . $hash->{Port}; + my $dev = $hash->{DeviceName}; + $readyfnlist{"$args[0].$dev"} = $hash; + } + + if ( $hash->{Port} != 55000 && $hash->{Port} != 52235 ){ + return "[STV] Port is not supported"; } - if($system eq "linux") { - $result = `ifconfig -a`; - my @myarp=split(/\n/,$result); - foreach (@myarp){ - if ( /^(lan|eth0) .*(..:..:..:..:..:..) .*$/ ) - { - $result = $2; - } - } - } - # Fritzbox "? (192.168.0.1) at 00:0b:5d:91:fc:bb [ether] on lan" - # debian "192.168.0.1 ether c0:25:06:1f:3c:14 C eth0" - #$result = "? (192.168.0.1) at 00:0b:5d:91:fc:bb [ether] on lan"; - $hash->{MAC} = $result; - $hash->{MyIP} = getIP(); -} - -if ( $hash->{Port} != 55000 && $hash->{Port} != 52235 ){ - return "[STV] Port is not supported"; -} - - Log3 undef, 3, "[STV] defined with host: $hash->{Host} port: $hash->{Port} MAC: $hash->{MAC}"; - $hash->{STATE} = 'Initialized'; - return undef; + Log3 undef, 3, "[STV] defined with host: $hash->{Host} port: $hash->{Port} MAC: $hash->{MAC}"; + $hash->{STATE} = 'Initialized'; + return undef; } sub connection($$) { - my $tmp = shift ; - Log3 undef, 4, "[STV] connection message: $tmp"; - my $TV = shift; - my $buffer = ""; - my $tmp2 = ""; + my $tmp = shift ; + Log3 undef, 4, "[STV] connection message: $tmp"; + my $TV = shift; + my $buffer = ""; + my @tmp2 = ""; - my $sock = new IO::Socket::INET ( - PeerAddr => $TV, - PeerPort => '52235', - Proto => 'tcp', - Timout => 5 + my $sock = new IO::Socket::INET ( + PeerAddr => $TV, + PeerPort => '52235', + Proto => 'tcp', + Timout => 5 ); - if (defined ($sock)){ - print $sock $tmp; - my $buff =""; - while ((read $sock, $buff, 1) > 0){ - $buffer .= $buff; - } - @tmp2 = split (/\n/,$buffer); + if (defined ($sock)){ + print $sock $tmp; + my $buff =""; + while ((read $sock, $buff, 1) > 0){ + $buffer .= $buff; + } + @tmp2 = split (/\n/,$buffer); - $sock->close(); - Log3 undef, 4, "[STV] $TV: socket closed"; - }else{ - Log3 undef, 4, "[STV] $TV: not able to close socket"; - } + $sock->close(); + Log3 undef, 4, "[STV] $TV: socket closed"; + }else{ + Log3 undef, 4, "[STV] $TV: not able to close socket"; + } } # new Samsung Models @@ -174,7 +248,6 @@ sub STV_55000($$$) my $par=undef; my @ARGV = split(" ",$cmd); #### Configuration - my $name = $hash->{NAME}; my $tv = "UE46ES8090"; # Might need changing to match your TV type #"UE46ES8090" my $port = $hash->{Port}; # TCP port of Samsung TV my $tvip = $hash->{Host}; # IP Address of TV #"192.168.2.124" @@ -186,60 +259,60 @@ sub STV_55000($$$) #### MAC überprüfen wenn nicht gültig vom attribute übernehmen. if ($mymac !~ /^\w\w:\w\w:\w\w:\w\w|\w\w:\w\w:\w\w:\w\w$/) { - Log3 $name, 3, "[STV] mymac: $mymac invalid format"; + Log3 $name, 3, "[STV] mymac: $mymac invalid format"; }else{ - # command-line help - if (!$tv|!$tvip|!$myip|!$mymac) { - return "[STV] Error - Parameter missing:\nmodel, tvip, myip, mymac."; - } - Log3 $name, 5, "[STV] opening socket with tvip: $tvip, cmd: $cmd"; - my $sock = new IO::Socket::INET ( - PeerAddr => $tvip, - PeerPort => $port, - Proto => 'tcp', - Timout => 5 - ); - if (defined ($sock)){ - my $messagepart1 = chr(0x64) . chr(0x00) . chr(length(encode_base64($myip, ""))) . chr(0x00) . encode_base64($myip, "") . chr(length(encode_base64($mymac, ""))) . chr(0x00) . encode_base64($mymac, "") . chr(length(encode_base64($remotename, ""))) . chr(0x00) . encode_base64($remotename, ""); - my $part1 = chr(0x00) . chr(length($appstring)) . chr(0x00) . $appstring . chr(length($messagepart1)) . chr(0x00) . $messagepart1; - print $sock $part1; + # command-line help + if (!$tv|!$tvip|!$myip|!$mymac) { + return "[STV] Error - Parameter missing:\nmodel, tvip, myip, mymac."; + } + Log3 $name, 5, "[STV] opening socket with tvip: $tvip, cmd: $cmd"; + my $sock = new IO::Socket::INET ( + PeerAddr => $tvip, + PeerPort => $port, + Proto => 'tcp', + Timout => 5 + ); + + if (defined ($sock)){ + my $messagepart1 = chr(0x64) . chr(0x00) . chr(length(encode_base64($myip, ""))) . chr(0x00) . encode_base64($myip, "") . chr(length(encode_base64($mymac, ""))) . chr(0x00) . encode_base64($mymac, "") . chr(length(encode_base64($remotename, ""))) . chr(0x00) . encode_base64($remotename, ""); + my $part1 = chr(0x00) . chr(length($appstring)) . chr(0x00) . $appstring . chr(length($messagepart1)) . chr(0x00) . $messagepart1; + print $sock $part1; - my $messagepart2 = chr(0xc8) . chr(0x00); - my $part2 = chr(0x00) . chr(length($appstring)) . chr(0x00) . $appstring . chr(length($messagepart2)) . chr(0x00) . $messagepart2; - print $sock $part2; - # Preceding sections all first time only + my $messagepart2 = chr(0xc8) . chr(0x00); + my $part2 = chr(0x00) . chr(length($appstring)) . chr(0x00) . $appstring . chr(length($messagepart2)) . chr(0x00) . $messagepart2; + print $sock $part2; + # Preceding sections all first time only - if (defined($par)) { - # Send text, e.g. in YouTube app's search, N.B. NOT BBC iPlayer app. - my $text = $par; - my $messagepart3 = chr(0x01) . chr(0x00) . chr(length(encode_base64($text, ""))) . chr(0x00) . encode_base64($text, ""); - my $part3 = chr(0x01) . chr(length($appstring)) . chr(0x00) . $appstring . chr(length($messagepart3)) . chr(0x00) . $messagepart3; - print $sock $part3; - } - else { - foreach my $argnum (0 .. $#ARGV) { - # Send remote key(s) - #Log4 $name, 4, "[STV] sending ".uc($ARGV[$argnum]); - my $key = "KEY_" . uc($ARGV[$argnum]); - my $messagepart3 = chr(0x00) . chr(0x00) . chr(0x00) . chr(length(encode_base64($key, ""))) . chr(0x00) . encode_base64($key, ""); - my $part3 = chr(0x00) . chr(length($tvappstring)) . chr(0x00) . $tvappstring . chr(length($messagepart3)) . chr(0x00) . $messagepart3; - print $sock $part3; - sleep(1); - # select(undef, undef, undef, 0.5); - } - } + if (defined($par)) { + # Send text, e.g. in YouTube app's search, N.B. NOT BBC iPlayer app. + my $text = $par; + my $messagepart3 = chr(0x01) . chr(0x00) . chr(length(encode_base64($text, ""))) . chr(0x00) . encode_base64($text, ""); + my $part3 = chr(0x01) . chr(length($appstring)) . chr(0x00) . $appstring . chr(length($messagepart3)) . chr(0x00) . $messagepart3; + print $sock $part3; + } + else { + foreach my $argnum (0 .. $#ARGV) { + # Send remote key(s) + #Log4 $name, 4, "[STV] sending ".uc($ARGV[$argnum]); + my $key = "KEY_" . uc($ARGV[$argnum]); + my $messagepart3 = chr(0x00) . chr(0x00) . chr(0x00) . chr(length(encode_base64($key, ""))) . chr(0x00) . encode_base64($key, ""); + my $part3 = chr(0x00) . chr(length($tvappstring)) . chr(0x00) . $tvappstring . chr(length($messagepart3)) . chr(0x00) . $messagepart3; + print $sock $part3; + sleep(1); + # select(undef, undef, undef, 0.5); + } + } - close($sock); - }else{ - #Log5 $name, 3, "[STV] Could not create socket. Aborting." unless $sock; - } + close($sock); + }else{ + #Log5 $name, 3, "[STV] Could not create socket. Aborting." unless $sock; + } } } # old Samsung Models -sub STV_52235($@$@) +sub STV_52235($@) { - my ($hash, @a) = @_; my $name = $hash->{NAME}; my $TV = $hash->{Host}; @@ -254,7 +327,7 @@ sub STV_52235($@$@) my $cont7 = ""; my $cont8 = ""; my $cont9 = ""; - + if (defined $a[3]) { $cont2 = $a[3]} if (defined $a[4]) { $cont3 = $a[4]} if (defined $a[5]) { $cont4 = $a[5]} @@ -270,79 +343,79 @@ sub STV_52235($@$@) my $size = ""; my $body = ""; -if ( $arg eq "mute" ) -{ - $kind = 1; - if ( $cont2 eq "off" ){ - $cont2 = 0 ; - }else { - $cont2 = 1 ; - } -} -if ( $arg eq "volume") -{ - if ( $cont2 > 0 and $cont2 < 100 ){ - $kind = 1; - }else { - Log3 $name, 3, "[STV] $name Volume: not correct"; - $kind = 0; - } -} -if ( $arg eq "call") -{ - $kind = 2; - -} -if ( $arg eq "sms") -{ - $kind = 3; - for my $i (6..$count){ - $body .= $a[$i]; - $body .= " "; - } -} -if ( $arg eq "date") -{ - $kind = 4; - for my $i (10..$count){ - $body .= $a[$i]; - $body .= " "; - } -} + if ( $arg eq "mute" ) + { + $kind = 1; + if ( $cont2 eq "off" ){ + $cont2 = 0 ; + }else { + $cont2 = 1 ; + } + } + if ( $arg eq "volume") + { + if ( $cont2 > 0 and $cont2 < 100 ){ + $kind = 1; + }else { + Log3 $name, 3, "[STV] $name Volume: not correct"; + $kind = 0; + } + } + if ( $arg eq "call") + { + $kind = 2; + + } + if ( $arg eq "sms") + { + $kind = 3; + for my $i (6..$count){ + $body .= $a[$i]; + $body .= " "; + } + } + if ( $arg eq "date") + { + $kind = 4; + for my $i (10..$count){ + $body .= $a[$i]; + $body .= " "; + } + } -if ( $kind eq 1){ - $callsoap .= "\r\n"; - $callsoap .= "\r\n"; - $callsoap .= "\r\n"; - $callsoap .= "\r\n"; - $callsoap .= "0\r\n"; - $callsoap .= "$cont2\r\n"; - $callsoap .= "Master\r\n"; - $callsoap .= "\r\n"; - $callsoap .= "\r\n"; - $callsoap .= "\r\n"; + if ( $kind eq 1){ + $callsoap .= "\r\n"; + $callsoap .= "\r\n"; + $callsoap .= "\r\n"; + $callsoap .= "\r\n"; + $callsoap .= "0\r\n"; + $callsoap .= "$cont2\r\n"; + $callsoap .= "Master\r\n"; + $callsoap .= "\r\n"; + $callsoap .= "\r\n"; + $callsoap .= "\r\n"; - $size = length($callsoap); + $size = length($callsoap); - $head .= "POST /upnp/control/RenderingControl1 HTTP/1.1\r\n"; - $head .= "Content-Type: text/xml; charset=\"utf-8\"\r\n"; - $head .= "SOAPACTION: \"SoapAction:urn:schemas-upnp-org:service:RenderingControl:1#Set$cont1\"\r\n"; - $head .= "Cache-Control: no-cache\r\n"; - $head .= "Host: $TV:52235\r\n"; - $head .= "Content-Length: $size\r\n"; - $head .= "Connection: Close\r\n"; - $head .= "\r\n"; + $head .= "POST /upnp/control/RenderingControl1 HTTP/1.1\r\n"; + $head .= "Content-Type: text/xml; charset=\"utf-8\"\r\n"; + $head .= "SOAPACTION: \"SoapAction:urn:schemas-upnp-org:service:RenderingControl:1#Set$cont1\"\r\n"; + $head .= "Cache-Control: no-cache\r\n"; + $head .= "Host: $TV:52235\r\n"; + $head .= "Content-Length: $size\r\n"; + $head .= "Connection: Close\r\n"; + $head .= "\r\n"; - $message .= $head; - $message .= $callsoap; -} + $message .= $head; + $message .= $callsoap; + } -my $calldate=`date +"%Y-%m-%d"`; -chomp($calldate); -my $calltime=`date +"%H:%M:%S"`; -chomp($calltime); + my $calldate=`date +"%Y-%m-%d"`; + chomp($calldate); + my $calltime=`date +"%H:%M:%S"`; + chomp($calltime); -if ( $kind eq 2 ){ # CALL + if ( $kind eq 2 ){ # CALL $callsoap .= "\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; @@ -383,7 +456,7 @@ if ( $kind eq 2 ){ # CALL $message .= $callsoap; } -if ( $kind eq 3 ){ # SMS + if ( $kind eq 3 ){ # SMS $callsoap .= "\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; @@ -405,7 +478,7 @@ if ( $kind eq 3 ){ # SMS $callsoap .= "<Name>Von: $cont2</Name>\r\n"; $callsoap .= "<Number>Nr: $cont3</Number>\r\n"; $callsoap .= "</Sender>\r\n"; - $callsoap .= "<Body>Inhalt: $body</Body>\r\n"; + $callsoap .= "<Body>Inhalt: $body</Body>\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; @@ -425,7 +498,7 @@ if ( $kind eq 3 ){ # SMS $message .= $callsoap; } -if ( $kind eq 4 ){ # Termin + if ( $kind eq 4 ){ # Termin $callsoap .= "\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; @@ -439,11 +512,11 @@ if ( $kind eq 4 ){ # Termin $callsoap .= "<Date>$cont2</Date>\r\n"; $callsoap .= "<Time>$cont3</Time>\r\n"; $callsoap .= "</StartTime>\r\n"; - $callsoap .= "<Owner>\r\n"; - $callsoap .= "<Name>Fr: $cont4</Name>\r\n"; - $callsoap .= "<Number>Nr: $cont5</Number>\r\n"; - $callsoap .= "</Owner>\r\n"; - $callsoap .= "<Subject>Betreff: $cont6</Subject>\r\n"; + $callsoap .= "<Owner>\r\n"; + $callsoap .= "<Name>Fr: $cont4</Name>\r\n"; + $callsoap .= "<Number>Nr: $cont5</Number>\r\n"; + $callsoap .= "</Owner>\r\n"; + $callsoap .= "<Subject>Betreff: $cont6</Subject>\r\n"; $callsoap .= "<EndTime>\r\n"; $callsoap .= "<Date>$cont7</Date>\r\n"; $callsoap .= "<Time>$cont8</Time>\r\n"; @@ -467,13 +540,13 @@ if ( $kind eq 4 ){ # Termin $message .= $head; $message .= $callsoap; -} + } -if ( $kind ne 0 ){ - connection($message, $TV); - }else{ - return "Unknown argument $name, choose one of mute volume call sms date"; - } + if ( $kind ne 0 ){ + connection($message, $TV); + }else{ + return "Unknown argument $name, choose one of mute volume call sms date"; + } } sub STV_Set($@) @@ -488,20 +561,25 @@ sub STV_Set($@) return $hash->{".validcommands"}; } if ($hash->{".validcommands"} =~ /$cmd/) { - if ($Port eq 55000 ){ - STV_55000($hash,$nam,$cmd); - } - if ($Port eq 52235 ){ - STV_52235($hash,@_); - } + if ((AttrVal($name, "setWhenOffline", undef) eq "ignore") and ($hash->{STATE} ne "opened")) { + Log3 $name, 3, "[STV] Device seems offline. Set command ignored: $cmd"; + return; + } + + if ($Port eq 55000 ){ + STV_55000($hash,$nam,$cmd); + } + if ($Port eq 52235 ){ + STV_52235($hash,@_); + } } else { - my $ret = "[STV] Invalid command $cmd. Use any of:\n"; - my @cmds = split(" ",$hash->{".validcommands"}); - foreach my $line (0..$#cmds) { - $ret .= "\n" if ($line > 1 && $line/10 == int($line/10)); - $ret .= $cmds[$line]." "; - } - return $ret; + my $ret = "[STV] Invalid command $cmd. Use any of:\n"; + my @cmds = split(" ",$hash->{".validcommands"}); + foreach my $line (0..$#cmds) { + $ret .= "\n" if ($line > 1 && $line/10 == int($line/10)); + $ret .= $cmds[$line]." "; + } + return $ret; } }