Ideas and help setting up EV charging automation in and off-grid environment

Hello friendly community at HA!

I’m on the path to create a situation where charging my car is no longer a full time job. Controlling my system is controlling me. It is not sustainable. Help.

I am completely off-grid. I have PV panels connected to a Fronius Primo, which is then connected to a Selectronic SP PRO, which is connected to the house (and the car charger which is a Wallbox Plus) and the battery bank which is 14.4kW of lithium storage.

I’m finding it hard to be creative enough to come up with an elegant solution, and will probably find it even harder to implement.

In an ideal world, the car would be treated like the ‘grid’ in a grid tied setup. IE the panels would power the house, with the remains going to the house batteries and once the batteries were full, the rest would be exported to the grid IE in this case, the car battery. I do not know how to implement this or if this is even the best solution.

What are your thoughts on the best way to set this up? Do you think I’m on the right track or is there something I’m missing? I’m worried that with the dynamism of solar production this would result in the car charging current fluctuating like crazy which doesn’t sound optimal for the car batteries.

Looking forward to any suggestions at all. Appreciate it.

Kindest regards.

First, this is not a question about blueprints, so that blueprint tag in the topic really doesn’t belong there.

Let me search that for you…

and seriously another dozen or so just here in the HA community. I didn’t even check Google.

I DID check google, but I guess my question was too specific. I should have kept it more broad in regards to excess generation rather than my particular off-grid EV charging setup.

Appreciate the candid response.

Will read with interest.

1 Like

Unfortunately I am really none the wiser. The following link was the closest I could get but I’m still well over my head.

For example, the following:

      - service: number.set_value
        data_template:
          entity_id: number.t_ray_charging_amps
          value: >-
            {% set current_amps = states('number.t_ray_charging_amps') | int %}
            {% set add_amps = (states('sensor.pow_k_po') | int / 688) | round(0,'floor') %}
            {{ current_amps + add_amps }}

I really do not know what to do with this information. Do I make a number template in my configuration.yaml? What do I put in it? I’ve tried reading documentation for hours but I’m going in circles and clueless regardless.

The same goes with the following:

value: >-
            {{ states('number.charging_amps') | int - (states('sensor.active_import_average_5_min')|int / 688) | round(0,'floor') }}

This seems more elegant and pertinent to me, but I still have no idea how to actually implement this. It also seems a lot of this formatting may be deprecated.

Really appreciate the effort.

Hi, I’m the author of the DIY smart grid: EV (Tesla) charging on excess solar power production.
Let me try and see if I can help you a bit in this.

Let’s start out by making some abstractions on what you want to achieve (goal = automation), and what you need (requirements) to do so.

First, let me try and see if I understand you right. You are off-grid. You want solar power to fill up your battery bank first and any surplus power production should fill up the car’s battery (in that order). There’s a few integrations and/or custom sensors that need to be present (I’ll name them for later reference):

  • a sensor that reads the sum of the PV power production and the power consumption, updated every few seconds (sensor pp)
  • a sensor that reads the battery bank charge level (sensor bb)
  • a sensor that reads the car battery charge level (sensor cb)
  • an input number (often in amps) that controls the charging amps for either the car or the wallbox (input number ca)

Now for the logical steps (working with a 235 volt grid here). The trigger for this automation to (re)run is any change in surplus power production:

  1. Prerequisite: input number ca = 0 (don’t charge the car)
  2. Check if there is any surplus power production (sensor pp => 235 watts)
  3. If yes and sensor pp < 100%, do nothing (the battery bank will fill up)
    3a. If yes and sensor pp.= 100%, calculate the charging amps
    3b. Set input number ca to the calculated charging amps (this calculation also sets the input number ca to 0 if sensor pp < 235 watts)
  4. If no, do nothing (any power will be drawn from the battery)

This should offer you a nice starting point in terms of automated charging logic. I can’t look into your situation for the exact sensors and everything, so you still have some work left to do. I advise you to look into my grid automation to see how I’ve handled the surplus power reading and the charging amps calculation. Hope this helps a bit!

