Charging an EV using Solar

I recently got an EV and wanted to setup a way to charge it using the excess solar power that my house generates. It should charge faster when there is excess power and it should suspend charging when the house is importing from the grid. I should also be able to manually override it and tell it to charge as fast as possible.

Supplies

For the hardware, I have an Autel MaxiCharger AC Elite (50A 240V, ~12kw), a Ford Mach-E (70kwh battery, ~10kw level 2 charge rate), and an Enphase-based Solar array.

For the software, I explored evcc, which I think worked but I didn't want to either pay $150 or have to regenerate a limited time token every 2 weeks. Instead, I used Home Assistant which also brings the ability to tie in data from other sources, and build nice dashboards.

Configuration

I added HACS to Home Assistant, which gave me access to the Open Charge Point Protocol (OCPP) plugin as well as the FordPass plugin.

Starting with the OCPP plugin, you setup a "central" entry with some minimal configuration:

Configuring the Autel to use the Home Assistant's OCPP requires I connect to it via bluetooth with the phone app. Also, switching to your own OCPP server breaks the Autel app unless you're connected via bluetooth. If you want their app back, you can switch back to the "Autel Cloud" OCPP server.

Change the OCPP server in the app
create a custom OCPP server and point it at your home assistant

For the ws:// protocol, you don't need a certificate. The "Charger ID" is whatever you want to name it. Once you've connected the charger to HA's OCPP, it should show up as a device:

The documentation for the HA OCPP plugin mentions a warning: be careful about what value you're changing when setting the charge rate, if it's a persistent setting it will get written to flash which has a limited number of write cycles. This could lead to bricking your EVSE quicker than you might expect. Instead, change the value on the current active charger transaction, which shouldn't require a write to flash.

Next, configure your solar and car data. The documentation for these are good, so I'll skip going into detail on how to set them up.

Dashboard: Energy

The built-in energy dashboard for Home Assistant is pretty cool, and has a bunch of great data:

Power Usage Summary
Hourly breakdown
Breakdown of where energy went

Dashboard: Charger

But I also made my own dashboard of the relevant data:

The "Charger Control" section provides a way to change the speed of the charger, as well as a way to turn on the solar charger automation.

The "Charge Rate", "Set Rate", "Automation Setting", and "Use Solar Charger Automation" are all HA "helpers", which provide inputs:

Settings > Devices & Services > Helpers

Automation: manual set

The Set Rate button (ev_charger_amps_go) triggers this automation:

alias: Manual Set Charger
description: ""
triggers:
  - trigger: state
    entity_id:
      - input_button.ev_charger_amps_go
conditions:
  - condition: device
    type: is_on
    device_id: ad1a433e39bcebccb12910f27a78d79a
    entity_id: 3ec7dfadb4203d450627024c805bae4a
    domain: switch
actions:
  - action: ocpp.set_charge_rate
    metadata: {}
    data:
      custom_profile:
        transactionId: "{{ states('sensor.charger_transaction_id') | int }}"
        chargingProfileId: 3001
        stackLevel: 19
        chargingProfilePurpose: TxProfile
        chargingProfileKind: Absolute
        chargingSchedule:
          startSchedule: "2026-04-17T16:01:45.000Z"
          chargingRateUnit: A
          chargingSchedulePeriod:
            - startPeriod: 0
              limit: "{{ states('input_number.ev_charger_amps') | int }}"
      conn_id: 1
mode: single

This uses the "Charge Rate" (ev_charger_amps) input to manually set the charger. The Autel does not accept Relative changes, so all my automation uses Absolute settings. It accepts both watts (W) and amps (A) for units, and I'm using amps. I have the default setting to be full power (50A) or otherwise it will refuse to go above that limit. The start schedule time is a random date in the past, which the EVSE requires but doesn't seem to mind. The condition is testing if the EVSE "charger Charge Control" is on.

I'm using ocpp.set_charge_rate to set the value on the charge transaction rather than the default charge setting in the hopes that will avoid wearing out the flash in my EVSE. Also, multiple pieces of OCPP software have 32A hard-coded as the maximum possible charge rate. Using set_charge_rate gets around that limitation.

