FHEM Forum

FHEM - Energiemanagement und Energieerzeugung => Solaranlagen => Thema gestartet von: 300P am 26 Mai 2025, 15:38:55

Titel: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: 300P am 26 Mai 2025, 15:38:55
In diesem Thread soll es allein um die Weitergabe von Praxisbeispielen die im SF-Modul (76_SolarForecast.pm) entweder ohne jegliche Anpassung oder auch mit weiteren (externen) Anpassungen bei der Einrichtung der eigenen Konfiguration übernommen werden könnten.
==>> Alles unter Ausschluss jeglicher Gewähr auf die Funktion der jeweiligen Codefragmente !  O:-)

Bitte möglichst jeweils die SF-Version dazu schreiben unter der es eingesetzt wird/wurde.
Manchmal müssen (leider) bekanntlich Anpassungen an den Beispielen und Attributen bei neueren Versionen vorgenommen werden, da Funktionen/Attribute umbenannt, zusammengefaßt, gelöscht oder erweitert werden/wurden. ;)

Anfallende Probleme zu den Beispielen und deren Klärung sollten möglichst nicht hier, sondern im normalen SF-Modul-Faden behandelt werden.
Der Grund dazu ist allein das die hier im Thread zur Verfügung gestellten Beispiele nicht in den allgemeinen Diskussionen untergehen sollen.

Link zum  <--SF-Modul--> (https://forum.fhem.de/index.php?topic=137058.new#new) und dessen Diskussion

Link zum  <--FHEM-Wiki des SF-Modules--> (https://wiki.fhem.de/wiki/SolarForecast_-_Solare_Prognose_(PV_Erzeugung)_und_Verbrauchersteuerung)

Gruß
300P
Titel: Aw: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: 300P am 26 Mai 2025, 15:53:57
Hier das erste Beispiel
Nr          : 001
SF-Version  : 76_SolarForecast.pm:v1.52.5-s29997/2025-05-25
Bereich      : ctrlUserExitFn



Netzdienliches Laden innerhalb von Solarforecast mit SMA-Batterie-Wechselrichtern:

Für 1 BWR ist der Code etwas tiefer auch dabei

{
# BEGIN ######## Netzdienliches Laden ################
  my $dt    = timestringsFromOffset (time, 0);
  my $hour  = $dt->{hour};                                                # aktuelle Stunde in 24h format (00-23)
  my $timestart = 9;  # ab welcher Uhrzeit soll es sein                                      ===>> Wert evtl. anpassen
  my $timeend = 15;  # bis wieviel Uhr soll es sein                                          ===>> Wert evtl. anpassen

  if (int $hour >= $timestart && int $hour < $timeend ) {
          # Nur zwischen xy:00 bis max. xy:59 (!!!! $timeend -1 Stunde !!!!)  Uhr


    my $curpowerbatin01 = ReadingsNum ($name, 'Current_PowerBatIn_01', 0);
    my $curpowerbatin02 = ReadingsNum ($name, 'Current_PowerBatIn_02', 0);
    my $curpowerbatout01 = ReadingsNum ($name, 'Current_PowerBatOut_01', 0);
    my $curpowerbatout02 = ReadingsNum ($name, 'Current_PowerBatOut_02', 0);
    my $curbatcharge01 = CurrentVal ($name, 'Current_BatCharge_01', 0);        # SoC Bat01 aktuell
    my $curbatcharge02 = CurrentVal ($name, 'Current_BatCharge_02', 0);        # SoC Bat02 aktuell
    my $currentsurplus = (ReadingsNum ($name, 'Current_Surplus', 0) - 50);  # etwas abziehen zwecks weniger als Puffer
    my $batchargerequest01 = ReadingsNum ($name, 'Battery_ChargeRequest_01', 0); # Batterie01 muss geladen werden ?
    my $batchargerequest02 = ReadingsNum ($name, 'Battery_ChargeRequest_02', 0); # Batterie02 muss geladen werden ?
    my $gridfriendlychargebat01 = ReadingsNum ($name, 'Battery_ChargeRecommended_01', 0); # Batterie01 recommended ?
    my $gridfriendlychargebat02 = ReadingsNum ($name, 'Battery_ChargeRecommended_02', 0); # Batterie02 recommended ?
    my $curbatchargemin01= 30;  # erst ab Mindes-BAT01-SOC XY netzdienlich                                  ===>> Wert evtl. anpassen 0..30
    my $curbatchargemin02= 30;  # erst ab Mindes-BAT02-SOC XY netzdienlich                                  ===>> Wert evtl. anpassen 0..30
    my $bwr01 = "MB_SBS25";      # Name des 1ten BWR-Modbus-Device                                            ===>> Wert evtl. anpassen
    my $bwr02 = "MB_SBS25_2";    # Name des 2ten BWR-Modbus-Device                                            ===>> Wert evtl. anpassen


    if (int $gridfriendlychargebat01 eq 0 || $gridfriendlychargebat02 eq 0) {
            # Nur wenn die Batterieladung01 oder 02 SF-Systemseitig nicht empfohlen wird dann

        Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended Battery01 $gridfriendlychargebat01 - Battery02 $gridfriendlychargebat02 });
      my $curbatcharge01 = CurrentVal ($name, 'Current_BatCharge_01', 0);        # SoC Bat01 aktuell
      my $curbatcharge02 = CurrentVal ($name, 'Current_BatCharge_02', 0);        # SoC Bat02 aktuell

        if (int $gridfriendlychargebat01 eq 0 && $batchargerequest01 eq 0 && $curbatcharge01 > $curbatchargemin01) {
              # wenn bei Bat01 = 0 -beides- und SOC groß genug ->>dann unterbinden Laden Bat01
              Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended only Battery01 Reading = $gridfriendlychargebat01 });
              fhem("set $bwr01 Set_Leistung_W $currentsurplus"); #  Watt Netzleistung am Übergabemesspunkt (WR1)
              fhem("set $bwr01 Set_Aktiv 802");  # 802 - Steuerung aktiv (WR1)
              Log3 ($name, 3, qq{$name - userFn -> Batteryload $bwr01 => activ  - BatIn01 $curpowerbatin01  BatOut01 $curpowerbatout01  });
            }
      else {
            if (int $gridfriendlychargebat01 eq 1 || $batchargerequest01 eq 1 || (($curbatcharge01 - 3) <= $curbatchargemin01)) {
                  # wenn bei Bat01 = beides oder nur eins auf 1 steht ---dann bitte jetzt Steuerung  aus / kein Laden unterbinden Bat01
                  Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended =>> Battery01 Reading = $gridfriendlychargebat01 and BatChargerequest =>> Battery01 Reading = $batchargerequest01 });
                  fhem("set $bwr01 Set_Leistung_W 0"); #  Watt Netzleistung am Übergabemesspunkt (WR1)
                  fhem("set $bwr01 Set_Aktiv 803");  # 803 - Steuerung nicht aktiv (WR1)
                  Log3 ($name, 3, qq{$name - userFn -> Batteryload $bwr01 => not activ  - BatIn01 $curpowerbatin01  BatOut01 $curpowerbatout01  });
                }
            }

        if (int $gridfriendlychargebat02 eq 0  && $batchargerequest02 eq 0 && $curbatcharge02 > $curbatchargemin02) {
              # wenn bei Bat02 = 0 -beides- und SOC groß genug  --->>dann unterbinden Laden Bat02
              Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended only Battery02 Reading = $gridfriendlychargebat02 });
              fhem("set $bwr02 Set_Leistung_W $currentsurplus");  #  Watt Netzleistung am Übergabemesspunkt (WR2)
              fhem("set $bwr02 Set_Aktiv 802");  # 802 - Steuerung aktiv (WR2)
              Log3 ($name, 3, qq{$name - userFn -> Batteryload $bwr02 => not activ  - BatIn02 $curpowerbatin02  BatOut02 $curpowerbatout02 });
            }
      else {
            if (int $gridfriendlychargebat02 eq 1 || int $batchargerequest02 eq 1 || (($curbatcharge02 - 3) <= $curbatchargemin02)) {
                  # wenn bei Bat02 = beides oder nur eins auf 1 steht ---dann bitte jetzt Steuerung  aus / kein Laden unterbinden Bat02
                  Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended =>> Battery02 Reading = $gridfriendlychargebat02 and BatChargerequest =>> Battery02 Reading = $batchargerequest02 });
                  fhem("set $bwr02 Set_Leistung_W 0"); #  Watt Netzleistung am Übergabemesspunkt (WR2)
                  fhem("set $bwr02 Set_Aktiv 803");  # 803 - Steuerung nicht aktiv (WR2)
                  Log3 ($name, 3, qq{$name - userFn -> Batteryload $bwr02 => not activ  - BatIn02 $curpowerbatin02  BatOut02 $curpowerbatout02  });
                }
            }

      }
  }
# ENDE ######## Netzdienliches Laden ################

}

Hier nur für 1 BWR:

{
# BEGIN ######## Netzdienliches Laden ################
  my $dt    = timestringsFromOffset (time, 0);
  my $hour  = $dt->{hour};                                                # aktuelle Stunde in 24h format (00-23)
  my $timestart = 9;  # ab welcher Uhrzeit soll es sein                                      ===>> Wert evtl. anpassen
  my $timeend = 15;  # bis wieviel Uhr soll es sein                                          ===>> Wert evtl. anpassen

  if (int $hour >= $timestart && int $hour < $timeend ) {
          # Nur zwischen xy:00 bis max. xy:59 (!!!! $timeend -1 Stunde !!!!)  Uhr


    my $curpowerbatin01 = ReadingsNum ($name, 'Current_PowerBatIn_01', 0);
    my $curpowerbatout01 = ReadingsNum ($name, 'Current_PowerBatOut_01', 0);
    my $curbatcharge01 = CurrentVal ($name, 'Current_BatCharge_01', 0);        # SoC Bat01 aktuell
    my $currentsurplus = (ReadingsNum ($name, 'Current_Surplus', 0) - 50);  # etwas abziehen zwecks weniger als Puffer
    my $batchargerequest01 = ReadingsNum ($name, 'Battery_ChargeRequest_01', 0); # Batterie01 muss geladen werden ?
    my $gridfriendlychargebat01 = ReadingsNum ($name, 'Battery_ChargeRecommended_01', 0); # Batterie01 recommended ?
    my $curbatchargemin01= 30;  # erst ab Mindes-BAT01-SOC XY netzdienlich                                  ===>> Wert evtl. anpassen 0..30
    my $bwr01 = "MB_SBS25";      # Name des 1ten BWR-Modbus-Device                                            ===>> Wert evtl. anpassen


    if (int $gridfriendlychargebat01 eq 0) {
            # Nur wenn die Batterieladung01 SF-Systemseitig nicht empfohlen wird dann

        Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended Battery01 $gridfriendlychargebat01 });
      my $curbatcharge01 = CurrentVal ($name, 'Current_BatCharge_01', 0);        # SoC Bat01 aktuell

        if (int $gridfriendlychargebat01 eq 0 && $batchargerequest01 eq 0 && $curbatcharge01 > $curbatchargemin01) {
              # wenn bei Bat01 = 0 -beides- und SOC groß genug ->>dann unterbinden Laden Bat01
              Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended only Battery01 Reading = $gridfriendlychargebat01 });
              fhem("set $bwr01 Set_Leistung_W $currentsurplus"); #  Watt Netzleistung am Übergabemesspunkt (WR1)
              fhem("set $bwr01 Set_Aktiv 802");  # 802 - Steuerung aktiv (WR1)
              Log3 ($name, 3, qq{$name - userFn -> Batteryload $bwr01 => activ  - BatIn01 $curpowerbatin01  BatOut01 $curpowerbatout01  });
            }
      else {
            if (int $gridfriendlychargebat01 eq 1 || $batchargerequest01 eq 1 || (($curbatcharge01 - 3) <= $curbatchargemin01)) {
                  # wenn bei Bat01 = beides oder nur eins auf 1 steht ---dann bitte jetzt Steuerung  aus / kein Laden unterbinden Bat01
                  Log3 ($name, 5, qq{$name - userFn -> Gridfriendlyloadrecommended =>> Battery01 Reading = $gridfriendlychargebat01 and BatChargerequest =>> Battery01 Reading = $batchargerequest01 });
                  fhem("set $bwr01 Set_Leistung_W 0"); #  Watt Netzleistung am Übergabemesspunkt (WR1)
                  fhem("set $bwr01 Set_Aktiv 803");  # 803 - Steuerung nicht aktiv (WR1)
                  Log3 ($name, 3, qq{$name - userFn -> Batteryload $bwr01 => not activ  - BatIn01 $curpowerbatin01  BatOut01 $curpowerbatout01  });
                }
            }
        }
    }
