evcc icon indicating copy to clipboard operation
evcc copied to clipboard

Add planner for dynamic pricing

Open schenlap opened this issue 2 years ago • 32 comments

  • [x] move cheap rate to site
  • [x] remove tariff IsCheap api
  • [x] add tests for planner
  • [ ] integrate with existing planner

schenlap avatar Jan 05 '22 18:01 schenlap

Ich glaub ich habs grob verstanden. Eine Pricetable generiert einen Plan über n nicht-kontinuierliche Slots und startet/stoppt bei jedem Planslot (außer dem letzten wo nicht mehr gestoppt wird). Der letzte müsste sich an der Zielzeit bemessen, nicht daran ob er der letzte des Plans ist?

Wenn das so grob stimmt dann könnten wir mal versuchen das zu zerlegen (und ich helfe gerne):

  • Tarife generieren PriceTables
  • PriceTable ist sortierbar ;)
  • Site verwurstet PriceTable mit einem Planner
  • Planner wird anstatt isCheap and Loadpoint übergeben und signalisiert oder zu laden ist (Active()?)

andig avatar Jan 05 '22 19:01 andig

Übel wirds wenn Lastmanagement kommt, aber das ist eine ganz andere Geschichte…

andig avatar Jan 05 '22 19:01 andig

Ja, das hast du richtig verstanden.

Der letzte müsste sich an der Zielzeit bemessen, nicht daran ob er der letzte des Plans ist?

beides ist möglich. Es geht aber nicht um Zielzeit sondern um erwartet ladezeit. Der letzte Slot kann auch Stunden vor der Zielzeit liegen. Beim Testen muss man aufpassen, Awattar sperrt nach zu vielen Versuchen die API (vermutlich für die IP Adresse), daher hatte ich das fake_awattar drinnen.

schenlap avatar Jan 06 '22 08:01 schenlap

@schenlap ich habe mal angefangen aufzuräumen. Die Logik gibts jetzt nur noch 1x in einer neuen Planner Komponente. Ein paar Todos hab ich oben ergänzt.

andig avatar Jan 14 '22 19:01 andig

ich würde das Cheap komplett rauswerfen, spricht etwas dagegen? Ich sehe darin keinen Nutzen mehr.

schenlap avatar Feb 09 '22 15:02 schenlap

ich würde das Cheap komplett rauswerfen, spricht etwas dagegen? Ich sehe darin keinen Nutzen mehr.

Wir brauchen ein Signal an den Ladepunkt ob geladen werden soll. was günstig ist weiß der Planner aufgrund der Zielzeit. Es mag aber auch im PV Modus Situationen geben wo man gerne zusätzlich aus dem Netz bezieht, z.B. bei negativen Preisen. Den zweiten Fall deckt heute „isCheap“ ab.

Der zweite Fall ist heute unter Tariff konfiguriert. Der gehört gem. Punk 1 oben nach Site als Parameter. Insofern bräuchte Tariff nur die Möglichkeit den aktuellen Preis auszugeben.

Ladeindikation für den Ladepunkt ist dann a) Planner sagt es muss geladen werden oder b) Site stellt fest, dass der Tarif gerade „billig“ ist- dann laden wir immer.

Bekommen wir Beides unter einen Hut?

andig avatar Feb 09 '22 16:02 andig

Bekommen wir Beides unter einen Hut?

aktuell geht beides. Dann müssen die von dir genannten Änderungen noch umgesetzt werden. (cheap aus konfig übernehmen, cheap in site verschieben). Auch da würde ich Unterstützung brauchen.

schenlap avatar Feb 09 '22 16:02 schenlap

Bekommen wir Beides unter einen Hut?

aktuell geht beides. Dann müssen die von dir genannten Änderungen noch umgesetzt werden. (cheap aus konfig übernehmen, cheap in site verschieben). Auch da würde ich Unterstützung brauchen.

Hab ich eingebaut- Du bist wieder dran ;)

andig avatar Feb 10 '22 13:02 andig

@schenlap was fehlt denn noch um auf "Ready for review" zu kommen?

andig avatar Feb 11 '22 17:02 andig

Ich hab nochmal aufgeräumt- die letzten Knackpunkte müsstest Du Dir bitte anschauen.

andig avatar Feb 13 '22 12:02 andig

@schenlap der ganze letzte Block vereinfacht sich dann zu:

    // is the current slot the last planned slot?
	if curSlotNr == cntExpectedSlots && plannedDuration > requiredDuration {
		curSlotNr = 0
	}

Dann verstehe sogar ich den ;)

Dazu:

s/cntExpectedSlots/plannedSlots/
s/curSlotNr/currentSlot/

andig avatar Feb 13 '22 12:02 andig

Jetzt produzieren alle Tests ein fail. Es wird nie eingeschaltet. Bei den aktuellen Strompreise wäre das vielleicht sogar sinnvoll :-) . Auf den ersten Blick sind es nur Umbenennungen aber es hat sich grundlegend an der Funktion was geändert, das muss ich zuerst finden bevor ich weitermachen kann.

schenlap avatar Feb 13 '22 14:02 schenlap

Entschuldige. Ich schau auch nochmal rein. Wers kaputt macht muss auch reparieren.

andig avatar Feb 13 '22 14:02 andig

Das wäre eine mögliche Lösung. Es wird zuvor auf t.clockNow gesetzt, da die Zeit noch nicht weitergeschritten ist, wird Before nicht erfüllt. Ev. muss man es auch anders lösen.

@@ -101,7 +104,7 @@ func (t *Planner) PlanActive(requiredDuration time.Duration, targetTime time.Tim
                        slot.Price, plannedDuration)
 
                // current timeslot is a cheap one
