| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- ##############################################################################
- #
- # 70_SML.pm
- #
- # a module to show smartmeter data
- #
- # written 2012 by Gabriel Bentele <gabriel at bentele.de>>
- #
- # $Id: 70_SML.pm 10650 2016-01-28 14:47:44Z bentele $
- #
- # Version = 2.7
- #
- ##############################################################################
- #
- # define <name> SML <host> <port> [<interval> [<timeout>]]
- #
- # 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 60 (i.e. 1 minutes).
- #
- # get <name> <key>
- #
- # where <key> is one of minPower, maxPower, lastPower, avgPower, DAYPOWER, MONTHPOWER, YEARPOWER, TOTALPOWER
- ##############################################################################
- package main;
- use IO::Socket::INET;
- use Blocking;
- my @gets = ('minPower', # min value
- 'maxPower', # max value
- 'lastPower', # last value
- 'avgPower', # avagare value in interval
- 'DAYPOWER',
- 'MONTHPOWER',
- 'YEARPOWER',
- 'TOTALPOWER',
- 'NT',
- 'HT');
- sub
- SML_Initialize($)
- {
- my ($hash) = @_;
- $hash->{DefFn} = "energy_Define";
- $hash->{UndefFn} = "energy_Undef";
- $hash->{GetFn} = "energy_Get";
- $hash->{StateFn} = "energy_State";
- $hash->{SetFn} = "energy_Set";
- }
- sub
- energy_State($$$$)
- {
- my ($hash, $tim, $vt, $val) = @_;
- $hash->{READINGS}{$vt}{VAL} = $val;
- $hash->{READINGS}{$vt}{TIME} = TimeNow();
- Log3 $hash, 4, "$tim Readings: $vt Value: $val";
- return undef;
- }
- sub
- energy_Set($$$$)
- {
- my ($hash, $tim, $vt, $val) = @_;
- $hash->{READINGS}{$vt}{VAL} = $val;
- $hash->{READINGS}{$vt}{TIME} = TimeNow();
- Log3 $hash, 4, "$tim Readings: $vt Value: $val";
- if ( $vt eq "?"){
- return "Unknown argument ?, choose one of Interval DAYPOWER MONTHPOWER YEARPOWER TOTALPOWER";
- }
- if ( $vt eq "Interval"){
- $hash->{Interval} = $val;
- }
- return undef;
- }
- sub
- energy_Define($$)
- {
- my ($hash, $def) = @_;
- my @args = split("[ \t]+", $def);
- if (int(@args) < 4)
- {
- return "energy_Define: too few arguments. Usage:\n" .
- "define <name> SML <host> <port> [<interval> [<timeout>]]";
- }
- $hash->{Host} = $args[2];
- $hash->{Port} = $args[3];
- if ( int(@args) >= 5 ){
- if ( int($args[4]) >= 100 ){
- $hash->{Interval} = 100;
- }else{
- $hash->{Interval} = int($args[4]);
- }
- }
- $hash->{Timeout} = int(@args) >= 6 ? int($args[5]) : 4;
- #Log 4, "$hash->{NAME} will read from SML at $hash->{Host}:$hash->{Port} " ;
- Log3 $hash, 4, "$hash->{NAME} will read from SML at $hash->{Host}:$hash->{Port} " ;
- $hash->{Rereads} = 2; # number of retries when reading curPwr of 0
- $hash->{UseSVTime} = ''; # use the SV time as timestamp (else: TimeNow())
- $hash->{READINGS}{STATE}{VAL} = "Initializing";
- $hash->{READINGS}{STATE}{TIME} = $timenow;
-
- my $timenow = TimeNow();
- for my $get (@gets)
- {
- $hash->{READINGS}{$get}{VAL} = -1;
- $hash->{READINGS}{$get}{TIME} = $timenow;
- }
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+$hash->{Interval}, "sml_energy_Update", $hash, 0);
- #sml_energy_Update($hash);
- Log3 $hash, 3, "$hash->{NAME} will read from SML at $hash->{Host}:$hash->{Port} " ;
- return undef;
- }
- sub
- energy_Counter($)
- {
- my ($hash) = @_;
- my $ip = $hash->{Host};
- my $port = $hash->{Port};
- my $interval = $hash->{Interval};
- my $timeout = $hash->{Timeout};
- my $url = "/?action=20";
- my $socket ;
- my $buf ;
- my $message ;
- my @array ;
- my $counts = 0 ;
- my $HT = 0;
- my $NT = 0;
-
- Log3 $hash, 4, "$hash->{NAME} $ip : $port : $url";
- $socket = new IO::Socket::INET (
- PeerAddr => $ip,
- PeerPort => $port,
- Proto => 'tcp',
- Reuse => 0,
- Timeout => $timeout
- );
- Log3 $hash, 4, "$hash->{NAME} socket new";
- if (defined ($socket) and $socket and $socket->connected())
- {
- Log3 $hash, 4, "$hash->{NAME} Connected ...";
- print $socket "GET $url HTTP/1.0\r\n\r\n";
- $socket->autoflush(1);
- while ((read $socket, $buf, 1024) > 0)
- {
- Log 5,"buf: $buf";
- $message .= $buf;
- }
- }else{
- Log3 $hash, 3, "$hash->{NAME} Cannot open socket ...";
- $success = 1;
- return 0;
- }
- @array = split(/\n/,$message);
- foreach (@array){
- if ( $_ =~ /^<h3>(.*)HT:(.*)$/){
- $HT = 1;
- }
- if ( $HT == 1 ){
- if ( $_ =~ /^<h3>(.*) kWh<\/h3>(.*)$/){
- $HT = $1;
- $counts = 0;
- }
- }
- if ( $_ =~ /^<h3>(.*)NT:(.*)$/){
- $NT = 1;
- }
- if ( $NT == 1 ){
- if ( $_ =~ /^<h3>(.*) kWh<\/h3>(.*)$/){
- $NT = $1;
- $counts = 0;
- }
- }
- }
- $hash->{READINGS}{HT}{VAL} = $HT;
- $hash->{READINGS}{NT}{VAL} = $NT;
- my $gesammt = $HT + $NT;
- push @{$hash->{CHANGED}}, "HT: $HT kWh NT: $NT kWh Summe: $gesammt kWh";
- DoTrigger($hash->{NAME}, undef) if ($init_done);
- return "HT: $HT kWh NT: $NT kWh Summe: $gesammt kWh";
- }
- sub
- sml_energy_Update($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $ip = $hash->{Host};
- my $port = $hash->{Port};
- my $interval = $hash->{Interval};
- #Log 4, "$hash->{NAME} tries to contact SML at $hash->{Host}:$hash->{Port}";
- Log3 $hash, 4, "$hash->{NAME} tries to contact SML at $hash->{Host}:$hash->{Port}";
- $hash->{helper}{RUNNING_PID} = BlockingCall("sml_energy_DoUpdate", $name."|".$ip."|".$port."|".$interval, "sml_energy_energyDone", 120, "sml_energy_energyAborted", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
- }
- sub
- sml_energy_DoUpdate($)
- {
- my ($string) = @_;
- my ($name, $ip, $port,$interval) = split("\\|", $string);
- my $success = 0;
- my %readings = ();
- my $timenow = TimeNow();
- my $counts = 0 ;
- my $summary = 0 ;
- #my $url = "/InstantView/request/getPowerProfile.html?ts=0\&n=$interval\¶m=Wirkleistung\&format=1";
- my $url = "/InstantView/request/getPowerProfile.html?ts=0\&n=$interval";
- my $socket ;
- my $buf ;
- my $message ;
- my @array;
- my $last;
- my $avg;
- my $min = 20000;
- my $max = 0;
- #Log 4, "$hash->{NAME} $url";
- Log3 $hash, 4, "$hash->{NAME} $url";
- $socket = new IO::Socket::INET (
- PeerAddr => $ip,
- PeerPort => $port,
- Proto => 'tcp',
- Reuse => 0,
- Timeout => 10
- );
- Log3 $hash, 4, "$name socket new";
- if (defined ($socket) and $socket and $socket->connected())
- {
- Log3 $name, 4, "$name Connected ...";
- print $socket "GET $url HTTP/1.0\r\n\r\n";
- $socket->autoflush(1);
- while ((read $socket, $buf, 1024) > 0)
- {
- Log 5,"buf: $buf";
- $message .= $buf;
- }
- $socket->close();
- Log3 $hash, 4, "$hash->{NAME} Socket closed";
- @array = split(/\n/,$message);
- foreach (@array){
- if ( $_ =~ /<v>(.*)<\/v>/ ){
- Log3 $hash, 5, "$hash->{NAME} got fresh values from $ip ($1)";
- if ( $1 eq "NaN" ){
- Log3 $hash, 5, "$hash->{NAME} NaN fehler ($1) summary: $summary";
- }else{
- $last = $1;
- $counts++ ;
- $summary += $1;
- if ($last < $min) {$min = $last};
- if ($last > $max) {$max = $last};
- }
- }
- if ( $_ =~ /<error>(.*)<\/error>/ )
- {
- if ( $1 eq "true" )
- {
- $success = 1;
- Log3 $name, 4, "$name error from the $ip ($1)";
- }
- }
- }
- }else{
- Log3 $hash, 3, "$hash->{NAME} Cannot open socket ...";
- $success = 1;
- # return 0;
- }
-
- if ( $success == 0 and $summary > 0 and $counts > 0){
- $avg = $summary/$counts;
- $avg =sprintf("%.2f",$avg);
- return "$name|$min|$max|$last|$avg|0" ;
- }else{
- return "$name|1|1|1|1|1";
- }
- } #sub ende
- sub
- sml_energy_energyDone($)
- {
- my ($string) = @_;
- return unless(defined($string));
- my (@a) = split("\\|", $string);
- my $hash = $defs{$a[0]};
- my ($min,$max,$last,$avg,$success) = ($a[1],$a[2],$a[3],$a[4],$a[5]);
- my $log = "";
- my $timenow = TimeNow();
- my $interval = $hash->{Interval};
- Log3 $hash, 4, "sml_energy_energyDone min: $min max: $max last: $last avg: $avg";
- delete($hash->{helper}{RUNNING_PID});
-
- # if ($hash->{Interval} > 0) {
- InternalTimer(gettimeofday() + $hash->{Interval}, "sml_energy_Update", $hash, 0);
- Log3 $hash, 4, "internalTimer funktion";
- # }
- if ( $success == 0){
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "minPower" , $min );
- readingsBulkUpdate($hash, "maxPower" , $max );
- readingsBulkUpdate($hash, "lastPower" , $last );
- readingsBulkUpdate($hash, "avgPower" , $avg );
- $log = "min: $min max: $max last: $last avg: $avg";
- $hash->{STATE} = "min: $min max: $max last: $last avg: $avg";
- my $newpower = $avg/(3600/$interval);
- $newpower = $newpower/1000;
- $newpower =sprintf("%.6f",$newpower);
- my ($date, $month, $day, $hour, $min, $sec) = $timenow =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
- # ######### DAYPOWER
- if ( $hash->{READINGS}{DAYPOWER}{VAL} eq "-1" ){
- $hash->{READINGS}{DAYPOWER}{VAL} = $newpower;
- }else{
- my ($dateLast, $monthLast, $dayLast, $hourLast, $minLast, $secLast) = $hash->{READINGS}{DAYPOWER}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
- my ($powLast) = $hash->{READINGS}{DAYPOWER}{VAL} =~ /^(.*)$/;
- Log3 $hash, 4, "$hash->{NAME} myhour: $dateLast $monthLast $dayLast $hourLast $minLast $secLast $powLast";
- $hash->{READINGS}{DAYPOWER}{TIME} = $timenow;
- if ( $dayLast eq $day ){ # es ist der gleiche Tag
- $powLast += $newpower ;
- #$hash->{READINGS}{DAYPOWER}{VAL} = $powLast;
- readingsBulkUpdate($hash, "DAYPOWER" , $powLast );
- Log3 $hash, 4, "$hash->{NAME} same day timenow: $timenow newpower $newpower powlast $powLast";
- $log .= " day: $powLast";
- }else{ # es ist eine Tag vergangen
- readingsBulkUpdate($hash, "DAYPOWER" , $newpower );
- #$hash->{READINGS}{DAYPOWER}{VAL} = $newpower;
- Log3 $hash, 4, "$hash->{NAME} new day timenow: $timenow newpower $newpower powlast $powLast";
- $log .= " day: $newpower";
- }
- }
- # ######### MONTH
- if ( $hash->{READINGS}{MONTHPOWER}{VAL} eq "-1" ){
- #$hash->{READINGS}{MONTHPOWER}{VAL} = $newpower;
- readingsBulkUpdate($hash, "MONTHPOWER" , $newpower );
- }else{
- my ($dateLast, $monthLast, $dayLast, $hourLast, $minLast, $secLast) = $hash->{READINGS}{MONTHPOWER}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
- my ($powLast) = $hash->{READINGS}{MONTHPOWER}{VAL} =~ /^(.*)$/;
- Log3 $hash, 4, "$hash->{NAME} month: $dateLast $monthLast $dayLast $hourLast $minLast $secLast $powLast";
- $hash->{READINGS}{MONTHPOWER}{TIME} = $timenow;
- if ( $monthLast eq $month ){ # es ist der gleiche Monat
- $powLast += $newpower ;
- #$hash->{READINGS}{MONTHPOWER}{VAL} = $powLast;
- readingsBulkUpdate($hash, "MONTHPOWER" , $powLast );
- Log3 $hash, 4, "$hash->{NAME} Gleicher Monat timenow: $timenow newpower $newpower powlast $powLast";
- $log .= " month: $powLast";
- }else{ # es ist eine Monat vergangen
- #$hash->{READINGS}{MONTHPOWER}{VAL} = $newpower;
- readingsBulkUpdate($hash, "MONTHPOWER" , $newpower );
- Log3 $hash, 4, "$hash->{NAME} Neuer Monat timenow: $timenow newpower $newpower powlast $powLast";
- $log .= " month: $newpower";
- }
- }
- # ######### YEARPOWER
- if ( $hash->{READINGS}{YEARPOWER}{VAL} eq "-1" ){
- readingsBulkUpdate($hash, "YEARPOWER" , $newpower );
- #$hash->{READINGS}{YEARPOWER}{VAL} = $newpower;
- }else{
- my ($dateLast, $monthLast, $dayLast, $hourLast, $minLast, $secLast) = $hash->{READINGS}{YEARPOWER}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
- my ($powLast) = $hash->{READINGS}{YEARPOWER}{VAL} =~ /^(.*)$/;
- Log3 $hash, 4, "$hash->{NAME} yearpower: $dateLast $monthLast $dayLast $hourLast $minLast $secLast $powLast";
- $hash->{READINGS}{YEARPOWER}{TIME} = $timenow;
- if ( $dateLast eq $date ){ # es ist das gleiche Jahr
- $powLast += $newpower ;
- readingsBulkUpdate($hash, "YEARPOWER" , $powLast );
- #$hash->{READINGS}{YEARPOWER}{VAL} = $powLast;
- Log3 $hash, 4, "$hash->{NAME} Gleiches Jahr timenow: $timenow newpower $newpower powlast $powLast";
- $log .= " year: $powLast";
- }else{ # es ist eine Jahr vergangen
- #$hash->{READINGS}{YEARPOWER}{VAL} = $newpower;
- readingsBulkUpdate($hash, "YEARPOWER" , $newpower );
- Log3 $hash, 4, "$hash->{NAME} Neues Jahr timenow: $timenow newpower $newpower powlast $powLast";
- $log .= " year: $newpower";
- }
- }
- # ######### TOTALPOWER
- $hash->{READINGS}{TOTALPOWER}{TIME} = $timenow;
- if ( $hash->{READINGS}{TOTALPOWER}{VAL} eq "-1" ){
- #$hash->{READINGS}{TOTALPOWER}{VAL} = $newpower;
- readingsBulkUpdate($hash, "TOTALPOWER" , $newpower );
- }else{
- my ($dateLast, $monthLast, $dayLast, $hourLast, $minLast, $secLast) = $hash->{READINGS}{TOTALPOWER}{TIME} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
- my ($powLast) = $hash->{READINGS}{TOTALPOWER}{VAL} =~ /^(.*)$/;
- Log3 $hash, 5, "$hash->{NAME} total: $dateLast $monthLast $dayLast $hourLast $minLast $secLast $powLast";
- $powLast += $newpower ;
- #$hash->{READINGS}{TOTALPOWER}{VAL} = $powLast;
- readingsBulkUpdate($hash, "TOTALPOWER" , $powLast );
- $log .= " total: $powLast";
- }
- $hash->{READINGS}{STATE}{VAL} = 'Connected';
- $hash->{READINGS}{STATE}{TIME} = $timenow;
- push @{$hash->{CHANGED}}, $log;
- DoTrigger($hash->{NAME}, undef) if ($init_done);
- Log3 $hash, 4, "$hash->{NAME} write log file: $log";
- Log3 $hash, 4, "$hash->{NAME} STATE: $hash->{READINGS}{STATE}{VAL} timenow: $timenow";
- readingsEndUpdate ($hash, 1 );
- }else{
- $hash->{STATE}{VAL} = 'disconnected';
- Log3 $hash, 3, "$hash->{NAME} can't update - device send a error";
-
- push @{$hash->{CHANGED}}, $log;
- DoTrigger($hash->{NAME}, undef) if ($init_done);
- Log3 $hash, 4, "$hash->{NAME} write log file: $log";
- Log3 $hash, 4, "$hash->{NAME} STATE: $hash->{READINGS}{STATE}{VAL} timenow: $timenow";
- }
- return undef;
- }
- sub
- sml_energy_energyAborted($)
- {
- my ($hash) = @_;
- Log3 $hash, 3, "$hash->{NAME} sml_energy_energyAborted";
- delete($hash->{helper}{RUNNING_PID});
- #new
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+$hash->{Interval}, "sml_energy_Update", $hash, 0);
- }
- sub
- energy_Get($@)
- {
- my ($hash, @args) = @_;
- return 'energy_Get needs two arguments' if (@args != 2);
- sml_energy_Update($hash) unless $hash->{Interval};
- my $get = $args[1];
- my $val = $hash->{Invalid};
-
- if ( $get eq "counter"){
- $val = energy_Counter($hash);
- }
- if (defined($hash->{READINGS}{$get})) {
- $val = $hash->{READINGS}{$get}{VAL};
- }
- if ( $get eq "?"){
- return "Unknown argument ?, choose one of counter minPower maxPower lastPower avgPower DAYPOWER MONTHPOWER YEARPOWER TOTALPOWER";
- }
- Log3 $hash, 3, "$args[0] $val";
- return $val;
- }
- sub
- energy_Undef($$)
- {
- my ($hash, $args) = @_;
- RemoveInternalTimer($hash) if $hash->{Interval};
- BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
- return undef;
- }
- 1;
- =pod
- =begin html
- <a name="SML"></a>
- <h3>SML</h3>
- <ul><p>
- This module supports "Intelligenter Strom Zhler"(ENBW) and "Sparzhler" (Yellow Strom).<br>
- The electricity meter will be polled in a defined interval (1-100) for new values.
- </p>
- <b>Define</b><br>
- <code>define <name> SML <host> <port> [<interval> <timeout>]</code><br>
- <p>
- Example:<br>
- define StromZ1 SML 192.168.178.20 <br>
- define StromZ2 SML 192.168.10.25 60 60 <br>
- </p>
- <b>Set</b><br>
- set <name> <value> <nummber><br>where value is one of:<br><br>
- <ul>
- <li><code>TOTALPOWER</code> </li>
- <li><code>YEARPOWER </code> </li>
- <li><code>MONTHPOWER</code> </li>
- <li><code>DAYPOWER </code> </li>
- <li><code>Interval </code> </li>
- </ul>
- <br>Example:<br>
- set <name> TOTALPOWER 12345 <br><br>
- <b>Get</b><br>
- get <name> <value> <br>where value is one of:<br>
- <ul>
- <li><code>TOTALPOWER</code></li>
- <li><code>YEARPOWER </code></li>
- <li><code>MONTHPOWER</code></li>
- <li><code>DAYPOWER </code></li>
- <li><code>Interval </code> </li>
- </ul>
- <br>Example:<br>
- get <name> DAYPOWER<br>
- get <name> YEARPOWER<br><br>
-
- </ul>
- =end html
- =cut
|