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,8 +41,7 @@ use Data::Dumper;
##################################### #####################################
sub TPLinkHS110_Initialize($) sub TPLinkHS110_Initialize($) {
{
my ($hash) = @_; my ($hash) = @_;
$hash->{DefFn} = "TPLinkHS110_Define"; $hash->{DefFn} = "TPLinkHS110_Define";
@ -59,8 +58,7 @@ sub TPLinkHS110_Initialize($)
} }
##################################### #####################################
sub TPLinkHS110_Define($$) sub TPLinkHS110_Define($$) {
{
my ($hash, $def) = @_; my ($hash, $def) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -79,10 +77,89 @@ sub TPLinkHS110_Define($$)
return undef; 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_port = 9999;
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", undef);
$socket->write($c);
IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(2.5);
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();
if (!defined($errmsg)) {
$data = decrypt($data);
return(undef, $data);
}
else {
return($errmsg, undef);
}
}
##################################### #####################################
sub TPLinkHS110_Get($$) sub TPLinkHS110_Get($$) {
{
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my ($success, $json, $realtimejson); my ($success, $json, $realtimejson);
@ -95,28 +172,21 @@ sub TPLinkHS110_Get($$)
$mon++; $mon++;
$year += 1900; $year += 1900;
my $remote_host = $hash->{HOST}; my $errmsg;
my $remote_port = 9999;
my $command = '{"system":{"get_sysinfo":{}}}';
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; my $data;
$data = <$socket>;
$socket->close(); 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); readingsBeginUpdate($hash);
$data = decrypt(substr($data,4));
($success, $json) = TPLinkHS110__evaljson($name, $data); ($success, $json) = TPLinkHS110__evaljson($name, $data);
if (!$success) { if (!$success) {
Log3 $hash, 1, "TPLinkHS110: $name Get failed"; # JV
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
return; return;
} }
@ -145,32 +215,30 @@ 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 $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($rc);
IO::Socket::Timeout->enable_timeouts_on($socket);
$socket->read_timeout(.5);
my $rdata; my $rdata;
$rdata = <$socket>; ($errmsg, $rdata) = TPLinkHS110_SendCommand($hash, $realtimejcommand);
$rdata = decrypt(substr($rdata,4)); if (defined($errmsg)) {
Log3 $hash, 1, "TPLinkHS110: $name Get realtime data failed - " . $errmsg; # JV
readingsEndUpdate($hash, 1);
return;
}
if (length($rdata) == 0) { if (length($rdata) == 0) {
Log3 $hash, 1, "TPLinkHS110: $name: Received zero bytes of realtime data. Cannot process realtime data"; Log3 $hash, 1, "TPLinkHS110: $name: Received zero bytes of realtime data. Cannot process realtime data";
readingsEndUpdate($hash, 1);
return; return;
} }
($success, $realtimejson) = TPLinkHS110__evaljson($name, $rdata); ($success, $realtimejson) = TPLinkHS110__evaljson($name, $rdata);
if (!$success) { 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 { }
else {
Log3 $hash, 2, "TPLinkHS110: $name Realtime data updated"; Log3 $hash, 2, "TPLinkHS110: $name Realtime data updated";
} }
@ -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,26 +266,19 @@ 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]) {
@ -225,7 +287,8 @@ sub TPLinkHS110_Get($$)
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 { }
else {
$total = $total + $key2->{'energy_wh'}; $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));
@ -240,7 +303,8 @@ sub TPLinkHS110_Get($$)
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 { }
else {
Log3 $hash, 1, "TPLinkHS110: $name Error updating daystat. Success: " . $success . ", json: " . $json; Log3 $hash, 1, "TPLinkHS110: $name Error updating daystat. Success: " . $success . ", json: " . $json;
Log3 $hash, 3, "TPLinkHS110: $name Updating readings"; Log3 $hash, 3, "TPLinkHS110: $name Updating readings";
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
@ -255,8 +319,7 @@ sub TPLinkHS110_Get($$)
##################################### #####################################
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);
@ -265,12 +328,10 @@ sub TPLinkHS110_Set($$)
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
@ -278,35 +339,31 @@ sub TPLinkHS110_Set($$)
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 $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; my $data;
$data = <$socket>;
$socket->close(); ($errmsg, $data) = TPLinkHS110_SendCommand($hash, $command);
if (defined($errmsg)) {
Log3 $hash, 1, "TPLinkHS110: $name Set failed - " . $errmsg;
return;
}
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
$data = decrypt(substr($data,4));
($success, $json) = TPLinkHS110__evaljson($name, $data); ($success, $json) = TPLinkHS110__evaljson($name, $data);
if (!$success) { if (!$success) {
Log3 $hash, 1, "TPLinkHS110: $name Set failed - parsing";
readingsEndUpdate($hash, 1); readingsEndUpdate($hash, 1);
return; return;
} }
if ($json->{'system'}->{'set_relay_state'}->{'err_code'} eq "0") { if ($json->{'system'}->{'set_relay_state'}->{'err_code'} eq "0") {
Log3 $hash, 3, "TPLinkHS110: $name Set OK - get status data";
TPLinkHS110_Get($hash, ""); TPLinkHS110_Get($hash, "");
} else { }
else {
Log3 $hash, 1, "TPLinkHS110: $name Set failed with error code";
return "Command failed!"; return "Command failed!";
} }
return undef; return undef;
@ -314,8 +371,7 @@ sub TPLinkHS110_Set($$)
##################################### #####################################
sub TPLinkHS110_Undefine($$) sub TPLinkHS110_Undefine($$) {
{
my ($hash, $arg) = @_; my ($hash, $arg) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
@ -341,7 +397,8 @@ sub TPLinkHS110_Attr {
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};
@ -410,6 +468,7 @@ sub encrypt {
} }
return $result; return $result;
} }
sub decrypt { sub decrypt {
my $key = 171; my $key = 171;
my $result = ""; my $result = "";
@ -482,7 +541,8 @@ sub TPLinkHS110__evaljson($$) {
if ($success) { if ($success) {
return($success, $json); return($success, $json);
} else { }
else {
return($success, undef); return($success, undef);
} }
} }