NEWS
Geräte zu Stundenpreisen (EPEX) oder PV-Überschuss betreiben
-
@hd666
Irgendwie habe ich Dein Problem noch nicht ganz verstanden...Du schreibst heute: "JSON von 0_userdata.0.Tibber.Stundenpreise_heute (keine Änderung bei Aktivierung der ersten beiden Objekte im 2.Abschnitt), ebenso bei _morgen" und zeigst die Inhalte der Datenpunkte "Stundenpreise_heute" und "Stundenpreise_morgen".
Gestern schreibst Du "1. Die Daten werden über den Zeitplan abgeholt und richtig unter Stundenpreise_heute und _morgen abgelegt. Jedoch werden die Daten nicht gesplittet und kopiert."
Wenn das Abholen der Preise von Tibber doch funktioniert... warum zeigst Du mir dann die beiden Datenpunkte Datenpunkte "Stundenpreise_heute" und "Stundenpreise_morgen"?
Ich dachte, es geht Dir um die Verteilung der Daten auf die einzelnen Stunden? Das LOG zum Script kenne ich so leider auch gar nicht.
Ich habe bei mir den DEBUG noch um den Wert ergänzt. Sieht jetzt so aus:
Dazu wird im Blockly-Editor direkt unter dem Script folgendes angezeigt:
Und daran könntest Du erkennen, ob die Stunden einzeln durchlaufen werden und welche Werte ermittelt wurden. -
@thomkast im ersten Teil hab ich dich warscheinlich mißverstanden.
Ich hatte die Debug Optionen auf ausführlich zu stehen.
Ich habe debug erweitert, es scheint so als ob das script gar nicht ausgeführt wird. Ich bekomme nur diese eine Zeile
script.js.Tibber.Tibber_Script: 0_userdata.0.Tibber.EPEX_Spot_DE_heute.ESDE_heute_24wertNaN
Hier das Script, so wie ich es bei mir habe. Vielleicht hat dort doch ein Fehler eingeschlichen.
var geringster_preis, TIBBERpreise_abholen, preisdurchschnitt, stunde, geringster_preis_stunde, stundenpreise, preis_hysterese_stunden, preis_hysterese, zeitraum, stundenpreise_heute, zeitraum_json, stundenpreise_morgen, preis__1, max_preis, abholzeit, wert, result, abholzeit_merker, preis_0; // Beschreibe diese Funktion … async function preise_holen() { TIBBERpreise_abholen = schedule((abholzeit.slice(3, 5)).toString().trim() + ' ' + (abholzeit.slice(0, 2)).toString().trim() + ' ' + '*'.toString().trim() + ' ' + '*'.toString().trim() + ' ' + '*'.toString().trim(), async function () { // Preise von "today" abholen // Anstatt der 5 ***** ist das TIBBER-Token einzusetzen. exec((['curl \\','-H "Authorization: Bearer XXXXX" \\','-H "Content-Type: application/json" \\','-d \'{ "query": "{viewer {homes {currentSubscription {priceInfo {today {total energy tax startsAt }}}}}}" }\' https://api.tibber.com/v1-beta/gql'].join('')), async function (error, result, stderr) { stundenpreise_heute = result; abholzeit_merker = abholzeit; setState("0_userdata.0.Tibber.Stundenpreise_heute"/*stündliche Preise von heute*/, result, true); zeitraum = 'heute'; await stundenpreise_splitten(); }); console.debug("exec: " + (['curl \\','-H "Authorization: Bearer XXXXX" \\','-H "Content-Type: application/json" \\','-d \'{ "query": "{viewer {homes {currentSubscription {priceInfo {today {total energy tax startsAt }}}}}}" }\' https://api.tibber.com/v1-beta/gql'].join(''))); await wait(2000); // Preise von "tomorrow" abholen // Die Preise vom kommenden Tag könnten auch abgeholt werden. // Ist aber im Anwendungsfall nicht relevant und daher deaktiviert. // Anstatt der 5 ***** ist das TIBBER-Token einzusetzen. exec((['curl \\','-H "Authorization: Bearer XXXXX" \\','-H "Content-Type: application/json" \\','-d \'{ "query": "{viewer {homes {currentSubscription {priceInfo {tomorrow {total energy tax startsAt }}}}}}" }\' https://api.tibber.com/v1-beta/gql'].join('')), async function (error, result, stderr) { stundenpreise_morgen = result; setState("0_userdata.0.Tibber.Stundenpreise_morgen"/*stündliche Preise von morgen*/, result, true); zeitraum = 'morgen'; await stundenpreise_splitten(); }); console.debug("exec: " + (['curl \\','-H "Authorization: Bearer XXXXX" \\','-H "Content-Type: application/json" \\','-d \'{ "query": "{viewer {homes {currentSubscription {priceInfo {tomorrow {total energy tax startsAt }}}}}}" }\' https://api.tibber.com/v1-beta/gql'].join(''))); await guenstigster_preis(); await guenstige_stunden(); }); } // Beschreibe diese Funktion … async function stundenpreise_splitten() { preisdurchschnitt = 0; // Im TIBBER-JSON wird heute/morgen mit "today"/"tomorrow" geschrieben. // Die Datenpunkte unterscheiden aber in "heute/morgen". Beim Auslesen // des JSON muss daher das englische Wort verwendet werden :-( // // Schlechtes Anfangsdesign! Ich hätte die Datenpunkte // ebenfalls mit "today"/"tomorrow" unterscheiden // müssen, dann wäre diese Abfrage überflüssig. if (zeitraum == 'heute') { stundenpreise = stundenpreise_heute; zeitraum_json = 'today'; } else { stundenpreise = stundenpreise_morgen; zeitraum_json = 'tomorrow'; } for (stunde = 0; stunde <= 23; stunde++) { // Hier wird das englische Wort benötigt. wert = parseFloat(jsonataExpression((function () { try {return JSON.parse(stundenpreise);} catch(e) {return {};}})(),(['data.viewer.homes[0].currentSubscription.priceInfo.',zeitraum_json,'[',('' + stunde),'].total'].join('')))); preisdurchschnitt = parseFloat(preisdurchschnitt) + wert; setStateDelayed((['0_userdata.0.Tibber.EPEX_Spot_DE_',zeitraum,'.ESDE_',zeitraum,'_',stunde].join('')), wert, true, parseInt(((0) || "").toString(), 10), false); } console.error((['0_userdata.0.Tibber.EPEX_Spot_DE_',zeitraum,'.ESDE_',zeitraum,'_',stunde,'wert',wert].join(''))); preisdurchschnitt = Math.round((parseFloat(preisdurchschnitt) / 24)*10000)/10000; setStateDelayed((['0_userdata.0.Tibber.EPEX_Spot_DE_',zeitraum,'.ESDE_',zeitraum,'_Mittel_ungewichtet'].join('')), preisdurchschnitt, true, parseInt(((0) || "").toString(), 10), false); } // Zuerst wird der geringste Preis und damit die günstigste Stunde ermittelt. // Beide Werte müssten nicht als Datenpunkt in den Objekten // abgebildet werden. Habe ich nur gemacht, um das Ergebnis zu sehen. async function guenstigster_preis() { if (zeitraum == 'heute') { stundenpreise = stundenpreise_heute; zeitraum_json = 'today'; } else { stundenpreise = stundenpreise_morgen; zeitraum_json = 'tomorrow'; } stunde = 0; // Hier wird das englische Wort benötigt. preis__1 = parseFloat(jsonataExpression((function () { try {return JSON.parse(stundenpreise);} catch(e) {return {};}})(),(['data.viewer.homes[0].currentSubscription.priceInfo.',zeitraum_json,'[',('' + stunde),'].total'].join('')))); for (stunde = 1; stunde <= 23; stunde++) { // Hier wird das englische Wort benötigt. preis_0 = parseFloat(jsonataExpression((function () { try {return JSON.parse(stundenpreise);} catch(e) {return {};}})(),(['data.viewer.homes[0].currentSubscription.priceInfo.',zeitraum_json,'[',('' + stunde),'].total'].join('')))); // Der Vergleich der Stundenpreise (Schleife) fängt mit // der 1ten Stunde an - 01:00 bis 01:59 -, da zur 0ten // Stunde kein vorheriger Vergleichspreis vorliegt. Daher // muss aber die 0'te Stunde separat behandelt werden. // if (stunde == 1) { if (preis_0 < preis__1) { geringster_preis = preis_0; geringster_preis_stunde = 1; } else { geringster_preis = preis__1; geringster_preis_stunde = 0; } } else { if (preis_0 < geringster_preis) { geringster_preis = preis_0; geringster_preis_stunde = stunde; } } } setState("0_userdata.0.Tibber.Geringster_Preis"/*Geringster_Preis*/, geringster_preis, true); setState("0_userdata.0.Tibber.Geringster_Preis_Stunde"/*günstigste Stunde*/, geringster_preis_stunde, true); } // Mit dem vorab ermittelten geringsten Stundenpreis + Hysterese, wird // jeder Stundenpreis verglichen und die günstigen Stunden ermittelt. // Diese günstigen Stunden werden als Datenpunkt weggeschrieben, // um für die EPEX-Freigabe im Geräte-Script herangezogen zu werden. async function guenstige_stunden() { if (zeitraum == 'heute') { stundenpreise = stundenpreise_heute; zeitraum_json = 'today'; } else { stundenpreise = stundenpreise_morgen; zeitraum_json = 'tomorrow'; } preis_hysterese_stunden = []; max_preis = parseFloat((parseFloat(geringster_preis) * (parseFloat(100) + preis_hysterese))) / 100; for (stunde = 0; stunde <= 23; stunde++) { preis_0 = parseFloat(jsonataExpression((function () { try {return JSON.parse(stundenpreise);} catch(e) {return {};}})(),(['data.viewer.homes[0].currentSubscription.priceInfo.',zeitraum_json,'[',('' + stunde),'].total'].join('')))); if (preis_0 <= max_preis) { preis_hysterese_stunden.push(stunde); } } setState("0_userdata.0.Tibber.Preis_Hysterese_Stunden"/*Stunden mit günstigen Preisen*/, ('' + preis_hysterese_stunden), true); } // Beschreibe diese Funktion … async function epex_preise_kopieren() { for (stunde = 0; stunde <= 23; stunde++) { setStateDelayed(('0_userdata.0.Tibber.EPEX_Spot_DE_heute.ESDE_heute_' + String(stunde)), getState(('0_userdata.0.Tibber.EPEX_Spot_DE_morgen.ESDE_morgen_' + String(stunde))).val, true, parseInt(((0) || "").toString(), 10), false); } setState("0_userdata.0.Tibber.EPEX_Spot_DE_heute.ESDE_heute_Mittel_ungewichtet"/*ESDE_heute_Mittel_ungewichtet*/, getState("0_userdata.0.Tibber.EPEX_Spot_DE_morgen.ESDE_morgen_Mittel_ungewichtet").val, true); setState("0_userdata.0.Tibber.EPEX_Spot_DE_morgen.ESDE_morgen_Mittel_ungewichtet"/*ESDE_morgen_Mittel_ungewichtet*/, 0, true); } // 1. Variablen einmalig bei Start setzen. geringster_preis = getState("0_userdata.0.Tibber.Geringster_Preis").val; geringster_preis_stunde = getState("0_userdata.0.Tibber.Geringster_Preis_Stunde").val; preis_hysterese = getState("0_userdata.0.Tibber.Preis_Hysterese").val; preis_hysterese_stunden = getState("0_userdata.0.Tibber.Preis_Hysterese_Stunden").val; abholzeit = getState("0_userdata.0.Tibber.Abholzeit").val; stundenpreise_heute = getState("0_userdata.0.Tibber.Stundenpreise_heute").val; stundenpreise_morgen = getState("0_userdata.0.Tibber.Stundenpreise_morgen").val; // 2. Für manuellen Aufruf // !! nach der manuellen Nutzung die Bausteine wieder deaktivieren !! // // Durch eintragen von "heute" oder "morgen" wird entschieden, // aus welchem Datenpunkt die Preise verarbeitet werden: // - "heute" --> 0_userdata.0.Tibber.Stundenpreise_heute // - "morgen" --> 0_userdata.0.Tibber.Stundenpreise_morgen // Ebenso wie entschieden, in welche Datenpunkte die Preise gesplittet werden sollen: // - "heute" --> 0_userdata.0.Tibber.EPEX_Spot_DE_heute.ESDE_heute_1 (bis 23) // - "morgen" --> 0_userdata.0.Tibber.EPEX_Spot_DE_morgen_ESDE_morgen_1 (bis 23) zeitraum = 'heute'; await stundenpreise_splitten(); // Durch das Aktualisieren der Werte nach einer Änderung, // kann das Script durchlaufen und muss nicht neu // gestartet werden, um aktuelle Werte zu berücksichtigen. // 3. Werte bei Änderung neu ermitteln on({id: "0_userdata.0.Tibber.Preis_Hysterese"/*%tualer Aufschlag*/, change: "ne"}, async function (obj) { var value = obj.state.val; var oldValue = obj.oldState.val; preis_hysterese = (obj.state ? obj.state.val : ""); // Die Änderung des Aufschlags kann vor oder nach Abholen der TIBBER-Preise erfolgen. // Erfolgt die Änderung vor der Preis-Aktualisierung finden // sich die aktuellen Preise in "stundenpreise_morgen". // Erfolgt die Änderung nach der Preis-Aktualisierung finden // sich die aktuellen Preise in "stundenpreise_heute". if (compareTime(abholzeit, null, "<", null)) { zeitraum = 'morgen'; } else { zeitraum = 'heute'; } await guenstige_stunden(); }); on({id: "0_userdata.0.Tibber.Abholzeit"/*wann Preise abgeholt werden - nicht vor 14:30 ( hh:mm )*/, change: "ne"}, async function (obj) { var value = obj.state.val; var oldValue = obj.oldState.val; abholzeit = (obj.state ? obj.state.val : ""); (function () {if (TIBBERpreise_abholen) {clearSchedule(TIBBERpreise_abholen); TIBBERpreise_abholen = null;}})(); await preise_holen(); }); // 4. um "abholzeit" Preise von Tibber abholen // Preise von "today" und "tomorrow" abholen // und auf Tage und Stunden splitten await preise_holen(); // 5. zu Tagesbeginn aktuelle Werte ermitteln schedule("2 0 * * *", async function () { // Die Preis-/Stunden-Ermittlung findet zu Beginn des // neuen Tages statt. zu diesem Zeitpunkt befinden // sich sie TIBBER-Preise in "stundenpreise_morgen". zeitraum = 'morgen'; await guenstigster_preis(); await guenstige_stunden(); await epex_preise_kopieren(); }); // 6. ausgelagerte Routinen // 6.1 Abrufen der TIBBER-JSON-Daten // 6.2 Neue Preise auf Tage und Stunden aufsplitten - für VIS // 6.3. Geringsten Stundenpreis ermitteln // 6.4. Stunden mit günstigen Preisen ermitteln // 6.5. Bei Tageswechsel Werte umziehen
-
Ich hab den Fehler bei mir gefunden.
In den Blöcken "stundenpreise_splitten, günstigster_preis und günstige_stunden" stehtdata.viewer.homes[0].currentSubscription.priceInfo.
Die
[0]
hinter homes hat den fehler verursacht. Ich habs gelöscht und alles funktioniert. -
Hallo zusammen,
kurze Frage in die Runde: Ich hatte neulich verschiedene Updates in ioB gemacht, unter anderem auch die JS Scriptausführung, die aber keine 'Breaking Warnnachricht' zeigte.
Stutzig wurde ich, als ein anderes Script nicht mehr korrekt lief - ich fand dann raus, dass die JSONata Funktion nicht mehr funktionierte wie erwartet.
Gerade sehe ich dann, dass auch das hier besprochene Tibber-Script keine stündlichen Werte mehr einträgt und erste Versuche zeigen wieder Probleme mit JSONata.Hat das von Euch auch schon jemand erfahren, bzw. kann mir jemand einen Tipp geben, was da eigentlich los ist?
-
@dieterh
Bei mir läuft 6.1.4. Welche Version bei Dir?
-
@thomkast - ich bin auf 7.0.1 und habe auch schon gefunden, dass es wohl Probleme mit JSONata gab, die angeblich gefixt sein sollen - allerdings klappt es bei mir noch nicht. Ich habe nicht verstanden, was genau man machen muss.
-
UPDATE:
Also, man muss wohl den Browser Cash löschen, irgend einen Block im Tibber Script verschieben und dann wieder SPEICHERN.
Danach hat - jedenfalls bei mir - wieder alles funktioniert. -
@dieterh
Hi und Danke für Deine Info. Eine Frage habe ich noch...
Wie bist Du auf die neue Version aufmerksam geworden? Ich habe gerade gestern alles aktualisiert und mit wurde 7.0.1 nicht angeboten. Daher würde mich interessieren wie Du die Aktualisierung durchführst? -
@thomkast - Ich mache periodisch ioB auf und lasse mir die Adapter anzeigen.
Dann klicke ich dort auf 'Adapter auf Updates überprüfen' und bekam angezeigt, dass es eine neue Version von JS Scriptausführung gibt. -
@thomkast Hallo Thomas, ich bin begeistert von deinem Skript. Ich benutze es um die Auto-Ladung automatisch auf die günstigsten Stunden zu legen. Da hier doch einiges an KWh zusammen kommt, würde ich gerne die Hysterese automatisch setzen lassen. Ich stelle mir eine Schleife vor, die die Hysterese so lange rauf setzt, bis ich 2 Nachtstunden (Stundenzahl kann in einer neuen Spalte vorgegeben werden) in den Hysterese-Zeiten zusammen habe. Mit dem festem Wert komme ich mal auf 4 Stunden, mal sind es Zuwenig Stunden. Könntest du mir hierbei helfen?
Gruß Michael
-
@miknu42 Hallo Michael, vielen Dank für Dein Feedback.
Für diesen Usecase habe ich noch keinen Ansatz gefunden. Ich warte ja auch erst seit über 500 Tagen auf mein BEV. Da hatte ich also immer noch Zeit ;-). Nun soll es wohl im JUN/JUL so weit sein... Soll heißen: Dann komme auch ich in die Notwendigkeit einer Ladesteuerung.
Deine Beschreibung der Logik ist ein wenig zu dürftig um es zu verstehen - also für mich. Du möchtest Dein Auto über mindestens 2h zu günstigem Strom laden und 4h sind Dir zu lange?..?
Da fehlen doch noch viele weitere Stellschrauben...- bis zu wieviel % SOC soll geladen werden?
- bis wann soll der SOC erreicht sein?
- reicht die Zeit überhaupt für eine ausreichende Ladung?
- existiert ein BATteriespeicher, der ebenfalls einbezogen werden soll?
- soll sich beim Laden wirklich nur auf die "günstigen Stunden" bezogen werden? Was ist mit zu erwartendem PV-Ertrag und BAT-Überlauf?
Ich werde mir dazu Gedanken machen und eine Lösung überlegen. Und sollte ich genug Vertrauen in die Lösung haben, werde ich diese auch gerne veröffentlichen.
-
@thomkast Hallo Thomas,
jetzt hole ich mal ein bisschen weiter aus. Ich habe die OpenWB Box. Hier gibt es den Modus Min+PV-Laden. Ab März ist das mein bevorzugter Lademodus. Hier gibt man den Mindestladeleistung ein, damit das Auto Dreiphasig lädt. Ab und zu, falls mal Wolken kommen oder es schon später ist, füllt die Box die Differenz aus dem Speicher auf, damit die Ladung nicht abbricht. Ab Mai ist das kein Thema, weil eh genug PV vorhanden ist. Im März/April brauche ich den Batteriestrom aber für die alltägliche Grundlast. Hier benutze ich dein Skript schon um bei extrem niedrigem Strompreis die Batterieentnahme beim Autoladen zu sperren. Im Winter ist es mit PV nicht weit her. Hier sind die günstigen Tibberstunden meistens Nachts. Die OPENWB hat schon eine die Anbindung an Timber und ich kann beim Lademodus Sofortladen einen Ladehöchstpreis angeben. Diesen kann ich auch über MQTT über den IObroker übermitteln. Da mir bei 11KW-Ladeleistung 2-Stunden Nachts bis 7:00 Uhr reichen berechne ich den zu übermittelnden Höchstpreis aus deinem Geringster-Preis x Hysterese. Meistens fängt das Laden damit schon um 12..1 Uhr an und ist um 2..3 Uhr fertig. Die günstigsten Stunden kommen da erst noch. Anderseits ist das Risiko, die Hysterese zu klein einzustellen und morgens mit leerem Auto dazustehen. Daher dachte ich, wenn man die benötigten Stunden als Ziel vorgibt, kann man die kleinste benötigte Hysterese ermitteln und landet somit in den günstigsten Stunden. Später kann man die benötigte Ladezeit bestimmt auch über die SOC-Werte des Autos übermitteln. Aber das wäre der 2. Schritt.
Liebe Grüße Michael
-
@miknu42 ok. Danke für Deine Erläuterung.
Dann benötigst Du die günstigste Stunde im Zeitraum von 00:01 bis 06:59. Zu dieser Stunde kannst Du das Laden dann starten. Richtig? -
@thomkast Genau, die Zeiten in dem Zeitraum habe ich schon selber hinbekommen. Die Schleife, die den Hysteresewert austestet, damit tu ich mich ein bisschen schwer.
-
@miknu42 Aber nur mal ins Unreine gesprochen... Eigentlich reicht Dir doch die günstigste Stunde in dem Zeitraum. Dann lädst Du die 2h und fertig. Die nächste günstige Stunde ist ja fast immer im direkten Anschluß an die günstigste Stunde. Somit benötigst Du doch gar keine Hysterese mehr?..?
-
@thomkast Ich werde das mal eine zeitlang beobachten. Danke für deine Hilfe.
-
@thomkast Hallo Thomas, ich stand vorhin auf dem Schlauch. Ich muss meiner Wallbox einen maximalen Ladepreis übermitteln. Also niedrigster Preis x Hysterese. Um das zu optimieren brauche ich doch die optimale Hysterese.
-
@miknu42 hmm, komme mir schon fast so vor, als wenn ich Dir nicht helfen wollte, weil ich Dir immer mit Gegenvorschlägen komme... Ich finde halt nur, man kann fast alles realisieren, aber muss man das auch immer?..?
Aus meiner Sicht sind die relevanten Punkte:
- Du musst einen maximalen Preis übermitteln und unterhalb dieses MaxPreis beginnt deine WB die Ladung.
- Du möchtest zur günstigsten Stunde die Ladung beginnen.
- Du benötigst mindestens 2h für eine ausreichende Ladung.
- Eine begonnene Ladung wird nicht wieder unterbrochen.
- Die Ladung soll spätestens um 0700 beendet werden.
- Wie verhält sich die WB, wenn in einem laufenden Ladevorgang der MaxPreis angehoben wird?
Damit kommt doch folgendes in Betracht:
- Es sind nur die Preise der Stunden 0, 1, 2, 3, 4, und 5 relevant.
... denn in der 5. Stunde muss die Ladung spätestens beginnen, damit diese in der Stunde 5+6 durchgeführt werden kann - also vor der Stunde 7 beendet ist. - Also benötigst Du nur den geringsten Preis der Stunden 0 bis 5.
- Dieser Preis wären dann der MaxPreis für die WB und die Ladung würde in der zugehörigen Stunde starten.
*Sobald die Ladung gestartet ist, erhöhst Du den MaxPreis um einen beliebigen Wert und die Ladung wird fortgesetzt.
Vorausgesetzt... Die WB verhält sich so, wie beschrieben
-
@thomkast
Den geringsten Preis der Stunden 0 bis 5 erhältst Du, in dem Du in der Schleife den Wert auf 5 veränderst. Zur Bestimmung der geringsten Stunde, spielt die Hysterese auch noch keine Rolle. -
@thomkast Alles gut, ich behaupte nicht, das meine Lösung der Weisheitszahn letzter Schluss ist. Vielleicht gibt es tatsächlich bessere Lösungen. Heute hätte ich allerdings bei einer Hysterese von 4 Nachts gar nicht geladen. Auch der Punkt :
Eine begonnene Ladung wird nicht wieder unterbrochen
ist kein Muss. Es stellt kein Problem da um 0:00 Uhr eine Stunde zu Laden und dann wieder um 3:00 Uhr. Das macht die Box bei manueller Bedingungen auch.
. In dem Fall zwar nicht, aber es kommt durchaus vor, dass zwischen den Ladestunden eine Pause ist.