# ENDE ######## Netzdienliches Laden ################

}

ACHTUNG:
Voraussetzung ist das die Modbus-Device bereits existieren und sauber dazu passend eingerichtet sind !

obj-h40149-expr $val
obj-h40149-len 2
obj-h40149-name SBS25_Set_Leistung_W
obj-h40149-poll once
obj-h40149-reading Set_Leistung_W
obj-h40149-set 1
obj-h40149-unpack I>
obj-h40151-expr $val
obj-h40151-name SBS25_Set_Aktiv
obj-h40151-poll once
obj-h40151-reading Set_Aktiv
obj-h40151-set 1

Gruß
300P

Titel: Aw: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: 300P am 26 Mai 2025, 16:39:55
Beispiel Nr : 002
SF-Version  : 76_SolarForecast.pm:v1.52.5-s29997/2025-05-25
Bereich     : ConsumerXX / ctrlUserExitFn

Netzdienliche Brauchwassererwärmung (WW-Erhitzung) am Tag per consumer06
und zusätzliche Nutzung der "überschüssigen" Rest-Batterieladung in der Nacht, jedoch unter bestimmten Bedingungen.

{
# BEGIN ######## WP-Heater in der Nacht mit Restladung der Batterien ################
  my $dt    = timestringsFromOffset (time, 0);
  my $hour  = $dt->{hour};                                # aktuelle Stunde in 24h format (00-23)
  my $timestart = 1;  # ab welcher Uhrzeit soll es sein.  ===>> Wert evtl. anpassen
  my $timeend = 8;    # bis wieviel Uhr soll es sein.     ===>> Wert evtl. anpassen

  if (int $hour >= $timestart && int $hour < $timeend ) {
          # Nur zwischen xy:00 bis max. xy:59 (!!!! $timeend -1 Stunde !!!!)  Uhr

      my $minsocbat = 20;   
      # nur bis mindesten XY SoC aller Batterien. ===>> Wert evtl. anpassen

      # Heater-Name bzw. consumerXX anpassen - hier ist consumer06 der "Heater"
      my $heater  = (split " ", AttrVal ($name, 'consumer06', ''))[0];   

      my $soctotal = CurrentVal ($name, 'batsoctotal', 0);         # SoC über alle Bat als Durchschnitt
      my $pvtot =  ReadingsNum ($name, 'RestOfDayPVforecast', 0);  # Restliche PV Prognose total heute in Wh
      my $caps  = CurrentVal ($name, 'batcapsum', 0);          # Summe installierte Bat Wh
      my $need  = $caps - ($soctotal/100 * $caps);             # benötigte Ladeenergie Bat bis 100% Ladung


      Log3 ($name, 5, qq{$name - userFn -> WWHeater Reading ist = $heater - aktuelle Stunde = $hour   aus $dt erzeugt });
      Log3 ($name, 5, qq{$name - userFn -> StartZeit von = $timestart bis EndeZeit = $timeend und mindestens ein SoC von $minsocbat % muss in den vorhandenen Batterien gegeben sein });
      Log3 ($name, 5, qq{$name - userFn -> Der PV-Tages-Restertrag betraegt $pvtot Wh ???mehr??? als die offene und noch zu ladende Batteriekapazitaet von $need Wh});

     if ($soctotal >= ($minsocbat - 2) && $pvtot > $need) { 
         # Zwischen xy:00 bis max. xy:59 (!!!! $timeend -1 Stunde !!!!)  Uhr
         # und % SoC-Vorgabe erfüllt
         # und der PV-Tages-Rest-Ertrag > 100% Batteriekapazitaet
         # send "on"-Kommando an Heater Device if(ne "on");
         if (ReadingsVal($heater,"state","") ne "on") {
              fhem ("set $heater on");
              Log3 ($name, 3, qq{$name - userFn -> $name Consumer $heater in $name set to --on--});
             }
         }
      else {
         if (($soctotal < $minsocbat) || ($pvtot <= $need)) { 
             # Wenn die (SoC-Vorgabe unterschritten !!!!! oder !!!! derTages-Rest-Ertrag < 100% Batteriekapazitaet)
             # danach sollte der Heater-ConsumerXY wieder nach "seinen" Regeln steuern...
             # send "off"-Kommando an Heater Device if(ne "off");
             if (ReadingsVal($heater,"state","") ne "off") {
                fhem ("set $heater off");
                Log3 ($name, 3, qq{$name - userFn -> $name Consumer $heater in $name set to --off--});
                }
             }
         }
    }
# ENDE ######## WP-Heater ################
}

Hier noch der consumer06, der im SF-Modul dazu bei mir existiert.  ;)