2 Likes

Hello @dikkertjedap! Thanks for responding.

I read your original thread and but was quite overwhelmed. I will read through it once more.

Your assumptions are correct! I have already setup sensors for PV production and Battery SOC (of the house), but unfortunately there doesn’t seem to be a way to get the cars SOC. Wallbox just lists this as unknown, but I couldn’t see this being a major problem? Where I get stuck is creating the input number in amps. I will show you what I’ve got so far and you can pick holes in it and provide further guidance if you are willing :slight_smile:

- id: '###'
  alias: test
  description: ''
  trigger:
  - platform: time_pattern
    minutes: /1
  condition:
  - condition: and
    conditions:
    - condition: sun
      after: sunrise
      after_offset: 02:00:00
    - condition: sun
      before: sunset
      before_offset: -02:00:00
  action:
  - if:
    - condition: numeric_state
      entity_id: sensor.battery_soc
      above: 90
    - condition: and
      conditions:
      - condition: numeric_state
        entity_id: sensor.battery_use
        below: -1300
    then:
    - if:
      - condition: device
        type: is_off
        device_id: dd9d56ea186f6428125c912832e9ad9c
        entity_id: 047f7ff3b13da53c4f688733685ea22c
        domain: switch
      then:
      - type: turn_on
        device_id: dd9d56ea186f6428125c912832e9ad9c
        entity_id: 047f7ff3b13da53c4f688733685ea22c
        domain: switch
    - service: number.set_value
      data:
        value: '{% set current_amps = states(''wallbox_portal_max_charging_current'')
          | int %} {% set add_amps = (states(''battery_use'') | int / -240) | round(0,''floor'')
          %} {{ current_amps + add_amps }}'
      target:
        entity_id: number.wallbox_portal_max_charging_current
    else:
    - if:
      - condition: numeric_state
        entity_id: sensor.battery_use
        above: 0
      then:
      - type: turn_off
        device_id: dd9d56ea186f6428125c912832e9ad9c
        entity_id: 047f7ff3b13da53c4f688733685ea22c
        domain: switch
  mode: single

To explain some of this further, I have set battery_use to below -1300 as I’m using this as my ‘surplus measurement’. IE when the battery is in negative use, it means it’s charging. And I have chosen -1300 because the minimum my wallbox can charge seems to be 6 amps (at 240V).

It’s the number.set_value section it all falls apart. I have tried to pilfer this from the thread above and changed a few parameters, but I think I’m very much missing something. There was a user mentioning inputting an average surplus over 5 minutes and triggering as often, but I was unsure how to do this. I should also explain that this fails with value/template error because no default specified. I haven’t worried to fix this because I’m not sure if it’s worth fixing and I should be going another route entirely.

Thanks again for the response. I will peruse your original thread with hopefully some improved mental capacity and see if it starts to make sense.

Kindest regards.

EDIT: If starting from scratch, this is where I get to before I lose all sense of purpose and electrical brain power and start banging my head against the wall.

- id: '1696108815309'
  alias: Test
  description: ''
  trigger:
  - platform: time_pattern
    minutes: /1
  condition:
  - condition: numeric_state
    entity_id: sensor.battery_soc
    above: 90
  action:
  - if:
    - condition: numeric_state
      entity_id: sensor.battery_use
      below: -1400
    then:
    - type: turn_on
      device_id: dd9d56ea186f6428125c912832e9ad9c
      entity_id: 047f7ff3b13da53c4f688733685ea22c
      domain: switch
    - variables:
        grid_usage: '{{ states(''sensor.battery_use'') | float }}'
        max_charging_amps: 25
        min_charging_amps: 6
        grid_voltage: 240}
    - condition: template
      value_template: '"{{ grid_usage < 0 }}"'
    - service: number.set_value
      target:
        entity_id: number.wallbox_portal_max_charging_current
      data:
        value: '{% set wallbox_portal_charging_power = (states(''number.wallbox_portal_max_charging_current'')
          | int) * grid_voltage %}  {% set wallbox_portal_max_charging_current = ((grid_usage
          + wallbox_portal_charging_power) / grid_voltage) | round(0, ''floor'') |
          int %} {{ [max_charging_amps, charging_amps] | min }}'

