E-Auto Überschuss-Ladeautomatik mit FHEM, GoE Charger und Tibber

Seit ein paar Wochen habe ich unser 230V-Ladegerät für den eUp! gegen die Wallbox GoE Charger getauscht. Ich hatte schon immer den Wunsch, auch die Beladung des eUp! etwas flexibler gestalten zu können und damit auch den PV-Strom noch etwas besser ausnutzen zu können. Für die Wallbox von SMA, die in der Garage für die Beladung des Kia sorgt, hatte ich selbst sogar ein kleines Modul für meine Haussteuerung geschrieben. Wie ich die Wallbox über FHEM für eine automatisierte Steuerung nutze, hatte ich bereits in einem Artikel erläutert.

Paketbox noch mit dem 230V Ladegerät

Dies war nun auch die Basis für die Steuerung des GoE Chargers. Ziel war es, dass die Wallbox idealerweise direkt über das SMA Portal für Überschußladung genutzt werden kann und dass zusätzliche Ladeautomatiken genutzt werden können. Da der eUp! in erster Linie von meiner Frau genutzt wird, gibt es hier ein paar Rahmenbedingungen, die ich möglichst automatisiert steuern wollte ;-). So darf die Batterie beispielsweise im Winter nie zu niedrig geladen sein, damit nicht die Gefahr besteht, dass die Heizung während der Fahrt abgeschaltet wird.

GoE Charger in meiner Paketbox

Leider unterstützt SMA die Wallbox von GoE nicht direkt, so dass ich einen Umweg nehmen musste, um die Wallbox in das Sunny-Portal zu integrieren. Da ich aktuell auch dynamische Strompreise über den Anbieter Tibber nutze, sollte natürlich auch die Beladung zu besonders günstigen Strompreisen mit realisiert werden.

Das Ergebnis ist nun eine vollautomastisierte Ladesteuerung, die in Verbindung mit SMA der FHEM Haussteuerung und dem GoE Charger kaum noch manuelle Eingriffe erfordert. Nachfolgend möchte ich euch die Lösung präsentieren, wobei ich mich auf die eigentliche Steuerung innerhalb FHEM beschränke. Damit kann man vielleicht die ein oder anderen Lösungsansätze für die eigene Umgebung realisieren.

Vorbereitende Maßnahmen sowie Voraussetzungen für die automatisierte Ladesteuerung

Installation des GoE Chargers und Integration ins SMA Sunny-Portal

Der erste Schritt war natürlich die Installation der Wallbox. Diese habe ich nur einphasig angeschlossen, weil ich für die bisherige Lösung nur ein dreiadriges Kabel verlegt hatte. Der Charger befindet sich nun auch in unserer Paketbox, so dass sich die Handhabung nicht ändert.

Weggefallen ist damit die schaltbare Fritz! Steckdose, die ich direkt in das SMA Portal integriert hatte. Den GoE Charger habe ich nun über die Open Source Software Smart Appliance Enabler (SAE) in das SMA Portal integriert. Damit habe ich bereits andere Steckdosen intergiert, die ich insbesondere zur Strommessung von Geräten nutze. Auf diese Weise ist der GoE Charger, wie meine SMA Wallbox in die Steuerung des Sunny-Portals integriert und wird somit optimal mit PV-Überschuß versorgt.

Smart Appliance Enabler mit diversen Steckdosen und dem GoE Charger

Grundsätzlich hat man auch über den SAE die Möglichkeit, diverse Ladeoptionen zu steuern. Mit einer „Schnellladung“ kann die Ladung sofort gestartet werden und wird bei Erreichen eines definierten Ziel-SoC durchgeführt. Mit der Option „Optimiert“ kann ein Zielzeitpunkt angegeben werden, bis zu dem eine Ladung erfolgen soll. Hierbei wird dann möglicht Sonnenstrom genutzt. Der Standard ist die Option „PV-Überschuß“. In diesem Zustand erfolgt die Beladung entsprechend der Einstellungen im Sunny-Portal mit überschüssigem Solarstrom.

Optionen zum Start der Wallbox über den SAE

Leider lässt sich der SAE nicht über eine API direkt steuern. Man könnte wohl etwas über MQTT realisieren aber soweit ich bisher verstanden habe betrifft dies nicht die entsprechenden Schaltbefehle. Darüber hinaus kenne ich mich mit MQTT bisher nicht so gut aus. Daher habe ich entsprechende Funktionen zunächst komplett in FHEM gelöst. Im SAE habe ich die Wallbox so konfiguriert, dass sie nach einem Start immer mindestens 20 Minuten läuft, bevor sie unterbrochen wird. Diesen Wert kann man natürlich nach seinen Bedürfnissen anpassen.

Nachteil dabei ist, dass das Sunny-Portal nur mitbekommt, dass die Wallbox gestartet wird aber nicht, zu welchem Zweck. Damit schaltet das Sunny-Portal die Wallbox nach der Mindestlaufzeit immer aus, wenn die Bedingungen nicht den Einstellungen im Portal entsprechen oder schaltet auch den Strom der Wallbox herunter. Dies musste ich daher in meiner Steuerung berücksichtigen.

Benötigte Funktionen in FHEM