attr Forecast consumer06 FBDECT_fbahahttp_FF_FF_FF_FF_FF \
type=heater \
power=2170 \
mode=can \
icon=sani_buffer_electric_heater_side@orange \
mintime=SunPath \
on=on \
off=off \
asynchron=1\
notbefore=08:00 \
notafter=19:00 \
locktime=300:300 \
pcurr=power:W \
etotal=energy:Wh\
surpmeth=10 \
interruptable=1\
swstate=state:.*on.*:.*off.*


Gruß
300P
Titel: Aw: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: 300P am 26 Mai 2025, 16:58:43
Beispiel Nr : 003
SF-Version  : 76_SolarForecast.pm:v1.52.5-s29997/2025-05-25
Bereich    : graphicHeaderOwnspec

Ausführliche Anzeige im Header mit Istwerten / Vorhersagewerten / Verbrauchswerten / Batteriewerten / Parameterabänderungsmöglichkeit

attr Forecast graphicHeaderOwnspec #aktuelle&nbsp;;Werte\
Netz&nbsp;;aktuell:Current_GridConsumption\
Einspeisung&nbsp;;aktuell:Current_GridFeedIn\
Netz&nbsp;;Heute&nbsp;;bis&nbsp;;jetzt:special_todayGridConsumption\
Einspeisung&nbsp;;Heute&nbsp;;bis&nbsp;;jetzt:special_todayGridFeedIn\
#Verbrauch\
Heute&nbsp;;bis&nbsp;;jetzt:special_todayConsumption\
Heute&nbsp;;erwartet:special_todayConsumptionForecastDay\
Heute&nbsp;;bis&nbsp;;Sonnenuntergang:special_todayConForecastTillSunset\
Heute&nbsp;;bis&nbsp;;Sonnenaufgang&nbsp;;:special_conForecastTillNextSunrise\
#PV\
Heute&nbsp;;bis&nbsp;;jetzt:Today_PVreal\
Heute&nbsp;;erwartet:Today_PVforecast\
Morgen&nbsp;;erwartet:Tomorrow_PVforecast\
Übermorgen&nbsp;;erwartet:special_dayAfterTomorrowPVforecast\
#Batterie&nbsp;;Gesamt\
Ladung&nbsp;;aktuell:special_BatPowerIn_Sum\
Entladung&nbsp;;aktuell:special_BatPowerOut_Sum\
Ladung&nbsp;;heute:special_todayBatInSum\
Entladung&nbsp;;heute:special_todayBatOutSum\
#Batterie01&nbsp;;|&nbsp;;02\
Bat01-Ladeanforderung:Battery_ChargeRequest_01\
Bat01-Ladung&nbsp;;empfohlen:Battery_ChargeRecommended_01\
Bat02-Ladeanforderung:Battery_ChargeRequest_02\
Bat02-Ladung&nbsp;;empfohlen:Battery_ChargeRecommended_02\
#\
 :\
MaxPVForecastTime:Today_MaxPVforecastTime\
 :\
 :\
#\
Bat01-Ladung&nbsp;;heute:special_todayBatIn_01\
Bat01-Entladung&nbsp;;heute:special_todayBatOut_01\
Bat02-Ladung&nbsp;;heute:special_todayBatIn_02\
Bat02-Entladung&nbsp;;heute:special_todayBatOut_02\
#\
Bat01-Ladeleistung&nbsp;;aktuell:Current_PowerBatIn_01\
Bat01-Entladeleistung&nbsp;;aktuell:Current_PowerBatOut_01\
Bat02-Ladeleistung&nbsp;;aktuell:Current_PowerBatIn_02\
Bat02-Entladeleistung&nbsp;;aktuell:Current_PowerBatOut_02\
#\
Bat01-Ladestatus&nbsp;;aktuell:Current_BatCharge_01\
Bat01-Restapazität&nbsp;;aktuell:Current_CapBat_01\
Bat02-Ladestatus&nbsp;;aktuell:Current_BatCharge_02\
Bat02-Restapazität&nbsp;;aktuell:Current_CapBat_02\
#Settings\
Autokorrektur:pvCorrectionFactor_Auto\
Wetter:graphicShowWeather\
History:graphicHistoryHour\
ShowNight:graphicShowNight\
#\
Beam1:graphicBeam1Content\
Beam2:graphicBeam2Content\
:\
:\
#\
Beam3:graphicBeam3Content\
Beam4:graphicBeam4Content\
:\
:\
#\
Beam5:graphicBeam5Content\
Beam6:graphicBeam6Content\
Debug:ctrlDebug\





Titel: Aw: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: 300P am 26 Mai 2025, 18:46:30
Beispiel Nr : 004
SF-Version : 76_SolarForecast.pm:v1.52.5-s29997/2025-05-25
Bereich      : setupBatteryDevXX / setupInverterDevXX

Beispielsetup
setupBatteryDevXY  : SMA-BWR SBS2.5 und SMA-BWR SBS3.7

attr Forecast setupBatteryDev01 SBS37 pin=-pout:kW pout=total_pac:kW pinmax=3680 poutmax=3680 intotal=bat_loadtotal:kWh outtotal=bat_unloadtotal:kWh charge=chargestatus cap=bat_residual_cap:Wh show=3:top icon=@yellow:@green:@red:@white asynchron=0
attr Forecast setupBatteryDev02 SBS25_2 pin=-pout:kW pout=total_pac:kW pinmax=2500 poutmax=2500 intotal=bat_loadtotal:kWh outtotal=bat_unloadtotal:kWh charge=chargestatus cap=bat_residual_cap:Wh show=3:top icon=@yellow:@green:@red:@blue asynchron=0


In den BWR gibt es je ein Userreading
attr SBSXY userReadings bat_residual_cap {ReadingsNum($name,"bat_rated_capacity",0)*ReadingsNum($name,"bat_capacity",0)*10}


Beispielsetup
setupInverterDevXY : SMA-WR SB2.5 (1 String) --- SMA-WR SB3.0 (2 String) und SMA-WR SB4.0 (2 String)
(die angegebenen Strings müssen aber real im SF-Modul angelegt sein)

attr Forecast setupInverterDev01 SB25 etotal=etotal:kWh asynchron=0 pvIn=string_sum_pdc:kW pvOut=total_pac:kW limit=70 strings=GarageSE capacity=2500
attr Forecast setupInverterDev02 SB30 etotal=etotal:kWh asynchron=0 pvIn=string_sum_pdc:kW pvOut=total_pac:kW limit=70 strings=GarageNW,HausNW capacity=3000
attr Forecast setupInverterDev03 SB40 etotal=etotal:kWh asynchron=0 pvIn=string_sum_pdc:kW pvOut=total_pac:kW limit=70 strings=HausSE1,HausSE2,HausSW capacity=4000
Titel: Aw: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: 300P am 24 August 2026, 08:38:49
Beispiel Nr : 005
SF-Version : 76_SolarForecast.pm:v2.10.1
Bereich      : Batteriesteuerung

Beispiel mit DOIF-Nutzung zur einfachen Batteriesteuerung unter SF-Readingnutzung "Battery_ChargeUnrestricted_XX"
(SMA-BWR SBS2.5 und SMA-BWR SBS3.7 funktionieren damit)
Genau hinsehen wie die Timeouts bei der externen Steuerung eingestellt sind.
Bei mir sind es extra 30 Minuten eingestellt. Deshalb reichen die eingetragenen 300 Sekunden bei "repeatcmd" 300:0:0 - weniger Last fürs System.

Dies übernimmt die Steuerung der Batterieladung mit 0 Watt für die Batterie:
set MB_SBS25 Set_Leistung_W 0, set MB_SBS25 Set_Aktiv 802
Dies gibt die Steuerung zurück an das SMA-BMS :
set MB_SBS25 Set_Aktiv 803
defmod di_SF_BatteryControl_01 DOIF ([08:00-15:00] and [Forecast:Today_PVforecast:d, 0] > [Forecast:Today_CONforecast:d, 0] and [Forecast:Battery_ChargeUnrestricted_01:d, 1] == 0 and [Forecast:Battery_ChargeUnrestricted_01:sec] < 200) \
  (set MB_SBS25 Set_Leistung_W 0, set MB_SBS25 Set_Aktiv 802, {Log 2, "di_SF_BatteryControl_01 - BatCtrl_01: MB_SBS25   Set_Leistung_W 0 ->> Lade die Batterie nicht wegen Battery_ChargeUnrestricted_01 = 0"}) \
DOELSEIF \
   ([08:00-15:00]) (set MB_SBS25 Set_Aktiv 803, {Log 2, "di_SF_BatteryControl_01 - BatCtrl_01:                    Automatik - Eigenverbrauchsregelung der Batterie ist wieder an (803)"})\
DOELSE\
  (set MB_SBS25 Set_Aktiv 803, {Log 2, "di_SF_BatteryControl_01 - BatCtrl_01: Feierabend/Nacht - Eigenverbrauchsregelung erzwungen (803)"})
attr di_SF_BatteryControl_01 DbLogExclude .*
attr di_SF_BatteryControl_01 comment Steuerung erfolgt aufgrund \
\
1. Zeitraum \
    Von 08.00 bis ??15:00?? Uhr (oder wie auch immer) wird geregelt\
\
\
2. SF-Reading "Today_PVforecast" > "Today_CONforecast"\
    \
    Nur wenn am gesamten Tag der Ertrag > als der erwartete Verbrauch ist greift diese DOIF-Steuerung ein  \
