18 min read

How I Optimized Charging My EV Using Excess Solar Power with Home Assistant

TL;DR: How I transformed my basic EV charger into a smart one for just $6, enabling it to charge from excess solar and battery storage—skipping the need for a $600-$1200 aftermarket charger with fewer features and no battery support.

Overview

As a Tesla owner with an EG4 home solar setup, I wanted to optimize my charging process to make the most out of excess solar energy. I charge my vehicle with the mobile connector that came with my car. It lacks internet connectivity, cannot be programmed to start or stop charging remotely, and cannot dynamically adjust amperage based on the excess energy currently being harvested.

This inspired me to explore automation options for my EV charging, ultimately leading to the development of two automations in Home Assistant tailored to achieve my goals.

While I thoroughly enjoyed going down this rabbit hole, I’d not recommend it to others unless you really enjoy geeking out. Premium vertical solar integrations from Tesla, Span, Enphase, and aftermarket EV smart chargers like Emporia and Wallbox offer reliable and robust smart EV charging solutions compared to the custom setup I’m about to describe in detail. They're worth considering! While they do have some limitations, they provide a hassle-free experience. That said, I wanted to challenge myself to build a custom setup on the cheap and add some functionality they don’t support.


Table of Contents


Background

When I installed solar panels and batteries, I initially explored upgrading to a smart EV charger, such as the Emporia Level 2 EV Charger with PowerSmart Load Management or the Wallbox Pulsar Plus paired with the required Power Meter.

Both of these solutions can adjust the amperage they supply to an EV based on excess solar production. By dynamically reducing or increasing the charging rate, they ensure efficient use of available solar energy, minimize reliance on the grid, and help avoid overloading the system during periods of lower solar output.

However, their costs range from $600 to $1,200. Moreover, the Emporia charger depends on a cloud provider that could potentially shut down, leaving the unit unusable. Neither option offered integrations that consider home batteries, limiting sourcing options to grid-only, grid and photovoltaic (PV), or PV alone.

For me, it was important to account for both charging and discharging my home battery when charging my electric vehicle (EV). While I’m comfortable using some of my home battery’s stored energy to charge my EV, it's not efficient and I want to ensure there’s always enough reserve for essential household needs and emergencies.

While researching how to integrate my EV charger into my solar setup, I discovered that my inverter features a Smart Loads plug.

Smart Loads are loads that the user wants to intelligently, strategically, and automatically enable and disable for the purposes of Load Shedding and Power Shedding in order to maximize Time of Use savings, Off-Grid operation, or maximize or minimize power sold back to the grid.

Initially, it seemed like a promising solution, offering configurable thresholds and failover scenarios to manage how the grid, batteries, and PV supply power to the plug.

However, further research revealed Smart Loads to be buggy, with unclear logic and poor documentation. My conclusion was that the inverter’s hardware was originally designed as an input plug for generators, and later adapted via software for dual-purpose use as a power output—without fully addressing the hardware requirements for the additional functions that were added in software. I decided I wasn't comfortable relying on a hacky plug for my EV charging.

To address these challenges, I aimed to purpose build a solution using a combination of software tools and low-cost hardware components. The setup described below dynamically adjusts the Tesla’s charging amperage based on real-time solar production, home battery status, and my specific charging requirements.

I chose to use the ESPHome Tesla BLE integration, which communicates with the car via Bluetooth to adjust charging settings,  instead of the Tesla Fleet API for several reasons. By avoiding the Fleet API, I sidestepped potential rate limits and dependency on a hosted cloud service for real-time adjustments. I also anticipated Tesla introducing fees that would make the API prohibitively expensive, a prediction that has since proven accurate.

In a previous post, I explained how I set up ESPHome on an ESP32 chip and integrated it with my Tesla and Home Assistant to optimize solar charging.

In this post, I’ll guide you through automating the system using Home Assistant, ESPHome, Teslamate, Eclipse MQTT, and a EG4 solar inverter. I’ll also show you how to use a dashboard to monitor the automations and adjust parameters.

A quick note: if you decide to use this, proceed at your own risk—I make no guarantees about the reliability of my Rube Goldberg-style charging system.


Use Case Overview

