first commit
This commit is contained in:
commit
40b2bd9946
931
82_LGTV_WebOS.pm
Normal file
931
82_LGTV_WebOS.pm
Normal file
@ -0,0 +1,931 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Developed with Kate
|
||||
#
|
||||
# (c) 2017 Copyright: Marko Oldenburg (leongaultier at gmail dot com)
|
||||
# All rights reserved
|
||||
#
|
||||
# This script is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# any later version.
|
||||
#
|
||||
# The GNU General Public License can be found at
|
||||
# http://www.gnu.org/copyleft/gpl.html.
|
||||
# A copy is found in the textfile GPL.txt and important notices to the license
|
||||
# from the author is found in LICENSE.txt distributed with these scripts.
|
||||
#
|
||||
# This script is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
#################################
|
||||
######### Wichtige Hinweise und Links #################
|
||||
|
||||
|
||||
##
|
||||
#
|
||||
|
||||
|
||||
################################
|
||||
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use MIME::Base64;
|
||||
use IO::Socket::INET;
|
||||
use Digest::SHA qw(sha1_hex);
|
||||
use JSON qw(decode_json encode_json);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
my $version = "0.0.30";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Declare functions
|
||||
sub LGTV_WebOS_Initialize($);
|
||||
sub LGTV_WebOS_Define($$);
|
||||
sub LGTV_WebOS_Undef($$);
|
||||
sub LGTV_WebOS_Set($@);
|
||||
sub LGTV_WebOS_Open($);
|
||||
sub LGTV_WebOS_Close($);
|
||||
sub LGTV_WebOS_Read($);
|
||||
sub LGTV_WebOS_Write($@);
|
||||
sub LGTV_WebOS_Attr(@);
|
||||
sub LGTV_WebOS_Handshake($);
|
||||
sub LGTV_WebOS_ResponseProcessing($$);
|
||||
sub LGTV_WebOS_Header2Hash($);
|
||||
sub LGTV_WebOS_Pairing($);
|
||||
sub LGTV_WebOS_CreateSendCommand($$$;$);
|
||||
sub LGTV_WebOS_Hybi10Encode($;$$);
|
||||
sub LGTV_WebOS_PreResponseProsessing($$);
|
||||
sub LGTV_WebOS_WriteReadings($$);
|
||||
sub LGTV_WebOS_GetCurrentChannel($);
|
||||
sub LGTV_WebOS_GetForgroundAppInfo($);
|
||||
sub LGTV_WebOS_GetAudioStatus($);
|
||||
sub LGTV_WebOS_TimerStatusRequest($);
|
||||
|
||||
|
||||
|
||||
|
||||
my %lgCommands = (
|
||||
|
||||
"getServiceList" => ["ssap://api/getServiceList"],
|
||||
"getChannelList" => ["ssap://tv/getChannelList"],
|
||||
"getVolume" => ["ssap://audio/getVolume"],
|
||||
"getAudioStatus" => ["ssap://audio/getStatus"],
|
||||
"getCurrentChannel" => ["ssap://tv/getCurrentChannel"],
|
||||
"getChannelProgramInfo" => ["ssap://tv/getChannelProgramInfo"],
|
||||
"getForegroundAppInfo" => ["ssap://com.webos.applicationManager/getForegroundAppInfo"],
|
||||
"getAppList" => ["ssap://com.webos.applicationManager/listApps"],
|
||||
"screenMsg" => ["ssap://system.notifications/createToast", "message"],
|
||||
"mute" => ["ssap://audio/setMute", "mute"],
|
||||
"powerOff" => ["ssap://system/turnOff"],
|
||||
"powerOn" => ["ssap://system/turnOn"],
|
||||
"volume" => ["ssap://audio/setVolume", "volume"],
|
||||
"volumeUp" => ["ssap://audio/volumeUp"],
|
||||
"volumeDown" => ["ssap://audio/volumeDown"],
|
||||
"channelDown" => ["ssap://tv/channelDown"],
|
||||
"channelUp" => ["ssap://tv/channelUp"],
|
||||
"openChannel" => ["ssap://tv/openChannel", "channel"],
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
sub LGTV_WebOS_Initialize($) {
|
||||
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "LGTV_WebOS_Read";
|
||||
$hash->{WriteFn} = "LGTV_WebOS_Write";
|
||||
|
||||
|
||||
# Consumer
|
||||
$hash->{SetFn} = "LGTV_WebOS_Set";
|
||||
$hash->{DefFn} = "LGTV_WebOS_Define";
|
||||
$hash->{UndefFn} = "LGTV_WebOS_Undef";
|
||||
$hash->{AttrFn} = "LGTV_WebOS_Attr";
|
||||
$hash->{AttrList} = "disable:1 ".
|
||||
$readingFnAttributes;
|
||||
|
||||
|
||||
foreach my $d(sort keys %{$modules{LGTV_WebOS}{defptr}}) {
|
||||
my $hash = $modules{LGTV_WebOS}{defptr}{$d};
|
||||
$hash->{VERSION} = $version;
|
||||
}
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Define($$) {
|
||||
|
||||
my ( $hash, $def ) = @_;
|
||||
|
||||
my @a = split( "[ \t][ \t]*", $def );
|
||||
|
||||
|
||||
return "too few parameters: define <name> LGTV_WebOS <HOST>" if( @a != 3 );
|
||||
|
||||
|
||||
|
||||
my $name = $a[0];
|
||||
my $host = $a[2];
|
||||
|
||||
$hash->{HOST} = $host;
|
||||
$hash->{VERSION} = $version;
|
||||
|
||||
|
||||
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} ) );
|
||||
$attr{$name}{room} = 'LGTV' if( !defined( $attr{$name}{room} ) );
|
||||
|
||||
readingsSingleUpdate($hash,'state','off', 1);
|
||||
|
||||
|
||||
$modules{LGTV_WebOS}{defptr}{$hash->{HOST}} = $hash;
|
||||
|
||||
|
||||
if( $init_done ) {
|
||||
LGTV_WebOS_Open($hash);
|
||||
} else {
|
||||
InternalTimer( gettimeofday()+15, "LGTV_WebOS_Open", $hash, 0 );
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
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" ) {
|
||||
readingsSingleUpdate ( $hash, "state", "disabled", 1 );
|
||||
Log3 $name, 3, "LGTV_WebOS ($name) - disabled";
|
||||
}
|
||||
|
||||
elsif( $cmd eq "del" ) {
|
||||
readingsSingleUpdate ( $hash, "state", "active", 1 );
|
||||
Log3 $name, 3, "LGTV_WebOS ($name) - enabled";
|
||||
}
|
||||
}
|
||||
|
||||
if( $attrName eq "disabledForIntervals" ) {
|
||||
if( $cmd eq "set" ) {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_TimerStatusRequest($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
RemoveInternalTimer($hash,'LGTV_WebOS_TimerStatusRequest');
|
||||
|
||||
if( !IsDisabled($name) and $hash->{CD} ) {
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - run get functions";
|
||||
|
||||
InternalTimer( gettimeofday()+1, 'LGTV_WebOS_GetAudioStatus', $hash, 0 );
|
||||
InternalTimer( gettimeofday()+2, 'LGTV_WebOS_GetCurrentChannel', $hash, 0 );
|
||||
InternalTimer( gettimeofday()+3, 'LGTV_WebOS_GetForgroundAppInfo', $hash, 0 );
|
||||
|
||||
} elsif( IsDisabled($name) ) {
|
||||
readingsSingleUpdate ( $hash, "state", "disabled", 1 );
|
||||
}
|
||||
|
||||
|
||||
LGTV_WebOS_Open($hash) if( !IsDisabled($name) and not $hash->{CD} );
|
||||
|
||||
InternalTimer( gettimeofday()+10,"LGTV_WebOS_TimerStatusRequest", $hash, 1 );
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Set($@) {
|
||||
|
||||
my ($hash, $name, $cmd, @args) = @_;
|
||||
my ($arg, @params) = @args;
|
||||
|
||||
my $uri;
|
||||
my %payload;
|
||||
|
||||
|
||||
if($cmd eq 'connect') {
|
||||
return "usage: connect" if( @args != 0 );
|
||||
|
||||
LGTV_WebOS_Open($hash);
|
||||
|
||||
return undef;
|
||||
|
||||
} elsif($cmd eq 'pairing') {
|
||||
return "usage: pairing" if( @args != 0 );
|
||||
|
||||
LGTV_WebOS_Pairing($hash);
|
||||
|
||||
return undef;
|
||||
|
||||
} elsif($cmd eq 'screenMsg') {
|
||||
return "usage: screenMsg <message>" if( @args < 1 );
|
||||
|
||||
$payload{$lgCommands{$cmd}->[1]} = join(" ", @args);
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'on' or $cmd eq 'off') {
|
||||
return "usage: on/off" if( @args != 0 );
|
||||
|
||||
if($cmd eq 'off') {
|
||||
$uri = $lgCommands{powerOff};
|
||||
} elsif ($cmd eq 'on') {
|
||||
$uri = $lgCommands{powerOn};
|
||||
}
|
||||
|
||||
} elsif($cmd eq 'mute') {
|
||||
return "usage: mute" if( @args != 1 );
|
||||
|
||||
if($args[0] eq 'off') {
|
||||
|
||||
$payload{$lgCommands{$cmd}->[1]} = 'false';
|
||||
|
||||
} elsif($args[0] eq 'on') {
|
||||
|
||||
$payload{$lgCommands{$cmd}->[1]} = 'true';
|
||||
}
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'volume') {
|
||||
return "usage: volume" if( @args != 1 );
|
||||
|
||||
$payload{$lgCommands{$cmd}->[1]} = join(" ", @args);
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'volumeUp') {
|
||||
return "usage: volumeUp" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'volumeDown') {
|
||||
return "usage: volumeDown" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'channelDown') {
|
||||
return "usage: channelDown" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'channelUp') {
|
||||
return "usage: channelUp" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'openChannel') {
|
||||
return "usage: openChannel" if( @args != 1 );
|
||||
|
||||
$payload{$lgCommands{$cmd}->[1]} = join(" ", @args);
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'getServiceList') {
|
||||
return "usage: getServiceList" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'getChannelProgramInfo') {
|
||||
return "usage: getChannelProgramInfo" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'getChannelList') {
|
||||
return "usage: getChannelList" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} elsif($cmd eq 'getAppList') {
|
||||
return "usage: getAppList" if( @args != 0 );
|
||||
|
||||
$uri = $lgCommands{$cmd}->[0];
|
||||
|
||||
} else {
|
||||
my $list = "";
|
||||
$list .= "connect:noArg pairing:noArg screenMsg mute:on,off volume volumeUp:noArg volumeDown:noArg channelDown:noArg channelUp:noArg openChannel getServiceList:noArg on:noArg off:noArg getAppList:noArg";
|
||||
return "Unknown argument $cmd, choose one of $list";
|
||||
}
|
||||
|
||||
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 = 1;
|
||||
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - Baue Socket Verbindung auf";
|
||||
|
||||
|
||||
my $socket = new IO::Socket::INET ( PeerHost => $host,
|
||||
PeerPort => $port,
|
||||
Proto => 'tcp',
|
||||
Timeout => 0.1
|
||||
)
|
||||
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";
|
||||
|
||||
LGTV_WebOS_Handshake($hash);
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - start Handshake";
|
||||
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Close($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return if( !$hash->{CD} );
|
||||
|
||||
close($hash->{CD}) if($hash->{CD});
|
||||
delete($hash->{FD});
|
||||
delete($hash->{CD});
|
||||
delete($selectlist{$name});
|
||||
|
||||
readingsSingleUpdate($hash, 'state', 'off', 1 );
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - Socket Disconnected";
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Write($@) {
|
||||
|
||||
my ($hash,$string) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - WriteFn called";
|
||||
|
||||
return Log3 $name, 4, "LGTV_WebOS ($name) - socket not connected"
|
||||
unless($hash->{CD});
|
||||
|
||||
Log3 $name, 5, "LGTV_WebOS ($name) - $string";
|
||||
syswrite($hash->{CD}, $string);
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Read($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $len;
|
||||
my $buf;
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - ReadFn gestartet";
|
||||
|
||||
$len = sysread($hash->{CD},$buf,1024); # die genaue Puffergröße wird noch ermittelt
|
||||
|
||||
if( !defined($len) or !$len ) {
|
||||
Log3 $name, 5, "LGTV_WebOS ($name) - connection closed by remote Host";
|
||||
LGTV_WebOS_Close($hash);
|
||||
return;
|
||||
}
|
||||
|
||||
unless( defined $buf) {
|
||||
Log3 $name, 3, "LGTV_WebOS ($name) - Keine Daten empfangen";
|
||||
return;
|
||||
}
|
||||
|
||||
Log3 $name, 5, "LGTV_WebOS ($name) - received buffer data, start response processing: $buf";
|
||||
LGTV_WebOS_ResponseProcessing($hash,$buf);
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Handshake($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
my $host = $hash->{HOST};
|
||||
my $wsKey = encode_base64(gettimeofday());
|
||||
|
||||
my $wsHandshakeCmd = "";
|
||||
$wsHandshakeCmd .= "GET / HTTP/1.1\r\n";
|
||||
$wsHandshakeCmd .= "Host: $host\r\n";
|
||||
$wsHandshakeCmd .= "User-Agent: FHEM\r\n";
|
||||
$wsHandshakeCmd .= "Upgrade: websocket\r\n";
|
||||
$wsHandshakeCmd .= "Connection: Upgrade\r\n";
|
||||
$wsHandshakeCmd .= "Sec-WebSocket-Version: 13\r\n";
|
||||
$wsHandshakeCmd .= "Sec-WebSocket-Key: " . $wsKey . "\r\n";
|
||||
|
||||
LGTV_WebOS_Write($hash,$wsHandshakeCmd);
|
||||
|
||||
$hash->{helper}{wsKey} = $wsKey;
|
||||
|
||||
Log3 $name, 5, "LGTV_WebOS ($name) - send Handshake to WriteFn";
|
||||
|
||||
|
||||
LGTV_WebOS_TimerStatusRequest($hash);
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - start timer status request";
|
||||
|
||||
LGTV_WebOS_Pairing($hash);
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - start pairing routine";
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_ResponseProcessing($$) {
|
||||
|
||||
my ($hash,$response) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
|
||||
|
||||
########################
|
||||
### Response has HTML Header
|
||||
if( $response =~ /HTTP\/1.1 101 Switching Protocols/ ) {
|
||||
|
||||
my $data = $response;
|
||||
my $header = LGTV_WebOS_Header2Hash($data);
|
||||
|
||||
################################
|
||||
### Handshake for first Connect
|
||||
if( defined($header->{'Sec-WebSocket-Accept'})) {
|
||||
|
||||
my $keyAccept = $header->{'Sec-WebSocket-Accept'};
|
||||
Log3 $name, 5, "LGTV_WebOS ($name) - keyAccept: $keyAccept";
|
||||
|
||||
my $wsKey = $hash->{helper}{wsKey};
|
||||
my $expectedResponse = trim(encode_base64(pack('H*', sha1_hex(trim($wsKey)."258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))));
|
||||
|
||||
if ($keyAccept eq $expectedResponse) {
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
elsif( $response =~ m/[\[{].*[}\]]$/ ) {
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - JSON detected, beginn PreResponseProsessing";
|
||||
|
||||
my $json = LGTV_WebOS_PreResponseProsessing($hash,$response);
|
||||
Log3 $name, 5, "LGTV_WebOS ($name) - Corrected JSON String: $json";
|
||||
|
||||
my $decode_json = decode_json($json);
|
||||
|
||||
LGTV_WebOS_WriteReadings($hash,$decode_json);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - no Match found";
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_WriteReadings($$) {
|
||||
|
||||
my ($hash,$decode_json) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $mute;
|
||||
my $response;
|
||||
|
||||
|
||||
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}}) {
|
||||
|
||||
readingsBulkUpdate($hash,'service_'.$services->{name},'v.'.$services->{version});
|
||||
}
|
||||
}
|
||||
|
||||
if( defined($decode_json->{payload}{'mute'}) or defined($decode_json->{payload}{'muted'})) {
|
||||
if( defined($decode_json->{payload}{'mute'}) and $decode_json->{payload}{'mute'} eq 'true' ) {
|
||||
|
||||
readingsBulkUpdate($hash,'mute','on');
|
||||
|
||||
} elsif( defined($decode_json->{payload}{'mute'}) and $decode_json->{payload}{'mute'} eq 'false' ) {
|
||||
|
||||
readingsBulkUpdate($hash,'mute','off');
|
||||
|
||||
}
|
||||
|
||||
if( defined($decode_json->{payload}{'muted'}) and $decode_json->{payload}{'muted'} eq 'true' ) {
|
||||
|
||||
readingsBulkUpdate($hash,'mute','on');
|
||||
|
||||
} elsif( defined($decode_json->{payload}{'muted'}) and $decode_json->{payload}{'muted'} eq 'false' ) {
|
||||
|
||||
readingsBulkUpdate($hash,'mute','off');
|
||||
}
|
||||
}
|
||||
|
||||
if( defined($decode_json->{type}) ) {
|
||||
if( ($decode_json->{type} eq 'response' and $decode_json->{payload}{returnValue} eq 'true') or ($decode_json->{type} eq 'registered') and defined($decode_json->{payload}{'client-key'}) ) {
|
||||
|
||||
$response = 'ok'; # if( $decode_json->{type} eq 'response' and $decode_json->{payload}{returnValue} eq 'true' );
|
||||
readingsBulkUpdate($hash,'pairing','paired');
|
||||
|
||||
} elsif( $decode_json->{type} eq 'error' ) {
|
||||
|
||||
$response = "error - $decode_json->{error}";
|
||||
|
||||
if($decode_json->{error} eq '401 insufficient permissions' or $decode_json->{error} eq '401 insufficient permissions (not registered)') {
|
||||
|
||||
readingsBulkUpdate($hash,'pairing','unpaired');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
readingsBulkUpdate($hash,'lgKey',$decode_json->{payload}{'client-key'});
|
||||
readingsBulkUpdate($hash,'volume',$decode_json->{payload}{'volume'});
|
||||
readingsBulkUpdate($hash,'lastResponse',$response);
|
||||
|
||||
readingsBulkUpdate($hash,'currentChannelNumber',$decode_json->{payload}{'channelNumber'});
|
||||
readingsBulkUpdate($hash,'currentChannelName',$decode_json->{payload}{'channelName'});
|
||||
readingsBulkUpdate($hash,'currentChannelTypeName',$decode_json->{payload}{'channelTypeName'});
|
||||
readingsBulkUpdate($hash,'currentApp',$decode_json->{payload}{'appId'});
|
||||
|
||||
readingsEndUpdate($hash, 1);
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Pairing($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $lgKey;
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - HASH handshakePayload";
|
||||
|
||||
my %handshakePayload = ( "pairingType" => "PROMPT",
|
||||
"manifest" => {
|
||||
"manifestVersion" => 1,
|
||||
"appVersion" => "1.1",
|
||||
"signed" => {
|
||||
"created" => "20161123",
|
||||
"appId" => "com.lge.test",
|
||||
"vendorId" => "com.lge",
|
||||
"localizedAppNames" => {
|
||||
"" => "FHEM LG Remote",
|
||||
"de-DE" => "FHEM LG Fernbedinung"
|
||||
},
|
||||
"localizedVendorNames" => {
|
||||
"" => "LG Electronics"
|
||||
},
|
||||
"permissions" => [
|
||||
"TEST_SECURE",
|
||||
"CONTROL_INPUT_TEXT",
|
||||
"CONTROL_MOUSE_AND_KEYBOARD",
|
||||
"READ_INSTALLED_APPS",
|
||||
"READ_LGE_SDX",
|
||||
"READ_NOTIFICATIONS",
|
||||
"SEARCH",
|
||||
"WRITE_SETTINGS",
|
||||
"WRITE_NOTIFICATION_ALERT",
|
||||
"CONTROL_POWER",
|
||||
"READ_CURRENT_CHANNEL",
|
||||
"READ_RUNNING_APPS",
|
||||
"READ_UPDATE_INFO",
|
||||
"UPDATE_FROM_REMOTE_APP",
|
||||
"READ_LGE_TV_INPUT_EVENTS",
|
||||
"READ_TV_CURRENT_TIME"
|
||||
],
|
||||
"serial" => "2f930e2d2cfe083771f68e4fe7bb07"
|
||||
},
|
||||
"permissions" => [
|
||||
"LAUNCH",
|
||||
"LAUNCH_WEBAPP",
|
||||
"APP_TO_APP",
|
||||
"CLOSE",
|
||||
"TEST_OPEN",
|
||||
"TEST_PROTECTED",
|
||||
"CONTROL_AUDIO",
|
||||
"CONTROL_DISPLAY",
|
||||
"CONTROL_INPUT_JOYSTICK",
|
||||
"CONTROL_INPUT_MEDIA_RECORDING",
|
||||
"CONTROL_INPUT_MEDIA_PLAYBACK",
|
||||
"CONTROL_INPUT_TV",
|
||||
"CONTROL_POWER",
|
||||
"READ_APP_STATUS",
|
||||
"READ_CURRENT_CHANNEL",
|
||||
"READ_INPUT_DEVICE_LIST",
|
||||
"READ_NETWORK_STATE",
|
||||
"READ_RUNNING_APPS",
|
||||
"READ_TV_CHANNEL_LIST",
|
||||
"WRITE_NOTIFICATION_TOAST",
|
||||
"READ_POWER_STATE",
|
||||
"READ_COUNTRY_INFO"
|
||||
],
|
||||
"signatures" => [
|
||||
{
|
||||
"signatureVersion" => 1,
|
||||
"signature" => "eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
my $usedHandshake = \%handshakePayload;
|
||||
|
||||
my $key = ReadingsVal($name, 'lgKey', '');
|
||||
|
||||
$usedHandshake->{'client-key'} = $key if( defined($key));
|
||||
|
||||
LGTV_WebOS_CreateSendCommand($hash, undef, $usedHandshake, 'register');
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - Send pairing informations";
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_CreateSendCommand($$$;$) {
|
||||
|
||||
my ($hash, $uri, $payload, $type) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
my $err;
|
||||
|
||||
|
||||
$type = 'request' if( not defined($type) );
|
||||
|
||||
my $command = {};
|
||||
$command->{'client-key'} = ReadingsVal($name, 'lgKey', '') if( $type ne 'register' );
|
||||
$command->{id} = $type."_".gettimeofday();
|
||||
$command->{type} = $type;
|
||||
$command->{uri} = $uri if($uri);
|
||||
$command->{payload} = $payload if( defined($payload) );
|
||||
|
||||
#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";
|
||||
|
||||
LGTV_WebOS_Write($hash, LGTV_WebOS_Hybi10Encode($cmd, "text", 1));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_Hybi10Encode($;$$) {
|
||||
|
||||
my ($payload, $type, $masked) = @_;
|
||||
|
||||
|
||||
$type //= "text";
|
||||
$masked //= 1;
|
||||
|
||||
my @frameHead;
|
||||
my $frame = "";
|
||||
my $payloadLength = length($payload);
|
||||
|
||||
|
||||
if ($type eq "text") {
|
||||
|
||||
# first byte indicates FIN, Text-Frame (10000001):
|
||||
$frameHead[0] = 129;
|
||||
|
||||
} elsif ($type eq "close") {
|
||||
|
||||
# first byte indicates FIN, Close Frame(10001000):
|
||||
$frameHead[0] = 136;
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
# set mask and payload length (using 1, 3 or 9 bytes)
|
||||
if ($payloadLength > 65535) {
|
||||
|
||||
# TODO
|
||||
my $payloadLengthBin = sprintf('%064b', $payloadLength);
|
||||
$frameHead[1] = ($masked) ? 255 : 127;
|
||||
|
||||
for (my $i = 0; $i < 8; $i++) {
|
||||
|
||||
$frameHead[$i + 2] = oct("0b".substr($payloadLengthBin, $i*8, $i*8+8));
|
||||
}
|
||||
|
||||
# most significant bit MUST be 0 (close connection if frame too big)
|
||||
if ($frameHead[2] > 127) {
|
||||
|
||||
#$this->close(1004);
|
||||
return undef;
|
||||
}
|
||||
|
||||
} elsif ($payloadLength > 125) {
|
||||
|
||||
my $payloadLengthBin = sprintf('%016b', $payloadLength);
|
||||
$frameHead[1] = ($masked) ? 254 : 126;
|
||||
$frameHead[2] = oct("0b".substr($payloadLengthBin, 0, 8));
|
||||
$frameHead[3] = oct("0b".substr($payloadLengthBin, 8, 16));
|
||||
|
||||
} else {
|
||||
|
||||
$frameHead[1] = ($masked) ? $payloadLength + 128 : $payloadLength;
|
||||
}
|
||||
|
||||
# convert frame-head to string:
|
||||
for (my $i = 0; $i < scalar(@frameHead); $i++) {
|
||||
|
||||
$frameHead[$i] = chr($frameHead[$i]);
|
||||
}
|
||||
|
||||
my @mask;
|
||||
if ($masked) {
|
||||
# generate a random mask:
|
||||
for (my $i = 0; $i < 4; $i++) {
|
||||
|
||||
#$mask[$i] = chr(int(rand(255)));
|
||||
$mask[$i] = chr(int(25*$i));
|
||||
}
|
||||
|
||||
@frameHead = (@frameHead, @mask);
|
||||
}
|
||||
|
||||
$frame = join("", @frameHead);
|
||||
|
||||
# append payload to frame:
|
||||
my $char;
|
||||
for (my $i = 0; $i < $payloadLength; $i++) {
|
||||
|
||||
$char = substr($payload, $i, 1);
|
||||
$frame .= ($masked) ? $char ^ $mask[$i % 4] : $char;
|
||||
}
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_GetAudioStatus($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
RemoveInternalTimer($hash,'LGTV_WebOS_GetAudioStatus');
|
||||
LGTV_WebOS_CreateSendCommand($hash,$lgCommands{getAudioStatus},undef);
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_GetCurrentChannel($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
RemoveInternalTimer($hash,'LGTV_WebOS_GetCurrentChannel');
|
||||
LGTV_WebOS_CreateSendCommand($hash,$lgCommands{getCurrentChannel},undef);
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_GetForgroundAppInfo($) {
|
||||
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
RemoveInternalTimer($hash,'LGTV_WebOS_GetForgroundAppInfo');
|
||||
LGTV_WebOS_CreateSendCommand($hash,$lgCommands{getForegroundAppInfo},undef);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#############################################
|
||||
### my little Helper
|
||||
|
||||
sub LGTV_WebOS_Header2Hash($) {
|
||||
|
||||
my ( $string ) = @_;
|
||||
my %hash = ();
|
||||
|
||||
foreach my $line (split("\r\n", $string)) {
|
||||
my ($key,$value) = split( ": ", $line );
|
||||
next if( !$value );
|
||||
|
||||
$value =~ s/^ //;
|
||||
$hash{$key} = $value;
|
||||
}
|
||||
|
||||
return \%hash;
|
||||
}
|
||||
|
||||
sub LGTV_WebOS_PreResponseProsessing($$) {
|
||||
|
||||
my ($hash,$json) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
|
||||
Log3 $name, 4, "LGTV_WebOS ($name) - pre processing response data";
|
||||
|
||||
my $len = length($json);
|
||||
my @letterArray = split("",$json);
|
||||
|
||||
my $letter = "";
|
||||
my $count = 0;
|
||||
my $marker = 0;
|
||||
my $corrected_json;
|
||||
|
||||
for(my $i = 0; $i < $len; $i++) {
|
||||
|
||||
$marker = 1 if($count > 0);
|
||||
$letter = $letterArray[0];
|
||||
|
||||
$count++ if($letter eq '{');
|
||||
$count-- if($letter eq '}');
|
||||
|
||||
$corrected_json .= $letter if($count > 0);
|
||||
|
||||
|
||||
if( $count == 0 and $marker == 1 ) {
|
||||
|
||||
$marker = 0;
|
||||
$corrected_json .= '}';
|
||||
return $corrected_json;
|
||||
}
|
||||
|
||||
shift(@letterArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1;
|
Loading…
Reference in New Issue
Block a user