I decided it would be potentially better to use your ‘Power Grid Usage’ sensor, so I tried adding it to my configuration.yaml file like so

    - name: "power consumption"
      device_class: energy
      state_class: total
      state: "{{(state_attr('sensor.selectronic', 'load_w')|float) | round(3)}}"
      unit_of_measurement: "W"
      unique_id: sensor.selectronic.pwrcon

    - name: "power production"
      device_class: energy
      state_class: total
      state: "{{(state_attr('sensor.selectronic', 'solarinverter_w')|float) | round(3)}}"
      unit_of_measurement: "W"
      unique_id: sensor.selectronic.pwrpro


    - name: "power grid usage"
      unit_of_measurement: "W"
      value_template: >
        {% set power_production_w = states('sensor.power_production') | float * 1000 %}
        {% set power_consumption_w = states('sensor.power_consumption') | float * 1000 %}
        {% if power_production_w >= 0 and power_consumption_w >= 0 %}
          {{ (power_production_w - power_consumption_w) }}
        {% elif power_production_w >= 0 %}
          {{ power_production_w }}
        {% else %}
          {{ -1 * power_consumption_w }}
        {% endif %}

But it’s spewing errors, probably because I have no idea what I’m doing :smiley:

EDIT II:

I just screwed around with the Template Developer Tools and came up with this, and it seems to give me the correct reading?

template:
  - sensor:
      - name: "Remaining power"
        unit_of_measurement: "W"
        state: >
          {% set power_production_w = states('sensor.power_production') | float %}
          {% set power_consumption_w = states('sensor.power_consumption') | float %}
          {% if power_production_w >= 0 and power_consumption_w >= 0 %}
          {{ (power_production_w - power_consumption_w) }}
          {% elif power_production_w >= 0 %}
          {{ power_production_w }}
          {% else %}
          {{ -1 * power_consumption_w }}
          {% endif %}

If you don’t see anything too gnarly with this, let me know.

I think I’m finally beginning to get my head around this, but I won’t know until the sun is up tomorrow. This is what I’ve got!

- id: '1696138397325'
  alias: IS THIS IT??
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.power_surplus_for_car
  condition:
  - condition: numeric_state
    entity_id: sensor.battery_soc
    above: 90
  action:
  - variables:
      grid_usage: '{{ states(''sensor.power_surplus_for_car'') | float }}'
      max_charging_amps: 25
      min_charging_amps: 6
      grid_voltage: 240
  - choose:
    - conditions:
      - condition: template
        value_template: '{{ grid_usage > 0 }}'
      sequence:
      - type: turn_on
        device_id: dd9d56ea186f6428125c912832e9ad9c
        entity_id: 047f7ff3b13da53c4f688733685ea22c
        domain: switch
      - service: number.set_value
        target:
          entity_id: number.wallbox_portal_max_charging_current
        data:
          value: '{% set charger_power_draw = (states(''number.wallbox_portal_max_charging_current'')
            | int) * grid_voltage %}  {% set charging_amps = ((grid_usage + charger_power_draw)
            / grid_voltage) | round(0, ''floor'') | int %} {{ [max_charging_amps,
            charging_amps] | min }}

            '
    - conditions:
      - condition: template
        value_template: '{{ grid_usage <= 0 }}'
      sequence:
      - service: number.set_value
        target:
          entity_id: number.wallbox_portal_max_charging_current
        data:
          value: '{% set deficit_watts = -1 * grid_usage %} {% set current_charging_amps
            = states(''number.wallbox_portal_max_charging_current'') | int %} {% set
            required_charging_amps = ((deficit_watts / grid_voltage) | round(0, ''ceil''))
            | int %} {% set updated_charging_amps = current_charging_amps - required_charging_amps
            %} {% if updated_charging_amps < min_charging_amps %} 1 {% else %} {{
            [max_charging_amps, updated_charging_amps] | min }} {% endif %}

            '
      - choose:
        - conditions:
          - condition: template
            value_template: '{{ states(''number.wallbox_portal_max_charging_current'')
              | int <= 6 }}'
          sequence:
          - type: turn_off
            device_id: dd9d56ea186f6428125c912832e9ad9c
            entity_id: 047f7ff3b13da53c4f688733685ea22c
            domain: switch
  mode: single