The goal is to dynamically adjust the Tesla's charging amperage based on:

  • Excess Solar Production: Utilize surplus solar energy that would otherwise be stored in home batteries or exported to the grid.
  • Home Battery Status: Prioritize charging the home battery based on preferred battery reserves.
  • Car's Rated Battery Range: Adjust charging priority dynamically for daily use—lowering priority when the range exceeds 120 miles and raising it when the range drops below 99 miles.
  • Support for Manual Override: For long trips requiring a full charge, I need the option to activate a highly reliable “one-click” mode to charge the car at maximum amperage using the grid while setting a custom battery discharge threshold to prevent the home battery from depleting to its minimum allowed levels. Although this scenario is infrequent, it is crucial that, once activated, it runs uninterrupted by any other automations during the charging period.

Energy Flow and Efficiency Challenges

I quickly discovered that optimizing energy usage requires a basic understanding of the physics of electrical flow, particularly the interplay between DC (direct current) and AC (alternating current). Solar panels and home batteries typically operate on DC power, while most household appliances use AC. Converting between these forms introduces efficiency losses, and while my solar system can invert in one direction and convert in the other, it cannot perform both functions simultaneously.

In an ideal setup, I would have the flexibility to power my house with excess PV energy during the day while simultaneously charging my home battery and, when needed, discharging the battery to supply power to both my car and home concurrently. Unfortunately, I do not have that option.

To maximize energy efficiency and operate within physical constraints, I discovered that I need to carefully manage power flows, balancing the charging of the home and car batteries during the day with excess solar energy while reserving the battery to power the house at night.

A smart energy management system is essential to address these challenges. It optimizes energy efficiency, fulfills typical daily EV driving requirements, and ensures the home battery remains a dependable power source, reducing reliance on the grid. To achieve this, I developed a few automations.

About the Automations

  1. Charge Tesla from PV Excess (Automatic):
  • Purpose: Automatically harvest excess solar energy to charge the Tesla, suitable for daily driving needs.
  • Operation: This automation runs during daylight hours when solar production is sufficient, dynamically adjusting the EV charging rate based on real-time solar data, the car’s battery range, and home battery thresholds.
  • Priority: Lower priority; designed not to interfere with manual charging needs.
  1. Charge Tesla Without Draining Home Battery (Manual):
  • Purpose: Manually triggered to charge the Tesla at maximum power without fully depleting the home battery. Designed for infrequent usage when the car needs to be fully charged for a long road trip.
  • Operation: Temporarily overrides the PV excess automation to prioritize rapid charging, with an adjustable threshold for the battery’s discharge rate. This threshold ensures the home battery contributes without being fully drained and resets to its original setting afterward.
  • Priority: Higher priority; it can override the PV excess automation but not vice versa, preventing potential disruptions to important travel plans.

By implementing both automations, it's possible to:

  • Optimize Daily Charging: Make the most of solar energy for everyday driving.
  • Ensure Readiness for Trips: Quickly charge the car when needed without worrying about home battery levels.

Prerequisites

Brace yourself Jason. To set up this system, you'll need the following:

MQTT Broker Configuration

Since Home Assistant only allows one MQTT integration, I needed to bridge data from both the solar inverter dongle and Teslamate. I used Eclipse Mosquitto to handle this.

Update your Mosquitto configuration file at /usr/local/etc/mosquitto/mosquitto.conf:

# Default listener for authenticated clients (dongle)
listener 1883 0.0.0.0
allow_anonymous false
password_file /etc/mosquitto/passwd

# Listener for TeslaMate (no authentication required)
listener 1884 0.0.0.0
allow_anonymous true

# Persistence settings
persistence true
persistence_location /usr/local/var/lib/mosquitto/

# Logging settings
log_dest file /usr/local/var/log/mosquitto/mosquitto.log
log_type error
log_type warning
log_type notice
log_type information

# Bridge configuration to ingest TeslaMate data
connection teslamate_bridge
address tesla.yourdomain.com:1883
topic teslamate/# in

Note: Replace tesla.yourdomain.com with your actual Teslamate domain.

Key Details:

  • Multiple Listeners: Configured two listeners to handle authenticated (dongle) and unauthenticated (Teslamate) clients.
  • Bridging: The connection and topic settings bridge data from Teslamate into the MQTT broker.
  • Note: My Teslamate instance is running on my Tailscale network with no open ports, so I’m not using a password. However, you might want to consider setting one!

Home Assistant Configuration

All these YAML configuration files must be added to Home Assistant, and the system needs to be restarted for the changes to take effect.

Add to configuration.yaml

