Automating Deye in Home Assistant: battery, Time of Use, tariffs, PV forecast and surplus power, step by step
Just displaying production, home consumption and battery state is only the starting point. The real value of a Deye integration in Home Assistant shows up once the system starts making predictable decisions: protecting the battery reserve, charging the storage in a cheap window, leaving room for solar production, building up a buffer before an outage, and only switching on big loads once there is real export.
This guide starts from the basics. It first explains Time of Use and the entities it needs. Then we build automations, starting with the simplest alerts and manual profiles and moving on to tariffs, PV forecasting, dynamic prices, surplus power, heat pumps and EV charging. Every automation has a manual override, a data-freshness check, a limit on how often it writes, a test procedure, and a safe fallback state.
Deye and Home Assistant series — where are you in it?
This article is the third part of a complete series. It assumes the inverter is already correctly connected and the key values have already been verified. If you’re just starting out, or your data looks suspicious, work through the earlier parts first.
The golden rule: one system decides, one system writes
The most dangerous setup is the one where several independent mechanisms change the same settings. The inverter’s own schedule, a Home Assistant automation, SolarAssistant, Node-RED, EMHASS and the manufacturer’s app can each work fine on their own, but together they’ll start overwriting each other’s SOC, time windows and Grid Charge settings.
Where do you want to start?
You don’t have to roll out the whole system at once. Pick the problem you want to solve first. The safest order is: check your entities, simulate the decisions, build one simple automation, and only then add the rest.
Will these automations work with your integration method?
Automating a boiler or a charger only needs correct sensors from the inverter. Changing SOC, Grid Charge and Time of Use also needs write-capable entities. That’s the single most important distinction in this whole article.
| Method | Monitoring | SOC / TOU | PV surplus |
|---|---|---|---|
| Solarman, sensors only | Yes | No | Yes |
| Solarman with R/W entities | Yes | Depends on the profile | Yes |
| Kellerza Sunsynk/Deye | Yes | For supported models | Yes |
| ESPHome | Yes | Only if your config exposes writes | Yes |
| MQTT | Yes | Depends on the bridge | Yes |
| SolarAssistant | Yes | For settings that are exposed | Yes |
sensor ones, you have monitoring only. number, switch and select entities can let you change settings, but you need to check each one by hand before relying on it.1. How Time of Use works on Deye
In Deye’s manuals, Time of Use is used to program when the grid or a generator is allowed to charge the battery, and when the battery is allowed to power your loads. A typical screen holds six programs. Each one covers a time, an SOC, a power limit, and permission for Grid Charge. What the fields actually mean can differ between inverter families and firmware versions, so the manual for your exact model always wins.
2. Where Deye’s entities come from, and why they’re not the same for everyone
Examples in this article use names like sensor.deye_battery_soc, sensor.deye_grid_power or number.deye_minimum_soc. Home Assistant doesn’t invent these on its own. Entities only appear once the inverter has been connected to Home Assistant through a specific integration. It’s that integration that reads the inverter’s registers, names them, and exposes them as sensors, switches, dropdowns or number fields.
The whole chain looks like this
Why the names differ between installations
How to find your own entities, step by step
sensor.deye_battery_soc name in this guide is a placeholder. If your integration created sensor.sunsynk_battery_soc, that’s exactly the name you need to use in your automation.Which entities come from the inverter, and which ones you create yourself
3. What needs to exist in Home Assistant first
Before building an automation, check whether your integration exposes both the data and the write entities. Monitoring alone doesn’t mean you can change the schedule.
sensor.deye..., number.deye... and switch.deye... mark data created by your inverter integration. Entities starting with input_... are helpers we create ourselves.Measurement data
Control entities
4. Before you copy any YAML — four concepts for beginners
Entity is a single value or control in Home Assistant, e.g. battery SOC or the Grid Charge switch. Helper is a virtual dial or switch you create yourself. A trigger starts an automation, a condition only lets it run in a specific situation, and an action carries out the change.
alias:, without adding an automation:.5. Helpers and a manual automation kill switch
Helpers let you change thresholds from the dashboard. The main switch has to stop every write, but it shouldn’t disable the inverter’s own local safeguards.
Where to paste this code
input_boolean:
deye_automatyka:
name: Deye — energy automation
icon: mdi:home-lightning-bolt
deye_tryb_awaryjny:
name: Deye — emergency reserve
icon: mdi:battery-alert
deye_ladowanie_sieci:
name: Deye — allow charging from grid
icon: mdi:transmission-tower-import
input_number:
deye_soc_normalny:
name: Deye — normal SOC reserve
min: 10
max: 80
step: 5
unit_of_measurement: "%"
icon: mdi:battery-medium
deye_soc_awaryjny:
name: Deye — emergency SOC reserve
min: 30
max: 100
step: 5
unit_of_measurement: "%"
icon: mdi:battery-alert-variant
deye_nadwyzka_start:
name: Deye — export required to start
min: 200
max: 5000
step: 100
unit_of_measurement: "W"
icon: mdi:transmission-tower-export
input_datetime:
deye_tania_od:
name: Deye — cheap tariff from
has_date: false
has_time: true
deye_tania_do:
name: Deye — cheap tariff until
has_date: false
has_time: true
The easiest route: create helpers in the UI
6. Data freshness and power direction
An automation running on stale data can leave a charge or a load in the wrong state. Start by building a freshness sensor. In the example, data counts as fresh for 90 seconds after the last grid-power update.
First, work out the sign convention for grid power
template:
- binary_sensor:
- name: "Deye — basic data available"
unique_id: deye_podstawowe_dane_dostepne
availability: >
{{ states('sensor.deye_grid_power') not in
['unknown', 'unavailable', 'none', ''] }}
state: >
{{ states('sensor.deye_grid_power') not in
['unknown', 'unavailable', 'none', ''] }}
- sensor:
- name: "Deye — export to grid"
unique_id: deye_eksport_do_sieci
unit_of_measurement: "W"
device_class: power
state_class: measurement
availability: >
{{ states('sensor.deye_grid_power') not in
['unknown', 'unavailable', 'none', ''] }}
state: >
{% set grid = states('sensor.deye_grid_power') | float(0) %}
{# This variant assumes: import positive, export negative. #}
{{ [0, -grid] | max | round(0) }}
- name: "Deye — import from grid"
unique_id: deye_import_z_sieci
unit_of_measurement: "W"
device_class: power
state_class: measurement
availability: >
{{ states('sensor.deye_grid_power') not in
['unknown', 'unavailable', 'none', ''] }}
state: >
{% set grid = states('sensor.deye_grid_power') | float(0) %}
{{ [0, grid] | max | round(0) }}
last_updated alone isn’t always a reliable freshness marker if the device keeps reporting an identical value for a long stretch.
Simulate first: check the decisions without touching the inverter
This is the safest way to start. Instead of immediately calling number.set_value or or switch.turn_on
alias: Deye — SOC decision simulation
description: Doesn't change the inverter, only logs the planned decision
triggers:
- trigger: time_pattern
minutes: "/15"
conditions:
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
actions:
- variables:
nowy_soc: "{{ states('input_number.deye_soc_normalny') | int }}"
obecny_soc: "{{ states('number.deye_minimum_soc') | int(-1) }}"
- action: logbook.log
data:
name: "Deye — simulation"
message: >
Current limit: {{ obecny_soc }}%.
Would set the limit to {{ nowy_soc }}%.
mode: single
7. Scenario 1 — manual battery reserve
The first automation does exactly one thing: it copies the helper’s value into the minimum-SOC entity. It’s the safest possible test of the whole write chain.
Preparation, step by step
alias: Deye — set normal battery reserve
id: deye_ustaw_normalna_rezerwe_baterii
description: Copies the helper's value into the minimum-SOC entity after checking the data.
triggers:
- trigger: state
entity_id: input_number.deye_soc_normalny
for: "00:00:10"
conditions:
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
actions:
- variables:
nowy_soc: "{{ states('input_number.deye_soc_normalny') | int(20) }}"
obecny_soc: "{{ states('number.deye_minimum_soc') | int(-1) }}"
- condition: template
value_template: "{{ obecny_soc >= 0 and nowy_soc != obecny_soc }}"
- action: number.set_value
target:
entity_id: number.deye_minimum_soc
data:
value: "{{ nowy_soc }}"
- delay: "00:00:05"
- if:
- condition: template
value_template: >
{{ states('number.deye_minimum_soc') | int(-1) == nowy_soc }}
then:
- action: logbook.log
data:
name: Deye
message: "Normal reserve confirmed: {{ nowy_soc }}%"
else:
- action: persistent_notification.create
data:
title: "Deye — SOC write not confirmed"
message: >
Tried to set {{ nowy_soc }}%, but the entity shows
{{ states('number.deye_minimum_soc') }}%.
mode: restart
sensor.deye_, number.deye_, switch.deye_ and every controlled-load entity is a placeholder name. Copy your own IDs from Developer tools.Test
8. Scenario 2 — charging during a fixed cheap-tariff window
The most predictable option is a fixed tariff window. The system checks the time, the user’s permission and the SOC. Only then does it turn on Grid Charge and set the target.
Preparation, step by step
alias: Deye — night charging in a fixed cheap tariff
id: deye_nocne_ladowanie_stala_taryfa
description: Turns on Grid Charge within the set window until the SOC target is reached.
triggers:
- trigger: time_pattern
minutes: "/5"
- trigger: state
entity_id:
- input_boolean.deye_automatyka
- input_boolean.deye_ladowanie_sieci
- input_datetime.deye_tania_od
- input_datetime.deye_tania_do
conditions: []
actions:
- variables:
dane_ok: >
{{ is_state('binary_sensor.deye_podstawowe_dane_dostepne', 'on') }}
automatyka_on: >
{{ is_state('input_boolean.deye_automatyka', 'on') }}
zgoda_on: >
{{ is_state('input_boolean.deye_ladowanie_sieci', 'on') }}
soc: "{{ states('sensor.deye_battery_soc') | float(-1) }}"
cel_soc: 80
start: "{{ states('input_datetime.deye_tania_od')[0:5] }}"
stop: "{{ states('input_datetime.deye_tania_do')[0:5] }}"
teraz: "{{ now().strftime('%H:%M') }}"
w_oknie: >
{% if start < stop %}
{{ start <= teraz < stop }}
{% else %}
{{ teraz >= start or teraz < stop }}
{% endif %}
ma_ladowac: >
{{ dane_ok and automatyka_on and zgoda_on
and soc >= 0 and soc < cel_soc and w_oknie }}
- choose:
- conditions:
- condition: template
value_template: "{{ ma_ladowac }}"
sequence:
- if:
- condition: template
value_template: >
{{ states('number.deye_program_1_soc') | int(-1) != cel_soc }}
then:
- action: number.set_value
target:
entity_id: number.deye_program_1_soc
data:
value: "{{ cel_soc }}"
- if:
- condition: state
entity_id: switch.deye_program_1_grid_charge
state: "off"
then:
- action: switch.turn_on
target:
entity_id: switch.deye_program_1_grid_charge
default:
- if:
- condition: state
entity_id: switch.deye_program_1_grid_charge
state: "on"
then:
- action: switch.turn_off
target:
entity_id: switch.deye_program_1_grid_charge
mode: single
sensor.deye_, number.deye_, switch.deye_ and every controlled-load entity is a placeholder name. Copy your own IDs from Developer tools.When it makes sense
9. Scenario 3 — PV forecast and a dynamic SOC target
Forecast.Solar can provide an energy forecast for tomorrow. A forecast is not a guarantee. React with wide bands and fall back to a neutral target when the sensor is unavailable.
Preparation, step by step
alias: Deye — set charging target from PV forecast
id: deye_cel_ladowania_prognoza_pv
description: Once a day, picks the target SOC based on tomorrow's forecast production.
triggers:
- trigger: time
at: "21:30:00"
conditions:
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
actions:
- variables:
prognoza: >
{{ states('sensor.energy_production_tomorrow') | float(-1) }}
cel_soc: >
{% if prognoza < 0 %}
70
{% elif prognoza >= 20 %}
30
{% elif prognoza >= 12 %}
50
{% elif prognoza >= 6 %}
70
{% else %}
90
{% endif %}
- condition: template
value_template: >
{{ states('number.deye_program_1_soc') | int(-1) != cel_soc | int }}
- action: number.set_value
target:
entity_id: number.deye_program_1_soc
data:
value: "{{ cel_soc | int }}"
- delay: "00:00:05"
- action: logbook.log
data:
name: Deye
message: >
Tomorrow's PV forecast: {{ prognoza }} kWh.
Overnight charging target set to: {{ cel_soc }}%.
mode: single
sensor.deye_, number.deye_, switch.deye_ and every controlled-load entity is a placeholder name. Copy your own IDs from Developer tools.Full example: charging to 70% overnight ahead of a cloudy day
Now let’s put all the pieces together into one simple project. Say tomorrow’s forecast production is weak. During the cheap tariff, we want to top the battery up to 70% — but only when the Deye data is current.
10. Scenario 4 — reserve before a grid outage
Trigger emergency mode manually at first. Only after testing should you link it to a weather warning or a planned outage. It raises the reserve and gives the tariff automation permission to charge.
Preparation, step by step
alias: Deye — manual emergency reserve mode
id: deye_reczny_tryb_rezerwy_awaryjnej
description: Raises minimum SOC when the helper turns on and restores the normal value when it turns off.
triggers:
- trigger: state
entity_id: input_boolean.deye_tryb_awaryjny
to: "on"
id: alarm_on
- trigger: state
entity_id: input_boolean.deye_tryb_awaryjny
to: "off"
id: alarm_off
conditions:
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
actions:
- choose:
- conditions:
- condition: trigger
id: alarm_on
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
sequence:
- action: number.set_value
target:
entity_id: number.deye_minimum_soc
data:
value: >
{{ states('input_number.deye_soc_awaryjny') | int(60) }}
- action: input_boolean.turn_on
target:
entity_id: input_boolean.deye_ladowanie_sieci
- conditions:
- condition: trigger
id: alarm_off
sequence:
- action: number.set_value
target:
entity_id: number.deye_minimum_soc
data:
value: >
{{ states('input_number.deye_soc_normalny') | int(20) }}
- action: input_boolean.turn_off
target:
entity_id: input_boolean.deye_ladowanie_sieci
mode: restart
sensor.deye_, number.deye_, switch.deye_ and every controlled-load entity is a placeholder name. Copy your own IDs from Developer tools.Tests
11. Scenario 5 — real PV surplus
The most common mistake is controlling things off PV power. 5 kW of production doesn’t mean 5 kW of surplus if the house and battery are using all of it. Use actual export instead.
Preparation, step by step
alias: Deye — boiler from real PV surplus
id: deye_bojler_z_nadwyzki_pv
description: Turns the boiler on once export is stable and safely turns it off when the surplus or the data disappears.
triggers:
- trigger: numeric_state
entity_id: sensor.deye_eksport_do_sieci
above: input_number.deye_nadwyzka_start
for: "00:03:00"
id: start
- trigger: numeric_state
entity_id: sensor.deye_eksport_do_sieci
below: 200
for: "00:02:00"
id: stop
- trigger: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
to: "off"
id: brak_danych
- trigger: state
entity_id: input_boolean.deye_automatyka
to: "off"
id: automatyka_off
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id: start
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
- condition: numeric_state
entity_id: sensor.deye_battery_soc
above: 85
- condition: state
entity_id: switch.bojler_nadwyzka
state: "off"
sequence:
- action: switch.turn_on
target:
entity_id: switch.bojler_nadwyzka
- conditions:
- condition: trigger
id:
- stop
- brak_danych
- automatyka_off
- condition: state
entity_id: switch.bojler_nadwyzka
state: "on"
sequence:
- action: switch.turn_off
target:
entity_id: switch.bojler_nadwyzka
mode: restart
sensor.deye_, number.deye_, switch.deye_ and every controlled-load entity is a placeholder name. Copy your own IDs from Developer tools.Hysteresis
Once a load switches on, export drops. An identical start and stop threshold causes rapid cycling. Turning on should require a higher threshold plus stability, and turning off a lower threshold held for a set time.
12. Priorities: battery, hot water, heat pump and EV
Not every installation should trigger every scenario at once. Decide the order in which energy gets used.
13. Limiting import and shedding load
Home Assistant can disconnect non-critical loads, but it doesn’t replace electrical protection or the inverter’s own internal peak shaving.
Preparation, step by step
alias: Energy — load shedding on high import
id: energia_redukcja_obciazenia_wysoki_import
description: First turns off the boiler, then limits the wallbox.
triggers:
- trigger: numeric_state
entity_id: sensor.deye_import_z_sieci
above: 7000
for: "00:00:20"
conditions:
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
actions:
- if:
- condition: state
entity_id: switch.bojler_nadwyzka
state: "on"
then:
- action: switch.turn_off
target:
entity_id: switch.bojler_nadwyzka
- stop: "Boiler turned off — don't shed the wallbox in the same cycle."
- if:
- condition: numeric_state
entity_id: number.wallbox_current
above: 6
then:
- action: number.set_value
target:
entity_id: number.wallbox_current
data:
value: 6
mode: single
sensor.deye_, number.deye_, switch.deye_ and every controlled-load entity is a placeholder name. Copy your own IDs from Developer tools.
14. EMHASS — the advanced level
EMHASS can factor in the PV forecast, load, buy and sell prices, battery parameters and shiftable loads. Its output is a power plan and an SOC trajectory. It doesn’t, however, automatically know how to control any given Deye.
15. Rollout plan and failure testing
Roll the system out in stages. Let each stage run for a few days before adding the next one.
Failure testing
16. Entity map to fill in
Fill in the map with your own entities before copying any automation.
17. The rules that matter most
An effective system is built from small layers: correct data, helpers, a write lock, simple decisions and a local schedule.
YAML correctness check
Every block has been re-checked as valid YAML. The automation syntax matches the current Home Assistant format, with triggers, conditions and actions fields. What can’t be automatically confirmed, though, is whether the entity names and settings actually mean what you expect on your specific Deye — that depends on your integration and model.
Checklist before the first real write
Operating profiles instead of automations fighting each other
The clearest approach is a profile system. Instead of separately setting random fields, automations pick a profile: Summer, Winter, Cheap tariff, Reserve, Away or Manual. One central script translates the profile into the inverter’s actual settings.
input_select:
deye_profil:
name: "Deye — operating profile"
options:
- Manual
- Summer
- Winter
- Cheap tariff
- Reserve
- Away
initial: Manual
input_boolean:
deye_automatyka:
name: "Deye — automation enabled"
deye_tryb_testowy:
name: "Deye — simulation only"
A central write script with checks and read-back
Entity names here are placeholders. The pattern first sets the SOC target, then Grid Charge, waits for the data to refresh, and checks whether the inverter actually returned the expected value.
script:
deye_ustaw_okno_ladowania:
alias: "Deye — set charging window"
mode: queued
max: 2
fields:
cel_soc:
required: true
grid_charge:
required: true
sequence:
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
- variables:
wymagany_soc: "{{ cel_soc | int }}"
wymagany_grid: "{{ grid_charge | bool }}"
- choose:
- conditions: "{{ is_state('input_boolean.deye_tryb_testowy', 'on') }}"
sequence:
- action: logbook.log
data:
name: "Deye — simulation"
message: >
Would set SOC to {{ wymagany_soc }}%,
Grid Charge {{ wymagany_grid }}.
default:
- action: number.set_value
target:
entity_id: number.deye_program_1_soc
data:
value: "{{ wymagany_soc }}"
- delay: "00:00:03"
- choose:
- conditions: "{{ wymagany_grid }}"
sequence:
- action: switch.turn_on
target:
entity_id: switch.deye_program_1_grid_charge
default:
- action: switch.turn_off
target:
entity_id: switch.deye_program_1_grid_charge
- delay: "00:00:10"
- if:
- condition: template
value_template: >
{{ states('number.deye_program_1_soc') | int(-1)
!= wymagany_soc }}
then:
- action: notify.notify
data:
title: "Deye — write not confirmed"
message: >
Expected SOC {{ wymagany_soc }}%,
read back {{ states('number.deye_program_1_soc') }}%.
- stop: "Inverter did not confirm the SOC"
Watchdog: reacting to stale data
An automation can’t make decisions off a value from several hours ago. A watchdog shuts off surplus loads and flags the problem.
alias: "Deye — data watchdog"
triggers:
- trigger: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
to: "off"
for: "00:03:00"
actions:
- action: switch.turn_off
target:
entity_id:
- switch.bojler_nadwyzka
- switch.ladowarka_ev_nadwyzka
- action: notify.notify
data:
title: "Deye — no fresh data"
message: >
Load control has been stopped.
Check the integration, logger or Modbus.
mode: single
Extra scenarios worth building
Notify without controlling — the best first step
For a few days, Home Assistant can simply tell you what decision it would have made, based on the forecast, price and SOC. Only once you’ve compared the recommendation against what actually happened in the house do you enable real writes.
alias: "Deye — overnight SOC recommendation"
triggers:
- trigger: time
at: "20:30:00"
actions:
- variables:
prognoza: "{{ states('sensor.energy_production_tomorrow') | float(-1) }}"
soc: "{{ states('sensor.deye_battery_soc') | float(-1) }}"
rekomendacja: >
{% if prognoza < 0 or soc < 0 %}
No reliable data — use the manual profile.
{% elif prognoza >= 18 %}
Sunny day ahead — leave room for PV.
{% elif prognoza >= 8 %}
Moderate production — top up partially.
{% else %}
Weak production — consider a higher overnight SOC.
{% endif %}
- action: notify.notify
data:
title: "Deye — recommendation"
message: "{{ rekomendacja }}"
mode: single
G12w and weekends
The hours of the cheap zone depend on your contract. Store them in helpers. An automation can pick a weekday or weekend profile, but shouldn’t hard-code hours as if they were universal.
Dynamic prices
Better than a simple threshold is picking a set number of the cheapest hours. You need to factor in the retail price, variable fees, cycle efficiency, expected consumption and available battery capacity.
Away mode
Limits unnecessary cycling and blocks surplus loads. The recommended storage SOC, though, depends on your battery’s manufacturer, so this article doesn’t dictate a single value.
Time-of-day-dependent reserve
In the evening you might keep more energy for the night, in the morning leave room for PV, and before an expected outage raise the reserve with a manual helper.
PV surplus without rapid load cycling
Real surplus isn’t the same as PV production. Stable control needs two thresholds, a confirmation delay, a minimum run time and an import limit.
alias: "Deye — boiler from PV surplus"
triggers:
- trigger: numeric_state
entity_id: sensor.deye_export_power
above: 2200
for: "00:03:00"
id: start
- trigger: numeric_state
entity_id: sensor.deye_grid_import_power
above: 500
for: "00:01:00"
id: stop_import
- trigger: numeric_state
entity_id: sensor.deye_export_power
below: 500
for: "00:03:00"
id: stop_surplus
conditions:
- condition: state
entity_id: input_boolean.deye_automatyka
state: "on"
- condition: state
entity_id: binary_sensor.deye_podstawowe_dane_dostepne
state: "on"
actions:
- choose:
- conditions: "{{ trigger.id == 'start' }}"
sequence:
- condition: numeric_state
entity_id: sensor.deye_battery_soc
above: 80
- action: switch.turn_on
target:
entity_id: switch.bojler_nadwyzka
- conditions: "{{ trigger.id in ['stop_import', 'stop_surplus'] }}"
sequence:
- action: switch.turn_off
target:
entity_id: switch.bojler_nadwyzka
mode: restart
Heat pump
Prefer SG Ready, the manufacturer’s Modbus interface, changing the setpoint, or an official self-consumption mode. Don’t cut the compressor like an ordinary outlet, and keep minimum run times.
Electric car
Control the charger’s current, factoring in minimum current, phase count, your connection limit and a delay between changes. The house’s reserve should have higher priority than the EV.
Does a battery cycle actually pay off?
An automation can be technically correct and still increase cost. Compare the charging price against the price of the purchase it avoided, the round-trip efficiency, and the cost you assign to battery wear.
We’re not giving a single efficiency figure or wear cost. They depend on the battery, the inverter, temperature, power, and how you choose to value degradation.
Operator dashboard
One card should show the system’s state and the reason behind the automation’s latest decision.
Sources and current references
Home Assistant’s documentation confirms that production forecasts are used in automations. The Deye/Sunsynk project’s definitions show which entities are available for read/write. SolarAssistant recommends testing changes first with writes disabled, before enabling actual control. The more elaborate adaptive-charging and EMHASS examples are community patterns and need adapting to your setup.
