From f85728faa3c9ee8a89c4779ed51e04079157e826 Mon Sep 17 00:00:00 2001 From: MadMax Date: Wed, 23 Jun 2021 14:19:30 +0000 Subject: [PATCH] MadMax:add Folder git-svn-id: https://svn.fhem.de/fhem/trunk@24671 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/MadMax/76_SMAInverter.pm | 2516 +++++++++++++++++++++++++ 1 file changed, 2516 insertions(+) create mode 100644 fhem/contrib/MadMax/76_SMAInverter.pm diff --git a/fhem/contrib/MadMax/76_SMAInverter.pm b/fhem/contrib/MadMax/76_SMAInverter.pm new file mode 100644 index 000000000..1d97c32c0 --- /dev/null +++ b/fhem/contrib/MadMax/76_SMAInverter.pm @@ -0,0 +1,2516 @@ +################################################################################################################# +# $Id: 76_SMAInverter.pm 23909 2021-03-07 20:06:59Z DS_Starter $ +################################################################################################################# +# +# Copyright notice +# +# Published according Creative Commons : Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0) +# Details: https://creativecommons.org/licenses/by-nc-sa/3.0/ +# +# Credits: +# - based on 77_SMASTP.pm by Volker Kettenbach with following credits: +# - based on an Idea by SpenZerX and HDO +# - Waldmensch for various improvements +# - sbfspot (https://sbfspot.codeplex.com/) +# - rewritten by Thomas Schoedl (sct14675) with inputs from Volker, waldmensch and DS_Starter +# +# Description: +# This is an FHEM-Module for SMA Inverters. +# +################################################################################################################# + +package main; + +use strict; +use warnings; +eval "use IO::Socket::INET;1" or my $MissModulSocket = "IO::Socket::INET"; +eval "use DateTime;1" or my $MissModulDateTime = "DateTime"; +use Time::HiRes qw(gettimeofday tv_interval); +use Blocking; +use Time::Local; +eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; + +# Versions History by DS_Starter +our %SMAInverter_vNotesIntern = ( + "2.16.1" => "21.06.2021 MadMax: hide unavailable data", + "2.16.0" => "21.06.2021 MadMax: AC Voltage and AC Curren read fixed, read CosPhi included ", + "2.15.1" => "18.06.2021 MadMax: SBS1.5, SBS2.0, SBS2.5 read battery data included ", + "2.15.0" => "14.06.2021 MadMax: SBS5.0-10, SBS6.0-10, SBS3.7-10 read battery data included ", + "2.14.2" => "02.06.2021 new inverter type 9359=SBS6.0-10 ", + "2.14.1" => "27.02.2021 change save .etotal_yesterday, Forum: https://forum.fhem.de/index.php/topic,56080.msg1134664.html#msg1134664 ", + "2.14.0" => "08.10.2019 readings bat_loadtotal (BAT_LOADTOTAL), bat_loadtoday (BAT_LOADTODAY) included by 300P, Forum: #topic,56080.msg986302.html#msg986302", + "2.13.4" => "30.08.2019 STP10.0-3AV-40 298 included into %SMAInverter_devtypes ", + "2.13.3" => "28.08.2019 commandref revised ", + "2.13.2" => "27.08.2019 fix WARNING: Use of uninitialized value \$_ in substitution (s///) at /opt/fhem//FHEM/Blocking.pm line 238 ", + "2.13.1" => "22.08.2019 commandref revised ", + "2.13.0" => "20.08.2019 support of Meta.pm ", + "2.12.0" => "20.08.2019 set warning to log if SPOT_ETODAY, SPOT_ETOTAL was not delivered or successfully ". + "calculated in SMAInverter_SMAcommand, Forum: https://forum.fhem.de/index.php/topic,56080.msg967823.html#msg967823 ", + "2.11.0" => "17.08.2019 attr target-serial, target-susyid are set automatically if not defined, commandref revised ", + "2.10.2" => "14.08.2019 new types to %SMAInverter_devtypes ", + "2.10.1" => "28.04.2019 fix perl warnings, Forum:#56080.msg933276.html#msg933276 ", + "2.10.0" => "29.06.2018 Internal MODEL added ", + "2.9.2" => "08.10.2017 adapted to use extended abortArg (Forum:77472) ", + "2.9.1" => "24.04.2017 fix for issue #24 (Wrong INV_TYPE for STP10000TL-20) and fix for issue #25 (unpack out of range for SB1.5-1VL-40) ", + "2.9.0" => "23.04.2017 fixed issue #22: wrong logon command for SunnyBoy systems ", + "2.8.3" => "19.04.2017 enhanced inverter Type-Hash ", + "2.8.2" => "23.03.2017 changed SMAInverter_SMAlogon sub ", + "2.8.1" => "06.12.2016 SMAInverter version as internal ", + "2.8.0" => "05.12.2016 changed commandsections to make sure getting only data from inverters with preset ". + "\$inv_susyid and \$inv_serial ", + "2.7.4" => "04.12.2016 change loading of IO::Socket::INET, DateTime ", + "2.7.3" => "04.12.2016 commandref adapted ", + "2.7.2" => "03.12.2016 use Time::HiRes qw(gettimeofday tv_interval ", + "2.7.1" => "02.12.2016 showproctime improved ", + "2.7.0" => "02.12.2016 showproctime added ", + "2.6.1" => "29.11.2016 SMAInverter_getstatusDoParse changed due to inititialized issues ", + "2.6.0" => "28.11.2016 bugfix warnings ParseDone redefine at startup, uninitialized value \$avg if FHEM was ". + "restarted in sleeptime, switched avg_energy to avg_power, commandref updated ", + "2.5.2" => "27.11.2016 bugfix average calc, bugfix warnings at startup ", + "2.5.1" => "26.11.2016 calc of averagebuf changed to 5, 10, 15 minutes ", + "2.5.0" => "26.11.2016 averagebuf changed, Attr timeout added ", + "2.4.0" => "26.11.2016 create ringbuffer for calculating average energy last 5, 10, 15 cycles ", + "2.3.0" => "25.11.2016 bugfixing ", + "2.2.0" => "24.11.2016 further optimize of non-blocking operation ", + "2.1.0" => "24.11.2016 avg_energy_lastcycles added ", + "2.0.0" => "24.11.2016 switched module to non-blocking operation ", + "1.8.4" => "23.11.2016 prepare non-blocking operation ", + "1.8.3" => "23.11.2016 readings opertime_start, opertime_stop ", + "1.8.2" => "22.11.2016 eliminate global vars, prepare non-blocking operation ", + "1.8.1" => "22.11.2016 eliminate global vars, create command array ", + "1.8.0" => "21.11.2016 eliminate \$r_OK, \$r_FAIL, create command-array ", + "1.7.0" => "21.11.2016 devtypes completed, minor bugfixes, commandref completed ", + "1.6.1" => "19.11.2016 bugfix perl warning during fhem start ", + "1.6.0" => "09.11.2016 added operation control by sunrise,sunset, Attr offset, suppressSleep added ", + "1.5.0" => "08.11.2016 added device classes hash ", + "1.4.0" => "07.11.2016 compatibility to SBFSpot improved, bilingual dependend on attr \"language\" of global-device ". + "added hash of SMA device types ", + "1.3.0" => "07.11.2016 Attr SBFSpotComp added to get compatibility mode with SBFSpot ", + "1.2.0" => "06.11.2016 function get data added, log output level changed to 4 in sub SMAInverter_Attr, some code changes ", + "1.1.0" => "06.11.2016 Attr mode manual, automatic added ", + "1.0.0" => "06.11.2016 Attr disable added, \$globalName replaced by \$name in all expressions (due to module redesign to non-blocking later) " +); + +# Inverter Data fields and supported commands flags. +# $inv_SPOT_ETODAY # Today yield +# $inv_SPOT_ETOTAL # Total yield +# $inv_SPOT_PDC1 # DC power input 1 +# $inv_SPOT_PDC2 # DC power input 2 +# $inv_SPOT_PAC1 # Power L1 +# $inv_SPOT_PAC2 # Power L2 +# $inv_SPOT_PAC3 # Power L3 +# $inv_PACMAX1 # Nominal power in Ok Mode +# $inv_PACMAX2 # Nominal power in Warning Mode +# $inv_PACMAX3 # Nominal power in Fault Mode +# $inv_PACMAX1_2 # Maximum active power device (Some inverters like SB3300/SB1200) +# $inv_SPOT_PACTOT # Total Power +# $inv_ChargeStatus # Battery Charge status +# $inv_SPOT_UDC1 # DC voltage input +# $inv_SPOT_UDC2 # DC voltage input +# $inv_SPOT_IDC1 # DC current input +# $inv_SPOT_IDC2 # DC current input +# $inv_SPOT_UAC1 # Grid voltage phase L1 +# $inv_SPOT_UAC2 # Grid voltage phase L2 +# $inv_SPOT_UAC3 # Grid voltage phase L3 +# $inv_SPOT_UAC1_2 # Grid voltage phase L1 - L2 +# $inv_SPOT_UAC2_3 # Grid voltage phase L2 - L3 +# $inv_SPOT_UAC3_1 # Grid voltage phase L3 - L1 +# $inv_SPOT_IAC1 # Grid current phase L1 +# $inv_SPOT_IAC2 # Grid current phase L2 +# $inv_SPOT_IAC3 # Grid current phase L3 +# $inv_BAT_UDC # Battery Voltage +# $inv_BAT_IDC # Battery Current +# $inv_BAT_CYCLES # Battery recharge cycles +# $inv_BAT_TEMP # Battery temperature +# $inv_SPOT_FREQ # Grid Frequency +# $inv_CLASS # Inverter Class +# $inv_TYPE # Inverter Type +# $inv_SPOT_OPERTM # Operation Time +# $inv_SPOT_FEEDTM # Feed-in time +# $inv_TEMP # Inverter temperature +# $inv_GRIDRELAY # Grid Relay/Contactor Status +# $inv_STATUS # Inverter Status +# $inv_BAT_LOADTODAY # Today Batteryload +# $inv_BAT_LOADTOTAL # Total Batteryload + +# Aufbau Wechselrichter Type-Hash +# https://github.com/SBFspot/SBFspot/blob/master/SBFspot/TagListDE-DE.txt +my %SMAInverter_devtypes = ( +0000 => "Unknown Inverter Type", +9015 => "SB 700", +9016 => "SB 700U", +9017 => "SB 1100", +9018 => "SB 1100U", +9019 => "SB 1100LV", +9020 => "SB 1700", +9021 => "SB 1900TLJ", +9022 => "SB 2100TL", +9023 => "SB 2500", +9024 => "SB 2800", +9025 => "SB 2800i", +9026 => "SB 3000", +9027 => "SB 3000US", +9028 => "SB 3300", +9029 => "SB 3300U", +9030 => "SB 3300TL", +9031 => "SB 3300TL HC", +9032 => "SB 3800", +9033 => "SB 3800U", +9034 => "SB 4000US", +9035 => "SB 4200TL", +9036 => "SB 4200TL HC", +9037 => "SB 5000TL", +9038 => "SB 5000TLW", +9039 => "SB 5000TL HC", +9066 => "SB 1200", +9067 => "STP 10000TL-10", +9068 => "STP 12000TL-10", +9069 => "STP 15000TL-10", +9070 => "STP 17000TL-10", +9084 => "WB 3600TL-20", +9085 => "WB 5000TL-20", +9086 => "SB 3800US-10", +9098 => "STP 5000TL-20", +9099 => "STP 6000TL-20", +9100 => "STP 7000TL-20", +9101 => "STP 8000TL-10", +9102 => "STP 9000TL-20", +9103 => "STP 8000TL-20", +9104 => "SB 3000TL-JP-21", +9105 => "SB 3500TL-JP-21", +9106 => "SB 4000TL-JP-21", +9107 => "SB 4500TL-JP-21", +9108 => "SCSMC", +9109 => "SB 1600TL-10", +9131 => "STP 20000TL-10", +9139 => "STP 20000TLHE-10", +9140 => "STP 15000TLHE-10", +9157 => "Sunny Island 2012", +9158 => "Sunny Island 2224", +9159 => "Sunny Island 5048", +9160 => "SB 3600TL-20", +9168 => "SC630HE-11", +9169 => "SC500HE-11", +9170 => "SC400HE-11", +9171 => "WB 3000TL-21", +9172 => "WB 3600TL-21", +9173 => "WB 4000TL-21", +9174 => "WB 5000TL-21", +9175 => "SC 250", +9176 => "SMA Meteo Station", +9177 => "SB 240-10", +9171 => "WB 3000TL-21", +9172 => "WB 3600TL-21", +9173 => "WB 4000TL-21", +9174 => "WB 5000TL-21", +9179 => "Multigate-10", +9180 => "Multigate-US-10", +9181 => "STP 20000TLEE-10", +9182 => "STP 15000TLEE-10", +9183 => "SB 2000TLST-21", +9184 => "SB 2500TLST-21", +9185 => "SB 3000TLST-21", +9186 => "WB 2000TLST-21", +9187 => "WB 2500TLST-21", +9188 => "WB 3000TLST-21", +9189 => "WTP 5000TL-20", +9190 => "WTP 6000TL-20", +9191 => "WTP 7000TL-20", +9192 => "WTP 8000TL-20", +9193 => "WTP 9000TL-20", +9254 => "Sunny Island 3324", +9255 => "Sunny Island 4.0M", +9256 => "Sunny Island 4248", +9257 => "Sunny Island 4248U", +9258 => "Sunny Island 4500", +9259 => "Sunny Island 4548U", +9260 => "Sunny Island 5.4M", +9261 => "Sunny Island 5048U", +9262 => "Sunny Island 6048U", +9278 => "Sunny Island 3.0M", +9279 => "Sunny Island 4.4M", +9281 => "STP 10000TL-20", +9282 => "STP 11000TL-20", +9283 => "STP 12000TL-20", +9284 => "STP 20000TL-30", +9285 => "STP 25000TL-30", +9301 => "SB1.5-1VL-40", +9302 => "SB2.5-1VL-40", +9303 => "SB2.0-1VL-40", +9304 => "SB5.0-1SP-US-40", +9305 => "SB6.0-1SP-US-40", +9306 => "SB8.0-1SP-US-40", +9307 => "Energy Meter", +9313 => "SB50.0-3SP-40", +9319 => "SB3.0-1AV-40 (Sunny Boy 3.0 AV-40)", +9320 => "SB3.6-1AV-40 (Sunny Boy 3.6 AV-40)", +9321 => "SB4.0-1AV-40 (Sunny Boy 4.0 AV-40)", +9322 => "SB5.0-1AV-40 (Sunny Boy 5.0 AV-40)", +9324 => "SBS1.5-1VL-10 (Sunny Boy Storage 1.5)", +9325 => "SBS2.0-1VL-10 (Sunny Boy Storage 2.0)", +9326 => "SBS2.5-1VL-10 (Sunny Boy Storage 2.5)", +9327 => "SMA Energy Meter", +9331 => "SI 3.0M-12 (Sunny Island 3.0M)", +9332 => "SI 4.4M-12 (Sunny Island 4.4M)", +9333 => "SI 6.0H-12 (Sunny Island 6.0H)", +9334 => "SI 8.0H-12 (Sunny Island 8.0H)", +9335 => "SMA Com Gateway", +9336 => "STP 15000TL-30", +9337 => "STP 17000TL-30", +9344 => "STP4.0-3AV-40 (Sunny Tripower 4.0)", +9345 => "STP5.0-3AV-40 (Sunny Tripower 5.0)", +9346 => "STP6.0-3AV-40 (Sunny Tripower 6.0)", +9347 => "STP8.0-3AV-40 (Sunny Tripower 8.0)", +9348 => "STP10.0-3AV-40 (Sunny Tripower 10.0)", +9356 => "SBS3.7-1VL-10 (Sunny Boy Storage 3.7)", +9358 => "SBS5.0-10 (Sunny Boy Storage 5.0)", +9359 => "SBS6.0-10 (Sunny Boy Storage 6.0)", +9366 => "STP3.0-3AV-40 (Sunny Tripower 3.0)", +9401 => "SB3.0-1AV-41 (Sunny Boy 3.0 AV-41)", +9402 => "SB3.6-1AV-41 (Sunny Boy 3.6 AV-41)", +9403 => "SB4.0-1AV-41 (Sunny Boy 4.0 AV-41)", +9404 => "SB5.0-1AV-41 (Sunny Boy 5.0 AV-41)", +9405 => "SB6.0-1AV-41 (Sunny Boy 6.0 AV-41)", +); + +# Wechselrichter Class-Hash DE +my %SMAInverter_classesDE = ( +8000 => "Alle Geräte", +8001 => "Solar-Wechselrichter", +8002 => "Wind-Wechselrichter", +8007 => "Batterie-Wechselrichter", +8033 => "Verbraucher", +8064 => "Sensorik allgemein", +8065 => "Stromzähler", +8128 => "Kommunikationsprodukte", +); + +# Wechselrichter Class-Hash EN +my %SMAInverter_classesEN = ( +8000 => "All Devices", +8001 => "Solar Inverters", +8002 => "Wind Turbine Inverter", +8007 => "Batterie Inverters", +8033 => "Consumer", +8064 => "Sensor System in General", +8065 => "Electricity meter", +8128 => "Communication products", +); + +############################################################### +# SMAInverter Initialize +############################################################### +sub SMAInverter_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = "SMAInverter_Define"; + $hash->{UndefFn} = "SMAInverter_Undef"; + $hash->{GetFn} = "SMAInverter_Get"; + $hash->{AttrList} = "interval " . + "detail-level:0,1,2 " . + "disable:1,0 " . + "mode:manual,automatic ". + "offset ". + "suppressSleep:1,0 ". + "SBFSpotComp:1,0 " . + "showproctime:1,0 ". + "timeout " . + "target-susyid " . + "target-serial " . + $readingFnAttributes; + + $hash->{AttrFn} = "SMAInverter_Attr"; + + eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) + +return; +} + +############################################################### +# SMAInverter Define +############################################################### +sub SMAInverter_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Error: Perl module ".$MissModulSocket." is missing. + Install it on Debian with: sudo apt-get install libio-socket-multicast-perl" if($MissModulSocket); + return "Error: Perl module ".$MissModulDateTime." is missing. + Install it on Debian with: sudo apt-get install libdatetime-perl" if($MissModulDateTime); + + return "Wrong syntax: use define SMAInverter " if ((int(@a) < 4) and (int(@a) > 5)); + + my $name = $hash->{NAME}; + $hash->{LASTUPDATE} = 0; + $hash->{INTERVAL} = $hash->{HELPER}{INTERVAL} = AttrVal($name, "interval", 60); + $hash->{HELPER}{FAULTEDCYCLES} = 0; + delete($hash->{HELPER}{AVERAGEBUF}) if($hash->{HELPER}{AVERAGEBUF}); + + # protocol related defaults + $hash->{HELPER}{MYSUSYID} = 233; # random number, has to be different from any device in local network + $hash->{HELPER}{MYSERIALNUMBER} = 123321123; # random number, has to be different from any device in local network + $hash->{HELPER}{DEFAULT_TARGET_SUSYID} = 0xFFFF; # 0xFFFF is any susyid + $hash->{HELPER}{DEFAULT_TARGET_SERIAL} = 0xFFFFFFFF; # 0xFFFFFFFF is any serialnumber + $hash->{HELPER}{PKT_ID} = 0x8001; # Packet ID + $hash->{HELPER}{MAXBYTES} = 300; # constant MAXBYTES scalar 300 + $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden + + # Versionsinformationen setzen + SMAInverter_setVersionInfo($hash); + + my ($IP,$Host,$Caps); + + my $Pass = $a[2]; # to do: check 1-12 Chars + + # extract IP or Hostname from $a[3] + if (!defined $Host) { + if ( $a[3] =~ /^([A-Za-z0-9_.])/ ) { + $Host = $a[3]; + } + } + + if (!defined $Host) { + return "Argument:{$a[3]} not accepted as Host or IP. Read device specific help file."; + } + + $hash->{PASS} = $Pass; + $hash->{HOST} = $Host; + + InternalTimer(gettimeofday()+5, "SMAInverter_GetData", $hash, 0); # Start Hauptroutine + +return undef; +} + +############################################################### +# SMAInverter Undefine +############################################################### +sub SMAInverter_Undef($$) { + my ($hash, $name) = @_; + RemoveInternalTimer($hash); + BlockingKill($hash->{HELPER}{RUNNING_PID}); +return undef; +} + +############################################################### +# SMAInverter Get +############################################################### +sub SMAInverter_Get($$) { + my ($hash, @a) = @_; + return "\"get X\" needs at least an argument" if ( @a < 2 ); + my $name = shift @a; + my $opt = shift @a; + my $timeout = AttrVal($name, "timeout", 60); + + my $getlist = "Unknown argument $opt, choose one of ". + "data:noArg "; + + return "module is disabled" if(IsDisabled($name)); + + if ($opt eq "data") { + SMAInverter_GetData($hash); + } else { + return "$getlist"; + } +return undef; +} + +############################################################### +# SMAInverter Attr +############################################################### +sub SMAInverter_Attr(@) { + my ($cmd,$name,$aName,$aVal) = @_; + # $cmd can be "del" or "set" + # $name is device name + # aName and aVal are Attribute name and value + my $hash = $defs{$name}; + my $do; + + if ($aName eq "mode") { + if ($cmd eq "set" && $aVal eq "manual") { + $hash->{INTERVAL} = $aVal; + } else { + $hash->{INTERVAL} = $hash->{HELPER}{INTERVAL}; + } + InternalTimer(time+5, 'SMAInverter_GetData', $hash, 0); + } + + 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); + + if ($do == 0) { + my $mode = AttrVal($name, "mode", "automatic"); + RemoveInternalTimer($hash); + InternalTimer(time+5, 'SMAInverter_GetData', $hash, 0); + } else { + RemoveInternalTimer($hash); + } + } + + if ($aName eq "detail-level") { + delete $defs{$name}{READINGS}; + } + + if ($aName eq "SBFSpotComp") { + delete $defs{$name}{READINGS}; + } + + if ($aName eq "interval") { + if ($cmd eq "set") { + $hash->{HELPER}{INTERVAL} = $aVal; + $hash->{INTERVAL} = $aVal if(AttrVal($name, "mode", "") ne "manual"); + delete($hash->{HELPER}{AVERAGEBUF}) if($hash->{HELPER}{AVERAGEBUF}); + Log3 $name, 3, "$name - Set $aName to $aVal"; + } else { + $hash->{INTERVAL} = $hash->{HELPER}{INTERVAL} = 60; + } + } + + if ($cmd eq "set" && $aName eq "offset") { + if($aVal !~ /^\d+$/ || $aVal < 0 || $aVal > 7200) { return "The Value of $aName is not valid. Use value between 0 ... 7200 !";} + } + if ($cmd eq "set" && $aName eq "timeout") { + unless ($aVal =~ /^[0-9]+$/) { return " The Value for $aName is not valid. Use only figures 1-9 !";} + } +return; +} + +############################################################### +# Hauptschleife Datenabruf +############################################################### +sub SMAInverter_GetData($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $interval = AttrVal($name, "interval", 60); + my $timeout = AttrVal($name, "timeout", 60); + + RemoveInternalTimer($hash, "SMAInverter_GetData"); + + if ($init_done != 1) { + InternalTimer(gettimeofday()+5, "SMAInverter_GetData", $hash, 0); + return; + } + + return if(IsDisabled($name)); + + if (exists($hash->{HELPER}{RUNNING_PID})) { + Log3 ($name, 3, "SMAInverter $name - WARNING - old process $hash->{HELPER}{RUNNING_PID}{pid} will be killed now to start a new BlockingCall"); + BlockingKill($hash->{HELPER}{RUNNING_PID}); + } + + Log3 ($name, 4, "$name - ###############################################################"); + Log3 ($name, 4, "$name - ########## Begin of new SMAInverter get data cycle ##########"); + Log3 ($name, 4, "$name - ###############################################################"); + Log3 ($name, 4, "$name - timeout cycles since module start: $hash->{HELPER}{FAULTEDCYCLES}"); + + # decide of operation + if(AttrVal($name,"mode","automatic") eq "automatic") { + # automatic operation mode + InternalTimer(gettimeofday()+$interval, "SMAInverter_GetData", $hash, 0); + } + +$hash->{HELPER}{RUNNING_PID} = BlockingCall("SMAInverter_getstatusDoParse", "$name", "SMAInverter_getstatusParseDone", $timeout, "SMAInverter_getstatusParseAborted", $hash); +$hash->{HELPER}{RUNNING_PID}{loglevel} = 4; + +return; +} + +############################################################### +# non-blocking Inverter Datenabruf +############################################################### +sub SMAInverter_getstatusDoParse($) { + my ($name) = @_; + my $hash = $defs{$name}; + my $interval = AttrVal($name, "interval", 60); + my $sc = AttrVal($name, "SBFSpotComp", 0); + my ($sup_EnergyProduction, + $sup_SpotDCPower, + $sup_SpotACPower, + $sup_MaxACPower, + $sup_MaxACPower2, + $sup_SpotACTotalPower, + $sup_ChargeStatus, + $sup_SpotDCVoltage, + $sup_SpotACVoltage, + $sup_SpotACCurrent, + $sup_BatteryInfo, + $sup_BatteryInfo_2, #SBS(1.5|2.0|2.5) + $sup_BatteryInfo_TEMP, + $sup_BatteryInfo_UDC, + $sup_BatteryInfo_IDC, + $sup_BatteryInfo_Capac, + $sup_SpotGridFrequency, + $sup_TypeLabel, + $sup_OperationTime, + $sup_InverterTemperature, + $sup_GridRelayStatus, + $sup_SpotBatteryLoad, + $sup_DeviceStatus); + + my ($inv_TYPE, $inv_CLASS, + $inv_SPOT_ETODAY, $inv_SPOT_ETOTAL, + $inv_susyid, + $inv_serial, + $inv_SPOT_PDC1, $inv_SPOT_PDC2, + $inv_SPOT_PAC1, $inv_SPOT_PAC2, $inv_SPOT_PAC3, $inv_SPOT_PACTOT, + $inv_PACMAX1, $inv_PACMAX2, $inv_PACMAX3, $inv_PACMAX1_2, + $inv_ChargeStatus, + $inv_SPOT_UDC1, $inv_SPOT_UDC2, + $inv_SPOT_IDC1, $inv_SPOT_IDC2, + $inv_SPOT_UAC1, $inv_SPOT_UAC2, $inv_SPOT_UAC3, + $inv_SPOT_UAC1_2, $inv_SPOT_UAC2_3, $inv_SPOT_UAC3_1, + $inv_SPOT_IAC1, $inv_SPOT_IAC2, $inv_SPOT_IAC3, + $inv_SPOT_CosPhi, + $inv_BAT_UDC, $inv_BAT_UDC_A, $inv_BAT_UDC_B, $inv_BAT_UDC_C, + $inv_BAT_IDC, $inv_BAT_IDC_A, $inv_BAT_IDC_B, $inv_BAT_IDC_C, + $inv_BAT_CYCLES, $inv_BAT_CYCLES_A, $inv_BAT_CYCLES_B, $inv_BAT_CYCLES_C, + $inv_BAT_TEMP, $inv_BAT_TEMP_A, $inv_BAT_TEMP_B, $inv_BAT_TEMP_C, + $inv_BAT_LOADTODAY, $inv_BAT_LOADTOTAL, + $inv_SPOT_FREQ, $inv_SPOT_OPERTM, $inv_SPOT_FEEDTM, $inv_TEMP, $inv_GRIDRELAY, $inv_STATUS,); + + my @row_array; + my @array; + my $avg = 0; + my ($ist,$bst,$irt,$brt,$rt); + + # Background-Startzeit + $bst = [gettimeofday]; + + Log3 ($name, 4, "$name -> Start BlockingCall SMAInverter_getstatusDoParse"); + + # set dependency from surise/sunset used for inverter operation time + my $offset = AttrVal($name,"offset",0); + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); + + my ($sunrise_h,$sunrise_m,$sunrise_s) = split(":",sunrise_abs('-'.$offset)); + my ($sunset_h,$sunset_m,$sunset_s) = split(":",sunset_abs('+'.$offset)); + + my $oper_start = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>$sunrise_h,minute=>$sunrise_m,second=>$sunrise_s,time_zone=>'local'); + my $oper_stop = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>$sunset_h,minute=>$sunset_m,second=>$sunset_s,time_zone=>'local'); + my $oper_start_d = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>00,minute=>10,second=>00,time_zone=>'local'); + my $dt_now = DateTime->now(time_zone=>'local'); + + Log3 $name, 4, "$name - current time: ".$dt_now->dmy('.')." ".$dt_now->hms; + Log3 $name, 4, "$name - operation time begin: ".$oper_start->dmy('.')." ".$oper_start->hms; + Log3 $name, 4, "$name - operation time end: ".$oper_stop->dmy('.')." ".$oper_stop->hms; + + my $opertime_start = $oper_start->dmy('.')." ".$oper_start->hms; + my $opertime_stop = $oper_stop->dmy('.')." ".$oper_stop->hms; + + # ETOTAL speichern für ETODAY-Berechnung wenn WR ETODAY nicht liefert + if ($dt_now <= $oper_start_d) { # V2.14.1, Forum: https://forum.fhem.de/index.php/topic,56080.msg1134664.html#msg1134664 + my $val = 0; + $val = ReadingsNum($name, "etotal", 0)*1000 if (exists $defs{$name}{READINGS}{etotal}); + $val = ReadingsNum($name, "SPOT_ETOTAL", 0) if (exists $defs{$name}{READINGS}{SPOT_ETOTAL}); + BlockingInformParent("SMAInverter_setReadingFromBlocking", [$name, ".etotal_yesterday", $val], 0); + } + + # BATTERYLOAD_TOTAL speichern für BAT_LOADTODAY-Berechnung wenn WR BAT_LOADTODAY nicht liefert + if ($dt_now <= $oper_start_d) { # V2.14.1, Forum: https://forum.fhem.de/index.php/topic,56080.msg1134664.html#msg1134664 + my $val = 0; + $val = ReadingsNum($name, "bat_loadtotal", 0)*1000 if (exists $defs{$name}{READINGS}{bat_loadtotal}); + $val = ReadingsNum($name, "BAT_LOADTOTAL", 0) if (exists $defs{$name}{READINGS}{BAT_LOADTOTAL}); + BlockingInformParent("SMAInverter_setReadingFromBlocking", [$name, ".bat_loadtotal_yesterday", $val], 0); + } + + if (($oper_start <= $dt_now && $dt_now <= $oper_stop) || AttrVal($name,"suppressSleep",0)) { + # normal operation or suppressed sleepmode + + # Abfrage Inverter Startzeit + $ist = [gettimeofday]; + + # Get the current attributes + my $detail_level = AttrVal($name, "detail-level", 0); + + # Aufbau Command-Array + my @commands = ("sup_TypeLabel", # Check TypeLabel + "sup_EnergyProduction", # Check EnergyProduction + "sup_SpotDCPower", # Check SpotDCPower + "sup_SpotACPower", # Check SpotACPower + "sup_SpotACTotalPower", # Check SpotACTotalPower + "sup_ChargeStatus" # Check BatteryChargeStatus + ); + + if($detail_level > 0) { + # Detail Level 1 or 2 >> get voltage and current levels + push(@commands, "sup_SpotDCVoltage"); # Check SpotDCVoltage + push(@commands, "sup_SpotACVoltage"); # Check SpotACVoltage + push(@commands, "sup_SpotACCurrent"); # Check SpotACCurrent + + if (ReadingsVal($name,"INV_TYPE","") =~ /SBS(6\.0|5\.0|3\.7)/xs || ReadingsVal($name,"device_type","") =~ /SBS(6\.0|5\.0|3\.7)/xs) + { + push(@commands, "sup_BatteryInfo_UDC"); # Check BatteryInfo Voltage + push(@commands, "sup_BatteryInfo_IDC"); # Check BatteryInfo current + } + elsif (ReadingsVal($name,"INV_TYPE","") =~ /SBS(1\.5|2\.0|2\.5)/xs || ReadingsVal($name,"device_type","") =~ /SBS(1\.5|2\.0|2\.5)/xs) + { + push(@commands, "sup_BatteryInfo_2"); # Check BatteryInfo Voltage + } + else{ + push(@commands, "sup_BatteryInfo"); # Check BatteryInfo + } + + push(@commands, "sup_SpotBatteryLoad"); # Check Batteryload + } + + if($detail_level > 1) { + # Detail Level 2 >> get all data + push(@commands, "sup_SpotGridFrequency"); # Check SpotGridFrequency + push(@commands, "sup_OperationTime"); # Check OperationTime + push(@commands, "sup_InverterTemperature"); # Check InverterTemperature + push(@commands, "sup_MaxACPower"); # Check MaxACPower + push(@commands, "sup_MaxACPower2"); # Check MaxACPower2 + push(@commands, "sup_GridRelayStatus"); # Check GridRelayStatus + push(@commands, "sup_DeviceStatus"); # Check DeviceStatus + + if (ReadingsVal($name,"INV_TYPE","") =~ /SBS(6\.0|5\.0|3\.7)/xs || ReadingsVal($name,"device_type","") =~ /SBS(6\.0|5\.0|3\.7)/xs) + { + push(@commands, "sup_BatteryInfo_TEMP"); # Check BatteryInfo Temperatur + push(@commands, "sup_BatteryInfo_Capac"); # Check BatteryInfo + } + } + + Log3 $name, 5, "$name - ".ReadingsVal($name,"INV_TYPE","")."".ReadingsVal($name,"device_type",""); + + + if(SMAInverter_SMAlogon($hash->{HOST}, $hash->{PASS}, $hash)) { + Log3 $name, 5, "$name - Logged in now"; + + for my $i(@commands) { + if ($i eq "sup_TypeLabel") { + ($sup_TypeLabel,$inv_TYPE,$inv_CLASS,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x58000200, 0x00821E00, 0x008220FF); + } + elsif ($i eq "sup_EnergyProduction") { + ($sup_EnergyProduction,$inv_SPOT_ETODAY,$inv_SPOT_ETOTAL,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x54000200, 0x00260100, 0x002622FF); + } + elsif ($i eq "sup_SpotDCPower") { + ($sup_SpotDCPower,$inv_SPOT_PDC1,$inv_SPOT_PDC2,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x53800200, 0x00251E00, 0x00251EFF); + } + elsif ($i eq "sup_SpotACPower") { + ($sup_SpotACPower,$inv_SPOT_PAC1,$inv_SPOT_PAC2,$inv_SPOT_PAC3,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00464000, 0x004642FF); + } + elsif ($i eq "sup_SpotACTotalPower") { + ($sup_SpotACTotalPower,$inv_SPOT_PACTOT,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00263F00, 0x00263FFF); + } + elsif ($i eq "sup_ChargeStatus") { + ($sup_ChargeStatus,$inv_ChargeStatus,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00295A00, 0x00295AFF); + } + elsif ($i eq "sup_SpotDCVoltage") { + ($sup_SpotDCVoltage,$inv_SPOT_UDC1,$inv_SPOT_UDC2,$inv_SPOT_IDC1,$inv_SPOT_IDC2,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x53800200, 0x00451F00, 0x004521FF); + } + elsif ($i eq "sup_SpotACVoltage") { + ($sup_SpotACVoltage,$inv_SPOT_UAC1,$inv_SPOT_UAC2,$inv_SPOT_UAC3,$inv_SPOT_UAC1_2,$inv_SPOT_UAC2_3,$inv_SPOT_UAC3_1,$inv_SPOT_CosPhi,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00464800, 0x004656FF); + } + elsif ($i eq "sup_SpotACCurrent") { + Log3 $name, 5, "$name -> sup_SpotACCurrent"; + ($sup_SpotACCurrent,$inv_SPOT_IAC1,$inv_SPOT_IAC2,$inv_SPOT_IAC3,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00465300, 0x004655FF); + } + elsif ($i eq "sup_BatteryInfo_TEMP") { + Log3 $name, 5, "$name -> sup_BatteryInfo_TEMP"; + ($sup_BatteryInfo_TEMP,$inv_BAT_TEMP,$inv_BAT_TEMP_A,$inv_BAT_TEMP_B,$inv_BAT_TEMP_C,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00495B00, 0x00495B10); + } + elsif ($i eq "sup_BatteryInfo_UDC") { + Log3 $name, 5, "$name -> sup_BatteryInfo_UDC"; + ($sup_BatteryInfo_UDC,$inv_BAT_UDC,$inv_BAT_UDC_A,$inv_BAT_UDC_B,$inv_BAT_UDC_C,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00495C00, 0x00495C10); + } + elsif ($i eq "sup_BatteryInfo_IDC") { + Log3 $name, 5, "$name -> sup_BatteryInfo_IDC"; + ($sup_BatteryInfo_IDC,$inv_BAT_IDC,$inv_BAT_IDC_A,$inv_BAT_IDC_B,$inv_BAT_IDC_C,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00495D00, 0x00495D10); + } + elsif ($i eq "sup_BatteryInfo_Capac") { + Log3 $name, 5, "$name -> sup_BatteryInfo_Capac"; + #($sup_BatteryInfo_IDC,$inv_BAT_IDC,$inv_BAT_IDC_A,$inv_BAT_IDC_B,$inv_BAT_IDC_C,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00496800, 0x004968FF); + } + elsif ($i eq "sup_BatteryInfo") { + Log3 $name, 5, "$name -> sup_BatteryInfo"; + ($sup_BatteryInfo,$inv_BAT_CYCLES,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00491E00, 0x00495DFF); + } + elsif ($i eq "sup_BatteryInfo_2") { + Log3 $name, 5, "$name -> sup_BatteryInfo_2"; + ($sup_BatteryInfo_2,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00491E00, 0x00495DFF); + } + elsif ($i eq "sup_SpotGridFrequency") { + ($sup_SpotGridFrequency,$inv_SPOT_FREQ,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00465700, 0x004657FF); + } + elsif ($i eq "sup_OperationTime") { + ($sup_OperationTime,$inv_SPOT_OPERTM,$inv_SPOT_FEEDTM,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x54000200, 0x00462E00, 0x00462FFF); + } + elsif ($i eq "sup_InverterTemperature") { + ($sup_InverterTemperature,$inv_TEMP,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x52000200, 0x00237700, 0x002377FF); + } + elsif ($i eq "sup_MaxACPower") { + ($sup_MaxACPower,$inv_PACMAX1,$inv_PACMAX2,$inv_PACMAX3,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00411E00, 0x004120FF); + } + elsif ($i eq "sup_MaxACPower2") { + ($sup_MaxACPower2,$inv_PACMAX1_2,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51000200, 0x00832A00, 0x00832AFF); + } + elsif ($i eq "sup_GridRelayStatus") { + ($sup_GridRelayStatus,$inv_GRIDRELAY,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51800200, 0x00416400, 0x004164FF); + } + elsif ($i eq "sup_DeviceStatus") { + ($sup_DeviceStatus,$inv_STATUS,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x51800200, 0x00214800, 0x002148FF); + } + elsif ($i eq "sup_SpotBatteryLoad") { + ($sup_SpotBatteryLoad,$inv_BAT_LOADTODAY,$inv_BAT_LOADTOTAL,$inv_susyid,$inv_serial) = SMAInverter_SMAcommand($hash, $hash->{HOST}, 0x54000200, 0x00496700, 0x004967FF); + } + } + + # nothing more to do, just log out + SMAInverter_SMAlogout($hash,$hash->{HOST}); + + # Inverter Laufzeit ermitteln + $irt = tv_interval($ist); + + # Aufbau Ergebnis-Array + push(@row_array, "modulstate normal"."\n"); + push(@row_array, "opertime_start ".$opertime_start."\n"); + push(@row_array, "opertime_stop ".$opertime_stop."\n"); + + # Durchschnittswerteberechnung Energieerzeugung der letzten 5, 10, 15 Messungen + + my ($sum05, $sum10, $sum15); + my $cnt05 = int(300/$interval); # Anzahl der Zyklen innerhalb 5 Minuten + my $cnt10 = int(600/$interval); # Anzahl der Zyklen innerhalb 10 Minuten + my $cnt15 = int(900/$interval); # Anzahl der Zyklen innerhalb 15 Minuten = Summe aller Messzyklen + my $cntsum = $cnt15+1; # Sicherheitszuschlag Summe Anzahl aller Zyklen + my @averagebuf; + if ($sup_TypeLabel && $sup_EnergyProduction && $inv_CLASS =~ /8001|8002|8007/xs) { + # only for this block because of warnings if values not set at restart + no warnings 'uninitialized'; + if (!$hash->{HELPER}{AVERAGEBUF}) { + for my $count (0..$cntsum) { + # fill with new values + $inv_SPOT_PACTOT = $inv_SPOT_PACTOT // 0; + push(@averagebuf, $inv_SPOT_PACTOT); + } + } else { + @averagebuf = split(/,/, $hash->{HELPER}{AVERAGEBUF}) + } + + pop(@averagebuf); # rechtes Element aus average buffer löschen + unshift(@averagebuf, $inv_SPOT_PACTOT); # und links mit neuem Wert füllen + $avg = join(',', @averagebuf); + + # calculate average energy and write to array for generate readings + my $k = 1; + my $avgsum = $averagebuf[0]; + while ($k < $cntsum) { + $avgsum = $avgsum + $averagebuf[$k] if($averagebuf[$k]); + if ($k == $cnt05) { + $sum05 = $avgsum; + Log3 $name, 5, "$name - CNT05: $cnt05 SUM05: $sum05"; + } + if ($k == $cnt10) { + $sum10 = $avgsum; + Log3 $name, 5, "$name - CNT10: $cnt10 SUM10: $sum10"; + } + if ($k == $cnt15) { + $sum15 = $avgsum; + Log3 $name, 5, "$name - CNT15: $cnt15 SUM15: $sum15"; + } + $k++; + } + + my $AvP05 = int( $sum05 / ($cnt05+1) ); + my $AvP10 = int( $sum10 / ($cnt10+1) ); + my $AvP15 = int( $sum15 / ($cnt15+1) ); + Log3 $name, 5, "$name - Content of Averagebuffer:"; + Log3 $name, 5, "$name - $avg"; + Log3 $name, 5, "$name - avg_power_lastminutes_05 = $AvP05, avg_power_lastminutes_10 = $AvP10, avg_power_lastminutes_15 = $AvP15"; + + push(@row_array, "avg_power_lastminutes_05 ".$AvP05."\n"); # Average Energy (last) 5 minutes + push(@row_array, "avg_power_lastminutes_10 ".$AvP10."\n"); # Average Energy (last) 10 minutes + push(@row_array, "avg_power_lastminutes_15 ".$AvP15."\n"); # Average Energy (last) 15 minutes + + use warnings; + } + + if ($sc) { # SBFSpot Kompatibilitätsmodus + if($sup_EnergyProduction) { + push(@row_array, "etotal ".($inv_SPOT_ETOTAL/1000)."\n"); + push(@row_array, "etoday ".($inv_SPOT_ETODAY/1000)."\n"); + } + if($sup_SpotDCPower) { + push(@row_array, "string_1_pdc ".sprintf("%.3f",$inv_SPOT_PDC1/1000)."\n"); + push(@row_array, "string_2_pdc ".sprintf("%.3f",$inv_SPOT_PDC2/1000)."\n"); + } + if($sup_SpotACPower) { + push(@row_array, "phase_1_pac ".sprintf("%.3f",$inv_SPOT_PAC1/1000)."\n") if ($inv_SPOT_PAC1 ne "-"); + push(@row_array, "phase_2_pac ".sprintf("%.3f",$inv_SPOT_PAC2/1000)."\n") if ($inv_SPOT_PAC2 ne "-"); + push(@row_array, "phase_3_pac ".sprintf("%.3f",$inv_SPOT_PAC3/1000)."\n") if ($inv_SPOT_PAC3 ne "-"); + } + if($sup_SpotACTotalPower) { + push(@row_array, "total_pac ".sprintf("%.3f",$inv_SPOT_PACTOT/1000)."\n"); + push(@row_array, "state ".sprintf("%.3f",$inv_SPOT_PACTOT/1000)."\n"); + } + if($sup_ChargeStatus) { + push(@row_array, "chargestatus ".$inv_ChargeStatus."\n"); + } + + if($inv_CLASS && $inv_CLASS eq 8007 && defined($inv_SPOT_PACTOT)) { # V2.10.1 28.04.2019 + if($inv_SPOT_PACTOT < 0) { + push(@row_array, "power_out "."0"."\n"); + push(@row_array, "power_in ".(-1 * $inv_SPOT_PACTOT)."\n"); + } + else { + push(@row_array, "power_out ".$inv_SPOT_PACTOT."\n"); + push(@row_array, "power_in "."0"."\n"); + } + } + + if($detail_level > 0) { + # For Detail Level 1 + if($sup_SpotDCVoltage) { + push(@row_array, "string_1_udc ".sprintf("%.2f",$inv_SPOT_UDC1)."\n"); + push(@row_array, "string_2_udc ".sprintf("%.2f",$inv_SPOT_UDC2)."\n"); + push(@row_array, "string_1_idc ".sprintf("%.3f",$inv_SPOT_IDC1)."\n"); + push(@row_array, "string_2_idc ".sprintf("%.3f",$inv_SPOT_IDC2)."\n"); + } + if($sup_SpotACVoltage) { + push(@row_array, "phase_1_uac ".sprintf("%.2f",$inv_SPOT_UAC1)."\n") if ($inv_SPOT_UAC1 ne "-"); + push(@row_array, "phase_2_uac ".sprintf("%.2f",$inv_SPOT_UAC2)."\n") if ($inv_SPOT_UAC2 ne "-"); + push(@row_array, "phase_3_uac ".sprintf("%.2f",$inv_SPOT_UAC3)."\n") if ($inv_SPOT_UAC3 ne "-"); + push(@row_array, "phase_1_2_uac ".sprintf("%.3f",$inv_SPOT_UAC1_2)."\n") if ($inv_SPOT_UAC1_2 ne "-"); + push(@row_array, "phase_2_3_uac ".sprintf("%.3f",$inv_SPOT_UAC2_3)."\n") if ($inv_SPOT_UAC2_3 ne "-"); + push(@row_array, "phase_3_1_uac ".sprintf("%.3f",$inv_SPOT_UAC3_1)."\n") if ($inv_SPOT_UAC3_1 ne "-"); + push(@row_array, "cosphi ".sprintf("%.3f",$inv_SPOT_CosPhi)."\n") if ($inv_SPOT_CosPhi ne "-"); + } + if($sup_SpotACCurrent) { + push(@row_array, "phase_1_iac ".sprintf("%.2f",$inv_SPOT_IAC1)."\n") if ($inv_SPOT_IAC1 ne "-"); + push(@row_array, "phase_2_iac ".sprintf("%.2f",$inv_SPOT_IAC2)."\n") if ($inv_SPOT_IAC2 ne "-"); + push(@row_array, "phase_3_iac ".sprintf("%.2f",$inv_SPOT_IAC3)."\n") if ($inv_SPOT_IAC3 ne "-"); + } + if($sup_BatteryInfo || $sup_BatteryInfo_2) { + push(@row_array, "bat_udc ".$inv_BAT_UDC."\n"); + push(@row_array, "bat_idc ".$inv_BAT_IDC."\n"); + } + if($sup_BatteryInfo_UDC) { + push(@row_array, "bat_udc ".$inv_BAT_UDC."\n"); + push(@row_array, "bat_udc_a ".$inv_BAT_UDC_A."\n") if ($inv_BAT_UDC_A ne "-"); + push(@row_array, "bat_udc_b ".$inv_BAT_UDC_B."\n") if ($inv_BAT_UDC_B ne "-"); + push(@row_array, "bat_udc_c ".$inv_BAT_UDC_C."\n") if ($inv_BAT_UDC_C ne "-"); + } + if($sup_BatteryInfo_IDC) { + push(@row_array, "bat_udc ".$inv_BAT_UDC."\n"); + push(@row_array, "bat_idc_a ".$inv_BAT_IDC_A."\n") if ($inv_BAT_IDC_A ne "-"); + push(@row_array, "bat_idc_b ".$inv_BAT_IDC_B."\n") if ($inv_BAT_IDC_B ne "-"); + push(@row_array, "bat_idc_c ".$inv_BAT_IDC_C."\n") if ($inv_BAT_IDC_C ne "-"); + } + if($sup_SpotBatteryLoad) { + push(@row_array, "bat_loadtotal ".($inv_BAT_LOADTOTAL/1000)."\n"); + push(@row_array, "bat_loadtoday ".($inv_BAT_LOADTODAY/1000)."\n"); + } + } + + if($detail_level > 1) { + # For Detail Level 2 + if($sup_BatteryInfo || $sup_BatteryInfo_2) { + push(@row_array, "bat_temp ".$inv_BAT_TEMP."\n"); + } + if($sup_BatteryInfo) { + push(@row_array, "bat_cycles ".$inv_BAT_CYCLES."\n"); + } + if($sup_BatteryInfo_TEMP) { + push(@row_array, "bat_temp ".$inv_BAT_TEMP."\n"); + push(@row_array, "bat_temp_a ".$inv_BAT_TEMP_A."\n") if ($inv_BAT_TEMP_A ne "-"); + push(@row_array, "bat_temp_b ".$inv_BAT_TEMP_B."\n") if ($inv_BAT_TEMP_B ne "-"); + push(@row_array, "bat_temp_c ".$inv_BAT_TEMP_C."\n") if ($inv_BAT_TEMP_C ne "-"); + } + if($sup_SpotGridFrequency) { + push(@row_array, "grid_freq ".sprintf("%.2f",$inv_SPOT_FREQ)."\n"); + } + if($sup_TypeLabel) { + push(@row_array, "device_type ".SMAInverter_devtype($inv_TYPE)."\n"); + push(@row_array, "device_class ".SMAInverter_classtype($inv_CLASS)."\n"); + push(@row_array, "susyid ".$inv_susyid." - SN: ".$inv_serial."\n") if($inv_susyid && $inv_serial); + push(@row_array, "device_name "."SN: ".$inv_serial."\n") if($inv_serial); + push(@row_array, "serial_number ".$inv_serial."\n") if($inv_serial); + } + if($sup_MaxACPower) { + push(@row_array, "pac_max_phase_1 ".$inv_PACMAX1."\n"); + push(@row_array, "pac_max_phase_2 ".$inv_PACMAX2."\n"); + push(@row_array, "pac_max_phase_3 ".$inv_PACMAX3."\n"); + } + if($sup_MaxACPower2) { + push(@row_array, "pac_max_phase_1_2 ".$inv_PACMAX1_2."\n"); + } + if($sup_InverterTemperature) { + push(@row_array, "device_temperature ".sprintf("%.1f",$inv_TEMP)."\n"); + } + if($sup_OperationTime) { + push(@row_array, "feed-in_time ".$inv_SPOT_FEEDTM."\n"); + push(@row_array, "operation_time ".$inv_SPOT_OPERTM."\n"); + } + if($sup_GridRelayStatus) { + push(@row_array, "gridrelay_status ".SMAInverter_StatusText($inv_GRIDRELAY)."\n"); + } + if($sup_DeviceStatus) { + push(@row_array, "device_status ".SMAInverter_StatusText($inv_STATUS)."\n"); + } + } + + } + else { # kein SBFSpot Compatibility Mode + if($sup_EnergyProduction) { + push(@row_array, "SPOT_ETOTAL ".$inv_SPOT_ETOTAL."\n"); + push(@row_array, "SPOT_ETODAY ".$inv_SPOT_ETODAY."\n"); + } + if($sup_SpotDCPower) { + push(@row_array, "SPOT_PDC1 ".$inv_SPOT_PDC1."\n"); + push(@row_array, "SPOT_PDC2 ".$inv_SPOT_PDC2."\n"); + } + if($sup_SpotACPower) { + push(@row_array, "SPOT_PAC1 ".$inv_SPOT_PAC1."\n") if ($inv_SPOT_PAC1 ne "-"); + push(@row_array, "SPOT_PAC2 ".$inv_SPOT_PAC2."\n") if ($inv_SPOT_PAC2 ne "-"); + push(@row_array, "SPOT_PAC3 ".$inv_SPOT_PAC3."\n") if ($inv_SPOT_PAC3 ne "-"); + } + if($sup_SpotACTotalPower) { + push(@row_array, "SPOT_PACTOT ".$inv_SPOT_PACTOT."\n"); + push(@row_array, "state ".$inv_SPOT_PACTOT."\n"); + } + if($sup_ChargeStatus) { + push(@row_array, "ChargeStatus ".$inv_ChargeStatus."\n"); + } + if($inv_CLASS && $inv_CLASS eq 8007 && defined($inv_SPOT_PACTOT)) { # V2.10.1 28.04.2019 + if($inv_SPOT_PACTOT < 0) { + push(@row_array, "POWER_OUT "."0"."\n"); + push(@row_array, "POWER_IN ".(-1 * $inv_SPOT_PACTOT)."\n"); + } + else { + push(@row_array, "POWER_OUT ".$inv_SPOT_PACTOT."\n"); + push(@row_array, "POWER_IN "."0"."\n"); + } + } + if($detail_level > 0) { + # For Detail Level 1 + if($sup_SpotDCVoltage) { + push(@row_array, "SPOT_UDC1 ".$inv_SPOT_UDC1."\n"); + push(@row_array, "SPOT_UDC2 ".$inv_SPOT_UDC2."\n"); + push(@row_array, "SPOT_IDC1 ".$inv_SPOT_IDC1."\n"); + push(@row_array, "SPOT_IDC2 ".$inv_SPOT_IDC2."\n"); + } + if($sup_SpotACVoltage) { + push(@row_array, "SPOT_UAC1 ".$inv_SPOT_UAC1."\n") if ($inv_SPOT_UAC1 ne "-"); + push(@row_array, "SPOT_UAC2 ".$inv_SPOT_UAC2."\n") if ($inv_SPOT_UAC2 ne "-"); + push(@row_array, "SPOT_UAC3 ".$inv_SPOT_UAC3."\n") if ($inv_SPOT_UAC3 ne "-"); + push(@row_array, "SPOT_UAC1_2 ".sprintf("%.3f",$inv_SPOT_UAC1_2)."\n") if ($inv_SPOT_UAC1_2 ne "-"); + push(@row_array, "SPOT_UAC2_3 ".sprintf("%.3f",$inv_SPOT_UAC2_3)."\n") if ($inv_SPOT_UAC2_3 ne "-"); + push(@row_array, "SPOT_UAC3_1 ".sprintf("%.3f",$inv_SPOT_UAC3_1)."\n") if ($inv_SPOT_UAC3_1 ne "-"); + push(@row_array, "SPOT_CosPhi ".sprintf("%.3f",$inv_SPOT_CosPhi)."\n") if ($inv_SPOT_CosPhi ne "-"); + } + if($sup_SpotACCurrent) { + push(@row_array, "SPOT_IAC1 ".sprintf("%.2f",$inv_SPOT_IAC1)."\n") if ($inv_SPOT_IAC1 ne "-"); + push(@row_array, "SPOT_IAC2 ".sprintf("%.2f",$inv_SPOT_IAC2)."\n") if ($inv_SPOT_IAC2 ne "-"); + push(@row_array, "SPOT_IAC3 ".sprintf("%.2f",$inv_SPOT_IAC3)."\n") if ($inv_SPOT_IAC3 ne "-"); + } + if($sup_BatteryInfo || $sup_BatteryInfo_2) { + push(@row_array, "BAT_UDC ". $inv_BAT_UDC."\n"); + push(@row_array, "BAT_IDC ". $inv_BAT_IDC."\n"); + } + if($sup_BatteryInfo_UDC) { + push(@row_array, "BAT_UDC ". $inv_BAT_UDC."\n"); + push(@row_array, "BAT_UDC_A ".$inv_BAT_UDC_A."\n") if ($inv_BAT_UDC_A ne "-"); + push(@row_array, "BAT_UDC_B ".$inv_BAT_UDC_B."\n") if ($inv_BAT_UDC_B ne "-"); + push(@row_array, "BAT_UDC_C ".$inv_BAT_UDC_C."\n") if ($inv_BAT_UDC_C ne "-"); + } + if($sup_BatteryInfo_IDC) { + push(@row_array, "BAT_IDC ". $inv_BAT_IDC."\n"); + push(@row_array, "BAT_IDC_A ".$inv_BAT_IDC_A."\n") if ($inv_BAT_IDC_A ne "-"); + push(@row_array, "BAT_IDC_B ".$inv_BAT_IDC_B."\n") if ($inv_BAT_IDC_B ne "-"); + push(@row_array, "BAT_IDC_C ".$inv_BAT_IDC_C."\n") if ($inv_BAT_IDC_C ne "-"); + } + if($sup_SpotBatteryLoad) { + push(@row_array, "BAT_LOADTOTAL ".$inv_BAT_LOADTOTAL."\n"); + push(@row_array, "BAT_LOADTODAY ".$inv_BAT_LOADTODAY."\n"); + } + } + + if($detail_level > 1) { + # For Detail Level 2 + if($sup_BatteryInfo || $sup_BatteryInfo_2) { + push(@row_array, "BAT_TEMP ". $inv_BAT_TEMP."\n"); + } + if($sup_BatteryInfo) { + push(@row_array, "BAT_CYCLES ".$inv_BAT_CYCLES."\n"); + } + if($sup_BatteryInfo_TEMP) { + push(@row_array, "BAT_TEMP ". $inv_BAT_TEMP."\n"); + push(@row_array, "BAT_TEMP_A ".$inv_BAT_TEMP_A."\n") if ($inv_BAT_TEMP_A ne "-"); + push(@row_array, "BAT_TEMP_B ".$inv_BAT_TEMP_B."\n") if ($inv_BAT_TEMP_B ne "-"); + push(@row_array, "BAT_TEMP_C ".$inv_BAT_TEMP_C."\n") if ($inv_BAT_TEMP_C ne "-"); + } + if($sup_SpotGridFrequency) { + push(@row_array, "SPOT_FREQ ".$inv_SPOT_FREQ."\n"); + } + if($sup_TypeLabel) { + push(@row_array, "INV_TYPE ". SMAInverter_devtype($inv_TYPE)."\n"); + push(@row_array, "INV_CLASS ". SMAInverter_classtype($inv_CLASS)."\n"); + push(@row_array, "SUSyID ". $inv_susyid."\n") if($inv_susyid); + push(@row_array, "Serialnumber ".$inv_serial."\n") if($inv_serial); + } + if($sup_MaxACPower) { + push(@row_array, "INV_PACMAX1 ".$inv_PACMAX1."\n"); + push(@row_array, "INV_PACMAX2 ".$inv_PACMAX2."\n"); + push(@row_array, "INV_PACMAX3 ".$inv_PACMAX3."\n"); + } + if($sup_MaxACPower2) { + push(@row_array, "INV_PACMAX1_2 ".$inv_PACMAX1_2."\n"); + } + if($sup_InverterTemperature) { + push(@row_array, "INV_TEMP ".$inv_TEMP."\n"); + } + if($sup_OperationTime) { + push(@row_array, "SPOT_FEEDTM ".$inv_SPOT_FEEDTM."\n"); + push(@row_array, "SPOT_OPERTM ".$inv_SPOT_OPERTM."\n"); + } + if($sup_GridRelayStatus) { + push(@row_array, "INV_GRIDRELAY ".SMAInverter_StatusText($inv_GRIDRELAY)."\n"); + } + if($sup_DeviceStatus) { + push(@row_array, "INV_STATUS ".SMAInverter_StatusText($inv_STATUS)."\n"); + } + } + } + } + else { + # Login failed/not possible + push(@row_array, "state Login failed"."\n"); + push(@row_array, "modulstate login failed"."\n"); + } + } + else { + # sleepmode at current time and not suppressed + push(@row_array, "modulstate sleep"."\n"); + push(@row_array, "opertime_start ".$opertime_start."\n"); + push(@row_array, "opertime_stop ".$opertime_stop."\n"); + push(@row_array, "state done"."\n"); + } + + Log3 ($name, 5, "$name -> row_array before encoding:"); + for my $row (@row_array) { + chomp $row; + Log3 ($name, 5, "$name -> $row"); + } + + # encoding result + my $rowlist = join('|', @row_array); + $rowlist = encode_base64($rowlist,""); + + # Background-Laufzeit ermitteln + $brt = tv_interval($bst); + + $rt = ($irt?$irt:'').",".$brt; + + Log3 ($name, 4, "$name -> BlockingCall SMAInverter_getstatusDoParse finished"); + +return "$name|$rowlist|$avg|$rt"; +} + +############################################################### +# Auswertung non-blocking Inverter Datenabruf +############################################################### +sub SMAInverter_getstatusParseDone ($) { + my ($string) = @_; + my @a = split("\\|",$string); + my $name = $a[0]; + my $hash = $defs{$name}; + my $rowlist = decode_base64($a[1]); + $hash->{HELPER}{AVERAGEBUF} = $a[2] if($a[2]); + my $rt = $a[3]; + my ($irt,$brt) = split(",", $rt); + + Log3 ($name, 4, "$name -> Start BlockingCall SMAInverter_getstatusParseDone"); + + # proctime Readings löschen + if(!AttrVal($name, "showproctime", undef)) { + delete($defs{$name}{READINGS}{inverter_processing_time}); + delete($defs{$name}{READINGS}{background_processing_time}); + } else { + delete($defs{$name}{READINGS}{inverter_processing_time}) if(!$irt); + } + + # Get current time + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); + $hash->{LASTUPDATE} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ; + + my @row_array = split("\\|", $rowlist); + + Log3 ($name, 5, "$name -> row_array after decoding:"); + foreach my $row (@row_array) { + chomp $row; + Log3 ($name, 5, "$name -> $row"); + } + + readingsBeginUpdate($hash); + foreach my $row (@row_array) { + chomp $row; + my @a = split(" ", $row, 2); + $hash->{MODEL} = $a[1] if($a[0] eq "device_type"); + readingsBulkUpdate($hash, $a[0], $a[1]); + } + readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt)) if(AttrVal($name, "showproctime", undef)); + readingsBulkUpdate($hash, "inverter_processing_time", sprintf("%.4f",$irt)) if(AttrVal($name, "showproctime", undef) && $irt); + readingsEndUpdate($hash, 1); + + delete($hash->{HELPER}{RUNNING_PID}); + Log3 ($name, 4, "$name -> BlockingCall SMAInverter_getstatusParseDone finished"); + +return; +} + +############################################################### +# Abbruchroutine Timeout Inverter Abfrage +############################################################### +sub SMAInverter_getstatusParseAborted(@) { + my ($hash,$cause) = @_; + my $name = $hash->{NAME}; + my $discycles = $hash->{HELPER}{FAULTEDCYCLES}; + $cause = $cause?$cause:"Timeout: process terminated"; + + # count of timeouts since module start + $discycles++; + $hash->{HELPER}{FAULTEDCYCLES} = $discycles; + + Log3 ($name, 1, "SMAInverter $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} $cause"); + readingsSingleUpdate($hash,"state",$cause, 1); + + delete($hash->{HELPER}{RUNNING_PID}); + +return; +} + +########################################################################## +# SMA Command Execution +########################################################################## +sub SMAInverter_SMAcommand($$$$$) { + # Parameters: $hash - host - command - first - last + my ($hash,$host,$command,$first,$last) = @_; + my $name = $hash->{NAME}; + my $cmdheader = "534D4100000402A00000000100"; + my $pktlength = "26"; # length = 38 for data commands + my $esignature = "0010606509A0"; + my ($inv_TYPE, $inv_CLASS, + $inv_SPOT_ETODAY, $inv_SPOT_ETOTAL, + $inv_susyid, + $inv_serial, + $inv_SPOT_PDC1, $inv_SPOT_PDC2, + $inv_SPOT_PAC1, $inv_SPOT_PAC2, $inv_SPOT_PAC3, $inv_SPOT_PACTOT, + $inv_PACMAX1, $inv_PACMAX2, $inv_PACMAX3, $inv_PACMAX1_2, + $inv_ChargeStatus, + $inv_SPOT_UDC1, $inv_SPOT_UDC2, + $inv_SPOT_IDC1, $inv_SPOT_IDC2, + $inv_SPOT_UAC1, $inv_SPOT_UAC2, $inv_SPOT_UAC3, + $inv_SPOT_UAC1_2, $inv_SPOT_UAC2_3, $inv_SPOT_UAC3_1, + $inv_SPOT_IAC1, $inv_SPOT_IAC2, $inv_SPOT_IAC3, + $inv_SPOT_CosPhi, + $inv_BAT_UDC, $inv_BAT_UDC_A, $inv_BAT_UDC_B, $inv_BAT_UDC_C, + $inv_BAT_IDC, $inv_BAT_IDC_A, $inv_BAT_IDC_B, $inv_BAT_IDC_C, + $inv_BAT_CYCLES, $inv_BAT_CYCLES_A, $inv_BAT_CYCLES_B, $inv_BAT_CYCLES_C, + $inv_BAT_TEMP, $inv_BAT_TEMP_A, $inv_BAT_TEMP_B, $inv_BAT_TEMP_C, + $inv_BAT_LOADTODAY, $inv_BAT_LOADTOTAL, + $inv_SPOT_FREQ, $inv_SPOT_OPERTM, $inv_SPOT_FEEDTM, $inv_TEMP, $inv_GRIDRELAY, $inv_STATUS); + my $mysusyid = $hash->{HELPER}{MYSUSYID}; + my $myserialnumber = $hash->{HELPER}{MYSERIALNUMBER}; + my ($cmd, $myID, $target_ID, $spkt_ID, $cmd_ID); + my ($socket,$data,$size,$data_ID); + my ($i, $temp, $count); # Variables for loops and calculation + + # Seriennummer und SuSyID des Ziel-WR setzen + my $default_target_susyid = $hash->{HELPER}{DEFAULT_TARGET_SUSYID}; + my $default_target_serial = $hash->{HELPER}{DEFAULT_TARGET_SERIAL}; + my $target_susyid = AttrVal($name, "target-susyid", $default_target_susyid); + my $target_serial = AttrVal($name, "target-serial", $default_target_serial); + + # Define own ID and target ID and packet ID + $myID = SMAInverter_ByteOrderShort(substr(sprintf("%04X",$mysusyid),0,4)) . SMAInverter_ByteOrderLong(sprintf("%08X",$myserialnumber)); + $target_ID = SMAInverter_ByteOrderShort(substr(sprintf("%04X",$target_susyid),0,4)) . SMAInverter_ByteOrderLong(sprintf("%08X",$target_serial)); + + # Increasing Packet ID + $hash->{HELPER}{PKT_ID} = $hash->{HELPER}{PKT_ID} + 1; + $spkt_ID = SMAInverter_ByteOrderShort(sprintf("%04X",$hash->{HELPER}{PKT_ID})); + + $cmd_ID = SMAInverter_ByteOrderLong(sprintf("%08X",$command)) . SMAInverter_ByteOrderLong(sprintf("%08X",$first)) . SMAInverter_ByteOrderLong(sprintf("%08X",$last)); + + #build final command to send + $cmd = $cmdheader . $pktlength . $esignature . $target_ID . "0000" . $myID . "0000" . "00000000" . $spkt_ID . $cmd_ID . "00000000"; + + # flush after every write + $| = 1; + + # Create Socket and check if successful + $socket = new IO::Socket::INET (PeerHost => $host, PeerPort => 9522, Proto => 'udp',); # open Socket + + if (!$socket) { + # in case of error + Log3 $name, 1, "$name - ERROR. Can't open socket to inverter: $!"; + return 0; + }; + + # Send Data + $data = pack("H*",$cmd); + $socket->send($data); + Log3 $name, 3, "$name - Send request $cmd_ID to $host on port 9522"; + Log3 $name, 5, "$name - send: $cmd"; + + # Receive Data and do a first check regarding length + # receive data + $socket->recv($data, $hash->{HELPER}{MAXBYTES}); + $size = length($data); + + # check if something was received + if (defined $size) { + my $received = unpack("H*", $data); + Log3 $name, 5, "$name - Received: $received"; + } + + # Nothing received -> exit + if (not defined $size) { + Log3 $name, 1, "$name - Nothing received..."; + return 0; + } + else { + # We have received something! + if ($size > 58) { + # Check all parameters of answer + my $r_susyid = unpack("v*", substr $data, 20, 2); + my $r_serial = unpack("V*", substr $data, 22, 4); + my $r_pkt_ID = unpack("v*", substr $data, 40, 2); + my $r_error = unpack("V*", substr $data, 36, 4); + + if (($r_susyid ne $mysusyid) || ($r_serial ne $myserialnumber) || ($r_pkt_ID ne $hash->{HELPER}{PKT_ID}) || ($r_error ne 0)) { + # Response does not match the parameters we have sent, maybe different target + Log3 $name, 3, "$name - Inverter answer does not match our parameters."; + Log3 $name, 5, "$name - Request/Response: SusyID $mysusyid/$r_susyid, Serial $myserialnumber/$r_serial, Packet ID $hash->{HELPER}{PKT_ID}/$r_pkt_ID, Error $r_error"; + $socket->close(); + return 0; + } + } + else { + Log3 $name, 3, "$name - Format of inverter response does not fit."; + $socket->close(); + return 0; + } + } + + # All seems ok, data received + $inv_susyid = unpack("v*", substr $data, 28, 2); + $inv_serial = unpack("V*", substr $data, 30, 4); + $socket->close(); + + if (AttrVal($name, "target-serial", undef)) { + return 0 unless($target_serial eq $inv_serial); + } + + if (AttrVal($name, "target-susyid", undef)) { + return 0 unless($target_susyid eq $inv_susyid); + } + + # Check the data identifier + $data_ID = unpack("v*", substr $data, 55, 2); + Log3 ($name, 5, "$name - Data identifier $data_ID"); + + if($data_ID eq 0x2601) { + if (length($data) >= 66) { + $inv_SPOT_ETOTAL = unpack("V*", substr($data, 62, 4)); + } + else { + Log3 ($name, 3, "$name - WARNING - ETOTAL wasn't deliverd ... set it to \"0\" !"); + $inv_SPOT_ETOTAL = 0; + } + + if (length($data) >= 82) { + $inv_SPOT_ETODAY = unpack("V*", substr ($data, 78, 4)); + } + else { + # ETODAY wurde vom WR nicht geliefert, es wird versucht ihn zu berechnen + Log3 ($name, 3, "$name - ETODAY wasn't delivered from inverter, try to calculate it ..."); + my $etotold = ReadingsNum($name, ".etotal_yesterday", 0); + + if($etotold && $inv_SPOT_ETOTAL > $etotold) { + $inv_SPOT_ETODAY = $inv_SPOT_ETOTAL - $etotold; + Log3 ($name, 3, "$name - ETODAY calculated successfully !"); + } + else { + Log3 ($name, 3, "$name - WARNING - unable to calculate ETODAY ... set it to \"0\" !"); + $inv_SPOT_ETODAY = 0; + } + } + + Log3 $name, 5, "$name - Data SPOT_ETOTAL=$inv_SPOT_ETOTAL and SPOT_ETODAY=$inv_SPOT_ETODAY"; + return (1,$inv_SPOT_ETODAY,$inv_SPOT_ETOTAL,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x4967) { + if (length($data) >= 66) { + $inv_BAT_LOADTOTAL = unpack("V*", substr($data, 62, 4)); + } + else { + Log3 $name, 3, "$name - WARNING - BATTERYLOAD_TOTAL wasn't deliverd ... set it to \"0\" !"; + $inv_SPOT_ETOTAL = 0; + } + + if (length($data) >= 82) { + $inv_BAT_LOADTODAY = unpack("V*", substr ($data, 78, 4)); + } + else { + # BATTERYLOAD_TODAY wurde vom WR nicht geliefert, es wird versucht ihn zu berechnen + Log3 $name, 3, "$name - BATTERYLOAD_TODAY wasn't delivered from inverter, try to calculate it ..."; + my $bltotold = ReadingsNum($name, ".bat_loadtotal_yesterday", 0); + + if($bltotold && $inv_BAT_LOADTOTAL > $bltotold) { + $inv_BAT_LOADTODAY = $inv_BAT_LOADTOTAL - $bltotold; + Log3 $name, 3, "$name - BATTERYLOAD_TODAY calculated successfully !"; + } + else { + Log3 $name, 3, "$name - WARNING - unable to calculate BATTERYLOAD_TODAY ... set it to \"0\" !"; + $inv_BAT_LOADTODAY = 0; + } + } + + Log3 $name, 5, "$name - Data BAT_LOADTOTAL=$inv_BAT_LOADTOTAL and BAT_LOADTODAY=$inv_BAT_LOADTODAY"; + return (1,$inv_BAT_LOADTODAY,$inv_BAT_LOADTOTAL,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x251E) { + $inv_SPOT_PDC1 = unpack("V*", substr $data, 62, 4); + if($size < 90) {$inv_SPOT_PDC2 = 0; } else {$inv_SPOT_PDC2 = unpack("V*", substr $data, 90, 4); } # catch short response, in case PDC2 not supported + $inv_SPOT_PDC1 = ($inv_SPOT_PDC1 == 2147483648) ? 0 : $inv_SPOT_PDC1; + $inv_SPOT_PDC2 = ($inv_SPOT_PDC2 == 2147483648) ? 0 : $inv_SPOT_PDC2; + Log3 $name, 5, "$name - Found Data SPOT_PDC1=$inv_SPOT_PDC1 and SPOT_PDC2=$inv_SPOT_PDC2"; + return (1,$inv_SPOT_PDC1,$inv_SPOT_PDC2,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x4640) { + $inv_SPOT_PAC1 = unpack("l*", substr $data, 62, 4); + if($inv_SPOT_PAC1 eq -2147483648) {$inv_SPOT_PAC1 = "-"; } # Catch 0x80000000 as 0 value + $inv_SPOT_PAC2 = unpack("l*", substr $data, 90, 4); + if($inv_SPOT_PAC2 eq -2147483648) {$inv_SPOT_PAC2 = "-"; } # Catch 0x80000000 as 0 value + $inv_SPOT_PAC3 = unpack("l*", substr $data, 118, 4); + if($inv_SPOT_PAC3 eq -2147483648) {$inv_SPOT_PAC3 = "-"; } # Catch 0x80000000 as 0 value + Log3 $name, 5, "$name - Found Data SPOT_PAC1=$inv_SPOT_PAC1 and SPOT_PAC2=$inv_SPOT_PAC2 and SPOT_PAC3=$inv_SPOT_PAC3"; + return (1,$inv_SPOT_PAC1,$inv_SPOT_PAC2,$inv_SPOT_PAC3,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x411E) { + $inv_PACMAX1 = unpack("V*", substr $data, 62, 4); + $inv_PACMAX2 = unpack("V*", substr $data, 90, 4); + $inv_PACMAX3 = unpack("V*", substr $data, 118, 4); + Log3 $name, 5, "$name - Found Data INV_PACMAX1=$inv_PACMAX1 and INV_PACMAX2=$inv_PACMAX2 and INV_PACMAX3=$inv_PACMAX3"; + return (1,$inv_PACMAX1,$inv_PACMAX2,$inv_PACMAX3,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x832A) { + $inv_PACMAX1_2 = unpack("V*", substr $data, 62, 4); + Log3 $name, 5, "$name - Found Data INV_PACMAX1_2=$inv_PACMAX1_2"; + return (1,$inv_PACMAX1_2,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x263F) { + $inv_SPOT_PACTOT = unpack("l*", substr $data, 62, 4); + if($inv_SPOT_PACTOT eq -2147483648) {$inv_SPOT_PACTOT = 0; } # Catch 0x80000000 as 0 value + + Log3 $name, 5, "$name - Found Data SPOT_PACTOT=$inv_SPOT_PACTOT"; + return (1,$inv_SPOT_PACTOT,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x295A) { + $inv_ChargeStatus = unpack("V*", substr $data, 62, 4); + Log3 $name, 5, "$name - Found Data Battery Charge Status=$inv_ChargeStatus"; + return (1,$inv_ChargeStatus,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x451F) { + $inv_SPOT_UDC1 = unpack("l*", substr $data, 62, 4); + # catch shorter responses in case not second string supported + if($size < 146) { + $inv_SPOT_UDC2 = 0; + $inv_SPOT_IDC1 = unpack("l*", substr $data, 90, 4); + $inv_SPOT_IDC2 = 0; + } else { + $inv_SPOT_UDC2 = unpack("l*", substr $data, 90, 4); + $inv_SPOT_IDC1 = unpack("l*", substr $data, 118, 4); + $inv_SPOT_IDC2 = unpack("l*", substr $data, 146, 4); + } + if(($inv_SPOT_UDC1 eq -2147483648) || ($inv_SPOT_UDC1 eq 0xFFFFFFFF)) {$inv_SPOT_UDC1 = 0; } else {$inv_SPOT_UDC1 = $inv_SPOT_UDC1 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_UDC2 eq -2147483648) || ($inv_SPOT_UDC2 eq 0xFFFFFFFF)) {$inv_SPOT_UDC2 = 0; } else {$inv_SPOT_UDC2 = $inv_SPOT_UDC2 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_IDC1 eq -2147483648) || ($inv_SPOT_IDC1 eq 0xFFFFFFFF)) {$inv_SPOT_IDC1 = 0; } else {$inv_SPOT_IDC1 = $inv_SPOT_IDC1 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_IDC2 eq -2147483648) || ($inv_SPOT_IDC2 eq 0xFFFFFFFF)) {$inv_SPOT_IDC2 = 0; } else {$inv_SPOT_IDC2 = $inv_SPOT_IDC2 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + + Log3 $name, 5, "$name - Found Data SPOT_UDC1=$inv_SPOT_UDC1 and SPOT_UDC2=$inv_SPOT_UDC2 and SPOT_IDC1=$inv_SPOT_IDC1 and SPOT_IDC2=$inv_SPOT_IDC2"; + return (1,$inv_SPOT_UDC1,$inv_SPOT_UDC2,$inv_SPOT_IDC1,$inv_SPOT_IDC2,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x4648) { + $inv_SPOT_UAC1 = unpack("l*", substr $data, 62, 4); + $inv_SPOT_UAC2 = unpack("l*", substr $data, 90, 4); + $inv_SPOT_UAC3 = unpack("l*", substr $data, 118, 4); + $inv_SPOT_UAC1_2 = unpack("l*", substr $data, 146, 4); + $inv_SPOT_UAC2_3 = unpack("l*", substr $data, 174, 4); + $inv_SPOT_UAC3_1 = unpack("l*", substr $data, 202, 4); + + if($size >= 230) { + $inv_SPOT_CosPhi = unpack("l*", substr $data, 230, 4); + if(($inv_SPOT_CosPhi eq -2147483648) || ($inv_SPOT_CosPhi eq 0xFFFFFFFF)) {$inv_SPOT_CosPhi = "-"; } else {$inv_SPOT_CosPhi = $inv_SPOT_CosPhi / 100; } + } + else + { + $inv_SPOT_CosPhi = "-"; + } + + if(($inv_SPOT_UAC1 eq -2147483648) || ($inv_SPOT_UAC1 eq 0xFFFFFFFF) || $inv_SPOT_UAC1 < 0) {$inv_SPOT_UAC1 = "-"; } else {$inv_SPOT_UAC1 = $inv_SPOT_UAC1 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_UAC2 eq -2147483648) || ($inv_SPOT_UAC2 eq 0xFFFFFFFF) || $inv_SPOT_UAC2 < 0) {$inv_SPOT_UAC2 = "-"; } else {$inv_SPOT_UAC2 = $inv_SPOT_UAC2 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_UAC3 eq -2147483648) || ($inv_SPOT_UAC3 eq 0xFFFFFFFF) || $inv_SPOT_UAC3 < 0) {$inv_SPOT_UAC3 = "-"; } else {$inv_SPOT_UAC3 = $inv_SPOT_UAC3 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_UAC1_2 eq -2147483648) || ($inv_SPOT_UAC1_2 eq 0xFFFFFFFF) || $inv_SPOT_UAC1_2 < 0) {$inv_SPOT_UAC1_2 = "-"; } else {$inv_SPOT_UAC1_2 = $inv_SPOT_UAC1_2 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_UAC2_3 eq -2147483648) || ($inv_SPOT_UAC2_3 eq 0xFFFFFFFF) || $inv_SPOT_UAC2_3 < 0) {$inv_SPOT_UAC2_3 = "-"; } else {$inv_SPOT_UAC2_3 = $inv_SPOT_UAC2_3 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_UAC3_1 eq -2147483648) || ($inv_SPOT_UAC3_1 eq 0xFFFFFFFF) || $inv_SPOT_UAC3_1 < 0) {$inv_SPOT_UAC3_1 = "-"; } else {$inv_SPOT_UAC3_1 = $inv_SPOT_UAC3_1 / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + + Log3 $name, 5, "$name - Found Data SPOT_UAC1=$inv_SPOT_UAC1 and SPOT_UAC2=$inv_SPOT_UAC2 and SPOT_UAC3=$inv_SPOT_UAC3 and inv_SPOT_UAC1_2=$inv_SPOT_UAC1_2 and inv_SPOT_UAC2_3=$inv_SPOT_UAC2_3 and inv_SPOT_UAC3_1=$inv_SPOT_UAC3_1 and inv_SPOT_CosPhi=$inv_SPOT_CosPhi"; + return (1,$inv_SPOT_UAC1,$inv_SPOT_UAC2,$inv_SPOT_UAC3,$inv_SPOT_UAC1_2,$inv_SPOT_UAC2_3,$inv_SPOT_UAC3_1,$inv_SPOT_CosPhi,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x4653) { + $inv_SPOT_IAC1 = unpack("l*", substr $data, 62, 4); + $inv_SPOT_IAC2 = unpack("l*", substr $data, 90, 4); + $inv_SPOT_IAC3 = unpack("l*", substr $data, 118, 4); + + if(($inv_SPOT_IAC1 eq -2147483648) || ($inv_SPOT_IAC1 eq 0xFFFFFFFF) || $inv_SPOT_IAC1 < 0) {$inv_SPOT_IAC1 = "-"; } else {$inv_SPOT_IAC1 = $inv_SPOT_IAC1 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_IAC2 eq -2147483648) || ($inv_SPOT_IAC2 eq 0xFFFFFFFF) || $inv_SPOT_IAC2 < 0) {$inv_SPOT_IAC2 = "-"; } else {$inv_SPOT_IAC2 = $inv_SPOT_IAC2 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + if(($inv_SPOT_IAC3 eq -2147483648) || ($inv_SPOT_IAC3 eq 0xFFFFFFFF) || $inv_SPOT_IAC3 < 0) {$inv_SPOT_IAC3 = "-"; } else {$inv_SPOT_IAC3 = $inv_SPOT_IAC3 / 1000; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + + Log3 $name, 5, "$name - Found Data inv_SPOT_IAC1=$inv_SPOT_IAC1 and inv_SPOT_IAC2=$inv_SPOT_IAC2 and inv_SPOT_IAC3=$inv_SPOT_IAC3"; + return (1,$inv_SPOT_IAC1,$inv_SPOT_IAC2,$inv_SPOT_IAC3,$inv_susyid,$inv_serial); + } + + if ($data_ID eq 0x495B && (ReadingsVal($name,"INV_TYPE","") =~ /SBS(1\.5|2\.0|2\.5)/xs || + ReadingsVal($name,"device_type","") =~ /SBS(1\.5|2\.0|2\.5)/xs)) { + + $inv_BAT_TEMP = unpack("V*", substr $data, 62, 4) / 10; + $inv_BAT_UDC = unpack("V*", substr $data, 90, 4) / 100; + $inv_BAT_IDC = unpack("l*", substr $data, 118, 4); + + if($inv_BAT_IDC eq -2147483648) { # Catch 0x80000000 as 0 value + $inv_BAT_IDC = "-"; + } + else { + $inv_BAT_IDC = $inv_BAT_IDC / 1000; + } + + Log3 $name, 5, "$name - Found Data and BAT_TEMP=$inv_BAT_TEMP and BAT_UDC=$inv_BAT_UDC and BAT_IDC=$inv_BAT_IDC"; + return (1,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial); + } + elsif($data_ID eq 0x495B) { + $count = 0; + $inv_BAT_TEMP = 0; + $inv_BAT_TEMP_A = unpack("V*", substr $data, 62, 4); + $inv_BAT_TEMP_B = unpack("V*", substr $data, 90, 4); + $inv_BAT_TEMP_C = unpack("V*", substr $data, 118, 4); + if(($inv_BAT_TEMP_A eq -2147483648) || ($inv_BAT_TEMP_A eq 0x80000000) || $inv_BAT_TEMP_A < 0) {$inv_BAT_TEMP_A = "-"; } else {$inv_BAT_TEMP_A = $inv_BAT_TEMP_A / 10; $count = $count + 1; $inv_BAT_TEMP = $inv_BAT_TEMP + $inv_BAT_TEMP_A;} + if(($inv_BAT_TEMP_B eq -2147483648) || ($inv_BAT_TEMP_B eq 0x80000000) || $inv_BAT_TEMP_B < 0) {$inv_BAT_TEMP_B = "-"; } else {$inv_BAT_TEMP_B = $inv_BAT_TEMP_B / 10; $count = $count + 1; $inv_BAT_TEMP = $inv_BAT_TEMP + $inv_BAT_TEMP_B;} + if(($inv_BAT_TEMP_C eq -2147483648) || ($inv_BAT_TEMP_C eq 0x80000000) || $inv_BAT_TEMP_C < 0) {$inv_BAT_TEMP_C = "-"; } else {$inv_BAT_TEMP_C = $inv_BAT_TEMP_C / 10; $count = $count + 1; $inv_BAT_TEMP = $inv_BAT_TEMP + $inv_BAT_TEMP_C;} + + $inv_BAT_TEMP = $inv_BAT_TEMP / $count; + + Log3 $name, 5, "$name - Found Data and BAT_TEMP=$inv_BAT_TEMP and BAT_TEMP_A=$inv_BAT_TEMP_A and BAT_TEMP_B=$inv_BAT_TEMP_B and BAT_TEMP_C=$inv_BAT_TEMP_C"; + return (1,$inv_BAT_TEMP,$inv_BAT_TEMP_A,$inv_BAT_TEMP_B,$inv_BAT_TEMP_C,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x495C) { + $count = 0; + $inv_BAT_UDC = 0; + $inv_BAT_UDC_A = unpack("V*", substr $data, 62, 4); + $inv_BAT_UDC_B = unpack("V*", substr $data, 90, 4); + $inv_BAT_UDC_C = unpack("V*", substr $data, 118, 4); + if(($inv_BAT_UDC_A eq -2147483648) || ($inv_BAT_UDC_A eq 0xFFFFFFFF) || $inv_BAT_UDC_A < 0) {$inv_BAT_UDC_A = "-"; } else {$inv_BAT_UDC_A = $inv_BAT_UDC_A / 100; $count = $count + 1; $inv_BAT_UDC = $inv_BAT_UDC + $inv_BAT_UDC_A;} + if(($inv_BAT_UDC_B eq -2147483648) || ($inv_BAT_UDC_B eq 0xFFFFFFFF) || $inv_BAT_UDC_B < 0) {$inv_BAT_UDC_B = "-"; } else {$inv_BAT_UDC_B = $inv_BAT_UDC_B / 100; $count = $count + 1; $inv_BAT_UDC = $inv_BAT_UDC + $inv_BAT_UDC_B;} + if(($inv_BAT_UDC_C eq -2147483648) || ($inv_BAT_UDC_C eq 0xFFFFFFFF) || $inv_BAT_UDC_C < 0) {$inv_BAT_UDC_C = "-"; } else {$inv_BAT_UDC_C = $inv_BAT_UDC_C / 100; $count = $count + 1; $inv_BAT_UDC = $inv_BAT_UDC + $inv_BAT_UDC_C;} + + $inv_BAT_UDC = $inv_BAT_UDC / $count; + + Log3 $name, 5, "$name - Found Data and BAT_UDC=$inv_BAT_UDC and BAT_UDC_A=$inv_BAT_UDC_A and BAT_UDC_B=$inv_BAT_UDC_B and BAT_UDC_C=$inv_BAT_UDC_C"; + return (1,$inv_BAT_UDC,$inv_BAT_UDC_A,$inv_BAT_UDC_B,$inv_BAT_UDC_C,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x495D) { + $count = 0; + $inv_BAT_IDC = 0; + $inv_BAT_IDC_A = unpack("l*", substr $data, 62, 4); + $inv_BAT_IDC_B = unpack("l*", substr $data, 90, 4); + $inv_BAT_IDC_C = unpack("l*", substr $data, 118, 4); + if(($inv_BAT_IDC_A eq -2147483648) || ($inv_BAT_IDC_A eq 0x80000000)) {$inv_BAT_IDC_A = "-"; } else {$inv_BAT_IDC_A = $inv_BAT_IDC_A / 1000; $count = $count + 1; $inv_BAT_IDC = $inv_BAT_IDC + $inv_BAT_IDC_A;} + if(($inv_BAT_IDC_B eq -2147483648) || ($inv_BAT_IDC_B eq 0x80000000)) {$inv_BAT_IDC_B = "-"; } else {$inv_BAT_IDC_B = $inv_BAT_IDC_B / 1000; $count = $count + 1; $inv_BAT_IDC = $inv_BAT_IDC + $inv_BAT_IDC_B;} + if(($inv_BAT_IDC_C eq -2147483648) || ($inv_BAT_IDC_C eq 0x80000000)) {$inv_BAT_IDC_C = "-"; } else {$inv_BAT_IDC_C = $inv_BAT_IDC_C / 1000; $count = $count + 1; $inv_BAT_IDC = $inv_BAT_IDC + $inv_BAT_IDC_C;} + + #$inv_BAT_IDC = $inv_BAT_IDC / $count; + + Log3 $name, 5, "$name - Found Data and BAT_IDC=$inv_BAT_IDC and BAT_IDC_A=$inv_BAT_IDC_A and BAT_IDC_B=$inv_BAT_IDC_B and BAT_IDC_C=$inv_BAT_IDC_C"; + return (1,$inv_BAT_IDC,$inv_BAT_IDC_A,$inv_BAT_IDC_B,$inv_BAT_IDC_C,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x491E) { + $inv_BAT_CYCLES = unpack("V*", substr $data, 62, 4); + $inv_BAT_TEMP = unpack("V*", substr $data, 90, 4) / 10; + $inv_BAT_UDC = unpack("V*", substr $data, 118, 4) / 100; + $inv_BAT_IDC = unpack("l*", substr $data, 146, 4); + + if($inv_BAT_IDC eq -2147483648) { # Catch 0x80000000 as 0 value + $inv_BAT_IDC = 0; + } + else { + $inv_BAT_IDC = $inv_BAT_IDC / 1000; + } + + Log3 $name, 5, "$name - Found Data BAT_CYCLES=$inv_BAT_CYCLES and BAT_TEMP=$inv_BAT_TEMP and BAT_UDC=$inv_BAT_UDC and BAT_IDC=$inv_BAT_IDC"; + return (1,$inv_BAT_CYCLES,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x495F) { + + $inv_BAT_CYCLES = unpack("V*", substr $data, 62, 4); + $inv_BAT_TEMP = unpack("V*", substr $data, 90, 4) / 10; + $inv_BAT_UDC = unpack("V*", substr $data, 118, 4) / 100; + $inv_BAT_IDC = unpack("l*", substr $data, 146, 4); + + if($inv_BAT_IDC eq -2147483648) { # Catch 0x80000000 as 0 value + $inv_BAT_IDC = "-"; + } + else { + $inv_BAT_IDC = $inv_BAT_IDC / 1000; + } + + Log3 $name, 5, "$name - Found Data BAT_CYCLES=$inv_BAT_CYCLES and BAT_TEMP=$inv_BAT_TEMP and BAT_UDC=$inv_BAT_UDC and BAT_IDC=$inv_BAT_IDC"; + return (1,$inv_BAT_CYCLES,$inv_BAT_TEMP,$inv_BAT_UDC,$inv_BAT_IDC,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x2377) { + $inv_TEMP = unpack("l*", substr $data, 62, 4); + + if($inv_TEMP eq -2147483648) { # Catch 0x80000000 as 0 value + $inv_TEMP = 0; + } + else { + $inv_TEMP = $inv_TEMP / 100; + } + + Log3 $name, 5, "$name - Found Data Inverter Temp=$inv_TEMP"; + return (1,$inv_TEMP,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x462E) { + $inv_SPOT_OPERTM = int(unpack("V*", substr $data, 62, 4) / 36) / 100; + $inv_SPOT_FEEDTM = int(unpack("V*", substr $data, 78, 4) / 36) / 100; + Log3 $name, 5, "$name - Found Data SPOT_OPERTM=$inv_SPOT_OPERTM and SPOT_FEEDTM=$inv_SPOT_FEEDTM"; + return (1,$inv_SPOT_OPERTM,$inv_SPOT_FEEDTM,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x4657) { + $inv_SPOT_FREQ = unpack("V*", substr $data, 62, 4); + if(($inv_SPOT_FREQ eq -2147483648) || ($inv_SPOT_FREQ eq 0xFFFFFFFF)) {$inv_SPOT_FREQ = 0; } else {$inv_SPOT_FREQ = $inv_SPOT_FREQ / 100; } # Catch 0x80000000 and 0xFFFFFFFF as 0 value + Log3 $name, 5, "$name - Found Data SPOT_FREQ=$inv_SPOT_FREQ"; + return (1,$inv_SPOT_FREQ,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x821E) { + $inv_CLASS = unpack("V*", substr $data, 102, 4) & 0x00FFFFFF; + $i = 142; # start address of INV_TYPE + $inv_TYPE = 0; # initialize to unknown inverter type + do { + $temp = unpack("V*", substr $data, $i, 4); + if(($temp & 0xFF000000) eq 0x01000000) { $inv_TYPE = $temp & 0x00FFFFFF; } # in some models a catalogue is transmitted, right model marked with: 0x01000000 OR INV_Type + $i = $i+4; + } while ((unpack("V*", substr $data, $i, 4) ne 0x00FFFFFE) && ($i<$size)); # 0x00FFFFFE is the end marker for attributes + + Log3 $name, 5, "$name - Found Data CLASS=$inv_CLASS and TYPE=$inv_TYPE"; + return (1,$inv_TYPE,$inv_CLASS,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x4164) { + $i = 0; + $temp = 0; + $inv_GRIDRELAY = 0x00FFFFFD; # Code for No Information; + do { + $temp = unpack("V*", substr $data, 62 + $i*4, 4); + if(($temp & 0xFF000000) ne 0) { $inv_GRIDRELAY = $temp & 0x00FFFFFF; } + $i = $i + 1; + } while ((unpack("V*", substr $data, 62 + $i*4, 4) ne 0x00FFFFFE) && ($i < 5)); # 0x00FFFFFE is the end marker for attributes + Log3 $name, 5, "$name - Found Data INV_GRIDRELAY=$inv_GRIDRELAY"; + return (1,$inv_GRIDRELAY,$inv_susyid,$inv_serial); + } + + if($data_ID eq 0x2148) { + $i = 0; + $temp = 0; + $inv_STATUS = 0x00FFFFFD; # Code for No Information; + do { + $temp = unpack("V*", substr $data, 62 + $i*4, 4); + if(($temp & 0xFF000000) ne 0) { $inv_STATUS = $temp & 0x00FFFFFF; } + $i = $i + 1; + } while ((unpack("V*", substr $data, 62 + $i*4, 4) ne 0x00FFFFFE) && ($i < 5)); # 0x00FFFFFE is the end marker for attributes + Log3 $name, 5, "$name - Found Data inv_STATUS=$inv_STATUS"; + return (1,$inv_STATUS,$inv_susyid,$inv_serial); + } + +return 0; +} + +########################################################################## +# Login +########################################################################## +sub SMAInverter_SMAlogon($$$) { + # Parameters: host - passcode + my ($host,$pass,$hash) = @_; + my $cmdheader = "534D4100000402A00000000100"; + my $pktlength = "3A"; # length = 58 for logon command + my $esignature = "001060650EA0"; + my $name = $hash->{NAME}; + my $mysusyid = $hash->{HELPER}{MYSUSYID}; + my $myserialnumber = $hash->{HELPER}{MYSERIALNUMBER}; + my $pkt_ID = $hash->{HELPER}{PKT_ID}; + my ($cmd, $timestmp, $myID, $target_ID, $spkt_ID, $cmd_ID); + my ($socket,$data,$size); + + # Seriennummer und SuSyID des Ziel-WR setzen + my $default_target_susyid = $hash->{HELPER}{DEFAULT_TARGET_SUSYID}; + my $default_target_serial = $hash->{HELPER}{DEFAULT_TARGET_SERIAL}; + my $target_susyid = AttrVal($name, "target-susyid", $default_target_susyid); + my $target_serial = AttrVal($name, "target-serial", $default_target_serial); + + #Encode the password + my $encpasswd = "888888888888888888888888"; # template for password + for my $index (0..length $pass ) # encode password + { + if ( (hex(substr($encpasswd,($index*2),2)) + ord(substr($pass,$index,1))) < 256 ) { + substr($encpasswd,($index*2),2) = substr(sprintf ("%lX", (hex(substr($encpasswd,($index*2),2)) + ord(substr($pass,$index,1)))),0,2); + } else { + substr($encpasswd,($index*2),2) = substr(sprintf ("%lX", (hex(substr($encpasswd,($index*2),2)) + ord(substr($pass,$index,1)))),1,2); + } + } + + # Get current timestamp in epoch format (unix format) + $timestmp = SMAInverter_ByteOrderLong(sprintf("%08X",int(time()))); + + # Define own ID and target ID and packet ID + $myID = SMAInverter_ByteOrderShort(substr(sprintf("%04X",$mysusyid),0,4)) . SMAInverter_ByteOrderLong(sprintf("%08X",$myserialnumber)); + $target_ID = SMAInverter_ByteOrderShort(substr(sprintf("%04X",$target_susyid),0,4)) . SMAInverter_ByteOrderLong(sprintf("%08X",$target_serial)); + $pkt_ID = 0x8001; # Reset to 0x8001 + $spkt_ID = SMAInverter_ByteOrderShort(sprintf("%04X",$pkt_ID)); + + #Logon command + $cmd_ID = "0C04FDFF" . "07000000" . "84030000"; # Logon command + User group "User" + (maybe) Timeout + + #build final command to send + $cmd = $cmdheader . $pktlength . $esignature . $target_ID . "0001" . $myID . "0001" . "00000000" . $spkt_ID . $cmd_ID . $timestmp . "00000000" . $encpasswd . "00000000"; + + # flush after every write + $| = 1; + + # Create Socket and check if successful + $socket = new IO::Socket::INET (PeerHost => $host, PeerPort => 9522, Proto => 'udp',); # open Socket + + if (!$socket) { + # in case of error + Log3 $name, 1, "$name - ERROR - Can't open socket to inverter: $!"; + return 0; + }; + + # Send Data + $data = pack("H*",$cmd); + $socket->send($data); + Log3 $name, 4, "$name - Send login to $host on Port 9522 with password $pass "; + Log3 $name, 5, "$name - Send: $cmd "; + + # Receive Data and do a first check regarding length + eval { + $socket->recv($data, $hash->{HELPER}{MAXBYTES}); + $size = length($data); + }; + + # check if something was received + if (defined $size) { + my $received = unpack("H*", $data); + Log3 $name, 5, "$name - Received: $received"; + } + + # Nothing received -> exit + if (not defined $size) { + Log3 $name, 1, "$name - Nothing received..."; + # send: cmd_logout + $socket->close(); + SMAInverter_SMAlogout($hash,$host); + return 0; + } else { + # We have received something! + if ($size > 62) { + # Check all parameters of answer + my $r_susyid = unpack("v*", substr $data, 20, 2); + my $r_serial = unpack("V*", substr $data, 22, 4); + my $r_pkt_ID = unpack("v*", substr $data, 40, 2); + my $r_cmd_ID = unpack("V*", substr $data, 42, 4); + my $r_error = unpack("V*", substr $data, 36, 4); + + if (($r_pkt_ID ne $pkt_ID) || ($r_cmd_ID ne 0xFFFD040D) || ($r_error ne 0)) { + # Response does not match the parameters we have sent, maybe different target + Log3 $name, 1, "$name - Inverter answer does not match our parameters."; + Log3 $name, 5, "$name - Request/Response: SusyID $mysusyid/$r_susyid, Serial $myserialnumber/$r_serial, Packet ID $hash->{HELPER}{PKT_ID}/$r_pkt_ID, Command 0xFFFD040D/$r_cmd_ID, Error $r_error"; + # send: cmd_logout + $socket->close(); + SMAInverter_SMAlogout($hash,$host); + return 0; + } + } else { + Log3 $name, 1, "$name - Format of inverter response does not fit."; + # send: cmd_logout + $socket->close(); + SMAInverter_SMAlogout($hash,$host); + return 0; + } + } + + # All seems ok, logged in! + my $inv_susyid = unpack("v*", substr $data, 28, 2); + my $inv_serial = unpack("V*", substr $data, 30, 4); + $socket->close(); + + if (AttrVal($name, "target-serial", undef)) { + return 0 unless($inv_serial eq $target_serial); + } else { + BlockingInformParent("SMAInverter_setAttrFromBlocking", [$name, "target-serial", $inv_serial], 0); # Serial automatisch setzen, Forum: https://forum.fhem.de/index.php/topic,56080.msg967448.html#msg967448 + } + + if (AttrVal($name, "target-susyid", undef)) { + return 0 unless($inv_susyid eq $target_susyid); + } else { + BlockingInformParent("SMAInverter_setAttrFromBlocking", [$name, "target-susyid", $inv_susyid], 0); # SuSyId automatisch setzen, Forum: https://forum.fhem.de/index.php/topic,56080.msg967448.html#msg967448 + } + + Log3 $name, 4, "$name - logged in to inverter serial: $inv_serial, susyid: $inv_susyid"; + +return 1; +} + +################################################################ +# Attributwert aus BlockingCall setzen +################################################################ +sub SMAInverter_setAttrFromBlocking($$$) { + my ($name,$attr,$val) = @_; + my $hash = $defs{$name}; + + CommandAttr(undef,"$name $attr $val"); + +return; +} + +################################################################ +# Readingwert aus BlockingCall setzen +################################################################ +sub SMAInverter_setReadingFromBlocking($$$) { + my ($name,$reading,$val) = @_; + my $hash = $defs{$name}; + + readingsSingleUpdate($hash, $reading, $val, 0); + +return; +} + +########################################################################## +# Logout +########################################################################## +sub SMAInverter_SMAlogout($$) { + # Parameters: host + my ($hash,$host) = @_; + my $name = $hash->{NAME}; + my $cmdheader = "534D4100000402A00000000100"; + my $pktlength = "22"; # length = 34 for logout command + my $esignature = "0010606508A0"; + my $mysusyid = $hash->{HELPER}{MYSUSYID}; + my $myserialnumber = $hash->{HELPER}{MYSERIALNUMBER}; + my $pkt_ID = $hash->{HELPER}{PKT_ID}; + my ($cmd, $myID, $target_ID, $spkt_ID, $cmd_ID); + my ($socket,$data,$size); + + # Seriennummer und SuSyID des Ziel-WR setzen + my $default_target_susyid = $hash->{HELPER}{DEFAULT_TARGET_SUSYID}; + my $default_target_serial = $hash->{HELPER}{DEFAULT_TARGET_SERIAL}; + my $target_susyid = AttrVal($name, "target-susyid", $default_target_susyid); + my $target_serial = AttrVal($name, "target-serial", $default_target_serial); + + # Define own ID and target ID and packet ID + $myID = SMAInverter_ByteOrderShort(substr(sprintf("%04X",$mysusyid),0,4)) . SMAInverter_ByteOrderLong(sprintf("%08X",$myserialnumber)); + $target_ID = SMAInverter_ByteOrderShort(substr(sprintf("%04X",$target_susyid),0,4)) . SMAInverter_ByteOrderLong(sprintf("%08X",$target_serial)); + # Increasing Packet ID + $hash->{HELPER}{PKT_ID} = $hash->{HELPER}{PKT_ID} + 1; + $spkt_ID = SMAInverter_ByteOrderShort(sprintf("%04X",$hash->{HELPER}{PKT_ID})); + + # Logout command + $cmd_ID = "0E01FDFF" . "FFFFFFFF"; # Logout command + + # build final command to send + $cmd = $cmdheader . $pktlength . $esignature . $target_ID . "0003" . $myID . "0003" . "00000000" . $spkt_ID . $cmd_ID . "00000000"; + + # flush after every write + $| = 1; + + # Create Socket and check if successful + $socket = new IO::Socket::INET (PeerHost => $host, PeerPort => 9522, Proto => 'udp',); # open Socket + + if (!$socket) { + # in case of error + Log3 $name, 1, "$name - ERROR - Can't open socket to inverter: $!"; + return 0; + }; + + # Send Data + $data = pack("H*",$cmd); + $socket->send($data); + Log3 $name, 4, "$name - Send logout to $host on Port 9522"; + Log3 $name, 5, "$name - Send: $cmd "; + + $target_serial = ($target_serial eq $default_target_serial)?"any inverter":$target_serial; + $target_susyid = ($target_susyid eq $default_target_susyid)?"any susyid":$target_susyid; + Log3 $name, 4, "$name - logged out now from inverter serial: $target_serial, susyid: $target_susyid"; + + $socket->close(); + +return 1; +} + +########################################################################## +# Versionierungen des Moduls setzen +# Die Verwendung von Meta.pm und Packages wird berücksichtigt +########################################################################## +sub SMAInverter_setVersionInfo($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $v = (sortTopicNum("desc",keys %SMAInverter_vNotesIntern))[0]; + my $type = $hash->{TYPE}; + $hash->{HELPER}{PACKAGE} = __PACKAGE__; + $hash->{HELPER}{VERSION} = $v; + + if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) { + # META-Daten sind vorhanden + $modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}} + if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id: 76_SMAInverter.pm 23909 2021-03-07 20:06:59Z DS_Starter $ im Kopf komplett! vorhanden ) + $modules{$type}{META}{x_version} =~ s/1.1.1/$v/g; + } else { + $modules{$type}{META}{x_version} = $v; + } + return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 76_SMAInverter.pm 23909 2021-03-07 20:06:59Z DS_Starter $ im Kopf komplett! vorhanden ) + if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) { + # es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen + # mit {->VERSION()} im FHEMWEB kann Modulversion abgefragt werden + use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + } + } else { + # herkömmliche Modulstruktur + $hash->{VERSION} = $v; + } + +return; +} + +########################################################################## +# Sortierung +########################################################################## +sub SMAInverter_ByteOrderShort($) { + my $input = $_[0]; + my $output = ""; + $output = substr($input, 2, 2) . substr($input, 0, 2); + +return $output; +} + +########################################################################## +# Sortierung +########################################################################## +sub SMAInverter_ByteOrderLong($) { + my $input = $_[0]; + my $output = ""; + $output = substr($input, 6, 2) . substr($input, 4, 2) . substr($input, 2, 2) . substr($input, 0, 2); + +return $output; +} + +########################################################################## +# Texte for State +# Parameter is the code, return value is the Text or if not known then +# the code as string +########################################################################## +sub SMAInverter_StatusText($) { + my $code = $_[0]; + + if($code eq 51) { return (AttrVal("global", "language", "EN") eq "DE") ? "geschlossen" : "Closed"; } + if($code eq 311) { return (AttrVal("global", "language", "EN") eq "DE") ? "offen" : "Open"; } + if($code eq 16777213) { return (AttrVal("global", "language", "EN") eq "DE") ? "Information liegt nicht vor" : "No Information"; } + + if($code eq 35) { return (AttrVal("global", "language", "EN") eq "DE") ? "Fehler" : "Fault"; } + if($code eq 303) { return "Off"; } + if($code eq 307) { return "Ok"; } + if($code eq 455) { return (AttrVal("global", "language", "EN") eq "DE") ? "Warnung" : "Warning"; } + +return sprintf("%d", $code); +} + +########################################################################## +# identify inverter type +########################################################################## +sub SMAInverter_devtype ($) { + my ($code) = @_; + + unless (exists($SMAInverter_devtypes{$code})) { return $code;} + my $dev = $SMAInverter_devtypes{$code}; + +return ($dev); +} + +########################################################################## +# identify device class +########################################################################## +sub SMAInverter_classtype ($) { + my ($code) = @_; + my $class; + + if(AttrVal("global", "language", "EN") eq "DE") { + unless (exists($SMAInverter_classesDE{$code})) { return $code;} + $class = $SMAInverter_classesDE{$code}; + } else { + unless (exists($SMAInverter_classesEN{$code})) { return $code;} + $class = $SMAInverter_classesEN{$code}; + } + +return ($class); +} + +1; + +=pod +=item summary Integration of SMA Inverters over it's Speedwire (=Ethernet) Interface +=item summary_DE Integration von SMA Wechselrichtern über Speedwire (=Ethernet) Interface + +=begin html + + +