Meine Ladesteuerung habe ich in FHEM mit einem DOIF gelöst, welches ich nachfolgend detailliert vorstelle. Weiterhin habe ich meine Tibber Funktion zur Ermittlung der günstigsten Strompreise genutzt bzw. entsprechend erweitert. Zusätzlich habe ich eine Funktion realisiert, die mir zu jeder Stunde den notwendigen PV-Strom berechnet, der mindestens benötigt wird, um den aktuellen Netzpreis so zu reduzieren, dass er dem günstigsten Strompreis entspricht.

Im Winter ist die besonders hilfreich. Da das Sunny-Portal leider keine dynamischen Strompreise berücksichtigt kann man nur allgemein einen Wert für den Anteil zwischen PV-Strom und Netzstrom angeben. Bisher habe ich den Wert während des Winters immer weiter nach unten angepasst, damit die Wallbox auch bei wenig Überschuß gestartet wird. Diesen Wert habe ich nun bei ca. 40%-50% belassen. Mit der Steuerung in FHEM wird nun regelmäßig berechnet, wie hoch der Anteil des Überschuß sein muß, damit eine Ladung günstiger ist, als wenn ich diese auch ohne PV-Strom nur mit dem günstigsten Netzstrom durchführe.

Damit die Automatik funktioniert, ist natürlich ein Abruf des Ladezustands des verbundenen E-Autos sinnvoll. Solche Funktionen findet man u.a. bei Github. Mit etwas Glück gibt es die Funktion auch beim SAE. Dieser bietet die Möglichkeit, den SoC über eine Funktion abzurufen. Da ich den Abruf bereits über FHEM realisiert habe, rufe ich aus dem SAE die Infos aus dem FHEM ab.

Grundsätzlich kann man meine Automatik auch nutzen, wenn man eine solche Integration nicht hat. Dazu wäre es dann aber hilfreich, wenn man wenigstens Lademenge ermitteln kann, die beim automatisierten Ladevorgang verbraucht hat. Beim GoE Charger funktioniert das ganz gut, da es in FHEM ein Modul für diese Wallbox gibt. Damit die Automatik auch ohne Anbindung an das E-Auto funktioniert, muß man FHEM den aktuellen SoC des Autos manuell übergeben. Wahrscheinlich kann man auch eine Lösung mittels Alexa realisieren.

Nicht vergessen darf man natürlich auch die Werte aus der PV-Anlage. Ich nutze hierzu die FHEM Module SMAEM für den Sunny Energiemanager und SMAInverter für den Wechselrichter von SMA.

GoE Charger im Sunny Portal

Status des E-Autos regelmäßig abrufen

Nachfolgend zeige ich euch mein DOIF, mit dem ich den Status des eUp! regelmäßig abrufe. Hierbei rufe ich eine entsprechende Python-Funktion auf. Ist diese erfolgreich, werden die Parameter in ein Dummy kopiert, so dass diese beispielsweise für die Anzeige in einem Dashboard genutzt werden kann. Auf die Funktionen selbst würde ich hier nicht eingehen, da diese doch sehr individuell sind. Das DOIF ist ansonsten hoffentlich aussagefähig genug, macht aber streng genommen auch nicht sehr viel. Für die Ladeautomatik ist es wichtig, den SoC des E-Autos zu erhalten.

defmod diEUPStatus DOIF ##\
## Status vom EUP abfragen\
##\
\
##\
## Wenn die Wallbox läuft, wird alle 20 Minuten der akt. SoC abgefragt\
## (1)\
([+00:20] and [duEUP:evBatteryPlugin] eq "connected" and [myGoE:allow_charging] eq "1")\
	({system("python3 ./weconnect/myeup/trunk/allVehicles.py &")})\
	\
##\
## Tagsüber wird jede Stunden einmal der Status abgefragt\
## (2)\
DOELSEIF ([07:00-20:00,+1:00] and [?myGoE:allow_charging] eq "0" and [?duUrlaub] eq "nein")\
	({system("python3 ./weconnect/myeup/trunk/allVehicles.py &")})\
	\
##\
## Wenn die Abfragefunktion erfolgreich war, dann werden die Werte in den Dummy für den eUp! übetragen\
## (3)\
DOELSEIF ([$SELF:EUPAPI] =~ m/"returnCode": "OK"/)\
	({eUpStatusSetzen()})({checkEUPGeofancy()}) \
	\
##\
## Feststellen, ob der eUP bewegt wurde\
## (4)\
DOELSEIF ((([rr_Anke] eq "absent" or [rr_Juergen] eq "absent") and [?rg_EUP] eq "home")\
	or (([rr_Anke] eq "home" or [rr_Juergen] eq "home") and [?rg_EUP] eq "absent") or [rg_EUP] eq "home"\
	or [myGoE:car_state] eq "3")\
	({system("python3 ./weconnect/myeup/trunk/allVehicles.py &")})({checkEUPGeofancy()})\
##\
## Heizung einschalten\
## (5)\
DOELSEIF ([duEUPKlima:desired-temp])\
   ({system("python3 ./weconnect/myeup/trunk/climatization.py &")};; set diEUPLadung Sofortladen ja) (set diEUPLadung Sofortladen nein)
attr diEUPStatus do always
attr diEUPStatus room Alles,PV_E_Auto
attr diEUPStatus userReadings nextUpdate {(ReadingsVal("duEUP","evBatteryCharge","off") eq "off") ? ReadingsVal("diEUPStatus","timer_04_c02","06:55") : ReadingsVal("diEUPStatus","timer_01_c01","06:00")}
attr diEUPStatus wait :::300,120:0,600:

