Tesla Car and Tesla Powerwall - charge on solar - Via Tessie integration

As Tesla don’t activate the charge on solar feature when the grid provider requites a power output limit (In my case 10kw limit), I needed another way to charge on solar using HA.

With the help of chatGPT have come up with this semi workable solution that looks at grid export at the time and sends spare power to car.

Keen for feedback and maybe even human refinements to make it better. Please let me know thoughts and if there is anyway to improve.

One issue I do seem to have is it doesnt ramp down enough and cuts the power off to the car too early.

For me this utilizes the tessie integration to connect to the car and powerwall

blueprint:
  name: Tesla Charging — Excess Solar Only (Tessie, kW) v7c (Enable + SOC cap + sleep-friendly + wait-for-charging)
  description: >
    Starts charging when you have excess solar (kW) and stops when you don't,
    with Tessie entities. Adds: master enable toggle, SOC cap (no commands at/above
    threshold), sleep-friendly behavior (only set amps while actually Charging),
    case-insensitive state handling, and a short wait-after-start to adjust amps
    once charging actually begins.
  domain: automation
  input:
    enable_helper:
      name: Enable toggle (input_boolean)
      description: Master ON/OFF for this automation.
      selector:
        entity:
          domain: input_boolean

    grid_power:
      name: Grid Power (kW)
      description: >
        Site grid power, in kW (signed). If your sensor reports export as negative,
        leave "Export is Positive?" OFF. If it reports export as positive, turn it ON.
      selector:
        entity:
          domain: sensor

    export_positive:
      name: Export is Positive?
      default: false
      selector:
        boolean: {}

    car_charge_switch:
      name: Car Charge Switch
      description: e.g. switch.tessie_charge
      selector:
        entity:
          domain: switch

    car_amps_number:
      name: Car Charge Current (A)
      description: e.g. number.tessie_charge_current
      selector:
        entity:
          domain: number

    car_state_sensor:
      name: Car Charging State
      description: e.g. sensor.tessie_charging (Charging/Stopped/etc.)
      selector:
        entity:
          domain: sensor

    supply_v:
      name: Supply Voltage (V)
      default: 230
      selector:
        number:
          min: 100
          max: 260
          step: 1
          mode: box

    start_excess_kw:
      name: Start when excess ≥ (kW)
      description: Minimum sustained export to begin
      default: 1.2
      selector:
        number:
          min: 0
          max: 50
          step: 0.1
          unit_of_measurement: kW

    stop_excess_kw:
      name: Stop when excess ≤ (kW)
      description: Stop when export drops to/below this
      default: 0.2
      selector:
        number:
          min: 0
          max: 50
          step: 0.1
          unit_of_measurement: kW

    stop_grace_sec:
      name: Stop grace (seconds)
      description: Wait this long below the stop threshold before stopping
      default: 30
      selector:
        number:
          min: 0
          max: 600
          step: 1
          mode: box

    min_amps:
      name: Minimum Charge Amps
      default: 6
      selector:
        number:
          min: 0
          max: 600
          step: 1
          mode: box

    max_amps:
      name: Maximum Charge Amps
      default: 32
      selector:
        number:
          min: 5
          max: 32
          step: 1
          mode: box

    min_change_amps:
      name: Minimum change in amps to send
      description: Only send if desired amps differs by at least this amount.
      default: 1
      selector:
        number:
          min: 1
          max: 10
          step: 1
          mode: box

    soc_sensor:
      name: Battery SOC sensor (%)
      description: e.g. sensor.tessie_battery_level
      selector:
        entity:
          domain: sensor

    soc_threshold:
      name: SOC threshold (%)
      description: Start when below; stop sending rate commands when at/above.
      default: 78
      selector:
        number:
          min: 50
          max: 100
          step: 1
          unit_of_measurement: '%'

    debug_logging:
      name: Debug logging to Logbook
      default: true
      selector:
        boolean: {}

mode: restart

trigger:
  # Re-evaluate when export or state changes
  - platform: state
    entity_id: !input grid_power
    for: "00:00:10"
  - platform: state
    entity_id: !input car_state_sensor
    for: "00:00:10"
  - platform: state
    entity_id: !input soc_sensor
    for: "00:00:10"
  # Low-rate heartbeat
  - platform: time_pattern
    minutes: "/2"

variables:
  enable_helper_entity: !input enable_helper
  grid_power_entity: !input grid_power
  car_state_sensor_entity: !input car_state_sensor
  car_amps_number_entity: !input car_amps_number
  car_charge_switch_entity: !input car_charge_switch
  export_positive_var: !input export_positive
  start_excess_kw_var: !input start_excess_kw
  stop_excess_kw_var: !input stop_excess_kw
  stop_grace_sec_var: !input stop_grace_sec
  min_amps_var: !input min_amps
  max_amps_var: !input max_amps
  supply_v_var: !input supply_v
  debug_logging_var: !input debug_logging
  soc_entity: !input soc_sensor
  soc_limit: !input soc_threshold
  min_change_var: !input min_change_amps

  enabled: "{{ is_state(enable_helper_entity, 'on') }}"
  soc: "{{ states(soc_entity) | float(0) }}"
  car_state_raw: "{{ states(car_state_sensor_entity) | string }}"
  car_state: "{{ car_state_raw | lower }}"

  excess_kw: >
    {% set g = states(grid_power_entity) | float(0) %}
    {% if export_positive_var %}
      {{ g if g > 0 else 0 }}
    {% else %}
      {{ (0 - g) if g < 0 else 0 }}
    {% endif %}

  # Gate start by enable + SOC
  want_on: >
    {{ enabled and (soc < soc_limit) and (excess_kw >= start_excess_kw_var) }}

  want_off: >
    {{ excess_kw <= stop_excess_kw_var }}

