Integrating Marstek Venus D into Home Assistant

Integrating Marstek Venus D into Home Assistant

Smart home energy monitoring is one of the most rewarding things you can set up in Home Assistant - but getting it right with a DC-coupled battery system like the Marstek Venus D requires some creative sensor work. In this guide, I'll walk you through how I integrated my Marstek Venus D (with 2 battery packs, 5.12 kWh total) and its 4 MPPT solar inputs into Home Assistant's Energy Dashboard - with accurate power flow tracking.

The Challenge

The Marstek Venus D is a plug-in battery system with built-in MPPT charge controllers for solar panels. Unlike a typical AC-coupled setup where PV, battery, and grid are separate systems, the Venus D is a single unit:

  • Solar panels connect directly to the battery's 4 MPPT inputs (DC side)
  • The battery charges from solar via DC
  • A single AC output feeds your home grid (capped at 800W for German "Balkonkraftwerk" regulations)

This means the AC output is always a combined result of solar + battery. Home Assistant's Energy Dashboard doesn't understand this topology natively - it treats PV, Battery, and Grid as three independent sources flowing into Home. We need to teach it.

Prerequisites

This guide assumes your Marstek Venus D is already fully installed and operational. Specifically:

  • The Venus D is connected to your home grid and solar panels are wired to the MPPT inputs
  • You have a Shelly Pro 3EM (or similar CT clamp meter) installed at your main distribution board
  • The Venus D's built-in CT functionality is configured in the Marstek app to work with your Shelly, so it automatically adjusts feed-in power based on your home's consumption (up to 800W for German "Balkonkraftwerk" regulations)
  • You can see the battery charging from solar and discharging to your home grid via the Marstek app
  • The Shelly Pro 3EM is set up with the Davc0m saldierend net metering script, which provides properly balanced sensor.shelly_pro_3em_saldierend_import and sensor.shelly_pro_3em_saldierend_export entities in Home Assistant via MQTT. If you haven't set this up yet, check out simon42's excellent guide on Shelly Pro 3EM saldierung which explains the problem and walks you through the solution

All of this should be working reliably before you start integrating with Home Assistant. This guide focuses purely on the HA integration and Energy Dashboard configuration - not the initial hardware setup.

What You'll Need

  • Marstek Venus D battery (works with Venus E too)
  • Marstek Venus Modbus Integration (HACS)
  • An RS485-to-WiFi/Ethernet gateway (e.g., Elfin EW11, PUSR DR134)
  • A grid power meter (I use a Shelly Pro 3EM)
  • Home Assistant with HACS installed

Part 1: Installing the Marstek Modbus Integration

The excellent marstek_venus_modbus integration by ViperRNMC provides all the sensors we need via Modbus TCP.

  1. Add the repository to HACS: Integrations → Custom repositories
  2. Install "Marstek Venus Modbus"
  3. Restart Home Assistant
  4. Add via Settings → Devices & Services with your gateway's IP, port 502, and Unit ID

After setup, you'll see sensors for battery voltage, current, SOC, AC output, MPPT power for each channel, charging/discharging energy, and much more. The key sensors we'll use:

  • sensor.marstek_venus_modbus_mppt1_leistung through mppt4_leistung (PV power per MPPT channel)
  • sensor.marstek_venus_d_ac_leistung (AC output power to home)
  • sensor.marstek_venus_modbus_batterieleistung (battery power, DC side)
  • sensor.marstek_venus_modbus_soc_batterie (state of charge)

Part 2: Understanding the Power Flows

Before diving into configuration, let's understand what happens inside the Venus D:

Daytime scenario (PV 926W, AC output 800W):

  • 800W of solar passes through to AC output → your home
  • 126W surplus solar charges the battery
  • Battery contributes 0W to home

Evening scenario (PV 0W, AC output 800W):

  • 0W from solar
  • Battery provides all 800W to home

Mixed scenario (PV 400W, AC output 800W):

  • 400W of solar passes through to AC output
  • Battery supplements with 400W
  • 0W surplus for charging

The formulas we need:

  • PV to Home = min(Total PV, AC output) - solar can't contribute more than what comes out
  • Battery to Home = max(AC output - Total PV, 0) - whatever solar doesn't cover
  • PV to Battery = max(Total PV - AC output, 0) - surplus solar that charges the battery

Part 3: Template Sensors

Add these template sensors to your configuration.yaml. They split the AC output into its solar and battery components.