Notwendige FHEM-Funktionen für die Lade-Automatisierung

Bevor ich auf das DOIF zur Ladesteuerung eingehe, findet ihr nachfolgend die zusätzlichen Funktionen, die ich in der 99_myUtils abgelegt habe.

Da es manchmal Probleme gibt hier noch der Auszug des Codes, den ich am Start der 99_myUtils eingetragen habe. Wahrscheinlich sind ein paar Einträge dabei, die für die gezeigten Funktionen nicht benötigt werden aber so sollten hoffentlich keine Fehler auftauchen, wenn man die Einträge noch nicht in der eigenen myutils hat.

use strict;
use warnings;
use POSIX qw(strftime);
use HttpUtils;
use Data::GUID;
use JSON::XS;
use Time::Piece;

use IO::Socket::SSL;
use LWP::UserAgent;

use List::Util qw( sum );
use List::MoreUtils qw{
        true first_index
    };

Die nächsten Funktionen sind eine leichte Anpassung meiner Funktionen aus dem Artikel der FHEM-Einbindung von Tibber. Daher würde ich dieser hier im Detail nicht erläutern. Benötigt wird natürlich alles, was ich zur Einbindung von Tibber in dem Artikel erläutert habe.

# Aus dem Array mit den Strompreisen des definierten Zeitfenster wird der Zeitraum mit dem niedrigsten Preis ermittelt
# Parameter1: Preisarray
# Parameter2: Laufzeit in Stunden
# Return: Der StartIndex innerhalb der übergebenen Strompreise
#
sub MinTibberZeitfenster
{
	my ($Strompreise, $Laufzeit) = @_;
	my $anz = @{$Strompreise};
	my @PreisIntervall;
	my @ret;
	
	#Log3 undef, 3, "MinTibberZeitfenster: Anzahl Strompreise=$anz Laufzeit=$Laufzeit";
	
	for (my $i = 0; $i < ($anz - $Laufzeit +1); $i++)
	{
	   @PreisIntervall[$i] = sum @{$Strompreise}[$i..$i+$Laufzeit-1];
	   Log3 undef, 3, "Preisintervall i=$i anz=$anz Summe: @PreisIntervall[$i]";
	}
	
	my $MinPreis = min @PreisIntervall;
	my $MinIndex = first_index { $_ eq $MinPreis } @PreisIntervall;
	
	Log3 undef, 3, "MinTibberZeitfenster: MinPreis=$MinPreis MinIndex=$MinIndex";

    @ret[0] = $MinIndex;
	@ret[1] = $MinPreis / $Laufzeit;

	return @ret;
}


# Funktion zur Ermittlung der Stunden, die nach einer Mindestuhrzeit liegen, um den günstigsten Strom zu nutzen
# Darüber hinaus gibt die Funktion den durchschnittlichen Preis in der Zeit zurück
#
sub IndexMinPreis($$$)
{
	my ($MinHour,$Laufzeit,$LaufzeitEnde) = @_;
	
	my @PreiseHeute = split /\|/, ReadingsVal("myTibber","TodayTotal",0.25);
	my @PreiseMorgen = split /\|/, ReadingsVal("myTibber","TomorrowTotal",0.25);
	my @AllePreise = @PreiseMorgen[0] ne "NV" ? (@PreiseHeute, @PreiseMorgen) : @PreiseHeute;
	my ($tmp, $m, $h, $tmp, $tmp, $tmp, $tmp, $tmp, $tmp) = localtime(time);
	my $MinZeit = "";
	
	# Variablen auf sinnvolle Werte setzen, falls diese nicht passen
	$Laufzeit = $Laufzeit <= 0 ? 1 : $Laufzeit;
	$MinHour = $MinHour < 0 || $MinHour > 24 ? 0 : $MinHour;

	if($h >= $MinHour)
	{
	   $MinHour = $h+1;
	   $LaufzeitEnde = $LaufzeitEnde - ($h - $MinHour);
	}

	$LaufzeitEnde = $MinHour + $LaufzeitEnde > 24 && $PreiseMorgen[0] eq "NV" ? 24 - $MinHour : $MinHour + $LaufzeitEnde > 48 ? 48 - $MinHour : $LaufzeitEnde; 
	
    my $LaufzeitIndex = $MinHour + $LaufzeitEnde - 1;
	
	@AllePreise = @AllePreise[$MinHour..$LaufzeitIndex];
	
	my @IndexPreis = MinTibberZeitfenster(\@AllePreise, $Laufzeit);
	
	return @IndexPreis;
}


