From d36b95ceb9dbd0639650eec131e10653cca1bf4b Mon Sep 17 00:00:00 2001 From: vk <> Date: Tue, 3 Jan 2017 14:22:56 +0000 Subject: [PATCH] 77_SMAEM: Merged pull request from DS_Starter git-svn-id: https://svn.fhem.de/fhem/trunk@12945 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/77_SMAEM.pm | 655 +++++++++++++++++++++++++++++------------- 1 file changed, 459 insertions(+), 196 deletions(-) diff --git a/fhem/FHEM/77_SMAEM.pm b/fhem/FHEM/77_SMAEM.pm index 0a4f44fc6..15ae7b2f4 100644 --- a/fhem/FHEM/77_SMAEM.pm +++ b/fhem/FHEM/77_SMAEM.pm @@ -1,4 +1,4 @@ -################################################################ +################################################################################################ # $Id$ # # Copyright notice @@ -23,20 +23,36 @@ # Origin: # https://github.com/kettenbach-it/FHEM-SMA-Speedwire # -################################################################ +################################################################################################# +# Versions History done by DS_Starter +# +# 2.8.2 03.12.2016 Prefix SMAEMserialnumber for Reading "state" removed, commandref adapted +# 2.8.1 02.12.2016 encode / decode $data +# 2.8 02.12.2016 plausibility check of measured differences, attr diffAccept, timeout +# validation checks, improvement of failure prevention +# 2.7 01.12.2016 logging of discarded cycles +# 2.6 01.12.2016 some improvements, better logging possibility +# 2.5 30.11.2016 some improvements +# 2.4 30.11.2016 some improvements, attributes disable, timeout for BlockingCall added +# 2.3 30.11.2016 getsum, setsum changed +# 2.2 29.11.2016 check error while writing values to file -> set state with error +# 2.1 29.11.2016 move $hash->{GRIDin_SUM}, $hash->{GRIDOUT_SUM} calc to smaread_ParseDone, +# some little improvements to logging process +# 2.0 28.11.2016 switch to nonblocking package main; use strict; use warnings; use bignum; - use IO::Socket::Multicast; +# use Scalar::Util qw(looks_like_number); +use Blocking; -##################################### -sub -SMAEM_Initialize($) -{ +############################################################### +# SMAEM Initialize +############################################################### +sub SMAEM_Initialize($) { my ($hash) = @_; $hash->{ReadFn} = "SMAEM_Read"; @@ -49,26 +65,30 @@ SMAEM_Initialize($) #$hash->{SetFn} = "SMAEM_Set"; $hash->{AttrFn} = "SMAEM_Attr"; $hash->{AttrList} = "interval ". + "disable:1,0 ". + "diffAccept ". "disableSernoInReading:1,0 ". "feedinPrice ". - "powerCost ". + "powerCost ". + "timeout ". "$readingFnAttributes"; } -##################################### -sub -SMAEM_Define($$) -{ +############################################################### +# SMAEM Define +############################################################### +sub SMAEM_Define($$) { my ($hash, $def) = @_; my $name= $hash->{NAME}; my ($success, $gridin_sum, $gridout_sum); - $hash->{INTERVAL} = 60 ; - - $hash->{LASTUPDATE}=0; - $hash->{HELPER}{LASTUPDATE} = 0; + $hash->{INTERVAL} = 60 ; + $hash->{LASTUPDATE} = 0; + $hash->{HELPER}{LASTUPDATE} = 0; + $hash->{HELPER}{FAULTEDCYCLES} = 0; + $hash->{HELPER}{STARTTIME} = time(); - Log3 $hash, 3, "$name - Opening multicast socket..."; + Log3 $hash, 3, "SMAEM $name - Opening multicast socket..."; my $socket = IO::Socket::Multicast->new( Proto => 'udp', LocalPort => '9522', @@ -84,36 +104,39 @@ SMAEM_Define($$) $selectlist{"$name"} = $hash; # gespeicherte Energiezählerwerte von File einlesen - ($success, $gridin_sum, $gridout_sum) = getsum($hash); - if ($success) { - $hash->{GRIDIN_SUM} = $gridin_sum; - $hash->{GRIDOUT_SUM} = $gridout_sum; - Log3 $name, 3, "$name - read saved energy values from file - GRIDIN_SUM: $gridin_sum, GRIDOUT_SUM: $gridout_sum"; + my $retcode = getsum($hash); + + if ($retcode) { + $hash->{HELPER}{READFILEERROR} = $retcode; } return undef; } -sub -SMAEM_Undef($$) -{ +############################################################### +# SMAEM Undefine +############################################################### +sub SMAEM_Undef($$) { my ($hash, $arg) = @_; my $name= $hash->{NAME}; my $socket= $hash->{TCPDev}; - Log3 $hash, 3, "$name: Closing multicast socket..."; + BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); + Log3 $hash, 3, "SMAEM $name - Closing multicast socket..."; $socket->mcast_drop('239.12.255.254'); - # $socket->close; my $ret = close($hash->{TCPDev}); - Log3 $hash, 4, "$name: Close-ret: $ret"; + Log3 $hash, 4, "SMAEM $name - Close-ret: $ret"; delete($hash->{TCPDev}); delete($selectlist{"$name"}); delete($hash->{FD}); - - return; + +return; } +############################################################### +# SMAEM Delete +############################################################### sub SMAEM_Delete { my ($hash, $arg) = @_; my $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum"; @@ -124,9 +147,13 @@ sub SMAEM_Delete { return undef; } +############################################################### +# SMAEM Attr +############################################################### sub SMAEM_Attr { my ($cmd,$name,$aName,$aVal) = @_; my $hash = $defs{$name}; + my $do; # $cmd can be "del" or "set" # $name is device name @@ -142,26 +169,103 @@ sub SMAEM_Attr { if ($aName eq "disableSernoInReading") { delete $defs{$name}{READINGS}; - readingsSingleUpdate($hash, "state", "readingsreset", 1); + readingsSingleUpdate($hash, "state", "initialized", 1); + } + + if ($aName eq "timeout" || $aName eq "diffAccept") { + unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";} + } + + if ($aName eq "disable") { + if($cmd eq "set") { + $do = ($aVal) ? 1 : 0; + } + $do = 0 if($cmd eq "del"); + my $val = ($do == 1 ? "disabled" : "initialized"); + + readingsSingleUpdate($hash, "state", $val, 1); } return undef; } -##################################### +############################################################### +# SMAEM Read (Hauptschleife) +############################################################### # called from the global loop, when the select for hash->{FD} reports data -sub SMAEM_Read($) -{ +sub SMAEM_Read($) { my ($hash) = @_; my $name= $hash->{NAME}; my $socket= $hash->{TCPDev}; - + my $timeout = AttrVal($name, "timeout", 60); my $data; + + return if(IsDisabled($name)); + return unless $socket->recv($data, 600); # Each SMAEM packet is 600 bytes of packed payload - Log3 $hash, 5, "$name: Received " . length($data) . " bytes."; + + if (time() <= $hash->{HELPER}{STARTTIME}+30) { + return; + } + + if ( $hash->{HELPER}{LASTUPDATE} == 0 || time() >= ($hash->{HELPER}{LASTUPDATE}+$hash->{INTERVAL}) ) { + Log3 ($name, 4, "SMAEM $name - ###############################################################"); + Log3 ($name, 4, "SMAEM $name - ######### Begin of new SMA Energymeter get data cycle #########"); + Log3 ($name, 4, "SMAEM $name - ###############################################################"); + Log3 ($name, 4, "SMAEM $name - discarded cycles since module start: $hash->{HELPER}{FAULTEDCYCLES}"); + + if($hash->{helper}{RUNNING_PID}) { + Log3 ($name, 3, "SMAEM $name - WARNING - old process $hash->{HELPER}{RUNNING_PID}{pid} has been killed to start a new BlockingCall"); + BlockingKill($hash->{HELPER}{RUNNING_PID}); + delete($hash->{HELPER}{RUNNING_PID}); + } - if ($hash->{HELPER}{LASTUPDATE} == 0 || time() >= $hash->{HELPER}{LASTUPDATE}+$hash->{INTERVAL}) { - + # update time + lastupdate_set($hash); + + my $dataenc = encode_base64($data,""); + + $hash->{HELPER}{RUNNING_PID} = BlockingCall("smaemread_DoParse", "$name|$dataenc", "smaemread_ParseDone", $timeout, "smaemread_ParseAborted", $hash); + Log3 ($name, 4, "SMAEM $name - Blocking process with PID: $hash->{HELPER}{RUNNING_PID}{pid} started"); + + } else { + + Log3 $hash, 5, "SMAEM $name: - received " . length($data) . " bytes but interval $hash->{INTERVAL}s isn't expired."; + } +return undef; +} + +############################################################### +# non-blocking Inverter Datenabruf +############################################################### +sub smaemread_DoParse($) { + my ($string) = @_; + my ($name, $dataenc) = split("\\|", $string); + my $hash = $defs{$name}; + my $data = decode_base64($dataenc); + my $discycles = $hash->{HELPER}{FAULTEDCYCLES}; + my $diffaccept = AttrVal($name, "diffAccept", 10); + my @row_array; + my @array; + + Log3 ($name, 4, "SMAEM $name -> Start BlockingCall smaemread_DoParse"); + + my $gridinsum = $hash->{GRIDIN_SUM} ?sprintf("%.4f",$hash->{GRIDIN_SUM}):''; + my $gridoutsum = $hash->{GRIDOUT_SUM}?sprintf("%.4f",$hash->{GRIDOUT_SUM}):''; + + # check if uniqueID-file has been opened at module start and try again if not + if($hash->{HELPER}{READFILEERROR}) { + my $retcode = getsum($hash); + if ($retcode) { + my $error = encode_base64($retcode,""); + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_DoParse finished"); + $discycles++; + return "$name|''|''|''|$error|$discycles"; + } else { + delete($hash->{HELPER}{READFILEERROR}) + } + } + # Format of the udp packets of the SMAEM: # http://www.sma.de/fileadmin/content/global/Partner/Documents/SMA_Labs/EMETER-Protokoll-TI-de-10.pdf # http://www.eb-systeme.de/?page_id=1240 @@ -173,275 +277,404 @@ sub SMAEM_Read($) # unpack big-endian to 2-digit hex (bin2hex) my $hex=unpack('H*', $data); + ################ Aufbau Ergebnis-Array #################### # Extract datasets from hex: # Generic: my $susyid=hex(substr($hex,36,4)); my $smaserial=hex(substr($hex,40,8)); my $milliseconds=hex(substr($hex,48,8)); - #readingsBulkUpdate($hash, "SUSy-ID", $susyid); - #readingsBulkUpdate($hash, "Seriennummer", $smaserial); - + # Prestring with NAME and SERIALNO or not + my $ps = (!AttrVal($name, "disableSernoInReading", undef)) ? "SMAEM".$smaserial."_" : ""; + # Counter Divisor: [Hex-Value]=Ws => Ws/1000*3600=kWh => divide by 3600000 # Sum L1-3 my $bezug_wirk=hex(substr($hex,64,8))/10; my $bezug_wirk_count=hex(substr($hex,80,16))/3600000; my $einspeisung_wirk=hex(substr($hex,104,8))/10; my $einspeisung_wirk_count=hex(substr($hex,120,16))/3600000; - - # Prestring with NAME and SERIALNO or not - my $ps = (!AttrVal($name, "disableSernoInReading", undef)) ? "SMAEM".$smaserial."_" : ""; - - readingsBeginUpdate($hash); - - readingsBulkUpdate($hash, "state", sprintf("%.1f", $einspeisung_wirk-$bezug_wirk)); - readingsBulkUpdate($hash, $ps."Saldo_Wirkleistung", sprintf("%.1f",$einspeisung_wirk-$bezug_wirk)); - readingsBulkUpdate($hash, $ps."Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$einspeisung_wirk_count-$bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."Bezug_Wirkleistung", sprintf("%.1f",$bezug_wirk)); - readingsBulkUpdate($hash, $ps."Bezug_Wirkleistung_Zaehler", sprintf("%.4f",$bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."Einspeisung_Wirkleistung", sprintf("%.1f",$einspeisung_wirk)); - readingsBulkUpdate($hash, $ps."Einspeisung_Wirkleistung_Zaehler", sprintf("%.4f",$einspeisung_wirk_count)); - - if(!$hash->{GRIDOUT_SUM} || ReadingsVal($name,$ps."Bezug_Wirkleistung_Zaehler","") < $hash->{GRIDOUT_SUM}) { - $hash->{GRIDOUT_SUM} = sprintf("%.4f",$bezug_wirk_count); - } else { - if (ReadingsVal($name,$ps."Bezug_Wirkleistung_Zaehler","") >= $hash->{GRIDOUT_SUM}) { - my $diffb = $bezug_wirk_count - $hash->{GRIDOUT_SUM}; - $hash->{GRIDOUT_SUM} = sprintf("%.4f",$bezug_wirk_count); - readingsBulkUpdate($hash, $ps."Bezug_WirkP_Zaehler_Diff", $diffb); - readingsBulkUpdate($hash, $ps."Bezug_WirkP_Kosten_Diff", sprintf("%.4f", $diffb*AttrVal($hash->{NAME}, "powerCost", 0))); - } - } + - if(!$hash->{GRIDIN_SUM} || ReadingsVal($name,$ps."Einspeisung_Wirkleistung_Zaehler","") < $hash->{GRIDIN_SUM}) { - $hash->{GRIDIN_SUM} = sprintf("%.4f",$einspeisung_wirk_count); + # calculation of GRID-hashes and persist to file + Log3 ($name, 4, "SMAEM $name - old GRIDIN_SUM got from RAM: $gridinsum"); + Log3 ($name, 4, "SMAEM $name - old GRIDOUT_SUM got from RAM: $gridoutsum"); + + my $plausibility_out = 0; + if( !$gridoutsum || ($bezug_wirk_count && $bezug_wirk_count < $gridoutsum) ) { + $gridoutsum = $bezug_wirk_count; + Log3 ($name, 4, "SMAEM $name - gridoutsum new set: $gridoutsum"); } else { - if (ReadingsVal($name,$ps."Einspeisung_Wirkleistung_Zaehler","") >= $hash->{GRIDIN_SUM}) { - my $diffe = $einspeisung_wirk_count - $hash->{GRIDIN_SUM}; - $hash->{GRIDIN_SUM} = sprintf("%.4f",$einspeisung_wirk_count); - readingsBulkUpdate($hash, $ps."Einspeisung_WirkP_Zaehler_Diff", $diffe); - readingsBulkUpdate($hash, $ps."Einspeisung_WirkP_Verguet_Diff", sprintf("%.4f", $diffe*AttrVal($hash->{NAME}, "feedinPrice", 0))); + if ($gridoutsum && $bezug_wirk_count >= $gridoutsum) { + if(($bezug_wirk_count - $gridoutsum) <= $diffaccept) { + # Plausibilitätscheck ob Differenz kleiner als erlaubter Wert -> Fehlerprävention + my $diffb = ($bezug_wirk_count - $gridoutsum)>0 ? sprintf("%.4f",$bezug_wirk_count - $gridoutsum) : 0; + Log3 ($name, 4, "SMAEM $name - bezug_wirk_count: $bezug_wirk_count"); + Log3 ($name, 4, "SMAEM $name - gridoutsum: $gridoutsum"); + Log3 ($name, 4, "SMAEM $name - diffb: $diffb"); + $gridoutsum = $bezug_wirk_count; + push(@row_array, $ps."Bezug_WirkP_Zaehler_Diff ".$diffb."\n"); + push(@row_array, $ps."Bezug_WirkP_Kosten_Diff ".sprintf("%.4f", $diffb*AttrVal($name, "powerCost", 0))."\n"); + $plausibility_out = 1; + } else { + # Zyklus verwerfen wenn Plusibilität nicht erfüllt + my $errtxt = "cycle discarded due to allowed diff GRIDOUT exceeding"; + my $error = encode_base64($errtxt,""); + Log3 ($name, 1, "SMAEM $name - $errtxt"); + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_DoParse finished"); + $gridinsum = $einspeisung_wirk_count; + $gridoutsum = $bezug_wirk_count; + $discycles++; + return "$name|''|$gridinsum|$gridoutsum|''|$discycles"; + } + } + } + + my $plausibility_in = 0; + if( !$gridinsum || ($einspeisung_wirk_count && $einspeisung_wirk_count < $gridinsum) ) { + $gridinsum = $einspeisung_wirk_count; + Log3 ($name, 4, "SMAEM $name - gridinsum new set: $gridinsum"); + } else { + if ($gridinsum && $einspeisung_wirk_count >= $gridinsum) { + if(($einspeisung_wirk_count - $gridinsum) <= $diffaccept) { + # Plausibilitätscheck ob Differenz kleiner als erlaubter Wert -> Fehlerprävention + my $diffe = ($einspeisung_wirk_count - $gridinsum)>0 ? sprintf("%.4f",$einspeisung_wirk_count - $gridinsum) : 0; + Log3 ($name, 4, "SMAEM $name - einspeisung_wirk_count: $einspeisung_wirk_count"); + Log3 ($name, 4, "SMAEM $name - gridinsum: $gridinsum"); + Log3 ($name, 4, "SMAEM $name - diffe: $diffe"); + $gridinsum = $einspeisung_wirk_count; + push(@row_array, $ps."Einspeisung_WirkP_Zaehler_Diff ".$diffe."\n"); + push(@row_array, $ps."Einspeisung_WirkP_Verguet_Diff ".sprintf("%.4f", $diffe*AttrVal($name, "feedinPrice", 0))."\n"); + $plausibility_in = 1; + } else { + # Zyklus verwerfen wenn Plusibilität nicht erfüllt + my $errtxt = "cycle discarded due to allowed diff GRIDIN exceeding"; + my $error = encode_base64($errtxt,""); + Log3 ($name, 1, "SMAEM $name - $errtxt"); + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_DoParse finished"); + $gridinsum = $einspeisung_wirk_count; + $gridoutsum = $bezug_wirk_count; + $discycles++; + return "$name|''|$gridinsum|$gridoutsum|''|$discycles"; + } } } - # GRIDIN_SUM und GRIDOUT_SUM in File schreiben - my $success = setsum($hash, $hash->{GRIDIN_SUM}, $hash->{GRIDOUT_SUM}); + # write GRIDIN_SUM and GRIDOUT_SUM to file if plausibility check ok + Log3 ($name, 4, "SMAEM $name - plausibility check done: GRIDIN -> $plausibility_in, GRIDOUT -> $plausibility_out"); + my $retcode = setsum($hash, $gridinsum, $gridoutsum) if($plausibility_in && $plausibility_out); + + # error while writing values to file + if ($retcode) { + my $error = encode_base64($retcode,""); + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_DoParse finished"); + $discycles++; + return "$name|''|''|''|$error|$discycles"; + } + + push(@row_array, "state ".sprintf("%.1f", $einspeisung_wirk-$bezug_wirk)."\n"); + push(@row_array, $ps."Saldo_Wirkleistung ".sprintf("%.1f",$einspeisung_wirk-$bezug_wirk)."\n"); + push(@row_array, $ps."Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$einspeisung_wirk_count-$bezug_wirk_count)."\n"); + push(@row_array, $ps."Bezug_Wirkleistung ".sprintf("%.1f",$bezug_wirk)."\n"); + push(@row_array, $ps."Bezug_Wirkleistung_Zaehler ".sprintf("%.4f",$bezug_wirk_count)."\n"); + push(@row_array, $ps."Einspeisung_Wirkleistung ".sprintf("%.1f",$einspeisung_wirk)."\n"); + push(@row_array, $ps."Einspeisung_Wirkleistung_Zaehler ".sprintf("%.4f",$einspeisung_wirk_count)."\n"); my $bezug_blind=hex(substr($hex,144,8))/10; my $bezug_blind_count=hex(substr($hex,160,16))/3600000; my $einspeisung_blind=hex(substr($hex,184,8))/10; my $einspeisung_blind_count=hex(substr($hex,200,16))/3600000; - readingsBulkUpdate($hash, $ps."Bezug_Blindleistung", sprintf("%.1f",$bezug_blind)); - readingsBulkUpdate($hash, $ps."Bezug_Blindleistung_Zaehler", sprintf("%.1f",$bezug_blind_count)); - readingsBulkUpdate($hash, $ps."Einspeisung_Blindleistung", sprintf("%.1f",$einspeisung_blind)); - readingsBulkUpdate($hash, $ps."Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$einspeisung_blind_count)); + push(@row_array, $ps."Bezug_Blindleistung ".sprintf("%.1f",$bezug_blind)."\n"); + push(@row_array, $ps."Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$bezug_blind_count)."\n"); + push(@row_array, $ps."Einspeisung_Blindleistung ".sprintf("%.1f",$einspeisung_blind)."\n"); + push(@row_array, $ps."Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$einspeisung_blind_count)."\n"); my $bezug_schein=hex(substr($hex,224,8))/10; my $bezug_schein_count=hex(substr($hex,240,16))/3600000; my $einspeisung_schein=hex(substr($hex,264,8))/10; my $einspeisung_schein_count=hex(substr($hex,280,16))/3600000; - readingsBulkUpdate($hash, $ps."Bezug_Scheinleistung", sprintf("%.1f",$bezug_schein)); - readingsBulkUpdate($hash, $ps."Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$bezug_schein_count)); - readingsBulkUpdate($hash, $ps."Einspeisung_Scheinleistung", sprintf("%.1f",$einspeisung_schein)); - readingsBulkUpdate($hash, $ps."Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$einspeisung_schein_count)); + push(@row_array, $ps."Bezug_Scheinleistung ".sprintf("%.1f",$bezug_schein)."\n"); + push(@row_array, $ps."Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$bezug_schein_count)."\n"); + push(@row_array, $ps."Einspeisung_Scheinleistung ".sprintf("%.1f",$einspeisung_schein)."\n"); + push(@row_array, $ps."Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$einspeisung_schein_count)."\n"); my $cosphi=hex(substr($hex,304,8))/1000; - readingsBulkUpdate($hash, $ps."CosPhi", sprintf("%.3f",$cosphi)); + push(@row_array, $ps."CosPhi ".sprintf("%.3f",$cosphi)."\n"); # L1 my $l1_bezug_wirk=hex(substr($hex,320,8))/10; my $l1_bezug_wirk_count=hex(substr($hex,336,16))/3600000; my $l1_einspeisung_wirk=hex(substr($hex,360,8))/10; my $l1_einspeisung_wirk_count=hex(substr($hex,376,16))/3600000; - readingsBulkUpdate($hash, $ps."L1_Saldo_Wirkleistung", sprintf("%.1f",$l1_einspeisung_wirk-$l1_bezug_wirk)); - readingsBulkUpdate($hash, $ps."L1_Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_wirk_count-$l1_bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."L1_Bezug_Wirkleistung", sprintf("%.1f",$l1_bezug_wirk)); - readingsBulkUpdate($hash, $ps."L1_Bezug_Wirkleistung_Zaehler", sprintf("%.1f",$l1_bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."L1_Einspeisung_Wirkleistung", sprintf("%.1f",$l1_einspeisung_wirk)); - readingsBulkUpdate($hash, $ps."L1_Einspeisung_Wirkleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_wirk_count)); + push(@row_array, $ps."L1_Saldo_Wirkleistung ".sprintf("%.1f",$l1_einspeisung_wirk-$l1_bezug_wirk)."\n"); + push(@row_array, $ps."L1_Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_wirk_count-$l1_bezug_wirk_count)."\n"); + push(@row_array, $ps."L1_Bezug_Wirkleistung ".sprintf("%.1f",$l1_bezug_wirk)."\n"); + push(@row_array, $ps."L1_Bezug_Wirkleistung_Zaehler ".sprintf("%.1f",$l1_bezug_wirk_count)."\n"); + push(@row_array, $ps."L1_Einspeisung_Wirkleistung ".sprintf("%.1f",$l1_einspeisung_wirk)."\n"); + push(@row_array, $ps."L1_Einspeisung_Wirkleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_wirk_count)."\n"); my $l1_bezug_blind=hex(substr($hex,400,8))/10; my $l1_bezug_blind_count=hex(substr($hex,416,16))/3600000; my $l1_einspeisung_blind=hex(substr($hex,440,8))/10; my $l1_einspeisung_blind_count=hex(substr($hex,456,16))/3600000; - readingsBulkUpdate($hash, $ps."L1_Bezug_Blindleistung", sprintf("%.1f",$l1_bezug_blind)); - readingsBulkUpdate($hash, $ps."L1_Bezug_Blindleistung_Zaehler", sprintf("%.1f",$l1_bezug_blind_count)); - readingsBulkUpdate($hash, $ps."L1_Einspeisung_Blindleistung", sprintf("%.1f",$l1_einspeisung_blind)); - readingsBulkUpdate($hash, $ps."L1_Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_blind_count)); + push(@row_array, $ps."L1_Bezug_Blindleistung ".sprintf("%.1f",$l1_bezug_blind)."\n"); + push(@row_array, $ps."L1_Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$l1_bezug_blind_count)."\n"); + push(@row_array, $ps."L1_Einspeisung_Blindleistung ".sprintf("%.1f",$l1_einspeisung_blind)."\n"); + push(@row_array, $ps."L1_Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_blind_count)."\n"); my $l1_bezug_schein=hex(substr($hex,480,8))/10; my $l1_bezug_schein_count=hex(substr($hex,496,16))/3600000; my $l1_einspeisung_schein=hex(substr($hex,520,8))/10; my $l1_einspeisung_schein_count=hex(substr($hex,536,16))/3600000; - readingsBulkUpdate($hash, $ps."L1_Bezug_Scheinleistung", sprintf("%.1f",$l1_bezug_schein)); - readingsBulkUpdate($hash, $ps."L1_Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$l1_bezug_schein_count)); - readingsBulkUpdate($hash, $ps."L1_Einspeisung_Scheinleistung", sprintf("%.1f",$l1_einspeisung_schein)); - readingsBulkUpdate($hash, $ps."L1_Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_schein_count)); + push(@row_array, $ps."L1_Bezug_Scheinleistung ".sprintf("%.1f",$l1_bezug_schein)."\n"); + push(@row_array, $ps."L1_Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$l1_bezug_schein_count)."\n"); + push(@row_array, $ps."L1_Einspeisung_Scheinleistung ".sprintf("%.1f",$l1_einspeisung_schein)."\n"); + push(@row_array, $ps."L1_Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$l1_einspeisung_schein_count)."\n"); my $l1_thd=hex(substr($hex,560,8))/1000; my $l1_v=hex(substr($hex,576,8))/1000; my $l1_cosphi=hex(substr($hex,592,8))/1000; - readingsBulkUpdate($hash, $ps."L1_THD", sprintf("%.2f",$l1_thd)); - readingsBulkUpdate($hash, $ps."L1_Spannung", sprintf("%.1f",$l1_v)); - readingsBulkUpdate($hash, $ps."L1_CosPhi", sprintf("%.3f",$l1_cosphi)); - + push(@row_array, $ps."L1_THD ".sprintf("%.2f",$l1_thd)."\n"); + push(@row_array, $ps."L1_Spannung ".sprintf("%.1f",$l1_v)."\n"); + push(@row_array, $ps."L1_CosPhi ".sprintf("%.3f",$l1_cosphi)."\n"); # L2 my $l2_bezug_wirk=hex(substr($hex,608,8))/10; my $l2_bezug_wirk_count=hex(substr($hex,624,16))/3600000; my $l2_einspeisung_wirk=hex(substr($hex,648,8))/10; my $l2_einspeisung_wirk_count=hex(substr($hex,664,16))/3600000; - readingsBulkUpdate($hash, $ps."L2_Saldo_Wirkleistung", sprintf("%.1f",$l2_einspeisung_wirk-$l2_bezug_wirk)); - readingsBulkUpdate($hash, $ps."L2_Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_wirk_count-$l2_bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."L2_Bezug_Wirkleistung", sprintf("%.1f",$l2_bezug_wirk)); - readingsBulkUpdate($hash, $ps."L2_Bezug_Wirkleistung_Zaehler", sprintf("%.1f",$l2_bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."L2_Einspeisung_Wirkleistung", sprintf("%.1f",$l2_einspeisung_wirk)); - readingsBulkUpdate($hash, $ps."L2_Einspeisung_Wirkleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_wirk_count)); + push(@row_array, $ps."L2_Saldo_Wirkleistung ".sprintf("%.1f",$l2_einspeisung_wirk-$l2_bezug_wirk)."\n"); + push(@row_array, $ps."L2_Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_wirk_count-$l2_bezug_wirk_count)."\n"); + push(@row_array, $ps."L2_Bezug_Wirkleistung ".sprintf("%.1f",$l2_bezug_wirk)."\n"); + push(@row_array, $ps."L2_Bezug_Wirkleistung_Zaehler ".sprintf("%.1f",$l2_bezug_wirk_count)."\n"); + push(@row_array, $ps."L2_Einspeisung_Wirkleistung ".sprintf("%.1f",$l2_einspeisung_wirk)."\n"); + push(@row_array, $ps."L2_Einspeisung_Wirkleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_wirk_count)."\n"); my $l2_bezug_blind=hex(substr($hex,688,8))/10; my $l2_bezug_blind_count=hex(substr($hex,704,16))/3600000; my $l2_einspeisung_blind=hex(substr($hex,728,8))/10; my $l2_einspeisung_blind_count=hex(substr($hex,744,16))/3600000; - readingsBulkUpdate($hash, $ps."L2_Bezug_Blindleistung", sprintf("%.1f",$l2_bezug_blind)); - readingsBulkUpdate($hash, $ps."L2_Bezug_Blindleistung_Zaehler", sprintf("%.1f",$l2_bezug_blind_count)); - readingsBulkUpdate($hash, $ps."L2_Einspeisung_Blindleistung", sprintf("%.1f",$l2_einspeisung_blind)); - readingsBulkUpdate($hash, $ps."L2_Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_blind_count)); + push(@row_array, $ps."L2_Bezug_Blindleistung ".sprintf("%.1f",$l2_bezug_blind)."\n"); + push(@row_array, $ps."L2_Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$l2_bezug_blind_count)."\n"); + push(@row_array, $ps."L2_Einspeisung_Blindleistung ".sprintf("%.1f",$l2_einspeisung_blind)."\n"); + push(@row_array, $ps."L2_Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_blind_count)."\n"); my $l2_bezug_schein=hex(substr($hex,768,8))/10; my $l2_bezug_schein_count=hex(substr($hex,784,16))/3600000; my $l2_einspeisung_schein=hex(substr($hex,808,8))/10; my $l2_einspeisung_schein_count=hex(substr($hex,824,16))/3600000; - readingsBulkUpdate($hash, $ps."L2_Bezug_Scheinleistung", sprintf("%.1f",$l2_bezug_schein)); - readingsBulkUpdate($hash, $ps."L2_Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$l2_bezug_schein_count)); - readingsBulkUpdate($hash, $ps."L2_Einspeisung_Scheinleistung", sprintf("%.1f",$l2_einspeisung_schein)); - readingsBulkUpdate($hash, $ps."L2_Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_schein_count)); + push(@row_array, $ps."L2_Bezug_Scheinleistung ".sprintf("%.1f",$l2_bezug_schein)."\n"); + push(@row_array, $ps."L2_Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$l2_bezug_schein_count)."\n"); + push(@row_array, $ps."L2_Einspeisung_Scheinleistung ".sprintf("%.1f",$l2_einspeisung_schein)."\n"); + push(@row_array, $ps."L2_Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$l2_einspeisung_schein_count)."\n"); my $l2_thd=hex(substr($hex,848,8))/1000; my $l2_v=hex(substr($hex,864,8))/1000; my $l2_cosphi=hex(substr($hex,880,8))/1000; - readingsBulkUpdate($hash, $ps."L2_THD", sprintf("%.2f",$l2_thd)); - readingsBulkUpdate($hash, $ps."L2_Spannung", sprintf("%.1f",$l2_v)); - readingsBulkUpdate($hash, $ps."L2_CosPhi", sprintf("%.3f",$l2_cosphi)); + push(@row_array, $ps."L2_THD ".sprintf("%.2f",$l2_thd)."\n"); + push(@row_array, $ps."L2_Spannung ".sprintf("%.1f",$l2_v)."\n"); + push(@row_array, $ps."L2_CosPhi ".sprintf("%.3f",$l2_cosphi)."\n"); # L3 my $l3_bezug_wirk=hex(substr($hex,896,8))/10; my $l3_bezug_wirk_count=hex(substr($hex,912,16))/3600000; my $l3_einspeisung_wirk=hex(substr($hex,936,8))/10; my $l3_einspeisung_wirk_count=hex(substr($hex,952,16))/3600000; - readingsBulkUpdate($hash, $ps."L3_Saldo_Wirkleistung", sprintf("%.1f",$l3_einspeisung_wirk-$l3_bezug_wirk)); - readingsBulkUpdate($hash, $ps."L3_Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_wirk_count-$l3_bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."L3_Bezug_Wirkleistung", sprintf("%.1f",$l3_bezug_wirk)); - readingsBulkUpdate($hash, $ps."L3_Bezug_Wirkleistung_Zaehler", sprintf("%.1f",$l3_bezug_wirk_count)); - readingsBulkUpdate($hash, $ps."L3_Einspeisung_Wirkleistung", sprintf("%.1f",$l3_einspeisung_wirk)); - readingsBulkUpdate($hash, $ps."L3_Einspeisung_Wirkleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_wirk_count)); + push(@row_array, $ps."L3_Saldo_Wirkleistung ".sprintf("%.1f",$l3_einspeisung_wirk-$l3_bezug_wirk)."\n"); + push(@row_array, $ps."L3_Saldo_Wirkleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_wirk_count-$l3_bezug_wirk_count)."\n"); + push(@row_array, $ps."L3_Bezug_Wirkleistung ".sprintf("%.1f",$l3_bezug_wirk)."\n"); + push(@row_array, $ps."L3_Bezug_Wirkleistung_Zaehler ".sprintf("%.1f",$l3_bezug_wirk_count)."\n"); + push(@row_array, $ps."L3_Einspeisung_Wirkleistung ".sprintf("%.1f",$l3_einspeisung_wirk)."\n"); + push(@row_array, $ps."L3_Einspeisung_Wirkleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_wirk_count)."\n"); my $l3_bezug_blind=hex(substr($hex,976,8))/10; my $l3_bezug_blind_count=hex(substr($hex,992,16))/3600000; my $l3_einspeisung_blind=hex(substr($hex,1016,8))/10; my $l3_einspeisung_blind_count=hex(substr($hex,1032,16))/3600000; - readingsBulkUpdate($hash, $ps."L3_Bezug_Blindleistung", sprintf("%.1f",$l3_bezug_blind)); - readingsBulkUpdate($hash, $ps."L3_Bezug_Blindleistung_Zaehler", sprintf("%.1f",$l3_bezug_blind_count)); - readingsBulkUpdate($hash, $ps."L3_Einspeisung_Blindleistung", sprintf("%.1f",$l3_einspeisung_blind)); - readingsBulkUpdate($hash, $ps."L3_Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_blind_count)); + push(@row_array, $ps."L3_Bezug_Blindleistung ".sprintf("%.1f",$l3_bezug_blind)."\n"); + push(@row_array, $ps."L3_Bezug_Blindleistung_Zaehler ".sprintf("%.1f",$l3_bezug_blind_count)."\n"); + push(@row_array, $ps."L3_Einspeisung_Blindleistung ".sprintf("%.1f",$l3_einspeisung_blind)."\n"); + push(@row_array, $ps."L3_Einspeisung_Blindleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_blind_count)."\n"); my $l3_bezug_schein=hex(substr($hex,1056,8))/10; my $l3_bezug_schein_count=hex(substr($hex,1072,16))/3600000; my $l3_einspeisung_schein=hex(substr($hex,1096,8))/10; my $l3_einspeisung_schein_count=hex(substr($hex,1112,16))/3600000; - readingsBulkUpdate($hash, $ps."L3_Bezug_Scheinleistung", sprintf("%.1f",$l3_bezug_schein)); - readingsBulkUpdate($hash, $ps."L3_Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$l3_bezug_schein_count)); - readingsBulkUpdate($hash, $ps."L3_Einspeisung_Scheinleistung", sprintf("%.1f",$l3_einspeisung_schein)); - readingsBulkUpdate($hash, $ps."L3_Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_schein_count)); + push(@row_array, $ps."L3_Bezug_Scheinleistung ".sprintf("%.1f",$l3_bezug_schein)."\n"); + push(@row_array, $ps."L3_Bezug_Scheinleistung_Zaehler ".sprintf("%.1f",$l3_bezug_schein_count)."\n"); + push(@row_array, $ps."L3_Einspeisung_Scheinleistung ".sprintf("%.1f",$l3_einspeisung_schein)."\n"); + push(@row_array, $ps."L3_Einspeisung_Scheinleistung_Zaehler ".sprintf("%.1f",$l3_einspeisung_schein_count)."\n"); my $l3_thd=hex(substr($hex,1136,8))/1000; my $l3_v=hex(substr($hex,1152,8))/1000; my $l3_cosphi=hex(substr($hex,1168,8))/1000; - readingsBulkUpdate($hash, $ps."L3_THD", sprintf("%.2f",$l3_thd)); - readingsBulkUpdate($hash, $ps."L3_Spannung", sprintf("%.1f",$l3_v)); - readingsBulkUpdate($hash, $ps."L3_CosPhi", sprintf("%.3f",$l3_cosphi)); + push(@row_array, $ps."L3_THD ".sprintf("%.2f",$l3_thd)."\n"); + push(@row_array, $ps."L3_Spannung ".sprintf("%.1f",$l3_v)."\n"); + push(@row_array, $ps."L3_CosPhi ".sprintf("%.3f",$l3_cosphi)."\n"); - readingsEndUpdate($hash, 1); - - $hash->{HELPER}{LASTUPDATE}=time(); - - # $update time - my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = localtime; - $hash->{LASTUPDATE} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ; - } + Log3 ($name, 5, "$name - row_array before encoding:"); + foreach my $row (@row_array) { + chomp $row; + Log3 ($name, 5, "SMAEM $name - $row"); + } + + # encoding result + my $rowlist = join('|', @row_array); + $rowlist = encode_base64($rowlist,""); + + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_DoParse finished"); + +return "$name|$rowlist|$gridinsum|$gridoutsum|''|$discycles"; } -###################################################################################### +############################################################### +# Auswertung non-blocking Inverter Datenabruf +############################################################### +sub smaemread_ParseDone ($) { + my ($string) = @_; + my @a = split("\\|",$string); + my $name = $a[0]; + my $hash = $defs{$name}; + my $rowlist = decode_base64($a[1]); + my $gridinsum = $a[2]; + my $gridoutsum = $a[3]; + my $error = decode_base64($a[4]) if($a[4]); + my $discycles = $a[5]; + + Log3 ($name, 4, "SMAEM $name -> Start BlockingCall smaemread_ParseDone"); + + $hash->{HELPER}{FAULTEDCYCLES} = $discycles; + + # update time + lastupdate_set($hash); + + if ($error) { + readingsSingleUpdate($hash, "state", $error, 1); + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_ParseDone finished"); + delete($hash->{HELPER}{RUNNING_PID}); + return; + } + + $hash->{GRIDIN_SUM} = $gridinsum; + $hash->{GRIDOUT_SUM} = $gridoutsum; + Log3($name, 4, "SMAEM $name - wrote new energy values to INTERNALS - GRIDIN_SUM: $gridinsum, GRIDOUT_SUM: $gridoutsum"); + + my @row_array = split("\\|", $rowlist); + + Log3 ($name, 5, "SMAEM $name - row_array after decoding:"); + foreach my $row (@row_array) { + chomp $row; + Log3 ($name, 5, "SMAEM $name - $row"); + } + + readingsBeginUpdate($hash); + foreach my $row (@row_array) { + chomp $row; + my @a = split(" ", $row, 2); + readingsBulkUpdate($hash, $a[0], $a[1]); + } + readingsEndUpdate($hash, 1); + + delete($hash->{HELPER}{RUNNING_PID}); + Log3 ($name, 4, "SMAEM $name -> BlockingCall smaemread_ParseDone finished"); + +return; +} + +############################################################### +# Abbruchroutine Timeout Inverter Abfrage +############################################################### +sub smaemread_ParseAborted($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $discycles = $hash->{HELPER}{FAULTEDCYCLES}; + + $discycles++; + $hash->{HELPER}{FAULTEDCYCLES} = $discycles; + Log3 ($name, 1, "SMAEM $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} timed out"); + readingsSingleUpdate($hash, "state", "timeout", 1); + delete($hash->{HELPER}{RUNNING_PID}); +} + + +############################################################### +# Hilfsroutinen +############################################################### + +############################################################### ### Summenwerte für GridIn, GridOut speichern sub setsum ($$$) { - my ($hash, $gridin_sum, $gridout_sum) = @_; + my ($hash, $gridinsum, $gridoutsum) = @_; my $name = $hash->{NAME}; - my $success; my $index; - my $retcode; + my $retcode = 0; my $sumstr; + my $modpath = AttrVal("global", "modpath", undef); - $sumstr = $gridin_sum."_".$gridout_sum; + $sumstr = $gridinsum."_".$gridoutsum; $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum"; $retcode = setKeyValue($index, $sumstr); if ($retcode) { - Log3($name, 1, "$name - Error while saving summary of energy values - $retcode"); - $success = 0; - } - else - { - Log3($name, 4, "$name - summary of energy values saved - GRIDIN_SUM: $gridin_sum, GRIDOUT_SUM: $gridout_sum"); - $success = 1; - } - -return ($success); + Log3($name, 1, "SMAEM $name - ERROR while saving summary of energy values - $retcode"); + } else { + Log3($name, 4, "SMAEM $name - new energy values saved to $modpath/FHEM/FhemUtils/uniqueID:"); + Log3($name, 4, "SMAEM $name - GRIDIN_SUM: $gridinsum, GRIDOUT_SUM: $gridoutsum"); + } +return ($retcode); } -###################################################################################### +############################################################### ### Summenwerte für GridIn, GridOut abtufen - sub getsum ($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $success; my $index; - my $retcode; + my $retcode = 0; my $sumstr; - my ($gridin_sum, $gridout_sum); - + my $modpath = AttrVal("global", "modpath", undef); + $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum"; ($retcode, $sumstr) = getKeyValue($index); if ($retcode) { - Log3($name, 1, "$name - ERROR -unable to read summary of energy values from file - $retcode"); - $success = 0; - } - - if ($sumstr) { - ($gridin_sum, $gridout_sum) = split(/_/, $sumstr); - Log3($name, 4, "$name - summary of energy values was read from file - GRIDIN_SUM: $gridin_sum, GRIDOUT_SUM: $gridout_sum"); - $success = 1; - } - -return ($success, $gridin_sum, $gridout_sum); + Log3($name, 1, "SMAEM $name - ERROR while reading saved energy values from $modpath/FHEM/FhemUtils/uniqueID:"); + Log3($name, 1, "SMAEM $name - $retcode"); + } else { + if ($sumstr) { + ($hash->{GRIDIN_SUM}, $hash->{GRIDOUT_SUM}) = split(/_/, $sumstr); + Log3 ($name, 3, "SMAEM $name - read saved energy values from $modpath/FHEM/FhemUtils/uniqueID:"); + Log3 ($name, 3, "SMAEM $name - GRIDIN_SUM: $hash->{GRIDIN_SUM}, GRIDOUT_SUM: $hash->{GRIDOUT_SUM}"); + } + } +return ($retcode); } -###################################################################################### - +############################################################### +### $update time of last update +sub lastupdate_set ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + $hash->{HELPER}{LASTUPDATE} = time(); + my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = localtime(); + $hash->{LASTUPDATE} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ; + Log3 ($name, 4, "SMAEM $name - last update time set to: $hash->{LASTUPDATE}"); +} + 1; - - =pod +=item summary Integration of SMA Energy Meters +=item summary_DE Integration von SMA Energy Meter + =begin html

SMAEM

-