From ea0709f979dfa8897b6e5500e6425ed475dc2483 Mon Sep 17 00:00:00 2001 From: vsauer <> Date: Mon, 18 Jul 2016 08:17:50 +0000 Subject: [PATCH] 77_SMAEM.pm: Added support for SMA Energymeter 77_SMASTP.pm: Added support for SMA Sunny Tripower git-svn-id: https://svn.fhem.de/fhem/trunk@11812 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/77_SMAEM.pm | 505 +++++++++++++++++++++++++++++ fhem/FHEM/77_SMASTP.pm | 698 +++++++++++++++++++++++++++++++++++++++++ fhem/MAINTAINER.txt | 2 + 4 files changed, 1207 insertions(+) create mode 100644 fhem/FHEM/77_SMAEM.pm create mode 100644 fhem/FHEM/77_SMASTP.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index 628834beb..739a97a85 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - added: 77_SMASTP: Support for SMA Sunny Tripower Inverter + - added: 77_SMAEM: Support for SMA Energy Meter - added: 00_HMUARTLGW: new module for eQ-3 HomeMatic UART/LanGateway - change: 93_DbRep: function "diffValue" added, readingnames changed to ensure valid reading order, write "-" for reading-value if no diff --git a/fhem/FHEM/77_SMAEM.pm b/fhem/FHEM/77_SMAEM.pm new file mode 100644 index 000000000..0a4f44fc6 --- /dev/null +++ b/fhem/FHEM/77_SMAEM.pm @@ -0,0 +1,505 @@ +################################################################ +# $Id$ +# +# Copyright notice +# +# (c) 2016 Copyright: Volker Kettenbach +# e-mail: volker at kettenbach minus it dot de +# +# Credits: +# - DS_Starter (Heiko) for persistent readings +# and various improvements +# +# Description: +# This is an FHEM-Module for the SMA Energy Meter, +# a bidirectional energy meter/counter used in photovoltaics +# +# Requirements: +# This module requires: +# - Perl Module: IO::Socket::Multicast +# On a Debian (based) system, these requirements can be fullfilled by: +# - apt-get install install libio-socket-multicast-perl +# +# Origin: +# https://github.com/kettenbach-it/FHEM-SMA-Speedwire +# +################################################################ + +package main; + +use strict; +use warnings; +use bignum; + +use IO::Socket::Multicast; + +##################################### +sub +SMAEM_Initialize($) +{ + my ($hash) = @_; + + $hash->{ReadFn} = "SMAEM_Read"; + $hash->{DefFn} = "SMAEM_Define"; + $hash->{UndefFn} = "SMAEM_Undef"; + $hash->{DeleteFn} = "SMAEM_Delete"; + #$hash->{WriteFn} = "SMAEM_Write"; + #$hash->{ReadyFn} = "SMAEM_Ready"; + #$hash->{GetFn} = "SMAEM_Get"; + #$hash->{SetFn} = "SMAEM_Set"; + $hash->{AttrFn} = "SMAEM_Attr"; + $hash->{AttrList} = "interval ". + "disableSernoInReading:1,0 ". + "feedinPrice ". + "powerCost ". + "$readingFnAttributes"; +} + +##################################### +sub +SMAEM_Define($$) +{ + my ($hash, $def) = @_; + my $name= $hash->{NAME}; + my ($success, $gridin_sum, $gridout_sum); + + $hash->{INTERVAL} = 60 ; + + $hash->{LASTUPDATE}=0; + $hash->{HELPER}{LASTUPDATE} = 0; + + Log3 $hash, 3, "$name - Opening multicast socket..."; + my $socket = IO::Socket::Multicast->new( + Proto => 'udp', + LocalPort => '9522', + ReuseAddr => '1', + ReusePort => defined(&ReusePort) ? 1 : 0, + ) or return "Can't bind : $@"; + + $socket->mcast_add('239.12.255.254'); + + $hash->{TCPDev}= $socket; + $hash->{FD} = $socket->fileno(); + delete($readyfnlist{"$name"}); + $selectlist{"$name"} = $hash; + + # gespeicherte Energiezählerwerte von File einlesen + ($success, $gridin_sum, $gridout_sum) = getsum($hash); + if ($success) { + $hash->{GRIDIN_SUM} = $gridin_sum; + $hash->{GRIDOUT_SUM} = $gridout_sum; + Log3 $name, 3, "$name - read saved energy values from file - GRIDIN_SUM: $gridin_sum, GRIDOUT_SUM: $gridout_sum"; + } + +return undef; +} + +sub +SMAEM_Undef($$) +{ + my ($hash, $arg) = @_; + my $name= $hash->{NAME}; + my $socket= $hash->{TCPDev}; + + Log3 $hash, 3, "$name: Closing multicast socket..."; + $socket->mcast_drop('239.12.255.254'); + # $socket->close; + + my $ret = close($hash->{TCPDev}); + Log3 $hash, 4, "$name: Close-ret: $ret"; + delete($hash->{TCPDev}); + delete($selectlist{"$name"}); + delete($hash->{FD}); + + return; +} + +sub SMAEM_Delete { + my ($hash, $arg) = @_; + my $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum"; + + # gespeicherte Energiezählerwerte löschen + setKeyValue($index, undef); + +return undef; +} + +sub SMAEM_Attr { + my ($cmd,$name,$aName,$aVal) = @_; + my $hash = $defs{$name}; + + # $cmd can be "del" or "set" + # $name is device name + # aName and aVal are Attribute name and value + + if ($aName eq "interval") { + if($cmd eq "set") { + $hash->{INTERVAL} = $aVal; + } else { + $hash->{INTERVAL} = "60"; + } + } + + if ($aName eq "disableSernoInReading") { + delete $defs{$name}{READINGS}; + readingsSingleUpdate($hash, "state", "readingsreset", 1); + } + +return undef; +} + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub SMAEM_Read($) +{ + my ($hash) = @_; + my $name= $hash->{NAME}; + my $socket= $hash->{TCPDev}; + + my $data; + return unless $socket->recv($data, 600); # Each SMAEM packet is 600 bytes of packed payload + Log3 $hash, 5, "$name: Received " . length($data) . " bytes."; + + if ($hash->{HELPER}{LASTUPDATE} == 0 || time() >= $hash->{HELPER}{LASTUPDATE}+$hash->{INTERVAL}) { + + # Format of the udp packets of the SMAEM: + # http://www.sma.de/fileadmin/content/global/Partner/Documents/SMA_Labs/EMETER-Protokoll-TI-de-10.pdf + # http://www.eb-systeme.de/?page_id=1240 + + # Conversion like in this python code: + # http://www.unifox.at/sma_energy_meter/ + # https://github.com/datenschuft/SMA-EM + + # unpack big-endian to 2-digit hex (bin2hex) + my $hex=unpack('H*', $data); + + # Extract datasets from hex: + # Generic: + my $susyid=hex(substr($hex,36,4)); + my $smaserial=hex(substr($hex,40,8)); + my $milliseconds=hex(substr($hex,48,8)); + #readingsBulkUpdate($hash, "SUSy-ID", $susyid); + #readingsBulkUpdate($hash, "Seriennummer", $smaserial); + + # Counter Divisor: [Hex-Value]=Ws => Ws/1000*3600=kWh => divide by 3600000 + # Sum L1-3 + my $bezug_wirk=hex(substr($hex,64,8))/10; + my $bezug_wirk_count=hex(substr($hex,80,16))/3600000; + my $einspeisung_wirk=hex(substr($hex,104,8))/10; + my $einspeisung_wirk_count=hex(substr($hex,120,16))/3600000; + + # Prestring with NAME and SERIALNO or not + my $ps = (!AttrVal($name, "disableSernoInReading", undef)) ? "SMAEM".$smaserial."_" : ""; + + readingsBeginUpdate($hash); + + readingsBulkUpdate($hash, "state", sprintf("%.1f", $einspeisung_wirk-$bezug_wirk)); + readingsBulkUpdate($hash, $ps."Saldo_Wirkleistung", sprintf("%.1f",$einspeisung_wirk-$bezug_wirk)); + readingsBulkUpdate($hash, $ps."Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$einspeisung_wirk_count-$bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."Bezug_Wirkleistung", sprintf("%.1f",$bezug_wirk)); + readingsBulkUpdate($hash, $ps."Bezug_Wirkleistung_Zaehler", sprintf("%.4f",$bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."Einspeisung_Wirkleistung", sprintf("%.1f",$einspeisung_wirk)); + readingsBulkUpdate($hash, $ps."Einspeisung_Wirkleistung_Zaehler", sprintf("%.4f",$einspeisung_wirk_count)); + + if(!$hash->{GRIDOUT_SUM} || ReadingsVal($name,$ps."Bezug_Wirkleistung_Zaehler","") < $hash->{GRIDOUT_SUM}) { + $hash->{GRIDOUT_SUM} = sprintf("%.4f",$bezug_wirk_count); + } else { + if (ReadingsVal($name,$ps."Bezug_Wirkleistung_Zaehler","") >= $hash->{GRIDOUT_SUM}) { + my $diffb = $bezug_wirk_count - $hash->{GRIDOUT_SUM}; + $hash->{GRIDOUT_SUM} = sprintf("%.4f",$bezug_wirk_count); + readingsBulkUpdate($hash, $ps."Bezug_WirkP_Zaehler_Diff", $diffb); + readingsBulkUpdate($hash, $ps."Bezug_WirkP_Kosten_Diff", sprintf("%.4f", $diffb*AttrVal($hash->{NAME}, "powerCost", 0))); + } + } + + if(!$hash->{GRIDIN_SUM} || ReadingsVal($name,$ps."Einspeisung_Wirkleistung_Zaehler","") < $hash->{GRIDIN_SUM}) { + $hash->{GRIDIN_SUM} = sprintf("%.4f",$einspeisung_wirk_count); + } else { + if (ReadingsVal($name,$ps."Einspeisung_Wirkleistung_Zaehler","") >= $hash->{GRIDIN_SUM}) { + my $diffe = $einspeisung_wirk_count - $hash->{GRIDIN_SUM}; + $hash->{GRIDIN_SUM} = sprintf("%.4f",$einspeisung_wirk_count); + readingsBulkUpdate($hash, $ps."Einspeisung_WirkP_Zaehler_Diff", $diffe); + readingsBulkUpdate($hash, $ps."Einspeisung_WirkP_Verguet_Diff", sprintf("%.4f", $diffe*AttrVal($hash->{NAME}, "feedinPrice", 0))); + } + } + + # GRIDIN_SUM und GRIDOUT_SUM in File schreiben + my $success = setsum($hash, $hash->{GRIDIN_SUM}, $hash->{GRIDOUT_SUM}); + + my $bezug_blind=hex(substr($hex,144,8))/10; + my $bezug_blind_count=hex(substr($hex,160,16))/3600000; + my $einspeisung_blind=hex(substr($hex,184,8))/10; + my $einspeisung_blind_count=hex(substr($hex,200,16))/3600000; + readingsBulkUpdate($hash, $ps."Bezug_Blindleistung", sprintf("%.1f",$bezug_blind)); + readingsBulkUpdate($hash, $ps."Bezug_Blindleistung_Zaehler", sprintf("%.1f",$bezug_blind_count)); + readingsBulkUpdate($hash, $ps."Einspeisung_Blindleistung", sprintf("%.1f",$einspeisung_blind)); + readingsBulkUpdate($hash, $ps."Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$einspeisung_blind_count)); + + my $bezug_schein=hex(substr($hex,224,8))/10; + my $bezug_schein_count=hex(substr($hex,240,16))/3600000; + my $einspeisung_schein=hex(substr($hex,264,8))/10; + my $einspeisung_schein_count=hex(substr($hex,280,16))/3600000; + readingsBulkUpdate($hash, $ps."Bezug_Scheinleistung", sprintf("%.1f",$bezug_schein)); + readingsBulkUpdate($hash, $ps."Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$bezug_schein_count)); + readingsBulkUpdate($hash, $ps."Einspeisung_Scheinleistung", sprintf("%.1f",$einspeisung_schein)); + readingsBulkUpdate($hash, $ps."Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$einspeisung_schein_count)); + + my $cosphi=hex(substr($hex,304,8))/1000; + readingsBulkUpdate($hash, $ps."CosPhi", sprintf("%.3f",$cosphi)); + + # L1 + my $l1_bezug_wirk=hex(substr($hex,320,8))/10; + my $l1_bezug_wirk_count=hex(substr($hex,336,16))/3600000; + my $l1_einspeisung_wirk=hex(substr($hex,360,8))/10; + my $l1_einspeisung_wirk_count=hex(substr($hex,376,16))/3600000; + readingsBulkUpdate($hash, $ps."L1_Saldo_Wirkleistung", sprintf("%.1f",$l1_einspeisung_wirk-$l1_bezug_wirk)); + readingsBulkUpdate($hash, $ps."L1_Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_wirk_count-$l1_bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."L1_Bezug_Wirkleistung", sprintf("%.1f",$l1_bezug_wirk)); + readingsBulkUpdate($hash, $ps."L1_Bezug_Wirkleistung_Zaehler", sprintf("%.1f",$l1_bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."L1_Einspeisung_Wirkleistung", sprintf("%.1f",$l1_einspeisung_wirk)); + readingsBulkUpdate($hash, $ps."L1_Einspeisung_Wirkleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_wirk_count)); + + my $l1_bezug_blind=hex(substr($hex,400,8))/10; + my $l1_bezug_blind_count=hex(substr($hex,416,16))/3600000; + my $l1_einspeisung_blind=hex(substr($hex,440,8))/10; + my $l1_einspeisung_blind_count=hex(substr($hex,456,16))/3600000; + readingsBulkUpdate($hash, $ps."L1_Bezug_Blindleistung", sprintf("%.1f",$l1_bezug_blind)); + readingsBulkUpdate($hash, $ps."L1_Bezug_Blindleistung_Zaehler", sprintf("%.1f",$l1_bezug_blind_count)); + readingsBulkUpdate($hash, $ps."L1_Einspeisung_Blindleistung", sprintf("%.1f",$l1_einspeisung_blind)); + readingsBulkUpdate($hash, $ps."L1_Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_blind_count)); + + my $l1_bezug_schein=hex(substr($hex,480,8))/10; + my $l1_bezug_schein_count=hex(substr($hex,496,16))/3600000; + my $l1_einspeisung_schein=hex(substr($hex,520,8))/10; + my $l1_einspeisung_schein_count=hex(substr($hex,536,16))/3600000; + readingsBulkUpdate($hash, $ps."L1_Bezug_Scheinleistung", sprintf("%.1f",$l1_bezug_schein)); + readingsBulkUpdate($hash, $ps."L1_Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$l1_bezug_schein_count)); + readingsBulkUpdate($hash, $ps."L1_Einspeisung_Scheinleistung", sprintf("%.1f",$l1_einspeisung_schein)); + readingsBulkUpdate($hash, $ps."L1_Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$l1_einspeisung_schein_count)); + + my $l1_thd=hex(substr($hex,560,8))/1000; + my $l1_v=hex(substr($hex,576,8))/1000; + my $l1_cosphi=hex(substr($hex,592,8))/1000; + readingsBulkUpdate($hash, $ps."L1_THD", sprintf("%.2f",$l1_thd)); + readingsBulkUpdate($hash, $ps."L1_Spannung", sprintf("%.1f",$l1_v)); + readingsBulkUpdate($hash, $ps."L1_CosPhi", sprintf("%.3f",$l1_cosphi)); + + + # L2 + my $l2_bezug_wirk=hex(substr($hex,608,8))/10; + my $l2_bezug_wirk_count=hex(substr($hex,624,16))/3600000; + my $l2_einspeisung_wirk=hex(substr($hex,648,8))/10; + my $l2_einspeisung_wirk_count=hex(substr($hex,664,16))/3600000; + readingsBulkUpdate($hash, $ps."L2_Saldo_Wirkleistung", sprintf("%.1f",$l2_einspeisung_wirk-$l2_bezug_wirk)); + readingsBulkUpdate($hash, $ps."L2_Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_wirk_count-$l2_bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."L2_Bezug_Wirkleistung", sprintf("%.1f",$l2_bezug_wirk)); + readingsBulkUpdate($hash, $ps."L2_Bezug_Wirkleistung_Zaehler", sprintf("%.1f",$l2_bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."L2_Einspeisung_Wirkleistung", sprintf("%.1f",$l2_einspeisung_wirk)); + readingsBulkUpdate($hash, $ps."L2_Einspeisung_Wirkleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_wirk_count)); + + my $l2_bezug_blind=hex(substr($hex,688,8))/10; + my $l2_bezug_blind_count=hex(substr($hex,704,16))/3600000; + my $l2_einspeisung_blind=hex(substr($hex,728,8))/10; + my $l2_einspeisung_blind_count=hex(substr($hex,744,16))/3600000; + readingsBulkUpdate($hash, $ps."L2_Bezug_Blindleistung", sprintf("%.1f",$l2_bezug_blind)); + readingsBulkUpdate($hash, $ps."L2_Bezug_Blindleistung_Zaehler", sprintf("%.1f",$l2_bezug_blind_count)); + readingsBulkUpdate($hash, $ps."L2_Einspeisung_Blindleistung", sprintf("%.1f",$l2_einspeisung_blind)); + readingsBulkUpdate($hash, $ps."L2_Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_blind_count)); + + my $l2_bezug_schein=hex(substr($hex,768,8))/10; + my $l2_bezug_schein_count=hex(substr($hex,784,16))/3600000; + my $l2_einspeisung_schein=hex(substr($hex,808,8))/10; + my $l2_einspeisung_schein_count=hex(substr($hex,824,16))/3600000; + readingsBulkUpdate($hash, $ps."L2_Bezug_Scheinleistung", sprintf("%.1f",$l2_bezug_schein)); + readingsBulkUpdate($hash, $ps."L2_Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$l2_bezug_schein_count)); + readingsBulkUpdate($hash, $ps."L2_Einspeisung_Scheinleistung", sprintf("%.1f",$l2_einspeisung_schein)); + readingsBulkUpdate($hash, $ps."L2_Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$l2_einspeisung_schein_count)); + + my $l2_thd=hex(substr($hex,848,8))/1000; + my $l2_v=hex(substr($hex,864,8))/1000; + my $l2_cosphi=hex(substr($hex,880,8))/1000; + readingsBulkUpdate($hash, $ps."L2_THD", sprintf("%.2f",$l2_thd)); + readingsBulkUpdate($hash, $ps."L2_Spannung", sprintf("%.1f",$l2_v)); + readingsBulkUpdate($hash, $ps."L2_CosPhi", sprintf("%.3f",$l2_cosphi)); + + # L3 + my $l3_bezug_wirk=hex(substr($hex,896,8))/10; + my $l3_bezug_wirk_count=hex(substr($hex,912,16))/3600000; + my $l3_einspeisung_wirk=hex(substr($hex,936,8))/10; + my $l3_einspeisung_wirk_count=hex(substr($hex,952,16))/3600000; + readingsBulkUpdate($hash, $ps."L3_Saldo_Wirkleistung", sprintf("%.1f",$l3_einspeisung_wirk-$l3_bezug_wirk)); + readingsBulkUpdate($hash, $ps."L3_Saldo_Wirkleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_wirk_count-$l3_bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."L3_Bezug_Wirkleistung", sprintf("%.1f",$l3_bezug_wirk)); + readingsBulkUpdate($hash, $ps."L3_Bezug_Wirkleistung_Zaehler", sprintf("%.1f",$l3_bezug_wirk_count)); + readingsBulkUpdate($hash, $ps."L3_Einspeisung_Wirkleistung", sprintf("%.1f",$l3_einspeisung_wirk)); + readingsBulkUpdate($hash, $ps."L3_Einspeisung_Wirkleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_wirk_count)); + + my $l3_bezug_blind=hex(substr($hex,976,8))/10; + my $l3_bezug_blind_count=hex(substr($hex,992,16))/3600000; + my $l3_einspeisung_blind=hex(substr($hex,1016,8))/10; + my $l3_einspeisung_blind_count=hex(substr($hex,1032,16))/3600000; + readingsBulkUpdate($hash, $ps."L3_Bezug_Blindleistung", sprintf("%.1f",$l3_bezug_blind)); + readingsBulkUpdate($hash, $ps."L3_Bezug_Blindleistung_Zaehler", sprintf("%.1f",$l3_bezug_blind_count)); + readingsBulkUpdate($hash, $ps."L3_Einspeisung_Blindleistung", sprintf("%.1f",$l3_einspeisung_blind)); + readingsBulkUpdate($hash, $ps."L3_Einspeisung_Blindleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_blind_count)); + + my $l3_bezug_schein=hex(substr($hex,1056,8))/10; + my $l3_bezug_schein_count=hex(substr($hex,1072,16))/3600000; + my $l3_einspeisung_schein=hex(substr($hex,1096,8))/10; + my $l3_einspeisung_schein_count=hex(substr($hex,1112,16))/3600000; + readingsBulkUpdate($hash, $ps."L3_Bezug_Scheinleistung", sprintf("%.1f",$l3_bezug_schein)); + readingsBulkUpdate($hash, $ps."L3_Bezug_Scheinleistung_Zaehler", sprintf("%.1f",$l3_bezug_schein_count)); + readingsBulkUpdate($hash, $ps."L3_Einspeisung_Scheinleistung", sprintf("%.1f",$l3_einspeisung_schein)); + readingsBulkUpdate($hash, $ps."L3_Einspeisung_Scheinleistung_Zaehler", sprintf("%.1f",$l3_einspeisung_schein_count)); + + my $l3_thd=hex(substr($hex,1136,8))/1000; + my $l3_v=hex(substr($hex,1152,8))/1000; + my $l3_cosphi=hex(substr($hex,1168,8))/1000; + readingsBulkUpdate($hash, $ps."L3_THD", sprintf("%.2f",$l3_thd)); + readingsBulkUpdate($hash, $ps."L3_Spannung", sprintf("%.1f",$l3_v)); + readingsBulkUpdate($hash, $ps."L3_CosPhi", sprintf("%.3f",$l3_cosphi)); + + readingsEndUpdate($hash, 1); + + $hash->{HELPER}{LASTUPDATE}=time(); + + # $update time + my ($sec,$min,$hour,$mday,$mon,$year,undef,undef,undef) = localtime; + $hash->{LASTUPDATE} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ; + } +} + +###################################################################################### +### Summenwerte für GridIn, GridOut speichern + +sub setsum ($$$) { + my ($hash, $gridin_sum, $gridout_sum) = @_; + my $name = $hash->{NAME}; + my $success; + my $index; + my $retcode; + my $sumstr; + + $sumstr = $gridin_sum."_".$gridout_sum; + + $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum"; + $retcode = setKeyValue($index, $sumstr); + + if ($retcode) { + Log3($name, 1, "$name - Error while saving summary of energy values - $retcode"); + $success = 0; + } + else + { + Log3($name, 4, "$name - summary of energy values saved - GRIDIN_SUM: $gridin_sum, GRIDOUT_SUM: $gridout_sum"); + $success = 1; + } + +return ($success); +} + +###################################################################################### +### Summenwerte für GridIn, GridOut abtufen + +sub getsum ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $success; + my $index; + my $retcode; + my $sumstr; + my ($gridin_sum, $gridout_sum); + + $index = $hash->{TYPE}."_".$hash->{NAME}."_energysum"; + ($retcode, $sumstr) = getKeyValue($index); + + if ($retcode) { + Log3($name, 1, "$name - ERROR -unable to read summary of energy values from file - $retcode"); + $success = 0; + } + + if ($sumstr) { + ($gridin_sum, $gridout_sum) = split(/_/, $sumstr); + Log3($name, 4, "$name - summary of energy values was read from file - GRIDIN_SUM: $gridin_sum, GRIDOUT_SUM: $gridout_sum"); + $success = 1; + } + +return ($success, $gridin_sum, $gridout_sum); +} + +###################################################################################### + +1; + + + +=pod +=begin html + + +

