| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- ##############################################################################
- #
- # 70_SolarView.pm
- #
- # A FHEM module to read power/energy values from solarview.
- #
- # written 2012 by Tobe Toben <fhem@toben.net>
- #
- # $Id: 70_SolarView.pm 2106 2012-11-10 16:38:07Z borisneubert $
- #
- ##############################################################################
- #
- # 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.solarview.info
- #
- # 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 <port>" to the startscript (see the SV manual).
- #
- # usage:
- # define <name> SolarView <host> <port> [wr<i> wr...] [<interval> [<timeout>]]
- #
- # example:
- # define sv SolarView fritz.box 15000 wr1 wr2 60
- #
- # If <interval> is positive, new values are read every <interval> seconds.
- # If <interval> is 0, new values are read whenever a get request is called
- # on <name>. The default for <interval> is 300 (i.e. 5 minutes).
- #
- # The parameters wr<i> specify the number(s) of the inverter(s) to be read.
- # When omitted, the sum of all inverters is read. If more than one inverter
- # is specified, the names of the readings are prefixed with the inverter
- # number, e.g. 'wr2_currentPower'.
- #
- # get <name> [wr<i>_]<key>
- #
- # where <key> is one of currentPower, totalEnergy, totalEnergyDay,
- # totalEnergyMonth, totalEnergyYear, UDC, IDC, UDCB, IDCB, UDCC, IDCC,
- # gridVoltage, gridCurrent and temperature.
- #
- ##############################################################################
- #
- # Copyright notice
- #
- # (c) 2012 Tobe Toben <fhem@toben.net>
- #
- # 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', 'gridCurrent', # V, A
- 'temperature'); # oC
- 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 event-on-update-reading event-on-change-reading";
- }
- sub
- SolarView_Define($$)
- {
- my ($hash, $def) = @_;
- my @args = split("[ \t]+", $def);
- if (int(@args) < 4)
- {
- return "SolarView_Define: too few arguments. Usage:\n" .
- "define <name> SolarView <host> <port> [wr<i> wr...] [<interval> [<timeout>]]";
- }
- $hash->{Host} = $args[2];
- $hash->{Port} = $args[3];
- # collect the set of inverters which are to be read
- @{$hash->{Inverters}} = (0);
- while ((int(@args) >= 5) && ($args[4] =~ /^[Ww][Rr](\d+)$/))
- {
- push @{$hash->{Inverters}}, $1 if int($1);
- splice(@args, 4, 1);
- }
- # remove WR0 if exactly one inverter has been specified
- shift @{$hash->{Inverters}} if (int(@{$hash->{Inverters}}) == 2);
- $hash->{Interval} = int(@args) >= 5 ? int($args[4]) : 300;
- $hash->{Timeout} = int(@args) >= 6 ? int($args[5]) : 4;
- # config variables
- $hash->{Invalid} = -1; # default value for invalid readings
- $hash->{Debounce} = 50; # minimum level for debouncing (0 to disable)
- $hash->{NightOff} = 'yes'; # skip reading at night? No sun, no power :-/
- $hash->{UseSVNight} = 'yes'; # use the on/off timings from SV (else: SUNRISE_EL)
- $hash->{STATE} = 'Initializing';
- readingsBeginUpdate($hash);
- # initialization
- for my $wr (@{$hash->{Inverters}})
- {
- $hash->{SolarView_WR($hash, 'Debounced', $wr)} = 0;
- for my $get (@gets)
- {
- readingsBulkUpdate($hash, SolarView_WR($hash, $get, $wr), $hash->{Invalid});
- }
- }
- readingsEndUpdate($hash, $init_done);
- 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} <key>' 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 SolarView_IsNight($hash) and
- $hash->{READINGS}{currentPower}{VAL} != $hash->{Invalid})
- {
- $hash->{STATE} = '0 W, '.$hash->{READINGS}{totalEnergyDay}{VAL}.' kWh (Night)';
- return undef;
- }
- Log 4, "$hash->{NAME} tries to contact solarview at $hash->{Host}:$hash->{Port}";
- my $success = 0;
- # loop over all inverters
- for my $wr (@{$hash->{Inverters}})
- {
- my %readings = ();
- my $retries = 2;
- eval {
- local $SIG{ALRM} = sub { die 'timeout'; };
- alarm $hash->{Timeout};
- READ_SV:
- my $socket = IO::Socket::INET->new(PeerAddr => $hash->{Host},
- PeerPort => $hash->{Port},
- Timeout => $hash->{Timeout});
- if ($socket and $socket->connected())
- {
- $socket->autoflush(1);
- printf $socket "%02d*\r\n", int($wr);
- my $res = <$socket>;
- close($socket);
- if ($res and $res =~ /^\{(\d\d,[^\}]+)\},/)
- {
- my @vals = split(/,/, $1);
- readingsBeginUpdate($hash);
- # parse the result from SV to dedicated values
- for my $i (6..19)
- {
- if (defined($vals[$i]))
- {
- $readings{$gets[$i - 6]} = 0 + $vals[$i];
- }
- }
- # need to retry?
- if ($retries > 0 and $readings{currentPower} == 0)
- {
- sleep(1);
- $retries = $retries - 1;
- goto READ_SV;
- }
- # if Debounce is enabled (>0), then skip one! drop of
- # currentPower from 'greater than Debounce' to 'Zero'
- #
- if ($hash->{Debounce} > 0 and
- $hash->{Debounce} < $hash->{READINGS}{SolarView_WR($hash, 'currentPower', $wr)}{VAL} and
- $readings{currentPower} == 0 and not $hash->{SolarView_WR($hash, 'Debounced', $wr)})
- {
- # revert to the previous value
- $readings{currentPower} = $hash->{READINGS}{SolarView_WR($hash, 'currentPower', $wr)}{VAL};
- $hash->{SolarView_WR($hash, 'Debounced', $wr)} = 1;
- } else {
- $hash->{SolarView_WR($hash, 'Debounced', $wr)} = 0;
- }
- # update Readings
- for my $get (@gets)
- {
- readingsBulkUpdate($hash, SolarView_WR($hash, $get, $wr), $readings{$get});
- }
-
- readingsEndUpdate($hash, $init_done);
- alarm 0;
- $success = 1;
- } # res okay
- } # socket okay
- }; # eval
- alarm 0;
- } # wr loop
- $hash->{STATE} = $hash->{READINGS}{currentPower}{VAL}.' W, '.$hash->{READINGS}{totalEnergyDay}{VAL}.' kWh';
- if ($success) {
- Log 4, "$hash->{NAME} got fresh values from solarview";
- } else {
- $hash->{STATE} .= ' (Fail)';
- 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;
- }
- sub
- SolarView_IsNight($)
- {
- my ($hash) = @_;
- my $isNight = 0;
- my ($sec,$min,$hour,$mday,$mon) = localtime(time);
- # reset totalEnergyX at midnight
- if ($hour == 0)
- {
- readingsBeginUpdate($hash);
- for my $wr (@{$hash->{Inverters}})
- {
- readingsBulkUpdate($hash, SolarView_WR($hash, 'totalEnergyDay', $wr), 0);
- }
-
- if ($mday == 1)
- {
- for my $wr (@{$hash->{Inverters}})
- {
- readingsBulkUpdate($hash, SolarView_WR($hash, 'totalEnergyMonth', $wr), 0);
- }
-
- if ($mon == 0)
- {
- for my $wr (@{$hash->{Inverters}})
- {
- readingsBulkUpdate($hash, SolarView_WR($hash, 'totalEnergyYear', $wr), 0);
- }
- }
- }
- readingsEndUpdate($hash, $init_done);
- }
- if ($hash->{UseSVNight})
- {
- # These are the on/off timings from Solarview, see
- # http://www.amhamberg.de/solarview-fb_Installieren.pdf
- #
- if ($mon == 0) { # Jan
- $isNight = ($hour < 7 or $hour > 17);
- } elsif ($mon == 1) { # Feb
- $isNight = ($hour < 7 or $hour > 18);
- } elsif ($mon == 2) { # Mar
- $isNight = ($hour < 6 or $hour > 19);
- } elsif ($mon == 3) { # Apr
- $isNight = ($hour < 5 or $hour > 20);
- } elsif ($mon == 4) { # May
- $isNight = ($hour < 5 or $hour > 21);
- } elsif ($mon == 5) { # Jun
- $isNight = ($hour < 5 or $hour > 21);
- } elsif ($mon == 6) { # Jul
- $isNight = ($hour < 5 or $hour > 21);
- } elsif ($mon == 7) { # Aug
- $isNight = ($hour < 5 or $hour > 21);
- } elsif ($mon == 8) { # Sep
- $isNight = ($hour < 6 or $hour > 20);
- } elsif ($mon == 9) { # Oct
- $isNight = ($hour < 7 or $hour > 19);
- } elsif ($mon == 10) { # Nov
- $isNight = ($hour < 7 or $hour > 17);
- } elsif ($mon == 11) { # Dec
- $isNight = ($hour < 8 or $hour > 16);
- }
- } else { # we use SUNRISE_EL
- $isNight = not isday();
- }
- return $isNight;
- }
- # prefix the reading name with inverter number
- sub
- SolarView_WR($$$)
- {
- my ($hash, $reading, $wr) = @_;
- if ((int(@{$hash->{Inverters}}) > 1) && (int($wr) > 0))
- {
- return sprintf("wr%s_%s", $wr, $reading);
- }
- else
- {
- return $reading;
- }
- }
- 1;
|