Excess charging Tesla and Zoe with Go-e charger Gemini flex

With this set of rules and items, I handle excess charging of a Tesla model Y and a Renault Zoe using a go-e Charger Gemini flex. The script may also work with other wallboxes and cars. The go-e wallbox allows to switch between 1 and 3 phases using the api. If your wallbox does not support this, just leave away the related parts of the code.

You need some source that delivers the current power consumption of your house, e.g. a smart meter. The script tries to keep the power consumption within the configured range.

The script also supports underpowering. If this is activated, it will not stop charging if there is not enough power, it just reduces the power to the minimum supported by the wallbox.

As the Zoe behaves significantly different from the Tesla when being charged, a different set of parameters is applied for it. The script uses the connection state of the Tesla to detect this, as the plug state of the Zoe is not really accurate all the time.

The script writes detailed logs, so you should always see whats going on. This is especially useful to fine-tune the settings

A few items to turn on and off excess charging and underpowering. The names of the items are hopefully self-explanating. As I am German, some of them have german names. I hope they are still understandable.

Ok, let’s go:

Rule to turn on Excess charging:

configuration: {}
triggers:
  - id: "1"
    configuration:
      command: ON
      itemName: Ueberschussladen
    type: core.ItemCommandTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      command: "6"
      itemName: GoEChargerMaxCurrent
    type: core.ItemCommandAction
  - inputs: {}
    id: "3"
    configuration:
      itemName: GoeCharger_Force_state
      command: "1"
    type: core.ItemCommandAction
  - inputs: {}
    id: "4"
    configuration:
      itemName: GoEChargerPhases
      command: "1"
    type: core.ItemCommandAction
  - inputs: {}
    id: "5"
    configuration:
      itemName: PhaseSwitchCountdown
      command: "6"
    type: core.ItemCommandAction

Rule to turn off excess charging:

configuration: {}
triggers:
  - id: "1"
    configuration:
      command: OFF
      itemName: Ueberschussladen
    type: core.ItemCommandTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      command: "32"
      itemName: GoEChargerMaxCurrent
    type: core.ItemCommandAction
  - inputs: {}
    id: "3"
    configuration:
      itemName: GoeCharger_Force_state
      command: "0"
    type: core.ItemCommandAction
  - inputs: {}
    id: "4"
    configuration:
      itemName: GoEChargerPhases
      command: "3"
    type: core.ItemCommandAction

And here the magic happens: The excess charge control rule, running every 10 seconds:

// is excess charging requested?
if (Ueberschussladen.state == OFF) {
  return;
} 

// is the wallbox available
if (GoEChargerPwmSignal.state == UNDEF) {
  logInfo("Charge control","No status from Wallbox")
  return;
}

// if there is no car connected, no need to do anything
if (GoEChargerPwmSignal.state == "READY_NO_CAR" || GoEChargerPwmSignal.state == "IDLE" ) {
  return;
}

// if openHAB was restarted, some parameters might not be set correctly. Set defaults
if (ExcessUnderpower.state == NULL ) {
  logInfo("Charge control","ExcessUnderpower was NULL, was openHAB restarted? Setting defaults")
  ExcessUnderpower.sendCommand ( OFF )
  ExcessChargeTesla.sendCommand( ON )
  ExcessDecreaseCounter.sendCommand(0)
  ExcessCurrentMin.sendCommand(6)
  return
}

// minimum charging current accepted by cars.
if (ExcessCurrentMin.state == NULL ) {
  logInfo("Charge control","ExcessCurrentMin not set")
  ExcessCurrentMin.sendCommand(6)
  return
}

var Number minChargingCurrent = ExcessCurrentMin.state as DecimalType

val Number phases = GoEChargerPhases.state as DecimalType

// maximum charging current possible for charger
val Number maxChargingCurrent = 16
  
// this could happen after openhab restart, so as fallback we assume Tesla
if (ExcessMinPower.state == NULL ) {
   ExcessChargeTesla.sendCommand( ON )
}

