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
+
+
+
+
+ Define
+
+ define <name> SMAEM
+
+ Defines a SMA Energy Meter (SMAEM), a bidirectional energy meter/counter used in photovoltaics.
+
+ You need at least one SMAEM on your local subnet or behind a multicast enabled network of routers to receive multicast messages from the SMAEM over the
+ multicast group 239.12.255.254 on udp/9522. Multicast messages are sent by SMAEM once a second (firmware 1.02.04.R, March 2016).
+
+ The update interval will be set by attribute "interval". If not set, it defaults to 60s. Since the SMAEM sends updates once a second, you can
+ update the readings once a second by lowering the interval to 1 (Not recommended, since it puts FHEM under heavy load).
+
+ The parameter "disableSernoInReading" changes the way readings are named: if disableSernoInReading is false or unset, the readings will be named
+ "SMAEMserialnooftheEM_.....
+ If set to true, the prefix SMA_serialnooftheEM_ is skipped.
+ Set this to true if you only have one SMAEM device on your network and you want shorter reading names.
+ If unsure, leave it unset.
+
+ You need the perl module IO::Socket::Multicast. Under Debian (based) systems it can be installed with apt-get install libio-socket-multicast-perl
.
+
+
+
+
+=end html
+
+=begin html_DE
+
+
+SMAEM
+
+
+
+
+ Define
+
+ define <name> SMAEM
+
+ Definiert ein SMA Energy Meter (SMAEM), einen bidirektionalen Stromzähler, der häufig in Photovolatikanlagen der Firma SMA zum Einsatz kommt.
+
+ Sie brauchen mindest ein SMAEM in Ihrem lokalen Netzwerk oder hinter einemmulticast fähigen Netz von Routern, um die Daten des SMAEM über die
+ Multicastgruppe 239.12.255.254 auf udp/9522 zu empfangen. Die Multicastpakete werden vom SMAEM einmal pro Sekunde ausgesendet (firmware 1.02.04.R, März 2016).
+
+ Das update interval kann über das Attribut "interval" gesetzt werden. Wenn es nicht gesetzt wird, werden updates per default alle 60 Sekunden durchgeführt.
+ Da das SMAEM seine Daten sekündlich aktualisiert, kann das update interval auf bis zu einer Sekunde reduziert werden. Das wird nicht empfohlen, da FHEM
+ sonst unter große Last gesetzt wird.
+
+ Der Parameter "disableSernoInReading" ändert die Art und Weise, wie die Readings des SMAEN bezeichnet werden: ist der Parameter false oder nicht gesetzt,
+ werden die Readings mit "SMAEMserialnodesEM_....." bezeichnet.
+ Wird der Parameter auf true gesetzt, wird das Prefix "SMAEMserialnodesEM_....." weg gelassen.
+ Sie können diesen Parameter auf true setzen, wenn Sie nicht mehr als ein SMAEM-Gerät in Ihrem Netzwerk haben und kürzere Namen für die Readings wünschen.
+ Falls Sie unsicher sind, setzen Sie diesen Parameter nicht.
+
+ Sie benötigen das Perl-Module IO::Socket::Multicast für dieses FHEM Modul. Unter Debian (basierten) System, kann dies mittels apt-get install libio-socket-multicast-perl
installiert werden.
+
+
+
+
+
+
+=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