Note: This guide assumes you already have grid power and consumption sensors set up (e.g., Power Import, Power Export, Power Consumption templates derived from your Shelly Pro 3EM phases). If you're starting from scratch, you'll also need those — see the full example config at the end of this post.

template:
  - sensor:
      # Total PV power from all 4 MPPT inputs (DC side)
      - name: "Power Solar Generation"
        unique_id: power_solar_generation
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set mppt1 = states('sensor.marstek_venus_modbus_mppt1_leistung')|float(0) %}
          {% set mppt2 = states('sensor.marstek_venus_modbus_mppt2_leistung')|float(0) %}
          {% set mppt3 = states('sensor.marstek_venus_modbus_mppt3_leistung')|float(0) %}
          {% set mppt4 = states('sensor.marstek_venus_modbus_mppt4_leistung')|float(0) %}
          {% set total = mppt1 + mppt2 + mppt3 + mppt4 %}
          {{ total if total > 0 else 0 }}

      # PV portion of AC output going to home
      - name: "Power Solar To Home"
        unique_id: power_solar_to_home
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set pv = states('sensor.power_solar_generation')|float(0) %}
          {% set ac_out = states('sensor.marstek_venus_d_ac_leistung')|float(0) %}
          {% set ac_positive = ac_out if ac_out > 0 else 0 %}
          {{ [pv, ac_positive] | min }}

      # Battery portion of AC output going to home
      - name: "Power Battery To Home"
        unique_id: power_battery_to_home
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set pv = states('sensor.power_solar_generation')|float(0) %}
          {% set ac_out = states('sensor.marstek_venus_d_ac_leistung')|float(0) %}
          {% set ac_positive = ac_out if ac_out > 0 else 0 %}
          {{ [ac_positive - pv, 0] | max }}

      # PV power going into battery charging
      - name: "Power Solar To Battery"
        unique_id: power_solar_to_battery
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set pv = states('sensor.power_solar_generation')|float(0) %}
          {% set ac_out = states('sensor.marstek_venus_d_ac_leistung')|float(0) %}
          {% set ac_positive = ac_out if ac_out > 0 else 0 %}
          {{ [pv - ac_positive, 0] | max }}

Why unique_id? Without it, Home Assistant won't let you rename sensors in the UI. Always add unique IDs to your template sensors.

Part 4: Riemann Sum Sensors (W → kWh)

The Energy Dashboard needs energy sensors (kWh), not power sensors (W). We use the Riemann Sum integration to convert them:

sensor:
  - platform: integration
    source: sensor.power_solar_generation
    name: energy_solar_sum
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.power_battery_to_home
    name: energy_battery_to_home_sum
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.power_solar_to_battery
    name: energy_solar_to_battery_sum
    unit_prefix: k
    round: 2
    method: left

Part 5: Optional Utility Meters

If you want daily and monthly tracking for your own dashboards:

utility_meter:
  energy_solar_daily:
    source: sensor.energy_solar_sum
    name: Energy Solar Daily
    cycle: daily
  energy_solar_monthly:
    source: sensor.energy_solar_sum
    name: Energy Solar Monthly
    cycle: monthly
  energy_battery_to_home_daily:
    source: sensor.energy_battery_to_home_sum
    name: Energy Battery To Home Daily
    cycle: daily
  energy_battery_to_home_monthly:
    source: sensor.energy_battery_to_home_sum
    name: Energy Battery To Home Monthly
    cycle: monthly
  energy_solar_to_battery_daily:
    source: sensor.energy_solar_to_battery_sum
    name: Energy Solar To Battery Daily
    cycle: daily
  energy_solar_to_battery_monthly:
    source: sensor.energy_solar_to_battery_sum
    name: Energy Solar To Battery Monthly
    cycle: monthly

Part 6: Friendly Names with customize

The Riemann sum sensors don't support unique_id in YAML, so you can't rename them in the UI. Use homeassistant: customize: to give them proper display names:

homeassistant:
  customize:
    sensor.energy_consumption_sum:
      friendly_name: "Energie Hausverbrauch"
    sensor.energy_solar_sum:
      friendly_name: "Energie PV-Erzeugung"
    sensor.energy_battery_to_home_sum:
      friendly_name: "Energie Battery to Home"
    sensor.energy_solar_to_battery_sum:
      friendly_name: "Energie PV to Battery"

Part 7: Energy Dashboard Configuration

After restarting Home Assistant, configure the Energy Dashboard:

Grid

  • Grid consumption: Your grid import energy sensor (e.g., sensor.shelly_pro_3em_saldierend_import)
  • Grid return: Your grid export energy sensor (e.g., sensor.shelly_pro_3em_saldierend_export)