# Günstigsten Strompreis für eine Dauer von X Minuten finden
# Parameter:
#    MinHour: FrühesterStart (Beispiel: 15 für 15:00 Uhr),
#    Laufzeit: Laufzeit in Stunden, (Da es nur Stundenpreise gibt, kann die Laufzeit immer auf Stunden aufgerundet werden)
#    Laufzeit_Ende: Anzahl Stunden nach frühestem Start (Beispiel: 12 für 12 Stunden nach frühester Start).
#                   Der Wert gibt dann quasi das Ende der Laufzeit an
# Es wird immer die Startzeit für den aktuellen Tag angenommen und wenn die aktuelle Zeit nach dem frühesten Start liegt,
# wird die früheste Startzeit auf die aktuelle Zeit zzgl. 2 Minuten gesetzt
# Die Funktion ermittelt dann die Uhrzeit, in der der günstigste Strom für die Dauer von Laufzeit zu erwarten ist
# 
# Beispiel: MinStromTime(15, 3, 24) -> Ermittelt den günstigsten Strom für 3 Stunden Laufzeit, 
# der am gleichen Tag nach 15:00 Uhr liegt und im Zeitfenster bis 15:00 + 24 Stunden - also am nächsten Tag um 15:00 Uhr liegt
# 
sub MinStromTime($$$)
{
	my ($MinHour,$Laufzeit,$LaufzeitEnde) = @_;
	
	my @PreiseHeute = split /\|/, ReadingsVal("myTibber","TodayTotal",0.25);
	my @PreiseMorgen = split /\|/, ReadingsVal("myTibber","TomorrowTotal",0.25);
	my @AllePreise = @PreiseMorgen[0] ne "NV" ? (@PreiseHeute, @PreiseMorgen) : @PreiseHeute;
	my ($tmp, $m, $h, $tmp, $tmp, $tmp, $tmp, $tmp, $tmp) = localtime(time);
	my $MinZeit = "";
	
	# Variablen auf sinnvolle Werte setzen, falls diese nicht passen
	$Laufzeit = $Laufzeit == 0 ? 1 : $Laufzeit;
	$MinHour = $MinHour < 0 || $MinHour > 24 ? 0 : $MinHour;
	
	if($h >= $MinHour)
	{
	   $MinHour = $h+1;
	   $LaufzeitEnde = $LaufzeitEnde - ($h - $MinHour);
	}
	
	my $MinZeitIndex = (IndexMinPreis($MinHour,$Laufzeit,$LaufzeitEnde))[0];
	
	if($MinZeitIndex + $MinHour > 24) # Uhrzeit ist am nächsten Tag
	{
		$MinZeit = sprintf("%02d", $MinZeitIndex + $MinHour - 24).":00";
	}
	else
	{
	    $MinZeit = sprintf("%02d", $MinZeitIndex + $MinHour).":00";
	}
	
	Log3 undef, 3, "MindestStrompreis: Startzeit:$MinZeit";
	
	return $MinZeit;
}

Die folgende Funktion dient zur Ermittlung des PV-Anteils, der dafür sorgt, dass der aktuelle Strompreis in Verbindung mit dem PV-Strom so niedrig ist, wie der günstigste durchschnittliche Strompreis. Gibt die Funktion beispielsweise 0,2 zurück, dann werden 20% PV-Strom benötigt. Bei einer Mindestladung mit 6A würde man mit mindestens 1.380 W laden. Es müsste also ein PV-Überschuß von mind. 0,2 x 1.380 = 276W vorhanden sein.

Den Gestehungspreis habe ich aus den Kosten der PV-Anlage und der erwarteten PV-Leistung über 20 Jahre ermittelt. Wie sich der Wert berechnet kann man u.a. in meiner Jahreauswertung zur Wirtschaftlichkeit der PV-Anlage nachlesen.

# Funktion zur Ermittlung des Minimum PV-Stromes, damit die E-Auto Ladung günstiger ist
# als über den günstigsten Netz-Strompreis
#
sub MinPVLaden($)
{
   my ($Laufzeit) = @_;
   my $Einspeiseverguetung = 0.0864;
   my $Gestehungskosten = 0.12;
   my $AktNetzPreis = ReadingsVal("myTibber","Strompreis","0.25") / 100;
   my @MinTibber = IndexMinPreis(0,$Laufzeit,33); # Annahme, immer bis 9:00 Uhr am nächsten Tag laden
   my $MinNetzPreis = @MinTibber[1];
   
   Log3 undef, 3, "Akt.Preis: $AktNetzPreis   MinimalerPreis: $MinNetzPreis";

   my $anteilPV = $Einspeiseverguetung-$AktNetzPreis != 0 ? ($MinNetzPreis - $AktNetzPreis) / ($Einspeiseverguetung-$AktNetzPreis) : 0;
   
   $anteilPV = $anteilPV < 0 ? 0 : $anteilPV;
   
   return $anteilPV;
}

Schließlich benötigen wir noch eine Funktion, die die nächste Abfahrzeit ermittelt. Diese Funktion hatte ich bereits einmal vorgestellt, als die Steuerung mit unserer SMA Wallbox vorgestellt habe.