SMAInverter

+ +Module for the integration of a SMA Inverter over it's Speedwire (=Ethernet) Interface.
+Tested on Sunny Tripower 6000TL-20 and Sunny Island 4.4 with Speedwire/Webconnect Piggyback. +

+ +Questions and discussions about this module you can find in the FHEM-Forum link:
+76_SMAInverter.pm - Abfrage von SMA Wechselrichter. +

+ +Requirements +

+This module requires: +
    +
  • Perl Module: IO::Socket::INET (apt-get install libio-socket-multicast-perl)
  • +
  • Perl Module: Date::Time (apt-get install libdatetime-perl)
  • +
  • Perl Module: Time::HiRes
  • +
  • FHEM Module: 99_SUNRISE_EL.pm
  • +
  • FHEM Module: Blocking.pm
  • +
+
+
+ + +Definition +
    +define <name> SMAInverter <pin> <hostname/ip>
    +
    +
  • pin: password of the inverter. Default is 0000.
    + inverter without webinterface: The password for the inverter can be changed by "Sunny Explorer" Client Software
    + inverter with webinterface: The password changed by the webinterface is also valid for the device definition.
  • +
  • hostname/ip: Hostname or IP-Adress of the inverter (or it's speedwire piggyback module).
  • +
  • The Speedwire port is 9522 by default. A Firewall has to allow connection on this port if present !
  • +
+ + +Operation method +
    +The module sends commands to the inverter and checks if they are supported by the inverter.
    +In case of a positive answer the data is collected and displayed in the readings according to the detail-level.

    + +The normal operation time of the inverter is supposed from sunrise to sunset. In that time period the inverter will be polled. +The time of sunrise and sunset will be calculated by functions of FHEM module 99_SUNRISE_EL.pm which is loaded automatically by default. +Therefore the global attribute "longitude" and "latitude" should be set to determine the position of the solar system +(see Commandref SUNRISE_EL).

    + +By the attribute "suppressSleep" the sleep mode between sunset and sunrise can be suppressed. Using attribute "offset" you may prefer the sunrise and +defer the sunset virtually. So the working period of the inverter will be extended.

    + +In operating mode "automatic" the inverter will be requested periodically corresponding the preset attribute "interval". The operating mode can be +switched to "manual" to realize the retrieval manually (e.g. to synchronize the requst with a SMA energy meter by notify).

    + +During inverter operating time the average energy production of the last 5, 10 and 15 minutes will be calculated and displayed in the readings +"avg_power_lastminutes_05", "avg_power_lastminutes_10" and "avg_power_lastminutes_15". Note: To permit a precise calculation, you should +also set the real request interval into the attribute "interval" although you would use the "manual" operation mode !

    + +The retrieval of the inverter will be executed non-blocking. You can adjust the timeout value for this background process by attribute "timeout".
    +
+ +Get +
+
    + +
  • get <name> data +

    + + The request of the inverter will be executed. Those possibility is especifically created for the "manual" operation + mode (see attribute "mode"). +
    +
  • + +
    +
+ +Attributes +
    + + +
  • detail-level [0|1|2]
    + Defines the complexity of the generated readings.

    + +
      + + + + + +
      0 - only Power and Energy
      1 - as 0, additional voltage and current
      2 - all values
      +
    + +
  • +
    + + +
  • disable [1|0]
    + Deactivate/activate the module. +
  • +
    + + +
  • interval
    + Request cycle in seconds. (default: 60) +
  • +
    + + +
  • mode [automatic|manual]
    + The request mode of the inverter. (default: automatic)

    + +
      + + + + +
      automatic - the inverter will be polled regularly as defined by attribute "interval"
      manual - query only by command "get <name> data"
      +
    + +
  • +
    + + +
  • offset <0 - 7200>
    + Time in seconds to forward the real sunrise respectively defer the real sunset. + You will be able to extend the working period of the module. +
  • +
    + + +
  • SBFSpotComp [1|0]
    + The reading names are created like the SBFSpot-style. (default: 0) +
  • +
    + + +
  • showproctime [1|0]
    + Shows the processing time in background and the wasted time to retrieve inverter data. (default: 0) +
  • +
    + + +
  • suppressSleep [1|0]
    + The sleep mode (after sunset and before sunrise) is deactivated and the inverter will be polled continuously. (default: 0) +
  • +
    + + +
  • target-serial
    + In case of a Multigate the target serial number has to be defined. If more than one inverter is installed, + you have to set the inverter serial number to assign the inverter to the device definition. + If only one inverter available, the attribut is set automatically once the serial number of the inverter was detected. + (default: 0xFFFFFFFF = means any serial number) +
  • +
    + + +
  • target-susyid
    + In case of a Multigate the target SUSyID has to be defined. If more than one inverter is installed, + you have to set the inverter-SUSyID to assign the inverter to the device definition. + If only one inverter available, the attribut is set automatically once the SUSyID of the inverter was detected. + (default: 0xFFFF = means any SUSyID) +
  • +
    + + +
  • timeout
    + Setup timeout of inverter data request in seconds. (default 60) +
  • +
    + +
+ +Readings +
    +
  • BAT_CYCLES / bat_cycles : Battery recharge cycles
  • +
  • BAT_IDC [A,B,C] / bat_idc [A,B,C] : Battery Current [A,B,C]
  • +
  • BAT_TEMP [A,B,C] / bat_temp [A,B,C] : Battery temperature [A,B,C]
  • +
  • BAT_UDC [A,B,C] / bat_udc [A,B,C] : Battery Voltage [A,B,C]
  • +
  • ChargeStatus / chargestatus : Battery Charge status
  • +
  • BAT_LOADTODAY : Battery Load Today
  • +
  • BAT_LOADTOTAL : Battery Load Total
  • +
  • ChargeStatus / chargestatus : Battery Charge status
  • +
  • CLASS / device_class : Inverter Class
  • +
  • PACMAX1 / pac_max_phase_1 : Nominal power in Ok Mode
  • +
  • PACMAX1_2 / pac_max_phase_1_2 : Maximum active power device (Some inverters like SB3300/SB1200)
  • +
  • PACMAX2 / pac_max_phase_2 : Nominal power in Warning Mode
  • +
  • PACMAX3 / pac_max_phase_3 : Nominal power in Fault Mode
  • +
  • Serialnumber / serial_number : Inverter Serialnumber
  • +
  • SPOT_ETODAY / etoday : Today yield
  • +
  • SPOT_ETOTAL / etotal : Total yield
  • +
  • SPOT_FEEDTM / feed-in_time : Feed-in time
  • +
  • SPOT_FREQ / grid_freq : Grid Frequency
  • +
  • SPOT_CosPhi / coshhi : displacement factor
  • +
  • SPOT_IAC1 / phase_1_iac : Grid current phase L1
  • +
  • SPOT_IAC2 / phase_2_iac : Grid current phase L2
  • +
  • SPOT_IAC3 / phase_3_iac : Grid current phase L3
  • +
  • SPOT_IDC1 / string_1_idc : DC current input
  • +
  • SPOT_IDC2 / string_2_idc : DC current input
  • +
  • SPOT_OPERTM / operation_time : Operation Time
  • +
  • SPOT_PAC1 / phase_1_pac : Power L1
  • +
  • SPOT_PAC2 / phase_2_pac : Power L2
  • +
  • SPOT_PAC3 / phase_3_pac : Power L3
  • +
  • SPOT_PACTOT / total_pac : Total Power
  • +
  • SPOT_PDC1 / string_1_pdc : DC power input 1
  • +
  • SPOT_PDC2 / string_2_pdc : DC power input 2
  • +
  • SPOT_UAC1 / phase_1_uac : Grid voltage phase L1
  • +
  • SPOT_UAC2 / phase_2_uac : Grid voltage phase L2
  • +
  • SPOT_UAC3 / phase_3_uac : Grid voltage phase L3
  • +
  • SPOT_UAC1_2 / phase_1_2_uac : Grid voltage phase L1-L2
  • +
  • SPOT_UAC2_3 / phase_2_3_uac : Grid voltage phase L2-L3
  • +
  • SPOT_UAC3_1 / phase_3_1_uac : Grid voltage phase L3-L1
  • +
  • SPOT_UDC1 / string_1_udc : DC voltage input
  • +
  • SPOT_UDC2 / string_2_udc : DC voltage input
  • +
  • SUSyID / susyid : Inverter SUSyID
  • +
  • INV_TEMP / device_temperature : Inverter temperature
  • +
  • INV_TYPE / device_type : Inverter Type
  • +
  • POWER_IN / power_in : Battery Charging power
  • +
  • POWER_OUT / power_out : Battery Discharging power
  • +
  • INV_GRIDRELAY / gridrelay_status : Grid Relay/Contactor Status
  • +
  • INV_STATUS / device_status : Inverter Status
  • +
  • opertime_start : Begin of iverter operating time corresponding the calculated time of sunrise with consideration of the + attribute "offset" (if set)
  • +
  • opertime_stop : End of iverter operating time corresponding the calculated time of sunrise with consideration of the + attribute "offset" (if set)
  • +
  • modulstate : shows the current module state "normal" or "sleep" if the inverter won't be requested at the time.
  • +
  • avg_power_lastminutes_05 : average power of the last 5 minutes.
  • +
  • avg_power_lastminutes_10 : average power of the last 10 minutes.
  • +
  • avg_power_lastminutes_15 : average power of the last 15 minutes.
  • +
  • inverter_processing_time : wasted time to retrieve the inverter data
  • +
  • background_processing_time : total wasted time by background process (BlockingCall)
  • +
+

+ +=end html + + +=begin html_DE + + +

SMAInverter

+ +Modul zur Einbindung eines SMA Wechselrichters über Speedwire (Ethernet).
+Getestet mit Sunny Tripower 6000TL-20 und Sunny Island 4.4 mit Speedwire/Webconnect Piggyback. +

+ +Fragen und Diskussionen rund um dieses Modul finden sie im FHEM-Forum unter:
+76_SMAInverter.pm - Abfrage von SMA Wechselrichter. +

+ +Voraussetzungen +

+Dieses Modul benötigt: +
    +
  • Perl Modul: IO::Socket::INET (apt-get install libio-socket-multicast-perl)
  • +
  • Perl Modul: Datetime (apt-get install libdatetime-perl)
  • +
  • Perl Modul: Time::HiRes
  • +
  • FHEM Modul: 99_SUNRISE_EL.pm
  • +
  • FHEM Modul: Blocking.pm
  • +
+
+
+ +Definition +
    +define <name> SMAInverter <pin> <hostname/ip>
    +
    +
  • pin: Passwort des Wechselrichters. Default ist 0000.
    + Wechselrichter ohne Webinterface: Das Passwort kann über die Client Software "Sunny Explorer" geändert werden.
    + Wechselrichter mit Webinterface: Das im Webinterface geänderte Passwort gilt auch für die Devicedefinition.
  • +
  • hostname/ip: Hostname oder IP-Adresse des Wechselrichters (bzw. dessen Speedwire Moduls mit Ethernetanschluss)
  • +
  • Der Speedwire-Port ist 9522. Dieser Port muss in der Firewall freigeschaltet sein !
  • +
+ +Arbeitsweise +
    +Das Modul schickt Befehle an den Wechselrichter und überprüft, ob diese unterstützt werden.
    +Bei einer positiven Antwort werden die Daten gesammelt und je nach Detail-Level in den Readings dargestellt.

    + +Die normale Betriebszeit des Wechselrichters wird in der Zeit vom Sonnenaufgang bis Sonnenuntergang angenommen. In dieser Periode werden die Wechselrichterdaten +abgefragt. Die Ermittlung von Sonnenaufgang / Sonnenuntergang wird über die Funktionen des FHEM-Moduls 99_SUNRISE_EL.pm vorgenommen. Zu diesem Zweck sollten die globalen +Attribute longitude und latitude gesetzt sein um den Standort der Anlage genau zu ermitteln. (siehe Commandref SUNRISE_EL)

    + +Mit dem Attribut "suppressSleep" kann der Schlafmodus unterdrückt werden. Das Attribut "offset" dient dazu den effektiven Zeitpunkt des Sonnenaufgangs / Sonnenuntergangs +um den Betrag "offset" vorzuziehen (Sonnenaufgang) bzw. zu verzögern (Sonnenuntergang) und somit die Abfrageperiode des Wechselrichters zu verlängern.

    + +Im Betriebsmodus "automatic" wird der Wechselrichter entsprechend des eingestellten Attributs "interval" abgefragt. Der Betriebsmodus kann in "manual" +umgestellt werden um eine manuelle Abfrage zu realisieren (z.B. Synchronisierung mit einem SMA Energymeter über ein Notify).

    + +Während der Betriebszeit des Wechselrichters wird die durchschnittliche Energieerzeugung der letzten 5, 10, 15 Minuten berechnet und in den Readings +"avg_power_lastminutes_05", "avg_power_lastminutes_10" und "avg_power_lastminutes_15" ausgegeben. Hinweis: Um eine korrekte Berechnung zu +ermöglichen, sollte auch im Betriebsmodus "manual" das tatsächliche Abfrageinterval im Attribute "interval" hinterlegt werden !

    + +Die Abfrage des Wechselrichters wird non-blocking ausgeführt. Der Timeoutwert für diesen Hintergrundprozess kann mit dem Attribut "timeout" eingestellt werden.
    + +
+ +Get +
+
    + +
  • get <name> data +

    + + Die Datenabfrage des Wechselrichters wird ausgeführt. Diese Möglichkeit ist speziell für den Betriebsmodus "manual" + vorgesehen (siehe Attribut "mode"). +
    +
  • +
    +
+ +Attribute + +
    + + +
  • detail-level [0|1|2]
    + Legt den Umfang der ausgegebenen Readings fest.

    + +
      + + + + + +
      0 - nur Leistung und Energie
      1 - wie 0, zusätzlich Strom und Spannung
      2 - alle Werte
      +
    + +
  • +
    + + +
  • disable [1|0]
    + Deaktiviert/aktiviert das Modul. +
  • +
    + + +
  • interval
    + Abfrageinterval in Sekunden. (default: 60) +
  • +
    + + +
  • mode [automatic|manual]
    + Abfragemodus des Wechselrichters. (default: automatic)

    + +
      + + + + +
      automatic - die Wechselrichterwerte werden im eingestellten Interval abgefragt (Attribut "interval")
      manual - Abfrage nur mit "get <name> data"
      +
    + +
  • +
    + + +
  • offset <0 - 7200>
    + Zeit in Sekunden, um die der reale Sonnenaufgang vorgezogen bzw. reale Sonnenuntergang verzögert wird. + Dadurch wird die effektive Aktivzeit des Moduls erweitert. +
  • +
    + + +
  • SBFSpotComp [1|0]
    + Die Readingnamen werden kompatibel zu SBFSpot-Ausgaben erzeugt. (default: 0) +
  • +
    + + +
  • showproctime [1|0]
    + Zeigt die für den Hintergrundprozess und die Abfrage des Wechselrichter verbrauchte Zeit. (default: 0) +
  • +
    + + +
  • suppressSleep [1|0]
    + Der Schlafmodus (nach Sonnenuntergang und vor Sonnenaufgang) wird ausgeschaltet und der WR abgefragt. (default: 0) +
  • +
    + + +
  • target-serial
    + Im Falle eines Multigate muss die Ziel-Seriennummer definiert werden. Ist mehr als ein Wechselrichter installiert, + muß die Wechselreichter-Seriennummer gesetzt werden um den Wechselrichter der Device-Definition eindeutig zuzuweisen. + Ist nur ein Wechselrichter vorhanden und das Attribut nicht gesetzt, wird es automatisch definiert sobald die + Seriennummer des Wechselrichters erkannt wurde. + (default: 0xFFFFFFFF = keine Einschränkung) +
  • +
    + + +
  • target-susyid
    + Im Falle eines Multigate muss die Ziel-SUSyID definiert werden. Ist mehr als ein Wechselrichter installiert, + muß die Wechselreichter-SUSyID gesetzt werden um den Wechselrichter der Device-Definition eindeutig zuzuweisen. + Ist nur ein Wechselrichter vorhanden und das Attribut nicht gesetzt, wird es automatisch definiert sobald die + SUSyID des Wechselrichters erkannt wurde. + (default: 0xFFFF = keine Einschränkung) +
  • +
    + + +
  • timeout
    + Einstellung des timeout für die Wechselrichterabfrage in Sekunden. (default 60) +
  • +
    + +
+ +Readings +
    +
  • BAT_CYCLES / bat_cycles : Akku Ladezyklen
  • +
  • BAT_IDC [A,B,C] / bat_idc [A,B,C] : Akku Strom [A,B,C]
  • +
  • BAT_TEMP [A,B,C] / bat_temp [A,B,C] : Akku Temperatur [A,B,C]
  • +
  • BAT_UDC [A,B,C] / bat_udc [A,B,C] : Akku Spannung [A,B,C]
  • +
  • ChargeStatus / chargestatus : Akku Ladestand
  • +
  • BAT_LOADTODAY : Battery Load Today
  • +
  • BAT_LOADTOTAL : Battery Load Total
  • +
  • CLASS / device_class : Wechselrichter Klasse
  • +
  • PACMAX1 / pac_max_phase_1 : Nominelle Leistung in Ok Mode
  • +
  • PACMAX1_2 / pac_max_phase_1_2 : Maximale Leistung (für einige Wechselrichtertypen)
  • +
  • PACMAX2 / pac_max_phase_2 : Nominelle Leistung in Warning Mode
  • +
  • PACMAX3 / pac_max_phase_3 : Nominelle Leistung in Fault Mode
  • +
  • Serialnumber / serial_number : Wechselrichter Seriennummer
  • +
  • SPOT_ETODAY / etoday : Energie heute
  • +
  • SPOT_ETOTAL / etotal : Energie Insgesamt
  • +
  • SPOT_FEEDTM / feed-in_time : Einspeise-Stunden
  • +
  • SPOT_FREQ / grid_freq : Netz Frequenz
  • +
  • SPOT_CosPhi / coshhi : Verschiebungsfaktor
  • +
  • SPOT_IAC1 / phase_1_iac : Netz Strom phase L1
  • +
  • SPOT_IAC2 / phase_2_iac : Netz Strom phase L2
  • +
  • SPOT_IAC3 / phase_3_iac : Netz Strom phase L3
  • +
  • SPOT_IDC1 / string_1_idc : DC Strom Eingang 1
  • +
  • SPOT_IDC2 / string_2_idc : DC Strom Eingang 2
  • +
  • SPOT_OPERTM / operation_time : Betriebsstunden
  • +
  • SPOT_PAC1 / phase_1_pac : Leistung L1
  • +
  • SPOT_PAC2 / phase_2_pac : Leistung L2
  • +
  • SPOT_PAC3 / phase_3_pac : Leistung L3
  • +
  • SPOT_PACTOT / total_pac : Gesamtleistung
  • +
  • SPOT_PDC1 / string_1_pdc : DC Leistung Eingang 1
  • +
  • SPOT_PDC2 / string_2_pdc : DC Leistung Eingang 2
  • +
  • SPOT_UAC1 / phase_1_uac : Netz Spannung phase L1
  • +
  • SPOT_UAC2 / phase_2_uac : Netz Spannung phase L2
  • +
  • SPOT_UAC3 / phase_3_uac : Netz Spannung phase L3
  • +
  • SPOT_UAC1_2 / phase_1_2_uac : Netz Spannung phase L1-L2
  • +
  • SPOT_UAC2_3 / phase_2_3_uac : Netz Spannung phase L2-L3
  • +
  • SPOT_UAC3_1 / phase_3_1_uac : Netz Spannung phase L3-L1
  • +
  • SPOT_UDC1 / string_1_udc : DC Spannung Eingang 1
  • +
  • SPOT_UDC2 / string_2_udc : DC Spannung Eingang 2
  • +
  • SUSyID / susyid : Wechselrichter SUSyID
  • +
  • INV_TEMP / device_temperature : Wechselrichter Temperatur
  • +
  • INV_TYPE / device_type : Wechselrichter Typ
  • +
  • POWER_IN / power_in : Akku Ladeleistung
  • +
  • POWER_OUT / power_out : Akku Entladeleistung
  • +
  • INV_GRIDRELAY / gridrelay_status : Netz Relais Status
  • +
  • INV_STATUS / device_status : Wechselrichter Status
  • +
  • opertime_start : Beginn Aktivzeit des Wechselrichters entsprechend des ermittelten Sonnenaufgangs mit Berücksichtigung des + Attributs "offset" (wenn gesetzt)
  • +
  • opertime_stop : Ende Aktivzeit des Wechselrichters entsprechend des ermittelten Sonnenuntergangs mit Berücksichtigung des + Attributs "offset" (wenn gesetzt)
  • +
  • modulstate : zeigt den aktuellen Modulstatus "normal" oder "sleep" falls der Wechselrichter nicht abgefragt wird.
  • +
  • avg_power_lastminutes_05 : durchschnittlich erzeugte Leistung der letzten 5 Minuten.
  • +
  • avg_power_lastminutes_10 : durchschnittlich erzeugte Leistung der letzten 10 Minuten.
  • +
  • avg_power_lastminutes_15 : durchschnittlich erzeugte Leistung der letzten 15 Minuten.
  • +
  • inverter_processing_time : verbrauchte Zeit um den Wechelrichter abzufragen.
  • +
  • background_processing_time : gesamte durch den Hintergrundprozess (BlockingCall) verbrauchte Zeit.
  • + +
+

+ +=end html_DE + +=for :application/json;q=META.json 76_SMAInverter.pm +{ + "abstract": "Integration of SMA Inverters over it's Speedwire (=Ethernet) Interface", + "x_lang": { + "de": { + "abstract": "Integration von SMA Wechselrichtern ueber Speedwire (=Ethernet) Interface" + } + }, + "keywords": [ + "SMA", + "photovoltaics", + "PV", + "inverter" + ], + "version": "v1.1.1", + "release_status": "stable", + "author": [ + "Thomas Schoedl (sct14675)", + "Heiko Maaz ", + null + ], + "x_fhem_maintainer": [ + "sct14675", + "DS_Starter", + null + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.014, + "IO::Socket::INET": 0, + "DateTime": 0, + "Time::HiRes": 0, + "Blocking": 0, + "Time::Local": 0 + }, + "recommends": { + "FHEM::Meta": 0 + }, + "suggests": { + } + } + } +} +=end :application/json;q=META.json + +=cut