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