############################################################################## # # 70_SamsungAV.pm # # module to send messages or commands to a Samsung TV # # written 2019 by kölnsolar # based on 70_STV which supports older generation of TV's. Thanks to Zwiebel. # extended for newer moduls. Python script samsungctl was used as reference. # # $Id$ # # Version = 1.0 # ############################################################################## package main; use strict; use warnings; use IO::Socket::INET; use Sys::Hostname; use MIME::Base64; use DevIo; use HttpUtils; my @gets = ('dummy'); sub SamsungAV_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "SamsungAV_Define"; $hash->{UndefFn} = "SamsungAV_Undefine"; $hash->{StateFn} = "SamsungAV_SetState"; $hash->{SetFn} = "SamsungAV_Set"; $hash->{AttrFn} = "SamsungAV_Attr"; $hash->{NotifyFn} = "SamsungAV_Notify"; $hash->{ReadFn} = "SamsungAV_Read"; $hash->{ReadyFn} = "SamsungAV_Ready"; $hash->{AttrList} = "callerURI screenURI delayRC delayMacro disable:0,1 " . $readingFnAttributes;; $data{RC_layout}{SamsungAV_TV} = "SamsungAV_RClayout_TV"; $data{RC_layout}{SamsungAV_TV_SVG} = "SamsungAV_RClayout_TV_SVG"; $data{RC_makenotify}{SamsungAV} = "SamsungAV_RCmakenotify"; } sub SamsungAV_Define($$) { my ($hash, $def) = @_; # DevIo_CloseDev($hash); my @args = split("[ \t]+", $def); if (int(@args) < 4) { return "[SamsungAV] Define: not enough arguments. Usage:\n" . "define SamsungAV "; } $hash->{Host} = $args[2]; $hash->{Port} = $args[3]; if ( $hash->{Port} ne "55000" && $hash->{Port} ne "52235" && $hash->{Port} ne "8000" && $hash->{Port} ne "8001" ){ return "[SamsungAV] Port is not supported"; } if (defined $args[4]) { $hash->{Mode} = $args[4]; if (!defined ReadingsVal($args[4],"state",undef)) { return "[SamsungAV] Define: optional DLNA device not known. $hash->{Mode} instead" } $hash->{".validcommands"} .= "volume:slider,0,1,100 sayText "; } else { $hash->{Mode} = undef } if ( $hash->{Port} eq 55000 || $hash->{Port} eq "8000" || $hash->{Port} eq "8001"){ $hash->{".validcommands"} .= "0:noArg 1:noArg 2:noArg 3:noArg 4:noArg 5:noArg 6:noArg 7:noArg 8:noArg 9:noArg ". "ad:noArg aspect:noArg av1:noArg av2:noArg channel:selectnumbers,0,1,99,0,lin channelDown:noArg channelUp:noArg channelList:noArg clear:noArg component1:noArg component2:noArg ". "contents:noArg convergence:noArg cyan:noArg down:noArg enter:noArg esaving:noArg exit:noArg ff:noArg guide:noArg green:noArg hdmi:noArg hdmi1:noArg ". "hdmi2:noArg help:noArg home:noArg info:noArg left:noArg menu:noArg mute:noArg pause:noArg pip_chdown:noArg pip_chup:noArg pip_onoff:noArg play:noArg ". "pmode:noArg power:noArg poweroff:noArg poweron:noArg prech:noArg program:noArg red:noArg return:noArg rec:noArg rewind:noArg right:noArg sleep:noArg ". "source:noArg stop:noArg tools:noArg ttx_mix:noArg tv:noArg tv_mode:noArg up:noArg volumeDown:noArg volumeUp:noArg yellow:noArg statusRequest:noArg ". "0_text_line 0_macro ". "G_AUTO_:AUTO_ARC_ANTENNA_AIR,AUTO_ARC_ANTENNA_CABLE,AUTO_ARC_ANTENNA_SATELLITE,AUTO_ARC_ANYNET_AUTO_START,AUTO_ARC_ANYNET_MODE_OK,". "AUTO_ARC_AUTOCOLOR_FAIL,AUTO_ARC_AUTOCOLOR_SUCCESS,AUTO_ARC_CAPTION_ENG,AUTO_ARC_CAPTION_KOR,AUTO_ARC_CAPTION_OFF,AUTO_ARC_CAPTION_ON,". "AUTO_ARC_C_FORCE_AGING,AUTO_ARC_JACK_IDENT,AUTO_ARC_LNA_OFF,AUTO_ARC_LNA_ON,AUTO_ARC_PIP_CH_CHANGE,AUTO_ARC_PIP_DOUBLE,AUTO_ARC_PIP_LARGE,". "AUTO_ARC_PIP_LEFT_BOTTOM,AUTO_ARC_PIP_LEFT_TOP,AUTO_ARC_PIP_RIGHT_BOTTOM,AUTO_ARC_PIP_RIGHT_TOP,AUTO_ARC_PIP_SMALL,AUTO_ARC_PIP_SOURCE_CHANGE,". "AUTO_ARC_PIP_WIDE,AUTO_ARC_RESET,AUTO_ARC_USBJACK_INSPECT,AUTO_FORMAT,AUTO_PROGRAM ". "G_EXTx:EXT1,EXT2,EXT3,EXT4,EXT5,EXT6,EXT7,EXT8,EXT9,EXT10,EXT11,EXT12,EXT13,EXT14,EXT15,EXT16,EXT17,EXT18,EXT19,EXT20,EXT21,EXT22,EXT23,". "EXT24,EXT25,EXT26,EXT27,EXT28,EXT29,EXT30,EXT31,EXT32,EXT33,EXT34,EXT35,EXT36,EXT37,EXT38,EXT39,EXT40,EXT41 ". "G_Others:3SPEED,4_3,16_9,ADDDEL,ALT_MHP,ANGLE,ANTENA,ANYNET,ANYVIEW,APP_LIST,AV3,BACK_MHP,BOOKMARK,CALLER_ID,CAPTION,CATV_MODE,". "CLOCK_DISPLAY,CONVERT_AUDIO_MAINSUB,CUSTOM,DEVICE_CONNECT,DISC_MENU,DMA,DNET,DNIe,DNSe,DOOR,DSS_MODE,DTV,DTV_LINK,DTV_SIGNAL,". "DVD_MODE,DVI,DVR,DVR_MENU,DYNAMIC,ENTERTAINMENT,FACTORY,FAVCH,FF_,FM_RADIO,GAME,HDMI3,HDMI4,ID_INPUT,ID_SETUP,INSTANT_REPLAY,LINK,". "LIVE,MAGIC_BRIGHT,MAGIC_CHANNEL,MDC,MIC,MORE,MOVIE1,MS,MTS,NINE_SEPERATE,OPEN,PANNEL_CHDOWN,PANNEL_CHUP,PANNEL_ENTER,PANNEL_MENU,". "PANNEL_POWER,PANNEL_SOURCE,PANNEL_VOLDOW,PANNEL_VOLUP,PANORAMA,PCMODE,PERPECT_FOCUS,PICTURE_SIZE,". "PIP_SCAN,PIP_SIZE,PIP_SWAP,PLUS100,POWER,PRINT,QUICK_REPLAY,REC,REPEAT,RESERVED1,REWIND_,RSS,RSURF,SCALE,SEFFECT,". "SETUP_CLOCK_TIMER,SOUND_MODE,SOURCE,SRS,STANDARD,STB_MODE,STILL_PICTURE,SUB_TITLE,SVIDEO1,SVIDEO2,SVIDEO3,TOPMENU,TTX_SUBFACE,". "TURBO,VCHIP,VCR_MODE,WHEEL_LEFT,WHEEL_RIGHT,W_LINK,ZOOM1,ZOOM2,ZOOM_IN,ZOOM_MOVE,ZOOM_OUT "; if($hash->{Port} ne "8000" && $hash->{Port} ne "8001") { my $rc = eval { require Net::Address::IP::Local; require IO::Interface::Simple; Net::Address::IP::Local->import(); IO::Interface::Simple->import(); 1; }; if($rc) { $hash->{MyIP} = getIP(); $hash->{MAC} = getMAC4IP($hash->{MyIP}); } else{ Log3 $args[0], 3, "[SamsungAV] $args[0] You are using a deprecated MAC detection mechanism using ifconfig."; Log3 $args[0], 3, "[SamsungAV] $args[0] Please install Pearl Modules libnet-address-ip-local-perl and libio-interface-perl"; 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 eth0`; my @myarp=split(/\n/,$result); foreach (@myarp){ if ( /(ether|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_old(); } } else { eval {require JSON; 1;} or do { return "[SamsungAV] Define: Module JSON not installed. Run 'sudo apt-get install libjson-perl'"; }; eval {require Time::HiRes; 1;} or do { return "[SamsungAV] Define: Module Time:HiRes not installed. Check perl libraries'"; }; # import Time::HiRes qw(usleep); } my $dev = $hash->{DeviceName}; } else { $hash->{".validcommands"} = "mute:on,off volume:slider,0,1,100 call sms date "; } $hash->{".validcommands"} .= "caller:noArg " if (defined(AttrVal($args[0], "callerURI", undef))); $hash->{".validcommands"} .= "screen:noArg " if (defined(AttrVal($args[0], "screenURI", undef))); Log3 $args[0], 3, "[SamsungAV] $args[0] defined with host: $hash->{Host} port: $hash->{Port}"; readingsSingleUpdate($hash,"state","defined",1); return undef; } sub SamsungAV_Undefine($$) { my ($hash,$arg) = @_; RemoveInternalTimer($hash); return undef; } sub SamsungAV_Attr(@) { my @a = @_; my $hash = $defs{$a[1]}; my $cmd = $a[0]; my $attrName = $a[2]; my $attrParm = $a[3]; my $mac = AttrVal($a[1], "MAC", undef); $hash->{MAC} = $mac if (defined($mac)); if( defined(index($attrName,"URI"))) { if( $cmd eq "set" ) { $hash->{".validcommands"} .= "caller:noArg " if ($attrName eq "callerURI"); $hash->{".validcommands"} .= "screen:noArg " if ($attrName eq "screenURI"); } } if($cmd eq "set" && $attrName eq "disable") { if($attrParm eq "0") { readingsSingleUpdate($hash, "state", "defined",0) if(exists($hash->{helper}{DISABLED}) && $hash->{helper}{DISABLED} == 1); SamsungAV_Init($hash); } elsif($attrParm eq "1") { RemoveInternalTimer($hash); readingsSingleUpdate($hash, "presence", "absent",1); readingsSingleUpdate($hash, "state", "disabled",1); } $hash->{helper}{DISABLED} = $attrParm; } elsif($cmd eq "del" && $attrName eq "disable") { readingsSingleUpdate($hash, "state", "defined",0) if(exists($hash->{helper}{DISABLED}) && $hash->{helper}{DISABLED} == 1); SamsungAV_Init($hash); $hash->{helper}{DISABLED} = 0; } return undef; } sub SamsungAV_Init($) { my ($hash) = @_; SamsungAV_State($hash); RemoveInternalTimer($hash, "SamsungAV_Init"); InternalTimer(gettimeofday()+60, "SamsungAV_Init", $hash, 0); } sub SamsungAV_State($) { my ($hash) = @_; my $name = $hash->{NAME}; my $port = $hash->{Port}; my $param = { url => "http://$hash->{Host}:$port/", timeout => 5, hash => $hash, method => "GET", header => "", callback => \&SamsungAV_ParseState }; HttpUtils_NonblockingGet($param); } sub SamsungAV_ParseState($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; # Log3 $name, 1, "[SamsungAV] $name parameter - code: $param->{code} - error: $err"; my ($state, $stateAV, $presence) = ""; $param->{code} = "" if(!defined($param->{code})); if($err ne "" && index($err,"empty") < 0) { # workaround for C/D/E/F-Series still to figure out http server Port 7676(ne "" $param->{code} ne "40") Log3 $name, 4, "[SamsungAV] $name not able to connect to $hash->{Host}:$hash->{Port} with $param->{url} - code: $param->{code} - error: $err"; $state = $stateAV = $presence = "absent"; } else { Log3 $name, 4, "[SamsungAV] $name online with $hash->{Host}:$hash->{Port} - HTTP-Response: $param->{code}"; $state = $stateAV = "on"; $presence = "present"; } readingsBeginUpdate($hash); readingsBulkUpdateIfChanged($hash, "state", $state,1); if (defined($hash->{Mode})) { my $mute = ReadingsVal($hash->{Mode},"mute","not found"); if($mute eq "1") {$mute = "on"} elsif ($mute eq "0") {$mute = "off"} if ($presence ne "absent") { my $DLNAstate = ReadingsVal($hash->{Mode},"state","not found"); if ($mute eq "on") { $stateAV = "muted" } elsif ($DLNAstate ne "online") { $stateAV = $DLNAstate } } readingsBulkUpdateIfChanged($hash, "stateAV", $stateAV); readingsBulkUpdateIfChanged($hash, "presence", $presence); readingsBulkUpdateIfChanged($hash, "mute", $mute); readingsBulkUpdateIfChanged($hash, "volume", ReadingsVal($hash->{Mode},"volume","not found")); readingsBulkUpdateIfChanged($hash, "friendlyName", ReadingsVal($hash->{Mode},"friendlyName","not found")); readingsBulkUpdateIfChanged($hash, "modelName", ReadingsVal($hash->{Mode},"modelName","not found")); } readingsEndUpdate($hash, 1); } sub SamsungAV_Notify($$) { my ($hash,$dev) = @_; return undef if(!defined($hash) or !defined($dev)); my $name = $hash->{NAME}; my $dev_name = $dev->{NAME}; return undef if(!defined($dev_name) || !defined($name)); my $events = deviceEvents($dev,1); if($dev_name eq "global" && grep(m/^INITIALIZED|REREADCFG$/, @{$events})) { if(!exists($hash->{helper}{DISABLED}) || $hash->{helper}{DISABLED} == 0) { Log3 $name, 3, "[SamsungAV] device $name initialising...."; SamsungAV_Init($hash); SamsungAV_Tizen_App_list($hash) if ($hash->{Port} ne "8000"); } } } sub SamsungAV_Ready($) { my ($hash) = @_; if(AttrVal($hash->{NAME},'fork','disable') eq 'enable') { } else { Log3 $hash->{NAME}, 5, "[SamsungAV] $hash->{NAME} SamsungAV_Ready(connection seems to be closed) OpenDev for DeviceName: $hash->{DeviceName}"; } return undef; } sub SamsungAV_SetState($$$$) { my ($hash, $tim, $vt, $val) = @_; $val = $1 if($val =~ m/^(.*) \d+$/); # return "Undefined value $val" if(!defined($it_c2b{$val})); return undef; } sub SamsungAV_Read($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $name, 3, "[SamsungAV] $name SamsungAV_Read: connection DeviceName $hash->{DeviceName} may be closed"; } sub SamsungAV_Set($@) { my ($hash, @a) = @_; my $nam = shift @a; my $name = $hash->{NAME}; my $Port = $hash->{Port}; my $Mode = $hash->{Mode}; my $cmd = (defined($a[0]) ? shift @a : ""); #command my $par = (defined($a[0]) ? shift @a : ""); #1 parameter my %translate = ("channelDown" => "CHDOWN", "channelUp" => "CHUP", "channelList" => "CH_LIST", "volumeDown" => "VOLDOWN", "volumeUp" => "VOLUP" ); if ($cmd eq "?" || $cmd eq "") { return $hash->{".validcommands"}; } $cmd = $par if (substr($cmd,0,2) eq "G_" ); if ($hash->{".validcommands"} =~ /$cmd/) { $cmd = $translate{$cmd} if (defined($translate{$cmd})); if ($cmd eq "statusRequest") { SamsungAV_Tizen_App_list($hash) if ($hash->{Port} ne "8000"); return SamsungAV_State($hash); # query connection state } # screen message via DLNA caller or individual return "$name currently not available. Try later. " if (ReadingsVal($name,"state","not found") ne "on" && !($cmd eq "power" || $cmd eq "poweron" )); # screen message, absolute volume, TTS via DLNARenderer if (defined $Mode) { my $command = ""; if ($cmd eq "caller" || $cmd eq "screen") { my $URI =""; if ( $cmd ne "caller" ) {$URI = AttrVal($name, "screenURI", undef)} else {$URI = AttrVal($name, "callerURI", undef)} $command = "set ".$hash->{Mode}." stream ".$URI; readingsSingleUpdate($hash, "currentMedia", $URI, 1); } elsif ($cmd eq "volume") { $command = "set ".$hash->{Mode}." volume ".$par; } elsif ($cmd eq "sayText") { $par = '"'.$par.' '.join(" ", @a).'"'; $command = "set ".$hash->{Mode}." speak ".$par; } if ($command ne "") { Log3 $name, 4, "[SamsungAV] $name call DLNA-Renderer. Command: $command"; return SamsungAV_DLNA($hash,$command) } } if ($cmd eq "channel") { if($par < 10){$cmd = $par} else { $cmd = "0_macro"; $par = substr($par,0,1). "," . substr($par,1,1); } } # App functionality for Samsung Tizen since K-Series return SamsungAV_Tizen_App($hash,$name,$cmd,$par) if (substr($cmd,0,5) eq "0_App" ); # RC commands if ($Port eq 55000 ){ return SamsungAV_55000($hash,$name,$cmd,$par); } elsif ($Port eq 52235 ){ return SamsungAV_52235($hash,@_); } elsif ($Port eq 8001 ){ return SamsungAV_Tizen_RC($hash,$name,$cmd,$par); } elsif ($Port eq 8000 ){ return SamsungAV_Tizen_RC($hash,$name,$cmd,$par); } } else { my $ret = "[SamsungAV] 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; } return undef; } # Samsung Tizen Models sub SamsungAV_Tizen_RC($$$$) { my ($hash,$name,$cmd,$par) = @_; Log3 $name, 5, "[SamsungAV] $name command ".$cmd. " parameter ".$par; my $dev = $hash->{Host}; my $wsport = $hash->{Port}; my ($payload); if($cmd ne "0_macro") {@ARGV = split(" ",$cmd)} else {@ARGV = split(",",$par)} # screen message caller or individual via Webbrowser if ( $cmd eq "caller" || $cmd eq "screen") { return SamsungAV_7676($hash,$cmd) if ($wsport ne "8001"); return SamsungAV_Tizen_App($hash, $name,$cmd,"Internet"); } (my $msg, my $socket) = &SamsungAV_Tizen_websocket_open($hash,$name); if ($msg ne "101") {return $msg} else { syswrite $socket, build_frame (0, 1, 0, 0, 0, 0x1, '1::/com.samsung.companion') if ($wsport != 8001); eval {require Time::HiRes; 1;} or do { return "[SamsungAV] Module Time:HiRes not installed. Check perl libraries"; }; foreach my $argnum (0 .. $#ARGV) { if ($argnum > 0) { Time::HiRes::usleep(AttrVal($name, 'delayMacro', 300000)); } else {Time::HiRes::usleep(AttrVal($name, 'delayRC', 0)); } if ($ARGV[$argnum] ne "") { # Send remote key(s) Log3 $name, 4, "[SamsungAV] $name sending ".uc($ARGV[$argnum]); if ($wsport != 8001) { $payload = $hash->{".commands"}[$argnum]; } else { $payload = JSON::encode_json({ "method" => "ms.remote.control", "params" => { "Cmd" => "Click", "DataOfCmd" => "KEY_".uc($ARGV[$argnum]), "Option" => "false", "TypeOfRemote" => "SendRemoteKey" } }); } SamsungAV_Tizen_write_payload($socket, $payload, $name); # BlockingCall("SamsungAV_execSamsungCtl", $name."|".$hash->{Host}."|".$ARGV[$argnum]); } else {Log3 $name, 4, "[SamsungAV] $name sending pause"} } SamsungAV_Tizen_websocket_close($socket); } return undef; } # Samsung Tizen open websocket sub SamsungAV_Tizen_websocket_open($$) { my ($hash,$name) = @_; my $dev = $hash->{Host}; my $port = $hash->{Port}; my ($msg,$webclient_header,$path,$socket); my $token = ReadingsVal($name,".token",undef); # (my $error, my $token) = getKeyValue($name."token"); # if ($port != 8001) { # $hash->{Port} = 8443; $path = SamsungAV_Tizen_Encrypt_Init($hash,$name); # $hash->{Port} = $port; return $path if (substr($path,0,11) eq "[SamsungAV]"); $webclient_header = "GET " . $path ." HTTP/1.1\r\n"; $socket = IO::Socket::INET->new(PeerAddr=>"$dev:$port", Timeout=>2, Blocking=>1, ReuseAddr=>1); # $port = 8443; # secure port 8002 since K-series, 8443 for H-/J-Series Log3 $name, 4, "[SamsungAV] HTTP socket-connection to $name. Reply: ". $socket->error(); } else { eval {require IO::Socket::SSL; 1;} or do { return "[SamsungAV] Set: Module IO::Socket::SSL not installed. Check perl libraries"; }; # $webclient_header = "GET /api/v2/channels/samsung.remote.control?name=RkhFTVJlbW90ZQ== HTTP/1.1\r\n"; $webclient_header = "GET /api/v2/channels/samsung.remote.control?name=RkhFTVJlbW90ZQ=="; # $webclient_header .= "&token=$hash->{token}" if (defined($hash->{token})) ; #temporary readingsSingleUpdate($hash, ".token", $hash->{token},0) if (defined($hash->{token})); #temporary $webclient_header .= "&token=$token" if (defined($token)); $webclient_header .= " HTTP/1.1\r\n"; $port = 8002; # secure port 8002 since K-series, 8443 for H-/J-Series $IO::Socket::SSL::DEBUG = 3 if(AttrVal($name,"verbose","0") eq "5"); $IO::Socket::SSL::DEBUG = 2 if(AttrVal($name,"verbose","0") eq "4"); $socket = IO::Socket::SSL->new(PeerAddr=>"$dev:$port", SSL_verify_mode=>0,Timeout=>2, Blocking=>1, ReuseAddr=>1); Log3 $name, 4, "[SamsungAV] HTTP socket-connection to $name. SSL_Reply: ". IO::Socket::SSL::errstr(); $IO::Socket::SSL::DEBUG = 0; # stop detailed debug lines } if($socket) { Log3 $name, 4, "[SamsungAV] HTTP socket-connection to $name successful."; $webclient_header .= "Upgrade: websocket" . "\r\n" . "Connection: Upgrade" . "\r\n" . "Host: $dev:$port" . "\r\n" . # "Origin: $dev:$port" . "\r\n" . "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" . "\r\n" . "Sec-WebSocket-Version: 13" . "\r\n\r\n"; Log3 $name, 5, "[SamsungAV] $name send to TV: " . $webclient_header; my $written = syswrite $socket, $webclient_header; Log3 $name, 3, "[SamsungAV] $name header written with error $!" if (!defined($written)); my $read = sysread $socket, my $buf, 10240; Log3 $name, 3, "[SamsungAV] $name read response with error $!" if (!defined($read)); Log3 $name, 5, "[SamsungAV] $name first websocket response: $buf"; $buf =~ /^[^ ]+ ([\d]{3})/; if (!defined($1)) { $msg = "[SamsungAV] HTTP websocket-connection to $name not successful; undefined response $buf of device"; Log3 $name, 2, $msg; return $msg; } my $sysbytes =""; $buf = ""; if ($port != 8000) { do { $sysbytes .= $buf; $read = sysread $socket, $buf, 1; Log3 $name, 3, "[SamsungAV] $name read response with error $!" if (!defined($read)); } until $buf eq "{"; Log3 $name, 5, "[SamsungAV] $name Statusbytes of second websocket response: " . unpack('H*', $sysbytes); } $read = sysread $socket, $buf, 10240; Log3 $name, 3, "[SamsungAV] $name read response with error $!" if (!defined($read)); $buf = "{" . $buf; Log3 $name, 5, "[SamsungAV] $name data of second websocket response: $buf"; # $read = sysread $socket, $buf, 3; # Log3 $name, 3, "[SamsungAV] $name data of third read websocket error response: $!, data: ".unpack('H*', $buf)." , bytecount = $read"; # $read = sysread $socket, $buf, 10240; # Log3 $name, 3, "[SamsungAV] $name data of fourth read websocket response: $buf, bytecount = $read"; if ($1 eq '101') { if ($port != 8000) { # my $line = '{"data":{"clients":[{"attributes":{"name":"RkhFTVJlbW90ZQ==","token":"14576607"},"connectTime":1542049383707,"deviceName":"RkhFTVJlbW90ZQ==","id":"c376a63e-61aa-4e73-b72e-7cb26596f9e7","isHost":false}],"id":"c376a63e-61aa-4e73-b72e-7cb26596f9e7","token":"11142450"},"event":"ms.channel.connect"}'; my $json = eval{JSON::decode_json($buf)}; if (defined($json->{data}->{token})) { # $hash->{token} = $json->{data}->{token}; #temporary readingsSingleUpdate($hash, ".token", $json->{data}->{token},0); # setKeyValue($name."token", $json->{data}->{token}); # Log3 $name, 5, "[SamsungAV] $name token: $json->{data}->{token} saved"; } } if ($buf =~ /timeOut/) { $msg = "[SamsungAV] $name connected but authorization timed out. Didn't you see the pop-up on TV ? "; } elsif ($buf =~ /unauthorized/) { $msg = "[SamsungAV] $name connected but authorization failed. Check authorizations of TV"; } elsif ($buf =~ /touchDisabled/) { # Trs received events ms.remote.touchDisable after committing new authorization request $msg = "[SamsungAV] $name connected but authorization failed as touchDisabled. Check authorizations of TV. Token deleted."; readingsDelete($hash, ".token") if (defined($token)); } else {return ("101", $socket);} Log3 $name, 3, $msg; SamsungAV_Tizen_websocket_close($socket); return $msg; } else { $msg = "[SamsungAV] HTTP websocket-connection to $name not successful; invalid response to websocket upgrade request"; Log3 $name, 2, $msg; return $msg; } } else { $msg = "[SamsungAV] HTTP socket-connection to $name not successful. "; $msg .= "SSL_Error: ". IO::Socket::SSL::errstr() if ($port != 8000); # $msg = "[SamsungAV] HTTP socket-connection to $name not successful. SSL_Error: ". $IO::SOCKET::SSL::SSL_Error . " SSL_Error: " .$SSL_Error . " IO_Error: " .$socket->error(); Log3 $name, 2, $msg; return $msg; } } # Samsung Tizen close websocket sub SamsungAV_Tizen_websocket_close($) { my ($socket) = @_; syswrite $socket, build_frame (0, 1, 0, 0, 0, 0x8, ''); shutdown $socket, 2; } # Samsung Tizen write_payload sub SamsungAV_Tizen_write_payload($$$) { my ($socket, $payload, $name) = @_; syswrite $socket, build_frame (0, 1, 0, 0, 0, 0x1, $payload); Log3 $name, 5, "[SamsungAV] $name send payload: " . $payload; } # Initialization of H-, J-Series with encrypted websocket access sub SamsungAV_Tizen_Encrypt_Init($$) { my ($hash,$name) = @_; my $dev = $hash->{Host}; my $wsport = $hash->{Port}; my $msg = SamsungAV_Tizen_Encrypt($hash,$name); return $msg if (defined($msg)); my $millis = int ([gettimeofday] * 1000); my ($err, $ret) = HttpUtils_BlockingGet ({ url => "http://$dev:$wsport/socket.io/1/?t=$millis", method => 'GET' }); if( defined($err) && $err ) { $msg = "[SamsungAV] $name: Error getting websocket url"; Log3 $name, 2, $msg; return $msg; } unless ($ret =~ m/:websocket,/i) { $msg = "[SamsungAV] $name: Content error in websocket url $err"; Log3 $name, 2, $msg; return $msg; } my $websocket_url = "ws://$dev:$wsport/socket.io/1/websocket/" . (split(':', $ret))[0]; unless ( $websocket_url =~ /^ws:\/\/([^:\/]+):(\d+)(\/.+)$/ ) { $msg = "[SamsungAV] $name: Error websocket url $websocket_url"; Log3 $name, 2, $msg; return $msg; } my ($host, $port, $path) = ($1, $2, $3); Log3 $name, 4, "[SamsungAV] $name: websocket path $path"; return $path; } # command encryption of H-, J-Series sub SamsungAV_Tizen_Encrypt($$) { my ($hash,$name) = @_; my $dev = $hash->{Host}; my $msg; my $KEYfile = $hash->{NAME} . "_session_key.txt"; #==================================================================================== #Read key and session-id from file, previously created with regapp.pl my $json; my $enc_key; my $line; my $session = ''; unless ( -e $KEYfile) { $KEYfile = "samsung_session_key.txt"; } if (open (KEY, $KEYfile)) { $line = ; close (KEY); } else { $msg = "[SamsungAV] $name: ERROR cannot open file for input the session key."; Log3 $name, 1, $msg; return $msg; } eval {require Crypt::Rijndael; 1;} or do { $msg = "[SamsungAV] $name: Module Crypt::Rijndael not installed. Run 'sudo apt-get install libcrypt-rijndael-perl'"; Log3 $name, 1, $msg; return $msg; }; if (JSON::decode_json($line)) { $json = JSON::decode_json($line); $enc_key = $json->{session_key}; Log3 $name, 5, "session_key: ".$enc_key; $session = $json->{session_id}; Log3 $name, 5, "session_id: ".$session; } else { $msg = "[SamsungAV] $name: ERROR: $line is no JSON."; Log3 $name, 1, $msg; return $msg; } # unhexlify key $enc_key =~ s/([a-fA-F0-9]{2})/chr(hex($1))/eg; my ($cipher, $command_bytes, @chars, $int_array, $command); undef $hash->{".commands"}; foreach (@ARGV) { # generate command array if ($_ ne "") { $_ = 'KEY_' . uc($_) if ( $_ !~ m/^KEY_/ ); Log3 $name, 5, "[SamsungAV] $name: generate command for key '$_'"; # generate json $json = '{"method":"POST","body":{"plugin":"RemoteControl","param1":"uuid:12345","param2":"Click","param3":"' . $_ . '","param4":false,"api":"SendRemoteKey","version":"1.000"}}'; # encrypt json $json = $json . chr(16 - length($json)%16) x ( 16 - length($json)%16 ); # padding $cipher = Crypt::Rijndael->new( $enc_key, Crypt::Rijndael::MODE_ECB() ); $command_bytes = $cipher->encrypt($json); # generate command @chars = $command_bytes =~ /./sg; foreach (@chars) { $_ = ord($_); } $int_array = join (',', @chars); $command = '5::/com.samsung.companion:{"name":"callCommon","args":[{"Session_Id":' . $session . ',"body":"[' . $int_array . ']"}]}'; } else {$command = $_} Log3 $name, 5, "[SamsungAV] $name: command: '$command'"; push @{$hash->{".commands"}} , $command; # save command to array } return undef; } sub build_frame { my ($masked, $fin, $rsv1, $rsv2, $rsv3, $op, $payload) = @_; # Head my $head = $op + ($fin ? 128 : 0); $head |= 0b01000000 if $rsv1; $head |= 0b00100000 if $rsv2; $head |= 0b00010000 if $rsv3; my $frame = pack 'C', $head; # Small payload my $len = length $payload; if ($len < 126) { $frame .= pack 'C', $masked ? ($len | 128) : $len; } # Extended payload (16-bit) elsif ($len < 65536) { $frame .= pack 'Cn', $masked ? (126 | 128) : 126, $len; } # Extended payload (64-bit with 32-bit fallback) else { $frame .= pack 'C', $masked ? (127 | 128) : 127; $frame .= pack('NN', 0, $len & 0xffffffff); #$frame .= MODERN ? pack('Q>', $len) : pack('NN', 0, $len & 0xffffffff); } # Mask payload if ($masked) { my $mask = pack 'N', int(rand 9 x 7); $payload = $mask . xor_encode($payload, $mask x 128); } return $frame . $payload; } # Samsung Tizen Model App list sub SamsungAV_Tizen_App_list($) { my ($hash) = @_; my $dev = $hash->{Host}; my $port = $hash->{Port}; my $name = $hash->{NAME}; return undef if ($hash->{STATE} ne "on"); my ($msg, $payload, $webclient_header, $socket, $appnames); ($msg, $socket) = &SamsungAV_Tizen_websocket_open($hash,$name); if ($msg ne "101") {return $msg} else { $payload = JSON::encode_json({ "method" => "ms.channel.emit", "params" => { "event" => "ed.installedApp.get", "to" => "host", "TypeOfRemote" => "SendRemoteKey" } }); SamsungAV_Tizen_write_payload($socket, $payload, $name); sysread $socket, my $buf, 4; # read hex header first Time::HiRes::usleep(500000); # just to get all data, otherwise crashing FHEM sysread $socket, $buf, 10240; Log3 $name, 5, "[SamsungAV] response $name to write_payload: $buf"; SamsungAV_Tizen_websocket_close($socket); my $json = eval {decode_json($buf)}; if ( ref($json->{data}{data}) eq "ARRAY" ) { Log3 $name, 5, "[SamsungAV] ARRAY found"; foreach my $rec (@{$json->{data}{data}}) { # Log3 $name, 3, "[SamsungAV] Application: $rec"; # Log3 $name, 3, "[SamsungAV] Application: $rec->appId"; Log3 $name, 5, '[SamsungAV] Application: ' . $rec->{"name"} . ' Id: ' . $rec->{"appId"}; $rec->{"name"} =~ tr/A-Za-z0-9#.-_//cd; # remove spaces an other characters $appnames .= $rec->{"name"} . ","; $hash->{helper}{app}{$rec->{"name"}} = $rec->{"appId"}; } $hash->{".validcommands"} .= "0_App_start:" . $appnames . " 0_App_state:" . $appnames . " "; } else { Log3 $name, 3, "[SamsungAV] $name timelag to reach all json data for app list might be too small"; } } return undef; } # Samsung Tizen Model App commands sub SamsungAV_Tizen_App($$$$) { my ($hash, $name, $cmd, $par) = @_; my $dev = $hash->{Host}; my $port = $hash->{Port}; my ($msg, $payload, $socket,$method,$action_type); if (substr($cmd,0,5) eq "0_App" && #App command, otherwise internal call(e.g. caller, screen...) substr($cmd,9,1) ne "r") { #not start command if (substr($cmd,9,1) ne "p") { #status command $method = "GET" } else {$method = "GET"} #stop command my $param = {url => "http://$dev:$port/ws/apps/$par", timeout => 5, # hash => $hash, method => $method, header => "", # callback => \&SamsungAV_Tizen_App_callback }; # HttpUtils_NonblockingGet($param); my ($err, $data) = HttpUtils_BlockingGet($param); if ($method eq "GET") { my $stateapp; if ($data =~ m/running/) {$stateapp = "running"} else {$stateapp = "stopped"} $msg = "[SamsungAV] $name: state of app $par: $stateapp"; Log3 $name, 4, $msg; return $msg; } } else { # $method = "POST"} #start command ; only useable for selected Apps(Netflix, YouTube) to be used with simple http_request ($msg, $socket) = &SamsungAV_Tizen_websocket_open($hash,$name); if ($msg ne "101") {return $msg} else { # if ($hash->app_type ne "4") {$action_type = "DEEP_LINK"} # else {$action_type = "NATIVE_LAUNCH"} my $URI = ""; if ($par ne "Internet") {$action_type = "DEEP_LINK"} else { if ( $cmd eq "caller" ) {$URI = AttrVal($name, "callerURI", undef)} elsif ( $cmd eq "screen" ) {$URI = AttrVal($name, "screenURI", undef)} Log3 $name, 4, "[SamsungAV] $name browser access with URI: $URI"; $action_type = "NATIVE_LAUNCH" } $payload = JSON::encode_json({ "method" => "ms.channel.emit", "params" => { "event" => "ed.apps.launch", "to" => "host", "data" => { "appId" => $hash->{helper}{app}{$par}, ### "appId" => "org.tizen.browser", # app_type 4 --> $action_type = "NATIVE_LAUNCH"; with link in metaTag possible ## "appId" => "3201710015067", # app_type 1 --> $action_type = "DEEP_LINK"; Universal Guide ## "appId" => "3201710015016", # app_type 1 --> $action_type = "DEEP_LINK"; SmartThings ## "appId" => "3201710015037", # app_type 1 --> $action_type = "DEEP_LINK"; Gallery # "appId" => "111299001912", # app_type 2 --> $action_type = "DEEP_LINK"; YouTube # "name" => "YouTube", #doesn't work "action_type" => $action_type, "metaTag" => $URI }, "TypeOfRemote" => "SendRemoteKey" } }); SamsungAV_Tizen_write_payload($socket, $payload, $name); sysread $socket, my $buf, 10240; Log3 $name, 5, "[SamsungAV] response $name to write_payload: $buf"; SamsungAV_Tizen_websocket_close($socket); } } return undef; } # old Samsung Models sub SamsungAV_52235($@) { my ($hash, @a) = @_; my $name = $hash->{NAME}; my $TV = $hash->{Host}; my $count = @a; my $arg = lc($a[2]); # mute volume my $cont1 = ucfirst($arg); # Mute my $cont2 = ""; my $cont3 = ""; my $cont4 = ""; my $cont5 = ""; my $cont6 = ""; 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]} if (defined $a[6]) { $cont5 = $a[6]} if (defined $a[7]) { $cont6 = $a[7]} if (defined $a[8]) { $cont7 = $a[8]} if (defined $a[9]) { $cont8 = $a[9]} my $head = ""; my $callsoap = "\r\n". "\r\n". "\r\n"; my $kind = 0; 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, "[SamsungAV] $name Volume: not correct"; $kind = 0; } } if ( $kind eq 1){ $callsoap .= "\r\n"; $callsoap .= "0\r\n"; $callsoap .= "$cont2\r\n"; $callsoap .= "Master\r\n"; $callsoap .= "\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"; } else { my $body = ""; my $operator = ""; my $calldate=`date +"%Y-%m-%d"`; chomp($calldate); my $calltime=`date +"%H:%M:%S"`; chomp($calltime); # if ( $arg ne "del") # { $operator = "Add"; # } # else # { # $kind = 5; # $operator = "Remove"; # } $head .= "POST /PMR/control/MessageBoxService HTTP/1.1\r\n"; $head .= "Content-Type: text/xml; charset=\"utf-8\"\r\n"; $head .= "SOAPACTION: \"urn:samsung.com:service:MessageBoxService:1#".$operator."Message\"\r\n"; $callsoap .= "\r\n"; $callsoap .= "text/xml\r\n"; $callsoap .= "1334799348\r\n"; $callsoap .= "\r\n"; if ( $arg eq "call") { $kind = 2; $callsoap .= "<Category>Incoming Call</Category>\r\n"; $callsoap .= "<DisplayType>Maximum</DisplayType>\r\n"; $callsoap .= "<CallTime>\r\n"; $callsoap .= "<Date>$calldate</Date>\r\n"; $callsoap .= "<Time>$calltime</Time>\r\n"; $callsoap .= "</CallTime>\r\n"; $callsoap .= "<Callee>\r\n"; $callsoap .= "<Name>An: $cont4</Name>\r\n"; $callsoap .= "<Number>Nr: $cont5</Number>\r\n"; $callsoap .= "</Callee>\r\n"; $callsoap .= "<Caller>\r\n"; $callsoap .= "<Name>Von: $cont2</Name>\r\n"; $callsoap .= "<Number>Nr: $cont3</Number>\r\n"; $callsoap .= "</Caller>\r\n"; } if ( $arg eq "sms") { $kind = 3; for my $i (6..$count){ $body .= $a[$i]; $body .= " "; } $callsoap .= "<Category>SMS</Category>\r\n"; $callsoap .= "<DisplayType>Maximum</DisplayType>\r\n"; $callsoap .= "<ReceiveTime>\r\n"; $callsoap .= "<Date>$calldate</Date>\r\n"; $callsoap .= "<Time>$calltime</Time>\r\n"; $callsoap .= "</ReceiveTime>\r\n"; $callsoap .= "<Receiver>\r\n"; $callsoap .= "<Name>An: $cont4</Name>\r\n"; $callsoap .= "<Number>Nr: $cont5</Number>\r\n"; $callsoap .= "</Receiver>\r\n"; $callsoap .= "<Sender>\r\n"; $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"; } if ( $arg eq "date") { $kind = 4; for my $i (10..$count){ $body .= $a[$i]; $body .= " "; } $callsoap .= "<Category>Schedule Reminder</Category>\r\n"; $callsoap .= "<DisplayType>Maximum</DisplayType>\r\n"; $callsoap .= "<StartTime>\r\n"; $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 .= "<EndTime>\r\n"; $callsoap .= "<Date>$cont7</Date>\r\n"; $callsoap .= "<Time>$cont8</Time>\r\n"; $callsoap .= "</EndTime>\r\n"; $callsoap .= "<Location>Ort: $cont9</Location>\r\n"; $callsoap .= "<Body>Inhalt: $body</Body>\r\n"; } $callsoap .= "\r\n"; $callsoap .= "\r\n"; } if ( $kind ne 0 ){ return soap_call($hash,$head,$callsoap,52235); }else{ return "Unknown argument, choose one of mute:on,off volume:slider,0,1,100 call sms date"; } } # new Samsung Models sub SamsungAV_55000($$$$) { my ($hash,$name,$cmd,$par) = @_; if($cmd ne "0_macro") {@ARGV = split(" ",$cmd)} else {@ARGV = split(",",$par)} #### Configuration 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" my $myip = $hash->{MyIP}; # IP Address of FHEM Server my $mymac = $hash->{MAC}; # Used for the access control/validation '"24:65:11:80:0D:01" # screen message caller or individual if ( $cmd eq "caller" || $cmd eq "screen") { return SamsungAV_7676($hash,$cmd); } my $msg; my $appstring = "iphone..iapp.samsung"; # What the iPhone app reports my $tvappstring = "iphone.".$tv.".iapp.samsung"; # TV type my $remotename = "Perl Samsung Remote"; # What gets reported when it asks for permission/also shows in General->Wireless Remote Control menu #### 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, "[SamsungAV] mymac: $mymac invalid format"; }else{ # command-line help if (!$tv|!$tvip|!$myip|!$mymac) { return "[SamsungAV] Error - Parameter missing:\nmodel, tvip, myip, mymac."; } Log3 $name, 5, "[SamsungAV] $name: 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 if ($cmd eq "0_text_line") { # 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) { Time::HiRes::usleep(300000) if ($argnum > 0); if ($ARGV[$argnum] ne "") { # Send remote key(s) # Log3 $name, 3, "[SamsungAV] 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; Log3 $name, 4, "[SamsungAV] $name: command: '$key'"; # select(undef, undef, undef, 0.5); } } } close($sock); }else{ $msg = "Could not create socket. Port: $port. Aborting."; Log3 $name, 2, "[SamsungAV] $name: $msg"; } } return $msg; } sub SamsungAV_7676($$) { my ($hash,$arg) = @_; my $TV = $hash->{Host}; my $name = $hash->{NAME}; my ($URI, $location) = undef; if ($hash->{Port} eq "8000") {$location = "smp_4_" } else {$location = "smp_12_" } my $callsoap .= "\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; $callsoap .= "\r\n"; if ( $arg ne "caller" ) {$URI = AttrVal($name, "screenURI", undef)} else {$URI = AttrVal($name, "callerURI", undef)} if (defined $URI) {$callsoap .= "".$URI."\r\n"} $callsoap .= "\r\n"; my $head .= "POST /$location HTTP/1.1\r\n"; $head .= "Content-Type: text/xml; charset=\"utf-8\"\r\n"; $head .= "SOAPACTION: \"urn:samsung.com:service:MainTVAgent2:1#RunBrowser\"\r\n"; return soap_call($hash,$head,$callsoap,7676); } sub soap_call($$$$) { my ($hash,$head,$callsoap,$port) = @_; my $name = $hash->{NAME}; my $TV = $hash->{Host}; my $msg; $callsoap .= "\r\n"; $callsoap .= "\r\n"; my $size = length($callsoap); $head .= "Cache-Control: no-cache\r\n"; $head .= "Host: $TV:$port\r\n"; $head .= "Content-Length: $size\r\n"; $head .= "Connection: Close\r\n"; $head .= "\r\n"; my $buffer = ""; my $tmp = $head . $callsoap; my @tmp2 = ""; Log3 $name, 4, "[SamsungAV] $name: $TV:$port connection message: $tmp"; my $sock = new IO::Socket::INET ( PeerAddr => $TV, PeerPort => $port, Proto => 'tcp', Timout => 5 ); if (defined ($sock)){ print $sock $tmp; my $buff =""; while ((read $sock, $buff, 1) > 0){ $buffer .= $buff; } Log3 $name, 2, "[SamsungAV] $name $TV: empty answer received" if ($buffer eq ""); @tmp2 = split (/\n/,$buffer); Log3 $name, 4, "[SamsungAV] $name $TV: socket answer $buffer"; $sock->close(); Log3 $name, 4, "[SamsungAV] $name $TV: socket closed"; }else{ $msg = "Could not create socket. Port: $port Aborting."; Log3 $name, 2, "[SamsungAV] $name $TV: $msg"; } return $msg; } sub SamsungAV_DLNA($$) { my ($hash,$command) = @_; my $name = $hash->{NAME}; if (ReadingsVal($hash->{Mode},"state","offline") ne "offline") { # copying of picture for caller to be done outside of program fhem($command); Log3 $name, 5, "[SamsungAV] $name DLNA-command: $command"; } else { return "[SamsungAV] $name DLNAdevice $hash->{Mode} currently offline"; } } sub getIP() { my $address = eval {Net::Address::IP::Local->public_ipv4}; if ($@) { $address = 'localhost'; } return "$address"; } sub getIP_old() { my $host = hostname(); my $address = inet_ntoa(scalar gethostbyname(hostname() || 'localhost')); if ($@) { $address = 'localhost'; } return "$address"; } sub getMAC4IP($) { my $IP = shift; my @interfaces = IO::Interface::Simple->interfaces; foreach my $if (@interfaces) { next unless defined ($if->address); if ($if->address eq $IP) { return $if->hwaddr; } } return ""; } # Callback from 95_remotecontrol for command makenotify. # Param1: Name of remoteControl device # Param2: Name of target FHEM device sub SamsungAV_RCmakenotify($$) { my ($name, $ndev) = @_; my $nname="notify_$name"; fhem("define $nname notify $name set $ndev remoteControl ".'$EVENT',1); Log3 undef, 2, "[remoteControl:SamsungAV] Notify created: $nname"; return "Notify created by SamsungAV: $nname"; } # Callback from 95_remotecontrol for command layout. Creates non svg layout sub SamsungAV_RClayout_TV() { my @row; my $i = 0; $row[$i++]="poweroff:POWEROFF, :blank, source:SOURCE"; $row[$i++]=":blank, hdmi:HDMI, :blank"; $row[$i++]="1, 2, 3"; $row[$i++]="4, 5, 6"; $row[$i++]="7, 8, 9"; $row[$i++]="ttx_mix:TEXT, 0, prech:PRECH"; # $row[$i++]=":blank, :blank, :blank"; $row[$i++]="volumeUp:UP, mute:MUTE, channelUp:CHUP"; $row[$i++]=":VOL, :blank, :PROG"; $row[$i++]="volumeDown:DOWN, channelList:CH_LIST,channelDown:CHDOWN"; $row[$i++]="menu:MENU, :blank, guide:GUIDE"; # $row[$i++]=":blank, :blank, :blank"; $row[$i++]="tools:TOOLS, up:UP, info:INFO"; $row[$i++]="left:LEFT, enter:ENTER, right:RIGHT"; $row[$i++]="reurn:RETURN, down:DOWN, exit:EXIT"; $row[$i++]="red:RED, green:GREEN, yellow:YELLOW, cyan:BLUE"; $row[$i++]=":blank, :SEARCH, :blank"; $row[$i++]=":EGUIDE, aspect:PICTURE_SIZE, ad:AD"; $row[$i++]="rewind:REWIND, pause:PAUSE, ff:FF"; $row[$i++]="rec:REC, play:PLAY, stop:STOP"; # unused available commands # SOURCE PIP_ONOFF # CONTENTS W_LINK # RSS MTS SRS CAPTION TOPMENU SLEEP ESAVING #Remove spaces for (@row) {s/\s+//g} $row[$i++]="attr rc_iconpath icons/remotecontrol"; $row[$i++]="attr rc_iconprefix black_btn_"; return @row; } # Callback from 95_remotecontrol for command layout. Creates svg layout sub SamsungAV_RClayout_TV_SVG() { my @row; my $i = 0; $row[$i++]="poweroff:rc_POWER.svg, :rc_BLANK.svg, tv:rc_TV.svg"; $row[$i++]=":rc_BLANK.svg, hdmi:rc_HDMI.svg, :rc_BLANK.svg"; $row[$i++]="1:rc_1.svg, 2:rc_2.svg, 3:rc_3.svg"; $row[$i++]="4:rc_4.svg, 5:rc_5.svg, 6:rc_6.svg"; $row[$i++]="7:rc_7.svg, 8:rc_8.svg, 9:rc_9.svg"; $row[$i++]="ttx_mix:rc_TEXT.svg, 0:rc_0.svg, prech:rc_PREVIOUS.svg"; $row[$i++]=":rc_BLANK.svg, :rc_BLANK.svg, :rc_BLANK.svg"; $row[$i++]="volumeUp:rc_UP.svg, mute:rc_MUTE.svg, channelUp:rc_UP.svg"; $row[$i++]=":rc_VOL.svg, :rc_BLANK.svg, :rc_PROG.svg"; $row[$i++]="volumeDown:rc_DOWN.svg, channelList:rc_PROG.svg, channelDown:rc_DOWN.svg"; $row[$i++]="menu:rc_MENU.svg, :rc_BLANK.svg, guide:rc_EPG.svg"; $row[$i++]=":rc_BLANK.svg, :rc_BLANK.svg, :rc_BLANK.svg"; $row[$i++]="tools:rc_OPTIONS.svg, up:rc_UP.svg, info:rc_INFO.svg"; $row[$i++]="left:rc_LEFT.svg, enter:rc_OK.svg, right:rc_RIGHT.svg"; $row[$i++]="reurn:rc_RETURN.svg, down:rc_DOWN.svg, exit:rc_EXIT.svg"; $row[$i++]="red:rc_RED.svg, green:rc_GREEN.svg, yellow:rc_YELLOW.svg, cyan:rc_BLUE.svg"; $row[$i++]=":rc_BLANK.svg, :rc_SEARCH.svg, :rc_BLANK.svg"; $row[$i++]=":rc_HELP.svg, aspect:rc_PICTURE_SIZE.svg, ad:rc_AD.svg"; $row[$i++]="rewind:rc_REW.svg, pause:rc_PAUSE.svg, ff:rc_FF.svg"; $row[$i++]="rec:rc_REC.svg, play:rc_PLAY.svg, stop:rc_STOP.svg"; #Remove spaces for (@row) {s/\s+//g} return @row; } 1; =pod =item summary device to communicate with Samsung AV devices =begin html