Solar Panels (PV)

  • PV energy produced (Energie der PV-Erzeugung): sensor.energy_solar_sum
  • PV power (Leistung der PV-Erzeugung): sensor.power_solar_generation

Battery

  • Energy charged into battery (In die Batterie geladene Energie): sensor.energy_solar_to_battery_sum
  • Energy discharged from battery (Aus der Batterie entladene Energie): sensor.energy_battery_to_home_sum
  • Power measurement type (Art der Leistungsmessung): Standard
  • Battery power (Batterieleistung): sensor.power_battery_to_home

How the Math Works Out

Home Assistant calculates Home consumption as: Grid import - Grid export + PV - Battery charge + Battery discharge. With our sensors:

  • PV shows total production (e.g., 4.98 kWh)
  • Battery charge shows what went from PV into battery (e.g., 3.8 kWh)
  • Battery discharge shows what came from battery to home (e.g., 1.07 kWh)
  • Grid shows net import/export

Everything adds up correctly because we're reporting total PV production and letting HA's accounting model handle the rest.

Part 8: A Note on MPPT Throttling

When your battery approaches full capacity (above ~90% SoC), you'll notice the MPPT channels throttling - some dropping to 0W, others reducing power. This is normal battery management behavior (absorption/CV charging) to protect the cells. The last 5-10% always charges slowly, just like your phone.

If you have surplus feed-in enabled, the Venus D should still output 800W via AC even when the battery is full, passing solar through directly.

Troubleshooting

New sensors show "unknown"

Riemann sum sensors need time to accumulate data. Wait 5-10 minutes for active sensors, or until the relevant power flow starts (e.g., battery discharge won't show until evening).

Energy Dashboard shows "Statistiken nicht definiert"

Normal for newly created sensors. Statistics metadata generates automatically within 5 minutes of the first data point.

PV sensor doesn't appear in Energy Dashboard dropdown

Ensure your template sensor has device_class: power and state_class: measurement. Without these, the dashboard filters won't show it.

Home consumption shows 0 or negative

Make sure your Power Consumption template includes battery-to-home power in the calculation. This is easy to miss and makes home consumption appear too low.

Conclusion

The Marstek Venus D is a great battery, but its DC-coupled architecture doesn't map directly to Home Assistant's energy model. With some template sensor magic, we can split the AC output into its solar and battery components, giving the Energy Dashboard accurate numbers that actually add up.

The key insight is that the AC output is always a mix of solar pass-through and battery discharge. By using min(PV, AC output) for solar-to-home and max(AC output - PV, 0) for battery-to-home, we get a clean separation that works with HA's accounting model.

Full configuration.yaml Reference

Here's the complete energy-related section of my configuration.yaml for reference, including grid and consumption sensors:

# Friendly names for Riemann sum sensors
homeassistant:
  customize:
    sensor.energy_consumption_sum:
      friendly_name: "Energie Hausverbrauch"
    sensor.energy_solar_sum:
      friendly_name: "Energie PV-Erzeugung"
    sensor.energy_battery_to_home_sum:
      friendly_name: "Energie Battery to Home"
    sensor.energy_solar_to_battery_sum:
      friendly_name: "Energie PV to Battery"

template:
  - sensor:
      # Current total grid power (positive = import, negative = export)
      - name: "Power Current"
        unique_id: power_current
        unit_of_measurement: "W"
        state: >-
          {{ states('sensor.shellypro3em_xxx_phase_a_active_power')|float
           + states('sensor.shellypro3em_xx_phase_b_active_power')|float
           + xstates('sensor.shellypro3em_xxx_phase_c_active_power')|float }}
        availability: >-
          {{ [ states('sensor.shellypro3em_xxx_phase_a_active_power'),
               states('sensor.shellypro3em_xxx_phase_b_active_power'),
               states('sensor.shellypro3em_xxx_phase_c_active_power')
             ] | map('is_number') | min }}

      # Power import from grid (positive only)
      - name: "Power Import"
        unique_id: power_import
        unit_of_measurement: "W"
        state: >-
          {% set total = states('sensor.power_current')|float(0) %}
          {{ total if total > 0 else 0 }}

      # Power export to grid (positive only)
      - name: "Power Export"
        unique_id: power_export
        unit_of_measurement: "W"
        state: >-
          {% set total = states('sensor.power_current')|float(0) %}
          {{ (total * -1) if total < 0 else 0 }}

      # Total home power consumption
      - name: "Power Consumption"
        unique_id: power_consumption
        unit_of_measurement: "W"
        state: >-
          {% set export = states('sensor.power_export')|float(0) %}
          {% set solar = states('sensor.power_solar_to_home')|float(0) %}
          {% set battery = states('sensor.power_battery_to_home')|float(0) %}
          {% set import = states('sensor.power_import')|float(0) %}
          {% if export > 0 %}
            {{ [solar + battery - export, 0] | max }}
          {% else %}
            {{ import + solar + battery }}
          {% endif %}

      # Total PV power from all 4 MPPT inputs (DC side)
      - name: "Power Solar Generation"
        unique_id: power_solar_generation
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set mppt1 = states('sensor.marstek_venus_modbus_mppt1_leistung')|float(0) %}
          {% set mppt2 = states('sensor.marstek_venus_modbus_mppt2_leistung')|float(0) %}
          {% set mppt3 = states('sensor.marstek_venus_modbus_mppt3_leistung')|float(0) %}
          {% set mppt4 = states('sensor.marstek_venus_modbus_mppt4_leistung')|float(0) %}
          {% set total = mppt1 + mppt2 + mppt3 + mppt4 %}
          {{ total if total > 0 else 0 }}

      # PV portion of AC output going to home
      - name: "Power Solar To Home"
        unique_id: power_solar_to_home
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set pv = states('sensor.power_solar_generation')|float(0) %}
          {% set ac_out = states('sensor.marstek_venus_d_ac_leistung')|float(0) %}
          {% set ac_positive = ac_out if ac_out > 0 else 0 %}
          {{ [pv, ac_positive] | min }}

      # Battery portion of AC output going to home
      - name: "Power Battery To Home"
        unique_id: power_battery_to_home
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set pv = states('sensor.power_solar_generation')|float(0) %}
          {% set ac_out = states('sensor.marstek_venus_d_ac_leistung')|float(0) %}
          {% set ac_positive = ac_out if ac_out > 0 else 0 %}
          {{ [ac_positive - pv, 0] | max }}

      # PV power going into battery charging
      - name: "Power Solar To Battery"
        unique_id: power_solar_to_battery
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >-
          {% set pv = states('sensor.power_solar_generation')|float(0) %}
          {% set ac_out = states('sensor.marstek_venus_d_ac_leistung')|float(0) %}
          {% set ac_positive = ac_out if ac_out > 0 else 0 %}
          {{ [pv - ac_positive, 0] | max }}