condition:
  # Master enable; if off, do nothing at all (no start/stop/adjust)
  - condition: template
    value_template: "{{ enabled }}"

action:
  - choose:
      - conditions: "{{ want_on }}"
        sequence:
          - if:
              - condition: template
                value_template: "{{ debug_logging_var }}"
            then:
              - service: logbook.log
                data:
                  name: "Tesla Excess"
                  message: "Excess available: {{ excess_kw }} kW (SOC {{ soc }}% < {{ soc_limit }}%, state '{{ car_state_raw }}') → turn on / adjust."
                  domain: automation
          # Start charging if stopped/idle/complete/disconnected (case-insensitive)
          - if:
              - condition: template
                value_template: >
                  {{ car_state in ['stopped','idle','complete','disconnected'] }}
            then:
              - service: switch.turn_on
                target:
                  entity_id: !input car_charge_switch
              - if:
                  - condition: template
                    value_template: "{{ debug_logging_var }}"
                then:
                  - service: logbook.log
                    data:
                      name: "Tesla Excess"
                      message: "Issued start charge."
                      domain: automation
              # Wait up to 90s for charging to commence before setting amps
              - wait_template: "{{ (states(car_state_sensor_entity) | lower) == 'charging' }}"
                timeout: "00:01:30"
                continue_on_timeout: true
              - if:
                  - condition: template
                    value_template: "{{ debug_logging_var }}"
                then:
                  - service: logbook.log
                    data:
                      name: "Tesla Excess"
                      message: "Post-start state: '{{ states(car_state_sensor_entity) }}'."
                      domain: automation

          # Compute target amps from export
          - variables:
              target_amps: >
                {% set watts = (excess_kw * 1000) %}
                {% set amps = (watts / supply_v_var) | round(0, 'floor') %}
                {% set amps = [amps, min_amps_var] | max %}
                {% set amps = [amps, max_amps_var] | min %}
                {{ amps }}
          # Only send while actually Charging or Starting (case-insensitive), SOC below cap, and if the change is meaningful
          - if:
              - condition: template
                value_template: "{{ car_state in ['charging','starting'] }}"
              - condition: template
                value_template: "{{ soc < soc_limit }}"
              - condition: template
                value_template: >
                  {% set current = states(car_amps_number_entity) | float(0) %}
                  {{ (target_amps - current) | abs >= min_change_var }}
            then:
              - service: number.set_value
                target:
                  entity_id: !input car_amps_number
                data:
                  value: "{{ target_amps }}"
              - if:
                  - condition: template
                    value_template: "{{ debug_logging_var }}"
                then:
                  - service: logbook.log
                    data:
                      name: "Tesla Excess"
                      message: "Set {{ target_amps }} A for {{ excess_kw }} kW (SOC {{ soc }}%, state '{{ car_state_raw }}')."
                      domain: automation
              - delay: "00:00:15"
          - if:
              - condition: template
                value_template: "{{ debug_logging_var }}"
            then:
              - service: logbook.log
                data:
                  name: "Tesla Excess"
                  message: "Post-check: target={{ target_amps }}A, current={{ states(car_amps_number_entity) }}, state='{{ car_state_raw }}', SOC={{ soc }}%, excess={{ excess_kw }} kW"
                  domain: automation

      - conditions: "{{ want_off }}"
        sequence:
          - if:
              - condition: state
                entity_id: !input car_charge_switch
                state: "on"
            then:
              - if:
                  - condition: template
                    value_template: "{{ debug_logging_var }}"
                then:
                  - service: logbook.log
                    data:
                      name: "Tesla Excess"
                      message: "Excess fell ({{ excess_kw }} kW ≤ {{ stop_excess_kw_var }} kW), waiting {{ stop_grace_sec_var }}s before stop."
                      domain: automation
              - delay:
                  seconds: "{{ stop_grace_sec_var | int }}"
              - condition: template
                value_template: "{{ excess_kw <= stop_excess_kw_var }}"
              - service: switch.turn_off
                target:
                  entity_id: !input car_charge_switch
              - if:
                  - condition: template
                    value_template: "{{ debug_logging_var }}"
                then:
                  - service: logbook.log
                    data:
                      name: "Tesla Excess"
                      message: "Stopped (excess {{ excess_kw }} kW ≤ {{ stop_excess_kw_var }} kW)."
                      domain: automation

Hello Tes8080,

Thanks for contributing to the community with a new Blueprint.
I have a suggestion for you. Many people who are not familiar with directory structures will have problems installing this without the Home Assistant MY tools.
Adding a MY link for this Blueprint to your top post would help them a lot.
Here is the link to make that.
Create a link – My Home Assistant

Additionally being able to understand and maintain the code you supply is very important. Relying on AI for your own use is one thing, but sharing AI stuff with others is a whole different problem and may go counter to site rules. Makes sure you can actually help someone if this breaks. As a Blueprint contributor, let me tell you, things in HA do change and these do break. This in particular is all written in the legacy code format, and might be OK, but is already 6 months behind. Not sure if you realized that. This is the case with nearly all LLM written stuff because that is the data they were trained on.