bei mir zuhause ist Loxone im Einsatz, ein Speicher implementiert, ein Tesla vorhanden, eine PV Anlage am werkeln usw. Ich hab mich vor einigen Tages mal für Tibber interessiert als neuen Stromanbieter. Da ich wechseln kann hab ich das auch getan.
Zu dem Thema hab ich diesen Thread erstellt als die Welt noch sehr sehr unklar war :-)
Nun, ich hab mich die letzten tage sehr intensiv mit dem ganzen Thema beschäftigt und wollte gerne meine Erkenntnisse teilen und vor allem auch verifizieren lassen...
Also... AKTUELL hab ich noch ein Lesekopf auf meinem MHZ Stromzähler. Ich brauche für meine Loxone Nachregelung genau 3 Sachen von meinem Stromzähler: Aktuelle Leistung, Einspeisung und Bezug. Das hol ich mir, wie viele andere auch hier, via Lox http Request alle paar Sekunden ab.
Nun... die einfachste aller Möglichkeiten wäre jetzt für mich gewesen einfach mein Lesekopf mit Tasmota um einen weiteres Leseauge zu erweitern wie im Thread ja beschrieben wurde. Der Tibber Pulse käme dann auf das zweite Auge drauf was sendet anstatt zu lesen. Ehrlich gesagt mag ich diese ganze Löt und Bastellösung nicht! Ich find den Tibber mit seiner Bridge und dem Puls mittlerweile auch recht cool! Deswegen hab ich mich dafür entschieden es anders zu machen OBWOHL es echt ein riesen Scheiß war die Tibber Bridge und den Pulse einmalig einzurichten! Irgendwann hat das dann mal geklappt :-)
Also, ich fragte mich also wie ich an Live Daten von meinem Tibber Pulse komme. Grundsätzlich holt die API ja schon einiges raus, allerdings nicht die Livedaten! Die bekommt man nur wenn man via Websocket drauf zugreift und das ist mir echt viel zu hoch! Ich kann PHP und hab zuhause ne Synology wo nen Webserver drauf läuft...aber jetzt noch Websocket mit ratchet oder sonstwas ist wieder zu viel Gebastel...
Der Tibber Pulse, einmal auf dem Zähler angepappt, sendet ja alle 3 Sekunden eine SML Nachricht an die Tibber Bridge. Diese schickt das über Amazon AWS an Tibber weiter. So wissen die bei Tibber wann ich welchen Strom verbrauche. Anstatt nun via API diese Infos zu holen mache ich das jetzt DIREKT UND LOKAL von der Tibber Bridge. Wie ich das mache erkläre ich nun Schritt für Schritt und hoffe, dass jemand vielleicht einen Denkfehler erkennt!
Die Tibber Bridge muss zuerst "manipuliert" werden, man muss ihren AP dauerhaft aktivieren sodass sie STÄNDIG im eigenen heimnetz verfügbar ist. Es gibt zig Anleitungen im netz wie das geht. Hat man das geschafft kann man aus dem Heimnetz auf die IP der Bridge zugreifen und erhält ALLE 3 SEKUNDEN eine SML Nachricht die vom Pulse an die Bridge übermittelt wurde. Man greift sozusagen einfach das ab was sowieso an Tibber selbst übermittelt wird. Wenn ihr eure Bridge "freigeschalten" habt dann könnt ihr mit folgendem Link die SML Nachricht abgreifen:
http://<DIE_INTERNE_IP_DEINER_TIBBER_BRIDGE>/data.json?node_id=1
zB
Diese RAW SML Message die alle 3 Sekunden vom Tibber Pulse an die Bridge übermittelt und von dort an Tibber selbst weitergeleitet wird lässt sich wunderbar abgreifen. Darin enthalten sind die 3 für mich wichtigen Werte Aktuelle Leistung, Einspeisung und Bezug.
Die SML Nachricht ist allerdings ein bisschen tricky und eigentlich war ich lange auf der Suche nach einem Parser. Ich wollte einen SML Parser für PHP, ich wollte kein Python, kein C oder sonstwas und habe leider nichts gefunden. Also blieb mir nix anderes übrig als selbst zu versuchen das SML etwas zu verstehen.
Ich zeig euch mal das fertige Ergebnis auf meinem Webserver und versuche es danach Schritt für Schritt zu erklären:
Diese Seite bleibt zu testzwecken mal paar Tage online. Mich würde interessieren ob jemand von euch meint ich hätte einen Denkfehler. Zu TESTZWECKEN aktualisiert die Seite alle 3 Sekunden... Später ist das ja nimmer nötig denn Loxone macht den Call auf die Seite ja selbständig.
WAS PASSIERT HIER?
1. Ich hole die SML Nachricht von der Bridge ab. Das geschieht mit Curl.
2. Ich wandle diese in HEX um. Ich hab, aus Datenschutzgründen, bei beiden den mittleren teil ausgeblendet
3. Die SML Nachricht ist bei MIR und MEINEM STROMZÄHLER IMMER gleich groß: 1163 Zeichen...nachdem ich sie in HEX umgewandelt, leerzeichen hinzugefügt und alle Buchstaben groß gemacht habe
4. Nun weiß ich aus der SML Spezifikation meines Zählerherstellers wo genau in diesem ganzen Kauderwelsch nun die aktuelle Leistung und die Zählwerke 1.8.0 und 2.8.0 versteckt sind und extrahiere diese! Als Ergebnis kommt dann sowas raus:
Aktuelle Leistung:
77 07 01 00 10 07 00 FF 01 01 62 1B 52 FF 55 FF FF A7 A3 01
Zählerstand (1.8.0):
77 07 01 00 01 08 00 FF 64 01 01 A2 01 62 1E 52 FF 56 00 12 92 A0 3B 01
Zählerstand (2.8.0):
77 07 01 00 02 08 00 FF 64 01 01 A2 01 62 1E 52 FF 56 00 0E AB 2E E2 01
Alle 3 Sekunden ändern sich diese HEX Daten logischerweise. Aber nicht alle: entweder 1.8.0 zählt hoch (ich beziehe grade) oder 2.8.0 tut das (wenn ich einspeise).
5. Damit ich sicher gehen kann dass die 3 SML Teile auch wirklich valide sind hab ich den Parser von Tasmota gefunden, link ist unter den dreiWerten. Wenn die dort valide sind und mir ein Ergebnis angezeigt wird dann weiß ich dass das passt.
6. Nun versuche ich aus jeder der drei Teile DEN NACKIGEN VALUE herauszufinden. Das mache ich indem ich eben weiß welche Sequenz vor dem Value steht, diese abschneide und dann einfach den nackten Value jeweils in DEZIMAL umwandle. Heraus kommt dann eben das fertige Ergebnis!
ACHTUNG: Je nach Zähler muss man natürlich selbst rausfinden welcher Teil der SML Nachricht relevant ist. Außerdem: mein Zähler stellt auch negative Werte da wenn die PV produziert. Dann bekomme ich zB -500W. Ich habe die negativen Werte in der Funktion berücksichtigt.
Die große Frage lautet nun also bevor ich hier weitermache:
KANN JEMAND BESTÄTIGEN DASS DAS SO KORREKT IST UND ICH KEINEN DENKFEHLER HABE?
Nachfolgend mein Code der natürlich so nicht bleiben wird:
include("inc.settings.php");
include("inc.functions.php");
echo "<meta http-equiv="refresh" content="$refresh; URL=https://mpmedia-cloud.de/web/tibber/">";
echo "
<style>
body {
font-family: Arial;
font-size: 12px;
</style>
";
echo "<h1>Tibber SML Decoder (Refresh alle $refresh Sekunden)</h1>";
echo "<b>Stromzähler:</b><br>" . $stromzaehler;
echo "<hr>";
echo "<b>Definition laut Zählerhersteller EHM:</b><br>";
echo $sml_definition;
echo "<hr>";
echo "<b>Tibber Bridge URL:</b><br>";
echo $tibber_bridge_url;
echo "<hr>";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $tibber_bridge_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "$tibber_bridge_username:$tibber_bridge_password");
$response = curl_exec($ch);
if (curl_errno($ch))
{
echo 'cURL-Fehler: ' . curl_error($ch);
echo "<hr>";
}
else
{
$sml_bin = $response;
echo "<b>SML BIN:</b><br>";
echo substr($sml_bin,0,24) . "...<i>ausgeblendet</i>..." . substr($sml_bin,-24,24);
//echo $sml_bin;
echo "<hr>";
$sml_hex = bin2hex($response);
echo "<b>SML HEX:</b><br>";
echo substr($sml_hex,0,24) . "...<i>ausgeblendet</i>..." . substr($sml_hex,-24,24);
//echo $sml_hex;
echo "<hr>";
$sml_hex_spacer = implode(' ', str_split($sml_hex, 2));
$sml_hex_spacer = strtoupper($sml_hex_spacer);
echo "<b>SML HEX LESBAR MIT SPACER UND UPPER():</b><br>";
echo substr($sml_hex_spacer,0,24) . "...<i>ausgeblendet</i>..." . substr($sml_hex_spacer,-24,24);
//echo $sml_hex_spacer;
echo "<hr>";
echo "<b>Anzahl Zeichen von lesbarem HEX:</b><br>";
echo strlen($sml_hex_spacer);
echo "<hr>";
}
curl_close($ch);
$sml = $sml_hex_spacer;
$leistung = extract_from_sml($sml,$aktuelle_positive_wirkleistung);
$zaehlerstand_180 = extract_from_sml($sml,$zaehlwerk_positive_wirkenergie_tariflos);
$zaehlerstand_280 = extract_from_sml($sml,$zaehlwerk_negative_wirkenergie_tariflos);
// Ausgabe zum Debuggen im Tasmota Parser
// -------------------------------------------------------------------------------------
echo "<h1>SML Sequenzen</h1>";
echo "<b>Aktuelle Leistung:</b><br>" . $leistung . "<hr>";
echo "<b>Zählerstand (1.8.0):</b><br>" . $zaehlerstand_180 . "<hr>";
echo "<b>Zählerstand (2.8.0):</b><br>" . $zaehlerstand_280 . "<hr>";
echo "<a href='https://tasmota-sml-parser.dicp.net/' target='_blank'>Parser</a><hr>";
// Values extrahieren
// -------------------------------------------------------------------------------------
echo "<h1>SML Values</h1>";
$value = substr($leistung,46,11);
echo $value . "<br>";
$value = str_replace(" ","",$value);
echo $value . "<br>";
$value = convert_to_decimal($value);
echo $value . "<br>";
$value = $value / 10 . " W";
echo "<h3>Leistung: " . $value . "</h3>";
echo "<hr>";
$value = substr($zaehlerstand_180,55,14);
echo $value . "<br>";
$value = str_replace(" ","",$value);
echo $value . "<br>";
$value = convert_to_decimal($value);
echo $value . "<br>";
$value = $value / 10000 . " kWh";
echo "<h3>Zählerstand 1.8.0: " . $value . "</h3>";
echo "<hr>";
$value = substr($zaehlerstand_280,55,14);
echo $value . "<br>";
$value = str_replace(" ","",$value);
echo $value . "<br>";
$value = convert_to_decimal($value);
echo $value . "<br>";
$value = $value / 10000 . " kWh";
echo "<h3>Zählerstand 2.8.0: " . $value . "</h3>";
echo "<hr>";
Im file "inc.settings.php" hab ich nur paar Basisvariablen versammelt:
$tibber_api_url = "https://api.tibber.com/v1-beta/gql";
$tibber_api_key = "DEIN API KEY";
$tibber_home_id = "DEINE HOME ID";
$tibber_bridge_url = "http://DEINE TIBBER BRIDGE IP/data.json?node_id=1";
$tibber_bridge_username = "admin";
$tibber_bridge_password = "XXXX-XXXX"; // Steht auf der Bridge UNTER dem QR Code
$stromzaehler = "ehz-kw8e2a5l0eq2p";
$sml_definition = "
81 81 C7 82 03 FF = Hersteller-Kennung <br>
01 00 00 00 09 FF = Geräte-Identifikation <br>
01 00 01 08 00 FF = Zählwerk positive Wirkenergie, tariflos <br>
01 00 01 08 01 FF = Zählwerk positive Wirkenergie, Tarif 1 <br>
01 00 01 08 02 FF = Zählwerk positive Wirkenergie, Tarif 2 <br>
01 00 01 08 0x FF = Zählwerk positive Wirkenergie, Tarif x <br>
01 00 02 08 00 FF = Zählwerk negative Wirkenergie, tariflos <br>
01 00 02 08 01 FF = Zählwerk negative Wirkenergie, Tarif 1 <br>
01 00 02 08 02 FF = Zählwerk negative Wirkenergie, Tarif 2 <br>
01 00 02 08 0x FF = Zählwerk negative Wirkenergie, Tarif x <br>
01 00 10 07 00 FF = Aktuelle positive Wirkleistung (nur beim „Vollständigen Datensatz“) <br>
01 00 01 11 00 FF = Signierter Zählestand (nur im EDL40-Modus) <br>
81 81 C7 82 05 FF = Public Key <br>
";
$hersteller_kennung = "81 81 C7 82 03 FF";
$geraete_identifikation = "01 00 00 00 09 FF";
$zaehlwerk_positive_wirkenergie_tariflos = "01 00 01 08 00 FF";
$zaehlwerk_positive_wirkenergie_tarif_1 = "01 00 01 08 01 FF";
$zaehlwerk_positive_wirkenergie_tarif_2 = "01 00 01 08 02 FF";
$zaehlwerk_positive_wirkenergie_tarif_x = "01 00 01 08 02 FF";
$zaehlwerk_negative_wirkenergie_tariflos = "01 00 02 08 00 FF";
$zaehlwerk_negative_wirkenergie_tarif_1 = "01 00 02 08 01 FF";
$zaehlwerk_negative_wirkenergie_tarif_2 = "01 00 02 08 02 FF";
$zaehlwerk_negative_wirkenergie_tarif_x = "01 00 02 08 0x FF";
$aktuelle_positive_wirkleistung = "01 00 10 07 00 FF";
$loxone_miniserver_ip = "BRAUCHEN WIR SPÄTER";
$refresh = "3";
Dann noch die functions.php:
function extract_from_sml($sml,$string)
{
$position = strpos($sml,$string);
$neuer_string = substr($sml,$position);
$position = strpos($neuer_string,"77");
$ergebnis = substr($neuer_string,0,$position);
return "77 07 " . $ergebnis;
}
function convert_to_decimal($hex)
{
$hex = preg_replace('/[^0-9A-Fa-f]/', '', $hex);
$dec = hexdec($hex);
$max = pow(2, 4 * (strlen($hex) + (strlen($hex) % 2)));
$_dec = $max - $dec;
return $dec > $_dec ? -$_dec : $dec;
}
GLEICH NCOH EINS: Ich weiß dass ich meinen PHP code recht eigenwillig schreibe, die geschweifte Klammer beginnt i.d.R. bei mir immer in einer Newline :-) Stört euch nicht daran.
Mich würde insbesondere interessieren ob die function "convert_to_decimal" so passt... Wie gesagt geht's bei mir darum auch darstellen zu können wenn die Leistung negativ ist. Ich weiß nicht wie das bei anderen Zählern ist...
Wenn das alles so passt und ich das Gefühl habe das funzt soweit dann nutze ich natürlich die minimalisierte Loxone Variante:
include("inc.settings.php");
include("inc.functions.php");
// Zugriff auf Daten NUR für Miniserver
$client_ip = $_SERVER['REMOTE_ADDR'];
$client_ip = "192.168.0.15";
if($client_ip == $loxone_miniserver_ip)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $tibber_bridge_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "$tibber_bridge_username:$tibber_bridge_password");
$response = curl_exec($ch);
if (curl_errno($ch))
{
echo 'cURL-Fehler: ' . curl_error($ch);
echo "<hr>";
}
else
{
$sml_bin = $response;
$sml_hex = bin2hex($response);
$sml_hex_spacer = implode(' ', str_split($sml_hex, 2));
$sml_hex_spacer = strtoupper($sml_hex_spacer);
}
curl_close($ch);
$sml = $sml_hex_spacer;
$leistung = extract_from_sml($sml,$aktuelle_positive_wirkleistung);
$zaehlerstand_180 = extract_from_sml($sml,$zaehlwerk_positive_wirkenergie_tariflos);
$zaehlerstand_280 = extract_from_sml($sml,$zaehlwerk_negative_wirkenergie_tariflos);
// Ausgeben der extrahierten SML Strings
//echo "leistung: " . $leistung . "<br>";
//echo "zaehlerstand_180: " . $zaehlerstand_180 . "<br>";
//echo "zaehlerstand_280: " . $zaehlerstand_280 . "<br>";
//echo "<a href='https://tasmota-sml-parser.dicp.net/' target='_blank'>Parser</a><hr>";
// SML String LEISTUNG umwandeln von HEX zu DEC und formatiert ausgeben
$value = substr($leistung,46,11);
$value = str_replace(" ","",$value);
$value = convert_to_decimal($value);
$value = $value / 10; // Wert in Watt
$value = $value / 1000; // Wert in Kilowatt
$leistung = $value;
// SML String ZAEHLERSTAND_180 umwandeln von HEX zu DEC und formatiert ausgeben
$value = substr($zaehlerstand_180,55,14);
$value = str_replace(" ","",$value);
$value = convert_to_decimal($value);
$value = $value / 10000;
$zaehlerstand_180 = $value;
// SML String zaehlerstand_280 umwandeln von HEX zu DEC und formatiert ausgeben
$value = substr($zaehlerstand_280,55,14);
$value = str_replace(" ","",$value);
$value = convert_to_decimal($value);
$value = $value / 10000;
$zaehlerstand_280 = $value;
//Ausgabe aufbereitet für Loxone
echo "leistung_kw:$leistung;bezug_kwh:$zaehlerstand_180;einspeisung_kwh:$zaehlerstand_280";
}
else
{
// Falls Zugriff nicht durch Miniserver...
echo "Zugriff verboten.";
}
Später dann, wenn mich Tibber wirklich beliefert, hol ich mir den Rest aus der API:
include("inc.settings.php");
include("inc.functions.php");
echo "
<style>
body {
font-family: Arial;
font-size: 12px;
</style>
";
$query = '
{
viewer {
name
homes {
address {
address1
address2
address3
postalCode
city
country
latitude
longitude
}
currentSubscription {
status
priceInfo {
current {
total
energy
tax
startsAt
}
today {
total
energy
tax
startsAt
}
tomorrow {
total
energy
tax
startsAt
}
}
}
consumption(resolution: HOURLY, last: 100) {
nodes {
from
to
cost
unitPrice
unitPriceVAT
consumption
consumptionUnit
}
}
}
}
}
';
$ch = curl_init($tibber_api_url);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"Authorization: Bearer $tibber_api_key",
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['query' => $query])
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "<h1>Tibber API Abfrage</h1>";
echo "<hr>";
echo "<b>Das Query:</b><br>";
echo $query;
echo "<hr>";
echo "Ausgeben von <b>['viewer']['homes'][0]['currentSubscription']['priceInfo']['today']:</b>";
echo "<table border='1' cellpadding='5'>";
echo "<tr><th align='left'>Zeit</th><th>Gesamtpreis (€/kWh)</th><th>Energiepreis (€/kWh)</th><th>Steuern (€/kWh)</th></tr>";
foreach ($data['data']['viewer']['homes'][0]['currentSubscription']['priceInfo']['today'] as $priceInfo)
{
echo "<tr>";
echo "<td>" . date("d.m.Y - H:i", strtotime($priceInfo['startsAt'])) . " Uhr</td>"; // Umwandlung der Startzeit in eine lesbare Form
echo "<td>" . $priceInfo['total'] . "</td>";
echo "<td>" . $priceInfo['energy'] . "</td>";
echo "<td>" . $priceInfo['tax'] . "</td>";
echo "</tr>";
}
echo "</table>";
// Ausgabe der kompletten API Antwort
/*
echo "<pre>";
print_r($data);
echo "</pre>";
*/
Ich weiß sehr wohl dass es auch in Loxone einen Baustein gibt welcher mir die aktuellen Strompreise für die nächsten 24 Stunden darstellt und ich eigentlich nur noch die Tibber Gebühren drauf hauen muss... dennoch mach ich das lieber wohl selbst mit der Tibber API.
Soderle, ziemlich viel Code und ziemlich viele Fragen. Vielleicht hilfts dem ein- oder anderen... aber noch hilfreicher wäre für mich wenn jemand der sich mit SML wirklich gut auskennt verifizieren könnte dass ich hier keinen Denkfehler mache!
HERZLICHEN DANK!
lG Mirko
PS: Wer keinen Webserver hat kann das Script ja auch online irgendwo laufen lassen. Auf dem Router (zB Fritzbox) einfach ne Weiterleitung einrichten mit nem Dyndns oder so. Wir reden hier ja nicht vom PIN der EC Karte sondern lediglich von Stromdaten! Dennoch werde ICH ES später nur intern im Netzwerk laufen lassen, deswegen auch die aktuell ausgeklammerte Prüfung auf die IP... ihr würdet sonst ja nichts sehen :-)
Kommentar