sensor:
  - platform: integration
    source: sensor.power_consumption
    name: energy_consumption_sum
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.power_solar_generation
    name: energy_solar_sum
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.power_battery_to_home
    name: energy_battery_to_home_sum
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.power_solar_to_battery
    name: energy_solar_to_battery_sum
    unit_prefix: k
    round: 2
    method: left

utility_meter:
  energy_import_daily:
    source: sensor.shelly_pro_3em_saldierend_import
    name: Energy Import Daily
    cycle: daily
  energy_import_monthly:
    source: sensor.shelly_pro_3em_saldierend_import
    name: Energy Import Monthly
    cycle: monthly
  energy_export_daily:
    source: sensor.shelly_pro_3em_saldierend_export
    name: Energy Export Daily
    cycle: daily
  energy_export_monthly:
    source: sensor.shelly_pro_3em_saldierend_export
    name: Energy Export Monthly
    cycle: monthly
  energy_consumption_daily:
    source: sensor.energy_consumption_sum
    name: Energy Consumption Daily
    cycle: daily
  energy_consumption_monthly:
    source: sensor.energy_consumption_sum
    name: Energy Consumption Monthly
    cycle: monthly
  energy_solar_daily:
    source: sensor.energy_solar_sum
    name: Energy Solar Daily
    cycle: daily
  energy_solar_monthly:
    source: sensor.energy_solar_sum
    name: Energy Solar Monthly
    cycle: monthly
  energy_battery_to_home_daily:
    source: sensor.energy_battery_to_home_sum
    name: Energy Battery To Home Daily
    cycle: daily
  energy_battery_to_home_monthly:
    source: sensor.energy_battery_to_home_sum
    name: Energy Battery To Home Monthly
    cycle: monthly
  energy_solar_to_battery_daily:
    source: sensor.energy_solar_to_battery_sum
    name: Energy Solar To Battery Daily
    cycle: daily
  energy_solar_to_battery_monthly:
    source: sensor.energy_solar_to_battery_sum
    name: Energy Solar To Battery Monthly
    cycle: monthly

Additional Resources