To keep things organized, I included separate YAML files for different configurations. These files will need to be created and live at the same level as your Home Assistant configuration.yaml.

input_number: !include tesla_charging.yaml
input_datetime: !include input_datetime.yaml
input_boolean: !include input_boolean.yaml

Creating Input Numbers for Thresholds

Create tesla_charging.yaml with the following content:

tesla_range_threshold:
  name: Tesla Range Threshold  
  min: 50                             
  max: 200                            
  step: 1                             
  unit_of_measurement: "mi"
  icon: mdi:car-battery        
  mode: box                           
  initial: 120                        

tesla_priority_range_threshold:
  name: Tesla Priority Range Threshold
  min: 50                          
  max: 200                         
  step: 1                          
  unit_of_measurement: "mi"
  icon: mdi:car-electric     
  mode: box                        
  initial: 99                      

tesla_high_battery_threshold:
  name: Home Battery High Threshold
  min: 0                          
  max: 100                        
  step: 1                         
  unit_of_measurement: "%"
  icon: mdi:battery-high    
  mode: box                       
  initial: 65                     

tesla_low_battery_threshold:
  name: Home Battery Low Threshold
  min: 0                  
  max: 100                
  step: 1                
  unit_of_measurement: "%"
  icon: mdi:battery-low   
  mode: box                    
  initial: 50                  

tesla_home_power_buffer:
  name: Home Power Buffer 
  min: 0                       
  max: 2000                       
  step: 100                       
  unit_of_measurement: "W"        
  icon: mdi:home-lightning-bolt
  mode: box                 
  initial: 500                    

tesla_validation_period:
  name: Validation Wait Time
  min: 1                
  max: 60                   
  step: 1                   
  unit_of_measurement: minutes
  icon: mdi:timer-outline
  mode: box    
  initial: 3  # Default wait time

Key Details:

  • Thresholds: Define customizable thresholds for car range, battery levels, and power buffer.
  • Adjustable Parameters: These inputs allow you to tweak the system behavior directly from a friendly dashboard in Home Assistant so you don't need to dive into code to modify the Charge Tesla from PV Excess automation business logic.

Input Datetime for Tracking

Used to store timestamps for when certain conditions are met.

Create input_datetime.yaml:

tesla_last_below_min_amps:
  name: Last Time Below Min Amps
  has_date: true
  has_time: true

Binary Sensors

Define sensors to monitor the charging status of the car and home battery.

Add to binary_sensor.yaml:

- platform: template
  sensors:
    tesla_charging_status:
      friendly_name: "EV Charging"
      value_template: >-
        {{ is_state('switch.tesla_ble_your_device_id_charger_switch', 'on') }}
      attribute_templates:
        power: >-
          {% if is_state('switch.tesla_ble_your_device_id_charger_switch', 'on') %}
            {% set amps = states('number.tesla_ble_your_device_id_charging_amps') | float %}
            {{ (amps * 240) | round }}
          {% else %}
            0
          {% endif %}

    home_battery_charging_status:
      friendly_name: "Battery Charging"
      value_template: >-
        {% set flow = states('sensor.dongle_device_id_batteryflow_live') | float %}
        {{ flow > 0 }}
      attribute_templates:
        power: >-
          {% set flow = states('sensor.dongle_device_id_batteryflow_live') | float %}
          {{ flow if flow > 0 else 0 }}

Note: To customize code on this page for your own use, you'll need to replace:

  • your_device_id with your Tesla BLE device ID (e.g., if your device ID is abc123, use tesla_ble_abc123_charging_amps)
  • dongle_device_id with your device ID (e.g., if your dongle ID is dongle-XX:XX:XX:XX:XX:XX, use dongle_XX_XX_XX_XX_XX_XX_soc)

Input Boolean for Manual Charging

To manually control the charging process and prevent automations from interfering.

Create input_boolean.yaml:

tesla_manual_charging_active:
  name: Tesla Manual Charging Active
  initial: off

The Automation Workflows

In Home Assistant, the following two automations run independently: one is manual, and the other is automatic. They are specifically designed to avoid interfering with each other, even if triggered simultaneously. Additionally, they include some mechanisms to recover and resume functionality if Home Assistant is interrupted or restarted.

Charge Tesla Without Draining Home Battery