# nächste Abfahrzeit vom Kia ermitteln für eine mögliche automatische Ladung
sub naechsteAbfahrt($)
{
	my ($device) = @_;
	my @wt = qw(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag); 
	my $heute = strftime('%w', localtime); 
	my $stunde = strftime('%H', localtime);
	my $minute = strftime('%M', localtime);
	my $next = $heute;
	
	my $abfahrt = ReadingsVal("$device", "$wt[$next]", "00:00");
	my ($abfahrt_stunde, $abfahrt_minute) = split(/:/,$abfahrt);
	
	if ( ($stunde * 60 + $minute) > ($abfahrt_stunde * 60 + $abfahrt_minute) )
	{
		$next = $heute + ($heute < 6 ? 1 : -6);
		$abfahrt = ReadingsVal("$device", "$wt[$next]", '00:00');
	}
	
	while( $abfahrt eq "00:00" and $next != $heute )  
	{
		$next += ($next < 6 ? 1 : -6);
		$abfahrt = ReadingsVal("$device", "$wt[$next]", '00:00');
	}
	
	if ($abfahrt eq "00:00")
	{
		return "Keine Abfahrzeit definiert"
	}
	else
	{
		
		my $t = localtime(); 
		$t =~ s/\d\d:\d\d:\d\d/$abfahrt:00/;
		my $dif = $heute <= $next ? $next - $heute : $next - $heute + 7;
		my $abfahrt_num = str2time($t) + ($dif * 24 * 60 * 60);
		
		return $abfahrt_num;
		#return $wt[$next]." um ".$abfahrt;
	}
}

Das FHEM-DOIF für die Ladeautomatisierung des E-Autos

Nachdem nun alle Funktionen und Daten vorhanden sein sollten, können wir uns der eigentlichen Automatisierungsfunktion zuwenden. Bei mir stelle ich die entsprechenden über mein Tablet mit einer FTUI-Oberfläche ein. Das DOIF ist aber so gestaltet, dass man alle Werte entsprechend über die normale FHEM-Oberfläche schalten kann. In meiner Oberfläche fehlt aktuell noch ein Schalter zu allgemeinen Aktivierung der Automatik. Da diese für den eUp! aber eigentlich immer eingeschaltet ist, habe ich mir diesen Schalter zunächst gespart.

Dashboard zur Steuerung der Ladeautomatik für unseren eUp!

Im DOIF sind daher auch Funktionen enthalten, die beispielsweise zur Speicherung der Abfahrzeiten aus dem Dashboard heraus, benötigt werden (Tablet Schalter). Die Anzeige des geplanten Ladeendes habe ich hier nicht vorgesehen und ist nur enthalten, weil ich für unseren Kia eine entsprechende Anzeige habe.

Ein kleines DOIF muß ich euch noch vorab zeigen, damit alles funktioniert. Dieses DOIF ermittelt stündlich den aktuellen Anteil der PV, der benötigt wird, um den günstigsten Netzstrompreis zu erreichen. Der entsprechende Wert wird im DOIF selbst gespeichert, so dass er von anderen Funktionen verwendet werden kann.

defmod di_PVAnteilTibber DOIF ([6:00-22:00,+[1]:05])\
   (set $SELF AnteilPV {(MinPVLaden(ceil([diEUPLadung:Lademenge]/3)))})
attr di_PVAnteilTibber do always
attr di_PVAnteilTibber readingList AnteilPV
attr di_PVAnteilTibber room PV_E_Auto

Das DOIF für die Automatisierung sorgt im Prinzip für folgende Funktionalitäten:

  • Bei Unterschreitung eines minimalen SoC werden Ladeparameter gesetzt, so dass bis spätestens am nächsten Tag um 9:00 Uhr mindestens dieser Ladestand vorhanden ist
  • Ist eine Abfahrzeit innerhalb der nächsten 24 Stunden, dann werden die Ladeparameter so gesetzt, dass die SoC-Obergrenze erreicht wird
  • Bei ausreichend Solarstrom oder bei manuellem Start wird die Ladung gestartet und bis zu maximal 80% oder SoC-Obergrenze geladen
  • Mit diversen Sicherheitsfunktionen wird sicher gestellt, dass entsprechend der FHEM-Steuerung geladen wird, auch wenn das Sunny-Portal etwas dagegen hat und Einstellungen verändert oder die Ladung stoppt
  • Mit einer Reset-Funktion können die Abfahrzeiten auf Defaultwerte zurück gesetzt werden. Dieser Schalter fehlt aktuell auch noch im Dashboard
defmod diEUPLadung DOIF ## Schaltzeiten werden geschaltet, wenn Ladung_Aktiv == on - damit kann also die gesamte Automatik deaktiviert werden\
## Für jeden Tag kann eine Abfahrtzeit definiert werden, bis zu der die Ladung mit SOC_Obergrenze erreicht sein muß\
## Wenn für einen Tag keine Abfahrzeit definiert werden soll - also keine Zielzeit für eine Ladung definiert wird - dann die Zeit 00:00 eintragen\
## Sobald der ermittelte SOC aus duEUP unterhalb von SOC_Untergrenze liegt, wird eine Ladung zur günstigsten Stromzeit ermittelt\
## Die zu ladende Energie wird aus der Differenz von SOC_Untergrenze und SOC_Obergrenze ermittelt \
## und mit MAX_Kapazitaet und dem Aufschlag für den Ladeverlust berechnet\
## Lademenge = (SOC_Ober/Untergrenze - SOC des Autos) / 100 * MAX_Kapazitaet * (100+Ladeverlust)/100\
##\
\
## Ladung mit Vorgabe, wenn nicht mehr genug Akkustand vorhanden ist\
## Wenn die nächste Abfahrt am nächsten Tag ist, dann bis zur Obergrenze laden\
## \
## (1)\
([?$SELF:Ladeautomatik, "off"] eq "on"\
and [duEUP:evBatteryPlugin] eq "connected" and [myGoE:allow_charging] eq "0"\
and [duEUP:evBatterySoc] < [$SELF:SOC_Obergrenze] and ([$SELF:Naechste_Abfahrt_Num]-time())/3600 <= 24 \
and [$SELF:Param_Betriebsart_Ladevorgang] ne "mit_Vorgabe")\
   ({WallboxAutomatik("$SELF","$SELF")};; set duEUP TibberLadung max;;\
   {fhem("set duEUP Ladestart ".MinStromTime(strftime("%H",localtime), ceil([diEUPLadung:Lademenge]/3),\
		  (int(([diEUPLadung:Naechste_Abfahrt_Num]-(time()))/3600))))})\