Thanks so much :smiley:

Thought I’d start a new post as opposed to spam editing the last one.

Unfortunately I’m still having some weird issues I can’t get to the bottom of with the following automation.

In the section that dictates whether the current needs to be reduced, it sometimes results in a value of 1 and does nothing. This can be a problem. A cloud can roll over after some nice sunshine, resulting in a high amperage of charging not being able to be reduced until the value spits out 6 or higher, meanwhile leading to a large drain on the house batteries.

Any advice on how to stop this?

Kindest regards.

- id: '1696138397325'
  alias: IS THIS IT??
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.power_surplus_for_car
    for:
      hours: 0
      minutes: 0
      seconds: 0
  condition:
  - condition: numeric_state
    entity_id: sensor.battery_soc
    above: 85
  action:
  - variables:
      grid_usage: '{{ states(''sensor.power_surplus_for_car'') | float }}'
      max_charging_amps: 20
      min_charging_amps: 6
      grid_voltage: 240
  - choose:
    - conditions:
      - condition: template
        value_template: '{{ grid_usage > 0 }}'
      sequence:
      - service: number.set_value
        target:
          entity_id: number.wallbox_portal_max_charging_current
        data:
          value: '{% set charger_power_draw = (states(''number.wallbox_portal_max_charging_current'')
            | int) * grid_voltage %}  {% set charging_amps = ((grid_usage + charger_power_draw)
            / grid_voltage) | round(0, ''floor'') | int %} {{ [max_charging_amps,
            charging_amps] | min }}

            '
      - if:
        - condition: device
          type: is_off
          device_id: dd9d56ea186f6428125c912832e9ad9c
          entity_id: 047f7ff3b13da53c4f688733685ea22c
          domain: switch
        then:
        - type: turn_on
          device_id: dd9d56ea186f6428125c912832e9ad9c
          entity_id: 047f7ff3b13da53c4f688733685ea22c
          domain: switch
    - conditions:
      - condition: template
        value_template: '{{ grid_usage <= 0 }}'
      sequence:
      - service: number.set_value
        target:
          entity_id: number.wallbox_portal_max_charging_current
        data:
          value: '{% set deficit_watts = -1 * grid_usage %} {% set current_charging_amps
            = states(''number.wallbox_portal_max_charging_current'') | int %} {% set
            required_charging_amps = ((deficit_watts / grid_voltage) | round(0, ''ceil''))
            | int %} {% set updated_charging_amps = current_charging_amps - required_charging_amps
            %} {% if updated_charging_amps < min_charging_amps %} 1 {% else %} {{
            [max_charging_amps, updated_charging_amps] | min }} {% endif %}

            '
      - choose:
        - conditions:
          - condition: template
            value_template: '{{ states(''number.wallbox_portal_max_charging_current'')
              | int <= 1 }}'
          sequence:
          - type: turn_off
            device_id: dd9d56ea186f6428125c912832e9ad9c
            entity_id: 047f7ff3b13da53c4f688733685ea22c
            domain: switch
  mode: single

Hi,