This automation is triggered manually and is designed to ensure that charging the Tesla doesn't deplete the home battery below desired thresholds. It also includes recovery logic to reset settings if charging is interrupted. Currently, I’m allowing my batteries to help charge the car when I’m in a rush, though this may not be a long-term solution due to efficiency losses—around 3% for DC-to-DC conversion and another 10% for DC-to-AC. My reasoning for doing this now is that I installed the system in winter and won’t start earning credits by selling energy back until summer.

Business Logic

  • Manual Activation: This automation is manually triggered when you need to charge the car at maximum power, such as before a long trip.
  • Home Battery Protection:
    My home batteries are set to discharge to 10% when connected to the grid.
    • If the home battery's state of charge (SOC) is above 51%, it allows the battery to discharge down to 50%.
    • If the SOC is below 50%, it sets the end of discharge (EOD) to 50% to prevent the battery from draining further. When EV charging is complete the EOD is set back to 10%.
      • Note: I’ve heard that the chips in the EG4 inverter use NOR flash, which may have a write cycle lifespan of 10,000 to 100,000 cycles. To preserve their longevity, I’ve been cautious about minimizing the frequency of register writes.
  • Charging Process:
    • Wakes up the car and sets the charging amperage to 32A (maximum).
    • Turns on the charger switch to start charging.
  • Monitoring and Adjustment:
    • Checks if the charger is still on and adjusts the EOD based on the home battery SOC.
    • Stops when the car reaches its charging limit or if charging is manually stopped.
  • Recovery Logic:
    • If charging is interrupted, the automation resets the home battery EOD to its initial value.
    • Turns off the manual charging indicator to allow other automations to run when complete even if there's a reboot mid process.

Adjustable Parameters

  • Home Battery Thresholds:
    • Adjust tesla_high_battery_threshold and tesla_low_battery_threshold to set when the automation should adjust the home battery EOD.
  • Car Charging Limit:
    • Set via the car's settings; the automation respects the car's charging limit (e.g. 80%, etc.)

Automation YAML:

alias: Charge Tesla Don't Kill Home Battery
description: >
  Charges Tesla while managing home battery discharge levels with recovery logic.
  Ensures the home battery doesn't drain excessively during car charging.

trigger:
  - platform: event
    event_type: call_service
    event_data:
      domain: automation
      service: trigger
      service_data:
        entity_id: automation.charge_tesla_dont_kill_home_battery
  - platform: homeassistant
    event: start
  - platform: state
    entity_id: switch.tesla_ble_your_device_id_charger_switch
    to: "off"

condition:
  - condition: state
    entity_id: input_boolean.tesla_manual_charging_active
    state: "on"
  - condition: template
    value_template: >
      {{ (states('number.tesla_ble_your_device_id_battery_level') | float) <
      (states('number.tesla_ble_your_device_id_charging_limit') | float) }}

action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.tesla_manual_charging_active

  - service: switch.turn_on
    target:
      entity_id: switch.tesla_ble_your_device_id_ble_connection

  - delay: "00:00:01"

  - service: button.press
    target:
      entity_id: button.tesla_ble_your_device_id_wake_up

  - delay: "00:00:02"

  - service: number.set_value
    target:
      entity_id: number.tesla_ble_your_device_id_charging_amps
    data:
      value: 32

  - variables:
      initial_eod: "{{ states('number.dongle_device_id_eod') }}"

  - choose:
      - conditions:
          - condition: numeric_state
            entity_id: sensor.dongle_device_id_soc
            above: 51
        sequence:
          - service: number.set_value
            target:
              entity_id: number.dongle_device_id_eod
            data:
              value: 10
      - conditions:
          - condition: numeric_state
            entity_id: sensor.dongle_device_id_soc
            below: 50
        sequence:
          - service: number.set_value
            target:
              entity_id: number.dongle_device_id_eod
            data:
              value: 50

  - service: switch.turn_on
    target:
      entity_id: switch.tesla_ble_your_device_id_charger_switch

  - repeat:
      sequence:
        - condition: state
          entity_id: switch.tesla_ble_your_device_id_charger_switch
          state: "on"
        - choose:
            - conditions:
                - condition: numeric_state
                  entity_id: sensor.dongle_device_id_soc
                  below: 50
              sequence:
                - service: number.set_value
                  target:
                    entity_id: number.dongle_device_id_eod
                  data:
                    value: 50
        - delay: "00:00:30"
        - condition: template
          value_template: >
            {{ (states('number.tesla_ble_your_device_id_battery_level') | float) <
            (states('number.tesla_ble_your_device_id_charging_limit') | float) }}
      until:
        - condition: or
          conditions:
            - condition: template
              value_template: >
                {{ (states('number.tesla_ble_your_device_id_battery_level') | float) >=
                (states('number.tesla_ble_your_device_id_charging_limit') | float) }}
            - condition: state
              entity_id: switch.tesla_ble_your_device_id_charger_switch
              state: "off"

  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.tesla_manual_charging_active

  - service: number.set_value
    target:
      entity_id: number.dongle_device_id_eod
    data:
      value: "{{ initial_eod }}"

