2021-06-23 14:19:30 +00:00
#################################################################################################################
# $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 = (
2021-07-02 14:59:59 +00:00
"2.17.0" = > "01.07.2021 fix ETOTAL/LOADTOTAL bug" ,
"2.16.1" = > "21.06.2021 hide unavailable data" ,
"2.16.0" = > "21.06.2021 AC Voltage and AC Curren read fixed, read CosPhi included " ,
"2.15.1" = > "18.06.2021 SBS1.5, SBS2.0, SBS2.5 read battery data included " ,
"2.15.0" = > "14.06.2021 SBS5.0-10, SBS6.0-10, SBS3.7-10 read battery data included " ,
2021-06-23 14:19:30 +00:00
"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 <name> SMAInverter <inv-userpwd> <inv-hostname/inv-ip > " 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 ) {
2021-07-02 14:59:59 +00:00
push ( @ row_array , "etotal " . ( $ inv_SPOT_ETOTAL / 1000 ) . "\n" ) if ( $ inv_SPOT_ETOTAL ne "-" ) ;
push ( @ row_array , "etoday " . ( $ inv_SPOT_ETODAY / 1000 ) . "\n" ) if ( $ inv_SPOT_ETODAY ne "-" ) ;
2021-06-23 14:19:30 +00:00
}
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 ) {
2021-07-02 14:59:59 +00:00
push ( @ row_array , "bat_loadtotal " . ( $ inv_BAT_LOADTOTAL / 1000 ) . "\n" ) if ( $ inv_BAT_LOADTOTAL ne "-" ) ;
push ( @ row_array , "bat_loadtoday " . ( $ inv_BAT_LOADTODAY / 1000 ) . "\n" ) if ( $ inv_BAT_LOADTODAY ne "-" ) ;
2021-06-23 14:19:30 +00:00
}
}
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 ) {
2021-07-02 14:59:59 +00:00
push ( @ row_array , "SPOT_ETOTAL " . $ inv_SPOT_ETOTAL . "\n" ) if ( $ inv_SPOT_ETOTAL ne "-" ) ;
push ( @ row_array , "SPOT_ETODAY " . $ inv_SPOT_ETODAY . "\n" ) if ( $ inv_SPOT_ETODAY ne "-" ) ;
2021-06-23 14:19:30 +00:00
}
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 ) {
2021-07-02 14:59:59 +00:00
push ( @ row_array , "BAT_LOADTOTAL " . $ inv_BAT_LOADTOTAL . "\n" ) if ( $ inv_BAT_LOADTOTAL ne "-" ) ;
push ( @ row_array , "BAT_LOADTODAY " . $ inv_BAT_LOADTODAY . "\n" ) if ( $ inv_BAT_LOADTODAY ne "-" ) ;
2021-06-23 14:19:30 +00:00
}
}
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\" !" ) ;
2021-07-02 14:59:59 +00:00
$ inv_SPOT_ETOTAL = "-" ;
2021-06-23 14:19:30 +00:00
}
if ( length ( $ data ) >= 82 ) {
$ inv_SPOT_ETODAY = unpack ( "V*" , substr ( $ data , 78 , 4 ) ) ;
}
2021-07-02 14:59:59 +00:00
elsif ( $ inv_SPOT_ETOTAL ne "-" ) {
2021-06-23 14:19:30 +00:00
# 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\" !" ) ;
2021-07-02 14:59:59 +00:00
$ inv_SPOT_ETODAY = "-" ;
2021-06-23 14:19:30 +00:00
}
}
2021-07-02 14:59:59 +00:00
else
{
$ inv_SPOT_ETODAY = "-" ;
}
2021-06-23 14:19:30 +00:00
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\" !" ;
2021-07-02 15:05:01 +00:00
$ inv_BAT_LOADTOTAL = "-" ;
2021-06-23 14:19:30 +00:00
}
if ( length ( $ data ) >= 82 ) {
$ inv_BAT_LOADTODAY = unpack ( "V*" , substr ( $ data , 78 , 4 ) ) ;
}
2021-07-02 15:05:01 +00:00
elsif ( $ inv_BAT_LOADTOTAL ne "-" ) {
2021-06-23 14:19:30 +00:00
# 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\" !" ;
2021-07-02 14:59:59 +00:00
$ inv_BAT_LOADTODAY = "-" ;
2021-06-23 14:19:30 +00:00
}
}
2021-07-02 14:59:59 +00:00
else
{
$ inv_BAT_LOADTODAY = "-" ;
}
2021-06-23 14:19:30 +00:00
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 {<Modul>->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
< a name = "SMAInverter" > </a>
<h3> SMAInverter </h3>
Module for the integration of a SMA Inverter over it ' s Speedwire ( = Ethernet ) Interface . <br>
Tested on Sunny Tripower 6000 TL - 20 and Sunny Island 4.4 with Speedwire / Webconnect Piggyback .
<br> <br>
Questions and discussions about this module you can find in the FHEM - Forum link : <br>
< a href = "https://forum.fhem.de/index.php/topic,56080.msg476525.html#msg476525" > 76 _SMAInverter . pm - Abfrage von SMA Wechselrichter </a> .
<br> <br>
<b> Requirements </b>
<br> <br>
This module requires:
<ul>
<li> Perl Module: IO::Socket:: INET ( apt - get install libio - socket - multicast - perl ) </li>
<li> Perl Module: Date:: Time ( apt - get install libdatetime - perl ) </li>
<li> Perl Module: Time:: HiRes </li>
<li> FHEM Module: 99 _SUNRISE_EL . pm </li>
<li> FHEM Module: Blocking . pm </li>
</ul>
<br>
<br>
<b> Definition </b>
<ul>
<code> define & lt ; name & gt ; SMAInverter & lt ; pin & gt ; & lt ; hostname /ip> </co de > <br>
<br>
<li> pin: password of the inverter . Default is 0000 . <br>
<b> inverter without webinterface: </b> The password for the inverter can be changed by "Sunny Explorer" Client Software <br>
<b> inverter with webinterface: </b> The password changed by the webinterface is also valid for the device definition . </li>
<li> hostname /ip: Hostname or IP-Adress of the inverter (or it's speedwire piggyback module).</ li >
<li> The Speedwire port is 9522 by default . A Firewall has to allow connection on this port if present ! </li>
</ul>
<b> Operation method </b>
<ul>
The module sends commands to the inverter and checks if they are supported by the inverter . <br>
In case of a positive answer the data is collected and displayed in the readings according to the detail - level . <br> <br>
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 < a href = "#SUNRISE_EL" > Commandref SUNRISE_EL </a> ) . <br> <br>
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 . <br> <br>
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 ) . <br> <br>
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" . <b> Note: </b> 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 ! <br> <br>
The retrieval of the inverter will be executed non - blocking . You can adjust the timeout value for this background process by attribute "timeout" . <br>
</ul>
<b> Get </b>
<br>
<ul>
<li> <b> get & lt ; name & gt ; data </b>
<br> <br>
The request of the inverter will be executed . Those possibility is especifically created for the "manual" operation
mode ( see attribute "mode" ) .
<br>
</li>
<br>
</ul>
<b> Attributes </b>
<ul>
< a name = "detail-level" > </a>
<li> <b> detail - level [ 0 | 1 | 2 ] </b> <br>
Defines the complexity of the generated readings . <br> <br>
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> 0 </td> <td> - only Power and Energy </td> </tr>
<tr> <td> 1 </td> <td> - as 0 , additional voltage and current </td> </tr>
<tr> <td> 2 </td> <td> - all values </td> </tr>
</table>
</ul>
</li>
<br>
< a name = "disable" > </a>
<li> <b> disable [ 1 | 0 ] </b> <br>
Deactivate / activate the module .
</li>
<br>
< a name = "interval" > </a>
<li> <b> interval </b> <br>
Request cycle in seconds . ( default: 60 )
</li>
<br>
< a name = "mode" > </a>
<li> <b> mode [ automatic | manual ] </b> <br>
The request mode of the inverter . ( default: automatic ) <br> <br>
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> automatic </td> <td> - the inverter will be polled regularly as defined by attribute "interval" </td> </tr>
<tr> <td> manual </td> <td> - query only by command "get <name> data" </td> </tr>
</table>
</ul>
</li>
<br>
< a name = "offset" > </a>
<li> <b> offset & lt ; 0 - 7200 & gt ; </b> <br>
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 .
</li>
<br>
< a name = "SBFSpotComp" > </a>
<li> <b> SBFSpotComp [ 1 | 0 ] </b> <br>
The reading names are created like the SBFSpot - style . ( default: 0 )
</li>
<br>
< a name = "showproctime" > </a>
<li> <b> showproctime [ 1 | 0 ] </b> <br>
Shows the processing time in background and the wasted time to retrieve inverter data . ( default: 0 )
</li>
<br>
< a name = "suppressSleep" > </a>
<li> <b> suppressSleep [ 1 | 0 ] </b> <br>
The sleep mode ( after sunset and before sunrise ) is deactivated and the inverter will be polled continuously . ( default: 0 )
</li>
<br>
< a name = "target-serial" > </a>
<li> <b> target - serial </b> <br>
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 )
</li>
<br>
< a name = "target-susyid" > </a>
<li> <b> target - susyid </b> <br>
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 )
</li>
<br>
< a name = "timeout" > </a>
<li> <b> timeout </b> <br>
Setup timeout of inverter data request in seconds . ( default 60 )
</li>
<br>
</ul>
<b> Readings </b>
<ul>
<li> <b> BAT_CYCLES / bat_cycles</ b > : Battery recharge cycles </li>
<li> <b> BAT_IDC [ A , B , C ] / bat_idc [A,B,C]</ b > : Battery Current [ A , B , C ] </li>
<li> <b> BAT_TEMP [ A , B , C ] / bat_temp [A,B,C]</ b > : Battery temperature [ A , B , C ] </li>
<li> <b> BAT_UDC [ A , B , C ] / bat_udc [A,B,C]</ b > : Battery Voltage [ A , B , C ] </li>
<li> <b> ChargeStatus / chargestatus</ b > : Battery Charge status </li>
<li> <b> BAT_LOADTODAY </b> : Battery Load Today </li>
<li> <b> BAT_LOADTOTAL </b> : Battery Load Total </li>
<li> <b> ChargeStatus / chargestatus</ b > : Battery Charge status </li>
<li> <b> CLASS / device_class</ b > : Inverter Class </li>
<li> <b> PACMAX1 / pac_max_phase_1</ b > : Nominal power in Ok Mode </li>
<li> <b> PACMAX1_2 / pac_max_phase_1_2</ b > : Maximum active power device ( Some inverters like SB3300 /SB1200) </ li >
<li> <b> PACMAX2 / pac_max_phase_2</ b > : Nominal power in Warning Mode </li>
<li> <b> PACMAX3 / pac_max_phase_3</ b > : Nominal power in Fault Mode </li>
<li> <b> Serialnumber / serial_number</ b > : Inverter Serialnumber </li>
<li> <b> SPOT_ETODAY / etoday</ b > : Today yield </li>
<li> <b> SPOT_ETOTAL / etotal</ b > : Total yield </li>
<li> <b> SPOT_FEEDTM / feed-in_time</ b > : Feed - in time </li>
<li> <b> SPOT_FREQ / grid_freq </ b > : Grid Frequency </li>
<li> <b> SPOT_CosPhi / coshhi </ b > : displacement factor </li>
<li> <b> SPOT_IAC1 / phase_1_iac</ b > : Grid current phase L1 </li>
<li> <b> SPOT_IAC2 / phase_2_iac</ b > : Grid current phase L2 </li>
<li> <b> SPOT_IAC3 / phase_3_iac</ b > : Grid current phase L3 </li>
<li> <b> SPOT_IDC1 / string_1_idc</ b > : DC current input </li>
<li> <b> SPOT_IDC2 / string_2_idc</ b > : DC current input </li>
<li> <b> SPOT_OPERTM / operation_time</ b > : Operation Time </li>
<li> <b> SPOT_PAC1 / phase_1_pac</ b > : Power L1 </li>
<li> <b> SPOT_PAC2 / phase_2_pac</ b > : Power L2 </li>
<li> <b> SPOT_PAC3 / phase_3_pac</ b > : Power L3 </li>
<li> <b> SPOT_PACTOT / total_pac</ b > : Total Power </li>
<li> <b> SPOT_PDC1 / string_1_pdc</ b > : DC power input 1 </li>
<li> <b> SPOT_PDC2 / string_2_pdc</ b > : DC power input 2 </li>
<li> <b> SPOT_UAC1 / phase_1_uac</ b > : Grid voltage phase L1 </li>
<li> <b> SPOT_UAC2 / phase_2_uac</ b > : Grid voltage phase L2 </li>
<li> <b> SPOT_UAC3 / phase_3_uac</ b > : Grid voltage phase L3 </li>
<li> <b> SPOT_UAC1_2 / phase_1_2_uac</ b > : Grid voltage phase L1 - L2 </li>
<li> <b> SPOT_UAC2_3 / phase_2_3_uac</ b > : Grid voltage phase L2 - L3 </li>
<li> <b> SPOT_UAC3_1 / phase_3_1_uac</ b > : Grid voltage phase L3 - L1 </li>
<li> <b> SPOT_UDC1 / string_1_udc</ b > : DC voltage input </li>
<li> <b> SPOT_UDC2 / string_2_udc</ b > : DC voltage input </li>
<li> <b> SUSyID / susyid</ b > : Inverter SUSyID </li>
<li> <b> INV_TEMP / device_temperature</ b > : Inverter temperature </li>
<li> <b> INV_TYPE / device_type</ b > : Inverter Type </li>
<li> <b> POWER_IN / power_in</ b > : Battery Charging power </li>
<li> <b> POWER_OUT / power_out</ b > : Battery Discharging power </li>
<li> <b> INV_GRIDRELAY / gridrelay_status</ b > : Grid Relay /Contactor Status </ li >
<li> <b> INV_STATUS / device_status</ b > : Inverter Status </li>
<li> <b> opertime_start </b> : Begin of iverter operating time corresponding the calculated time of sunrise with consideration of the
attribute "offset" ( if set ) </li>
<li> <b> opertime_stop </b> : End of iverter operating time corresponding the calculated time of sunrise with consideration of the
attribute "offset" ( if set ) </li>
<li> <b> modulstate </b> : shows the current module state "normal" or "sleep" if the inverter won ' t be requested at the time . </li>
<li> <b> avg_power_lastminutes_05 </b> : average power of the last 5 minutes . </li>
<li> <b> avg_power_lastminutes_10 </b> : average power of the last 10 minutes . </li>
<li> <b> avg_power_lastminutes_15 </b> : average power of the last 15 minutes . </li>
<li> <b> inverter_processing_time </b> : wasted time to retrieve the inverter data </li>
<li> <b> background_processing_time </b> : total wasted time by background process ( BlockingCall ) </li>
</ul>
<br> <br>
= end html
= begin html_DE
< a name = "SMAInverter" > </a>
<h3> SMAInverter </h3>
Modul zur Einbindung eines SMA Wechselrichters über Speedwire ( Ethernet ) . <br>
Getestet mit Sunny Tripower 6000 TL - 20 und Sunny Island 4.4 mit Speedwire / Webconnect Piggyback .
<br> <br>
Fragen und Diskussionen rund um dieses Modul finden sie im FHEM - Forum unter: <br>
< a href = "https://forum.fhem.de/index.php/topic,56080.msg476525.html#msg476525" > 76 _SMAInverter . pm - Abfrage von SMA Wechselrichter </a> .
<br> <br>
<b> Voraussetzungen </b>
<br> <br>
Dieses Modul benötigt:
<ul>
<li> Perl Modul: IO::Socket:: INET ( apt - get install libio - socket - multicast - perl ) </li>
<li> Perl Modul: Datetime ( apt - get install libdatetime - perl ) </li>
<li> Perl Modul: Time:: HiRes </li>
<li> FHEM Modul: 99 _SUNRISE_EL . pm </li>
<li> FHEM Modul: Blocking . pm </li>
</ul>
<br>
<br>
<b> Definition </b>
<ul>
<code> define & lt ; name & gt ; SMAInverter & lt ; pin & gt ; & lt ; hostname /ip></co de > <br>
<br>
<li> pin: Passwort des Wechselrichters . Default ist 0000 . <br>
<b> Wechselrichter ohne Webinterface: </b> Das Passwort kann über die Client Software "Sunny Explorer" geändert werden . <br>
<b> Wechselrichter mit Webinterface: </b> Das im Webinterface geänderte Passwort gilt auch für die Devicedefinition . </li>
<li> hostname /ip: Hostname oder IP-Adresse des Wechselrichters (bzw. dessen Speedwire Moduls mit Ethernetanschluss) </ li >
<li> Der Speedwire - Port ist 9522 . Dieser Port muss in der Firewall freigeschaltet sein ! </li>
</ul>
<b> Arbeitsweise </b>
<ul>
Das Modul schickt Befehle an den Wechselrichter und überprüft , ob diese unterstützt werden . <br>
Bei einer positiven Antwort werden die Daten gesammelt und je nach Detail - Level in den Readings dargestellt . <br> <br>
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 < a href = "#SUNRISE_EL" > Commandref SUNRISE_EL </a> ) <br> <br>
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 . <br> <br>
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 ) . <br> <br>
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 . <b> Hinweis: </b> Um eine korrekte Berechnung zu
ermöglichen , sollte auch im Betriebsmodus "manual" das tatsächliche Abfrageinterval im Attribute "interval" hinterlegt werden ! <br> <br>
Die Abfrage des Wechselrichters wird non - blocking ausgeführt . Der Timeoutwert für diesen Hintergrundprozess kann mit dem Attribut "timeout" eingestellt werden . <br>
</ul>
<b> Get </b>
<br>
<ul>
<li> <b> get & lt ; name & gt ; data </b>
<br> <br>
Die Datenabfrage des Wechselrichters wird ausgeführt . Diese Möglichkeit ist speziell für den Betriebsmodus "manual"
vorgesehen ( siehe Attribut "mode" ) .
<br>
</li>
<br>
</ul>
<b> Attribute </b>
<br<br>
<ul>
< a name = "detail-level" > </a>
<li> <b> detail - level [ 0 | 1 | 2 ] </b> <br>
Legt den Umfang der ausgegebenen Readings fest . <br> <br>
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> 0 </td> <td> - nur Leistung und Energie </td> </tr>
<tr> <td> 1 </td> <td> - wie 0 , zusätzlich Strom und Spannung </td> </tr>
<tr> <td> 2 </td> <td> - alle Werte </td> </tr>
</table>
</ul>
</li>
<br>
< a name = "disable" > </a>
<li> <b> disable [ 1 | 0 ] </b> <br>
Deaktiviert / aktiviert das Modul .
</li>
<br>
< a name = "interval" > </a>
<li> <b> interval </b> <br>
Abfrageinterval in Sekunden . ( default: 60 )
</li>
<br>
< a name = "mode" > </a>
<li> <b> mode [ automatic | manual ] </b> <br>
Abfragemodus des Wechselrichters . ( default: automatic ) <br> <br>
<ul>
<table>
<colgroup> < col width = 10 % > < col width = 90 % > < / colgroup >
<tr> <td> automatic </td> <td> - die Wechselrichterwerte werden im eingestellten Interval abgefragt ( Attribut "interval" ) </td> </tr>
<tr> <td> manual </td> <td> - Abfrage nur mit "get <name> data" </td> </tr>
</table>
</ul>
</li>
<br>
< a name = "offset" > </a>
<li> <b> offset & lt ; 0 - 7200 & gt ; </b> <br>
Zeit in Sekunden , um die der reale Sonnenaufgang vorgezogen bzw . reale Sonnenuntergang verzögert wird .
Dadurch wird die effektive Aktivzeit des Moduls erweitert .
</li>
<br>
< a name = "SBFSpotComp" > </a>
<li> <b> SBFSpotComp [ 1 | 0 ] </b> <br>
Die Readingnamen werden kompatibel zu SBFSpot - Ausgaben erzeugt . ( default: 0 )
</li>
<br>
< a name = "showproctime" > </a>
<li> <b> showproctime [ 1 | 0 ] </b> <br>
Zeigt die für den Hintergrundprozess und die Abfrage des Wechselrichter verbrauchte Zeit . ( default: 0 )
</li>
<br>
< a name = "suppressSleep" > </a>
<li> <b> suppressSleep [ 1 | 0 ] </b> <br>
Der Schlafmodus ( nach Sonnenuntergang und vor Sonnenaufgang ) wird ausgeschaltet und der WR abgefragt . ( default: 0 )
</li>
<br>
< a name = "target-serial" > </a>
<li> <b> target - serial </b> <br>
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 )
</li>
<br>
< a name = "target-susyid" > </a>
<li> <b> target - susyid </b> <br>
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 )
</li>
<br>
< a name = "timeout" > </a>
<li> <b> timeout </b> <br>
Einstellung des timeout für die Wechselrichterabfrage in Sekunden . ( default 60 )
</li>
<br>
</ul>
<b> Readings </b>
<ul>
<li> <b> BAT_CYCLES / bat_cycles</ b > : Akku Ladezyklen </li>
<li> <b> BAT_IDC [ A , B , C ] / bat_idc [A,B,C]</ b > : Akku Strom [ A , B , C ] </li>
<li> <b> BAT_TEMP [ A , B , C ] / bat_temp [A,B,C]</ b > : Akku Temperatur [ A , B , C ] </li>
<li> <b> BAT_UDC [ A , B , C ] / bat_udc [A,B,C]</ b > : Akku Spannung [ A , B , C ] </li>
<li> <b> ChargeStatus / chargestatus</ b > : Akku Ladestand </li>
<li> <b> BAT_LOADTODAY </b> : Battery Load Today </li>
<li> <b> BAT_LOADTOTAL </b> : Battery Load Total </li>
<li> <b> CLASS / device_class</ b > : Wechselrichter Klasse </li>
<li> <b> PACMAX1 / pac_max_phase_1</ b > : Nominelle Leistung in Ok Mode </li>
<li> <b> PACMAX1_2 / pac_max_phase_1_2</ b > : Maximale Leistung ( für einige Wechselrichtertypen ) </li>
<li> <b> PACMAX2 / pac_max_phase_2</ b > : Nominelle Leistung in Warning Mode </li>
<li> <b> PACMAX3 / pac_max_phase_3</ b > : Nominelle Leistung in Fault Mode </li>
<li> <b> Serialnumber / serial_number</ b > : Wechselrichter Seriennummer </li>
<li> <b> SPOT_ETODAY / etoday</ b > : Energie heute </li>
<li> <b> SPOT_ETOTAL / etotal</ b > : Energie Insgesamt </li>
<li> <b> SPOT_FEEDTM / feed-in_time</ b > : Einspeise - Stunden </li>
<li> <b> SPOT_FREQ / grid_freq </ b > : Netz Frequenz </li>
<li> <b> SPOT_CosPhi / coshhi </ b > : Verschiebungsfaktor </li>
<li> <b> SPOT_IAC1 / phase_1_iac</ b > : Netz Strom phase L1 </li>
<li> <b> SPOT_IAC2 / phase_2_iac</ b > : Netz Strom phase L2 </li>
<li> <b> SPOT_IAC3 / phase_3_iac</ b > : Netz Strom phase L3 </li>
<li> <b> SPOT_IDC1 / string_1_idc</ b > : DC Strom Eingang 1 </li>
<li> <b> SPOT_IDC2 / string_2_idc</ b > : DC Strom Eingang 2 </li>
<li> <b> SPOT_OPERTM / operation_time</ b > : Betriebsstunden </li>
<li> <b> SPOT_PAC1 / phase_1_pac</ b > : Leistung L1 </li>
<li> <b> SPOT_PAC2 / phase_2_pac</ b > : Leistung L2 </li>
<li> <b> SPOT_PAC3 / phase_3_pac</ b > : Leistung L3 </li>
<li> <b> SPOT_PACTOT / total_pac</ b > : Gesamtleistung </li>
<li> <b> SPOT_PDC1 / string_1_pdc</ b > : DC Leistung Eingang 1 </li>
<li> <b> SPOT_PDC2 / string_2_pdc</ b > : DC Leistung Eingang 2 </li>
<li> <b> SPOT_UAC1 / phase_1_uac</ b > : Netz Spannung phase L1 </li>
<li> <b> SPOT_UAC2 / phase_2_uac</ b > : Netz Spannung phase L2 </li>
<li> <b> SPOT_UAC3 / phase_3_uac</ b > : Netz Spannung phase L3 </li>
<li> <b> SPOT_UAC1_2 / phase_1_2_uac</ b > : Netz Spannung phase L1 - L2 </li>
<li> <b> SPOT_UAC2_3 / phase_2_3_uac</ b > : Netz Spannung phase L2 - L3 </li>
<li> <b> SPOT_UAC3_1 / phase_3_1_uac</ b > : Netz Spannung phase L3 - L1 </li>
<li> <b> SPOT_UDC1 / string_1_udc</ b > : DC Spannung Eingang 1 </li>
<li> <b> SPOT_UDC2 / string_2_udc</ b > : DC Spannung Eingang 2 </li>
<li> <b> SUSyID / susyid</ b > : Wechselrichter SUSyID </li>
<li> <b> INV_TEMP / device_temperature</ b > : Wechselrichter Temperatur </li>
<li> <b> INV_TYPE / device_type</ b > : Wechselrichter Typ </li>
<li> <b> POWER_IN / power_in</ b > : Akku Ladeleistung </li>
<li> <b> POWER_OUT / power_out</ b > : Akku Entladeleistung </li>
<li> <b> INV_GRIDRELAY / gridrelay_status</ b > : Netz Relais Status </li>
<li> <b> INV_STATUS / device_status</ b > : Wechselrichter Status </li>
<li> <b> opertime_start </b> : Beginn Aktivzeit des Wechselrichters entsprechend des ermittelten Sonnenaufgangs mit Berücksichtigung des
Attributs "offset" ( wenn gesetzt ) </li>
<li> <b> opertime_stop </b> : Ende Aktivzeit des Wechselrichters entsprechend des ermittelten Sonnenuntergangs mit Berücksichtigung des
Attributs "offset" ( wenn gesetzt ) </li>
<li> <b> modulstate </b> : zeigt den aktuellen Modulstatus "normal" oder "sleep" falls der Wechselrichter nicht abgefragt wird . </li>
<li> <b> avg_power_lastminutes_05 </b> : durchschnittlich erzeugte Leistung der letzten 5 Minuten . </li>
<li> <b> avg_power_lastminutes_10 </b> : durchschnittlich erzeugte Leistung der letzten 10 Minuten . </li>
<li> <b> avg_power_lastminutes_15 </b> : durchschnittlich erzeugte Leistung der letzten 15 Minuten . </li>
<li> <b> inverter_processing_time </b> : verbrauchte Zeit um den Wechelrichter abzufragen . </li>
<li> <b> background_processing_time </b> : gesamte durch den Hintergrundprozess ( BlockingCall ) verbrauchte Zeit . </li>
</ul>
<br> <br>
= 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"
] ,
2021-07-02 14:59:59 +00:00
"version" : "v2.16.1" ,
2021-06-23 14:19:30 +00:00
"release_status" : "stable" ,
"author" : [
2021-07-02 14:59:59 +00:00
"Maximilian Paries" ,
2021-06-23 14:19:30 +00:00
"Heiko Maaz <heiko.maaz@t-online.de>" ,
null
] ,
"x_fhem_maintainer" : [
2021-07-02 14:59:59 +00:00
"MadMax" ,
2021-06-23 14:19:30 +00:00
"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