Go-eCharger control based on photovoltaik grid feed

This is my rule I currently use for controlling the charging power for a Go-eCharger based on the surplus from photovoltaik energy. It assumes the following items to be defined:

Switch Charging_Solar_Automatic_Override             // a virtual switch in OpenHAB2 to force charging
Number Solar_Meter_Power_CurrentGridConsumed      // the current electric power consumption from the power grid (in my case, this is negative when there is a surplus, i.e. when the car charger should be enabled)
Number:Power GoECharger_PowerL1     // as defined by the default example for the Go-eCharger binding
Number:Power GoECharger_PowerL2     // as defined by the default example for the Go-eCharger binding
Number:Power GoECharger_PowerL3     // as defined by the default example for the Go-eCharger binding
Number:ElectricCurrent GoECharger_MaxCurrent     // as defined by the default example for the Go-eCharger binding
Switch GoECharger_AllowCharging     // as defined by the default example for the Go-eCharger binding

Note that the item values are assumed to be stored in a database that supports averaging over a timeframe (e.g. influxdb).

I currently use a Fronius converter with the respective binding and the GridConsumed item uses channel "fronius:powerinverter:datalogger:inverter:powerflowchannelpgrid". However, any smart meter / inverter will work if it can return the current surplus.

The rule is then defined as

// ATTENTION: Constants to tweak for the local installation
val GridVoltage = 220
val MaxAmperage = 16		// the maximum amperage the charger can use (typically the fuse/cable rating, typically 16A)
val MinAmperage = 6			// the minimum amperage the charger can be set to (e.g. for the Go-eCharger this is 6A)

rule "Set maximum current for car charger based on PV feed (compute every 10 minutes)"
when Time cron "0 */10 * ? * *" or
	 Item Charging_Solar_Automatic_Override changed
then
	// Note: all values averaged over the last 10 minutes to even out spikes in solar generation and other local load
	val averageSince = now.minusMinutes(10)
	val currentChargePower = GoECharger_PowerL1.averageSince(averageSince) +
		GoECharger_PowerL2.averageSince(averageSince) +
		GoECharger_PowerL3.averageSince(averageSince)
	// this is the total power available, i.e. current feeding into grid + what we are currently charging into the car
	// Note: the powerflowchannelpgrid channel for Fronius smart meter is negative when feeding into the grid, positive when pulling,
	// therefore need to invert here
	val totalPower = currentChargePower - Solar_Meter_Power_CurrentGridConsumed.averageSince(averageSince)
	logInfo("Electric power optimization", "Available power for charging: " + (-(Solar_Meter_Power_CurrentGridConsumed.state as Number)) + " (current grid feed, average in last 10 min: " + (-Solar_Meter_Power_CurrentGridConsumed.averageSince(averageSince)) + ") + " + currentChargePower + " (current charge power) = " + totalPower + "W")

	var boolean allow = ON
	var int current = MinAmperage

	// If the override switch is set, allow charging at maximum amperage 
	if (Charging_Solar_Automatic_Override.state == ON) {
		logInfo("Electric power optimization", "Override switch is ON, allowing charging with " + MaxAmperage + "A")
		allow = ON
		current = MaxAmperage
	}
	else if (totalPower < (MinAmperage)*3*GridVoltage) {
		logInfo("Electric power optimization", "Not enough surplus power to charge, switching off")
		allow = OFF
		current = MinAmperage
	}
	else {
		allow = ON
		/*logDebug("Electric power optimization", "totalPower=" + totalPower + " / (GridVoltage*3)= " + (GridVoltage*3) + " = " +
			(totalPower / (GridVoltage*3))) */
		current = Math::floor((totalPower / (GridVoltage*3) as Number).doubleValue).intValue
		if (current > MaxAmperage) {
			logInfo("Electric power optimization", "Power surplus exceeds maximum power for charging, capping to " + current + "A")
			current = MaxAmperage
		}
	}

	// set maxCurrent and allow fields
	logInfo("Electric power optimization", "Setting charger to allow=" + allow + ", maxCurrent=" + current)
	GoECharger_MaxCurrent.sendCommand(current)
	GoECharger_AllowCharging.sendCommand(allow)
end
2 Likes

Hi,

thanks for your rule. I will be expanding my solar system in the coming months from now just 2 kWp to then just over 25 kWp and will be using a go e-Charger and openHAB to use the surplus to charge my car.

I was wondering one thing: You set your GridVoltage to 220 fixed. Does it make sense to not use fixed 220, but rather use the voltages that we get from the go e-Charger binding for each phase?

So instead of multiplying 220*3 I would do something like:

var int voltageSum = (GoeCharger_VoltageL1.state as Number + GoeCharger_VoltageL2.state as Number + GoeCharger_VoltageL3.state as Number).intValue

I am absolutely no expert, but would have guesses that this make the result come closer to the real world? Or is there something I’m missing?