Note: Replace your_device_id with the name of your ESPHome device and dongle_device_id with the name of your dongle device macID.

Key Modifications and Choices:

  • Manual Charging Indicator: Uses input_boolean.tesla_manual_charging_active to prevent other automations from interfering.
  • Recovery Logic: If charging is interrupted (e.g., charger switch turns off), the automation resets the home battery's EOD to its initial value.
  • Dynamic EOD Adjustment: Adjusts the home battery EOD based on its current SOC.
  • Repeat Loop with Exit Conditions: Continues to adjust EOD and monitor charging until the car is fully charged or charging stops.

Charge Tesla from PV Excess

This automation is triggered automatically and is designed to dynamically adjust the Tesla's charging amperage based on excess solar production and the state of the home battery.

Business Logic

  • Automatic Operation: Runs automatically during daylight hours when the car is plugged in and excess solar power is available.
  • Charging Logic:
    • Car Range Priority:
      • If car range is below the priority threshold (e.g., 99 miles), prioritize charging the car with up to 95% of the excess solar power.
      • If car range is between the priority threshold and the general threshold (e.g., 99–120 miles), allocate 50–70% of excess power to the car based on home battery SOC.
      • If car range is above the general threshold (e.g., above 120 miles), allocate less power to the car, prioritizing the home battery.
    • Home Battery SOC:
      • Adjusts the allocation of excess power based on the home battery's SOC.
      • If the home battery is low, more power is allocated to it; if it's high, more power can go to the car.
  • Amperage Calculation:
    • Calculates the available amperage for charging based on excess solar power.
    • Applies a safety limit to ensure the amperage stays between 0 and 32A.
  • Validation Period:
    • Includes a validation period (e.g., 3 minutes) to ensure that once the amperage falls to zero it is sustainable before attempting re-engage the charger.
    • This is one of the few stopgaps to prevent frequent on/off cycles that could lead to vampire drain.

Adjustable Parameters

  • Thresholds:
    • tesla_range_threshold: Above this range, car charging is deprioritized.
    • tesla_priority_range_threshold: Below this range, car charging is prioritized.
    • tesla_high_battery_threshold and tesla_low_battery_threshold: Define the SOC levels for the home battery to adjust power allocation.
  • Home Power Buffer:
    • tesla_home_power_buffer: Watts reserved to account for rapid fluctuations in home consumption.
  • Validation Period:
    • tesla_validation_period: Time in minutes that the amperage must be above the minimum sustainable level (e.g. zero) before starting charging.

Automation YAML:

alias: 🔌 Charge Tesla From PV Excess
description: >
  Optimizes Tesla charging based on solar production and home battery state.
  Includes a validation period to prevent frequent adjustments.

trigger:
  - platform: state
    entity_id: binary_sensor.tesla_plugged_in
    to: "on"
  - platform: time_pattern
    minutes: "/1"

condition:
  - condition: state
    entity_id: input_boolean.tesla_manual_charging_active
    state: "off"
  - condition: state
    entity_id: binary_sensor.tesla_plugged_in
    state: "on"
  - condition: template
    value_template: >
      {{ states('number.tesla_ble_your_device_id_battery_level') | float < 
      states('number.tesla_ble_your_device_id_charging_limit') | float }}
  - condition: numeric_state
    entity_id: sensor.dongle_device_id_pall
    above: 1000
  - condition: sun
    before: sunset
    after: sunrise
  - condition: template
    value_template: >
      {% set solar_production = states('sensor.dongle_device_id_pall') | float %}
      {% set home_consumption = states('sensor.dongle_device_id_pload') | float %}
      {% set home_buffer_watts = states('input_number.tesla_home_power_buffer') | float %}
      {% set solar_excess = solar_production - home_buffer_watts - home_consumption %}
      {{ solar_excess > 0 }}