\
\
3. SF-Reading "Battery_ChargeUnrestricted_XX" \
\
    A  =>> 0 ->> Umschaltung auf Wert 802 und Vorgabe 0 Watt  (alle 300 Sekunden -wegen repeatcmd=300:0:0 !!)\
    Ereignismeldung im SMA-BWR 2.5 (nicht mehr bei dem SMA \
    "10421    Eigenverbrauchsregelung wurde gestoppt"  (nur bei SBS 2.5 - bei SBS3.7 nicht mehr)\
\
    ->- Es erfolgt ab kurze Zeit später (10-30 Sekunden) gar keine Batterieladung mehr (0 Watt)\
    Ladung fährt auf den vorgegeben Wert "0 Watt"  \
\
    B  =>> 1 ->> Umschaltung auf Wert 803 (1X -wegen repeatcmd=300:0:0 !!)\
        Ereignismeldung im SMA-BWR \
    "10420    Eigenverbrauchsregelung wurde gestartet"  (bei SBS2.5 - bei SBS3.7 nicht mehr)\
        ->- Es erfolgt dann kurze Zeit später (10-30 Sekunden) die normal eigenständige SMA-Batteriesteurung\
        Laden/Entladen erfolgt so wie SMA es "will"\
\
\
4. Außerhalb der o.g. Zeiten oder wenn keine der vorhergehenden Abfragen zutreffend sind  \
\
    A  Es erfolgt (1X -wegen repeatcmd=300:0:0 !!) das Umschaltung auf Wert 803 - egal welcher Wert in SF besteht\
        Ereignismeldung im SMA-BWR \
    "10420    Eigenverbrauchsregelung wurde gestartet"  (bei SBS2.5 - bei SBS3.7 nicht)\
        ->- Es erfolgt dann kurze Zeit später (10-30 Sekunden) die normal eigenständige SMA-Batteriesteurung\
        Laden/Entladen erfolgt so wie SMA es "will"\
\

attr di_SF_BatteryControl_01 group SF
attr di_SF_BatteryControl_01 repeatcmd 300:0:0
attr di_SF_BatteryControl_01 room 020_PV,DoIf_Labor
attr di_SF_BatteryControl_01 stateFormat BatteryChargeOptTargetPower_01 : [Forecast:Battery_ChargeOptTargetPower_01] - BatteryChargeUnrestricted_01 : [Forecast:Battery_ChargeUnrestricted_01] - BatteryCurrentBatCharge_01 : [Forecast:Current_BatCharge_01]  Überschuss : [Forecast:Current_Surplus]

setstate di_SF_BatteryControl_01 BatteryChargeOptTargetPower_01 : 3600 W - BatteryChargeUnrestricted_01 : 0 - BatteryCurrentBatCharge_01 : 67 %   Überschuss : 63 W
setstate di_SF_BatteryControl_01 2026-08-24 08:24:00 Device Forecast
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 cmd 0
setstate di_SF_BatteryControl_01 2026-08-24 08:06:19 cmd_event Forecast
setstate di_SF_BatteryControl_01 2026-08-24 08:06:19 cmd_nr 1
setstate di_SF_BatteryControl_01 2026-08-24 08:21:28 e_Forecast_Battery_ChargeUnrestricted_01 0
setstate di_SF_BatteryControl_01 2026-08-24 08:21:13 e_Forecast_Today_CONforecast 26724 Wh
setstate di_SF_BatteryControl_01 2026-08-24 08:24:00 e_Forecast_Today_PVforecast 54469 Wh
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 mode enabled
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 state initialized
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 timer_01_c01 25.08.2026 08:00:00
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 timer_02_c01 24.08.2026 15:00:00
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 timer_03_c02 25.08.2026 08:00:00
setstate di_SF_BatteryControl_01 2026-08-24 08:08:37 timer_04_c02 24.08.2026 15:00:00
setstate di_SF_BatteryControl_01 2026-08-24 08:06:19 wait_timer 24.08.2026 08:11:19 cmd_1 Forecast
Titel: Aw: 76_SolarForecast - Beispiele für ctrlUserExitFn / consumerXX / setupXXYY.. etc.
Beitrag von: alkazaa am 26 August 2026, 15:36:04
Beispiel Nr : 006
SF-Version : 76_SolarForecast.pm:v2.10.1
Bereich      : Batteriesteuerung

Ich stelle hier auch mal meine DOIF-basierte Steuerung vor. Ich bin nach wie vor am optimieren, bzw. beobachte das Verhalten, bin aber parallel dabei, alles in user-functions (userFN_...) umzusetzen. Vermutlich mache ich einiges zu umständlich, aber sei's drum.

Das PV-System ist ein Fronius Symo GEN24 8 kW (für 9.79 kWp Module) mit BYD-HVS 10.2 und eine (6 Jahre ältere) Vaillant-Anlage mit 2.9 kWp (als sie neu war). Ein Fronius Smart Meter am Netzeinspeisepunkt ist vorhanden. Die Batterie wird über den Fronius WR kontrolliert, liefert aber auch über direkten modbus Zugang weitere Infos, z.B. die Spannungen einzelner Zellen.

IN FHEM gibt es HTTPMOD devices 'Vaillant_WR', 'Fronius_WR' ( für http://192.168.188.174/solar_api/v1/GetPowerFlowRealtimeData.fcgi) und 'BYD_battery_API' (für http://192.168.188.174/solar_api/v1/GetStorageRealtimeData.cgi) sowie ein ModbusAttr device 'Fronius_BYD' für die Batterie-Ladesteuerung über die Fronius-BYD modbus Verbindung. Die Daten einzelner Zellspannungen können von einem BYDBox device geliefert werden, oder per MQTT von Olli's tool, das als Service auf dem RasPi 3B+ läuft. Ich teste noch, was von beiden stabiler ist, nutze aber z.Zt. den Olli-MQTT Zugang.

Die DOIFs sind:
A) di_BYD_charge_control,
B) di_PV_throttle_detect,
C) di_PV_peak_reserve_tomorrow

Die C) prüft am Nachmittag, ob der target-SoC diese Tages begrenzt werden soll, damit am nächsten Tag genug Platz in der Batterie ist, um die nicht einspeisbare Energie aufzunehmen.

A) ist die eigentliche Ladesteuerung, die auf SF-Signale wie z.B. charge_unrestricted reagiert. In A) ist auch (optional deaktivierbar) eine Ladelimitierung integriert, die die Ladeleistung ab max_cell_voltage 3.375 V nach und nach begrenzt und ab 3.475 V abbricht. Die Optimierung dieser Werte ist mein Haupt-Spielfeld z.Zt.

B) hat zwei Funktionsblöcke, manage_PV_throttling und get_required_bat_cap. Der Block get_required_bat_cap prüft früh morgens (4 und 6 Uhr), ob die PV-Vorhersage für heute sich über Nacht so geändert hat, dass noch mehr 'Platz' in der Batterie benötigt wird, als am Nachmittag vorher geschätzt. Falls ja, wird ins Netz entladen.
Block manage_PV_throttling kümmert sich um das peak-Shaving. Die Leistung über dem curtailing Limit (7904 W bei mir) wird berechnet, und genau diese Leistung geht dann in die Batterie (oder die von SF empfohlene Leistung, falls dies höher ist und charge_unrestricted=true). Da das Abregeln der Anlage bei mir nur über den MPPT1 geht, wird die verfügbare PV-Leistung als 2*MPPT2 + p_Vaillant genommen.