Its kind of undoable to really debug your specific situation, but here’s a couple of semantic recommendations to make sure all code is run as intended.

  1. First of all, you can drop all the if-statements testing for switches to be off and then turn them on. If you just want to make sure they are on, set them on in the automation. If they are off, they will turn on. If they are already on, they will stay that way. This makes for far cleaner yaml code.

  2. Second, instead of using the long strings, use the home assistant entity reference for entity_id (i.e. switch.wallbox instead of 047f7ff3b13da53c4f688733685ea22c). This makes the automation much easier to understand.

  3. Drop the ‘for’-statement that contains the time in the trigger. When using state alone, any state change triggers the automation to run.

  4. Remove all the double single(!) quotations, so: ''sensor.power_surplus_for_car'' should be
    'sensor.power_surplus_for_car'

  5. Change all the single quotes to double quotes around the accolades, so: '{{ states(''sensor.power_surplus_for_car'') | float }}' should be "{{ states(''sensor.power_surplus_for_car'') | float }}"

  6. I see you’ve added the concepts of the surplus power calculation (which is a prerequisite to use only surplus power) and the variables. That’s good, as that was what you were missing before. Try to make sure that the sensor.power_surplus_for_car is working correctly, as this is the starting point and calculation base of your automation.

  7. Now as you can see, the automation starts running when there is surplus power. As I read it, it sets the power cap for your wallbox (number.wallbox_portal_max_charging_current). I haven’t checked, but I assume you’ve copied the set charging_amps calculation directly from my automation.

  8. The part where a renewed calculation takes place when grid_usage <= 0 looks incorrect. What this does is calculate a new power requirement based upon the knowledge of how much power is already drawn by the set number of amps. It looks at the variable min_charging_amps (which you’ve set to 6!!) to determine what the outcome is. You should read {% if updated_charging_amps < min_charging_amps %} as: "if my new power requirement (based upon available power and accounting for my actual current draw) is below the minimum current draw that my wallbox allows, do the following. In you’re script you’ve set that outcome to result in the number 1. So, there’s that… :wink: Set that to be 0.

Hey Dick.

Thanks so much for all that.

Thankfully all the semantic recommendations you recommended were already actioned before reading this post.

I can confirm that the sensor.power_surplus_for_car is working perfectly. I can also confirm that the renewed calculation also works very well, until the result is <6, which gives me a value of 1, and does not shut off the charger as intended. Yes, 6 amps is the lowest my charger is able to charge unfortunately. The reason this script is set to result in the number 1 is because I copy and pasted this directly from your automation and have no idea what I’m doing :smiley:

Appreciate all the help. Will continue to try and figure this out.

EDIT: Setting 1 to 0 results in the same result. When it should be turning off, the value shows 0/1, but does not turn it off. This is literally the only problem I am having.

Hey there, it’s been I while since I’ve read up on things. How are you progressing, did you get this to work already?

Hey @dikkertjedap. Thanks so much for keeping up with me.

I have settled on a messy but working implementation after several iterations. This one creates the least number of errors, can be contained in a single automation and does essentially exactly what I want it to. I will show it below for completions sake in case anyone comes across an identical use case.

