Dynamic (smart) EV Charging

HI,

For the past year or two, I’ve been using my home-brew EV charger as a dynamic solar charging solution.

I’ve created some sensors and a few automations, and I thought I’d share them with you. Are they perfect? Not at all. Do they work? Yes, though with some minor caveats.

So, how does it work?

Firstly, I created a sensor that measures my net energy usage. My solar system (Enphase Envoy-S Metered) provides all the necessary information.

CleanShot 2024-08-15 at 17.43.47

Sensor:

    sensor:
      - name: Netto electriciteitsverbruik
        unique_id: netto_electriciteitsverbruik
        state: "{{ ((states('sensor.envoy_122239064314_current_power_consumption') | float(0) * 0.001) - (states('sensor.envoy_122239064314_current_power_production') | float(0) * 0.001))|round(2) }}"
        unit_of_measurement: "Kw"

My 1-phase car charger is able to set the max amps charging, So I use that to dynamically control the charging current.
In my case I am on a 240V European system, so the minimum EV charging amperage of 6 amps x 240v = 1.44kW and in my case the maximum charging current is 20A x 240v = 4.8kW. Therefor I can dynamically control the charging from 1.44kW up to 4.8kW.
If you have a 3-phase charger, your minimum charge will start at 4.3kW ((3x6A) 18A x 240V = 4320W) which is way less efficient.

My new charger will be able to switch between 1-phase and 3-phase charging to make it even more efficient and to enable faster charging when needed.

####### smart solar charging ########
# evse = 1 phase car charger (API)  #
# Vehicle state:                    #
# 2 = connected                     #
# 3 = charging                      #
# duracell = name of the Tesla EV   #
# envoy = solar invertor monitoring #
#####################################

## turn smart solar charging on
  - id: smart_solar_charging_on
    alias: smart_solar_charging_on
    initial_state: on
    trigger:
      - platform: state
        entity_id: sensor.netto_electriciteitsverbruik  # net energy usage
    condition:
      condition: and
      conditions:
      - condition: numeric_state
        entity_id: sensor.netto_electriciteitsverbruik
        below: "-1"    # only when "uploading"  more then 1Kw to the net
      - condition: state
        entity_id: input_boolean.solar_smart_charging  # a boolean to be able to enable smart charging when needed
        state: "on"
      - condition: state
        entity_id: sensor.evse_vehiclestate  
        state: "2"     # Only when vehicle connected
      - condition: numeric_state
        entity_id: sensor.duracell_battery   # Duracell is my tesla's name ;), just charge when the LFP battery is below 100%
        below: "100"
    action:
      - service: input_number.set_value
        data:
          value: "6"   # start at 6 amps
        target:
          entity_id: input_number.evse_max_amps  # set the charging speed
      - service: switch.turn_on
        entity_id: switch.evse     # turn charger on
      - delay: 00:00:01
      - service: switch.turn_on
        entity_id: switch.duracell_charger  # wake up the tesla, not really needed, is just slightly faster
      - delay: 00:00:03
      - service: homeassistant.update_entity
        entity_id: sensor.evse   # faster update the rest sensor of the charger
    mode: restart

## turn smart solar charging off
  - id: smart_solar_charging_off
    alias: smart_solar_charging_off
    initial_state: on
    trigger:
      - platform: numeric_state
        entity_id: sensor.netto_electriciteitsverbruik
        above: "0"
        for: 
          minutes: "1"
    condition:
      condition: and
      conditions:
      - condition: state
        entity_id: input_boolean.solar_smart_charging
        state: "on"
      - condition: state
        entity_id: sensor.evse_vehiclestate
        state: "3"
    action:
      - service: switch.turn_off
        entity_id: switch.evse
      - delay: 00:00:03
      - service: homeassistant.update_entity
        entity_id: sensor.evse
    mode: restart

#### set charging amperage ####

## up
  - id: smart_solar_charging_amperage_up
    alias: smart solar charging amperage up
    initial_state: on
    trigger:
      - platform: state
        entity_id: sensor.netto_electriciteitsverbruik
    condition:
      condition: and
      conditions:
      - condition: numeric_state
        entity_id: sensor.netto_electriciteitsverbruik
        below: "-0.25"  # we need at least 1A before we can go up
      - condition: state
        entity_id: input_boolean.solar_smart_charging
        state: "on"
      - condition: state
        entity_id: sensor.evse_vehiclestate
        state: "3"    # only when actually charging
      - condition: template
        value_template: >-
          {{ states('input_number.evse_max_amps') | float(0) < 
          (state_attr('input_number.evse_max_amps', 'max') | float(0)) |round(0) }}
    action:
      - service: input_number.set_value
        data:
          value: "{{ (states.input_number.evse_max_amps.state | float(0) + 1) | round(0) }}"
        target:
          entity_id: input_number.evse_max_amps
      - service: homeassistant.update_entity
        data: {}
        target:
          entity_id: sensor.evse
    mode: restart

## down
  - id: smart_solar_charging_amperage_down
    alias: smart solar charging amperage down
    initial_state: on
    trigger:
      - platform: state
        entity_id: sensor.netto_electriciteitsverbruik
    condition:
      condition: and
      conditions:
      - condition: numeric_state
        entity_id: sensor.netto_electriciteitsverbruik
        above: "0"
      - condition: state
        entity_id: input_boolean.solar_smart_charging
        state: "on"
      - condition: state
        entity_id: sensor.evse_vehiclestate
        state: "3"
      - condition: template
        value_template: >-
          {{ states('input_number.evse_max_amps') | float(0) > 
          (state_attr('input_number.evse_max_amps', 'min') | float(0)) |round(0) }}
    action:
      - service: input_number.set_value
        data:
          value: "{{ (states.input_number.evse_max_amps.state | float(0) - 1) | round(0) }}"
        target:
          entity_id: input_number.evse_max_amps
      - service: homeassistant.update_entity
        data: {}
        target:
          entity_id: sensor.evse
    mode: restart

This results in:

As you can see, I am by no means an expert. I’m sure there is room for improvement, especially by combining automations.

On a cloudy day, the net energy usage checking every 10 seconds and adjusting by only 1 amp, is too slow to completely prevent any grid usage. For example, when our washing machines heater kicks in, it takes about 40-50 seconds for the charger to adjust the amperage accordingly.

CleanShot 2024-08-15 at 18.13.38

This could be optimized by directly calculating the required amperage adjustment based on the actual net usage, rather than stepping up or down incrementally.

For example; if the net usage is 1.8kW, the charger would have to ideally step down 8A (1800/240=7.2) at once (with a minimum of 6A).

I haven’t yet figured out the best way to achieve this. If anyone is interested in helping out, please feel free to share! :blush:

Nice example how to do this, thanks!

The HA Core Enphase integration (and some custom integrations) also have sensor.envoy_serialno_net_consumption which is the power exchange with the grid when CT are installed. What you are calculating from power_production and power_consumption.

If you have a smart meter like DSMR you can use that as a source for your calculations.