SMAEM

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

SMAEM

+ + + + +=end html_DE + diff --git a/fhem/FHEM/77_SMASTP.pm b/fhem/FHEM/77_SMASTP.pm new file mode 100644 index 000000000..cfec5ff83 --- /dev/null +++ b/fhem/FHEM/77_SMASTP.pm @@ -0,0 +1,698 @@ +############################################################### +# $Id$ +# +# Copyright notice +# +# (c) 2016 Copyright: Volker Kettenbach (volker at kettenbach minus it dot de) +# +# Credits: +# - based on an Idea by SpenZerX and HDO +# - Waldmensch for various improvements +# - sbfspot (https://sbfspot.codeplex.com/) +# +# Description: +# This is an FHEM-Module for the SMA Sunny Tripower Inverter. +# Tested on Sunny Tripower 6000TL-20, 10000-TL20 and 10000TL-10 with +# Speedwire/Webconnect Piggyback +# +# Requirements: +# This module requires: +# - Perl Module: IO::Socket::INET +# - Perl Module: Datime +# +# Origin: +# https://github.com/kettenbach-it/FHEM-SMA-Speedwire +# +############################################################### + +package main; + +use strict; +use warnings; +use IO::Socket::INET; +use DateTime; + +# Global vars +my $cmd_login = "534d4100000402a000000001003a001060650ea0ffffffffffff00017800C8E8033800010000000004800c04fdff07000000840300004c20cb5100000000encpw00000000"; +my $cmd_logout = "534d4100000402a00000000100220010606508a0ffffffffffff00037800C8E80338000300000000d7840e01fdffffffffff00000000"; +my $cmd_query_total_today = "534d4100000402a00000000100260010606509e0ffffffffffff00007800C8E80338000000000000f1b10002005400002600ffff260000000000"; +my $cmd_query_spot_ac_power = "534d4100000402a00000000100260010606509e0ffffffffffff00007800C8E8033800000000000081f00002005100002600ffff260000000000"; +my $cmd_query_spot_dc_power = "534d4100000402a00000000100260010606509e0ffffffffffff00007800C8E8033800000000000081f00002805300002500ffff260000000000"; +my $averagebuf = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + +my $code_login = "0d04fdff"; #0xfffd040d; +my $code_total_today = "01020054"; #0x54000201; +my $code_spot_ac_power = "01020051"; #0x51000201; +my $code_spot_dc_power = "01028053"; #0x53800201; + +my $default_starthour = "05:00"; +my $starthour = 5; +my $startminute = 0; +my $default_endhour = "22:00"; +my $endhour = 22; +my $endminute = 0; + +my $force_sleep = 0; +my $sleep_forced = 0; + +my $suppress_night_mode = 0; +my $suppress_inactivity_mode = 0; + +my $modulstate_enabled = 0; +my ($alarm_value1,$alarm_value2,$alarm_value3); + + +################################### +sub SMASTP_Initialize($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $hval; + my $mval; + + $hash->{DefFn} = "SMASTP_Define"; + $hash->{UndefFn} = "SMASTP_Undef"; + $hash->{AttrList} = "suppress-night-mode:0,1 " . + "suppress-inactivity-mode:0,1 " . + "starttime " . + "endtime " . + "force-sleepmode:0,1 " . + "enable-modulstate:0,1 " . + "alarm1-value " . + "alarm2-value " . + "alarm3-value " . + "interval " . + $readingFnAttributes; + $hash->{AttrFn} = "SMASTP_Attr"; + + if ($attr{$name}{"starttime"}) + { + ($hval, $mval) = split(/:/,$attr{$name}{"starttime"}); + } + else + { + ($hval, $mval) = split(/:/,$default_starthour); + } + $starthour = int($hval); + $startminute = int($mval); + + if ($attr{$name}{"endtime"}) + { + ($hval, $mval) = split(/:/,$attr{$name}{"endtime"}); + } + else + { + ($hval, $mval) = split(/:/,$default_endhour); + } + $endhour = int($hval); + $endminute = int($mval); + + $suppress_night_mode = ($attr{$name}{"suppress-night-mode"}) ? $attr{$name}{"suppress-night-mode"} : 0; + $suppress_inactivity_mode = ($attr{$name}{"suppress-inactivity-mode"}) ? $attr{$name}{"suppress-inactivity-mode"} : 0; + $force_sleep = ($attr{$name}{"force-sleepmode"}) ? $attr{$name}{"force-sleepmode"} : 0; + $modulstate_enabled = ($attr{$name}{"enable-modulstate"}) ? $attr{$name}{"enable-modulstate"} : 0; + + $alarm_value1 = ($attr{$name}{"alarm1-value"}) ? $attr{$name}{"alarm1-value"} : 0; + $alarm_value2 = ($attr{$name}{"alarm2-value"}) ? $attr{$name}{"alarm2-value"} : 0; + $alarm_value3 = ($attr{$name}{"alarm3-value"}) ? $attr{$name}{"alarm3-value"} : 0; + + Log3 $name, 0, "$name: Started with sleepmode from $endhour:$endminute - $starthour:$startminute"; +} + +################################### +sub is_Sleepmode() +{ + # Build 3 DateTime Objects to make the comparison more robust + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); + my $dt_startdate = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>$starthour,minute=>$startminute,second=>0,time_zone=>'local'); + my $dt_enddate = DateTime->new(year=>$year+1900,month=>$mon+1,day=>$mday,hour=>$endhour,minute=>$endminute,second=>0,time_zone=>'local'); + my $dt_now = DateTime->now(time_zone=>'local'); + + # Return of any value != 0 means "sleeping" + if ($dt_now >= $dt_enddate || $dt_now <= $dt_startdate) + { + # switch forced sleepmode off because we have reached normal sleepmode now + $sleep_forced = 0; + return 1; + } + elsif ($sleep_forced == 1) + { + # 2 = forced sleep + return 2; + } + else + { + return 0; + } +} + +################################### +sub SMASTP_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Wrong syntax: use define SMASTP " if ((int(@a) < 4) and (int(@a) > 5)); + + my $name = $a[0]; + $hash->{NAME} = $name; + $hash->{LASTUPDATE}=0; + $hash->{INTERVAL} = 60; + + # SMASTP = $a[1]; + my ($IP,$Host,$Caps); + + my $Pass = $a[2]; # to do: check 1-12 Chars + + # extract IP or Hostname from $a[4] + if ( $a[3] ~~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ ) + { + if ( $1 <= 255 && $2 <= 255 && $3 <= 255 && $4 <= 255 ) + { + $Host = int($1).".".int($2).".".int($3).".".int($4); + } + } + + 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; + + + # Use C8E80338, but NOT the number of the Inverter! + # my $src_serial = 939780296; + + my $encpw = "888888888888888888888888"; # unencoded pw + for my $index (0..length $Pass ) # encode password + { + substr($encpw,($index*2),2) = substr(sprintf ("%lX", (hex(substr($encpw,($index*2),2)) + ord(substr($Pass,$index,1)))),0,2); + } + + $cmd_login =~ s/encpw/$encpw/g; #replace the placeholder with password + + InternalTimer(gettimeofday()+5, "SMASTP_GetStatus", $hash, 0); # refresh timer start + + return undef; +} + +##################################### +sub SMASTP_Undef($$) +{ + my ($hash, $name) = @_; + RemoveInternalTimer($hash); + Log3 $hash, 0, "$name: Undefined!"; + return undef; +} + +################################### +sub SMASTP_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 $hval; + my $mval; + + if (($aName eq "starttime" || $aName eq "endtime") && not ($aVal =~ /^([0-1]?[0-9]|[2][0-3]):([0-5][0-9])$/)) + { + return "value $aVal invalid"; # no correct time format hh:mm + } + + if ($aName eq "enable-modulstate") + { + $modulstate_enabled = ($cmd eq "set") ? int($aVal) : 0; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + + if ($aName eq "alarm1-value") + { + $alarm_value1 = ($cmd eq "set") ? int($aVal) : 0; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + + if ($aName eq "alarm2-value") + { + $alarm_value2 = ($cmd eq "set") ? int($aVal) : 0; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + + if ($aName eq "alarm3-value") + { + $alarm_value3 = ($cmd eq "set") ? int($aVal) : 0; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + + if ($aName eq "starttime") + { + if ($cmd eq "set") + { + ($hval, $mval) = split(/:/,$aVal); + } + else + { + ($hval, $mval) = split(/:/,$default_starthour); + } + if (int($hval) < 12) + { + $starthour = int($hval); + $startminute = int($mval); + } + else + { + return "$name: Attr starttime must be set smaller than 12:00! Not set to $starthour:$startminute"; + } + + Log3 $name, 3, "$name: Attr starttime is set to " . sprintf("%02d:%02d",$starthour,$startminute); + } + + if ($aName eq "endtime") + { + if ($cmd eq "set") + { + ($hval, $mval) = split(/:/,$aVal); + } + else + { + ($hval, $mval) = split(/:/,$default_endhour); + } + + if (int($hval) > 12) + { + $endhour = int($hval); + $endminute = int($mval); + } + else + { + return "$name: Attr endtime must be set larger than 12:00! Not set to $endhour:$endminute"; + } + + Log3 $name, 3, "$name: Attr endtime is set to " . sprintf("%02d:%02d",$endhour,$endminute); + } + + if ($aName eq "suppress-night-mode") + { + $suppress_night_mode = ($cmd eq "set") ? $aVal : 0; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + + if ($aName eq "suppress-inactivity-mode") + { + $suppress_inactivity_mode = ($cmd eq "set") ? $aVal : 0; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + + if ($aName eq "force-sleepmode") + { + if ($cmd eq "set") + { + $force_sleep = $aVal; + $sleep_forced = ($aVal == 0) ? 0 : $sleep_forced; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + else + { + $force_sleep = 0; + $sleep_forced = 0; + } + } + + if ($aName eq "interval") + { + if ($cmd eq "set") + { + $hash->{INTERVAL} = $aVal; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + } else + { + $hash->{INTERVAL} = "60"; + Log3 $name, 3, "$name: Set $aName to $aVal"; + } + return undef; +} + +##################################### +sub SMASTP_GetStatus($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + $hash->{INTERVAL} = $attr{$name}{"interval"}; + + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); + + if ($suppress_night_mode == 0) + { + if(is_Sleepmode() > 0) + { + Log3 $name, 5, "$name: " . + sprintf("%02d:%02d",$hour,$min) . + " is out of working hours " . + sprintf("%02d:%02d",$starthour,$startminute) . + " - " . + sprintf("%02d:%02d",$endhour,$endminute) . + " " . + (($sleep_forced == 1) ? " FORCED" : ""); + + my $modulstate = ($hash->{READINGS}{modulstate}{VAL}) ? $hash->{READINGS}{modulstate}{VAL} : "unknown"; + if($modulstate ne "sleeping" && $modulstate_enabled == 1) + { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "modulstate", "sleeping"); + readingsEndUpdate($hash, 1); + } + + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "SMASTP_GetStatus", $hash, 1); + return; + } + } + + use constant MAXBYTES => scalar 200; #1024 #80 + + my $Host = $hash->{Host}; + my $interval = $hash->{INTERVAL}; + # my $averagebuf = $hash->{averagebuf}; + + my ($AvP01,$AvP05,$AvP15,$TodayTotal,$SpotPower,$AlltimeTotal,$statusval,$PDC1,$PDC2); + my ($socket,$data,$size,$code); + my $error = 0; + + # flush after every write + $| = 1; + + $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 undef; + }; + + # send login command + Log3 $name, 2, "$name: Sending query to inverter $Host:9522"; + $data = pack "H*",$cmd_login; + $socket->send($data); + + do + { + eval + { + local $SIG{ALRM} = sub { die "alarm time out" }; + alarm 5; + # receive data + $socket->recv($data, MAXBYTES) or die "recv: $!"; + $size = length($data); + + # too little data - exit loop + if ((defined $size) && ($size > 60)) + { + my $received = unpack("H*", $data); + Log3 $name, 5, "$name: Received: ($received)"; + } else { + if($size > 0) + { + my $received = unpack("H*", $data); + Log3 $name, 5, "$name: Received Garbage: ($received)"; + } + } + + alarm 0; + 1; + } or Log3 $name, 1, "$name query timed out"; + + # too little data -> exit loop + if ((not defined $size) || ($size < 60)) + { + Log3 $name, 1, "$name: Too little data received (Len:".((not defined $size) ? "NaN/timeout" : $size).")"; + # send: cmd_logout + $data = pack "H*",$cmd_logout; + $size = $socket->send($data); + $socket->close(); + $error = 1; + } + else + { + # unpack command + $code = unpack("H*", substr $data, 42, 4); + + # answer to command login + if ($code_login eq $code) + { + # send: Query total today + $data = pack "H*",$cmd_query_total_today; + $size = $socket->send($data); + } + + # answer to command total today + if ($code_total_today eq $code) + { + $TodayTotal = unpack("V*", substr $data, 78, 4); + $AlltimeTotal = unpack("V*", substr $data, 62, 4); + # send: Query spot power + $data = pack "H*",$cmd_query_spot_ac_power; + $size = $socket->send($data); + } + + # answer to command AC Power + if ($code_spot_ac_power eq $code) + { + $SpotPower = unpack("V*", substr $data, 62, 4); + # special case at night ? Inverter off? + if ($SpotPower eq 0x80000000) {$SpotPower = 0}; + # send: query spot DC power + $data = pack "H*",$cmd_query_spot_dc_power; + $size = $socket->send($data); + } + + # answer to command DC Power + if ($code_spot_dc_power eq $code) + { + $PDC1 = unpack("V*", substr $data, 62, 4); + if ($PDC1 eq 0x80000000) {$PDC1 = 0}; + $PDC2 = unpack("V*", substr $data, 90, 4); + if ($PDC2 eq 0x80000000) {$PDC2 = 0}; + # send: cmd_logout + $data = pack "H*",$cmd_logout; + $size = $socket->send($data); + # close Socket + $socket->close(); + } + } + + } while (($code_spot_dc_power ne $code) && ($error eq 0)); # answer to command spot_ac_power + + if ($error ne 1) + { + if ( (int(hex(substr($averagebuf,0*8,8)))) eq 0) + { + for my $count (0..15) + { + # fill with new values + substr($averagebuf,$count*8,1*8) = substr(sprintf ("%08X",$AlltimeTotal),0,8); + } + } + + # average buffer shiften und mit neuem Wert füllen + substr($averagebuf,1*8,15*8) = substr($averagebuf,0*8,15*8); + # und mit neuem Wert füllen + substr($averagebuf,0*8,1*8) = substr(sprintf ("%08X",$AlltimeTotal),0,8); + $AvP01 = int( ( (hex(substr($averagebuf,0*8,8))) - (hex(substr($averagebuf,1*8,8))) ) * ((3600 / 01) / $interval) ); + $AvP05 = int( ( (hex(substr($averagebuf,0*8,8))) - (hex(substr($averagebuf,5*8,8))) ) * ((3600 / 05) / $interval) ); + $AvP15 = int( ( (hex(substr($averagebuf,0*8,8))) - (hex(substr($averagebuf,15*8,8))) ) * ((3600 / 15) / $interval) ); + + $statusval = "SP:$SpotPower W AvP1:$AvP01 W TTP:$TodayTotal Wh ATP:$AlltimeTotal Wh"; + + Log3 $name, 4, "$name: from ($Host): ($statusval) "; + + Log3 $name, 5, "$name: AvP05 = $AvP05, SpotPower = $SpotPower, AvP15 = $AvP15"; + #Filter out error zero values and stop readingsupdate after 15 Mins on zero power + if ( ((not ($AvP05 > 0 && $SpotPower == 0)) && $AvP15 > 0) || $suppress_inactivity_mode == 1 || $hash->{LASTUPDATE} eq 0) + { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "state", $statusval); # Status Overview + readingsBulkUpdate($hash, "SpotP", $SpotPower); # Momentary Spot Power + readingsBulkUpdate($hash, "SpotPDC1", $PDC1); # Momentary Spot DC Power String1 + readingsBulkUpdate($hash, "SpotPDC2", $PDC2); # Momentary Spot DC Power String2 + readingsBulkUpdate($hash, "TodayTotalP", $TodayTotal); # Today Total Power + readingsBulkUpdate($hash, "AlltimeTotalP", $AlltimeTotal); # Alltime Total Power + readingsBulkUpdate($hash, "AvP01", $AvP01); # Average Power (last) 1 Minute (if delay 60) + readingsBulkUpdate($hash, "AvP05", $AvP05); # Average Power (last) 5 Minutes (if delay 60) + readingsBulkUpdate($hash, "AvP15", $AvP15); # Average Power (last) 15 Minutes (if delay 60) + readingsBulkUpdate($hash, "modulstate", "normal"); + + if($alarm_value1 > 0 && $SpotPower > $alarm_value1) { readingsBulkUpdate($hash, "Alarm1", 1); } + elsif ($alarm_value1 > 0 && $SpotPower < $alarm_value1) { readingsBulkUpdate($hash, "Alarm1", (-1)); } + else { readingsBulkUpdate($hash, "Alarm1", 0); } + + if($alarm_value2 > 0 && $SpotPower > $alarm_value2) { readingsBulkUpdate($hash, "Alarm2", 1); } + elsif ($alarm_value2 > 0 && $SpotPower < $alarm_value2) { readingsBulkUpdate($hash, "Alarm2", (-1)); } + else { readingsBulkUpdate($hash, "Alarm2", 0); } + + if($alarm_value3 > 0 && $SpotPower > $alarm_value3) { readingsBulkUpdate($hash, "Alarm3", 1); } + elsif ($alarm_value3 > 0 && $SpotPower < $alarm_value3) { readingsBulkUpdate($hash, "Alarm3", (-1)); } + else { readingsBulkUpdate($hash, "Alarm3", 0); } + + readingsEndUpdate($hash, 1); # Notify is done by Dispatch + + $hash->{LASTUPDATE} = sprintf "%02d.%02d.%04d / %02d:%02d:%02d" , $mday , $mon+=1 ,$year+=1900 , $hour , $min , $sec ; + Log3 $name, 5, "$name: Readings updated"; + } + else + { + if ($AvP15 == 0 && $SpotPower == 0 && $force_sleep == 1 && $sleep_forced == 0 && $hour > 12) + { + $sleep_forced = 1; + Log3 $name, 1, "$name: sleepmode forced after 15 minutes zero power"; + } + + my $modulstate = ($hash->{READINGS}{modulstate}{VAL}) ? $hash->{READINGS}{modulstate}{VAL} : ""; + if($modulstate ne "inactive" && $modulstate_enabled == 1) + { + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "modulstate", "inactive"); + readingsEndUpdate($hash, 1); + } + + Log3 $name, 5, "$name: Readings not updated"; + } + } + InternalTimer(gettimeofday()+$interval, "SMASTP_GetStatus", $hash, 1); + #return undef; +} + +1; + +=pod + +=begin html + + +

SMASTP

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

+ +Define +

    +define <name> SMASTP <pin> <hostname/ip> [port]
    +
    +
  • pin: User-Password of the SMA STP Inverter. Default is 0000. Can be changed by "Sunny Explorer" Windows Software
  • +
  • hostname/ip: Hostname or IP-Adress of the inverter (or it's speedwire piggyback module).
  • +
  • port: Port of the inverter. 9522 by default.
  • +
+ +

+ +Modus +

    +The module automatically detects the inactvity of the inverter due to a lack of light (night).
    +This inactivity is therefore called "nightmode". During nightmode, the inverter is not queried over the network.
    +By default nightmode is between 9pm and 5am. This can be changed by "starttime" (start of inverter
    +operation, end of nightmode) and "endtime" (end of inverter operation, start of nightmode).
    +Further there is the inactivitymode: in inactivitymode, the inverter is queried but readings are not updated. +
+ +Parameter +
    +
  • interval: Queryintreval in seconds
  • +
  • suppress-night-mode: The nightmode is deactivated
  • +
  • suppress-inactivity-mode: The inactivitymode is deactivated
  • +
  • starttime: Starttime of inverter operation (default 5am)
  • +
  • endtime: Endtime of inverter operation (default 9pm)
  • +
  • force-sleepmode: The nightmode is activated on inactivity, even the endtime is not reached
  • +
  • enable-modulstate: Turns the reading "modulstate" (normal / inactive / sleeping) on
  • +
  • alarm1-value, alarm2-value, alarm3-value: Set an alarm on the reading SpotP in watt.
    + The readings Alarm1..Alarm3 are set accordingly: -1 for SpotP < alarmX-value and 1 for SpotP >= alarmX-value
  • +
+ +Readings +
    +
  • SpotP: spotpower - Current power in watt delivered by the inverter
  • +
  • AvP01: average power 1 minute: average power in watt of the last minute
  • +
  • AvP05: average power 5 minutes: average power in watt of the five minutes
  • +
  • AvP15: average power 15 minutes: average power in watt of the fifteen minutes
  • +
  • SpotPDC1: current d.c. voltage delivered by string 1
  • +
  • SpotPDC2: current d.c. voltage delivered by string 2
  • +
  • TotalTodayP: generated power in Wh of the current day
  • +
  • AlltimeTotalP: all time generated power in Wh
  • +
  • Alarm1..3: alrm trigger 1..3. Set by parameter alarmN-value
  • +
+ + +=end html + + +=begin html_DE + + +

SMASTP

+ +Modul zur Einbindung eines Sunny Tripower Wechselrichters der Firma SMA über Speedwire (Ethernet).
+Getestet mit Sunny Tripower 6000TL-20, 10000-TL20 sowie 10000TL-10 mit Speedwire/Webconnect Piggyback + +

+ +Define +

    +define <name> SMASTP <pin> <hostname/ip> [port]
    +
    +
  • pin: Benutzer-Passwort des SMA STP Wechselrichters. Default ist 0000. Kann über die Windows-Software "Sunny Explorer" geändert werden
  • +
  • hostname/ip: Hostname oder IP-Adresse des Wechselrichters (bzw. dessen Speedwire Moduls mit Ethernetanschluss)
  • +
  • port: Optional der Ports des Wechselrichters. Per default 9522.
  • +
+ +

+ +Modus +

    +Das Modul erkennt automatisch eine Inaktivität des Wechselrichters, wenn dieser aufgrund Dunkelheit seinen Betrieb einstellt.
    +Diese Betriebspause wird als "Nightmode" bezeichnet. Im Nightmode wird der Wechelrichter nicht mehr über das Netzwerk abgefragt.
    +Per default geht das Modul davon aus, dass vor 5:00 und nach 21:00 der Nightmode aktiv ist.
    +Diese Grenzen lassen sich mit den Parametern "starttime" (Start des Wechelrichterbetriebs, also Ende des Nightmode)
    +und "endtime" (Ende des Wechselrichterbetriebs, also Beginn des Nightmode) umdefinieren.
    +Darüber hinaus gibt es den "Inactivitymode": hier wird der Wechselrichter abgefragt, aber es werden keine Readings mehr aktualisiert.
    +
+ +Parameter +
    +
  • interval: Abfrageinterval in Sekunden
  • +
  • suppress-night-mode: Der Nightmode wird deaktiviert
  • +
  • suppress-inactivity-mode: Der Inactivitymode wird deaktiviert
  • +
  • starttime: Startzeit des Betriebsmodus (Default 5:00 Uhr)
  • +
  • endtime: Endezeit des Betriebsmodus (Default 21:00 Uhr)
  • +
  • force-sleepmode: Der Nightmode wird bei entdeckter Inaktivität auch dann aktiviert, wenn endtime noch nicht erreicht ist
  • +
  • enable-modulstate: Schaltet das reading "modulstate" (normal / inactive / sleeping) ein
  • +
  • alarm1-value, alarm2-value, alarm3-value: Setzt einen Alarm in Watt auf das Reading SpotP. +
    Die Readings Alarm1..Alarm3 werden entsprechend gesetzt: -1 für SpotP < alarmX-value und 1 für Spot >= alarmX-value.
  • +
+ +Readings +
    +
  • SpotP: SpotPower - Leistung in W zum Zeitpunkt der Abfrage
  • +
  • AvP01: Average Power 1 Minute - Durchschnittliche Leistung in W der letzten Minute
  • +
  • AvP05: Average Power 5 Minuten - Durchschnittliche Leistung in W der letzten 5 Minuten
  • +
  • AvP15: Average Power 15 Minuten - Durchschnittliche Leistung in W der letzten 15 Minuten
  • +
  • SpotPDC1: Spot Gleichspannung String 1
  • +
  • SpotPDC2: Spot Gleichspannung String 2
  • +
  • TotalTodayP: Erzeuge Leistung (in Wh) des heutigen Tages
  • +
  • AlltimeTotalP: Erzeugte Leistung (in Wh) seit Inbetriebsnahme des Gerätes
  • +
  • Alarm1..3: Alarm Trigger 1-3. Können über die Parameter "alarmN-value" gesetzt werden
  • +
+ + +=end html_DE diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 8094ab08d..d2c59b44e 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -277,6 +277,8 @@ FHEM/75_msgConfig.pm loredo http://forum.fhem.de Automatis FHEM/76_MSGFile.pm gandy http://forum.fhem.de Automatisierung FHEM/76_MSGMail.pm gandy http://forum.fhem.de Automatisierung FHEM/77_UWZ.pm CoolTux http://forum.fhem.de Unterstuetzende Dienste/Wettermodule +FHEM/77_SMAEM.pm VolkerKettenbach http://forum.fhem.de Sonstige Systeme +FHEM/77_SMASTP.pm VolkerKettenbach http://forum.fhem.de Sonstige Systeme FHEM/79_BDKM.pm arnoaugustin http://forum.fhem.de Heizungssteuerung/Raumklima (bitte auch PM) FHEM/80_M232.pm borisneubert http://forum.fhem.de Sonstige Systeme FHEM/80_xxLG7000.pm markusbloch http://forum.fhem.de Multimedia