Here’s your rule that I am currently “tweaking” for my system. Unfortunately, I can’t totally test it until we get the bigger solar system :slight_smile:

// ATTENTION: Constants to tweak for the local installation
// val int GridVoltage = 220
val reduceFactor = 0.95 // factor to reduce newPower to be less close to available power, e.g. 0.9 sets newPower to 90% of possible power
val MaxAmperage = 16		// the maximum amperage the charger can use (typically the fuse/cable rating, typically 16A)
val MinAmperage = 6			// the minimum amperage the charger can be set to (e.g. for the Go-eCharger this is 6A)

rule "Set maximum current for car charger based on PV feed (compute every 10 minutes)"
// when Time cron "0 */10 * ? * *" or
// 	 Item Charging_Solar_Automatic_Override changed
when Item WR_Live_Export changed or
     Item WR_Live_Export_Test changed or
 	 Item Charging_Solar_Automatic_Override changed
then
 
	// Note: all values averaged over the last 10 minutes to even out spikes in solar generation and other local load
	//val averageSince = now.minusMinutes(10)
	// val currentChargePower = GoeCharger_PowerL1.averageSince(averageSince) +
	// 	GoeCharger_PowerL2.averageSince(averageSince) +
	// 	GoeCharger_PowerL3.averageSince(averageSince)
    val currentChargePower = (GoeCharger_PowerL1.state as Number).intValue +
		(GoeCharger_PowerL2.state as Number).intValue  +
		(GoeCharger_PowerL3.state as Number).intValue 


	// // this is the total power available, i.e. current feeding into grid + what we are currently charging into the car
	val totalPower = (currentChargePower + ((WR_Live_Export_Test.state as Number)*1000)) * reduceFactor

	var int allow = 0
	var int current = MinAmperage
    var int voltageSum = (GoeCharger_VoltageL1.state as Number + GoeCharger_VoltageL2.state as Number + GoeCharger_VoltageL3.state as Number).intValue


	// If the override switch is set, allow charging at maximum amperage 
	if (Charging_Solar_Automatic_Override.state == ON) {
		logInfo("Electric power optimization", "Override switch is ON, allowing charging with " + MaxAmperage + "A")
		allow = 1
		current = MaxAmperage
	}
	//else if (totalPower < MinAmperage * (GoeCharger_VoltageL1.state as Number + GoeCharger_VoltageL2.state as Number + GoeCharger_VoltageL3.state as Number)) { //else if (totalPower < (MinAmperage)*3*GridVoltage) {
	else if (totalPower < (MinAmperage * voltageSum)) { //
        logInfo("Electric power optimization", "Not enough surplus power to charge, switching off; totalPower: " + totalPower + "; MinAmperage * Voltages: " + (MinAmperage * voltageSum))
		
        allow = 0
		current = MinAmperage
	}
	else {
		allow = 1
		/*logDebug("Electric power optimization", "totalPower=" + totalPower + " / (GridVoltage*3)= " + (GridVoltage*3) + " = " +
			(totalPower / (GridVoltage*3))) */
        current = Math::floor((totalPower / (voltageSum) as Number).doubleValue).intValue

        if (current > MaxAmperage) {
			logInfo("Electric power optimization", "Power surplus exceeds maximum power for charging, capping to " + MaxAmperage + "A")
			current = MaxAmperage
		}
	}

	// set maxCurrent and allow fields
	logInfo("Electric power optimization", "Setting charger to allow=" + allow + ", maxCurrent=" + current)

    // if (allow == 1)
    //     GoeCharger_MaximumCurrent.sendCommand(ON)
    // else 
    //     GoeCharger_MaximumCurrent.sendCommand(OFF)

	// GoeCharger_AllowCharging.sendCommand(allow)
end

That makes sense to me - I will look into that on my rule as well.

Cool.

One more question. When using (a modified version of) your rule, it looks like to takes quite a long time to get down from for example 16 A charging to turning the go-e off. I even changed the rule to not run every 10 but every 1 minute, and my rule went from 16 A down to 15 A after the first minute, then to 14 A after another… to 6 A, and then off. So over 10 minutes. Maybe I messed something up changing the rule though. Is it the same for you, and did you do that on purpose?

I guess it does make sense to not turn off charging right away with every cloud, but maybe it’s better to do it a little more quickly? :slight_smile:

I started my rules with very abrupt changes of the wallbox current, even switching it off when available power dropped below 1,2 kW. Now I just average with the last used value, adjusting evey 30 s.
So when a cloud changes the available power and the corresponding current should drop from 16 A to 0 A, I get a sequence of calculated 16 A, 8 A, 4 A which results in 16A, 8 A, 0 A because of the minimum charge current of 6 A.

What do you think? Does that strain the wallbox or the car too much?

One important parameter is how long you average the consumption for. Note that this averaging is assumed to be done by the persistence layer.
If you choose a shorter average, it will come down more quickly.