if (ExcessDecreaseCounter.state == NULL) {
  ExcessDecreaseCounter.sendCommand(0)
}
// minimum available power needed to start charging
var Number minAvailablePower = ExcessMinPower.state as DecimalType
  
// the power necessary to increase charging current by one step
var Number powerForIncrease = 270 * phases
// the power necessare to decrease charging current. Negative means we allow some underpowering
var Number powerForDecrease = -20 * phases
// power needed to switch to 3 phases
var Number powerForTPh = 750
// minimum available power needed to start charging -- for 1 phases ~ 6*230
minAvailablePower = 1350
// the number of countdown steps we wait after turning on or switching phases
var Number countdownSteps = 6
// minimum current for 3 phases
var Number minCurrent3P = 6

  
// Special values for Zoe. If Tesla is disconnected assume Zoe is charging
if (Tessi_Charging_State.state == "Disconnected" ) {
  powerForIncrease = 320 * phases
  powerForDecrease = -40 * phases
  minCurrent3P = 8
  powerForTPh = 500
  if (phases > 1) {
    minChargingCurrent = 8
  } else {
    minChargingCurrent = 6
  }
}

var Number increase = 0
var Number decrease = 0
  
// available power is the sum of watts off all phases from smartmeter. Negative values mean we are exporting energy
var Number powerP1 = SmP1Watt.state as Number
var Number powerP2 = SmP2Watt.state as Number
var Number powerP3 = SmP3Watt.state as Number

var Number availablePower = ( powerP1 + powerP2 + powerP3 ) * (-1)
var Number current = GoEChargerMaxCurrent.state as QuantityType
logInfo("Charge control","Available power: "+availablePower+", phases: "+phases+", current: "+current+", increase at "+powerForIncrease+", decrease at "+powerForDecrease)
  
// After charging start or phase switch, cars and wallbox need some time to adapt.
if (PhaseSwitchCountdown.state == NULL) {
  PhaseSwitchCountdown.sendCommand(0)
} else {
  var Number countdown = PhaseSwitchCountdown.state as DecimalType
  if (countdown > 0) {
    PhaseSwitchCountdown.sendCommand(countdown - 1)
    logInfo("Charge control","Start / phase change running, wait "+countdown+" more cycles")
    ExcessDecreaseCounter.sendCommand(0)
    return
  }
}

// if state is off, we do not charge yet and need the initial power before starting
var Number forcestate = GoeCharger_Force_state.state as DecimalType

if (forcestate == 1 ) {
  logInfo("Charge control","Mindestleistung: "+ minAvailablePower )
  if (availablePower >= minAvailablePower || ExcessUnderpower.state == ON) {
    logInfo("Charge control","Min power reached, start charging with "+minChargingCurrent+" amps")
    GoEChargerMaxCurrent.sendCommand(minChargingCurrent)
    GoeCharger_Force_state.sendCommand(0)
    PhaseSwitchCountdown.sendCommand(countdownSteps)
  } 
  return;
}

// if car is charged, no need to further control power
if (GoEChargerPwmSignal.state == "COMPLETE") {
  return
}

// handle power increase and switch from 1 to 3 phases
if (availablePower >= powerForIncrease ) {
  if (current < maxChargingCurrent) {
    increase = Math.floor(availablePower.doubleValue / powerForIncrease.doubleValue )
    if ( (current + increase) > maxChargingCurrent) {
      increase = maxChargingCurrent - current
    }
    logInfo("Charge control","increase amps to "+(current + increase))
    GoEChargerMaxCurrent.sendCommand (current + increase)
  } else if (phases == 1) {
    if ( availablePower > powerForTPh ) {
      logInfo("Charge control","switch to 3 phases, "+minCurrent3P+" amps ")
      GoEChargerPhases.sendCommand(3)
      GoEChargerMaxCurrent.sendCommand (minCurrent3P)
      PhaseSwitchCountdown.sendCommand(countdownSteps) 
    } else {
      logInfo("Charge control","not enough power for phase switch")
    }
  } else {
    logInfo("Charge control","reached maximum")
  }
  ExcessDecreaseCounter.sendCommand(0)
  return;
}

// handle power decrease and switch from 3 to one phases
if (availablePower <= powerForDecrease) {
  if (current > minChargingCurrent) {
    decrease = Math.floor(availablePower.doubleValue / powerForIncrease.doubleValue)*-1
    if ((current - decrease) < minChargingCurrent) {
      decrease = current - minChargingCurrent
    }
    logInfo("Charge control","decreasing to "+(current - decrease)+" amps")
    GoEChargerMaxCurrent.sendCommand (current - decrease)
  } else if (phases > 1) {
    var Number decreaseCounter = ExcessDecreaseCounter.state as DecimalType
    if (decreaseCounter > 6) {   
      logInfo("Charge control","switching to 1 phase, 16 amps") 
      GoEChargerPhases.sendCommand(1)
      GoEChargerMaxCurrent.sendCommand (16)
      PhaseSwitchCountdown.sendCommand(countdownSteps)
      ExcessDecreaseCounter.sendCommand(0)
    } else {
      ExcessDecreaseCounter.sendCommand(decreaseCounter+1)
      logInfo("Charge control","DecreaseCounter: "+decreaseCounter) 
    }
    
  }
  // we already hit the minimum, so we turn off charging completely now
  else if (ExcessUnderpower.state == OFF) {
    logInfo("Charge control","too little power, stop charging")
    GoeCharger_Force_state.sendCommand(1)
  }
  return;
}

// reset decreaseCounter, just in case
ExcessDecreaseCounter.sendCommand(0)
// we are within the allowed power range, do nothing.
logInfo("Charge control","no action")

In case of questions, just ask :slight_smile:

3 Likes

For completeness, here is the part of the sitemap to activate the charging modes:

Text label="Wallbox" {
    Switch  item=Ueberschussladen
    Switch  item=ExcessUnderpower
}

Hi @Wolfgang1966
Thanks for your detailled example.
two questions:

  1. I just bought a Gemini flex and was wondering, if you use the Go-E charger binding, which states support of chargers HOME+ and HomeFix only, or if you use mqtt / http option directly?
  2. Does switching between different current and switching from 1 to 3 phases or back work for both cars during charging (i.e. without interrupting the charging process)?

Hi @Li0n

1.: I use the Go-E charger binding for the Gemini, works well with API version set to V2 in the binding config. At the Gemini I activated both v1 and v2 API, all others are deactivated.

2.: Switching between 1 and 3 phases works without problems for both Zoe and Tesla. The charging process is interrupted for a couple of seconds, as the CCS protocol seems not to allow switching in a running charging process, but everything happens within one minute. Due to this interruption the script waits for some time before downgrading from 3 to one phases, so that a small cloud (or myself getting a coffee from my coffee machine :smile: ) doesn’t trigger a downgrade immediately followed by an upgrade. For cloudy weather we could think about some kind of “flap detection” that holds the charging at 1 phase when it detects too many switches within a short period of time, but up to now I never was in that situation.

Perfect, thanks for your response!
I just tried out the binding now “dry” (without connected car) with only activating V2 for the wallbox and it works fine.
The only thing which is currently not clear for me is the usage of the “force state”. Why do you set it to 0 (neutral) and not 2 (off), when solar excess charging is turned off?
My intuition would have been to use value 2 instead.

Hi @Li0n , the script sets forceState to 0 (neutral) when charging should start and to 1 (off) when it should stop. 2 would be “ON” according to go-eCharger-API-v2/apikeys-de.md at main · goecharger/go-eCharger-API-v2 · GitHub. During rule development I found some explanation regarding force state at go-e Charger Überschussladen mit V2 API und "amx" Befehl - Ladeequipment - Elektroauto Forum . For me it works with switching between 0 and 1, according to the forum article using 2 instead of 0 might be useful when timers are in place which need to be overridden. This is not the case in my setup, but feel free to try it out :slight_smile: