Integrating Smartfox energy manager EM2 in Home Assistant

The objective is to read and control the Smartfox energy manager with Home Assistant. I am using it in combination with a wall charger for surplus charging of an electric vehicle.

The communication with the device acts via Modbus TCP. You find a table of modbus registers on:
https://smartfox.at/wp-content/uploads/2022/12/Modbus-Register-SMARTFOX-Pro-SMARTFOX-Pro-2-v22e-00.01.03.10.xlsx

First assign a static IP address to the Smartfox device. Home Assistant must be in the same network/ LAN as the Smartfox device.

Maybe you need to add the modbus integration on Home Assistant.

Add the following lines to your configuration.yaml an replace the IP address host: with your IP of the smartfox device:

modbus:
  - name: "smartfox"
    type: tcp
    host: 192.168.0.4
    port: 502
    timeout: 5
    delay: 5
    sensors:
      - name: "Energy from grid"
        unique_id: smartfox.energy_from_grid
        unit_of_measurement: Wh
        data_type: uint64
        input_type: holding
        slave: 1
        address: 40999
        scan_interval: 100    
      - name: "Energy into grid"
        unique_id: smartfox.energy_into_grid
        unit_of_measurement: Wh
        data_type: uint64
        input_type: holding
        slave: 1
        address: 41003
        scan_interval: 100
      - name: "Energy smartfox"
        unique_id: smartfox.energy_smartfox
        unit_of_measurement: Wh
        data_type: uint64
        input_type: holding
        slave: 1
        address: 41007
        scan_interval: 100
      - name: "Day Energy from grid"
        unique_id: smartfox.day_energy_from_grid
        unit_of_measurement: Wh
        data_type: uint32
        input_type: holding
        slave: 1
        address: 41011
        scan_interval: 100
      - name: "Day Energy into grid"
        unique_id: smartfox.day_energy_into_grid
        unit_of_measurement: Wh
        data_type: uint32
        input_type: holding
        slave: 1
        address: 41013
        scan_interval: 100
      - name: "Day Energy Smartfox"
        unique_id: smartfox.day_energy_smartfox
        unit_of_measurement: Wh
        data_type: uint32
        input_type: holding
        slave: 1
        address: 41015
        scan_interval: 100
      - name: "Power total"
        unique_id: smartfox.power_total
        unit_of_measurement: W
        data_type: int32
        input_type: holding
        slave: 1
        address: 41017
        scan_interval: 100
      - name: "Car charge energy total"
        unique_id: smartfox.car_charge_1_energy_total
        unit_of_measurement: Wh
        data_type: uint64
        input_type: holding
        slave: 1
        address: 41599
        scan_interval: 100
      - name: "Car charge energy pres"
        unique_id: smartfox.car_charge_1_energy_pres
        unit_of_measurement: Wh
        data_type: uint32
        input_type: holding
        slave: 1
        address: 41603
        scan_interval: 100
      - name: "Car charge power"
        unique_id: smartfox.car_charge_1_power
        unit_of_measurement: W
        data_type: uint32
        input_type: holding
        slave: 1
        address: 41605
        scan_interval: 100
      - name: "Car charge man. charging value"
        unique_id: smartfox.car_charge_1_man_charging_value
        unit_of_measurement: "%"
        data_type: uint16
        input_type: holding
        slave: 1
        address: 41608
        scan_interval: 100
    switches:
      - name: "Car charge mode (Ein = Man.)"
        unique_id: smartfox.car_charge_1_charge_mode
        write_type: holdings
        slave: 1
        address: 41607
        command_on: 1
        command_off: 0
        verify:
            delay: 10

With the switch you can control the charging mode (OFF = surplus, ON = force charging).
Note that there is an offset of -1 to the modbus addresses.

I added a KNX switch in Home Assistant and added automations for updating the switch status (both the KNX switch and the modbus switch updating each other). Thus it is possible to set the charging mode with an external KNX switch or a KNX touch panel. I am using Busch-SmartTouch 10 therefore.

I hope this helps someone.

Hi,

first of all, thanks a lot for your great work and the detailed instructions! :pray:
I managed to get the integration working and can see the entities in Home Assistant.

However, I can’t assign them in the Energy Dashboard – they don’t show up as selectable sources there.
Were you able to feed your Smartfox data into the Energy Dashboard as well?

Best regards

You need to add ‘device_class: energy’ in the configuration.yaml for the relevant sensors. Only entities with this device_class can be selected in the energy dashboard.

For example:

    sensors:
      - name: "Energy from grid"
        unique_id: smartfox.energy_from_grid
        unit_of_measurement: Wh
        data_type: uint64
        input_type: holding
        slave: 1
        address: 40999
        scan_interval: 100
        device_class: energy

Hello, thank you a lot for the hint about the divice_class, unfortuned it doesnt solve my problem to get the data from the smartfox to the energy dash board. Is there anybody out there who made it happen ?

Hi everyone,

After a few hours of tinkering, I managed to get my power dashboard working by reading the values.xml file from the Smartfox Pro 2 EM3, and I’d like to share the results with you. Maybe it’ll save some of you a bit of time :slight_smile:

smartfoxrest.yaml: Adjust the resource IP!

###############################################################
# SMARTFOX – REST SENSOR DATA
###############################################################
rest:
  - resource: "http://192.168.0.254/values.xml"
    scan_interval: 15
    timeout: 5
    headers:
      Accept: "application/xml"
    sensor:

      # Netz Gesamt (positiv = Bezug; negativ = Einspeisung)
      - name: "Smartfox Leistung Netz Gesamt"
        unique_id: smartfox_leistung_netz_gesamt
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"detailsPowerValue","#text":"([\-0-9\.]+) W"') %}
          {{ v[0] | float(0) if v else 0 }}

      # Netzbezug (nur positive Werte)
      - name: "Smartfox Leistung nur Netzbezug"
        unique_id: smartfox_leistung_nur_netzbezug
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"detailsPowerValue","#text":"([\-0-9\.]+) W"') %}
          {% set p = v[0] | float(0) if v else 0 %}
          {{ p if p > 0 else 0 }}

      # Einspeisung (negative Werte → positive Leistung)
      - name: "Smartfox Leistung nur Einspeisung"
        unique_id: smartfox_leistung_nur_einspeisung
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"detailsPowerValue","#text":"([\-0-9\.]+) W"') %}
          {% set p = v[0] | float(0) if v else 0 %}
          {{ (p * -1) if p < 0 else 0 }}

      # PV Produktion
      - name: "Smartfox Leistung PV"
        unique_id: smartfox_leistung_pv
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"wr1PowerValueinW","#text":"([\-0-9\.]+) W"') %}
          {% set p = v[0] | float(0) if v else 0 %}
          {{ p if p > 0 else 0 }}

      # PV Energie Gesamt (Smartfox liefert kWh)
      - name: "Smartfox Energie PV"
        unique_id: smartfox_energie_pv
        unit_of_measurement: "kWh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {% set v = value | regex_findall('"@id":"wr1EnergyValue","#text":"([\-0-9\.]+) kWh"') %}
          {{ v[0] | float(0) if v else 0 }}

      # Tagesbezug (Wh)
      - name: "Smartfox Energie Tagesbezug"
        unique_id: smartfox_energie_tagesbezug
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {% set v = value | regex_findall('"@id":"eDayValue","#text":"([\-0-9\.]+) Wh"') %}
          {{ v[0] | float(0) if v else 0 }}

      # Tageseinspeisung (Wh)
      - name: "Smartfox Energie Tageseinspeisung"
        unique_id: smartfox_energie_tageseinspeisung
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {% set v = value | regex_findall('"@id":"eDayToGridValue","#text":"([\-0-9\.]+) Wh"') %}
          {{ v[0] | float(0) if v else 0 }}

      # Tagesproduktion (Wh)
      - name: "Smartfox Energie Tagesproduktion"
        unique_id: smartfox_energie_tagesproduktion
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {% set v = value | regex_findall('"@id":"hidWr1EnergyDay","#text":"([\-0-9\.]+)"') %}
          {{ v[0] | float(0) if v else 0 }}


###############################################################
# TEMPLATE-SENSOREN (ALLE IN EINEM BLOCK!)
###############################################################
template:
  - sensor:

      # Hausverbrauch = PV + Netz-Gesamt
      - name: "Smartfox Leistung Hausverbrauch"
        unique_id: smartfox_leistung_hausverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_total = states('sensor.smartfox_leistung_netz_gesamt') | float(0) %}
          {{ pv + grid_total }}

      # Eigenverbrauch = PV - Einspeisung
      - name: "Smartfox Leistung Eigenverbrauch"
        unique_id: smartfox_leistung_eigenverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_out = states('sensor.smartfox_leistung_nur_einspeisung') | float(0) %}
          {{ pv - grid_out }}

      # Autarkiegrad = Eigenverbrauch / Hausverbrauch * 100
      - name: "Smartfox Autarkiegrad"
        unique_id: smartfox_autarkiegrad
        unit_of_measurement: "%"
        state: >
          {% set haus = states('sensor.smartfox_leistung_hausverbrauch') | float(0) %}
          {% set eigen = states('sensor.smartfox_leistung_eigenverbrauch') | float(0) %}
          {% if haus > 0 %}
            {{ (eigen / haus * 100) | round(1) }}
          {% else %}
            0
          {% endif %}

      # Hausverbrauchsquote = Hausverbrauch / PV * 100 oder 100% wenn Hausverbrauch >= PV
      - name: "Smartfox Hausverbrauchsquote"
        unique_id: smartfox_hausverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set haus = states('sensor.smartfox_leistung_hausverbrauch') | float(0) %}

          {% if pv <= 0 %}
            0
          {% elif haus >= pv %}
            100
          {% else %}
            {{ (haus / pv * 100) | round(1) }}
          {% endif %}

      # Eigenverbrauchsquote = Eigenverbrauch / PV * 100
      - name: "Smartfox Eigenverbrauchsquote"
        unique_id: smartfox_eigenverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set eigen = states('sensor.smartfox_leistung_eigenverbrauch') | float(0) %}
          {% if pv > 0 %}
            {{ (eigen / pv * 100) | round(1) }}
          {% else %}
            0
          {% endif %}
 

Dashboard YAML

title: Smartfox Energie
views:
  - title: Übersicht
    icon: mdi:solar-power
    cards:
      - type: gauge
        name: PV Produktion
        entity: sensor.smartfox_leistung_pv
        min: 0
        max: 6500
        needle: true
        severity:
          green: 0
          yellow: 6000
          red: 6300
      - type: gauge
        entity: sensor.smartfox_hausverbrauchsquote
        needle: true
        max: 100
        min: 0
        severity:
          green: 10
          yellow: 5
          red: 0
        name: Hausverbrauchsquote
      - type: gauge
        name: Einspeisung
        entity: sensor.smartfox_leistung_nur_einspeisung_2
        min: 0
        max: 6500
        needle: false
      - type: gauge
        entity: sensor.smartfox_leistung_hausverbrauch
        min: 0
        needle: false
        max: 8000
        name: Hausverbrauch
      - type: gauge
        entity: sensor.smartfox_autarkiegrad
        min: 0
        needle: true
        max: 100
        name: Autarkiegrad
        severity:
          green: 66
          yellow: 33
          red: 0
      - type: gauge
        name: Netzbezug
        entity: sensor.smartfox_leistung_nur_netzbezug_2
        min: 0
        max: 8000
        needle: false
        severity:
          green: 0
          yellow: 250
          red: 500
      - type: history-graph
        title: Leistungen (24h)
        hours_to_show: 24
        entities:
          - entity: sensor.smartfox_leistung_netz_gesamt
          - entity: sensor.smartfox_leistung_pv
        logarithmic_scale: false
    type: masonry

EDIT: I extended the sensors for daily energy statistics. Use smartfox_energie_tagesbezug and smartfox_energie_tageseinspeisung to configure the energy dashboard. With smartfox_energie_tagesproduktion the solar production can be also added.

XML reading generally works very reliably. Via Modbus, I was only able to read the total power data:

smartfox.yaml

modbus:
  - name: "smartfox"
    type: tcp
    host: 192.168.0.254
    port: 502

    sensors:
      - name: "Gesamtleistung"
        unique_id: smartfox_total_power
        unit_of_measurement: "W"
        slave: 1
        address: 41017
        data_type: int32
        input_type: holding
        device_class: power
        state_class: measurement
        scan_interval: 5

Hi everyone,

I found a way to control the SMARTFOX EV charger from Home Assistant without Modbus writes (which don’t work reliably on the EM3, and possibly EM2).

The SMARTFOX web UI uses an undocumented local HTTP CGI endpoint to switch charger modes:

GET http://<SMARTFOX-IP>/setswcc.cgi?num=0&mode=<mode>&value=<percent>

Modes:

mode Name
0 A (Surplus/Automatic)
1 M (Forced charging)
2 A+ (Automatic+)
3 OFF
4 M+

value = charging power in % (0–100), relevant for modes 1 and 4.

No authentication required — just a plain GET request.

Home Assistant Setup

configuration.yaml:

rest_command:
  smartfox_charger_mode:
    url: "http://<SMARTFOX-IP>/setswcc.cgi?num=0&mode={{ mode }}&value={{ value }}"
    method: GET

Note: requires HA restart (rest_command doesn’t support YAML reload).

Example automation — force charge at night if SoC < 70%:

- id: smartfox_ev_force_charge
  alias: "SMARTFOX: Force charge when below 70%"
  triggers:
    - trigger: time
      at: "00:00:00"
    - trigger: time
      at: "01:00:00"
    - trigger: time
      at: "02:00:00"
    - trigger: time
      at: "03:00:00"
    - trigger: time
      at: "04:00:00"
    - trigger: time
      at: "05:00:00"
  conditions:
    - condition: numeric_state
      entity_id: sensor.audi_e_tron_state_of_charge
      below: 70
  actions:
    - action: rest_command.smartfox_charger_mode
      data:
        mode: "1"
        value: "100"

Switch back to surplus charging when SoC >= 70%:

- id: smartfox_ev_surplus_charge
  alias: "SMARTFOX: Surplus charging above 70%"
  triggers:
    - trigger: numeric_state
      entity_id: sensor.audi_e_tron_state_of_charge
      above: 69
  actions:
    - action: rest_command.smartfox_charger_mode
      data:
        mode: "3"
        value: "0"
    - delay:
        seconds: 5
    - action: rest_command.smartfox_charger_mode
      data:
        mode: "0"
        value: "100"

Tip: When switching from forced charging (M) to automatic (A), stop charging first (mode=3/OFF), wait a few seconds, then switch to automatic — otherwise charging may continue from the grid.

How I found this

I analyzed the minified JavaScript of the SMARTFOX web UI. The hideDeviceModal() function calls sendCommand(deviceModalCommand), which sends a GET XMLHttpRequest. The charger mode buttons set deviceModalCommand = 'setswcc.cgi?num=...'.

Why Modbus writes don’t work

On the EM3 (firmware 00.01.13.10):

  • Function Code 6 (Write Single Register) is rejected entirely
  • Function Code 16 (Write Multiple Registers) is accepted but values are immediately reset by the internal controller (~200ms)
  • The official Modbus register table is for Pro/Pro 2, not EM3

Other CGI endpoints (not tested for write)

  • setswaout.cgi — analog output (boiler) control
  • setswrel.cgi — relay control
  • setvb.cgi — consumption controller

Tested on: SMARTFOX EM3, firmware 00.01.13.10, with SMARTFOX Pro Charger (RS485).

Hope this helps others!

Hi Michael,
Thnx a lot for the Script, this helps a lot.

I am trying to build Up an amortisation rate in HA, therefore I need the Data from smartfox.

Do you know we’re I Paste the yaml Scripts? All in the configuration.yaml?

Hi,

you can edit in the configuration.yaml directly or create a folder packages and extend the configuration.yaml by:

homeassistant:
  packages: !include_dir_named packages

Then you can add yaml files more separated.

BTW: With the help of AI I optimized my XML reading a lot. It now correctly resets the values to 0 at midnight, and implausible values are filtered out (anti-spike). Here is my latest setup:

smartfoxrest.yaml