\
## Ladung mit Vorgabe, wenn nicht mehr genug Akkustand vorhanden ist\
## Wenn die nächste Abfahrt nicht am nächsten Tag ist, dann bis zur Untergrenze laden\
## \
## (2)\
DOELSEIF ([?$SELF:Ladeautomatik, "off"] eq "on"\
and [duEUP:evBatteryPlugin] eq "connected" and [myGoE:allow_charging] eq "0"\
and [duEUP:evBatterySoc] < [$SELF:SOC_Untergrenze]\
and [$SELF:Param_Betriebsart_Ladevorgang] ne "mit_Vorgabe") \
   ({WallboxAutomatik("$SELF","$SELF")};; set duEUP TibberLadung min;; \
   {fhem("set duEUP Ladestart ".MinStromTime(strftime("%H",localtime),\
          ceil([diEUPLadung:Lademenge_Min]/3), 24 - strftime("%H",localtime) + 10))})\
\
## Wenn Ladung manuell gestartet wird, Wallbox sofort mit voller Energie einschalten\
## Abschaltung erfolgt entweder manuell oder spätestens nach Erreichung von SOC_Obergrenze\
## Wenn die Startzeit für eine Ladung mit billigem Strom erreicht ist, die Wallbox einschalten\
## Ladung erfolgt dann abhängig vom Status beim duEUP bis zur Ober- oder Untergrenze\
## (3)\
DOELSEIF (([$SELF:Sofortladen] eq "ja" or ([duEUP:TibberLadung] ne "nein" and [[duEUP:Ladestart]])) and [duEUP:evBatteryPlugin] eq "connected")\
   (set $SELF Param_Betriebsart_Ladevorgang mit_Vorgabe;; set myGoE on;; set myGoE amp_current 14)\
\
##\
## Beim Abstecken werden die Parameter auf 0 gesetzt und der Ladevorgang auf "optimiert" gesetzt\
##\
## (4)\
DOELSEIF([duEUP:evBatteryPlugin] ne "connected")\
   (setreading diEUPLadung Param_Energiemenge_Ladevorgang 0;; setreading diEUPLadung Param_Dauer_Ladevorgang 0;; \
    set $SELF Param_Betriebsart_Ladevorgang Optimiert;; set $SELF Sofortladen nein;;)\
\
##\
## Ist der gewünschte Batteriestand erreicht, wird die Ladung beendet und auf "optimiert" geschaltet\
##\
## (5)\
DOELSEIF ( (  ([duEUP:TibberLadung] eq "nein" and [duEUP:evBatterySoc] >= [$SELF:SOC_Obergrenze])\
           or ([duEUP:TibberLadung] eq "max" and [duEUP:evBatterySoc] >= [$SELF:SOC_Obergrenze])\
		   or ([duEUP:TibberLadung] eq "min" and [duEUP:evBatterySoc] >= [$SELF:SOC_Untergrenze]) )\
         and [$SELF:Param_Betriebsart_Ladevorgang] eq "mit_Vorgabe" )\
   (set myGoE off;; set $SELF Sofortladen nein;; set $SELF Param_Betriebsart_Ladevorgang Optimiert;; set duEUP TibberLadung nein;; )\
\
##\
## Wird der Strom während einer Ladung mit Vorgabe oder mit Tibber durch die PV-Steuerung ausgeschaltet\
## dann wird der Strom nach 1 Minute wieder angeschaltet\
##\
## (6)\
DOELSEIF ([myGoE:allow_charging] eq "0" and ([$SELF:Param_Betriebsart_Ladevorgang] eq "mit_Vorgabe" or \
           ([$SELF:Param_Betriebsart_Ladevorgang] eq "PV_Tibber" and [smaenergy:Einspeisung_Wirkleistung] > 1200 * [di_PVAnteilTibber:AnteilPV]))\
           and [duEUP:evBatteryPlugin] eq "connected" and [duEUP:evBatterySoc] < [$SELF:SOC_Obergrenze])\
   (set myGoE on)\
\
##\
## Falls ausreichend Sonne vorhanden ist, um einen günstigeren Strompreis zu erzielen, als der günstigste Preis für die Ladung bis SOC_Obergrenze\
## wird die Ladung gestartet, wobei der Zustand zweimal innerhalb von 5 Minuten geprüft wird. Zusätzlich wird geprüft, ob sich Großverbraucher im \
## Automatikmodus befinden und deren potentieller Verbrauch mit eingerechnet.\
## (7)  \
DOELSEIF ([myGoE:allow_charging] eq "0" and [duEUP:evBatteryPlugin] eq "connected" and [duEUP:evBatterySoc] <= max(int([$SELF:SOC_Obergrenze]), 80)\
   and [mySMA_WR:avg_power_lastminutes_15:d] > (1200 * [di_PVAnteilTibber:AnteilPV] + aktVerbrauch_Grossverbraucher())\
   and [smaenergy:Einspeisung_Wirkleistung] >= 1200 * [di_PVAnteilTibber:AnteilPV])\
   (set myGoE amp_current {([di_PVAnteilTibber:AnteilPV] eq 0 ? 16 : max(min(int(([smaenergy:Einspeisung_Wirkleistung]) / [di_PVAnteilTibber:AnteilPV] / 230), 16),6))};; set myGoE on;; set $SELF Param_Betriebsart_Ladevorgang PV_Tibber)\