OK, hier nun die DOIFs:
di_BYD_charge_control
PV_ChargeControl
{
my $charge_unrestricted    = [PV_Vorhersage_KI:Battery_ChargeUnrestricted_01];
my $charge_requested       = [PV_Vorhersage_KI:Battery_ChargeRequest_01];
#my $BatteryMaxVolt         = [BYD_Logger:MaxCellVolt];
my $BatteryMaxVolt         = 0.001*[BYD_battery_cells:Battery_1_MaxmVolt];
my $charge_abort           = [PV_Vorhersage_KI:Battery_ChargeAbort_01];
my $current_charging_power = abs(ReadingsNum("Fronius_BYD","BatConfigMaxChargeWatt",0));
my $SOC                    = [Fronius_WR:battery_SOC];
[PV_Vorhersage_KI:Battery_ChargeOptTargetPower_01] =~ /(.*)\sW$/;
my $opt_charging_power     = $1;
#AttrVal("PV_Vorhersage_KI","setupBatteryDev01","pinmax=3000") =~ /pinmax=(\d*)/;
#my $max_charging_power = $1;
my $throttle_state         = ReadingsVal("di_PV_throttle_detect","state","");
my $factor = 1.0;
if ( (($charge_unrestricted eq "1") or ($charge_requested eq "1")) and ($throttle_state ne "throttling") and ($charge_abort ne "1") ) {
if (AttrVal("di_BYD_charge_control","reduction_strategy","off") eq "on") {
                my $Uoff = (($SOC < 20.0) or  ($SOC > 90.0)) ? 0.0 :  0.006;
if( $BatteryMaxVolt > 3.375 + $Uoff ) {
$factor = 1.0 - ($BatteryMaxVolt - 3.375 - $Uoff) / 0.100; #lowerTermVoltage = 3.375
$factor = ($factor < 0) ? 0 : $factor;
$opt_charging_power *= $factor;  # above 3.375, opt_c_p is reduced by factor
}
$opt_charging_power = 500 if ($opt_charging_power < 500);
};
if (($current_charging_power == 0) and ($opt_charging_power > 0)) {
fhem_set("Pushover1 msg title=PV_ChargeControl initiated charging at $opt_charging_power W sound='none'")
};
fhem_set("Fronius_BYD BatConfigMaxChargeWatt $opt_charging_power");
#main::Debug("\$opt_charging_power: $opt_charging_power");
set_State ("charging");
}
else {
  if ( ($current_charging_power > 100) and ($throttle_state ne "throttling") ) {
fhem_set("Pushover1 msg title=PV_ChargeControl set charging to 0 W ($throttle_state) sound='none'");
fhem_set("Fronius_BYD BatConfigMaxChargeWatt 0");
}
set_State ("idle");
};

fhem("setreading di_BYD_charge_control factor $factor");
}

di_PV_throttle_detect
manage_PV_throttling
{
[Fronius_WR:power_grid];                                        # change of power_grid
my $p_Vaillant = [Vaillant_WR:AC_Power];                        # change of Vaillant PV power
my $BatteryMaxVolt = [BYD_Logger:MaxCellVolt];                    # change of battery MaxCellVolt
my $unrestricted = [PV_Vorhersage_KI:Battery_ChargeUnrestricted_01];
if  ( ($p_Vaillant < 1630) or ( $BatteryMaxVolt > 3.475) ) {     # state will be "no_throttling" if battery max_cell_voltage too high or PV too low
    if (get_State() eq "throttling") {   
        fhem_set("di_BYD_charge_control enable");                # re-enable regular charge control
        set_State("no_throttling");                                # ste state=no_throttling
        fhem_set("di_BYD_charge_control PV_ChargeControl");        # call regular charge control once
        }
    set_State("no_throttling");                                    # just in case ...
    }
else {
    AttrVal("PV_Vorhersage_KI","plantControl","") =~ /feedinPowerLimit=(\d*)/;
    my $limit = $1;                                                # feedin limit
    #
    # my $surplus = 4.85 * $p_Vaillant;                         # varient based on upscaling of Vaillant power
        # variant based on MPPT imbalance: (MPPT1 is down-regulated in my PV-system)
    my $surplus = $p_Vaillant + 2 * ReadingsNum("Fronius_BYD","DCPowerMPPT2",0);
    $surplus -= $limit + 1000.0 * [Fronius_WR:power_con];        # momentary power surplus
    #
    my $charging = $surplus < 4000 ? $surplus : 4000;            # limit charging power to 4000 W
    $charging = sprintf("%.1f",$charging >= 0 ? $charging : 0);    # round charging power to non-negative number with one digit precision
    if ($unrestricted) { # unrestricted charging is active
        ReadingsVal("PV_Vorhersage_KI","Battery_ChargeOptTargetPower_01","0 W") =~ /(.*)\sW$/;
        my $recommended_charging_power = $1;
        $charging = max($recommended_charging_power,$charging); # use recommended charging power if it is higher
        }
    if ($charging > 0) {   
        if (get_State() ne "throttling") { # switch to state "throttling"
            fhem_set("Tecalor_modbus SGR_aktiv 1");                                # first activate my only switchable consumer (heatpump for hot water reservoir)
            fhem("defmod SGR_off at +02:00 set Tecalor_modbus SGR_aktiv 0");    # take care that SGR mode is switched 2 hrs later
            fhem_set("Pushover1 msg title=PV_ChargeControl set throttle-charging to $charging W sound='none'"); # send push message
            fhem_set("di_BYD_charge_control disable");                            # disable the regular charge-control
            set_State ("throttling");
            };
        fhem_set("Fronius_BYD BatConfigMaxChargeWatt ".$charging);                # set new charging power
        }
    else {                                                         # charging-power <= 0: this can only happen if power_con has suddenly increased
        fhem_set("di_BYD_charge_control enable");                # re-enable regular charge control
        set_State("no_throttling");                                # set state=no_throttling
        fhem_set("di_BYD_charge_control PV_ChargeControl");        # call regular charge control once           
        }
    }
}