###############################################################
# SMARTFOX – REST SENSOR DATA
###############################################################
rest:
  - resource: "http://192.168.77.15/values.xml"
    scan_interval: 15
    timeout: 5
    headers:
      Accept: "application/xml"
    sensor:

      # Netz Gesamt (positiv = Bezug; negativ = Einspeisung)
      - name: "Smartfox Leistung Netz Gesamt"
        unique_id: smartfox_leistung_netz_gesamt
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"detailsPowerValue","#text":"([\-0-9\.]+) W"') %}
          {{ v[0] | float(0) if v else 0 }}

      # Netzbezug (nur positive Werte)
      - name: "Smartfox Leistung nur Netzbezug"
        unique_id: smartfox_leistung_nur_netzbezug
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"detailsPowerValue","#text":"([\-0-9\.]+) W"') %}
          {% set p = v[0] | float(0) if v else 0 %}
          {{ p if p > 0 else 0 }}

      # Einspeisung (negative Werte → positive Leistung)
      - name: "Smartfox Leistung nur Einspeisung"
        unique_id: smartfox_leistung_nur_einspeisung
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"detailsPowerValue","#text":"([\-0-9\.]+) W"') %}
          {% set p = v[0] | float(0) if v else 0 %}
          {{ (p * -1) if p < 0 else 0 }}

      # PV Produktion
      - name: "Smartfox Leistung PV"
        unique_id: smartfox_leistung_pv
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        value_template: >
          {% set v = value | regex_findall('"@id":"wr1PowerValueinW","#text":"([\-0-9\.]+) W"') %}
          {% set p = v[0] | float(0) if v else 0 %}
          {{ p if p > 0 else 0 }}

      # PV Energie Gesamt (Smartfox liefert kWh)
      - name: "Smartfox Energie PV"
        unique_id: smartfox_energie_pv
        unit_of_measurement: "kWh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {% set v = value | regex_findall('"@id":"wr1EnergyValue","#text":"([\-0-9\.]+) kWh"') %}
          {{ v[0] | float(0) if v else 0 }}

      # Tagesbezug (Wh) – robust + Neustart-sicher + unknown-frei + Anti-Spike
      - name: "Smartfox Energie Tagesbezug"
        unique_id: smartfox_energie_tagesbezug
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {# --- XML-Wert extrahieren --- #}
          {% set v = value | regex_findall('"@id":"hidFromGridEnergyDay","#text":"([0-9]+)"') %}

          {# --- alten Wert laden (unknown → 0) --- #}
          {% set old_state = states('sensor.smartfox_energie_tagesbezug') %}
          {% if old_state in ['unknown','unavailable','none'] %}
            {% set old = 0 %}
            {% set has_old = false %}
          {% else %}
            {% set old = old_state | float %}
            {% set has_old = (old > 0) %}
          {% endif %}

          {# --- Wenn XML keinen Wert liefert → alten Wert behalten --- #}
          {% if not v %}
            {{ old }}
          {% else %}
            {% set new = v[0] | float %}

            {# --- Zeitkontext --- #}
            {% set last = states.sensor.smartfox_energie_tagesbezug.last_changed %}
            {% set now_dt = now() %}
            {% set now_time = now().time() %}
            {% set today = now().date() %}
            {% set last_day = last.date() if last is not none else today %}
            {% set age = (now_dt - last).total_seconds() if last is not none else 999999 %}

            {# --- 00:00 Reset --- #}
            {% if now_time == strptime('00:00','%H:%M').time() %}
              {{ 0 }}

            {# --- Tageswechsel --- #}
            {% elif last_day != today %}
              {{ new }}

            {# --- Erster gültiger Wert nach Neustart --- #}
            {% elif not has_old %}
              {{ new }}

            {# --- Rückwärts-Sprung blocken --- #}
            {% elif new < old %}
              {{ old }}

            {# --- 0 nur um 00:00 akzeptieren --- #}
            {% elif new == 0 %}
              {{ old }}

            {# --- Unplausibler Sprung nur im etablierten Betrieb --- #}
            {% elif has_old and (new - old) > 2000 and age < 120 %}
              {{ old }}

            {# --- Normalfall --- #}
            {% else %}
              {{ new }}
            {% endif %}
          {% endif %}


      # Tageseinspeisung (Wh) – robust + Nacht-Blockade + Neustart-sicher + unknown-frei
      - name: "Smartfox Energie Tageseinspeisung"
        unique_id: smartfox_energie_tageseinspeisung
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {# --- XML-Wert extrahieren --- #}
          {% set v = value | regex_findall('"@id":"hidToGridEnergyDay","#text":"([0-9]+)"') %}

          {# --- alten Wert laden (unknown → 0) --- #}
          {% set old_state = states('sensor.smartfox_energie_tageseinspeisung') %}
          {% if old_state in ['unknown','unavailable','none'] %}
            {% set old = 0 %}
            {% set has_old = false %}
          {% else %}
            {% set old = old_state | float %}
            {% set has_old = (old > 0) %}
          {% endif %}

          {# --- Wenn XML keinen Wert liefert → alten Wert behalten --- #}
          {% if not v %}
            {{ old }}
          {% else %}
            {% set new = v[0] | float %}

            {# --- Zeitkontext --- #}
            {% set last = states.sensor.smartfox_energie_tageseinspeisung.last_changed %}
            {% set now_dt = now() %}
            {% set now_time = now().time() %}
            {% set today = now().date() %}
            {% set last_day = last.date() if last is not none else today %}
            {% set age = (now_dt - last).total_seconds() if last is not none else 999999 %}

            {# --- 00:00 Reset --- #}
            {% if now_time == strptime('00:00','%H:%M').time() %}
              {{ 0 }}

            {# --- Nacht-Blockade (22:00–03:00) --- #}
            {% elif now_time >= strptime('22:00','%H:%M').time()
                  or now_time <= strptime('03:00','%H:%M').time() %}
              {# Wenn noch kein alter Wert existiert → neuen Wert akzeptieren #}
              {% if not has_old %}
                {{ new }}
              {% else %}
                {{ old }}
              {% endif %}

            {# --- Tageswechsel --- #}
            {% elif last_day != today %}
              {{ new }}

            {# --- Erster gültiger Wert nach Neustart --- #}
            {% elif not has_old %}
              {{ new }}

            {# --- Rückwärts-Sprung blocken --- #}
            {% elif new < old %}
              {{ old }}

            {# --- 0 nur um 00:00 akzeptieren --- #}
            {% elif new == 0 %}
              {{ old }}

            {# --- Unplausibler Sprung nur im etablierten Betrieb --- #}
            {% elif has_old and (new - old) > 2000 and age < 120 %}
              {{ old }}

            {# --- Normalfall --- #}
            {% else %}
              {{ new }}
            {% endif %}
          {% endif %}


      # Tagesproduktion (Wh) – robust + Neustart-sicher + unknown-frei + Anti-Spike
      - name: "Smartfox Energie Tagesproduktion"
        unique_id: smartfox_energie_tagesproduktion
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {# --- XML-Wert extrahieren --- #}
          {% set v = value | regex_findall('"@id":"hidWr1EnergyDay","#text":"([0-9]+)"') %}

          {# --- alten Wert laden (unknown → 0) --- #}
          {% set old_state = states('sensor.smartfox_energie_tagesproduktion') %}
          {% if old_state in ['unknown','unavailable','none'] %}
            {% set old = 0 %}
            {% set has_old = false %}
          {% else %}
            {% set old = old_state | float %}
            {% set has_old = (old > 0) %}
          {% endif %}

          {# --- Wenn XML keinen Wert liefert → alten Wert behalten --- #}
          {% if not v %}
            {{ old }}
          {% else %}
            {% set new = v[0] | float %}

            {# --- Zeitkontext --- #}
            {% set last = states.sensor.smartfox_energie_tagesproduktion.last_changed %}
            {% set now_dt = now() %}
            {% set now_time = now().time() %}
            {% set today = now().date() %}
            {% set last_day = last.date() if last is not none else today %}
            {% set age = (now_dt - last).total_seconds() if last is not none else 999999 %}

            {# --- 00:00 Reset --- #}
            {% if now_time == strptime('00:00','%H:%M').time() %}
              {{ 0 }}

            {# --- Tageswechsel --- #}
            {% elif last_day != today %}
              {{ new }}

            {# --- Erster gültiger Wert nach Neustart --- #}
            {% elif not has_old %}
              {{ new }}

            {# --- Rückwärts-Sprung blocken --- #}
            {% elif new < old %}
              {{ old }}

            {# --- 0 nur um 00:00 akzeptieren --- #}
            {% elif new == 0 %}
              {{ old }}

            {# --- Unplausibler Sprung nur im etablierten Betrieb --- #}
            {% elif has_old and (new - old) > 2000 and age < 120 %}
              {{ old }}

            {# --- Normalfall --- #}
            {% else %}
              {{ new }}
            {% endif %}
          {% endif %}


      # Tagesverbrauch (Wh) – robust + Neustart-sicher + unknown-frei + Anti-Spike
      - name: "Smartfox Energie Tagesverbrauch"
        unique_id: smartfox_energie_tagesverbrauch
        unit_of_measurement: "Wh"
        device_class: energy
        state_class: total_increasing
        value_template: >
          {# --- XML-Wert extrahieren --- #}
          {% set v = value | regex_findall('"@id":"hidTotalEnergyDay","#text":"([0-9]+) Wh"') %}

          {# --- alten Wert laden (unknown → 0) --- #}
          {% set old_state = states('sensor.smartfox_energie_tagesverbrauch') %}
          {% if old_state in ['unknown','unavailable','none'] %}
            {% set old = 0 %}
            {% set has_old = false %}
          {% else %}
            {% set old = old_state | float %}
            {% set has_old = (old > 0) %}
          {% endif %}

          {# --- Wenn XML keinen Wert liefert → alten Wert behalten --- #}
          {% if not v %}
            {{ old }}
          {% else %}
            {% set new = v[0] | float %}

            {# --- Zeitkontext --- #}
            {% set last = states.sensor.smartfox_energie_tagesverbrauch.last_changed %}
            {% set now_dt = now() %}
            {% set now_time = now().time() %}
            {% set today = now().date() %}
            {% set last_day = last.date() if last is not none else today %}
            {% set age = (now_dt - last).total_seconds() if last is not none else 999999 %}

            {# --- 00:00 Reset --- #}
            {% if now_time == strptime('00:00','%H:%M').time() %}
              {{ 0 }}

            {# --- Tageswechsel --- #}
            {% elif last_day != today %}
              {{ new }}

            {# --- Erster gültiger Wert nach Neustart --- #}
            {% elif not has_old %}
              {{ new }}

            {# --- Rückwärts-Sprung blocken --- #}
            {% elif new < old %}
              {{ old }}

            {# --- 0 nur um 00:00 akzeptieren --- #}
            {% elif new == 0 %}
              {{ old }}

            {# --- Unplausibler Sprung nur im etablierten Betrieb --- #}
            {% elif has_old and (new - old) > 2000 and age < 120 %}
              {{ old }}

            {# --- Normalfall --- #}
            {% else %}
              {{ new }}
            {% endif %}
          {% endif %}


###############################################################
# TEMPLATE-SENSOREN (ALLE IN EINEM BLOCK!)
###############################################################
template:
  - sensor:

      # Hausverbrauch = PV + Netz-Gesamt
      - name: "Smartfox Leistung Hausverbrauch"
        unique_id: smartfox_leistung_hausverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_total = states('sensor.smartfox_leistung_netz_gesamt') | float(0) %}
          {{ pv + grid_total }}

      # Eigenverbrauch = PV - Einspeisung
      - name: "Smartfox Leistung Eigenverbrauch"
        unique_id: smartfox_leistung_eigenverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_out = states('sensor.smartfox_leistung_nur_einspeisung') | float(0) %}
          {{ pv - grid_out }}

      # Hausverbrauchsquote = Hausverbrauch / PV * 100 oder 100% wenn Hausverbrauch >= PV
      - name: "Smartfox Hausverbrauchsquote"
        unique_id: smartfox_hausverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set haus = states('sensor.smartfox_leistung_hausverbrauch') | float(0) %}

          {% if pv <= 0 %}
            0
          {% elif haus >= pv %}
            100
          {% else %}
            {{ (haus / pv * 100) | round(1) }}
          {% endif %}

      # Eigenverbrauchsquote = Eigenverbrauch / PV * 100
      - name: "Smartfox Eigenverbrauchsquote"
        unique_id: smartfox_eigenverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set eigen = states('sensor.smartfox_leistung_eigenverbrauch') | float(0) %}
          {% if pv > 0 %}
            {{ (eigen / pv * 100) | round(1) }}
          {% else %}
            0
          {% endif %}
 
      # Smartfox Tagesbezug kWh
      - name: "Smartfox Tagesbezug kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: "{{ states('sensor.smartfox_energie_tagesbezug') | float / 1000 }}"

      # Smartfox Tageseinspeisung kWh
      - name: "Smartfox Tageseinspeisung kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: "{{ states('sensor.smartfox_energie_tageseinspeisung') | float / 1000 }}"

      # Smartfox Tagesproduktion kWh
      - name: "Smartfox Tagesproduktion kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: "{{ states('sensor.smartfox_energie_tagesproduktion') | float / 1000 }}"

      # Smartfox Tagesverbrauch kWh
      - name: "Smartfox Tagesverbrauch kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: "{{ states('sensor.smartfox_energie_tagesverbrauch') | float / 1000 }}"

Thanks a lot for your reply, and your acual Code.

Important for me are Tageseinspeisung and Eigenverbrauch to calculate the amortisation rate on a daily Basis with a sum Up of These values as cost saving against PV invest. Don’t know If I will reach my Goal, but give it a try.

That sounds also interesting to me and I did my own amortization dashboard:

Here’s my extension of smartfoxrest.yaml:

template:
  - sensor:

      # Hausverbrauch = PV + Netz-Gesamt
      - name: "Smartfox Leistung Hausverbrauch"
        unique_id: smartfox_leistung_hausverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_total = states('sensor.smartfox_leistung_netz_gesamt') | float(0) %}
          {{ pv + grid_total }}

      # Eigenverbrauch = PV - Einspeisung
      - name: "Smartfox Leistung Eigenverbrauch"
        unique_id: smartfox_leistung_eigenverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_out = states('sensor.smartfox_leistung_nur_einspeisung') | float(0) %}
          {{ pv - grid_out }}

      # Hausverbrauchsquote = Hausverbrauch / PV * 100 oder 100% wenn Hausverbrauch >= PV
      - name: "Smartfox Hausverbrauchsquote"
        unique_id: smartfox_hausverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set haus = states('sensor.smartfox_leistung_hausverbrauch') | float(0) %}

          {% if pv <= 0 %}
            0
          {% elif haus >= pv %}
            100
          {% else %}
            {{ (haus / pv * 100) | round(1) }}
          {% endif %}

      # Eigenverbrauchsquote = Eigenverbrauch / PV * 100
      - name: "Smartfox Eigenverbrauchsquote"
        unique_id: smartfox_eigenverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set eigen = states('sensor.smartfox_leistung_eigenverbrauch') | float(0) %}
          {% if pv > 0 %}
            {{ (eigen / pv * 100) | round(1) }}
          {% else %}
            0
          {% endif %}
 
      # Smartfox Tagesbezug kWh
      - name: "Smartfox Tagesbezug kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tagesbezug') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tageseinspeisung kWh
      - name: "Smartfox Tageseinspeisung kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tageseinspeisung') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tagesproduktion kWh
      - name: "Smartfox Tagesproduktion kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tagesproduktion') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tagesverbrauch kWh
      - name: "Smartfox Tagesverbrauch kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tagesverbrauch') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tages Eigenverbrauch kWh = Tagesproduktion - Tageseinspeisung
      - name: "PV Tages Eigenverbrauch kWh"
        unit_of_measurement: "kWh"
        state: >
          {% set prod = states('sensor.smartfox_energie_tagesproduktion') | float / 1000 %}
          {% set eins = states('sensor.smartfox_energie_tageseinspeisung') | float / 1000 %}
          {{ (prod - eins) | round(3) }}

      # Smartfox Tages Eigenverbrauch Ersparnis € = Tages Eigenverbrauch kWh * Strompreis Bezug
      - name: "PV Tages Eigenverbrauch Ersparnis"
        unit_of_measurement: "€"
        state: >
          {% set eigen = states('sensor.pv_tages_eigenverbrauch_kwh') | float %}
          {% set preis = states('input_number.pv_strompreis_bezug') | float %}
          {{ (eigen * preis) | round(2) }}

      # Smartfox Tages Einspeise Einnahmen € = Tageseinspeisung kWh * Einspeisevergütung
      - name: "PV Tages Einspeise Einnahmen"
        unit_of_measurement: "€"
        state: >
          {% set eins = states('sensor.smartfox_energie_tageseinspeisung') | float / 1000 %}
          {% set verg = states('input_number.pv_einspeisevergutung') | float %}
          {{ (eins * verg) | round(2) }}

      # PV Tagesertrag € = Eigenverbrauch Ersparnis + Einspeise Einnahmen
      - name: "PV Tages Gesamtertrag"
        unit_of_measurement: "€"
        state: >
          {% set ev = states('sensor.pv_tages_eigenverbrauch_ersparnis') | float %}
          {% set ei = states('sensor.pv_tages_einspeise_einnahmen') | float %}
          {{ (ev + ei) | round(2) }}

      # PV Gesamtertrag
      - name: "PV Gesamtertrag"
        unit_of_measurement: "€"
        state: >
          {% set start = states('input_number.pv_bisheriger_ertrag') | float(0) %}
          {% set jahr = states('sensor.pv_jahresertrag') | float(0) %}
          {{ (start + jahr) | round(2) }}

      # PV Amortisation % = PV Gesamtertrag / Investitionskosten * 100
      - name: "PV Amortisation Prozent"
        unit_of_measurement: "%"
        state: >
          {% set invest = states('input_number.pv_investitionskosten') | float(0) %}
          {% set total = states('sensor.pv_gesamtertrag') | float(0) %}
          {% if invest > 0 %}
            {{ ((total / invest) * 100) | round(1) }}
          {% else %}
            0
          {% endif %}

      # Smartfox Amortisation Resttage = (Investitionskosten - PV Gesamtertrag) / Tagesertrag
      - name: "PV Amortisation Resttage"
        unit_of_measurement: "Tage"
        state: >
          {% set invest = states('input_number.pv_investitionskosten') | float(0) %}
          {% set total = states('sensor.pv_gesamtertrag') | float(0) %}
          {% set daily = states('sensor.pv_tages_gesamtertrag') | float(0) %}

          {% if daily > 0 %}
            {{ ((invest - total) / daily) | round(0) }}
          {% else %}
            9999
          {% endif %}


utility_meter:
  # PV Ertrag pro Monat
  pv_ertrag_monat:
    source: sensor.pv_tages_gesamtertrag
    cycle: monthly
    name: "PV Monatsertrag"

  # PV Ertrag pro Jahr
  pv_ertrag_jahr:
    source: sensor.pv_tages_gesamtertrag
    cycle: yearly
    name: "PV Jahresertrag"

Then it’s needed to create some number values. Go to Settings → Devices & Services → Helper → Number Value (Einstellungen → Geräte & Dienste → Helfer → Zahlenwert-Eingabe):

Purpose Name Entity-ID Unit
Investment costs PV Investitionskosten input_number.pv_investitionskosten
Price of electricity PV Strompreis Bezug input_number.pv_strompreis_bezug €/kWh
Feed-in compensation PV Einspeisevergütung input_number.pv_einspeisevergutung €/kWh
Total revenue to date PV Bisheriger Ertrag input_number.pv_bisheriger_ertrag

Important: You need to sum up the total revenue at the end of year by value of pv_ertrag_jahr (Yearly revenue) because this utlility meter will be reset every year. I didn’t found another way to sum up it automatically.

And here’s the dashboard.yaml:

          cards:
          - type: gauge
            entity: sensor.pv_amortisation_prozent
            name: Amortisation
            min: 0
            max: 100
          - type: gauge
            entity: sensor.pv_amortisation_resttage
            name: Resttage
            min: 0
            max: 5000
          - type: entity
            entity: sensor.pv_tages_gesamtertrag
            icon: mdi:currency-eur
            name: Tagesertrag
          - type: entity
            entity: sensor.pv_monatsertrag
            icon: mdi:currency-eur
            name: Monatsertrag
          - type: entity
            entity: sensor.pv_jahresertrag
            name: Jahresertrag
          - type: entity
            entity: sensor.pv_gesamtertrag
            name: Gesamtertrag

Dear Michael,
thank you so much! That saved me days or even weeks of trial and error…
I have just copied your code and it seems to work out of the box! Amazing.

I have a few questions left, I’am not sure if have done it right:

  1. “PV bisheriger Ertrag”: I calculated manually the “PV Direktverbrauch” + “Batterie Entladung” and sum it up over the years * average electricity price and put it in the Box? Because these data can’t be read out of smart fox?
    (Sorry I’am not allowed to put more than one pic in the post :thinking:)

  2. The values of “Amortisation” and “Resttage” seems to vary over the day.
    Ist this normal?
    Also “Monatsertrag” and “Jahresertrag” are the same values…
    “Gesamtertrag” is “PV Bisheriger Ertrag” (which I have calculated manually once + “Monatsertrag”?

  3. I copied your .yaml Code for the other dashboard in my Dashboard Card under three dots: “in Yaml bearbeiten” but no chart is visible. Where is my mistake?
    Update: I found it with KI :slight_smile: great board!

Hi Phil,

I also recognized some issues with the statistics over time. With Co-Pilot I generated a new counter for total PV output as we don’t get it from Smartfox. It will cumulative total of daily outputs and will be updated daily at 23:59:50 with the daily output.

The solar amortization remaining Days will be now calculated with revenue from a 7-day average statistic:

smartfoxrest.yaml (from template section on):

template:
  - sensor:

      # Hausverbrauch = PV + Netz-Gesamt
      - name: "Smartfox Leistung Hausverbrauch"
        unique_id: smartfox_leistung_hausverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_total = states('sensor.smartfox_leistung_netz_gesamt') | float(0) %}
          {{ pv + grid_total }}

      # Eigenverbrauch = PV - Einspeisung
      - name: "Smartfox Leistung Eigenverbrauch"
        unique_id: smartfox_leistung_eigenverbrauch
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set grid_out = states('sensor.smartfox_leistung_nur_einspeisung') | float(0) %}
          {{ pv - grid_out }}

      # Hausverbrauchsquote = Hausverbrauch / PV * 100 oder 100% wenn Hausverbrauch >= PV
      - name: "Smartfox Hausverbrauchsquote"
        unique_id: smartfox_hausverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set haus = states('sensor.smartfox_leistung_hausverbrauch') | float(0) %}

          {% if pv <= 0 %}
            0
          {% elif haus >= pv %}
            100
          {% else %}
            {{ (haus / pv * 100) | round(1) }}
          {% endif %}

      # Eigenverbrauchsquote = Eigenverbrauch / PV * 100
      - name: "Smartfox Eigenverbrauchsquote"
        unique_id: smartfox_eigenverbrauchsquote
        unit_of_measurement: "%"
        state: >
          {% set pv = states('sensor.smartfox_leistung_pv') | float(0) %}
          {% set eigen = states('sensor.smartfox_leistung_eigenverbrauch') | float(0) %}
          {% if pv > 0 %}
            {{ (eigen / pv * 100) | round(1) }}
          {% else %}
            0
          {% endif %}
 
      # Smartfox Tagesbezug kWh
      - name: "Smartfox Tagesbezug kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tagesbezug') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tageseinspeisung kWh
      - name: "Smartfox Tageseinspeisung kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tageseinspeisung') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tagesproduktion kWh
      - name: "Smartfox Tagesproduktion kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tagesproduktion') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tagesverbrauch kWh
      - name: "Smartfox Tagesverbrauch kWh"
        unit_of_measurement: "kWh"
        state_class: total_increasing
        device_class: energy
        state: >
          {% set v = states('sensor.smartfox_energie_tagesverbrauch') %}
          {{ (v | float(0) / 1000) | round(3) }}

      # Smartfox Tages Eigenverbrauch kWh = Tagesproduktion - Tageseinspeisung
      - name: "PV Tages Eigenverbrauch kWh"
        unit_of_measurement: "kWh"
        state: >
          {% set prod = states('sensor.smartfox_energie_tagesproduktion') | float(0) / 1000 %}
          {% set eins = states('sensor.smartfox_energie_tageseinspeisung') | float(0) / 1000 %}
          {{ (prod - eins) | round(3) }}

      # Smartfox Tages Eigenverbrauch Wh = Tagesproduktion - Tageseinspeisung
      - name: "PV Tages Eigenverbrauch Wh"
        unit_of_measurement: "Wh"
        state: >
          {% set prod = states('sensor.smartfox_energie_tagesproduktion') | float(0) %}
          {% set eins = states('sensor.smartfox_energie_tageseinspeisung') | float(0) %}
          {{ (prod - eins) | round(0) }}

      # Smartfox Tages Eigenverbrauch Ersparnis € = Tages Eigenverbrauch kWh * Strompreis Bezug
      - name: "PV Tages Eigenverbrauch Ersparnis"
        unit_of_measurement: "€"
        state: >
          {% set eigen = states('sensor.pv_tages_eigenverbrauch_kwh') | float(0) %}
          {% set preis = states('input_number.pv_strompreis_bezug') | float(0) %}
          {{ (eigen * preis) | round(2) }}

      # Smartfox Tages Einspeise Einnahmen € = Tageseinspeisung kWh * Einspeisevergütung
      - name: "PV Tages Einspeise Einnahmen"
        unit_of_measurement: "€"
        state: >
          {% set eins = states('sensor.smartfox_energie_tageseinspeisung') | float(0) / 1000 %}
          {% set verg = states('input_number.pv_einspeisevergutung') | float(0) %}
          {{ (eins * verg) | round(2) }}

      # PV Tagesertrag € = Eigenverbrauch Ersparnis + Einspeise Einnahmen
      - name: "PV Tages Gesamtertrag"
        unit_of_measurement: "€"
        state: >
          {% set ev = states('sensor.pv_tages_eigenverbrauch_ersparnis') | float(0) %}
          {% set ei = states('sensor.pv_tages_einspeise_einnahmen') | float(0) %}
          {% set total = (ev + ei) | float(0) %}

          {# Negative Werte blocken #}
          {% if total < 0 %}
            0
          {% else %}
            {{ total | round(2) }}
          {% endif %}

      # PV Gesamtertrag
      - name: "PV Gesamtertrag"
        unit_of_measurement: "€"
        device_class: monetary
        state_class: total
        state: >
          {% set start = states('input_number.pv_bisheriger_ertrag') | float(0) %}
          {% set counter = states('input_number.pv_ertrag_gesamt_counter') | float(0) %}
          {{ (start + counter) | round(2) }}

      # PV Amortisation % = PV Gesamtertrag / Investitionskosten * 100
      - name: "PV Amortisation Prozent"
        unit_of_measurement: "%"
        state: >
          {% set invest = states('input_number.pv_investitionskosten') | float(0) %}
          {% set total  = states('sensor.pv_gesamtertrag') | float(0) %}
          {% if invest > 0 %}
            {{ ((total / invest) * 100) | round(1) }}
          {% else %}
            0
          {% endif %}

      # PV Amortisation Resttage = (Investitionskosten - PV Gesamtertrag) / Tagesertrag
      - name: "PV Amortisation Resttage"
        unit_of_measurement: "Tage"
        state: >
          {% set invest = states('input_number.pv_investitionskosten') | float(0) %}
          {% set total  = states('sensor.pv_gesamtertrag') | float(0) %}
          {% set daily  = states('sensor.pv_tagesertrag_7d_statistik') | float(0) %}
          {% if daily > 0 %}
            {{ ((invest - total) / daily) | round(0) }}
          {% else %}
            9999
          {% endif %}

      # PV Break-Even Datum = Heute + Amortisation Resttage
      - name: "PV Break-Even Datum"
        state: >
          {% set invest = states('input_number.pv_investitionskosten') | float(0) %}
          {% set total  = states('sensor.pv_gesamtertrag') | float(0) %}
          {% set daily  = states('sensor.pv_tagesertrag_7d_statistik') | float(0) %}
          {% if daily > 0 %}
            {{ (now() + timedelta(days=((invest - total) / daily))).date() }}
          {% else %}
            "unbekannt"
          {% endif %}


sensor:
  # Statistik-Sensor für 7-Tage-Mittel
  - platform: statistics
    name: "PV Tagesertrag 7d Statistik"
    entity_id: sensor.pv_tages_gesamtertrag
    state_characteristic: mean
    max_age:
      days: 7
    sampling_size: 200


input_number:
  # Zähler für PV Ertrag Gesamt (kumuliert aus Tageserträgen, wird täglich um den Tagesertrag erhöht)
  pv_ertrag_gesamt_counter:
    name: "PV Ertrag Gesamt Counter"
    min: 0
    max: 100000
    step: 0.01
    mode: box
    unit_of_measurement: "€"


automation:
  # PV Tagesertrag täglich um 23:59:30 auf Gesamtzähler buchen
  - alias: "PV Tagesertrag auf Gesamtzähler buchen"
    trigger:
      - platform: time
        at: "23:59:30"
    action:
      - service: input_number.set_value
        target:
          entity_id: input_number.pv_ertrag_gesamt_counter
        data:
          value: >
            {% set alt = states('input_number.pv_ertrag_gesamt_counter') | float(0) %}
            {% set tag = states('sensor.pv_tages_gesamtertrag') | float(0) %}
            {{ (alt + tag) | round(2) }}

The dashboard looks now like:

Dashboard YAML:

    sections:
      - type: grid
        cards:
          - type: markdown
            content: |
              ## 📉 Amortisation & ROI
          - type: gauge
            entity: sensor.pv_amortisation_prozent
            name: Amortisation
            min: 0
            max: 100
            grid_options:
              columns: 6
              rows: auto
          - type: gauge
            entity: sensor.pv_amortisation_resttage
            name: Resttage
            min: 0
            max: 5000
            grid_options:
              columns: 6
              rows: auto
          - type: tile
            entity: input_number.pv_investitionskosten
            name: Investitionskosten
            vertical: false
            features_position: bottom
          - type: tile
            entity: sensor.pv_break_even_datum
            name: Amortisiert bis
            icon: mdi:clock-time-eight-outline
            vertical: false
            features_position: bottom
      - type: grid
        cards:
          - type: markdown
            content: |
              ## 💶 Erträge
          - type: entity
            entity: sensor.pv_tages_gesamtertrag
            icon: mdi:currency-eur
            name: Tagesertrag
          - type: entity
            entity: sensor.pv_tagesertrag_7d_statistik
            name: Tagesertrag 7d-Mittel
            icon: mdi:currency-eur
          - type: entity
            entity: sensor.pv_gesamtertrag
            name: Gesamtertrag
            icon: mdi:currency-eur
      - type: grid
        cards:
          - type: markdown
            content: |
              ## 📊 Jahresverlauf
          - chart_type: line
            period: hour
            type: statistics-graph
            entities:
              - sensor.pv_gesamtertrag
            stat_types:
              - sum
              - state
              - change
            hide_legend: true

simply brilliant!
I have copied your code and replaced the previous one.

It’s just working. I will wait until tomorrow and see whats happen with “Tagesertrag” and “Jahresverlauf” and “Gesamtertrag”. They should be updated…
The data seems plausible.

I’am still wondering why this kind of information isn’t a “standard” within all PV Apps out there. Every user should be interested when the PV is amortisized.

just want to share my implementation. its more or less the same, just other formatted.
it’s like a all in one for configuration.yaml

modbus:
  - name: "smartfox"
    type: tcp
    host: <ip-address>
    port: 502
    delay: 5
    sensors: 
    - {name:  solar.boiler       ,  unit_of_measurement:  pct ,  data_type:  int   ,  input_type:  holding  ,  swap:  none  ,  slave:  1 ,  address:  41046  ,  scan_interval:  20}
    - {name: solar.energy_from_grid     ,  unit_of_measurement: Wh ,   data_type: uint64 ,   input_type: holding   ,      slave: 1  ,  address: 40999  ,scan_interval: 600}
    - {name: solar.energy_into_grid     ,  unit_of_measurement: Wh ,   data_type: uint64 ,   input_type: holding   ,      slave: 1  ,  address: 41003  ,scan_interval: 600}
    - {name: solar.energy_smartfox      ,  unit_of_measurement: Wh ,   data_type: uint64 ,   input_type: holding   ,     slave: 1  ,  address: 41007  ,scan_interval: 600}
    - {name: solar.day_energy_from_grid ,  unit_of_measurement: Wh ,   data_type: uint32 ,   input_type: holding   ,      slave: 1  ,  address: 41011  ,scan_interval: 600}
    - {name: solar.day_energy_into_grid ,  unit_of_measurement: Wh ,   data_type: uint32 ,   input_type: holding   ,      slave: 1  ,  address: 41013  ,scan_interval: 600}
    - {name: solar.day_energy_smartfox  ,  unit_of_measurement: Wh ,   data_type: uint32 ,   input_type: holding   ,      slave: 1  ,  address: 41015  ,scan_interval: 600}
    - {name: solar.power_net            ,  unit_of_measurement: W  ,   data_type: int32  ,   input_type: holding   ,      slave: 1  ,  address: 41017  ,scan_interval: 17}
    - {name: solar.power_l1             ,  unit_of_measurement: W  ,   data_type: int32  ,   input_type: holding   ,      slave: 1  ,  address: 41019  ,scan_interval: 17}
    - {name: solar.power_l2             ,  unit_of_measurement: W  ,   data_type: int32  ,   input_type: holding   ,     slave: 1  ,  address: 41021  ,scan_interval: 17}
    - {name: solar.power_l3             ,  unit_of_measurement: W  ,   data_type: int32  ,   input_type: holding   ,      slave: 1  ,  address: 41023  ,scan_interval: 17}
    - {name: solar.boiler               ,  unit_of_measurement: pct ,  data_type: int16  ,   input_type: holding   ,      slave: 1  ,  address: 41046  ,scan_interval: 17}
    - {name: solar.power_pv1            ,  unit_of_measurement: W  ,   data_type: uint32 ,   input_type: holding   ,      slave: 1  ,  address: 41399  ,scan_interval: 17}
    - {name: solar.relay_1                                         ,   data_type: int16  ,   input_type: holding   ,      slave: 1  ,  address: 42249  ,scan_interval: 17}
    - {name: solar.relay_2                                         ,   data_type: int16  ,   input_type: holding   ,      slave: 1  ,  address: 42279  ,scan_interval: 17}
    - {name: solar.relay_3                                         ,   data_type: int16  ,   input_type: holding   ,      slave: 1  ,  address: 42309  ,scan_interval: 17}
    - {name: solar.relay_4                                         ,   data_type: int16  ,   input_type: holding   ,      slave: 1  ,  address: 42339  ,scan_interval: 17}
    - {name: solar.energy_pv1           ,  unit_of_measurement: Wh ,   data_type: uint64 ,   input_type: holding   ,     slave: 1  ,  address: 41401  ,scan_interval: 600}