\
##\
## Wenn die Schaltung nicht über die PV-Steuerung initiiert wurde und ein größerer Verbraucher startet, dann Ladestation ausschalten\
##\
## (8)\
DOELSEIF ([$SELF:Param_Betriebsart_Ladevorgang] eq "PV_Tibber"	 \
              and (     [duEUP:evBatteryPlugin] ne "connected" \
					 or [mySMA_WR:avg_power_lastminutes_15:d] < (1200 * [di_PVAnteilTibber:AnteilPV] + aktVerbrauch_Grossverbraucher())\
		             or [duEUP:evBatterySoc] >=  [$SELF:SOC_Obergrenze]\
				  )\
	     )\
   (set myGoE off;; set $SELF Param_Betriebsart_Ladevorgang Optimiert)\
\
##\
## Wenn Sofortladen ausgeschaltet wird, dann Ladung beendet\
##\
## (9)\
DOELSEIF ([$SELF:Sofortladen] eq "nein")\
   (set $SELF Param_Betriebsart_Ladevorgang Optimiert;; set myGoE off)\
\
##\
## Prüfen, ob der Strom eventuell zu hoch eingestellt ist, wenn mit Vorgabe geladen wird\
## \
## (10)\
DOELSEIF ([myGoE:allow_charging] eq "1" and [smaenergy:L2_Strom] > 20)\
   (set myGoE amp_current min(10, (20 - max(6, int([smaenergy:L2_Strom]-[myGoE:amp_current])))))\
\
##\
## Festlegung mit wieviel Strom im Zustand PV_Tibber geladen werden kann\
## Es wird an hand der aktuellen PV-Leistung und einer Annahme von 400W Grundlast die theoretische Einspeisung \
## ohne die Wallbox ermittelt\
##\
## (11)\
DOELSEIF ([$SELF:Param_Betriebsart_Ladevorgang] eq "PV_Tibber" and [myGoE:allow_charging] eq 1 and\
            ( [di_PVAnteilTibber:AnteilPV] eq "0" or\
            ([mySMA_WR:SPOT_PACTOT]-400 > 0 and max(min(int(([mySMA_WR:SPOT_PACTOT]-400) / [di_PVAnteilTibber:AnteilPV] / 230), 16),6) != [myGoE:amp_current]\
			 or ([di_PVAnteilTibber:AnteilPV] eq "0" and [myGoE:amp_current] ne "16")\
			))\
         )\
   (set myGoE amp_current {([di_PVAnteilTibber:AnteilPV] eq 0 ? 16 : max(min(int(([smaenergy:Einspeisung_Wirkleistung]) / [di_PVAnteilTibber:AnteilPV] / 230), 16),6))})\
\
##\
## Falls Laden mit Vorgabe und die PV schaltet den Strom auf 6A, dann wieder auf 14A einstellen\
##\
## (12)\
DOELSEIF ([$SELF:Param_Betriebsart_Ladevorgang] eq "mit_Vorgabe" and [myGoE:amp_current] eq "6" and [smaenergy:L2_Strom] <= 8)\
   (set myGoE amp_current 14)\
\
##\
## Reset der Abfahrzeiten\
## (13)\
DOELSEIF ([$SELF:Reset])\
   (set $SELF Montag 09:00, set $SELF Dienstag 00:00, set $SELF Mittwoch 09:00, set $SELF Donnerstag 09:00, set $SELF Freitag 09:00, \
   set $SELF Samstag 00:00, set $SELF Sonntag 00:00)
attr diEUPLadung do always
attr diEUPLadung readingList Ladeautomatik Reset Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag SOC_Untergrenze SOC_Obergrenze Max_Kapazitaet Ladeverlust Sofortladen Tablet_Wochentag Tablet_Uhrzeit Tablet_Ok Param_Dauer_Ladevorgang Param_Energiemenge_Ladevorgang Param_Betriebsart_Ladevorgang
attr diEUPLadung room Alles,PV_E_Auto
attr diEUPLadung setList Sofortladen:ja,nein Ladeautomatik:on,off Reset:on Montag:time Dienstag:time Mittwoch:time Donnerstag:time Freitag:time Samstag:time Sonntag:time SOC_Untergrenze:slider,20,10,60 SOC_Obergrenze:slider,40,10,90 Max_Kapazitaet Ladeverlust Param_Energiemenge_Ladevorgang Param_Dauer_Ladevorgang Param_Betriebsart_Ladevorgang:Optimiert,mit_Vorgabe,PV_Optimiert Zaehler_Startladung
attr diEUPLadung stateFormat Param_Betriebsart_Ladevorgang
attr diEUPLadung userReadings Lademenge {ceil(max(0, ReadingsVal("diEUPLadung","SOC_Obergrenze",30) - ReadingsVal("duEUP","evBatterySoc",20)) \
					* ReadingsVal("diEUPLadung","Max_Kapazitaet",32) / 100 \
					* (1 + ReadingsVal("diEUP","Ladeverlust", 15) / 100))},\
