Optimisation usage of power excess of photovoltaic system

Hi guys,

i just wanted to share a script that combines my photovoltaik system (power inverter: sma) with the heatpump (waterkotte eco air a1 touch) to maximize the usage of the power excess. It is based on an already posted script here in the community (but i wasn’t able to find the original post again. (this is only for demostration purpose, use it on your own risk)
I started last week with openhab2 by moving from zwave.me to the openhab2 system.
I’m still working on the improvement of the scripts, so feel free to give me some hints.

binding = ntp,astro,smaenergymeter,ecotouch1

Rules

import java.util.concurrent.locks.ReentrantLock

/* 
  based on a script of the openhab2 community
*/
var Timer execHeatingTimer = null
var Timer execWarmWaterTimer = null
var ReentrantLock ruleIsRunning = new ReentrantLock 

rule "PV Optimisation - Initialize Values"
when
    System started
then
    PV_PowerExcessThreshold.postUpdate(1500) //exceed threshold in kWh
    PV_PowerExcessDuration.postUpdate(10)     //average exceed must be above threshold for this duration (minutes)
        
    PV_Warmwater_TargetTemperature.postUpdate(60)
    PV_Heating_TargetAdaption.postUpdate("2.0")
    PV_Optimisation_MinRunTime.postUpdate(60)
    /* Do set default values only on start up (Rule is also trigged on changes of the rule itselfs on code changes) */    
    PV_Warmwater_DefaultTemperature.postUpdate("45")
    PV_Heating_DefaultAdaption.postUpdate("-2.0")
    if (PV_Optimisation.state == NULL) PV_Optimisation.postUpdate(ON)           //activate optimatisation 
    if (PV_Optimisation_WarmwaterStatus.state == NULL) PV_Optimisation_WarmwaterStatus.postUpdate(OFF) //indicates if warm water is running
    if (PV_Optimisation_HeatingStatus.state == NULL) PV_Optimisation_HeatingStatus.postUpdate(OFF)   //indicates if heating is running
       

    //force to execute rules to end heating even if rule was reloaded due code changes
    if (PV_Optimisation_WarmwaterStatus.state == ON) {
        PV_Optimisation_WarmwaterStatus.postUpdate(OFF)
        PV_Optimisation_WarmwaterStatus.postUpdate(ON) // rule below should start
    }
    //force to execute rules to end heating even if rule was reloaded due code changes
    if (PV_Optimisation_HeatingStatus.state == ON) {
        PV_Optimisation_HeatingStatus.postUpdate(OFF)
        PV_Optimisation_HeatingStatus.postUpdate(ON) // rule below should start
    }    
end

/* Stop heating on System Shut Down or Switch for STOP Optimisation or Sunset
*/
rule "Set Default Values On System Shut Down"
when
    System shuts down 
    or
    Channel 'astro:sun:home:set#event' triggered START
    or
    Item PV_Optimisation changed to OFF
then
    if (PV_Optimisation_HeatingStatus.state == ON) {
        val normAdaption = (PV_Heating_DefaultAdaption.state as DecimalType).toString;        
        HeatPump_Adapt_Heating.sendCommand(normAdaption)
        PV_Optimisation_HeatingStatus.sendCommand(OFF)
        sendMail(EMails.state.toString, "Smart-Home-Nachricht [PV] Heizen beendet.", "Eigenverbrauchsoptimierung beendet! (Heizen)")
        logInfo("[PV]","Eigenverbrauchsoptimierung (Heizkurve) wurde ausgeschalten!")
    }
    if (PV_Optimisation_WarmwaterStatus.state == ON) {
        val normTemperature = (PV_Warmwater_DefaultTemperature.state as DecimalType).toString;        
        HeatPump_Warmwater_Temperature.sendCommand(normTemperature)
        PV_Optimisation_WarmwaterStatus.sendCommand(OFF)
        sendMail(EMails.state.toString, "Smart-Home-Nachricht [PV] Warmwasser beendet.", "Eigenverbrauchsoptimierung beendet! (Warmwasser)")
        logInfo("[PV]","Eigenverbrauchsoptimierung (Warmwasser) wurde ausgeschalten!")
    }    
end

/*
  This Rule is trigged as soon as the Heating of Enery Usage Optimization is started. 
  It ensures that the heating is running a specific time and after that it ensures that the heating stops.
*/
rule "End Heating Time"
when 
    Item PV_Optimisation_HeatingStatus changed from OFF to ON
then     
    var Boolean isNotAlreadyRunning = (execHeatingTimer === null || execHeatingTimer.hasTerminated);
    if (isNotAlreadyRunning) {

        val sunsetTime = new DateTime(Sunset_Time.state.toString)
        // create timer to allow the dumping to run for unitl 1h before sunset
        execHeatingTimer = createTimer(sunsetTime.minusMinutes(60), [|         
            try {
                val normAdaption = (PV_Heating_DefaultAdaption.state as DecimalType).toString;        
                HeatPump_Adapt_Heating.sendCommand(normAdaption)
                PV_Optimisation_HeatingStatus.sendCommand(OFF)
                logInfo("[PV]","Rücksetzen der Heizparameter auf " + normAdaption)
                var float currentEnergyProduction = (SMAEnergyMeter_BezogeneLeistung.state as Number).floatValue
                val energythreshhold = (PV_PowerExcessThreshold.state as Number).floatValue        
                if ( currentEnergyProduction < energythreshhold ) {                                    
                    logInfo("[PV]", "Laufzeit Eigenverbrauchsoptimierung beendet, Produktion < Schwelle")
                } else {
                    logInfo("[PV]", "Laufzeit Eigenverbrauchsoptimierung beendet, Produktion > Schwelle")
                }
            } finally {
                execHeatingTimer = null;
            }
        ])
    }
end

/*
  This Rule is trigged as soon as the warm water heating of Enery Usage Optimization is started. 
  It ensures that the warm water heating is running a specific time and after that it ensures that the heating stops.
*/
rule "End Water Heating Time"
when 
    Item PV_Optimisation_WarmwaterStatus changed from OFF to ON
then     
    var Boolean isNotAlreadyRunning = (execWarmWaterTimer === null || execWarmWaterTimer.hasTerminated);
    if (isNotAlreadyRunning) {
        // create timer to allow the dumping to run for at least the minimum run time
        execWarmWaterTimer = createTimer(now.plusMinutes((PV_Optimisation_MinRunTime.state as Number).intValue), [|    
            try {
                val normTemperature = (PV_Warmwater_DefaultTemperature.state as DecimalType).toString;        
                HeatPump_Warmwater_Temperature.sendCommand(normTemperature)
                PV_Optimisation_WarmwaterStatus.sendCommand(OFF)
                logInfo("[PV]","Rücksetzen der Warmwasserparameter auf " + normTemperature)
                var float currentEnergyProduction = (SMAEnergyMeter_BezogeneLeistung.state as Number).floatValue
                val energythreshhold = (PV_PowerExcessThreshold.state as Number).floatValue        
                if ( currentEnergyProduction < energythreshhold ) {                                    
                    logInfo("[PV]", "Laufzeit Eigenverbrauchsoptimierung beendet, Produktion < Schwelle")
                } else {
                    logInfo("[PV]", "Laufzeit Eigenverbrauchsoptimierung beendet, Produktion > Schwelle")
                }
            } 
            catch(Throwable t) {
               logError("[PV]", "Error End Heating Time" + t.getMessage())
            } 
            finally {
                execWarmWaterTimer = null;                
            }
        ])
    }
end

/*
  This Rule is trigged as soon as the sma meter device reports an change on the output of power. 
  Requires: 
  Persistence-Binding; E-Mail-Binding; Heating-Binding; PV-Binding
*/
rule "Energy Usage Optimatisation"
when Item SMAEnergyMeter_BezogeneLeistung changed
then
    if (ruleIsRunning.isLocked) {
        logDebug("[PV]", "Rule is locked")
        return;
    }
    try {
        ruleIsRunning.lock()  

        val sunsetTime = new DateTime(Sunset_Time.state.toString)
        //check if its already to late to start heating
        if (now.plusMinutes((PV_Optimisation_MinRunTime.state as Number).intValue + 30).isAfter(sunsetTime)) {
            logDebug("[PV]", "Sunset is to close to start optimisation " + Sunset_Time)
            ruleIsRunning.unlock()
            return;
        }

        // check if the logic to dump excess power production into the heating of "hot" warmwater is switched on
        // and heating is not already startet
        if ((PV_Optimisation.state == OFF) || ((PV_Optimisation_WarmwaterStatus.state == ON) && (PV_Optimisation_HeatingStatus.state == ON))) {
            logDebug("[PV]", "Nothing to do... Optimisation is:" + PV_Optimisation.state.toString + "/WarmWater:" + PV_Optimisation_WarmwaterStatus.state.toString + "/Heating:" + PV_Optimisation_HeatingStatus.state.toString)
            ruleIsRunning.unlock()
            return;
        }
        logDebug("[PV]", "Single Instance of Rule is Executed") 
        val float avgEnergyExcess = SMAEnergyMeter_BezogeneLeistung.averageSince(now.minusMinutes((PV_PowerExcessDuration.state as Number).intValue)).floatValue //need persistance       
        val energythreshhold = (PV_PowerExcessThreshold.state as Number).floatValue
        val energythreshhold2 = 2 * energythreshhold
        // check if the photovoltaics plant is currently generating enough energy to that the net export to the grid is higher than
        // the excess threshold
        if ( avgEnergyExcess < energythreshhold ) { 
            ruleIsRunning.unlock()
            return;
        }               
        logDebug("[PV]", "Produktion der letzten 10 Minuten: " + avgEnergyExcess + " > Schwelle "+ energythreshhold)
        // check current excess energy
        val currentEnergyProduction= (SMAEnergyMeter_BezogeneLeistung.state as Number).floatValue                    
        if ( currentEnergyProduction < energythreshhold ) {
            logDebug("[PV]","Aktuelle Produktion "+ currentEnergyProduction +" unterhalb der Schwelle, Vorgang abgebrochen.")
            ruleIsRunning.unlock()
            return;
        }
        logDebug("[PV]","Produktion " + currentEnergyProduction + "> Schwelle "+ energythreshhold + ", Starte Prüfung für Eigenverbrauchsoptimierung")

        //-- All requirements are met so we can start using energy output
        val currentTemperature = (HeatPump_Warmwater_CurrentTemperature.state as DecimalType).floatValue
        val targetTemperature = (PV_Warmwater_TargetTemperature.state as DecimalType).floatValue
        val Boolean startWarmWater = (currentTemperature < (targetTemperature-15)); //careful, if running a time based heating on the heat pump itself (on my device this means a decrease of target temperature so that the temperatur could never be reached)
        if (startWarmWater && (PV_Optimisation_WarmwaterStatus.state != ON)) {                                
                //begin dumping excess porwer into warmwater heating
                logInfo("[PV]","Produktion > Schwelle, Starte Warmwasser " + currentTemperature + "/" + targetTemperature)
                HeatPump_Warmwater_Temperature.sendCommand((PV_Warmwater_TargetTemperature.state as DecimalType).toString)
                PV_Optimisation_WarmwaterStatus.postUpdate(ON)  //triggers additional rule                                                             
                Thread::sleep(100)
                sendMail(EMails.state.toString, "Smart-Home-Nachricht [PV] Warmwasser aktiviert.", "Produktion " + currentEnergyProduction + " > Schwelle "+ energythreshhold + " => Starte Warmwasser.")
        } 

        var float temperatureOutside = (HeatPump_Temperature_Outside24h.state as DecimalType).floatValue
        val boolean itsColdOutside = temperatureOutside < 16;
        val Boolean startHeating = (!startWarmWater || ((currentEnergyProduction > energythreshhold2) && (avgEnergyExcess > energythreshhold2) ))
        if (startHeating  && (PV_Optimisation_HeatingStatus.state != ON) && itsColdOutside) {
                logInfo("[PV]","Produktion > Schwelle, Starte Heizung")
                HeatPump_Adapt_Heating.sendCommand((PV_Heating_TargetAdaption.state as DecimalType).toString)
                PV_Optimisation_HeatingStatus.postUpdate(ON)
                Thread::sleep(100)
                sendMail(EMails.state.toString, "Smart-Home-Nachricht [PV] Heizung aktiviert.", "Produktion " + currentEnergyProduction + " > Schwelle "+ energythreshhold + " => Erhöhe Heizkurve.")
        }        
    } 
    finally {
        ruleIsRunning.unlock()
    } 
end

Items

/*Global*/
Number Chart_Period "Chart Period"
String EMails "Für Informationen genutzte E-Mail [%s]"
/* Astro 
    Requires: Astro Binding / Gerhard Riegler
*/
DateTime Sunrise_Time "Sunrise [%1$tH:%1$tM]" { channel="astro:sun:home:rise#start" }
DateTime Sunset_Time "Sunset [%1$tH:%1$tM]" { channel="astro:sun:home:set#start" }
/* Photovoltaik
    Requires: SMA Energy Meter Binding / Binding for SMA Energy Meter. /Author: Osman Basha
*/
Group Chart_Photovoltaik
Group Photovoltaik (Home)
Group Photovoltaik_power (Home)
Group Photovoltaik_energy (Home) 
Switch PV_Optimisation "Optimierung Eigenverbrauch [%i]" <settings> (Photovoltaik) 
Switch PV_Optimisation_WarmwaterStatus "Optimierung Warmwasser [%i]"   <switch> (Heatpump,Photovoltaik)  //ReadOnly
Switch PV_Optimisation_HeatingStatus "Optimierung Heizung [%i]" <switch> (Heatpump,Photovoltaik) //ReadOnly
Number PV_PowerExcessThreshold "Grenze für Eigenverbrauchsoptimierung [%i kWh]" <energy> (Photovoltaik)  
Number PV_PowerExcessDuration "Minimale Dauer des Überschusses [%i Min]" <temperature> (Photovoltaik) 
Number PV_Warmwater_DefaultTemperature "Normal-Vorgabe für Warmwassertemperatur [%.1f °C]" <temperature> (Photovoltaik, Heatpump) 
Number PV_Warmwater_TargetTemperature "Ziel-Vorgabe für Warmwassertemperatur [%.1f °C]" <temperature> (Photovoltaik, Heatpump) 
Number PV_Optimisation_MinRunTime "Dauer der Aufheizperiode [%i Min]" <settings> (Photovoltaik, Heatpump)
Number PV_Heating_TargetAdaption "Vorgabe für Heizkurve-Niveau [%.1f °C]" <number> (Photovoltaik, Heatpump)
Number PV_Heating_DefaultAdaption "Normal-Vorgabe für Heizkurve-Niveau [%.1f °C]" <number> (Photovoltaik, Heatpump) 
/* Waterkotte EcoTouch heat pump
  Requires Waterkotte EcoTouch Binding / This is the binding for the Waterkotte EcoTouch heatpump. /Author: Sebastian Held
*/
Group Heatpump (Home)
Group Heatpump_outside "Heizung-Außen" (Outside)
Group Heatpump_source (Home)
Group Heatpump_control (Home)
Group Heatpump_water "Heizung-Wasser" (Home)
Group Heatpump_heating "Heizung-Heizen" (Home)
Group Heatpump_power "Heizung-Leistung" (Home)
Group Heatpump_state "Heizung-Status" (Home)
Number HeatPump_Temperature_Outside   "Wärmepumpe Außentemperatur [%.1f °C]"   <temperature> (Heatpump,Heatpump_outside,Outside) { ecotouch="temperature_outside" }
Number HeatPump_Temperature_Outside1h   "Wärmepumpe Außentemperatur 1h [%.1f °C]"   <temperature> (Heatpump,Heatpump_outside,Outside) { ecotouch="temperature_outside_1h" }
Number HeatPump_Temperature_Outside24h   "Wärmepumpe Außentemperatur 24h [%.1f °C]"   <temperature> (Heatpump,Heatpump_outside,Outside) { ecotouch="temperature_outside_24h" }
Number HeatPump_Temperature_8   "Wärmepumpe Vorlauf [%.1f °C]"   <temperature> (Heatpump,Heatpump_control,Home) { ecotouch="temperature_flow" }
Number HeatPump_Temperature_9   "Wärmepumpe Rücklauf [%.1f °C]"   <temperature> (Heatpump,Heatpump_control,Home) { ecotouch="temperature_return" }
Number HeatPump_Temperature_10   "Wärmepumpe Rücklauf Soll [%.1f °C]"   <temperature> (Heatpump,Heatpump_control,Home) { ecotouch="temperature_return_set" }
Number HeatPump_Warmwater_CurrentTemperature "Wärmepumpe Warmwasser Ist [%.1f °C]"   <temperature> (Heatpump,Heatpump_water,Home) { ecotouch="temperature_water" }
Number HeatPump_Temperature_12   "Wärmepumpe Warmwasser Soll [%.1f °C]"   <temperature> (Heatpump,Heatpump_water,Home) { ecotouch="temperature_water_set" }
Number HeatPump_Warmwater_Temperature   "Wärmepumpe Warmwasser Soll2 [%.1f °C]"   <temperature> (Heatpump,Heatpump_water,Home) { ecotouch="temperature_water_set2" }
Number HeatPump_Temperature_14   "Wärmepumpe Heizung Ist [%.1f °C]"   <temperature> (Heatpump,Heatpump_heating,Home) { ecotouch="temperature_heating_return" }
Number HeatPump_Temperature_15   "Wärmepumpe Heizung Soll [%.1f °C]"   <temperature> (Heatpump,Heatpump_heating,Home) { ecotouch="temperature_heating_set" }
Number HeatPump_Temperature_16   "Wärmepumpe Heizung Soll-Wert [%.1f °C]"   <temperature> (Heatpump,Heatpump_heating,Home) { ecotouch="temperature_heating_set2" }
Number HeatPump_Adapt_Heating    "Wärmepumpe Temperaturanpassung -2,0/+2,0 [%.1f °C]" <number> (Heatpump,Heatpump_heating,Home) { ecotouch="adapt_heating" } 
Number HeatPump_state            "Wärmepumpe Status [%i]"   <settings> (Heatpump,Home) { ecotouch="state" }
Switch HeatPump_state_sourcepump "Wärmepumpe Status Quellenpumpe [%i]"   <settings> (Heatpump,Heatpump_state,Home) { ecotouch="state_sourcepump" }
Switch HeatPump_state_heatingpump "Wärmepumpe Status Heizungsumwälzpumpe [%i]"   <settings> (Heatpump,Heatpump_state,Home) { ecotouch="state_heatingpump" }
Switch HeatPump_state_compressor1 "Wärmepumpe Status Kompressor [%i]"   <settings> (Heatpump,Heatpump_state,Home) { ecotouch="state_compressor1" }
Switch HeatPump_state_water      "Wärmepumpe Status Motorventil Warmwasser[%i]"   <settings> (Heatpump,Heatpump_state,Home) { ecotouch="state_water" }
  /* <not used> */
Number HeatPump_Temperature_6   "Wärmepumpe Quelleneintrittstemperatur [%.1f °C]"   <temperature> (Heatpump,Heatpump_source,Home) { ecotouch="temperature_source_in" }
Number HeatPump_Temperature_7   "Wärmepumpe Quellenaustrittstemperatur [%.1f °C]"   <temperature> (Heatpump,Heatpump_source,Home) { ecotouch="temperature_source_out" }
Number HeatPump_power_1   "Wärmepumpe elektrische Leistung [%.1f kW]"   <energy> (Heatpump,Heatpump_power,Home) { ecotouch="power_compressor" }
Number HeatPump_power_2   "Wärmepumpe thermische Leistung [%.1f kW]"   <energy> (Heatpump,Heatpump_power,Home) { ecotouch="power_heating" }

Things I don’t like about the framework:

  • To much different types for same semantics: null versus NULL, Number versus int etc. (remembers to visual and borland c++ variable type chaos)
  • I know thread programming is a pain … but createTimer is not one of my favorite solutions (that semaphore handling is real strange)
  • No defined order of rule initialization. (even rules with System started trigger can be executed after others)
  • No possibility to define an own order for execution of .rules .items files etc.
  • Break of good object oriented programming practice in rules due to missing of language element for reusable code (i know lambda / inline functions but those are no improvement in coding language evolution)
3 Likes