===== SMA Wechselrichter =====
Der Hersteller SMA bietet Solarwechselrichter [[http://www.sma.de/produkte/solarwechselrichter.html]] an,
deren Daten über verschiedenen Schnittstellen ausgelesen werden können. SMA bietet zum Auslesen die
Software "Sunny Explorer" an, die unter Windows läuft.
Für die Nutzung mit der Volkszähler Lösung ist die Quelloffene Software SMASpot besser geeignet, die platttformübergreifend genutzt werden kann, und sich in die Volkszaehler Infrastruktur auf unterschiedliche Weise
einbinden lässt. Auf dieser Seite sind einige Beispiele gezeigt.
==== SMAspot ====
Die Software SMAspot ist bei [[https://smaspot.codeplex.com/]] zu finden.
SMASpot wurde eingestellt, das Projekt wird unter dem Namen SBFspot weitergeführt. [[https://github.com/SBFspot/SBFspot/]]
=== Kompilieren von SMAspot unter Linux ===
Die folgenden Punkte wurden unter Ubuntu 12.04 LTS getestet sollten aber auch in anderen Debian-kompatiblen
Distributionen funktionieren:
- Herunterladen des Sourcecodes z.B. als SMAspot_SRC_244_Linux_Win32.tar.gz z.B. /usr/local/src
- Anlegen eines Verzeichnisses z.b. "smaspot"
- Wechseln in dieses Verzeichnis
- Auspacken mit ../tar xvfz SMAspot_SRC_244_Linux_Win32.tar.gz
- Installieren der nötigen Libraries sudo apt-get install libbluetooth-dev libcurl4-openssl-dev libboost-all-dev
- Übersetzen mit sudo make
- Kopieren des Ergebnisses sudo cp bin/Release/SMASpot /usr/local/bin
=== Anpassen der Konfiguration für Bluetooth ===
== Bluetooth Adapter prüfen ==
Beispiel: Cambridge Silicon Radio Bluetooth Adapter
lsusb | grep -i blue
Bus 002 Device 003: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
== SMA Geräte mit Bluetooth suchen ==
XXXX verdeckt die Angaben des konkreten Beispiels
hcitool scan | grep -i SMA
00:80:25:24:XX:XX SMA002d SN: 200221XXXX SN200221XXXX
00:80:25:29:XX:XX SMA002d SN: 213010XXXX SN213010XXXX
== Ergebnisse in SMASpot.cfg eintragen ==
Wenn mehrere Inverter vorliegen einfach mehrere cfg Dateien erstellen z.B. 4000Watt.cfg, 1300Watt.cfg
== SMASpot testen ==
SMASpot -v
oder
SMASpot -v -cfg4000Watt.cfg
==== Beispiel STP 9000 TL-20 mit SMASpot per Ethernet / Perl->Middleware ====
Ich besitze den Wechselrichter STP 9000TL-20 [[http://www.sma.de/produkte/solar-wechselrichter-ohne-transformator/sunny-tripower-5000tl-6000tl-7000tl-8000tl-9000tl.html]]. Dieser hat direkt einen Ethernetanschluss, der mit dem Tool SMAspot angesprochen werden kann. Das Tool ist zu kompilieren. Sobald es läuft, findet es den Wechselrichter und gibt ein paar Infos aus:
SMAspot -sp0 -v
Ich möchte den Gesamtertrag, sowie den Ertrag je String in den Volkszaehler übernehmen:
Hierzu habe ich für den Gesamtertrag einen Kanal
El. Energie (Zählerstände)
und für den Ertrag je String je einen Kanal
El. Energie (Leistungswerte)
angelegt. Die UUID sind später im Script einzutragen.
Anbei das Script:
#!/usr/bin/perl
use LWP::UserAgent;
open STATUS, "/home/markus/hack/sma/smaspot/bin/Release/SMAspot -sp0 -v |"
or die "can't fork: $!";
while () {
if (/ETotal:[ ]*(.*)kWh/){
&submitt("e63106b0-dd25-11e3-9cd5-27aa144849cd", $1);
}
if (/String 1 Pdc:[ ]*(.*)kW/){
$value = $1;
$value =~ s/\.//g;
&submitt("103ec300-dd27-11e3-84b2-a98f0b16e92d", $value);
}
if (/String 2 Pdc:[ ]*(.*)kW/){
$value = $1;
$value =~ s/\.//g;
&submitt("2d130060-dd27-11e3-b78b-738251a19608", $value);
}
}
close STATUS or die "bad netstat: $! $?";
#------
sub submitt
{
$uuid = $_[0] ;
$val = $_[1] ;
print $uuid . " : " . $val . "\n";
my $server_endpoint = "http://localhost/volkszaehler.org/middleware.php/data/${uuid}.json?value=" . $val;
# get("http://localhost/volkszaehler.org/middleware.php/data/2d130060-dd27-11e3-b78b-738251a19608.json?value=" . $1)."\n";
#print "serverget = " . $server_endpoint . "\n";
# set custom HTTP request header fields
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => 'application/json');
$req->header('x-auth-token' => 'kfksj48sdfj4jd9d');
# add POST data to HTTP request body
$req->content(" ");
my $ua = LWP::UserAgent->new;
my $resp = $ua->request($req);
if ($resp->is_success) {
my $message = $resp->decoded_content;
print "Received reply: $message\n";
} else {
print "HTTP GET error code: ", $resp->code, "\n";
print "HTTP GET error message: ", $resp->message, "\n";
}
}
Das Script wird dabei einmal pro Minute mit cron aufgerufen.
* * * * * /usr/bin/perl /home/markus/hack/Volksz/sma.pl
Um den Eigenverbrauch zu bestimmen bilde ich die Differenz zwischen der Einspeisung, gemessen am Stromzähler. ( mit vzlogger) und der erzeugten Strommenge des SMA. Da der SMA immer etwas verzögert die Werte anzeigt, lege ich nur einen Messpunkt alle 15 Minuten ab: Die Kanäle sind zuvor in der Weboberfläche anzulegen, und mit mysql die channel_id zu selektieren.
Änderung 04.06.2014: Die Berechnung des Gesamtverbrauchs ist aktualisiert.
== Originalscript von Markus ==
#!/bin/bash
echo '
delete from `data` where `timestamp` = (select * from (select max(timestamp) from data where channel_id = 7) x) and channel_id = 7
' | mysql --user=vz --password=fdfdfdfdf volkszaehler -T
echo '
INSERT INTO `data`( `channel_id`, `timestamp`, `value`) select 7, timestamp_2, data_4.value - data_2.value /1000 from (
SELECT
max(case when `channel_id` = 2 then timestamp else 0 end) timestamp_2,
max(case when `channel_id` = 4 then timestamp else 0 end) timestamp_4
FROM `data` where channel_id in (2,4)
and timestamp > (select max(timestamp) from data where `channel_id` = 7)
group by floor(`timestamp`/60/1000/15) ) a inner join data data_2 on timestamp_2 = data_2.timestamp and data_2.channel_id = 2
inner join data data_4 on timestamp_4 = data_4.timestamp and data_4.channel_id = 4
' | mysql --user=vz --password=fdfdfdfdf volkszaehler
echo '
delete from `data` where channel_id = 8
' | mysql --user=vz --password=fdfdfdfdf volkszaehler -T
#and timestamp > (select max(timestamp) from data where `channel_id` = 8)
echo '
INSERT INTO `data`( `channel_id`, `timestamp`, `value`) select 8, timestamp_1, (select data.value from data where channel_id = 7 and data.timestamp <= data_1.timestamp order by timestamp desc limit 1) + data_1.value/1000 from
(
SELECT
max(case when `channel_id` = 1 then timestamp else 0 end) timestamp_1,
max(case when `channel_id` = 7 then timestamp else 0 end) timestamp_7
FROM `data` where channel_id in (1,7)
group by floor(`timestamp`/60/1000/15)
) a inner join data data_1 on timestamp_1 = data_1.timestamp and data_1.channel_id = 1
' | mysql --user=vz --password=dsdsdsdsds volkszaehler
auch diese Script wird mit cron gestartet, aber nur ein mal die Stunde.
1 * * * * bash /home/markus/hack/Volksz/eigenverbrauch.sh
== Modifiziertes Script (2. Versuch von Markus) ==
Läuft Fix, und berechnet den Eigenverbrauch sehr gut, insbesondere bei Lücken in der AUfzeichnung.
#!/usr/bin/perl
use Time::Piece;
use LWP::UserAgent;
use DBI;
#Gesamtverbrauch einbauen
$debug = 0;
#$debug = 1;
my $dbh = DBI->connect("DBI:mysql:database=volkszaehler;", "vz", "Geheimagent",{AutoCommit => 0} ) or die $DBI::errstr;
my $ideigen = "7";
#step_clean();
#step_get_SMA();
step_copy_SMA();
step_max_10();
step_lin();
add_missig_dates(1);
step_gesamtverbrauch();
sub step_get_SMA { # ad300 -> 300 tage
system("/home/markus/hack/sma/SBFspot-3.7.0/SBFspot/mariadb/bin/SBFspot -finq -nocsv -am2 -ad300 -v");
}
sub step_copy_SMA {
my $sth = $dbh->prepare ("insert into volkszaehler.data (channel_id, timestamp, value) SELECT 4 , TimeStamp * 1000, TotalYield/1000 FROM SMA.DayData where cast(TimeStamp/60/10 as int) not in (select cast(timestamp/1000/60/10 as int) from volkszaehler.data where channel_id = 4) and Serial = 304951132");
my $numrows = $sth->execute();
$dbh->commit();
print "step_copy_SMA done - $numrows copy\n";
}
sub step_clean {
my $sth = $dbh->prepare ("delete FROM volkszaehler.`data` where channel_id in (1029, 1030, 1031, 8, ".$ideigen.")");
my $numrows = $sth->execute();
$dbh->commit();
print "step_clean done - $numrows delted\n";
}
sub step_max_10 {xRe9AnfH7b9uNPS8
my $inserted = 0;
# Neue Idee zuerst die 10 Minuten lücken füllen ...
# Danach Linear zur Einspeisung ...
my $sth_4_solar = $dbh->prepare ("SELECT id, channel_id, `timestamp`, value FROM volkszaehler.`data` left outer join (select timestamp as ts1029 from volkszaehler.`data` where channel_id = ".$ideigen." ) x on ts1029 = timestamp where channel_id = 4 and ts1029 is null order by timestamp");
my $sth_2_einsp = $dbh->prepare ("SELECT id, channel_id, `timestamp`, value FROM volkszaehler.`data` where channel_id = 2 order by timestamp");
my $sth = $dbh->prepare ("INSERT into volkszaehler.`data` (channel_id, `timestamp`, value) VALUES (?,?, ?) ");
$sth_4_solar->execute ();
$sth_2_einsp->execute ();
my $timestamp3 = 0;
while (my ($id, $channel_id, $timestamp, $value) = $sth_4_solar->fetchrow_array()) {
while ( $timestamp3 < $timestamp and my ($id2, $channel_id2, $timestamp2, $value2) = $sth_2_einsp->fetchrow_array()){
$timestamp3_old = $timestamp3;
$value3_old = $value3;
$timestamp3 = $timestamp2;
$value3 = $value2/1000;
#print " " .$timestamp3 ."\n";
}
my $time_diff_tot = $timestamp3 - $timestamp3_old;
my $einsp_MS = ($value3 - $value3_old)/ $time_diff_tot;
my $time_diff = $timestamp - $timestamp3_old;
my $einsp_estim = $value3_old + ( ($time_diff ) * $einsp_MS );
my $value_eigenv = $value - $einsp_estim ;
if ($debug > 0) {
print "-------------------------";
print localtime($timestamp/1000)->strftime('%Y-%m-%d-%H:%M:%S'."\n");
print "Zaehlerstand WR: $value \n";
print "Einspeisung:\n";
print " " .localtime($timestamp3_old/1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value3_old \n");
print " NEW" .localtime($timestamp /1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $einsp_estim \n");
print " " .localtime($timestamp3 /1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value3 \n");
print "Zeitdifferenz Einspeisung:". ( $timestamp3 - $timestamp3_old ) ." \n";
print "Zaehlerdifferenz Einspeisung:". ( $value3 - $value3_old ) ." \n";
print "einspeisung MS:". $einsp_MS ." \n";
print "eigenverbrauch:". $value_eigenv." \n";
}
if ($time_diff_tot< (1000*60*10)){ # nur bis 10 Minuten pausen
$inserted += 1;
$sth->execute($ideigen, $timestamp, $value_eigenv);
$sth->execute(2, $timestamp, $einsp_estim*1000);
}
}
$dbh->commit();
print "step_max_10 done $inserted affected\n";
}
sub step_lin{
my $inserted = 0;
# Neue Idee zuerst die 10 Minuten lücken füllen ...
# Danach Linear zur Einspeisung ...
my $sth_4_solar = $dbh->prepare ("SELECT id, channel_id, `timestamp`, value FROM volkszaehler.`data` left outer join (select timestamp as ts1029 from volkszaehler.`data` where channel_id = ".$ideigen." ) x on ts1029 = timestamp where channel_id = 4 and ts1029 is null order by timestamp");
my $sth_2_einsp = $dbh->prepare ("select timestamp4, value1029, value4 FROM
(SELECT `timestamp` as timestamp4, value as value4 FROM volkszaehler.`data` where channel_id = 4 ) as z4 inner join
(SELECT channel_id, `timestamp` as timestamp1029, value as value1029 FROM volkszaehler.`data` where channel_id = ".$ideigen." ) as z2 on timestamp4 = timestamp1029
order by timestamp4");
my $sth = $dbh->prepare ("INSERT into volkszaehler.`data` (channel_id, `timestamp`, value) VALUES (?,?, ?) ");
$sth_4_solar->execute ();
$sth_2_einsp->execute ();
my $timestamp3 = 0;
while (my ($id, $channel_id, $timestamp, $value) = $sth_4_solar->fetchrow_array()) {
#print localtime($timestamp /1000)->strftime('%Y-%m-%d-%H:%M:%S'."\n");
while ( $timestamp3 < $timestamp and my ($timestamp2, $value1029, $value4, ) = $sth_2_einsp->fetchrow_array()){
$timestamp3_old = $timestamp3;
$value1029_old = $value1029_new;
$value4_old = $value4_new;
$timestamp3 = $timestamp2;
$value1029_new = $value1029;
$value4_new = $value4;
#print " " .$timestamp3 ."\n";
}
#my $einsp_MS = ($value3 - $value3_old)/ ( $timestamp3 - $timestamp3_old);
#my $time_diff = $timestamp - $timestamp3_old;
#my $einsp_estim = $value3_old + ( ($time_diff ) * $einsp_MS );
#my $value_eigenv = $value - $einsp_estim ;
my $diffeinspeisung = $value1029_new - $value1029_old;
my $differz = $value4_new - $value4_old;
my $differz_daz = $value - $value4_old;
if ($differz > 0){
my $anteil = $diffeinspeisung /$differz ;
my $diff_ber = $differz_daz * $anteil;
my $value_ber = $diff_ber + $value1029_old;
my $value_ber_einsp = $value- $value_ber;
if ($debug > 0) {
print "-----------------------------------\n";
print "Zeit:\n";
print " ALT:" .localtime($timestamp3_old/1000)->strftime('%Y-%m-%d-%H:%M:%S'." \n");
print " DAZ:" .localtime($timestamp /1000)->strftime('%Y-%m-%d-%H:%M:%S'." \n");
print " NEU:" .localtime($timestamp3 /1000)->strftime('%Y-%m-%d-%H:%M:%S'." \n");
print "\n";
print "Zaehlerdifferenz EIGENVERB\n";
print " NEU: $value1029_new\n" ;
print " ALT: $value1029_old\n" ;
print " --------------\n" ;
print " $diffeinspeisung\n";
print "\n";
print "Zaehlerdifferenz Erzeugung\n";
print " NEU: $value4_new\n" ;
print " ALT: $value4_old\n" ;
print " --------------\n" ;
print " $differz\n";
print "\n";
print "ANTEIL: $anteil\n";
print "\n";
print "Zaehlerdifferenz Erzeugung2\n";
print " DAZ: $value\n" ;
print " ALT: $value4_old\n" ;
print " --------------\n" ;
print " $differz_daz \n";
print "\n";
print "Differenz Berechnet: $diff_ber\n";
print "Zähler NEU: $value_ber \n";
print "Zähler Einspeisung: $value_ber_einsp \n";
print "\n";
}
$inserted += 1;
$sth->execute($ideigen, $timestamp, $value_ber);
$sth->execute(2, $timestamp, $value_ber_einsp*1000);
}
}
$dbh->commit();
print "step_lin done $inserted affected\n";
}
sub step_gesamtverbrauch {
#my $sth = $dbh->prepare ("delete FROM volkszaehler.`data` where channel_id in ( 8)");
#my $numrows = $sth->execute();
my $inserted = 0;
# Neue Idee zuerst die 10 Minuten lücken füllen ...
# Danach Linear zur Einspeisung ...
my $sth_4_solar = $dbh->prepare ("SELECT id, channel_id, `timestamp`, value FROM volkszaehler.`data` left outer join (select timestamp as ts1029 from volkszaehler.`data` where channel_id = 8 ) x on ts1029 = timestamp where channel_id = 1 and ts1029 is null and timestamp < (SELECT max(`timestamp`) FROM volkszaehler.`data` where channel_id = 7 ) order by timestamp");
my $sth_2_einsp = $dbh->prepare ("SELECT id, channel_id, `timestamp`, value FROM volkszaehler.`data` where channel_id = 7 order by timestamp");
my $sth = $dbh->prepare ("INSERT into volkszaehler.`data` (channel_id, `timestamp`, value) VALUES (?,?, ?) ");
$sth_4_solar->execute ();
$sth_2_einsp->execute ();
my $timestamp3 = 0;
while (my ($id, $channel_id, $timestamp, $value) = $sth_4_solar->fetchrow_array()) {
while ( $timestamp3 < $timestamp and my ($id2, $channel_id2, $timestamp2, $value2) = $sth_2_einsp->fetchrow_array()){
$timestamp3_old = $timestamp3;
$value3_old = $value3;
$timestamp3 = $timestamp2;
$value3 = $value2;
#print " " .$timestamp3 ."\n";
}
my $time_diff_tot = $timestamp3 - $timestamp3_old;
my $einsp_MS = ($value3 - $value3_old)/ $time_diff_tot;
my $time_diff = $timestamp - $timestamp3_old;
my $einsp_estim = $value3_old + ( ($time_diff ) * $einsp_MS );
my $value_eigenv = $value/1000 + $einsp_estim ;
if ($debug > 0) {
print "-------------------------";
print localtime($timestamp/1000)->strftime('%Y-%m-%d-%H:%M:%S'."\n");
print "Zaehlerstand BEZUG: $value \n";
print "Eigenverbrauch:\n";
print " " .localtime($timestamp3_old/1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value3_old \n");
print " NEW" .localtime($timestamp /1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $einsp_estim \n");
print " " .localtime($timestamp3 /1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value3 \n");
print "Zeitdifferenz Einspeisung:". ( $timestamp3 - $timestamp3_old ) ." \n";
print "Zaehlerdifferenz Einspeisung:". ( $value3 - $value3_old ) ." \n";
print "einspeisung MS:". $einsp_MS ." \n";
print "Gesamtverbauch:". $value_eigenv." \n";
}
#if ($time_diff_tot< (1000*60*10)){ # nur bis 10 Minuten pausen
$inserted += 1;
$sth->execute(8, $timestamp, $value_eigenv);
#$sth->execute(1031, $timestamp, $einsp_estim*1000);
#}
}
$dbh->commit();
print "step_max_10 done $inserted affected\n";
}
sub add_missig_dates {
my ($id) = @_;
$inserted = 0;
#my $id = 1;
my $timestamp3 = 0;
my $sth = $dbh->prepare ("SELECT id, channel_id, `timestamp`, value FROM volkszaehler.`data` where channel_id = $id order by timestamp");
$sth->execute ();
my $sth2 = $dbh->prepare ("INSERT into volkszaehler.`data` (channel_id, `timestamp`, value) VALUES (?,?, ?) ");
my $t = Time::Piece->strptime("2014-06-01", "%Y-%m-%d");
print $t->epoch*1000;
for (my $i= $t->epoch - 60*60*2-1; $i <= localtime; $i = $i + (24*60*60)) {
# print "$i\n";
my $timestamp = $i*1000;
while ( $timestamp3 < $timestamp and my ($id2, $channel_id2, $timestamp2, $value2) = $sth->fetchrow_array()){
$timestamp3_old = $timestamp3;
$value3_old = $value3;
$timestamp3 = $timestamp2;
$value3 = $value2;
#print " " .$timestamp3 ."\n";
}
my $time_diff_tot = $timestamp3 - $timestamp3_old;
my $einsp_MS = ($value3 - $value3_old)/ ( $time_diff_tot);
my $time_diff = $timestamp - $timestamp3_old;
my $value = ($value3_old + ( ($time_diff ) * $einsp_MS )) ;
if ($time_diff_tot> (1000*60*60*5)){ # nur bis 10 Minuten pausen
if ($debug > 0) {
print "-------------------------\n$id\n";
print localtime($timestamp/1000)->strftime('%Y-%m-%d-%H:%M:%S'."\n");
print " " .localtime($timestamp3_old/1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value3_old \n");
print "NEW " .localtime($timestamp /1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value \n");
print " " .localtime($timestamp3 /1000)->strftime('%Y-%m-%d-%H:%M:%S'.": $value3 \n");
print "Zeitdifferenz Einspeisung:". ( $timestamp3 - $timestamp3_old ) ." \n";
}
$inserted += 1;
$sth2->execute($id, $timestamp, $value);
}
}
$dbh->commit();
print "step_max_10 done $inserted affected - $id\n";
}
== Modifiziertes Script (Versuch von Wolfgang) ==
#!/bin/bash
# calculate own PV usage
# Author: Markus 2014-05-20
# latest version:
# $Header: /home/wf/smaspot/RCS/eigenverbrauch.bash,v 1.5 2014/05/29 10:17:11 wf Exp wf $
#
# get the delete query for the given channel
#
deletequery() {
local l_channel=$1
cat << EOF
DELETE
FROM `data`
WHERE `timestamp` =
(SELECT MAX(timestamp) FROM data WHERE channel_id = $l_channel)
AND channel_id = $l_channel;
EOF
}
#
# get the insert query for the given channels
# l_chanel the channel to insert into
# l_c2 - the channel to substract
# l_c3 - the channel to substract the value from
#
insertquery() {
local l_channel=$1
local l_c2=$2
local l_c3=$3
cat << EOF
INSERT INTO `data`( `channel_id`, `timestamp`, `value`)
SELECT $l_chanel, timestamp_2, data_3.value - data_2.value /1000
FROM (
SELECT
MAX(CASE WHEN `channel_id` = $l_c2 then timestamp else 0 end) timestamp_2,
MAX(CASE WHEN `channel_id` = $l_c3 then timestamp else 0 end) timestamp_3
FROM `data`
WHERE channel_id in ($l_c2,$l_c3)
AND timestamp > (
SELECT MAX(timestamp) FROM data WHERE `channel_id` = $l_channel
)
GROUP by floor(`timestamp`/60/1000/15) ) a
INNER JOIN data data_2
ON timestamp_2 = data_2.timestamp
AND data_2.channel_id = $l_c2
INNER JOIN data data_3
ON timestamp_3 = data_3.timestamp
AND data_3.channel_id = $l_c3
EOF
}
# modify according to your volkszaehler mysql database settings
user=vz
password=fdfdfdfdf
db=volkszaehler
# debug setting
debug=
# uncomment to debug sql
#debug=-T
# modify to suite your channel settings
# channel 7 is calculated from as channel 4 - channel 2 / 1000
deletequery 7 | egrep -v "^#" | mysql --user=$user --password=$password $db $debug
insertquery 7 2 4 | egrep -v "^#" | mysql --user=$user --password=$password $db $debug
# channel 8 is calculated from as channel 7 - channel 1 / 1000
echo "DELETE FROM `data` WHERE channel_id = 8" | mysql --user=$user --password=$password $db $debug
insertquery 8 1 7 | egrep -v "^#" | mysql --user=$user --password=$password $db $debug
Und so sieht es aus:
{{:howto:screenshotsma.jpg?800|}}
==== Beispiel SB 4000 TL-21 + SB 1300TL-10 mit SMASpot und PHP ====
The solution consists of three parts:
- the bash script sma2vz
- the php script sma2vz.php
- the php helper script vzapihelper.php (same as in the youless page)
the bash script sma2vz has a function configure - this needs to run once you might want
to configure the here-document part in the function inverters of this script
there is a help screen available:
./sma2vz --help
usage: ./sma2vz --vzurl=vzurl --cuuid_pwr=x --cuuid_kwh=y
[ --daytimeonly --lat=lattitude --lon=longitude]
[--loop --delay=delay]
| [--help]
| [--configure]
--vzurl=
volkszaehler middleware url
--cuuid_pwr=
channel uuid for power (watt) PV output
--cuuid_kwh=
channel uuid for energy (kwH) PV total
--daytimeonly
do not post data at night (e.g. if your device does not supply data)
--lon=
plant longitude geo coordinate
--lat=
plant lattitude geo coordinate
--loop
poll SMA inverters in a loop
--delay=
how many secs to wait between each reading (default: 15 secs)
--configure
create SMAsport configuration file(s)
modify ./sma2vz inverters shell function
here document to fit your plant's data
./sma2vz --lat 51.244 --lon 6.52 --configure
./sma2vz \
--vzurl "http://capri/vz/middleware.php/data" \
--cuuid_pwr "7744bbf0-e74d-11e3-9ec7-xxxx" \
--cuuid_kwh "460feba0-e74f-11e3-8a0d-xxxx" \
--daytimeonly --lat 51.244 --lon 6.52 \
--loop --delay 15
cd /home/wf/smaspot
/home/wf/smaspot/sma2vz \
--vzurl "http://capri/vz/middleware.php/data" \
--cuuid_pwr "7744bbf0-e74d-11e3-9ec7-xxxx" \
--cuuid_kwh "460feba0-e74f-11e3-8a0d-xxxx" \
--daytimeonly --lat 51.244 --lon 6.52 >> /var/log/energymeter.log
2014-06-01 12:52:08 PV: 1409 Watt 6699.801 kwH sun rise:05:21 set:21:42
2014-06-01 12:53:07 PV: 2176 Watt 6699.826 kwH sun rise:05:21 set:21:42
2014-06-01 12:54:07 PV: 1699 Watt 6699.855 kwH sun rise:05:21 set:21:42
2014-06-01 12:55:07 PV: 3375 Watt 6699.898 kwH sun rise:05:21 set:21:42
2014-06-01 12:56:07 PV: 3578 Watt 6699.957 kwH sun rise:05:21 set:21:42
Any feedback please to wf (at) bitplan.com - enjoy!
#/bin/bash
# SMAspot with Volkszaehler
# WF 2014-05-29
# $Header: /home/wf/smaspot/RCS/sma2vz,v 1.8 2014/06/01 10:49:38 wf Exp wf $
#
# get a configuration
# param 1: bluetooth address
# param 2: name of configuration/plant
# param 3: "USER" password for SMA device
# param 4: latitude of plant
# param 5: longitude of plant
# param 6: path to output files of SMAspot
#
smaspot_config() {
local l_btaddr=$1
local l_name=$2
local l_password=$3
local l_lon=$4
local l_lat=$5
local l_path=$6
cat << EOF
################################################################################
# ____ __ __ _ _
# / ___|| \/ | / \ ___ _ __ ___ | |_
# \___ \| |\/| | / _ \ / __| '_ \ / _ \| __|
# ___) | | | |/ ___ \\__ \ |_) | (_) | |_
# |____/|_| |_/_/ \_\___/ .__/ \___/ \__|
# |_|
#
# SMAspot.cfg - Configuration file for SMAspot.exe
# SMAspot - Yet another tool to read power production of SMA solar inverters
# (c)2012-2014, SBF
#
# DISCLAIMER:
# A user of SMAspot software acknowledges that he or she is receiving this
# software on an "as is" basis and the user is not relying on the accuracy
# or functionality of the software for any purpose. The user further
# acknowledges that any use of this software will be at his own risk
# and the copyright owner accepts no responsibility whatsoever arising from
# the use or application of the software.
#
################################################################################
# SMA Inverter's Bluetooth address
# Windows: smaspot -scan
# Linux : hcitool scan
# IMPORTANT FOR SPEEDWIRE USERS: COMMENT OUT BTADDRESS (PUT # IN FRONT)
BTAddress=$l_btaddr
# SMA Inverter's Speedwire IP address
# If IP_Address is not set or is 0.0.0.0 SMAspot will try to detect the speedwire inverter by broadcast
# If IP_Address is set to a valid IP, SMAspot will try to connect directly to that IP without broadcast detection
#IP_Address=0.0.0.0
# User password (default 0000)
Password=$l_password
# MIS_Enabled (Multi Inverter Support: Default=0 Disabled)
# +------------+-------+-------------+
# | #Inverters | NetID | MIS_Enabled |
# +------------+-------+-------------+
# | 1 | 1 | Don't Care |
# +------------+-------+-------------+
# | 1 | >1 | 0 |
# +------------+-------+-------------+
# | >1 | >1 | 1 |
# +------------+-------+-------------+
MIS_Enabled=0
# Plantname
Plantname=$l_name
# OutputPath (Place to store CSV files)
#
# Windows: C:\TEMP\SMA\%Y
# Linux : /home/sbf/Documents/sma/%Y
# %Y %m and %d will be expanded to Year Month and Day
OutputPath=$l_path/%Y
# OutputPathEvents (Place to store CSV files for events)
# If omitted, OutputPath is used
OutputPathEvents=$l_path/Events
# Position of pv-plant http://itouchmap.com/latlong.html
# Example for Ukkel, Belgium
Latitude=$l_lat
Longitude=$l_lon
# Calculate Missing SpotValues
# If set to 1, values not provided by inverter will be calculated
# eg: Pdc1 = Idc1 * Udc1
CalculateMissingSpotValues=1
# DateTimeFormat (default %d/%m/%Y %H:%M:%S)
# For details see strftime() function
# http://www.cplusplus.com/reference/clibrary/ctime/strftime/
DateTimeFormat=%Y-%m-%d %H:%M:%S
# DateFormat (default %d/%m/%Y)
DateFormat=%-%m-%d
# DecimalPoint (comma/point default comma)
DecimalPoint=point
# TimeFormat (default %H:%M:%S)
TimeFormat=%H:%M:%S
# SynchTime (default 1 = On)
# If set to 1 the Inverter time is synchronised with pc time
# Some inverters don't have a real-time clock
SynchTime=1
# SunRSOffset
# Offset to start before sunrise and end after sunset (0-3600 - default 900 seconds)
SunRSOffset=900
# Locale
# Translate Entries in CSV files
# Surpported locales: de-DE;en-US;fr-FR;nl-NL;es-ES;it-IT
# Default en-US
Locale=de-DE
# Timezone
# Select the right timezone in date_time_zonespec.csv
# e.g. Timezone=Europe/Brussels
Timezone=Europe/Berlin
###########################
### CSV Export Settings ###
###########################
# With CSV_* settings you can define the CSV file format
# CSV_Export (default 1 = Enabled)
# Enables or disables the CSV Export functionality
CSV_Export=1
# CSV_ExtendedHeader (default 1 = On)
# Enables or disables the SMA extended header info (8 lines)
# isep=;
# Version CSV1|Tool SMAspot|Linebreaks CR/LF|Delimiter semicolon|Decimalpoint comma|Precision 3
# etc...
# This is usefull for manual data upload to pvoutput.org
CSV_ExtendedHeader=1
# CSV_Header (default 1 = On)
# Enables or disables the CSV data header info (1 line)
# dd/MM/yyyy HH:mm:ss;kWh;kW
# This is usefull for manual data upload to pvoutput.org
# If CSV_ExtendedHeader is enabled, CSV_Header is also enabled
CSV_Header=1
# CSV_SaveZeroPower (default 1 = On)
# When enabled, daily csv files contain all data from 00:00 to 23:55
# This is usefull for manual data upload to pvoutput.org
CSV_SaveZeroPower=1
# CSV_Delimiter (comma/semicolon default semicolon)
CSV_Delimiter=semicolon
# CSV_Spot_TimeSource (Inverter|Computer default Inverter)
CSV_Spot_TimeSource=Inverter
# CSV_Spot_WebboxHeader (Default 0 = Off)
# When enabled, use Webbox style header (DcMs.Watt[A];DcMs.Watt[B]...)
CSV_Spot_WebboxHeader=0
#################################
### Online Monitoring Systems ###
#################################
#
# In the future, multiple online monitoring systems can be defined
# Here we can activate the ones we like
#
################################
### PVoutput Upload Settings ###
################################
# PVoutput (default 0 = Disabled)
# Enables or disables the upload functionality to pvoutput.org
# When enabled, be sure to use -u switch on the command line
PVoutput=0
#PVoutput_SID
#Sets PVoutput System ID
PVoutput_SID=
#PVoutput_Key
#Sets PVoutput API Key
PVoutput_Key=
# VoltageLogging sets AC or DC logging.
# Possible values are:
# NONE (disabled)
# MAX(AC) (default)
# AC(PH1) or AC(PH2) or AC(PH3)
# MAX(DC) or DC(ST1) or DC(ST2)
VoltLogging=MAX(AC)
# InverterTemp (default 0 = disabled)
# Enables or disables the upload of the inverter's temperature
InverterTemp=0
# InverterTempMapTo (default v5 = Use standard PVoutput Temperature Graph)
# In Donation Mode only, map inverter's temperature to extended data field (v7..v12)
# For more info, see http://pvoutput.org/help.html#donations
InverterTempMapTo=v5
# CumulativeEnergy (default 0 = Today's Energy)
# Set the cumulative flag = 1 when you wish to pass lifetime energy or 0 for today's energy
# WARNING!!! DO NOT CHANGE THIS FLAG DURING DAYLIGHT AS THIS WILL MESS UP YOUR PVOUTPUT GRAPHS
CumulativeEnergy=0
EOF
}
#
#inverters
#
inverters() {
# insert your data from
# hcitool scan | grep SMA
#
# first column is bluetooth address
# second column is the name of the device (here just the wattage is used)
# third column is the password for "USER" that the SMA Device expects
# fourth column is the latitude
# fifth column is the longitude
cat << EOF
00:80:25:24:xx:xy 1300Watt password /home/wf/smaspot
00:80:25:29:xx:xy 4000Watt password /home/wf/smaspot
EOF
}
#
# create configuration files
#
configure() {
checklonlat "configure"
inverters | while read btaddr name password path; do
echo "creating ${name}.cfg for bluetooth addr $btaddr" 1>&2
smaspot_config $btaddr $name $password $lon $lat $path > ${name}.cfg
done
}
#
# get the sma meter reading
#
getsmameter() {
# temp filename base for SMAspor result
tmp=/tmp/smaspot$$
# read data from all inverters
inverters | while read btaddr name password path; do
# single shot run of SMAspot with no CVS export
# uncomment to debug
#echo "running smaspot with ${name}.cfg for bluetooth addr $btaddr"
./SMAspot -v -nocsv -cfg${name}.cfg > ${tmp}_${name}
# the lines we need look like:
# EToday: 3.358kWh
# ETotal: 5151.294kWh
# Total Pac : 0.442kW
# let's filter the result with awk
cat ${tmp}_${name} | awk '
# set the field separator fitting the x: y format
BEGIN { FS=":";doublequote="\x22" }
# check the input lines for the three patterns and remove
# the unit at the end - assign to the three variables
# etoday, etotal and totalpac
/EToday:/ { etoday =$2;gsub("kWh","",etoday) }
/ETotal:/ { etotal =$2;gsub("kWh","",etotal) }
/Total Pac/ { totalpac=$2;gsub("kW" ,"",totalpac) }
# at the end of all lines print out a single json formatted result line
END {
printf("{%s,%s,%s}\n",
json("etoday",etoday),
json("etotal",etotal),
json("totalpac",totalpac))
}
# helper function to create json name values
function json(name,value,result) {
# trim value
gsub(" ","",value);
result=quote(name)":"quote(value);
return result
}
# helper function to quote a string
function quote(s,result) {
result=doublequote s doublequote;
return result
}'
rm ${tmp}_${name}
done
}
#
# show usage
#
usage() {
cat << EOF
usage: $0 --vzurl=vzurl --cuuid_pwr=x --cuuid_kwh=y
[ --daytimeonly --lat=lattitude --lon=longitude]
[--loop --delay=delay]
| [--help]
| [--configure]
--vzurl=
volkszaehler middleware url
--cuuid_pwr=
channel uuid for power (watt) PV output
--cuuid_kwh=
channel uuid for energy (kwH) PV total
--daytimeonly
do not post data at night (e.g. if your device does not supply data)
--lon=
plant longitude geo coordinate
--lat=
plant lattitude geo coordinate
--loop
poll SMA inverters in a loop
--delay=
how many secs to wait between each reading (default: 15 secs)
--configure
create SMAsport configuration file(s)
modify $0 inverters shell function
here document to fit your plant's data
EOF
exit 1
}
#
# show error and exit
#
error() {
local l_msg=$1
local l_hint=$2
echo "error $0: $l_msg" 1>&2
echo " you might want to $l_hint !" 1>&2
exit 1
}
#
# check that longitude and lattitude are supplied
#
checklonlat() {
local l_title="$1"
if [ "$lon" == "" ]
then
error "option --lon" "supply lon longitude setting for $l_title to work"
fi
if [ "$lat" == "" ]
then
error "option --lat" "supply lat lattitude setting for $l_title to work"
fi
}
# defaults
delay=15
maxloops=1
daytimeonly=0
# check command line arguments
while [ $# -gt 0 ]
do
arg="$1"
#echo "$arg"
case "$arg" in
--help)
usage
;;
--vzurl)
vzurl="$2"
shift
;;
--cuuid_pwr)
cuuid_pwr="$2"
shift
;;
--cuuid_kwh)
cuuid_kwh="$2"
#echo $cuuid_kwh
shift
;;
--daytimeonly)
daytimeonly=1;
;;
--lat)
lat="$2"
shift
;;
--lon)
lon="$2"
shift
;;
--delay)
delay=$2
shift
;;
--loop)
maxloops=10000000;
;;
--configure)
echo "creating configuration files"
configure
exit
;;
*)
echo >&2 "Invalid argument: $1"
exit 1
;;
esac
shift
done
# check that options are set
if [ "$vzurl" == "" ]
then
error "option --vzurl is missing" "check and use volkszaehler middleware url"
fi
if [ "${cuuid_pwr}" == "" ]
then
error "option --cuuid_pwr is missing" "check the uuid for the PV power (Watt) channel"
fi
if [ "$cuuid_kwh" == "" ]
then
error "option --cuuid_kwh" "check the uuid for the PV energy (kwH) channel"
fi
if [ "$daytimeonly" -eq 1 ]
then
checklonlat "daytimeonly"
fi
loop=0
while [ $loop -lt $maxloops ]
do
# get the SMA meter readings
getsmameter | php sma2vz.php --vzurl=$vzurl --cuuid_pwr=$cuuid_pwr --cuuid_kwh=$cuuid_kwh --daytimeonly=$daytimeonly --lat=$lat --lon=$lon
# sleep a while
if [ $maxloops -gt 1 ]
then
sleep $delay
fi
loop=`expr $loop + 1 `
done
$sunrise_epoch);
$result["rise"] = $sunrise_time;
$result["set"] = $sunset_time;
$result["time"] = date("Y-m-d H:i:s",time());
return $result;
}
$loop=0;
// possible command line options
// --vzurl= --cuuid_pwr= --cuuid_kwh= --daytimeonly
$longopts=array("vzurl:","cuuid_pwr:","cuuid_kwh:","lat:","lon:","daytimeonly:");
$shortopts="";
$options=getopt($shortopts,$longopts);
// the volkszaehler middleware url
$vzurl=checkoption("vzurl",$options);
// channel uuids
// power (watts)
$cuuid_pwr=checkoption("cuuid_pwr",$options);
// energy (kWh)
$cuuid_kwh=checkoption("cuuid_kwh",$options);
// daytimeonly?
$daytimeonly=checkoption("daytimeonly",$options);
if ($daytimeonly) {
$latitude=checkoption("lat",$options);
$longitude=checkoption("lon",$options);
}
$jsonlines=file("php://stdin");
$tmeter=array();
foreach ($jsonlines as $line_nume => $json) {
// get the meter reading
$meter=json_decode($json, true);
#var_dump($meter);
foreach ($meter as $name => $value) {
if (!array_key_exists($name,$tmeter))
$tmeter[$name]=0;
$tmeter[$name]+=$value;
}
}
#var_dump($tmeter);
//array(3) { ["etoday"]=> float(2.52)
// ["etotal"]=> float(6632.462)
//["totalpac"]=> float(2.478) }
$etotal=$tmeter["etotal"];
$totalpac=$tmeter["totalpac"]*1000;
if ($daytimeonly) {
$daytime=daytime($latitude,$longitude);
if (!$daytime["daytime"]) {
printf("%s after sunset: %s wait for sunrise: %s\n",$daytime["time"],$daytime["set"],$daytime["rise"]);
exit(2);
}
}
# total wattage of all inverters
post2vz($vzurl,$cuuid_pwr,$totalpac);
# total kwh of all inverters
post2vz($vzurl,$cuuid_kwh,$etotal);
if ($daytimeonly) {
printf("%s PV: % 5d Watt % 10.3f kwH sun rise:%s set:%s\n",$daytime["time"],$totalpac,$etotal,$daytime["rise"],$daytime["set"]);
} else {
printf("PV Current/Total: %4.0f Watt %.3f kwH\n",$totalpac,$etotal);
}
exit(0);
?>
$value) {
$fields_string .= $key.'='.$value.'&';
}
rtrim($fields_string, '&');
curl_setopt($ch,CURLOPT_POST, count($fields));
curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string);
$result=curl_exec($ch);
return $result;
}
/**
* post data to vz middleware
* param 1: vzurl - middleware url of volkszaehler
* param 2: channel uuid
* param 3: value to post
*/
function post2vz($vzurl,$cuuid,$value,$debug=false) {
// post data to middleware according to:
// http://wiki.volkszaehler.org/development/api/reference
// adapt timestamp to volkszaehler conventions
$timestamp=time()*1000;
# first
$posturl=$vzurl."/".$cuuid.".json";
$fields=array("ts"=>$timestamp,"value" => $value );
$presult=postUrl($posturl,$fields);
if ($debug) {
echo $presult;
}
}
/**
* check that option $opt is available in $options
* return the value if available
*/
function checkoption($opt,$options) {
if (array_key_exists($opt,$options))
return $options[$opt];
else
die("option $opt missing!\n");
}
?>
===== SMA Sunny Boy =====
Den SMA Sunny Boy kann man per URL auslesen, dazu muss man die Default seite von dem Sunny Boy freischalten, so dass man auch online login die Daten sehen kann.
Dann kann man per request die JSON daten abfragen.
http://sunnyboyurl/dyn/getDashValues.json
json response example:
...
"6100_40263F00": {
"1": [
{
"val": 1488
}
]
},
...
Hierzu ein python script, dass den aktullen Wert in eine Datei schreibt, die wir dann mit dem vzlogger auslesen können:
import requests
import time
import logging
import argparse
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)-12.12s] [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s",
handlers=[logging.StreamHandler()],
)
sma_host = : str = None
out_put_file : str = None
def pullData():
try:
data = requests.get(
"http://" + sma_host + "/dyn/getDashValues.json", timeout=5
).json()
value = data["result"]["012F-730C09D7"]["6100_40263F00"]["1"][0]["val"]
logging.debug(data)
if value:
return value
except Exception as e:
logging.error("Unexpected error: {}".format(e))
return -1
def main():
while True:
value = pullData()
if value != -1:
if out_put_file:
f = open(out_put_file, "w")
f.write(str(value))
f.close()
else:
print(value)
time.sleep(5)
if __name__ == "__main__":
print("SMA sunny boy file writer logger v0.1")
parser = argparse.ArgumentParser()
parser.add_argument("--sma", "-s", type=str, help="sma sunny boy host name")
parser.add_argument("--out", "-o", type=str, help="output file name is not set sysout is used")
args = parser.parse_args()
if not args.sma:
print("missing sma host name parameter")
exit(-1)
#global sma_host
sma_host = args.sma
print("connect to sma host: " + sma_host)
if args.out:
out_put_file = args.out
print("use output file: " + out_put_file)
else:
print("output file not set use sysout")
main()
vzlogger.conf file meter:
{
"enabled": true,
"allowskip": false,
"aggtime": 300,
"channels": [
{
"api": "volkszaehler",
"uuid": "60964f00-9f85-11ea-8200-37539aba2eb7",
"identifier": "",
"middleware": "http://localhost/middleware.php",
"aggmode": "AVG",
"duplicates": 0
}
],
"protocol": "file",
"path": "/home/pi/sma/sma.log",
"rewind": true
}