Hallo allerseits,
wer ein SMA Energymeter hat, konnte vermutlich bisher nicht an die per Multicast gelieferten Daten gelangen. In Bezug auf zwei andere Threats hier und hier und mit einem PDF von SMA mit der Protokoll-Doku ("EMETER-Protokoll-TI-de-10.pdf") und ein paar Infos anderen Seiten im www habe ich nun eine lauffähige Lösung, die am Ende überraschend einfach war. Insgesamt war ich mit dem SMA EM schon zuvor zufrieden, denn damit arbeitet meine Batterielösung (SMA sunny Island + home manager) wirklich ordentlich. Mit einer iphone-App konnte ich bereits recht lange auf die Multicast-Daten zugreifen und so nutzen. Sehr hohe Liefer-Frequenz und sehr umfangreich (rund 58 Messwerte). Nun auch in edomi...
Mein aktueller Stand:
Die Lösung scheint mir hinreichend flexibel und verlässlich, ohne sonderlich Ressourcen zu fressen: Die Datenqualität scheint wirklich gut, auf dem Linux-NUC ist das Script als Last nahezu nicht messbar, in edomi ist bei einer Sendefrequenz von 2-3 Sekunden auch wenig zu spüren. Bei sekündlicher Lieferung steigt die Last nur durch die Lieferung reproduzierbar um 1-2% (edomi-NUC).
Voraussetzung ist hier eine andere Linux-Umgebung mit einer höheren Version als edomi. Es gibt sicher verschienden Wege dafür (anderer Linux-Server, VM, ggf. auch auf edomi mit einer zusätzlich(!) installierten höheren PHP-Version,...). Die verwendete Multicast-Lösung geht nicht mit der edomi-PHP-Version 5.3.3! (siehe oben 1. Link auf Threat)
Ich lass' das mal ein paar Tage so laufen. Das Script ist sicher keine Schönheit und (zu) wenig Fehlerprüfung, aber es ist zweckdienlich und bis jetzt problemlos. Schöner geht natürlich immer...
Vielleicht kann es jemand gebrauchen oder sieht Verbesserungspotential.
Viel Spaß bei eigenen Lösungen damit!
Carsten
der Crontab-Eintrag
wer ein SMA Energymeter hat, konnte vermutlich bisher nicht an die per Multicast gelieferten Daten gelangen. In Bezug auf zwei andere Threats hier und hier und mit einem PDF von SMA mit der Protokoll-Doku ("EMETER-Protokoll-TI-de-10.pdf") und ein paar Infos anderen Seiten im www habe ich nun eine lauffähige Lösung, die am Ende überraschend einfach war. Insgesamt war ich mit dem SMA EM schon zuvor zufrieden, denn damit arbeitet meine Batterielösung (SMA sunny Island + home manager) wirklich ordentlich. Mit einer iphone-App konnte ich bereits recht lange auf die Multicast-Daten zugreifen und so nutzen. Sehr hohe Liefer-Frequenz und sehr umfangreich (rund 58 Messwerte). Nun auch in edomi...
Mein aktueller Stand:
- Ein PHP-Script auf meinem Server, dass die Multicast-Daten vom SMA EM liest, lesbar aufbereitet und an edomi per http-API sendet. Das Script kann parametrisiert aufgerufen werden, um den Ausgabeumfang zu wählen: Grunddaten, Phasensummen, Einzelphasen. Für die Summen und Phasen kann noch gewählt werden P, S, Q, U, I cos phi, damit man nur das benötigte zu edomi sendet. Für P, S, Q wird neben dem momentan Wert auch der kumulierte Wert/Zählerstand geliefert. SMA nutzt zur Identifikation der gelieferten Messwerte die OBIS-Identifier - die meisten kennen sicher 1 oder 2 davon: "180" und "280", wenn man die Zählerstände (Wirkarbeit) von seinem Zähler abliest. Das Script erwartet beim Aufruf auch einen Parameter mit der Laufzeit in Minuten.
- Das Script wird per cron alle 15 Minuten aufgerufen mit einem Laufzeitparameter 15 Minuten. Damit sollte der Prozess robust und verlässlich sein (auch nach reboot, etc)
- In edomi 5 KO für Grunddaten, Summen und die 4 Phasen. Die Daten kommen in die KO als String mit ;-Trennung. Werden dann per LBS auf Ziel-KO verteilt. Technisch wird die http-API verwendet (Siehe edomi Hilfe zu lesendem und schreibendem Fernzugriff).
- Die Sendefrequenz des Scripts kann aus edomi heraus über ein 6. KO jederzeit (alle 15 Sekunden) gesteuert werden; in edomi muss das KO in Millisekunden angegeben werden, d.h. "1000" für 1 sek oder "60000" für minütlich. So kann man die Frequenz im Normalfall z.B. auf 5 Sekunden reduzieren, doch wenn man eine Visu-Seite mit Verbrauchsdaten aufruft kann die Frequenz auf 1sek oder noch weniger gesenkt werden bis zur Maximal-Lieferrate des SMA EM.
- Die 6 KO muss für Fernzugriff freigeschaltet sein.
Die Lösung scheint mir hinreichend flexibel und verlässlich, ohne sonderlich Ressourcen zu fressen: Die Datenqualität scheint wirklich gut, auf dem Linux-NUC ist das Script als Last nahezu nicht messbar, in edomi ist bei einer Sendefrequenz von 2-3 Sekunden auch wenig zu spüren. Bei sekündlicher Lieferung steigt die Last nur durch die Lieferung reproduzierbar um 1-2% (edomi-NUC).
Voraussetzung ist hier eine andere Linux-Umgebung mit einer höheren Version als edomi. Es gibt sicher verschienden Wege dafür (anderer Linux-Server, VM, ggf. auch auf edomi mit einer zusätzlich(!) installierten höheren PHP-Version,...). Die verwendete Multicast-Lösung geht nicht mit der edomi-PHP-Version 5.3.3! (siehe oben 1. Link auf Threat)
Ich lass' das mal ein paar Tage so laufen. Das Script ist sicher keine Schönheit und (zu) wenig Fehlerprüfung, aber es ist zweckdienlich und bis jetzt problemlos. Schöner geht natürlich immer...