Automation: Calculate Charge Rate

Next up, an automation to calculate how many amps should the charger supply based on the net grid import/export:

alias: Solar to Charger
description: ""
triggers:
  - trigger: state
    entity_id:
      - sensor.envoy_1234_current_net_power_consumption
conditions: []
actions:
  - action: input_number.set_value
    metadata: {}
    target:
      entity_id: input_number.ev_charger_amps_set
    data:
      value: >
        {% set amps = min(31, int(int(states('input_number.ev_charger_amps')) -
        float(states('sensor.envoy_482518015855_current_net_power_consumption'))
        * 1000 / 240)) %} {% if amps <= 6 %}
          6
        {% else %}
          {{ amps }}
        {% endif %}
mode: single

Every time the Enphase system is polled for the current net power consumption (~1 minute), this automation converts the net kw into net amps (at 240V), and subtracts that from the current amps. It clamps the max to 31A and minimum to 6A. Below 6A, the EVSE suspends the charger session. Above 31A is more than my solar panels can produce. This value is stored in "Automation Setting" (ev_charger_amps_set).

Automation: Apply Charge Rate

There's another automation that watches changes to this value and sets the current transaction's amp limit if "Use Solar Charger Automation" is turned on:

alias: Automated Set Charger
description: Automated Solar Car Charging
triggers:
  - trigger: state
    entity_id:
      - input_number.ev_charger_amps_set
conditions:
  - condition: state
    entity_id: input_boolean.ev_charger_solar
    state:
      - "on"
  - condition: device
    type: is_on
    device_id: ad1a433e39bcebccb12910f27a78d79a
    entity_id: 3ec7dfadb4203d450627024c805bae4a
    domain: switch
actions:
  - action: ocpp.set_charge_rate
    metadata: {}
    data:
      custom_profile:
        transactionId: "{{ states('sensor.charger_transaction_id') | int }}"
        chargingProfileId: 3001
        stackLevel: 19
        chargingProfilePurpose: TxProfile
        chargingProfileKind: Absolute
        chargingSchedule:
          startSchedule: "2026-04-17T16:01:45.000Z"
          chargingRateUnit: A
          chargingSchedulePeriod:
            - startPeriod: 0
              limit: "{{ states('input_number.ev_charger_amps_set') | int }}"
      conn_id: 1
  - action: input_number.set_value
    metadata: {}
    target:
      entity_id: input_number.ev_charger_amps
    data:
      value: "{{ states('input_number.ev_charger_amps_set') | int }}"
mode: single

This also updates "Charge Rate" (ev_charger_amps) as an input to the next run of "Solar to Charger" automation.

There's two more cases I wanted to automate: when to turn the EVSE on and when to turn it off. If the EVSE turns on and off too quickly, the car considers it faulty and will refuse to charge any more. So I give it extra time before turning it off to limit how often the EVSE cycles on and off.

Automation: power on

alias: Turn on charger
description: ""
triggers:
  - trigger: numeric_state
    entity_id:
      - sensor.envoy_482518015855_current_net_power_consumption
    for:
      hours: 0
      minutes: 3
      seconds: 0
    below: -0.4
conditions:
  - condition: state
    entity_id: input_boolean.ev_charger_solar
    state:
      - "off"
  - condition: device
    type: is_on
    device_id: ad1a433e39bcebccb12910f27a78d79a
    entity_id: 3ec7dfadb4203d450627024c805bae4a
    domain: switch
actions:
  - action: input_boolean.turn_on
    metadata: {}
    target:
      entity_id: input_boolean.ev_charger_solar
    data: {}
  - action: ocpp.set_charge_rate
    metadata: {}
    data:
      custom_profile:
        transactionId: "{{ states('sensor.charger_transaction_id') | int }}"
        chargingProfileId: 3001
        stackLevel: 19
        chargingProfilePurpose: TxProfile
        chargingProfileKind: Absolute
        chargingSchedule:
          startSchedule: "2026-04-17T16:01:45.000Z"
          chargingRateUnit: A
          chargingSchedulePeriod:
            - startPeriod: 0
              limit: 6
      conn_id: 1
  - action: input_number.set_value
    metadata: {}
    target:
      entity_id: input_number.ev_charger_amps
    data:
      value: 6
mode: single

This looks to see if the net grid is 400W export over 3 minutes, the "Automation Setting" is off, and the "Charge Control" is on. If everything matches, it turns on the "Automation Setting", sets the charge rate to 6A, and updates the "Charge Rate" to match that.

Automation: power off

alias: Turn off charger
description: ""
triggers:
  - trigger: numeric_state
    entity_id:
      - sensor.envoy_482518015855_current_net_power_consumption
    for:
      hours: 0
      minutes: 10
      seconds: 0
    above: 1.6
conditions:
  - condition: state
    entity_id: input_boolean.ev_charger_solar
    state:
      - "on"
  - condition: device
    type: is_on
    device_id: ad1a433e39bcebccb12910f27a78d79a
    entity_id: 3ec7dfadb4203d450627024c805bae4a
    domain: switch
  - condition: numeric_state
    entity_id: input_number.ev_charger_amps
    below: 8
actions:
  - action: input_boolean.turn_off
    metadata: {}
    target:
      entity_id: input_boolean.ev_charger_solar
    data: {}
  - action: ocpp.set_charge_rate
    metadata: {}
    data:
      custom_profile:
        transactionId: "{{ states('sensor.charger_transaction_id') | int }}"
        chargingProfileId: 3001
        stackLevel: 19
        chargingProfilePurpose: TxProfile
        chargingProfileKind: Absolute
        chargingSchedule:
          startSchedule: "2026-04-17T16:01:45.000Z"
          chargingRateUnit: A
          chargingSchedulePeriod:
            - startPeriod: 0
              limit: 0
      conn_id: 1
  - action: input_number.set_value
    metadata: {}
    target:
      entity_id: input_number.ev_charger_amps
    data:
      value: 0
mode: single

This looks to see if the net grid import is over 1.6kw for 10 minutes, that the "Automation Setting" is on, the "Charge Control" is on, and the "Charge Rate" is below 8A. If so, then it sets the "Charge Rate" to 0A and tells the EVSE to match that.

Dashboard: power usage

I also used an ESP32 (Crowpanel 3.5") + esphome to make a physical dashboard on my desk:

The green + red graph in the center shows power usage. The yellow is the solar generation, the black is the net grid import/export, and the blue is the car charger. The "SOC" at the bottom is the car's state of charge. Source: esphome yaml

Full speed charging

Full speed charging is currently the default for me. I have to go an extra step to turn on the solar charger automation.

Having all this data in one system leads to a useful automation, like this one to full speed charge to 50% battery, and then switch to solar power charge:

alias: Turn on solar control when over 50%
description: ""
triggers:
  - trigger: numeric_state
    entity_id:
      - sensor.fordpass_123vin_soc
    above: 50
conditions:
  - condition: state
    entity_id: input_boolean.ev_charger_solar
    state:
      - "off"
  - condition: state
    entity_id: switch.charger_charge_control
    state:
      - "on"
actions:
  - action: input_boolean.turn_on
    metadata: {}
    target:
      entity_id: input_boolean.ev_charger_solar
    data: {}
mode: single
Battery goes up to 50% quickly, and then charges on the available solar

Future Improvements

I have a few things to make easier:

I don't have an automation setup to automatically toggle the "Charge Control" setting on the EVSE, so I have to do that manually after plugging in the car. And then I have to manually set the charge rate once the transaction starts or it'll charge at full speed.

I'd like a pair of buttons near the charger that I can use to specify if I want to full speed charge or charge as solar power is available. This could be another esphome ESP32.

Full speed charging has a race condition that could cause the solar charger automation to turn on and take over. To avoid that, the EVSE needs to get the net power imported high enough quickly enough that the "Turn on charger" automation doesn't run.

Questions? Comments? Contact information