Lademenge_Min {ceil(max(0, ReadingsVal("diEUPLadung","SOC_Untergrenze",30) - ReadingsVal("duEUP","evBatterySoc",20)) \
					* ReadingsVal("diEUPLadung","Max_Kapazitaet",32) / 100 \
					* (1 + ReadingsVal("diEUP","Ladeverlust", 15) / 100))},\
Naechste_Abfahrt_Num {naechsteAbfahrt("diEUPLadung")},\
Naechste_Abfahrt {FmtDateTime(ReadingsNum("diEUPLadung", "Naechste_Abfahrt_Num", time()))}
attr diEUPLadung wait :::::60::300::::
attr diEUPLadung waitsame ::::::300:::::

Das Dummy Device für das E-Auto wird natürlich auch benötigt, damit die Werte gespeichert bzw. der aktuelle SoC ermittelt werden kann. Hier werden mindestens die Readings „Ladestart“, „TibberLadung“, „evBatterySoc“, „evBatteryPlugin“ benötigt.

Anpassungen ohne E-Auto Anbindung für die Ermittlung des Ladestands

Habt ihr nicht die Möglichkeit, den SoC eures E-Autos automatisch zu ermitteln, dann müsste der Wert im Dummy für das E-Auto von Hand gesetzt werden. Das ist zwar etwas umständlich aber nur so kann die Automatik sinnvoll genutzt werden. Man könnte sich auch damit behelfen, immer einen konstanten (niedrigen) Wert einzutragen, so dass die Automatik auf jeden Fall ausgelöst wird.

Bei mir arbeite ich in den DOIFs immer mit dem SoC-Wert. Man kann die Abfragen natürlich auch durch konkrete Ladewerte austauschen. Wenn das Auto darüber hinaus so eingestellt werden kann, dass nur bis zu einer maximalen Obergrenze geladen wird, dann verhindert man auch, dass beispielsweise über 80% SoC noch geladen wird.

Beim GoE Charger bzw. bei dem FHEM Modul kann man mit dem Reading „kw_charged_last“ die Lademengenge des letzten Ladevorgangs ermitteln. Wenn ich den Wert bisher richtig beobachtet habe, scheint der Wert immer bei erneuter Verbindung mit dem Auto bei 0 zu beginnen. Oder man merkt sich den Wert „kwh_charged_total“ beim Anschluß des Autos und bildet immer eine aktuelle Differenz mit dem aktuellen Wert und dem gemerkten. Diese Differenz kann man dann mit den Readings „Lademenge“ bzw. „Lademenge_Min“ vergleichen und damit die Ladung beispielsweise stoppen, wenn die Werte erreicht wurden.

Den Wert im E-Auto Dummy für „evBatteryPlugin“ kann man auch durch den „car_state“ des GoE-Charger austauschen. Ist dieser Wert 3 oder 4, dann ist ein Auto angeschlossen.

Wenn man den SoC über Alexa steuern möchte – sofern in FHEM eingebunden – dann müsste das wahrscheinlich über einen Dummy-Schalter funktionieren, den man als Dimmer definiert. Getestet habe ich das nicht aber so ähnlich mache ich das mit einen Dummy-Thermostatschalter zu Einstellung der Heizung über Alexa. Mit einem Befehl „Alexa stelle Dummy_SoC auf 40%“ müsste dann der entsprechende Wert einstellbar sein. Vielleicht teste ich das mal mit den Ober-/Untergrenzen, so dass ich diese auch über Alexa steuern kann und berichte ggf. nochmal, ob es tatsächlich umsetzbar ist.

Ansonsten hoffe ich, dass ich euch ein paar Anregungen und Ideen vermitteln konnte, wie auch eure Ladesteuerung automatisieren könnt. Fragen und Anregungen könnt ihr gerne wieder über die Kommentare loswerden. Da ich beim Schreiben des Artikels tatsächlich noch ein paar Anpassungen im Code vorgenommen habe, hoffe, dass ich damit nicht wieder ein paar Fehler eingebaut habe ;-).

2 comments

  1. Schau dir mal evcc an. (Einfach mal googeln). Dann bist du vermutlich alle Skripte hier los,… 😉

  2. Danke für den Hinweis. Tatsächlich hatte ich mir EVCC vor ewiger Zeit schon einmal angesehen. Erst vor ein paar Tagen habe ich auch ein YouTube Video einer KOnferenz gesehen, wo der Entwickler EVCC vorgestellt hat. Viele der Dinge, die ich mit meinen Scripten umgesetzt habe, lässt sich mittlerweile mit EVCC lösen. Vielleicht schau ich es mir bei Gelegenheit nochmal an. Andererseits macht es auch Spaß, selbst bestimmte Dinge, entsprechend der eigenen Anforderungen zu realisieren. 😉

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Why ask?

x

Check Also

Erster Eindruck und Inbetriebnahme der Ecoflow Powerstation

Vor einiger Zeit hatte ich davon berichtet, dass ich nun doch auch einmal einen Speicher ...