diff --git a/fhem/contrib/70_SolarView.pm b/fhem/contrib/70_SolarView.pm index b4e0b2cd7..1cbacf2ad 100644 --- a/fhem/contrib/70_SolarView.pm +++ b/fhem/contrib/70_SolarView.pm @@ -1,207 +1,252 @@ -############################################################################## -# -# 70_SolarView.pm -# -# A FHEM module to read power/energy values from solarview. -# -# written 2012 by Tobe Toben -# -# $Id$ -# -############################################################################## -# -# SolarView is a powerful datalogger for photovoltaic systems that runs on -# an AVM Fritz!Box (and also on x86 systems). For details see the SV homepage: -# http://www.amhamberg.de/solarview_fritzbox.aspx -# -# SV supports many different inverters. To read the SV power values using -# this module, a TCP-Server must be enabled for SV by adding the parameter -# "-TCP " to the startscript (see the SV manual). -# -# usage: -# define SolarView [ []] -# -# If is positive, new values are read every seconds. -# If is 0, new values are read whenever a get request is called -# on . The default for is 300 (i.e. 5 minutes). -# -# get -# -# where is one of currentPower, totalEnergy, totalEnergyDay, -# totalEnergyMonth, totalEnergyYear and temperature. -# -############################################################################## -# -# Copyright notice -# -# (c) 2012 Tobe Toben -# -# 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 -# (at your option) any later version. -# -# The GNU General Public License can be found at -# http://www.gnu.org/copyleft/gpl.html. -# -# 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. -# -# This copyright notice MUST APPEAR in all copies of the script! -# -############################################################################## - -package main; - -use strict; -use warnings; - -use IO::Socket::INET; - -sub -SolarView_Initialize($) -{ - my ($hash) = @_; - - $hash->{DefFn} = "SolarView_Define"; - $hash->{UndefFn} = "SolarView_Undef"; - $hash->{GetFn} = "SolarView_Get"; - $hash->{AttrList} = "loglevel:0,1,2,3,4,5"; -} - -sub -SolarView_Define($$) -{ - my ($hash, $def) = @_; - - my @args = split("[ \t]+", $def); - - if (@args < 4) - { - return "SolarView_Define: too few arguments. Usage:\n" . - "define SolarView [ []]"; - } - - $hash->{HOST} = $args[2]; - $hash->{PORT} = $args[3]; - $hash->{INTERVAL} = (@args>=5) ? int($args[4]) : 300; - $hash->{TIMEOUT} = (@args>=6) ? int($args[5]) : 4; - $hash->{INVALID} = -1; - - SolarView_Update($hash); - - $hash->{STATE} = 'Initialized'; - - Log 2, "$hash->{NAME} will read power values from solarview at $hash->{HOST}:$hash->{PORT} " . - ($hash->{INTERVAL} ? "every $hash->{INTERVAL} seconds" : "for every 'get $hash->{NAME} ' request"); - - return undef; -} - -sub -SolarView_Update($) -{ - my ($hash) = @_; - - my $timenow = TimeNow(); - - my %gets = ('totalEnergy' => $hash->{INVALID}, - 'totalEnergyDay' => $hash->{INVALID}, - 'totalEnergyMonth' => $hash->{INVALID}, - 'totalEnergyYear' => $hash->{INVALID}, - 'currentPower' => $hash->{INVALID}, - 'temperature' => $hash->{INVALID},); - - if ($hash->{INTERVAL} > 0) { - InternalTimer(gettimeofday() + $hash->{INTERVAL}, "SolarView_Update", $hash, 0); - } - - Log 4, "$hash->{NAME} tries to connect solarview at $hash->{HOST}:$hash->{PORT}"; - - eval { - local $SIG{ALRM} = sub { die 'timeout'; }; - alarm $hash->{TIMEOUT}; - - my $socket = IO::Socket::INET->new(PeerAddr => $hash->{HOST}, - PeerPort => $hash->{PORT}, - Timeout => $hash->{TIMEOUT}); - - if ($socket and $socket->connected()) - { - $socket->autoflush(1); - print $socket "00*\r\n"; - my $res = <$socket>; - $timenow = TimeNow(); - close($socket); - alarm 0; - - if ($res and $res =~ /^\{(00,.*)\},.+$/) - { - my @vals = split(/,/, $1); - - $gets{'totalEnergyDay'} = 0 + $vals[6] if defined($vals[6]); - $gets{'totalEnergyMonth'} = 0 + $vals[7] if defined($vals[7]); - $gets{'totalEnergyYear'} = 0 + $vals[8] if defined($vals[8]); - $gets{'totalEnergy'} = 0 + $vals[9] if defined($vals[9]); - $gets{'currentPower'} = 0 + $vals[10] if defined($vals[10]); - $gets{'temperature'} = 0 + $vals[19] if defined($vals[19]); - } - } - }; - - alarm 0; - - if ($gets{'currentPower'} != $hash->{INVALID}) { - Log 4, "$hash->{NAME} got fresh values from solarview, currentPower: $gets{'currentPower'}"; - } else { - Log 4, "$hash->{NAME} was unable to get fresh values from solarview"; - } - - while ( my ($key,$val) = each(%gets) ) - { - $hash->{READINGS}{$key}{VAL} = $val; - $hash->{READINGS}{$key}{TIME} = $timenow; - - Log 5, "$hash->{NAME} $key => $gets{$key}"; - } - - return undef; -} - -sub -SolarView_Get($@) -{ - my ($hash, @args) = @_; - - return 'SolarView_Get needs two arguments' if (@args != 2); - - SolarView_Update($hash) unless $hash->{INTERVAL}; - - my $get = $args[1]; - my $val = $hash->{INVALID}; - - if (defined($hash->{READINGS}{$get})) { - $val = $hash->{READINGS}{$get}{VAL}; - } else { - return "SolarView_Get: no such reading: $get"; - } - - Log 3, "$args[0] $get => $val"; - - return $val; -} - -sub -SolarView_Undef($$) -{ - my ($hash, $args) = @_; - - RemoveInternalTimer($hash) if $hash->{INTERVAL}; - - return undef; -} - -1; - +############################################################################## +# +# 70_SolarView.pm +# +# A FHEM module to read power/energy values from solarview. +# +# written 2012 by Tobe Toben +# +# $Id$ +# +############################################################################## +# +# SolarView is a powerful datalogger for photovoltaic systems that runs on +# an AVM Fritz!Box (and also on x86 systems). For details see the SV homepage: +# http://www.amhamberg.de/solarview_fritzbox.aspx +# +# SV supports many different inverters. To read the SV power values using +# this module, a TCP-Server must be enabled for SV by adding the parameter +# "-TCP " to the startscript (see the SV manual). +# +# usage: +# define SolarView [ []] +# +# If is positive, new values are read every seconds. +# If is 0, new values are read whenever a get request is called +# on . The default for is 300 (i.e. 5 minutes). +# +# get +# +# where is one of currentPower, totalEnergy, totalEnergyDay, +# totalEnergyMonth, totalEnergyYear, UDC, IDC, UDCB, IDCB, UDCC, IDCC, +# gridVoltage, gridPower and temperature. +# +############################################################################## +# +# Copyright notice +# +# (c) 2012 Tobe Toben +# +# 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 +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# +# 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +############################################################################## + +package main; + +use strict; +use warnings; + +use IO::Socket::INET; + +my @gets = ('totalEnergyDay', # kWh + 'totalEnergyMonth', # kWh + 'totalEnergyYear', # kWh + 'totalEnergy', # kWh + 'currentPower', # W + 'UDC', 'IDC', 'UDCB', # V, A, V + 'IDCB', 'UDCC', 'IDCC', # A, V, A + 'gridVoltage', 'gridPower', # V, A + 'temperature'); # °C + +sub +SolarView_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "SolarView_Define"; + $hash->{UndefFn} = "SolarView_Undef"; + $hash->{GetFn} = "SolarView_Get"; + $hash->{AttrList} = "loglevel:0,1,2,3,4,5"; +} + +sub +SolarView_Define($$) +{ + my ($hash, $def) = @_; + + my @args = split("[ \t]+", $def); + + if (@args < 4) + { + return "SolarView_Define: too few arguments. Usage:\n" . + "define SolarView [ []]"; + } + + $hash->{Host} = $args[2]; + $hash->{Port} = $args[3]; + $hash->{Interval} = (@args>=5) ? int($args[4]) : 300; + $hash->{Timeout} = (@args>=6) ? int($args[5]) : 4; + + $hash->{Invalid} = -1; + $hash->{NightOff} = 1; + $hash->{Debounce} = 50; + $hash->{Sleep} = 0; + + $hash->{STATE} = 'Initializing'; + + my $timenow = TimeNow(); + + for my $get (@gets) + { + $hash->{READINGS}{$get}{VAL} = $hash->{Invalid}; + $hash->{READINGS}{$get}{TIME} = $timenow; + } + + SolarView_Update($hash); + + Log 2, "$hash->{NAME} will read from solarview at $hash->{Host}:$hash->{Port} " . + ($hash->{Interval} ? "every $hash->{Interval} seconds" : "for every 'get $hash->{NAME} ' request"); + + return undef; +} + +sub +SolarView_Update($) +{ + my ($hash) = @_; + + if ($hash->{Interval} > 0) { + InternalTimer(gettimeofday() + $hash->{Interval}, "SolarView_Update", $hash, 0); + } + + # if NightOff is set and there has been a successful + # reading before, then skip this update "at night" + # + if ($hash->{NightOff} and $hash->{READINGS}{currentPower}{VAL} != $hash->{Invalid}) + { + my ($sec,$min,$hour) = localtime(time); + return undef if ($hour < 6 or $hour > 22); + } + + sleep($hash->{Sleep}) if $hash->{Sleep}; + + Log 4, "$hash->{NAME} tries to connect solarview at $hash->{Host}:$hash->{Port}"; + + my $success = 0; + + eval { + local $SIG{ALRM} = sub { die 'timeout'; }; + alarm $hash->{Timeout}; + + my $socket = IO::Socket::INET->new(PeerAddr => $hash->{Host}, + PeerPort => $hash->{Port}, + Timeout => $hash->{Timeout}); + + if ($socket and $socket->connected()) + { + $socket->autoflush(1); + print $socket "00*\r\n"; + my $res = <$socket>; + close($socket); + + alarm 0; + + if ($res and $res =~ /^\{(00,[\d\.,]+)\},/) + { + my @vals = split(/,/, $1); + + my $tn = sprintf("%04d-%02d-%02d %02d:%02d:00", + $vals[3], $vals[2], $vals[1], $vals[4], $vals[5]); + + my $cpVal = $hash->{READINGS}{currentPower}{VAL}; + my $cpTime = $hash->{READINGS}{currentPower}{TIME}; + + for my $i (6..19) + { + my $getIdx = $i-6; + + if (defined($vals[$i])) + { + $hash->{READINGS}{$gets[$getIdx]}{VAL} = 0 + $vals[$i]; + $hash->{READINGS}{$gets[$getIdx]}{TIME} = $tn; + } + } + + # if Debounce is enabled, then skip one drop of + # currentPower from 'greater than Debounce' to 'Zero' + # + if ($hash->{Debounce} > 0 and + $hash->{Debounce} < $cpVal and + $hash->{READINGS}{currentPower}{VAL} == 0) + { + $hash->{READINGS}{currentPower}{VAL} = $cpVal; + $hash->{READINGS}{currentPower}{TIME} = $cpTime; + } + + $success = 1; + } + } + }; + + alarm 0; + + if ($success) + { + $hash->{STATE} = 'Initialized'; + Log 4, "$hash->{NAME} got fresh values from solarview"; + } else { + $hash->{STATE} = 'Failure'; + Log 4, "$hash->{NAME} was unable to get fresh values from solarview"; + } + + return undef; +} + +sub +SolarView_Get($@) +{ + my ($hash, @args) = @_; + + return 'SolarView_Get needs two arguments' if (@args != 2); + + SolarView_Update($hash) unless $hash->{Interval}; + + my $get = $args[1]; + my $val = $hash->{Invalid}; + + if (defined($hash->{READINGS}{$get})) { + $val = $hash->{READINGS}{$get}{VAL}; + } else { + return "SolarView_Get: no such reading: $get"; + } + + Log 3, "$args[0] $get => $val"; + + return $val; +} + +sub +SolarView_Undef($$) +{ + my ($hash, $args) = @_; + + RemoveInternalTimer($hash) if $hash->{Interval}; + + return undef; +} + +1; +