SamsungAV

    This module supports Samsung devices like TV or BD players.

    Define
    define <name> SamsungAV <host> <port> [DLNA]

    (B|C|D Series) use port 52235 for screen messages: call, sms, date and volume change
    (C|D|E|F Series) use port 55000 for RC commands
    (H|J Series) use port 8000 for RC commands
    (newest Series) use port 8001 for RC commands
    (all Series)The device name of optional DLNA client might be defined to send full screen messages(OSD)
    Example:
    define Television SamsungAV 192.168.178.20 52235
    define Television SamsungAV 192.168.178.20 55000
    define Television SamsungAV 192.168.178.20 55000 DLNA_Client

    Set
    Port 52235 functionality: set <name> <command> [arguments]
    where command is one of:
    • mute
    • volume
    • call
    • sms
    • date
    Example:
    set Television mute on
    set Television volume 20
    set Television call Peter 012345678 Phone 87654321

    RC functionality:
    set <name> <RC command> [arguments]
    Example:
    set Television channelDown

    Special commands:
    caller/screen
    send message(URI defined by attr callerURI/screenURI) via DLNA to device
    Example:
    set Television caller

    sayText
    TTS-function, if optional DLNA-device is defined
    Example:
    set Television sayText das ist ein test
    see commandref of DLNA-device for further information.

    0_macro
    macro function
    send several comma separated RC commands to device(each command will cause a pause of delayMacro microsec.)
    set <name> 0_macro <RC command1> [,RC command2,RC command3....]
    Example:
    set Television 0_macro contents,right,right,,down,enter
    some TVs need different delays between RC commands in a macro. You may solve this with usage of multiple commas.

    0_text_line
    sent text to device(migh be helpful to submit data used by interactive apps)
    set <name> 0_text_line <TEXT>
    Example:
    set Television 0_text_line http://192.168.178.1:8083/fhem/btip/info.html

    0_App_start Netflix/YouTube (0_App_status Netflix/YouTube)
    start app on TV (get status of app)

    Get
      N/A


    Attributes
  • callerURI: path to an URI of a media(jpg,mp3...)to be displayed on screen main purpose: URI to be displayed on screen if a phone call comes in

  • screenURI: path to an URI of a media(jpg,mp3...)to be displayed on screen every other purpose than a phone call as event

  • delayRC: delay in microsec. default=0 some TV's need a delay before transmition of a RC command. be careful: attribute causes system freezes

  • delayMacro: delay in microsec. default=300000 most TV's need a delay between each RC command in macro function. be careful: attribute causes system freezes

  • disable: 0/1: enables/disables device
=end html =cut