- id: '1702182687920'
  alias: Car Charging
  description: ''
  trigger:
  - platform: state
    entity_id:
    - sensor.power_surplus_for_car
    for:
      hours: 0
      minutes: 0
      seconds: 0
  condition:
  - condition: or
    conditions:
    - condition: device
      type: is_on
      device_id: dd9d56ea186f6428125c912832e9ad9c
      entity_id: 047f7ff3b13da53c4f688733685ea22c
      domain: switch
    - condition: device
      type: is_off
      device_id: dd9d56ea186f6428125c912832e9ad9c
      entity_id: 047f7ff3b13da53c4f688733685ea22c
      domain: switch
  action:
  - variables:
      grid_usage: '{{ states(''sensor.power_surplus_for_car'') | float }}'
      max_charging_amps: 25
      min_charging_amps: 6
      grid_voltage: 240
  - choose:
    - conditions:
      - condition: and
        conditions:
        - condition: template
          value_template: '{{ grid_usage > 0 }}'
        - condition: numeric_state
          entity_id: sensor.battery_soc
          above: 94
      sequence:
      - service: number.set_value
        target:
          entity_id: number.wallbox_portal_max_charging_current
        data:
          value: '{% set charger_power_draw = (states(''number.wallbox_portal_max_charging_current'')
            | int) * grid_voltage %}  {% set charging_amps = ((grid_usage + charger_power_draw)
            / grid_voltage) | round(0, ''floor'') | int %} {{ [max_charging_amps,
            charging_amps] | min }}

            '
      - if:
        - condition: device
          type: is_off
          device_id: dd9d56ea186f6428125c912832e9ad9c
          entity_id: 047f7ff3b13da53c4f688733685ea22c
          domain: switch
        then:
        - type: turn_on
          device_id: dd9d56ea186f6428125c912832e9ad9c
          entity_id: 047f7ff3b13da53c4f688733685ea22c
          domain: switch
    - conditions:
      - condition: and
        conditions:
        - condition: template
          value_template: '{{ grid_usage <= 0 }}'
        - condition: numeric_state
          entity_id: sensor.battery_soc
          above: 94
      sequence:
      - service: number.set_value
        target:
          entity_id: number.wallbox_portal_max_charging_current
        data:
          value: '{% set deficit_watts = -1 * grid_usage %} {% set current_charging_amps
            = states(''number.wallbox_portal_max_charging_current'') | int %} {% set
            required_charging_amps = ((deficit_watts / grid_voltage) | round(0, ''ceil''))
            | int %} {% set updated_charging_amps = current_charging_amps - required_charging_amps
            %} {% if updated_charging_amps < min_charging_amps %} 6 {% else %} {{
            [max_charging_amps, updated_charging_amps] | min }} {% endif %}

            '
    - conditions:
      - condition: and
        conditions:
        - condition: numeric_state
          entity_id: sensor.battery_soc
          below: 95
        - condition: numeric_state
          entity_id: sensor.wallbox_portal_charging_power
          above: 0.1
      sequence:
      - type: turn_off
        device_id: dd9d56ea186f6428125c912832e9ad9c
        entity_id: 047f7ff3b13da53c4f688733685ea22c
        domain: switch
  mode: single
1 Like

Hey @xenoexclusive, I have been following your battle here and have a suggestion for you.

You seem to be using the instant power state (sensor.power_surplus_for_car) as a trigger for your charger.
However, in many cases, power supply companies bill in time slots. That is, they look at a particular time slot (every 30min in the case of smart meters in Victoria’s Powercor area of Australia) and calculate the differential of import and export. If the net is export, you are given credit at whatever rate you are credited, if the net is import you are charged.
You should investigate if your power bill is calculated like that, if it is, you should be able to improve your automation by calculating a simple time average of your sensor.power_surplus_for_car and use that as a trigger.
In my case, I create a filter sensor for my net consumption sensor that averages the values over the last 5 min.

#these filters power export to smooth it over 5 min to better match the meter intervals of 30 min

  - platform: filter
    name: "filtered power export"
    entity_id: sensor.ct1
    filters:
      - filter: time_simple_moving_average
        window_size: "00:05"
        precision: 0

This makes the net power equivalent to your ‘sensor.power_surplus_for_car’ change much slower, making the charger turn on or off (or change rate) less often. This setup is specially good to accomodate for clouds passing and peaks of sunshine which make the solar production oscillate enourmously. In any given interval there is times I will be exporting for some minutes, and then importing for some others, however by the end of 30 min period, it all balances out and should not have a net import.

I hope someone finds it useful.

Hey there :slight_smile: Thanks for your input.

I hope someone does find your addition useful! I have been thinking about something similar for some time as I occasionally worry if the drastic fluctuations in current could ever make my car battery unhappy. Thankfully your use case is not important to me as I am completely off-grid.

But I appreciate your response as I may find a use for this filter yet :slight_smile: