Hi everyone!
I wanted to share a project I’ve been working on to get the absolute best out of my Marstek Venus E (5.12 kWh) storage system using Home Assistant, specifically optimized for zero-export and off-grid photovoltaic setups (zero immissione / impianti isola).
Since the official app doesn’t support zero-export / off-grid charging modes at all, I set up a complete automation and sensor suite to handle everything smoothly. Here is a brief overview of all the automations:
-
01 - PV Charging Ramp: Gradually increases battery charging power step-by-step based on available solar production (aiming for 85% of PV generation), dynamically reducing it if grid import is detected while keeping the system in passive mode.
-
02 - Self-Consumption: Automatically switches the battery to
automode whenever home grid import exceeds a specific threshold for more than a few seconds to support household loads. -
03 - Passive After Import: Safely switches the battery back to passive mode during daytime hours when grid import drops below a safe threshold, optimizing energy flow.
-
04 - Night Discharge: Manages the evening/night and early morning window by activating
automode so the battery can discharge and power the house overnight. -
05 - Marstek Reload: Triggers a scheduled reload of the device configuration every morning at a set time to be sure that every automation works fine.
-
06 - Keepalive Venus: Sends periodic afternoon read-commands when the battery is nearly full, preventing the device from entering daytime sleep mode.
-
07 - Periodic Grid Charge Maintenance: Checks every 14 days if the battery has reached 100% capacity; if not, it safely forces a 2000W grid charge one hour after sunset, stopping automatically once it hits 99.9% or after a 3-hour timeout.
(Note: Make sure to replace the placeholder YOUR_DEVICE_ID in the YAML code below with your actual device ID!)
Supporting Configuration Files (configuration.yaml)
Add these snippets to your main configuration.yaml to handle templates, energy tracking integration, utility meters, and dashboard mapping:
YAML
template:
- sensor:
- name: "Battery Status"
unique_id: battery_status_venus
state: >
{% set soc = states('sensor.venus_e_3_0_battery_level')|float(0) %}
{% set power = states('sensor.venus_e_3_0_battery_power')|float(0) %}
{% set cap = 5120 %}
{% if soc >= 99.5 %}
100% - Full Charge
{% elif power >= -10 and power <= 10 %}
0h - Standby
{% elif power < -10 %}
{% set rimanente = cap * (100 - soc) / 100 %}
{% set p = power|abs %}
{% set ore = (rimanente / p) if p > 0 else 0 %}
{% set h = ore|int %}
{% set m = ((ore - h)*60)|int %}
{{ h }}h {{ m }}m to 100%
{% elif power > 10 %}
{% set rimanente = cap * (soc - 12) / 100 %}
{% set ore = (rimanente / power) if power > 0 else 0 %}
{% set h = ore|int %}
{% set m = ((ore - h)*60)|int %}
{{ h }}h {{ m }}m to 12%
{% endif %}
lovelace:
dashboards:
venus-battery:
mode: yaml
filename: venus.yaml
title: Venus
icon: mdi:battery-high
show_in_sidebar: true
sensor:
- platform: integration
source: sensor.grid_power_import
name: Total Home Energy from Power
unique_id: total_home_energy_from_power
unit_prefix: k
unit_time: h
method: left
utility_meter:
daily_home_energy:
name: Daily Home Energy Consumption
unique_id: daily_home_energy_consumption
source: sensor.total_home_energy_from_power
cycle: daily
monthly_home_energy:
name: Monthly Home Energy Consumption
unique_id: monthly_home_energy_consumption
source: sensor.total_home_energy_from_power
cycle: monthly
Dashboard Layout (venus.yaml)
Create a venus.yaml file in your configuration directory with the following layout for real-time monitoring:
YAML
title: Photovoltaic
icon: mdi:solar-power
views:
- title: Home
cards:
- type: markdown
content: >
## Energy System Overview
- type: markdown
content: >
{% set pv = states('sensor.inverter_power')|float(0) %}
{% set prod_oggi = states('sensor.inverter_today_production')|float(0) %}
{% set prod_tot = states('sensor.inverter_total_production')|float(0) %}
## ☀️ PV Generation
{% if pv > 100 %}
### <font color="#2ecc71">☀️ Instant PV: {{ pv|int }} W</font>
{% elif pv > 0 %}
### <font color="#e67e22">⛅ Instant PV: {{ pv|int }} W</font>
{% else %}
### <font color="#e74c3c">🌙 Instant PV: 0 W</font>
{% endif %}
#### ⚡ Today Production: {{ prod_oggi }} kWh
#### 📈 Total Production: {{ prod_tot }} kWh
{% set temp = states('sensor.inverter_inverter_module_temperature')|float(0) %}
{% if temp < 40 %}
### <font color="#2ecc71">🌡️ Inverter Temp: {{ temp|int }} °C</font>
{% elif temp <= 50 %}
### <font color="#e67e22">🌡️ Inverter Temp: {{ temp|int }} °C</font>
{% else %}
### <font color="#e74c3c">🔥 Inverter Temp: {{ temp|int }} °C</font>
{% endif %}
- type: markdown
content: >
## 🏠 Home Load
{% set power_grid = states('sensor.grid_power_import')|float(0) %}
{% if power_grid < 50 %}
### <font color="#2ecc71">⚡ Grid Import: {{ power_grid|int }} W</font>
{% elif power_grid <= 500 %}
### <font color="#e67e22">⚡ Grid Import: {{ power_grid|int }} W</font>
{% else %}
### <font color="#e74c3c">⚡ Grid Import: {{ power_grid|int }} W</font>
{% endif %}
### 📊 Daily Consumption: {{ states('sensor.daily_home_energy_consumption') | float(0) | round(2) }} kWh
### 📅 Monthly Consumption: {{ states('sensor.monthly_home_energy_consumption') | float(0) | round(1) }} kWh
- type: markdown
content: >
## 🔋 Marstek Venus E 5,12 kWh
{% set soc = states('sensor.venus_e_3_0_battery_level')|float(0) %}
{% set p = states('sensor.venus_e_3_0_battery_power')|float(0) %}
{% set cap = 5120 %}
{% if p < -10 %}
### <font color="#2ecc71">⚡ Charging: {{ p|abs|int }} W</font>
{% set rimanente = cap * (100 - soc) / 100 %}
{% set ore = (rimanente / p|abs) if p|abs > 0 else 0 %}
#### ⏳ Time to 100%: {{ ore|int }}h {{( (ore - ore|int)*60 )|int }}m
{% elif p > 10 %}
### <font color="#f1c40f">🔌 Discharging: {{ p|int }} W</font>
{% set rimanente = cap * (soc - 12) / 100 %}
{% set ore = (rimanente / p) if p > 0 else 0 %}
#### ⏳ Remaining to 12%: {{ ore|int }}h {{( (ore - ore|int)*60 )|int }}m
{% else %}
### ⏸️ Standby ({{ soc|int }}%)
{% endif %}
#### ⚡ Battery Level: {{ soc|int }}%
- type: markdown
content: >
## 🕒 Recent Triggered Automations
{% set ns = namespace(ults=[]) %}
{% for aut in states.automation %}
{% if aut.attributes.last_triggered is defined and aut.attributes.last_triggered != none %}
{% set ns.ults = ns.ults + [(aut.attributes.last_triggered, aut.name)] %}
{% endif %}
{% endfor %}
{% set sorted_auts = ns.ults | sort(reverse=true) %}
{% for time, name in sorted_auts[:2] %}
- **{{ name }}**: {{ as_timestamp(time) | timestamp_custom('%H:%M - %d/%m') }}
{% endfor %}
Automations Code
YAML
- id: '01'
alias: 01 - PV Charging Ramp
description: Increases charge +100W every 60s. 85% PV start, reduces charge based on grid import. Passive only, PV>100W, SOC<99%
triggers:
- trigger: time_pattern
minutes: /1
conditions:
- condition: state
entity_id: select.venus_e_3_0_operating_mode
state: passive
- condition: template
value_template: '{% set sunrise = as_datetime(state_attr(''sun.sun'', ''next_rising'')) - timedelta(days=1) %} {{ now() > sunrise + timedelta(hours=1, minutes=30) }}'
- condition: numeric_state
entity_id: sensor.venus_e_3_0_battery_level
below: 99
- condition: template
value_template: '{{ states(''sensor.inverter_power'') | float(0) > 200 or states(''input_number.marstek_last_charge_power'') | float(0) > 0 }}'
- condition: numeric_state
entity_id: sensor.inverter_power
above: 100
- condition: not
conditions:
- condition: state
entity_id: sensor.inverter_power
state: unavailable
actions:
- variables:
pv: '{{ states(''sensor.inverter_power'') | float(0) }}'
last: '{{ states(''input_number.marstek_last_charge_power'') | float(0) }}'
import_now: '{{ states(''sensor.grid_power_import'') | float(0) }}'
pv_85: '{{ (pv * 0.85) | int }}'
target: '{% set imp = [import_now, 0] | max %} {% if last == 0 %} {% set t = pv_85 %} {% elif imp > 100 %} {% set t = last - imp - 50 %} {% elif imp > 40 %} {% set t = last - imp %} {% else %} {% set t = last + 100 %} {% endif %} {% set t = [t, 2500] | min %} {% set t = [t, 50] | max %} {{ t | int }} '
delta: '{{ (target | int - last | int) | abs }}'
- choose:
- conditions:
- condition: template
value_template: '{{ delta | int >= 100 }}'
sequence:
- action: marstek.set_passive_mode
data:
device_id: YOUR_DEVICE_ID
power: '{{ target | int * -1 }}'
- action: input_number.set_value
target:
entity_id: input_number.marstek_last_charge_power
data:
value: '{{ target }}'
mode: single
- id: '02'
alias: 02 - Self Consumption
description: Forces AUTO if import > 700W (both on threshold exceed and if already present)
triggers:
- trigger: numeric_state
entity_id: sensor.grid_power_import
above: 700
for:
seconds: 10
- trigger: time_pattern
minutes: /1
conditions:
- condition: numeric_state
entity_id: sensor.grid_power_import
above: 700
- condition: state
entity_id: select.venus_e_3_0_operating_mode
state: passive
- condition: numeric_state
entity_id: sensor.venus_e_3_0_battery_level
above: 12
actions:
- action: select.select_option
target:
entity_id: select.venus_e_3_0_operating_mode
data:
option: auto
mode: single
- id: '03'
alias: 03 - Passive After Import
description: 'Cyclic check: returns to passive if import < 500W for 2 min, daytime only'
triggers:
- trigger: numeric_state
entity_id: sensor.grid_power_import
below: 500
for:
minutes: 2
- trigger: time_pattern
minutes: /2
conditions:
- condition: template
value_template: '{% set sunrise = as_datetime(state_attr(''sun.sun'', ''next_rising'')) - timedelta(days=1) %} {% set sunset = as_datetime(state_attr(''sun.sun'', ''next_setting'')) %} {{ now() > sunrise + timedelta(minutes=80) and now() < sunset - timedelta(minutes=90) }} '
- condition: state
entity_id: select.venus_e_3_0_operating_mode
state: auto
- condition: numeric_state
entity_id: sensor.venus_e_3_0_battery_power
below: 500
- condition: not
conditions:
- condition: state
entity_id: sensor.inverter_power
state: unavailable
actions:
- action: marstek.set_passive_mode
data:
device_id: YOUR_DEVICE_ID
power: 0
- action: input_number.set_value
target:
entity_id: input_number.marstek_last_charge_power
data:
value: 0
mode: single
- id: '04'
alias: '04 - Night Discharge'
description: Switches to AUTO every 5 min within sunset-60min / sunrise+100min window if not already in AUTO and SOC > 10%
triggers:
- trigger: time_pattern
minutes: /5
conditions:
- condition: template
value_template: '{% set is_night = is_state(''sun.sun'', ''below_horizon'') %} {% set sunrise = as_datetime(state_attr(''sun.sun'', ''next_rising'')) %} {% set sunset = as_datetime(state_attr(''sun.sun'', ''next_setting'')) %} {% if is_night %} {{ now() <= sunrise + timedelta(hours=1, minutes=40) }} {% else %} {{ now() >= sunset - timedelta(minutes=60) }} {% endif %}'
- condition: numeric_state
entity_id: sensor.venus_e_3_0_battery_level
above: 10
- condition: not
conditions:
- condition: state
entity_id: select.venus_e_3_0_operating_mode
state: auto
actions:
- action: select.select_option
target:
entity_id: select.venus_e_3_0_operating_mode
data:
option: auto
- action: input_number.set_value
target:
entity_id: input_number.marstek_last_charge_power
data:
value: 0
mode: single
- id: '05'
alias: 05 - Marstek Reload
triggers:
- trigger: time
at: 06:30:00
actions:
- action: homeassistant.reload_config_entry
target:
device_id: YOUR_DEVICE_ID
- id: '06'
alias: 06 - Venus Keepalive
description: Reads every 30min 12:00-19:30 if in passive and SOC>98%, prevents daytime sleep
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: state
entity_id: select.venus_e_3_0_operating_mode
state: passive
- condition: numeric_state
entity_id: sensor.venus_e_3_0_battery_level
above: 98
- condition: time
after: '12:00:00'
before: '19:00:00'
actions:
- action: marstek.set_passive_mode
data:
device_id: YOUR_DEVICE_ID
power: 0
mode: single
- id: '07'
alias: 07 - Periodic Marstek 2000W Grid Charge Maintenance
description: Forces 2000W grid charge 1h after sunset every 14gg if 100% was not reached in the last 2 weeks. Stops at 100% or after 3h.
triggers:
- trigger: sun
event: sunset
offset: "+01:00:00"
conditions:
- condition: numeric_state
entity_id: sensor.venus_e_3_0_battery_level
below: 99
- condition: template
value_template: >
{% set last = state_attr('automation.07_manutenzione_ricarica_periodica_marstek_2000w', 'last_triggered') %}
{{ last is none or (as_timestamp(now()) - as_timestamp(last)) / 86400 >= 14 }}
actions:
- action: marstek.set_passive_mode
data:
device_id: YOUR_DEVICE_ID
power: -2000
- action: input_number.set_value
target:
entity_id: input_number.marstek_last_charge_power
data:
value: 2000
- wait_for_trigger:
- trigger: numeric_state
entity_id: sensor.venus_e_3_0_battery_level
above: 99.9
timeout: "03:00:00"
- action: marstek.set_passive_mode
data:
device_id: YOUR_DEVICE_ID
power: 0
- action: input_number.set_value
target:
entity_id: input_number.marstek_last_charge_power
data:
value: 0
mode: single
I’m currently running this setup and it’s working like a charm. Just a quick heads-up: I’m an enthusiast rather than a professional programmer, so while I’ll do my best to help and reply, I might not always be able to handle very complex feature requests or dedicate a lot of time to troubleshooting. Contributions, feedback, and improvements from the community are always more than welcome!
Here is a quick preview of how the dashboard looks in action once everything is set up:
Disclaimer: This configuration works smoothly on my personal setup, but I take no responsibility for any system malfunctions, unexpected behaviors, battery wear, or potential hardware damage if you decide to use or adapt these scripts. Always double-check your configurations, hardware IDs, and safety limits before running them on your own equipment.
If you find this useful or if it saved you some time, feel free to buy me a coffee
:
https://ko-fi.com/dibilino
Let me know if you have any questions, suggestions, or improvements!