Viel Spaß bei eigenen Lösungen damit!
Carsten
PHP-Code:
<?php
$len = 700 ; // sind rund 600 Bytes
$flags = 0 ;
$from = "" ;
$adress = "239.12.255.254"; // Multicast IP
$port = 9522 ; // Multicast Port
$targetAPI = "http://<edomi-IP>/remote/?login=remote&pass=remote"; // an eigenen User/PW anpassen: remote/remote ist default)
$targetKO = array("1388","1389","1390","1393","1394"); // Header, Summen, Phase 1, Phase 2, Phase 3
$sleepKO = "1395";
$sleep_old = 0; // should always be zero!
// Ausgabe-Umfang
$options = getopt("r:t::p::");
$runtime = $options["r"]; // Run time until stop [min]
$type = $options["t"]; // Select types of needed output values
$part = $options["p"]; // Select the output parts
if ($runtime===false) { $runtime = 60; }
if ($type=="") {
$type = 'PU'; // P=Wirkleistung,Q=Blindleistung,S=Scheinleistung,I=Strom,U=Spoannung,C=cos phi
}
if ($part=="") {
$part = 'SL'; // H=Header,S=Summen,L=Phasen,v=verbose=lokale Ausgabe
}
echo "Read SMA Energymeter -> Runtime [min]: ".$runtime . " | Type: ". $type . " | Part:" . $part;
// OBIS Parameter
$list_sum = array();
$list_sum[ ] = array('OBIS' => "0140",'type'=>'P','search' => "00010400",'len' => 4,'divisor' => 10);
$list_sum[ ] = array('OBIS' => "0180",'type'=>'P','search' => "00010800",'len' => 8,'divisor' => 3600000);
$list_sum[ ] = array('OBIS' => "0240",'type'=>'P','search' => "00020400",'len' => 4,'divisor' => 10);
$list_sum[ ] = array('OBIS' => "0280",'type'=>'P','search' => "00020800",'len' => 8,'divisor' => 3600000);
$list_sum[ ] = array('OBIS' => "0340",'type'=>'Q','search' => "00030400",'len' => 4,'divisor' => 10);
$list_sum[ ] = array('OBIS' => "0380",'type'=>'Q','search' => "00030800",'len' => 8,'divisor' => 3600000);
$list_sum[ ] = array('OBIS' => "0440",'type'=>'Q','search' => "00040400",'len' => 4,'divisor' => 10);
$list_sum[ ] = array('OBIS' => "0480",'type'=>'Q','search' => "00040800",'len' => 8,'divisor' => 3600000);
$list_sum[ ] = array('OBIS' => "0940",'type'=>'S','search' => "00090400",'len' => 4,'divisor' => 10);
$list_sum[ ] = array('OBIS' => "0980",'type'=>'S','search' => "00090800",'len' => 8,'divisor' => 3600000);
$list_sum[ ] = array('OBIS' => "1040",'type'=>'S','search' => "000a0400",'len' => 4,'divisor' => 10);
$list_sum[ ] = array('OBIS' => "1080",'type'=>'S','search' => "000a0800",'len' => 8,'divisor' => 3600000);
$list_sum[ ] = array('OBIS' => "1340",'type'=>'C','search' => "000d0400",'len' => 4,'divisor' => 1000);
$list_l = array();
$list_l[ ] = array('OBIS' => "2140",'type'=>'P','search' => "00150400",'len' => 4,'divisor' => 10);
$list_l[ ] = array('OBIS' => "2180",'type'=>'P','search' => "00150800",'len' => 8,'divisor' => 3600000);
$list_l[ ] = array('OBIS' => "2240",'type'=>'P','search' => "00160400",'len' => 4,'divisor' => 10);
$list_l[ ] = array('OBIS' => "2280",'type'=>'P','search' => "00160800",'len' => 8,'divisor' => 3600000);
$list_l[ ] = array('OBIS' => "2340",'type'=>'Q','search' => "00170400",'len' => 4,'divisor' => 10);
$list_l[ ] = array('OBIS' => "2380",'type'=>'Q','search' => "00170800",'len' => 8,'divisor' => 3600000);
$list_l[ ] = array('OBIS' => "2440",'type'=>'Q','search' => "00180400",'len' => 4,'divisor' => 10);
$list_l[ ] = array('OBIS' => "2480",'type'=>'Q','search' => "00180800",'len' => 8,'divisor' => 3600000);
$list_l[ ] = array('OBIS' => "2940",'type'=>'S','search' => "001d0400",'len' => 4,'divisor' => 10);
$list_l[ ] = array('OBIS' => "2980",'type'=>'S','search' => "001d0800",'len' => 8,'divisor' => 3600000);
$list_l[ ] = array('OBIS' => "3040",'type'=>'S','search' => "001e0400",'len' => 4,'divisor' => 10);
$list_l[ ] = array('OBIS' => "3080",'type'=>'S','search' => "001e0800",'len' => 8,'divisor' => 3600000);
$list_l[ ] = array('OBIS' => "3140",'type'=>'I','search' => "001f0400",'len' => 4,'divisor' => 1);
$list_l[ ] = array('OBIS' => "3240",'type'=>'U','search' => "00200400",'len' => 4,'divisor' => 1000);
$list_l[ ] = array('OBIS' => "3340",'type'=>'C','search' => "00210400",'len' => 4,'divisor' => 1000);
// Listen to Multicast
$grpparms = array("group"=>$adress,"interface"=>0) ;
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_bind($socket,0,$port);
socket_set_option($socket,IPPROTO_IP,MCAST_JOIN_GROUP,$grpparms);
// Process loop for a defined runtime only...
$time_end = time() - 1 + ( $runtime * 60 );
$time_sleep = time();
while (time()<$time_end) {
socket_recvfrom($socket,$raw,$len,$flags,$from,$port);
// Prepare data
$hraw = bin2hex($raw);
$hraw_head = substr($hraw,0,56);
$hraw_sum = substr($hraw,56,256);
$hraw_l = substr($hraw,312);
// Header
if (strpos($part,'H')!==false) {
$res_head[1] = base_convert(substr($hraw_head,40,8),16,10); // Zähler
$res_head[2] = base_convert(substr($hraw_head,48,8),16,10); // Ticker
$url_data = $targetAPI."&koid=".$targetKO[0]."&kovalue=".implode(';',$res_head);
$handle = fopen($url_data, "r");
}
// Summen
if (strpos($part,'S')!==false) {
$i=0;
foreach ($list_sum as &$record) {
$i++;
$res_sum[$i] = '';
if (strpos($type,$record['type'])===false) { continue; }
$pos = strpos($hraw_sum,$record['search'])+8;
if ($pos !== false) { $res_sum[$i] = base_convert(substr($hraw_sum,$pos,$record['len']*2),16,10) / $record['divisor']; }
}
$url_data = $targetAPI."&koid=".$targetKO[1]."&kovalue=".implode(';',$res_sum);
$handle = fopen($url_data, "r");
if (strpos($part,'v')!==false) { print_r($res_sum); }
}
// Phasen
if (strpos($part,'L')!==false) {
$i=0;
foreach ($list_l as &$record) {
$i++;
$res_l1[$i] = '';
$res_l2[$i] = '';
$res_l3[$i] = '';
if (strpos($type,$record['type'])===false) { continue; }
// L1
$pos = strpos($hraw_l,$record['search'])+8;
if ($pos !== false) { $res_l1[$i] = base_convert(substr($hraw_l,$pos,$record['len']*2),16,10) / $record['divisor']; }
// L2
$tmp = str_split($record['search'], 2);
$tmp[1] = base_convert(base_convert(substr($record['search'],2,2),16,10)+20,10,16); // transpose for L2
$search = implode('',$tmp);
$pos = strpos($hraw_l,$search)+8;
if ($pos !== false) { $res_l2[$i] = base_convert(substr($hraw_l,$pos,$record['len']*2),16,10) / $record['divisor']; }
else { $res_l2[$i] = ''; }
// L3
$tmp = str_split($record['search'], 2);
$tmp[1] = base_convert(base_convert(substr($record['search'],2,2),16,10)+40,10,16); // transpose for L3
$search = implode('',$tmp);
$pos = strpos($hraw_l,$search)+8;
if ($pos !== false) { $res_l3[$i] = base_convert(substr($hraw_l,$pos,$record['len']*2),16,10) / $record['divisor']; }
else { $res_l3[$i] = ''; }
}
$url_data = $targetAPI."&koid=".$targetKO[2]."&kovalue=".implode(';',$res_l1);
$handle = fopen($url_data, "r");
$url_data = $targetAPI."&koid=".$targetKO[3]."&kovalue=".implode(';',$res_l2);
$handle = fopen($url_data, "r");
$url_data = $targetAPI."&koid=".$targetKO[4]."&kovalue=".implode(';',$res_l3);
$handle = fopen($url_data, "r");
if (strpos($part,'v')!==false) { print_r($res_l1); print_r($res_l2); print_r($res_l3); }
}
// get sleeptime = change frequence from edomi
if (time()>=$time_sleep) {
$time_sleep = $time_sleep + 15; // get frequence every x seconds only
$url_data = $targetAPI."&koid=".$sleepKO;
$handle = fopen($url_data, "r");
$value = fread($handle, 6);
if (is_numeric($value)) {
$sleep = intval($value);
} else {
$sleep = 3000; // as default
}
if ($sleep!=$sleep_old) {
echo " | Frequence [ms]: ".$sleep;
$sleep_old = $sleep;
}
}
usleep(1000*$sleep); // keep CPU cold...
}
socket_close($socket);
echo " -> done!\n";
?>
Code:
1,16,31,46 * * * * root php /usr/local/bin/read_sma_energymeter_multicast.php -r15 -tPU -pSL > /dev/null 2> /dev/null
Kommentar