24_TPLinkHS110: Added patch for viegener for more reliable communication with newer models or firmwares

git-svn-id: https://svn.fhem.de/fhem/trunk@19532 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
vk 2019-06-02 06:38:05 +00:00
parent ead21543e1
commit 1e63f6700d

View File

@ -1,7 +1,7 @@
################################################################ ################################################################
# $Id$ # $Id$
# #
# Release 2018-11-01 SetExtension # Release 2019-05-31 SendCommand2
# #
# Copyright notice # Copyright notice
# #
@ -25,7 +25,7 @@
# In older distribution try "cpan IO::Socket::Timeout" # In older distribution try "cpan IO::Socket::Timeout"
# #
# Origin: # Origin:
# https://github.com/kettenbach-it/FHEM-TPLink-HS110 # https://gitlab.com/volkerkettenbach/FHEM-TPLink-Kasa
# #
################################################################ ################################################################
@ -41,84 +41,154 @@ use Data::Dumper;
##################################### #####################################
sub TPLinkHS110_Initialize($) sub TPLinkHS110_Initialize($) {
{
my ($hash) = @_;
$hash->{DefFn} = "TPLinkHS110_Define";
$hash->{ReadFn} = "TPLinkHS110_Get";
$hash->{SetFn} = "TPLinkHS110_Set";
$hash->{UndefFn} = "TPLinkHS110_Undefine";
$hash->{DeleteFn} = "TPLinkHS110_Delete";
$hash->{AttrFn} = "TPLinkHS110_Attr";
$hash->{AttrList} = "interval ".
"disable:0,1 " .
"nightmode:on,off " .
"timeout " .
"$readingFnAttributes";
}
#####################################
sub TPLinkHS110_Define($$)
{
my ($hash, $def) = @_;
my $name= $hash->{NAME};
my @a = split( "[ \t][ \t]*", $def );
return "Wrong syntax: use define <name> TPLinkHS110 <hostname/ip> " if (int(@a) != 3);
$hash->{INTERVAL}=300;
$hash->{TIMEOUT}=1;
$hash->{HOST}=$a[2];
$attr{$name}{"disable"} = 0;
# initial request after 2 secs, there timer is set to interval for further update
InternalTimer(gettimeofday()+2, "TPLinkHS110_Get", $hash, 0);
Log3 $hash, 3, "TPLinkHS110: $name defined.";
return undef;
}
#####################################
sub TPLinkHS110_Get($$)
{
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME};
my($success,$json,$realtimejson);
return "Device disabled in config" if ($attr{$name}{"disable"} eq "1");
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{INTERVAL}, "TPLinkHS110_Get", $hash, 1);
$hash->{NEXTUPDATE}=localtime(gettimeofday()+$hash->{INTERVAL});
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $hash->{DefFn} = "TPLinkHS110_Define";
$mon++; $hash->{ReadFn} = "TPLinkHS110_Get";
$year += 1900; $hash->{SetFn} = "TPLinkHS110_Set";
$hash->{UndefFn} = "TPLinkHS110_Undefine";
$hash->{DeleteFn} = "TPLinkHS110_Delete";
$hash->{AttrFn} = "TPLinkHS110_Attr";
$hash->{AttrList} = "interval " .
"disable:0,1 " .
"nightmode:on,off " .
"timeout " .
"$readingFnAttributes";
}
#####################################
sub TPLinkHS110_Define($$) {
my ($hash, $def) = @_;
my $name = $hash->{NAME};
my @a = split("[ \t][ \t]*", $def);
return "Wrong syntax: use define <name> TPLinkHS110 <hostname/ip> " if (int(@a) != 3);
$hash->{INTERVAL} = 300;
$hash->{TIMEOUT} = 1;
$hash->{HOST} = $a[2];
$attr{$name}{"disable"} = 0;
# initial request after 2 secs, there timer is set to interval for further update
InternalTimer(gettimeofday() + 2, "TPLinkHS110_Get", $hash, 0);
Log3 $hash, 3, "TPLinkHS110: $name defined.";
return undef;
}
#####################################
# sends given command and returns ($errmsg/undef,undef/$decrypteddata)
sub TPLinkHS110_SendCommand($$) {
my ($hash, $command) = @_;
my $name = $hash->{NAME};
my $remote_host = $hash->{HOST}; my $remote_host = $hash->{HOST};
my $remote_port = 9999; my $remote_port = 9999;
my $command = '{"system":{"get_sysinfo":{}}}';
my $c = encrypt($command); my $c = encrypt($command);
my $socket = IO::Socket::INET->new(PeerAddr => $remote_host, my $socket = IO::Socket::INET->new(PeerAddr => $remote_host,
PeerPort => $remote_port, PeerPort => $remote_port,
Proto => 'tcp', Proto => 'tcp',
Type => SOCK_STREAM, Type => SOCK_STREAM,
Timeout => $hash->{TIMEOUT} ) Timeout => $hash->{TIMEOUT})
or return "Couldn't connect to $remote_host:$remote_port: $@\n"; or return("Couldn't connect to $remote_host:$remote_port: $@\n", undef);
$socket->write($c); $socket->write($c);
IO::Socket::Timeout->enable_timeouts_on($socket); IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(.5); $socket->read_timeout(2.5);
my $data;
$data = <$socket>; my $dlen;
my $res;
my $errmsg;
my $data;
$res = sysread($socket, $dlen, 4);
$dlen = "" if (!defined($res));
if ($res != 4) {
$errmsg = "Could not read 4 length bytes";
}
my $datalen = 0;
if (!defined($errmsg)) {
for (my $i = 0; $i < 4; $i++) {
$datalen *= 256;
$datalen += ord(substr($dlen, $i, 1));
}
Log3 $hash, 4, "TPLinkHS110: $name Get length - " . $datalen; # JV
my $datapart;
$data = "";
my $partlen = 0;
my $remainlen = $datalen;
my $ctr = 0;
while (($remainlen > 0) && (!defined($errmsg))) {
$res = sysread($socket, $datapart, $remainlen);
if (!defined($res) || $res < 0) {
$errmsg = "Data reading failed - received errcode: " . $res;
}
elsif ($res == 0) {
$ctr++;
$errmsg = "Could not read correct length - expected: " . $datalen . " received: " . $partlen if ($ctr > 2);
}
else {
$ctr = 0;
$data .= $datapart;
$remainlen -= $res;
}
}
Log3 $hash, 4, "TPLinkHS110: $name Get read data length - " . length($data); # JV
}
$socket->close(); $socket->close();
readingsBeginUpdate($hash); if (!defined($errmsg)) {
$data = decrypt(substr($data,4)); $data = decrypt($data);
return(undef, $data);
}
else {
return($errmsg, undef);
}
($success,$json) = TPLinkHS110__evaljson($name,$data); }
if(!$success) {
readingsEndUpdate($hash, 1);
return;
#####################################
sub TPLinkHS110_Get($$) {
my ($hash) = @_;
my $name = $hash->{NAME};
my ($success, $json, $realtimejson);
return "Device disabled in config" if ($attr{$name}{"disable"} eq "1");
RemoveInternalTimer($hash);
InternalTimer(gettimeofday() + $hash->{INTERVAL}, "TPLinkHS110_Get", $hash, 1);
$hash->{NEXTUPDATE} = localtime(gettimeofday() + $hash->{INTERVAL});
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
$mon++;
$year += 1900;
my $errmsg;
my $data;
my $command = '{"system":{"get_sysinfo":{}}}';
($errmsg, $data) = TPLinkHS110_SendCommand($hash, $command);
if (defined($errmsg)) {
Log3 $hash, 1, "TPLinkHS110: $name Get failed - " . $errmsg; # JV
return;
}
readingsBeginUpdate($hash);
($success, $json) = TPLinkHS110__evaljson($name, $data);
if (!$success) {
Log3 $hash, 1, "TPLinkHS110: $name Get failed"; # JV
readingsEndUpdate($hash, 1);
return;
} }
Log3 $hash, 3, "TPLinkHS110: $name Get called. Relay state: $json->{'system'}->{'get_sysinfo'}->{'relay_state'}, RSSI: $json->{'system'}->{'get_sysinfo'}->{'rssi'}"; Log3 $hash, 3, "TPLinkHS110: $name Get called. Relay state: $json->{'system'}->{'get_sysinfo'}->{'relay_state'}, RSSI: $json->{'system'}->{'get_sysinfo'}->{'rssi'}";
@ -137,7 +207,7 @@ sub TPLinkHS110_Get($$)
$key = $hwMap{$hw_ver}{'system'}{'get_sysinfo'}{$key}{'name'} $key = $hwMap{$hw_ver}{'system'}{'get_sysinfo'}{$key}{'name'}
} }
readingsBulkUpdate($hash, $key, $sysinfoValue); readingsBulkUpdate($hash, $key, $sysinfoValue);
} }
if ($json->{'system'}->{'get_sysinfo'}->{'relay_state'} == 0) { if ($json->{'system'}->{'get_sysinfo'}->{'relay_state'} == 0) {
readingsBulkUpdate($hash, "state", "off"); readingsBulkUpdate($hash, "state", "off");
} }
@ -145,34 +215,32 @@ sub TPLinkHS110_Get($$)
readingsBulkUpdate($hash, "state", "on"); readingsBulkUpdate($hash, "state", "on");
} }
# If the device is a HS110, get realtime data: # If the device is a HS110, get realtime data:
# if ( 1 == 0 ) {
if ($json->{'system'}->{'get_sysinfo'}->{'model'} eq "HS110(EU)" or $json->{'system'}->{'get_sysinfo'}->{'model'} eq "HS110(UK)") { if ($json->{'system'}->{'get_sysinfo'}->{'model'} eq "HS110(EU)" or $json->{'system'}->{'get_sysinfo'}->{'model'} eq "HS110(UK)") {
my $realtimejcommand='{"emeter":{"get_realtime":{}}}'; my $realtimejcommand = '{"emeter":{"get_realtime":{}}}';
my $rc = encrypt($realtimejcommand); my $rdata;
my $socket = IO::Socket::INET->new(PeerAddr => $remote_host, ($errmsg, $rdata) = TPLinkHS110_SendCommand($hash, $realtimejcommand);
PeerPort => $remote_port, if (defined($errmsg)) {
Proto => 'tcp', Log3 $hash, 1, "TPLinkHS110: $name Get realtime data failed - " . $errmsg; # JV
Type => SOCK_STREAM, readingsEndUpdate($hash, 1);
Timeout => $hash->{TIMEOUT} )
or return "Couldn't connect to $remote_host:$remote_port: $@\n";
$socket->write($rc);
IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(.5);
my $rdata;
$rdata = <$socket>;
$rdata = decrypt(substr($rdata,4));
if (length($rdata)==0) {
Log3 $hash, 1, "TPLinkHS110: $name: Received zero bytes of realtime data. Cannot process realtime data";
return; return;
} }
($success,$realtimejson) = TPLinkHS110__evaljson($name,$rdata); if (length($rdata) == 0) {
if(!$success) { Log3 $hash, 1, "TPLinkHS110: $name: Received zero bytes of realtime data. Cannot process realtime data";
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
return; return;
} else { }
Log3 $hash, 2, "TPLinkHS110: $name Realtime data updated";
} ($success, $realtimejson) = TPLinkHS110__evaljson($name, $rdata);
if (!$success) {
Log3 $hash, 1, "TPLinkHS110: $name: Received zero bytes of realtime data. Cannot process realtime data";
readingsEndUpdate($hash, 1);
return;
}
else {
Log3 $hash, 2, "TPLinkHS110: $name Realtime data updated";
}
my %emeterReadings = (); my %emeterReadings = ();
@ -188,7 +256,8 @@ sub TPLinkHS110_Get($$)
$key2 = $hwMap{$hw_ver}{'emeter'}{'get_realtime'}{$key2}{'name'}; $key2 = $hwMap{$hw_ver}{'emeter'}{'get_realtime'}{$key2}{'name'};
readingsBulkUpdate($hash, $key2, $emeterValue); readingsBulkUpdate($hash, $key2, $emeterValue);
$emeterReadings{$key2} = $emeterValue; $emeterReadings{$key2} = $emeterValue;
} else { }
else {
return "Check supported hw_ver of device: $hw_ver\n"; return "Check supported hw_ver of device: $hw_ver\n";
} }
} }
@ -197,127 +266,114 @@ sub TPLinkHS110_Get($$)
# Get Daily Stats # Get Daily Stats
my $command = '{"emeter":{"get_daystat":{"month":'.$mon.',"year":'.$year.'}}}'; $command = '{"emeter":{"get_daystat":{"month":' . $mon . ',"year":' . $year . '}}}';
my $c = encrypt($command); ($errmsg, $data) = TPLinkHS110_SendCommand($hash, $command);
$socket = IO::Socket::INET->new(PeerAddr => $remote_host, if (defined($errmsg)) {
PeerPort => $remote_port, Log3 $hash, 1, "TPLinkHS110: $name Get daily stats failed - " . $errmsg; # JV
Proto => 'tcp', readingsEndUpdate($hash, 1);
Type => SOCK_STREAM, return;
Timeout => $hash->{TIMEOUT} ) }
or return "Couldn't connect to $remote_host:$remote_port: $@\n";
$socket->write($c);
IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(.5);
my $data;
$data = <$socket>;
$socket->close();
$data = decrypt(substr($data,4));
Log3 $hash, 3, "TPLinkHS110: $name Updating daystat. Data: " . $data; Log3 $hash, 3, "TPLinkHS110: $name Updating daystat. Data: " . $data;
($success,$json) = TPLinkHS110__evaljson($name,$data); ($success, $json) = TPLinkHS110__evaljson($name, $data);
if($success && $json) { if ($success && $json) {
my $total=0;
my $total = 0;
foreach my $key (sort keys @{$json->{'emeter'}->{'get_daystat'}->{'day_list'}}) { foreach my $key (sort keys @{$json->{'emeter'}->{'get_daystat'}->{'day_list'}}) {
foreach my $key2 ($json->{'emeter'}->{'get_daystat'}->{'day_list'}[$key]) { foreach my $key2 ($json->{'emeter'}->{'get_daystat'}->{'day_list'}[$key]) {
if ($hw_ver eq "1.0") { if ($hw_ver eq "1.0") {
$total = $total+ $key2->{'energy'}; $total = $total + $key2->{'energy'};
if ($key2->{'day'} == $mday) { if ($key2->{'day'} == $mday) {
readingsBulkUpdate($hash, "daily_total", sprintf("%.3f", $key2->{'energy'})); readingsBulkUpdate($hash, "daily_total", sprintf("%.3f", $key2->{'energy'}));
} }
} else { }
$total = $total+ $key2->{'energy_wh'}; else {
$total = $total + $key2->{'energy_wh'};
if ($key2->{'day'} == $mday) { if ($key2->{'day'} == $mday) {
readingsBulkUpdate($hash, "daily_total", sprintf("%.3f", $key2->{'energy_wh'}*0.001)); readingsBulkUpdate($hash, "daily_total", sprintf("%.3f", $key2->{'energy_wh'} * 0.001));
} }
} }
} }
} }
my $count=1; my $count = 1;
$count = @{$json->{'emeter'}->{'get_daystat'}->{'day_list'}}; $count = @{$json->{'emeter'}->{'get_daystat'}->{'day_list'}};
if ($hw_ver eq "1.0") {readingsBulkUpdate($hash, "monthly_total", $total);} if ($hw_ver eq "1.0") {readingsBulkUpdate($hash, "monthly_total", $total);}
if ($hw_ver eq "2.0") {readingsBulkUpdate($hash, "monthly_total", $total*0.001);} if ($hw_ver eq "2.0") {readingsBulkUpdate($hash, "monthly_total", $total * 0.001);}
if ($count) { readingsBulkUpdate($hash, "daily_average", $total/$count)}; if ($count) {readingsBulkUpdate($hash, "daily_average", $total / $count)};
Log3 $hash, 2, "TPLinkHS110: $name Daystat updated"; Log3 $hash, 2, "TPLinkHS110: $name Daystat updated";
} else { }
Log3 $hash, 1, "TPLinkHS110: $name Error updating daystat. Success: " . $success . ", json: " . $json; else {
Log3 $hash, 3, "TPLinkHS110: $name Updating readings"; Log3 $hash, 1, "TPLinkHS110: $name Error updating daystat. Success: " . $success . ", json: " . $json;
readingsEndUpdate($hash, 1); Log3 $hash, 3, "TPLinkHS110: $name Updating readings";
Log3 $hash, 3, "TPLinkHS110: $name Get end"; readingsEndUpdate($hash, 1);
Log3 $hash, 3, "TPLinkHS110: $name Get end";
return; return;
} }
} }
Log3 $hash, 3, "TPLinkHS110: $name Updating readings"; Log3 $hash, 3, "TPLinkHS110: $name Updating readings";
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
Log3 $hash, 3, "TPLinkHS110: $name Get end"; Log3 $hash, 3, "TPLinkHS110: $name Get end";
} }
##################################### #####################################
sub TPLinkHS110_Set($$) sub TPLinkHS110_Set($$) {
{ my ($hash, $name, $cmd, @args) = @_;
my ( $hash, $name, $cmd, @args ) = @_;
my $cmdList = "on off"; my $cmdList = "on off";
my($success,$json,$realtimejson); my ($success, $json, $realtimejson);
return "\"set $name\" needs at least one argument" unless(defined($cmd)); return "\"set $name\" needs at least one argument" unless (defined($cmd));
return if ($attr{$name}{"disable"} eq "1"); return if ($attr{$name}{"disable"} eq "1");
Log3 $hash, 3, "TPLinkHS110: $name Set <". $cmd ."> called" if ($cmd !~ /\?/); Log3 $hash, 3, "TPLinkHS110: $name Set <" . $cmd . "> called" if ($cmd !~ /\?/);
my $command=""; my $command = "";
if($cmd eq "on") if ($cmd eq "on") {
{ $command = '{"system":{"set_relay_state":{"state":1}}}';
$command = '{"system":{"set_relay_state":{"state":1}}}'; }
} elsif ($cmd eq "off") {
elsif($cmd eq "off") $command = '{"system":{"set_relay_state":{"state":0}}}';
{ }
$command = '{"system":{"set_relay_state":{"state":0}}}'; else # wenn der übergebene Befehl nicht durch X_Set() verarbeitet werden kann, Weitergabe an SetExtensions
} {
else # wenn der übergebene Befehl nicht durch X_Set() verarbeitet werden kann, Weitergabe an SetExtensions return SetExtensions($hash, $cmdList, $name, $cmd, @args);
{ }
return SetExtensions($hash, $cmdList, $name, $cmd, @args);
}
my $remote_host = $hash->{HOST}; my $errmsg;
my $remote_port = 9999; my $data;
my $c = encrypt($command);
my $socket = IO::Socket::INET->new(PeerAddr => $remote_host,
PeerPort => $remote_port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => $hash->{TIMEOUT})
or return "Couldn't connect to $remote_host:$remote_port: $@\n";
$socket->write($c);
IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(.5);
my $data;
$data = <$socket>;
$socket->close();
readingsBeginUpdate($hash); ($errmsg, $data) = TPLinkHS110_SendCommand($hash, $command);
$data = decrypt(substr($data,4)); if (defined($errmsg)) {
Log3 $hash, 1, "TPLinkHS110: $name Set failed - " . $errmsg;
return;
}
($success,$json) = TPLinkHS110__evaljson($name,$data); readingsBeginUpdate($hash);
if(!$success) {
readingsEndUpdate($hash, 1); ($success, $json) = TPLinkHS110__evaljson($name, $data);
return; if (!$success) {
Log3 $hash, 1, "TPLinkHS110: $name Set failed - parsing";
readingsEndUpdate($hash, 1);
return;
} }
if ($json->{'system'}->{'set_relay_state'}->{'err_code'} eq "0") { if ($json->{'system'}->{'set_relay_state'}->{'err_code'} eq "0") {
TPLinkHS110_Get($hash,""); Log3 $hash, 3, "TPLinkHS110: $name Set OK - get status data";
TPLinkHS110_Get($hash, "");
} else { }
return "Command failed!"; else {
} Log3 $hash, 1, "TPLinkHS110: $name Set failed with error code";
return "Command failed!";
}
return undef; return undef;
} }
##################################### #####################################
sub TPLinkHS110_Undefine($$) sub TPLinkHS110_Undefine($$) {
{
my ($hash, $arg) = @_; my ($hash, $arg) = @_;
my $name= $hash->{NAME}; my $name = $hash->{NAME};
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
Log3 $hash, 3, "TPLinkHS110: $name undefined."; Log3 $hash, 3, "TPLinkHS110: $name undefined.";
return; return;
@ -327,7 +383,7 @@ sub TPLinkHS110_Undefine($$)
##################################### #####################################
sub TPLinkHS110_Delete { sub TPLinkHS110_Delete {
my ($hash, $arg) = @_; my ($hash, $arg) = @_;
my $name= $hash->{NAME}; my $name = $hash->{NAME};
Log3 $hash, 3, "TPLinkHS110: $name deleted."; Log3 $hash, 3, "TPLinkHS110: $name deleted.";
return undef; return undef;
} }
@ -335,13 +391,14 @@ sub TPLinkHS110_Delete {
##################################### #####################################
sub TPLinkHS110_Attr { sub TPLinkHS110_Attr {
my ($cmd,$name,$aName,$aVal) = @_; my ($cmd, $name, $aName, $aVal) = @_;
my $hash = $defs{$name}; my $hash = $defs{$name};
if ($aName eq "interval") { if ($aName eq "interval") {
if ($cmd eq "set") { if ($cmd eq "set") {
$hash->{INTERVAL} = $aVal; $hash->{INTERVAL} = $aVal;
} else { }
else {
$hash->{INTERVAL} = 300; $hash->{INTERVAL} = 300;
} }
Log3 $hash, 3, "TPLinkHS110: $name INTERVAL set to " . $hash->{INTERVAL}; Log3 $hash, 3, "TPLinkHS110: $name INTERVAL set to " . $hash->{INTERVAL};
@ -350,7 +407,8 @@ sub TPLinkHS110_Attr {
if ($aName eq "timeout") { if ($aName eq "timeout") {
if ($cmd eq "set") { if ($cmd eq "set") {
$hash->{TIMEOUT} = $aVal; $hash->{TIMEOUT} = $aVal;
} else { }
else {
$hash->{TIMEOUT} = 1; $hash->{TIMEOUT} = 1;
} }
Log3 $hash, 3, "TPLinkHS110: $name TIMEOUT set to " . $hash->{TIMEOUT}; Log3 $hash, 3, "TPLinkHS110: $name TIMEOUT set to " . $hash->{TIMEOUT};
@ -361,30 +419,30 @@ sub TPLinkHS110_Attr {
if ($cmd eq "set") { if ($cmd eq "set") {
$hash->{NIGHTMODE} = $aVal; $hash->{NIGHTMODE} = $aVal;
Log3 $hash, 3, "TPLinkHS110: $name Nightmode $aVal."; Log3 $hash, 3, "TPLinkHS110: $name Nightmode $aVal.";
$command = '{"system":{"set_led_off":{"off":1}}}' if ($aVal eq "on"); $command = '{"system":{"set_led_off":{"off":1}}}' if ($aVal eq "on");
$command = '{"system":{"set_led_off":{"off":0}}}' if ($aVal eq "off"); $command = '{"system":{"set_led_off":{"off":0}}}' if ($aVal eq "off");
} }
if ($cmd eq "del") { if ($cmd eq "del") {
Log3 $hash, 3, "TPLinkHS110: $name Nightmode attribute removed. Nightmode disabled."; Log3 $hash, 3, "TPLinkHS110: $name Nightmode attribute removed. Nightmode disabled.";
$command = '{"system":{"set_led_off":{"off":0}}}'; $command = '{"system":{"set_led_off":{"off":0}}}';
$hash->{NIGHTMODE} = "off"; $hash->{NIGHTMODE} = "off";
} }
my $remote_host = $hash->{HOST}; my $remote_host = $hash->{HOST};
my $remote_port = 9999; my $remote_port = 9999;
my $c = encrypt($command); my $c = encrypt($command);
my $socket = IO::Socket::INET->new(PeerAddr => $remote_host, my $socket = IO::Socket::INET->new(PeerAddr => $remote_host,
PeerPort => $remote_port, PeerPort => $remote_port,
Proto => 'tcp', Proto => 'tcp',
Type => SOCK_STREAM, Type => SOCK_STREAM,
Timeout => $hash->{TIMEOUT} ) Timeout => $hash->{TIMEOUT} )
or return "Couldn't connect to $remote_host:$remote_port: $@\n"; or return "Couldn't connect to $remote_host:$remote_port: $@\n";
$socket->write($c); $socket->write($c);
IO::Socket::Timeout->enable_timeouts_on($socket); IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(.5); $socket->read_timeout(.5);
my $data; my $data;
$data = <$socket>; $data = <$socket>;
$socket->close(); $socket->close();
$data = decrypt(substr($data,4)); $data = decrypt(substr($data, 4));
my $json; my $json;
eval { eval {
$json = decode_json($data); $json = decode_json($data);
@ -400,61 +458,62 @@ sub TPLinkHS110_Attr {
# XOR Autokey Cipher with starting key = 171 # XOR Autokey Cipher with starting key = 171
# Based on https://www.softscheck.com/en/reverse-engineering-tp-link-hs110/ # Based on https://www.softscheck.com/en/reverse-engineering-tp-link-hs110/
sub encrypt { sub encrypt {
my $key = 171; my $key = 171;
my @string=split(//, $_[0]); my @string = split(//, $_[0]);
my $result = "\0\0\0".chr(@string); my $result = "\0\0\0" . chr(@string);
foreach (@string) { foreach (@string) {
my $a = $key ^ ord($_); my $a = $key ^ ord($_);
$key = $a; $key = $a;
$result .= chr($a); $result .= chr($a);
} }
return $result; return $result;
} }
sub decrypt { sub decrypt {
my $key = 171; my $key = 171;
my $result = ""; my $result = "";
my @string=split(//, $_[0]); my @string = split(//, $_[0]);
foreach (@string) { foreach (@string) {
my $a = $key ^ ord($_); my $a = $key ^ ord($_);
$key = ord($_); $key = ord($_);
$result .= chr($a); $result .= chr($a);
} }
return $result; return $result;
} }
# mapping for different hardware versions # mapping for different hardware versions
sub hwMapping { sub hwMapping {
my %hwMap = (); my %hwMap = ();
$hwMap{'1.0'}{'system'}{'get_sysinfo'}{'longitude'}{'name'} = 'longitude'; $hwMap{'1.0'}{'system'}{'get_sysinfo'}{'longitude'}{'name'} = 'longitude';
$hwMap{'1.0'}{'system'}{'get_sysinfo'}{'longitude'}{'factor'} = 1; $hwMap{'1.0'}{'system'}{'get_sysinfo'}{'longitude'}{'factor'} = 1;
$hwMap{'1.0'}{'system'}{'get_sysinfo'}{'latitude'}{'name'} = 'latitude'; $hwMap{'1.0'}{'system'}{'get_sysinfo'}{'latitude'}{'name'} = 'latitude';
$hwMap{'1.0'}{'system'}{'get_sysinfo'}{'latitude'}{'factor'} = 1; $hwMap{'1.0'}{'system'}{'get_sysinfo'}{'latitude'}{'factor'} = 1;
$hwMap{'2.0'}{'system'}{'get_sysinfo'}{'longitude_i'}{'name'} = 'longitude'; $hwMap{'2.0'}{'system'}{'get_sysinfo'}{'longitude_i'}{'name'} = 'longitude';
$hwMap{'2.0'}{'system'}{'get_sysinfo'}{'longitude_i'}{'factor'} = 0.0001; $hwMap{'2.0'}{'system'}{'get_sysinfo'}{'longitude_i'}{'factor'} = 0.0001;
$hwMap{'2.0'}{'system'}{'get_sysinfo'}{'latitude_i'}{'name'} = 'latitude'; $hwMap{'2.0'}{'system'}{'get_sysinfo'}{'latitude_i'}{'name'} = 'latitude';
$hwMap{'2.0'}{'system'}{'get_sysinfo'}{'latitude_i'}{'factor'} = 0.0001; $hwMap{'2.0'}{'system'}{'get_sysinfo'}{'latitude_i'}{'factor'} = 0.0001;
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'power'}{'name'} = 'power'; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'power'}{'name'} = 'power';
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'power'}{'factor'} = 1; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'power'}{'factor'} = 1;
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'voltage'}{'name'} = 'voltage'; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'voltage'}{'name'} = 'voltage';
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'voltage'}{'factor'} = 1; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'voltage'}{'factor'} = 1;
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'current'}{'name'} = 'current'; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'current'}{'name'} = 'current';
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'current'}{'factor'} = 1; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'current'}{'factor'} = 1;
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'total'}{'name'} = 'total'; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'total'}{'name'} = 'total';
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'total'}{'factor'} = 1; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'total'}{'factor'} = 1;
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'err_code'}{'name'} = 'err_code'; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'err_code'}{'name'} = 'err_code';
$hwMap{'1.0'}{'emeter'}{'get_realtime'}{'err_code'}{'factor'} = 1; $hwMap{'1.0'}{'emeter'}{'get_realtime'}{'err_code'}{'factor'} = 1;
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'power_mw'}{'name'} = 'power'; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'power_mw'}{'name'} = 'power';
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'power_mw'}{'factor'} = 0.001; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'power_mw'}{'factor'} = 0.001;
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'voltage_mv'}{'name'} = 'voltage'; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'voltage_mv'}{'name'} = 'voltage';
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'voltage_mv'}{'factor'} = 0.001; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'voltage_mv'}{'factor'} = 0.001;
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'current_ma'}{'name'} = 'current'; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'current_ma'}{'name'} = 'current';
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'current_ma'}{'factor'} = 0.001; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'current_ma'}{'factor'} = 0.001;
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'total_wh'}{'name'} = 'total'; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'total_wh'}{'name'} = 'total';
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'total_wh'}{'factor'} = 0.001; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'total_wh'}{'factor'} = 0.001;
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'err_code'}{'name'} = 'err_code'; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'err_code'}{'name'} = 'err_code';
$hwMap{'2.0'}{'emeter'}{'get_realtime'}{'err_code'}{'factor'} = 1; $hwMap{'2.0'}{'emeter'}{'get_realtime'}{'err_code'}{'factor'} = 1;
return %hwMap; return %hwMap;
} }
@ -462,29 +521,30 @@ sub hwMapping {
############################################################################### ###############################################################################
# Test ob JSON-String empfangen wurde # Test ob JSON-String empfangen wurde
sub TPLinkHS110__evaljson($$) { sub TPLinkHS110__evaljson($$) {
my ($name,$data)= @_; my ($name, $data) = @_;
my $hash = $defs{$name}; my $hash = $defs{$name};
my $json; my $json;
my $success = 1; my $success = 1;
my $jerr = "ok"; my $jerr = "ok";
Log3 $name, 5, "$name - Data returned: ". Dumper $data; Log3 $name, 5, "$name - Data returned: " . Dumper $data;
eval {$json = decode_json($data);} or do eval {$json = decode_json($data);} or do
{ {
$success = 0; $success = 0;
}; };
if($@) { if ($@) {
$jerr = $@; $jerr = $@;
}; };
readingsBulkUpdate($hash, "decode_json", $jerr); readingsBulkUpdate($hash, "decode_json", $jerr);
if($success) { if ($success) {
return($success,$json); return($success, $json);
} else { }
return($success,undef); else {
} return($success, undef);
}
} }
###################################################################################### ######################################################################################