get_required_bat_cap
{
[04:01] or [06:01];        # in case that PV-forecast for today has changed over night, dump an appropriate amouint of
                        # battery capacity to the grid
AttrVal("PV_Vorhersage_KI","plantControl","") =~ /feedinPowerLimit=(\d*)/;
my $limit = $1;
my $surplus = 0; my $nhr; my $bat_required_energy = 0; my $confc; my $pvfc;
my $starthour = 1; my $endhour = 24;
for my $i ($starthour..$endhour) { # calculate expected surplus fo the next 24 hours
    $nhr = 'NextHour'.sprintf("%02d",$i);
    $pvfc = FHEM::SolarForecast::NexthoursVal('PV_Vorhersage_KI', $nhr, 'pvfc',0);
    $confc = FHEM::SolarForecast::NexthoursVal('PV_Vorhersage_KI', $nhr, 'confc',0);
    $surplus = $pvfc - $confc - $limit;
    $bat_required_energy += $surplus if ($surplus > 0);
    #main::Debug("$nhr | $pvfc | $confc | $surplus");
    };
 # inform PV_Vorhersage_KI about Battery_energy_to_discharge:
my $energy_to_discharge = int(List::Util::max( $bat_required_energy - (10200/100) * (ReadingsVal("PV_Vorhersage_KI","maxSoC_for_PeakShaving",100) - ReadingsVal("Fronius_WR","battery_SOC",0)), 0));
        # "maxSoC_for_PeakShaving" has been calculated the day before;
fhem("setreading PV_Vorhersage_KI Battery_energy_to_discharge $energy_to_discharge");
# dump the required energy to the grid (if it's more than 250 Wh, otherwise ignore):
if ( $energy_to_discharge > 250 ) {
    my $discharge_hours = $energy_to_discharge / 2500;
    $discharge_hours = sprintf("%02d:%02d:%02d", int($discharge_hours), int(60*$discharge_hours % 60), int($discharge_hours*3600 % 60));
        # define an 'at' to end the forced ´discharge and to re-enable regular-charge control:
    fhem("defmod stop_discharging at +$discharge_hours set Fronius_BYD BatConfigMaxChargeWatt 0;; set di_BYD_charge_control enable");
        # deactivate regular charge-control and start dumping at 2500 W:
    fhem_set("di_BYD_charge_control disable");
    fhem_set("Fronius_BYD BatConfigMaxChargeWatt -2500");
    }
}

di_PV_peak_reserve_tomorrow
Check_SoC_reserve_required_tomorrow
{
# Calculate at 15:01 and 17:01 and 18:01 (or on manual demand) the battery capacity (in %) to accomodate
# the peak shaving energy of the next day. E.g. a value of 10 means that 10% of the battery is required
# to store the PV energy above the curtailing limit.
# The result is stored as reading "maxSoC_for_PeakShaving" in device PV_Vorhersage_KI
# and in attrKeyVal 'loadTarget'.
# The attribute 'confactor' (default: 1) can be used to consider only part of the consumed energy.
#
[15:01] or [17:01] or [18:01];
return if ($hour > 19); # no manual calculation after 19:00
AttrVal("PV_Vorhersage_KI","plantControl","") =~ /feedinPowerLimit=(\d*)/;
my $limit = $1;
my $confactor = AttrVal("di_PV_peak_reserve","confactor",1);
# ausrechnen, ob das Ladeziel reduziert werden sollte, bzw. ob sogar noch aktiv entladen werden sollte
# calculate energy which can't be fed to grid during next 24 hours
# calculate energy discharged from battery overnight
my $starthour = 19 - $hour; # consider data from 19:00 today to 18:59 next day
my $endhour = $starthour + 23; my $nhr; my $zeitstring;
my $peakenergy; my $hourlydischarge; my $pvfc; my $confc; my $totalsocrequired = 0;
for my $i ($starthour..$endhour) {
    $zeitstring = sprintf("%02d:00",($i + $hour) % 24)."-".sprintf("%02d:00",($i + 1 + $hour) % 24);
    $nhr = 'NextHour'.sprintf("%02d",$i);
    $pvfc  = FHEM::SolarForecast::NexthoursVal('PV_Vorhersage_KI', $nhr, 'pvfc',0); # pv forecast
    $confc = FHEM::SolarForecast::NexthoursVal('PV_Vorhersage_KI', $nhr, 'confc',0) * $confactor; # consumption forecast
    # sum of charge energy needed for next 24 hours:
    $peakenergy = List::Util::max(0,$pvfc - $confc - $limit);
    $totalsocrequired += $peakenergy / 102.0; # energy converted to SoC
    my $socfc = FHEM::SolarForecast::NexthoursVal('PV_Vorhersage_KI', $nhr, 'soc01',0);
    #main::Debug($zeitstring." SoC-available:".sprintf("%0.1f",100-$socfc).", SoC-required: ".sprintf("%0.1f",$totalsocrequired));
    };
fhem("setreading di_PV_peak_reserve_tomorrow SoC_required ".sprintf("%.1f",$totalsocrequired));
my $macSoC_today = 100 - $totalsocrequired;
fhem("setreading PV_Vorhersage_KI maxSoC_for_PeakShaving ".sprintf("%.1f",$macSoC_today));
fhem_set("PV_Vorhersage_KI attrKeyVal ctrlBatSocManagement01 loadTarget=".sprintf("%.0f",$macSoC_today));           
}

So, das war's fürs Erste. Ich werde überarbeiten, sobald ich ändere oder nachbessere.
Ansonsten: gern her mit Fragen oder Kritik.

-Franz

EDIT 1:

hier ein Beispiel fürs peak-Shaving heute:
peak-shaving.png
Um etwa 12:40 setzt die Eisnspeisebegrenzumg ein: die Warmwasserbereitung wird aktiviert und endet automatisch gegen 13:25 (WW-Temperatur erreicht). Dann übernimmt die Batterie. Zum Zeitpunkt des screenshots war immer noch Einspeisebegrenzung,

EDIT 2: Update des DOIF "di_BYD_charge_control"