-               if slot.Start.Before(t.clock.Now()) && slot.End.After(t.clock.Now()) {
+               if !slot.Start.After(t.clock.Now()) && slot.End.After(t.clock.Now()) {
                        cheapSlotNow = true
                        curSlotNr = cntExpectedSlots
                        t.log.TRACE.Printf(" (now, slot number %v)", curSlotNr)

schenlap avatar Feb 13 '22 14:02 schenlap

ok, Problem 1 sind die Test Cases. Durch umdrehen der before/after Logik gibts jetzt 1ns Differenz

andig avatar Feb 13 '22 15:02 andig

Es bleibt noch:

    planner_test.go:111: fixed tariff case 1: expected true, got false
    planner_test.go:111: fixed tariff case 1: expected true, got false
FAIL

andig avatar Feb 13 '22 15:02 andig

Mit if !slot.Start.After(t.clock.Now()) bekomme ich keinen Fail. Mit dieser Änderung bekommst du

    planner_test.go:111: fixed tariff case 1: expected true, got false
    planner_test.go:111: fixed tariff case 1: expected true, got false

?

Das wäre dann vielleicht wirklich ein Fehler im Test selbst. Muss ich mir ansehen

schenlap avatar Feb 13 '22 15:02 schenlap

Ich glaube ich habe noch eine große Unschönheit gefunden: Ist kein Tarif konfiguriert, dann wird mit Zielzeit über timer.go geladen. Konfiguriert man einen fixed-Tarif, z.B wegen der Ersparnisanzeige, dann wird mit planner.go geladen. Nur weil mein einen Preis einstellen will darf sich die Ladelogik nicht ändern. Auch wenn beide das selbe Verhalten erzeugen ist es nicht schön wenn unterschiedliche Code-Teile ausgeführt werden. Wie ich es in awattar implementiert habe, gab es das Problem nicht. War aber auch nicht sauber. Mit der Umstellung auf planner ist da jetzt der fixed Tarif aber mit rein gerutscht.

schenlap avatar Feb 13 '22 15:02 schenlap

Wie ich es in awattar implementiert habe, gab es das Problem nicht. War aber auch nicht sauber. Mit der Umstellung auf planner ist da jetzt der fixed Tarif aber mit rein gerutscht.

Mhhm. Für's erste könnten wir den Typ prüfen und den ausschließen. Aber wovon hängt das ab? Ob der Tarif überhaupt nur 1 Zone hat? Was bedeutet das mit Bezug auf https://github.com/evcc-io/evcc/issues/2524?

andig avatar Feb 13 '22 15:02 andig

Typ prüfen ist auch nicht schön. planner sollte timer.go eigentlich komplett ersetzen. Wobei das jetzt nicht so ist (nämlich dann wenn der SoC zum Zielzeitpunkt noch nicht erreicht wird, übernimmt derzeit noch der timer die restliche Ladung)

schenlap avatar Feb 13 '22 15:02 schenlap

Mit if !slot.Start.After(t.clock.Now()) bekomme ich keinen Fail. Mit dieser Änderung bekommst du

    planner_test.go:111: fixed tariff case 1: expected true, got false
    planner_test.go:111: fixed tariff case 1: expected true, got false

?

Das wäre dann vielleicht wirklich ein Fehler im Test selbst. Muss ich mir ansehen

Der "Fehler" ist in den Tests und im Verständnis von before/after und der Frage ob die genau matchende Millisekunde dazu zählt. Da das in der Realität irrelevant ist sollten m.E. die Tests defensiv sein und die Timestamps um 1ns verschieben, im Code selbst sollten wir das nicht tun.

andig avatar Feb 13 '22 15:02 andig

Typ prüfen ist auch nicht schön. planner sollte timer.go eigentlich komplett ersetzen. Wobei das jetzt nicht so ist (nämlich dann wenn der SoC zum Zielzeitpunkt noch nicht erreicht wird, übernimmt derzeit noch der timer die restliche Ladung)

Das eine ist doch eine Zeitsteuerung, das andere eine Preissteuerung. Preissteuerung macht nur Sinn wenn es unterschiedliche Preise gibt (>1 Zone, >1 Preis).

Alternativ: wir schmeissen die Zeitsteuerung weg und ersetzen sie- sollte es keinen Grid Tarif geben- durch einen "fake" Grid Tarif mit Preis 0.

andig avatar Feb 13 '22 15:02 andig

Ich frag mich auch grad, warum eigentlich die site bestimmen sollte, ob der Ladepunkt laden muss wg. seiner Planung. Auch nicht schön...

andig avatar Feb 13 '22 15:02 andig

Bingo! Im Ladepunkt können wir die Prio der beiden Planner umdrehen -> Problem gelöst. Also erst Restzeit, dann nach Tarif.

andig avatar Feb 13 '22 15:02 andig

Ich bau das nochmal komplett in Richtung Loadpoint; das wird allerdings umfangreicher...

andig avatar Feb 13 '22 17:02 andig

Update: sorry für die Verzögerung. Der Komplettumbau erfordert einige Anpassungen am bestehenden Zielladen die etwas komplexer sind. Bin noch dran...

andig avatar Feb 18 '22 12:02 andig

Depends on https://github.com/evcc-io/evcc/pull/2780 which will provide better abstraction for the time planner.

andig avatar Mar 15 '22 11:03 andig

My Electricity provider publishes the daily tariff using an API in JSON format. Is HTTP as source type supported for tariff parsing?If not, it might be an option to allow type: custom and source: http for using the feedin and grid tariffs

stefaanbolle avatar May 05 '22 17:05 stefaanbolle

Bitte neues Thema- wer ist Dein Anbieter?

andig avatar May 05 '22 18:05 andig

Done in discussion. Topic #3333 Provider is Eneco

stefaanbolle avatar May 05 '22 18:05 stefaanbolle