action:
  - variables:
      calculated_amps: >
        {# Retrieve thresholds #}
        {% set range_threshold = states('input_number.tesla_range_threshold') | float %}
        {% set priority_range_threshold = states('input_number.tesla_priority_range_threshold') | float %}
        {% set high_battery_threshold = states('input_number.tesla_high_battery_threshold') | float %}
        {% set low_battery_threshold = states('input_number.tesla_low_battery_threshold') | float %}
        {% set home_buffer_watts = states('input_number.tesla_home_power_buffer') | float %}

        {# Calculate available power #}
        {% set solar_production = states('sensor.dongle_device_id_pall') | float %}
        {% set home_consumption = states('sensor.dongle_device_id_pload') | float %}
        {% set solar_excess = solar_production - home_buffer_watts - home_consumption %}
        {% set excess_pv_energy = solar_excess / 240 %}
        {% set car_range = states('sensor.tesla_rated_battery_range_mi') | float %}
        {% set house_battery_soc = states('sensor.dongle_device_id_soc') | float %}

        {# Charging logic #}
        {% if car_range < range_threshold %}
          {# Below range threshold #}
          {% if car_range < priority_range_threshold %}
            {% set max_amps = (excess_pv_energy * 0.95) | int %}
          {% elif house_battery_soc > high_battery_threshold %}
            {% set max_amps = (excess_pv_energy * 0.70) | int %}
          {% else %}
            {% set max_amps = (excess_pv_energy * 0.50) | int %}
          {% endif %}
        {% else %}
          {# Above range threshold #}
          {% if house_battery_soc < low_battery_threshold %}
            {% set max_amps = (excess_pv_energy * 0.30) | int %}
          {% elif house_battery_soc < high_battery_threshold %}
            {% set max_amps = (excess_pv_energy * 0.40) | int %}
          {% else %}
            {% set max_amps = (excess_pv_energy * 0.60) | int %}
          {% endif %}
        {% endif %}

        {# Safety limits #}
        {% if max_amps > 32 %}32{% elif max_amps < 0 %}0{% else %}{{ max_amps }}{% endif %}

  - condition: template
    value_template: >
      {% set current_time = now() %}
      {% set last_below_min = states('input_datetime.tesla_last_below_min_amps') %}
      {% set validation_period_minutes = states('input_number.tesla_validation_period') | float %}
      {% set has_sufficient_amps = calculated_amps | int >= min_sustainable_amps %}

      {% if has_sufficient_amps %}
        {% if not last_below_min %}
          true
        {% else %}
          {% set time_diff = (as_timestamp(current_time) - as_timestamp(last_below_min)) / 60 %}
          {{ time_diff >= validation_period_minutes }}
        {% endif %}
      {% else %}
        false
      {% endif %}

  - choose:
      - conditions:
          - condition: template
            value_template: "{{ calculated_amps | int < min_sustainable_amps }}"
        sequence:
          - service: input_datetime.set_datetime
            target:
              entity_id: input_datetime.tesla_last_below_min_amps
            data:
              timestamp: "{{ now().timestamp() }}"

  - choose:
      - conditions:
          - condition: template
            value_template: "{{ calculated_amps | int >= min_sustainable_amps }}"
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.tesla_ble_your_device_id_ble_connection

          - delay: "00:00:01"

          - service: button.press
            target:
              entity_id: button.tesla_ble_your_device_id_wake_up

          - delay: "00:00:02"

          - service: number.set_value
            target:
              entity_id: number.tesla_ble_your_device_id_charging_amps
            data:
              value: "{{ calculated_amps }}"

          - service: switch.turn_on
            target:
              entity_id: switch.tesla_ble_your_device_id_charger_switch

      - conditions:
          - condition: template
            value_template: "{{ calculated_amps | int < min_sustainable_amps }}"
          - condition: state
            entity_id: switch.tesla_ble_your_device_id_charger_switch
            state: "on"
        sequence:
          - service: switch.turn_off
            target:
              entity_id: switch.tesla_ble_your_device_id_charger_switch

variables:
  min_sustainable_amps: 1

mode: single
max_exceeded: silent

Note: Replace your_device_id with the name of your ESPHome device and dongle_device_id with the name of your dongle device macID.

Key Features:

  • Dynamic Amperage Calculation: Adjusts charging amperage based on solar excess, car range, and battery SOC.
  • Validation Period: Includes a validation period (adjustable via the dashboard) to ensure the charging current remains above a minimum sustainable level before re-engaging the charger.
  • Priority Logic: Prioritizes car charging or home battery charging based on defined thresholds.
  • Safety Checks: Ensures amperage stays within safe limits (1–32 A).

Key Modifications and Choices:

  • Avoiding Frequent BLE Communication: The validation period attempts to reduces the frequency of Bluetooth communications with the car, which can be resource-intensive.
  • Manual Charging Override: Checks if manual charging is active to prevent automation conflicts.
  • Use of Variables: Improves readability and maintainability by using variables for thresholds and calculations.

Custom Dashboard Setup

I created a custom dashboard in Home Assistant to monitor and adjust the system parameters.

When the "Charge Tesla from PV Excess" automation is running I have some visibility into what's happening and why. And, I can see if the Manual grid charging automation is running.
I've exposed most of the parameters in the "Charge Tesla from PV Excess" automation so that I can easily tweak them
To make it easy to fast charge from the grid I added a "button" to manually trigger the automation to "Charge Tesla Without Draining Home Battery" and added a shortcut on my phone

Dashboard YAML Configuration:

title: Tesla PV Charging
views:
  - title: Main
    path: main
    badges: []
    cards:
      - type: markdown
        style: |
          ha-card {
            height: auto;
            min-height: 100px;
            padding: 16px;
            margin: 8px;
            background: var(--card-background-color);
            opacity: 0;
            animation: fadeIn 0.5s ease-in forwards;
            animation-delay: 0.5s;
          }
          @keyframes fadeIn {
            to {
              opacity: 1;
            }
          }
          ha-markdown {
            font-size: 14px !important;
            line-height: 1.8 !important;
            min-height: 400px;
          }
        content: |-
          {% if states('sensor.tesla_rated_battery_range_mi') != 'unavailable' 
             and states('sensor.dongle_device_id_soc') != 'unavailable' %}
            {% set range_threshold = states('input_number.tesla_range_threshold') | float(120) %}
            {% set priority_range_threshold = states('input_number.tesla_priority_range_threshold') | float(100) %}
            {% set high_battery_threshold = states('input_number.tesla_high_battery_threshold') | float(80) %}
            {% set low_battery_threshold = states('input_number.tesla_low_battery_threshold') | float(20) %}
            {% set solar_production = states('sensor.dongle_device_id_pall') | float(0) %}
            {% set home_consumption = states('sensor.dongle_device_id_pload') | float(0) %}
            {% set home_buffer_watts = states('input_number.tesla_home_power_buffer') | float(500) %}
            {% set solar_excess = solar_production - home_buffer_watts - home_consumption %}
            {% set excess_pv_energy = solar_excess / 240 %}
            {% set car_range = states('sensor.tesla_rated_battery_range_mi') | float(0) %}
            {% set house_battery_soc = states('sensor.dongle_device_id_soc') | float(0) %}
            {% set actual_charging_current = states('sensor.tesla_charger_actual_current') | float(0) %}
            đźš™ Range: {{ car_range | round(1) }} mi
            🔋 Added: {{ states('sensor.tesla_charge_energy_added') | float(0) | round(1) }} kWh{% if car_range < priority_range_threshold %}
            ⚠️ Car: Priority Charging needed (below {{ priority_range_threshold }}mi){%- elif car_range < range_threshold -%}
            đź“Š Car: Normal Charging mode (below {{ range_threshold }}mi){% else %}
            â›˝ Car: Conservative Charging (above {{ range_threshold }}mi){% endif %}
            đźš— Set Amps: {{ states('number.tesla_ble_your_device_id_charging_amps') | float(0) }}A{% set manual_charging = is_state('input_boolean.tesla_manual_charging_active', 'on') %}
            ⚡ Manual Grid Charging: {{ "On" if manual_charging else "Off" }}
            đź‘€ EV Charging: {% if actual_charging_current > 0 %}Yes {{ (actual_charging_current * 240) | round }}W{% else %}No{% endif %}

            🏡 Battery: {{ house_battery_soc | round(1) }}%
            🪫 Discharge Level: {{ states('number.dongle_device_id_eod') | float(10) }}%{% if house_battery_soc < low_battery_threshold %}
            ⚠️ House: Prioritizing Home Battery (below {{ low_battery_threshold }}%){% elif house_battery_soc < high_battery_threshold %}
            đź“Š House: Moderate Charging mode (below {{ high_battery_threshold }}%){% else %}
            âś… House: Increased Car Charging enabled (above {{ high_battery_threshold }}%){% endif %}
            đź‘€ Battery Charging: {% if is_state('binary_sensor.home_battery_charging_status', 'on') %}Yes {{ state_attr('binary_sensor.home_battery_charging_status', 'power') | float(0) | round }}W{% else %}No{% endif %}

            🌞 Harvest: {{ solar_production | round }}W
            🏠 Use: {{ home_consumption | round }}W
            🛟 Buffer: {{ home_buffer_watts | round }}W
            âž• Excess: {{ solar_excess | round }}W
            🔌 Available for car: {{ excess_pv_energy | round(1) }}A
          {% else %}
            Loading data...
          {% endif %}
      - type: entities
        title: System Thresholds
        entities:
          - entity: input_number.tesla_range_threshold
            name: Range Threshold
          - entity: input_number.tesla_priority_range_threshold
            name: Priority Range
          - entity: input_number.tesla_high_battery_threshold
            name: High Battery
          - entity: input_number.tesla_low_battery_threshold
            name: Low Battery
          - entity: input_number.tesla_home_power_buffer
            name: Home Power Buffer
          - entity: input_number.tesla_validation_period
            name: Wait Time >= 1A
            control_mode: box
      - type: entities
        entities:
          - type: button
            name: 🔌 Tesla 🚙 Don't Kill 🏡 🔋
            icon: mdi:car-electric-outline
            tap_action:
              action: call-service
              service: automation.trigger
              target:
                entity_id: automation.charge_tesla_dont_kill_home_battery
      - type: horizontal-stack
        cards:
          - type: gauge
            name: Car Range
            entity: sensor.tesla_rated_battery_range_mi
            min: 0
            max: 300
            severity:
              green: 120
              yellow: 99
              red: 0
          - type: gauge
            name: Home Battery
            entity: sensor.dongle_device_id_soc
            unit: '%'
            min: 0
            max: 100
            severity:
              green: 65
              yellow: 50
              red: 0
      - type: history-graph
        title: Power Distribution (24h)
        hours_to_show: 24
        entities:
          - entity: sensor.dongle_device_id_pall
            name: Solar Production
          - entity: sensor.dongle_device_id_pload
            name: Home Consumption
          - entity: number.tesla_ble_your_device_id_charging_amps
            name: Car Charging Rate

Key Features:

  • Dynamic Data Display: The markdown card shows real-time data and status messages.
  • Adjustable Thresholds: Entities card allows you to adjust system thresholds on the fly.
  • Manual Control: A button to manually trigger the "Charge Tesla Without Draining Home Battery" automation.
  • Visual Gauges: Quick view of car range and home battery status.
  • Historical Graph: Monitor power distribution over the last 24 hours.

Note: Replace your_device_id and dongle_device_id with your actual device IDs in the dashboard configuration.

Conclusion

By integrating data into Home Assistant via MQTT from Teslamate, my solar inverter using the Monitor My Solar dongle, and my Tesla using an ESP32 chip running ESPHome Tesla BLE, I created a dynamic system that optimizes Tesla charging based on real-time solar production and home energy demands.

The customizable dashboard provides monitoring and adjustment of parameters, ensuring efficient management of both the car and home battery. Additionally, when rapid EV charging is needed, the system avoids depleting the home battery.

Although I learned a lot and already had many prerequisites in place, which made the configuration relatively quick, I find the setup fragile and likely to be annoying to maintain. I plan to keep it running and hope it won’t cause too many headaches. However, I intend to be cautious about investing significant additional effort into optimizing it unless the improvements are clearly worthwhile.

Benefits of this setup:

  • Energy Efficiency: Maximizes the use of excess solar energy.
  • Cost Savings: Reduces reliance on grid electricity, lowering energy bills.
  • Environmental Impact: Enhances the use of renewable energy sources.
  • Affordability: Extremely budget-friendly, with the ESP32 chip costing $6.
  • Customization and Control: Allows you to control the logic and easily tweak the setup to meet specific needs.

I’d love to hear your thoughts, feedback, or experiences if you actually try using this approach. Hopefully, we’ll see simpler aftermarket solutions from vehicle manufacturers integrating this functionality into their chargers in an open and accessible way.

Happy charging!