mirror of
https://github.com/fhem/fhem-mirror.git
synced 2024-11-22 16:09:49 +00:00
de957d774e
git-svn-id: https://svn.fhem.de/fhem/trunk@26523 2b470e98-0d58-463d-a4d8-8e2adae1ed80
2533 lines
117 KiB
Perl
2533 lines
117 KiB
Perl
#################################################################################################################
|
|
# $Id: 76_SMAInverter.pm 24737 2021-07-12 16:46:51Z MadMax $
|
|
#################################################################################################################
|
|
#
|
|
# Copyright notice
|
|
#
|
|
# Published according Creative Commons : Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
|
|
# Details: https://creativecommons.org/licenses/by-nc-sa/3.0/
|
|
#
|
|
# Credits:
|
|
# - based on 77_SMASTP.pm by Volker Kettenbach with following credits:
|
|
# - based on an Idea by SpenZerX and HDO
|
|
# - Waldmensch for various improvements
|
|
# - sbfspot (https://sbfspot.codeplex.com/)
|
|
# - rewritten by Thomas Schoedl (sct14675) with inputs from Volker, waldmensch and DS_Starter
|
|
#
|
|
# Description:
|
|
# This is an FHEM-Module for SMA Inverters.
|
|
#
|
|
#################################################################################################################
|
|
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
eval "use IO::Socket::INET;1" or my $MissModulSocket = "IO::Socket::INET";
|
|
eval "use DateTime;1" or my $MissModulDateTime = "DateTime";
|
|
use Time::HiRes qw(gettimeofday tv_interval);
|
|
use Blocking;
|
|
use Time::Local;
|
|
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1;
|
|
|
|
# Versions History by DS_Starter
|
|
our %SMAInverter_vNotesIntern = (
|
|
"2.17.1" => "12.07.2021 fix ETOTAL/LOADTOTAL bug",
|
|
"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 ",
|
|
"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) {
|
|
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 "-");
|
|
}
|
|
if($sup_SpotDCPower) {
|
|
push(@row_array, "string_1_pdc ".sprintf("%.3f",$inv_SPOT_PDC1/1000)."\n");
|
|
push(@row_array, "string_2_pdc ".sprintf("%.3f",$inv_SPOT_PDC2/1000)."\n");
|
|
}
|
|
if($sup_SpotACPower) {
|
|
push(@row_array, "phase_1_pac ".sprintf("%.3f",$inv_SPOT_PAC1/1000)."\n") if ($inv_SPOT_PAC1 ne "-");
|
|
push(@row_array, "phase_2_pac ".sprintf("%.3f",$inv_SPOT_PAC2/1000)."\n") if ($inv_SPOT_PAC2 ne "-");
|
|
push(@row_array, "phase_3_pac ".sprintf("%.3f",$inv_SPOT_PAC3/1000)."\n") if ($inv_SPOT_PAC3 ne "-");
|
|
}
|
|
if($sup_SpotACTotalPower) {
|
|
push(@row_array, "total_pac ".sprintf("%.3f",$inv_SPOT_PACTOT/1000)."\n");
|
|
push(@row_array, "state ".sprintf("%.3f",$inv_SPOT_PACTOT/1000)."\n");
|
|
}
|
|
if($sup_ChargeStatus) {
|
|
push(@row_array, "chargestatus ".$inv_ChargeStatus."\n");
|
|
}
|
|
|
|
if($inv_CLASS && $inv_CLASS eq 8007 && defined($inv_SPOT_PACTOT)) { # V2.10.1 28.04.2019
|
|
if($inv_SPOT_PACTOT < 0) {
|
|
push(@row_array, "power_out "."0"."\n");
|
|
push(@row_array, "power_in ".(-1 * $inv_SPOT_PACTOT)."\n");
|
|
}
|
|
else {
|
|
push(@row_array, "power_out ".$inv_SPOT_PACTOT."\n");
|
|
push(@row_array, "power_in "."0"."\n");
|
|
}
|
|
}
|
|
|
|
if($detail_level > 0) {
|
|
# For Detail Level 1
|
|
if($sup_SpotDCVoltage) {
|
|
push(@row_array, "string_1_udc ".sprintf("%.2f",$inv_SPOT_UDC1)."\n");
|
|
push(@row_array, "string_2_udc ".sprintf("%.2f",$inv_SPOT_UDC2)."\n");
|
|
push(@row_array, "string_1_idc ".sprintf("%.3f",$inv_SPOT_IDC1)."\n");
|
|
push(@row_array, "string_2_idc ".sprintf("%.3f",$inv_SPOT_IDC2)."\n");
|
|
}
|
|
if($sup_SpotACVoltage) {
|
|
push(@row_array, "phase_1_uac ".sprintf("%.2f",$inv_SPOT_UAC1)."\n") if ($inv_SPOT_UAC1 ne "-");
|
|
push(@row_array, "phase_2_uac ".sprintf("%.2f",$inv_SPOT_UAC2)."\n") if ($inv_SPOT_UAC2 ne "-");
|
|
push(@row_array, "phase_3_uac ".sprintf("%.2f",$inv_SPOT_UAC3)."\n") if ($inv_SPOT_UAC3 ne "-");
|
|
push(@row_array, "phase_1_2_uac ".sprintf("%.3f",$inv_SPOT_UAC1_2)."\n") if ($inv_SPOT_UAC1_2 ne "-");
|
|
push(@row_array, "phase_2_3_uac ".sprintf("%.3f",$inv_SPOT_UAC2_3)."\n") if ($inv_SPOT_UAC2_3 ne "-");
|
|
push(@row_array, "phase_3_1_uac ".sprintf("%.3f",$inv_SPOT_UAC3_1)."\n") if ($inv_SPOT_UAC3_1 ne "-");
|
|
push(@row_array, "cosphi ".sprintf("%.3f",$inv_SPOT_CosPhi)."\n") if ($inv_SPOT_CosPhi ne "-");
|
|
}
|
|
if($sup_SpotACCurrent) {
|
|
push(@row_array, "phase_1_iac ".sprintf("%.2f",$inv_SPOT_IAC1)."\n") if ($inv_SPOT_IAC1 ne "-");
|
|
push(@row_array, "phase_2_iac ".sprintf("%.2f",$inv_SPOT_IAC2)."\n") if ($inv_SPOT_IAC2 ne "-");
|
|
push(@row_array, "phase_3_iac ".sprintf("%.2f",$inv_SPOT_IAC3)."\n") if ($inv_SPOT_IAC3 ne "-");
|
|
}
|
|
if($sup_BatteryInfo || $sup_BatteryInfo_2) {
|
|
push(@row_array, "bat_udc ".$inv_BAT_UDC."\n");
|
|
push(@row_array, "bat_idc ".$inv_BAT_IDC."\n");
|
|
}
|
|
if($sup_BatteryInfo_UDC) {
|
|
push(@row_array, "bat_udc ".$inv_BAT_UDC."\n");
|
|
push(@row_array, "bat_udc_a ".$inv_BAT_UDC_A."\n") if ($inv_BAT_UDC_A ne "-");
|
|
push(@row_array, "bat_udc_b ".$inv_BAT_UDC_B."\n") if ($inv_BAT_UDC_B ne "-");
|
|
push(@row_array, "bat_udc_c ".$inv_BAT_UDC_C."\n") if ($inv_BAT_UDC_C ne "-");
|
|
}
|
|
if($sup_BatteryInfo_IDC) {
|
|
push(@row_array, "bat_udc ".$inv_BAT_UDC."\n");
|
|
push(@row_array, "bat_idc_a ".$inv_BAT_IDC_A."\n") if ($inv_BAT_IDC_A ne "-");
|
|
push(@row_array, "bat_idc_b ".$inv_BAT_IDC_B."\n") if ($inv_BAT_IDC_B ne "-");
|
|
push(@row_array, "bat_idc_c ".$inv_BAT_IDC_C."\n") if ($inv_BAT_IDC_C ne "-");
|
|
}
|
|
if($sup_SpotBatteryLoad) {
|
|
push(@row_array, "bat_loadtotal ".($inv_BAT_LOADTOTAL/1000)."\n") if ($inv_BAT_LOADTOTAL ne "-");
|
|
push(@row_array, "bat_loadtoday ".($inv_BAT_LOADTODAY/1000)."\n") if ($inv_BAT_LOADTODAY ne "-");
|
|
}
|
|
}
|
|
|
|
if($detail_level > 1) {
|
|
# For Detail Level 2
|
|
if($sup_BatteryInfo || $sup_BatteryInfo_2) {
|
|
push(@row_array, "bat_temp ".$inv_BAT_TEMP."\n");
|
|
}
|
|
if($sup_BatteryInfo) {
|
|
push(@row_array, "bat_cycles ".$inv_BAT_CYCLES."\n");
|
|
}
|
|
if($sup_BatteryInfo_TEMP) {
|
|
push(@row_array, "bat_temp ".$inv_BAT_TEMP."\n");
|
|
push(@row_array, "bat_temp_a ".$inv_BAT_TEMP_A."\n") if ($inv_BAT_TEMP_A ne "-");
|
|
push(@row_array, "bat_temp_b ".$inv_BAT_TEMP_B."\n") if ($inv_BAT_TEMP_B ne "-");
|
|
push(@row_array, "bat_temp_c ".$inv_BAT_TEMP_C."\n") if ($inv_BAT_TEMP_C ne "-");
|
|
}
|
|
if($sup_SpotGridFrequency) {
|
|
push(@row_array, "grid_freq ".sprintf("%.2f",$inv_SPOT_FREQ)."\n");
|
|
}
|
|
if($sup_TypeLabel) {
|
|
push(@row_array, "device_type ".SMAInverter_devtype($inv_TYPE)."\n");
|
|
push(@row_array, "device_class ".SMAInverter_classtype($inv_CLASS)."\n");
|
|
push(@row_array, "susyid ".$inv_susyid." - SN: ".$inv_serial."\n") if($inv_susyid && $inv_serial);
|
|
push(@row_array, "device_name "."SN: ".$inv_serial."\n") if($inv_serial);
|
|
push(@row_array, "serial_number ".$inv_serial."\n") if($inv_serial);
|
|
}
|
|
if($sup_MaxACPower) {
|
|
push(@row_array, "pac_max_phase_1 ".$inv_PACMAX1."\n");
|
|
push(@row_array, "pac_max_phase_2 ".$inv_PACMAX2."\n");
|
|
push(@row_array, "pac_max_phase_3 ".$inv_PACMAX3."\n");
|
|
}
|
|
if($sup_MaxACPower2) {
|
|
push(@row_array, "pac_max_phase_1_2 ".$inv_PACMAX1_2."\n");
|
|
}
|
|
if($sup_InverterTemperature) {
|
|
push(@row_array, "device_temperature ".sprintf("%.1f",$inv_TEMP)."\n");
|
|
}
|
|
if($sup_OperationTime) {
|
|
push(@row_array, "feed-in_time ".$inv_SPOT_FEEDTM."\n");
|
|
push(@row_array, "operation_time ".$inv_SPOT_OPERTM."\n");
|
|
}
|
|
if($sup_GridRelayStatus) {
|
|
push(@row_array, "gridrelay_status ".SMAInverter_StatusText($inv_GRIDRELAY)."\n");
|
|
}
|
|
if($sup_DeviceStatus) {
|
|
push(@row_array, "device_status ".SMAInverter_StatusText($inv_STATUS)."\n");
|
|
}
|
|
}
|
|
|
|
}
|
|
else { # kein SBFSpot Compatibility Mode
|
|
if($sup_EnergyProduction) {
|
|
push(@row_array, "SPOT_ETOTAL ".$inv_SPOT_ETOTAL."\n") if ($inv_SPOT_ETOTAL ne "-");
|
|
push(@row_array, "SPOT_ETODAY ".$inv_SPOT_ETODAY."\n") if ($inv_SPOT_ETODAY ne "-");
|
|
}
|
|
if($sup_SpotDCPower) {
|
|
push(@row_array, "SPOT_PDC1 ".$inv_SPOT_PDC1."\n");
|
|
push(@row_array, "SPOT_PDC2 ".$inv_SPOT_PDC2."\n");
|
|
}
|
|
if($sup_SpotACPower) {
|
|
push(@row_array, "SPOT_PAC1 ".$inv_SPOT_PAC1."\n") if ($inv_SPOT_PAC1 ne "-");
|
|
push(@row_array, "SPOT_PAC2 ".$inv_SPOT_PAC2."\n") if ($inv_SPOT_PAC2 ne "-");
|
|
push(@row_array, "SPOT_PAC3 ".$inv_SPOT_PAC3."\n") if ($inv_SPOT_PAC3 ne "-");
|
|
}
|
|
if($sup_SpotACTotalPower) {
|
|
push(@row_array, "SPOT_PACTOT ".$inv_SPOT_PACTOT."\n");
|
|
push(@row_array, "state ".$inv_SPOT_PACTOT."\n");
|
|
}
|
|
if($sup_ChargeStatus) {
|
|
push(@row_array, "ChargeStatus ".$inv_ChargeStatus."\n");
|
|
}
|
|
if($inv_CLASS && $inv_CLASS eq 8007 && defined($inv_SPOT_PACTOT)) { # V2.10.1 28.04.2019
|
|
if($inv_SPOT_PACTOT < 0) {
|
|
push(@row_array, "POWER_OUT "."0"."\n");
|
|
push(@row_array, "POWER_IN ".(-1 * $inv_SPOT_PACTOT)."\n");
|
|
}
|
|
else {
|
|
push(@row_array, "POWER_OUT ".$inv_SPOT_PACTOT."\n");
|
|
push(@row_array, "POWER_IN "."0"."\n");
|
|
}
|
|
}
|
|
if($detail_level > 0) {
|
|
# For Detail Level 1
|
|
if($sup_SpotDCVoltage) {
|
|
push(@row_array, "SPOT_UDC1 ".$inv_SPOT_UDC1."\n");
|
|
push(@row_array, "SPOT_UDC2 ".$inv_SPOT_UDC2."\n");
|
|
push(@row_array, "SPOT_IDC1 ".$inv_SPOT_IDC1."\n");
|
|
push(@row_array, "SPOT_IDC2 ".$inv_SPOT_IDC2."\n");
|
|
}
|
|
if($sup_SpotACVoltage) {
|
|
push(@row_array, "SPOT_UAC1 ".$inv_SPOT_UAC1."\n") if ($inv_SPOT_UAC1 ne "-");
|
|
push(@row_array, "SPOT_UAC2 ".$inv_SPOT_UAC2."\n") if ($inv_SPOT_UAC2 ne "-");
|
|
push(@row_array, "SPOT_UAC3 ".$inv_SPOT_UAC3."\n") if ($inv_SPOT_UAC3 ne "-");
|
|
push(@row_array, "SPOT_UAC1_2 ".sprintf("%.3f",$inv_SPOT_UAC1_2)."\n") if ($inv_SPOT_UAC1_2 ne "-");
|
|
push(@row_array, "SPOT_UAC2_3 ".sprintf("%.3f",$inv_SPOT_UAC2_3)."\n") if ($inv_SPOT_UAC2_3 ne "-");
|
|
push(@row_array, "SPOT_UAC3_1 ".sprintf("%.3f",$inv_SPOT_UAC3_1)."\n") if ($inv_SPOT_UAC3_1 ne "-");
|
|
push(@row_array, "SPOT_CosPhi ".sprintf("%.3f",$inv_SPOT_CosPhi)."\n") if ($inv_SPOT_CosPhi ne "-");
|
|
}
|
|
if($sup_SpotACCurrent) {
|
|
push(@row_array, "SPOT_IAC1 ".sprintf("%.2f",$inv_SPOT_IAC1)."\n") if ($inv_SPOT_IAC1 ne "-");
|
|
push(@row_array, "SPOT_IAC2 ".sprintf("%.2f",$inv_SPOT_IAC2)."\n") if ($inv_SPOT_IAC2 ne "-");
|
|
push(@row_array, "SPOT_IAC3 ".sprintf("%.2f",$inv_SPOT_IAC3)."\n") if ($inv_SPOT_IAC3 ne "-");
|
|
}
|
|
if($sup_BatteryInfo || $sup_BatteryInfo_2) {
|
|
push(@row_array, "BAT_UDC ". $inv_BAT_UDC."\n");
|
|
push(@row_array, "BAT_IDC ". $inv_BAT_IDC."\n");
|
|
}
|
|
if($sup_BatteryInfo_UDC) {
|
|
push(@row_array, "BAT_UDC ". $inv_BAT_UDC."\n");
|
|
push(@row_array, "BAT_UDC_A ".$inv_BAT_UDC_A."\n") if ($inv_BAT_UDC_A ne "-");
|
|
push(@row_array, "BAT_UDC_B ".$inv_BAT_UDC_B."\n") if ($inv_BAT_UDC_B ne "-");
|
|
push(@row_array, "BAT_UDC_C ".$inv_BAT_UDC_C."\n") if ($inv_BAT_UDC_C ne "-");
|
|
}
|
|
if($sup_BatteryInfo_IDC) {
|
|
push(@row_array, "BAT_IDC ". $inv_BAT_IDC."\n");
|
|
push(@row_array, "BAT_IDC_A ".$inv_BAT_IDC_A."\n") if ($inv_BAT_IDC_A ne "-");
|
|
push(@row_array, "BAT_IDC_B ".$inv_BAT_IDC_B."\n") if ($inv_BAT_IDC_B ne "-");
|
|
push(@row_array, "BAT_IDC_C ".$inv_BAT_IDC_C."\n") if ($inv_BAT_IDC_C ne "-");
|
|
}
|
|
if($sup_SpotBatteryLoad) {
|
|
push(@row_array, "BAT_LOADTOTAL ".$inv_BAT_LOADTOTAL."\n") if ($inv_BAT_LOADTOTAL ne "-");
|
|
push(@row_array, "BAT_LOADTODAY ".$inv_BAT_LOADTODAY."\n") if ($inv_BAT_LOADTODAY ne "-");
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
if(($inv_SPOT_ETOTAL eq -2147483648) || ($inv_SPOT_ETOTAL eq 0xFFFFFFFF) || $inv_SPOT_ETOTAL <= 0) {$inv_SPOT_ETOTAL = "-"; }
|
|
|
|
|
|
}
|
|
else {
|
|
Log3 ($name, 3, "$name - WARNING - ETOTAL wasn't deliverd ... set it to \"0\" !");
|
|
$inv_SPOT_ETOTAL = "-";
|
|
}
|
|
|
|
if (length($data) >= 82) {
|
|
$inv_SPOT_ETODAY = unpack("V*", substr ($data, 78, 4));
|
|
}
|
|
elsif($inv_SPOT_ETOTAL ne "-") {
|
|
# ETODAY wurde vom WR nicht geliefert, es wird versucht ihn zu berechnen
|
|
Log3 ($name, 3, "$name - ETODAY wasn't delivered from inverter, try to calculate it ...");
|
|
my $etotold = ReadingsNum($name, ".etotal_yesterday", 0);
|
|
|
|
if($etotold && $inv_SPOT_ETOTAL > $etotold) {
|
|
$inv_SPOT_ETODAY = $inv_SPOT_ETOTAL - $etotold;
|
|
Log3 ($name, 3, "$name - ETODAY calculated successfully !");
|
|
}
|
|
else {
|
|
Log3 ($name, 3, "$name - WARNING - unable to calculate ETODAY ... set it to \"0\" !");
|
|
$inv_SPOT_ETODAY = "-";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$inv_SPOT_ETODAY = "-";
|
|
}
|
|
|
|
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));
|
|
|
|
if(($inv_BAT_LOADTOTAL eq -2147483648) || ($inv_BAT_LOADTOTAL eq 0xFFFFFFFF) || $inv_BAT_LOADTOTAL <= 0) {$inv_BAT_LOADTOTAL = "-"; }
|
|
}
|
|
else {
|
|
Log3 $name, 3, "$name - WARNING - BATTERYLOAD_TOTAL wasn't deliverd ... set it to \"0\" !";
|
|
$inv_BAT_LOADTOTAL = "-";
|
|
}
|
|
|
|
if (length($data) >= 82) {
|
|
$inv_BAT_LOADTODAY = unpack("V*", substr ($data, 78, 4));
|
|
}
|
|
elsif($inv_BAT_LOADTOTAL ne "-") {
|
|
# BATTERYLOAD_TODAY wurde vom WR nicht geliefert, es wird versucht ihn zu berechnen
|
|
Log3 $name, 3, "$name - BATTERYLOAD_TODAY wasn't delivered from inverter, try to calculate it ...";
|
|
my $bltotold = ReadingsNum($name, ".bat_loadtotal_yesterday", 0);
|
|
|
|
if($bltotold && $inv_BAT_LOADTOTAL > $bltotold) {
|
|
$inv_BAT_LOADTODAY = $inv_BAT_LOADTOTAL - $bltotold;
|
|
Log3 $name, 3, "$name - BATTERYLOAD_TODAY calculated successfully !";
|
|
}
|
|
else {
|
|
Log3 $name, 3, "$name - WARNING - unable to calculate BATTERYLOAD_TODAY ... set it to \"0\" !";
|
|
$inv_BAT_LOADTODAY = "-";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$inv_BAT_LOADTODAY = "-";
|
|
}
|
|
|
|
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 24737 2021-07-12 16:46:51Z MadMax $ 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 24737 2021-07-12 16:46:51Z MadMax $ 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 6000TL-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 <name> SMAInverter <pin> <hostname/ip> </code><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 <name> 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 <0 - 7200> </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 6000TL-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 <name> SMAInverter <pin> <hostname/ip></code><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 <name> 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 <0 - 7200> </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"
|
|
],
|
|
"version": "v2.16.1",
|
|
"release_status": "stable",
|
|
"author": [
|
|
"Maximilian Paries",
|
|
"Heiko Maaz <heiko.maaz@t-online.de>",
|
|
null
|
|
],
|
|
"x_fhem_maintainer": [
|
|
"MadMax",
|
|
"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
|