Heidelberg Wallbox via Waveshare Modbus in Home Assistant

Einen ESP habe ich bisher noch nicht programmiert, wie macht man das? Kannst du mich da durchführen?

Bei heidelbridge ist das etwas komplizierter da es noch kein fertiges Image gibt.
Für den Anfang kannst du auch wbec nehmen, DAV gibt es fertige Images.

Ich hab Heidelbridge jetzt auch mal im Testaufbau umgesetzt. Im Grunde funktioniert es gut, allerdings bekomme ich alle x Sekunden (je nach eingestellter Abfragezeit in EVCC) eine Fehlermeldung mit heatbeat: EOF.
Ich werde das mal beobachten, evtl. steige ich auf das Wireshark um. Kann man das auch ohne Token im EVCC einbinden?

Die Fehlermeldung bekomme ich seit dem letzten EVCC-update tatsächlich auch.

Da ich bis jetzt aber keine Fehlfunktion feststellen konnte, lasse ich es so laufen.

Hallo zusammen,

ich stand vor dem gleichen Problem und es läuft bei mir soweit stabil mit

  • HA,
  • 2 mal energy control WBen und dem
  • Waveshare rs485 to eth (b).

ich bin leider kein ha profi aber vllt. hilft meine lösung dem ein oder anderen. Das Ergebnis sieht in etwa so aus:

  • Modbus-Verbindung für zwei Wallboxen (Unit-ID 1 und 2) via TCP-Gateway
  • Automatische Deaktivierung des Standby-Modus beim Start sowie alle 5 Minuten
  • Regelmäßiger Watchdog-Refresh (alle 15 Sekunden)
  • Zentrale Leistungsbegrenzung über input_number.total_charge_power_kw (0–11 kW) samt
  • VNB-Sperrsignal (wichtig für §14 EnWG, Modul 3…)
  • Python-Skript heidelberg_power_manager verteilt den zur Verfügung stehenden Strom dynamisch, schreibt Register 261/262 und pre-armed die Boxen
  • Textsensoren für Ladezustand, Sperre, Remote-Lock und Watchdog-Rücklesung
  • Lovelace-Dashboard mit Status, zentraler Steuerung und Verlauf beider Wallboxen
  • Skripte zur schnellen Umschaltung des Modbus-Loglevels (Debug/Normal)

Hier meine Waveshare-Settings. Die Timing-Werte weiter unten sind durchaus kritisch:

configuration.yaml anpassen

  • homeassistant:
      packages: !include_dir_named packages
    
    lovelace:
      dashboards:
        heidelberg-wallbox:
          mode: yaml
          filename: lovelace_heidelberg.yaml
          title: Heidelberg Wallbox
          icon: mdi:ev-station
          show_in_sidebar: true
    

Für die Inbetriebnahme zusätzlich sicherstellen, dass der Modbus-Logger aktiviert ist:

logger:
  default: info
  logs:
    homeassistant.components.modbus: debug

Modbus-Verbindungsdaten anpassen
In packages/heidelberg.yaml im Abschnitt modbus Host/Port ggf. auf eure Umgebung ändern.

Python Scripts aktivieren
In configuration.yaml muss der Eintrag python_script: vorhanden sein (erledigt in dieser Repo-Version).

Funktion der Automationen & Skripte

  • wb*_disable_standby_on_start / ..._periodic: Setzen Register 258 auf 4 (Standby deaktiviert)
  • wb*_set_currents_on_start: Setzt Maximal- und Failsafe-Strom (Register 261/262) auf 16 A und ruft anschließend das Python-Skript
  • heidelberg_distribute_total_power: reagiert auf Leistungs-Slider, VNB-Lock, Ladezustände und Remote-Unlock-Schalter und startet das Python-Skript
  • heidelberg_power_manager.py: berechnet die Stromverteilung (inkl. Pre-Arm), berücksichtigt gesperrte Boxen und schreibt Register 261/262
  • set_modbus_logging_debug / set_modbus_logging_normal: Umschalten der Modbus-Loglevel

Lovelace-Dashboard

Nach dem Neustart erscheint links der Eintrag „Heidelberg Wallbox“. Die Ansicht enthält:

  • eine zentrale Karten für Leistungsvorgabe und VNB-Sperrsignal
  • Statuskarten mit Klartext-Ladezustand, Sperr-/Watchdog-Rückmeldung, Leistung, Energie beider Wallboxen
  • Bedienkarten für Remote-Unlock (als Switch)
  • Verlaufsgrafiken (Zustände, Leistung/Stromwerte)

Modbus-Logging bei Bedarf

Über Einstellungen → Automatisierungen & Szenen → Skripte stehen zwei Hilfsskripte bereit:

  • Logger: Modbus Debug – schaltet homeassistant.components.modbus und sämtliche pymodbus-Logger auf DEBUG
  • Logger: Modbus Normal – stellt sie wieder auf INFO

Logs lassen sich dann über SSH/Terminal analysieren, z. B.:

ha core logs --follow | grep -Ei 'modbus|pymodbus'

Hier das Skript python_scripts/heidelberg_power_manager.py:

# Heidelberg Energy Control load distribution logic

PHASES = 3
VOLTAGE = 230
MIN_CURRENT = 6.0
MAX_CURRENT = 16.0
VNB_LOCK_LIMIT_KW = 4.14
DUAL_THRESHOLD_KW = 8.28
PRIORITY_BOX = 1  # 1 = WB1, 2 = WB2


def get_float(entity_id, default=0.0):
    state = hass.states.get(entity_id)
    if state is None:
        return default
    try:
        return float(state.state)
    except (TypeError, ValueError):
        return default


def is_on(entity_id):
    state = hass.states.get(entity_id)
    return state is not None and state.state == "on"


def wants_charging(code_entity_id):
    state = hass.states.get(code_entity_id)
    if state is None:
        return False
    try:
        code = int(state.state)
    except (TypeError, ValueError):
        return False
    return code in (5, 6, 7)


def get_state_code(entity_id):
    state = hass.states.get(entity_id)
    if state is None:
        return -1
    try:
        return int(state.state)
    except (TypeError, ValueError):
        return -1


def is_available(code):
    return code not in (10,)


def clamp_current(value):
    if value <= 0:
        return 0.0
    return max(MIN_CURRENT, min(MAX_CURRENT, value))


def write_modbus_current(unit, register, current):
    value = int(round(max(current, 0.0) * 10.0))
    hass.services.call(
        "modbus",
        "write_register",
        {
            "hub": "heidelberg_bus",
            "unit": unit,
            "address": register,
            "value": value,
        },
        False,
    )


def apply_unit_current(unit, current):
    applied = clamp_current(current)
    write_modbus_current(unit, 261, applied)
    write_modbus_current(unit, 262, applied)
    return applied


def effective_power_kw():
    slider_kw = get_float("input_number.total_charge_power_kw", 0.0)
    power_kw = max(slider_kw, 0.0)
    if is_on("input_boolean.vnb_lock"):
        power_kw = min(power_kw, VNB_LOCK_LIMIT_KW)
    return power_kw


def apply_currents():
    power_kw = effective_power_kw()
    power_w = power_kw * 1000.0

    wb1_request = wants_charging("sensor.wb1_charging_state_code")
    wb2_request = wants_charging("sensor.wb2_charging_state_code")
    wb1_code = get_state_code("sensor.wb1_charging_state_code")
    wb2_code = get_state_code("sensor.wb2_charging_state_code")
    wb1_available = is_available(wb1_code)
    wb2_available = is_available(wb2_code)

    wb1_current = 0.0
    wb2_current = 0.0

    if power_w <= 0:
        pass
    elif wb1_available and not wb2_available:
        single_current = power_w / (PHASES * VOLTAGE)
        wb1_current = max(single_current, MIN_CURRENT)
    elif wb2_available and not wb1_available:
        single_current = power_w / (PHASES * VOLTAGE)
        wb2_current = max(single_current, MIN_CURRENT)
    elif wb1_request and not wb2_request:
        if wb1_available:
            available_current = power_w / (PHASES * VOLTAGE)
            wb1_current = max(available_current, MIN_CURRENT)
    elif wb2_request and not wb1_request:
        if wb2_available:
            available_current = power_w / (PHASES * VOLTAGE)
            wb2_current = max(available_current, MIN_CURRENT)
    else:
        single_current = power_w / (PHASES * VOLTAGE)
        shared_current = power_w / (PHASES * VOLTAGE * 2)

        if not wb1_available and not wb2_available:
            pass
        elif not wb1_available and wb2_available:
            wb2_current = max(single_current, MIN_CURRENT)
        elif not wb2_available and wb1_available:
            wb1_current = max(single_current, MIN_CURRENT)
        elif power_kw < DUAL_THRESHOLD_KW:
            selected_current = max(single_current, MIN_CURRENT)
            priority = PRIORITY_BOX if wb1_available and wb2_available else (1 if wb1_available else 2)
            if priority == 2:
                wb2_current = selected_current
            else:
                wb1_current = selected_current
        else:
            shared = max(shared_current, MIN_CURRENT)
            wb1_current = shared
            wb2_current = shared

    wb1_applied = apply_unit_current(1, wb1_current)
    wb2_applied = apply_unit_current(2, wb2_current)

    logger.info(
        "Heidelberg power manager -> P_eff=%.2f kW, WB1=%.1f A, WB2=%.1f A",
        power_kw,
        wb1_applied,
        wb2_applied,
    )


apply_currents()

Hier mein packages/heidelberg.yaml:

modbus:
  - name: heidelberg_bus
    type: tcp
    host: 192.168.10.40
    port: 502
    message_wait_milliseconds: 1000
    delay: 2
    timeout: 5
    retries: 2
    close_comm_on_error: true

    sensors:
      - <<: &charging_state_sensor
          address: 5
          input_type: input
          data_type: uint16
          scan_interval: 10
        name: wb1_charging_state_code
        unique_id: wb1_charging_state_code
        slave: 1
      - <<: *charging_state_sensor
        name: wb2_charging_state_code
        unique_id: wb2_charging_state_code
        slave: 2

      - <<: &external_unlock_sensor
          address: 13
          input_type: input
          data_type: uint16
          scan_interval: 10
        name: wb1_external_unlock_state
        unique_id: wb1_external_unlock_state
        slave: 1
      - <<: *external_unlock_sensor
        name: wb2_external_unlock_state
        unique_id: wb2_external_unlock_state
        slave: 2

      - <<: &total_power_sensor
          address: 14
          input_type: input
          data_type: uint16
          unit_of_measurement: "W"
          scan_interval: 15
        name: wb1_total_power
        unique_id: wb1_total_power
        slave: 1
      - <<: *total_power_sensor
        name: wb2_total_power
        unique_id: wb2_total_power
        slave: 2

      - <<: &energy_poweron_sensor
          address: 15
          input_type: input
          data_type: uint32
          unit_of_measurement: "Wh"
          scan_interval: 30
        name: wb1_energy_since_poweron
        unique_id: wb1_energy_since_poweron
        slave: 1
      - <<: *energy_poweron_sensor
        name: wb2_energy_since_poweron
        unique_id: wb2_energy_since_poweron
        slave: 2

      - <<: &energy_install_sensor
          address: 17
          input_type: input
          data_type: uint32
          unit_of_measurement: "Wh"
          scan_interval: 60
        name: wb1_energy_since_install
        unique_id: wb1_energy_since_install
        slave: 1
      - <<: *energy_install_sensor
        name: wb2_energy_since_install
        unique_id: wb2_energy_since_install
        slave: 2

      - <<: &temp_sensor
          address: 9
          input_type: input
          data_type: uint16
          scale: 0.1
          unit_of_measurement: "°C"
          scan_interval: 50
        name: wb1_temp_pcb
        unique_id: wb1_temp_pcb
        slave: 1
      - <<: *temp_sensor
        name: wb2_temp_pcb
        unique_id: wb2_temp_pcb
        slave: 2

      - <<: &remote_unlock_state_sensor
          address: 259
          input_type: holding
          data_type: uint16
          scan_interval: 10
        name: wb1_remote_unlock_state
        unique_id: wb1_remote_unlock_state
        slave: 1
      - <<: *remote_unlock_state_sensor
        name: wb2_remote_unlock_state
        unique_id: wb2_remote_unlock_state
        slave: 2

      - <<: &standby_state_sensor
          address: 258
          input_type: holding
          data_type: uint16
          scan_interval: 10
        name: wb1_standby_state
        unique_id: wb1_standby_state
        slave: 1
      - <<: *standby_state_sensor
        name: wb2_standby_state
        unique_id: wb2_standby_state
        slave: 2

      - <<: &watchdog_state_sensor
          address: 257
          input_type: holding
          data_type: uint16
          scale: 0.001
          precision: 3
          unit_of_measurement: "s"
          scan_interval: 30
        name: wb1_watchdog_timeout_ms_state
        unique_id: wb1_watchdog_timeout_ms_state
        slave: 1
      - <<: *watchdog_state_sensor
        name: wb2_watchdog_timeout_ms_state
        unique_id: wb2_watchdog_timeout_ms_state
        slave: 2

      - <<: &max_current_state_sensor
          address: 261
          input_type: holding
          data_type: uint16
          scale: 0.1
          precision: 1
          unit_of_measurement: "A"
          scan_interval: 30
        name: wb1_max_current_setpoint_state
        unique_id: wb1_max_current_setpoint_state
        slave: 1
      - <<: *max_current_state_sensor
        name: wb2_max_current_setpoint_state
        unique_id: wb2_max_current_setpoint_state
        slave: 2

      - <<: &failsafe_current_state_sensor
          address: 262
          input_type: holding
          data_type: uint16
          scale: 0.1
          precision: 1
          unit_of_measurement: "A"
          scan_interval: 30
        name: wb1_failsafe_current_state
        unique_id: wb1_failsafe_current_state
        slave: 1
      - <<: *failsafe_current_state_sensor
        name: wb2_failsafe_current_state
        unique_id: wb2_failsafe_current_state
        slave: 2

    switches:
    # Standby-Schalter nicht im UI benötig – Steuerung erfolgt automatisch

      - <<: &remote_unlock_switch_base
          address: 259
          command_on: 1
          command_off: 0
          verify:
            input_type: holding
            address: 259
        name: wb1_remote_unlock
        unique_id: wb1_remote_unlock_sw
        slave: 1
      - <<: *remote_unlock_switch_base
        name: wb2_remote_unlock
        unique_id: wb2_remote_unlock_sw
        slave: 2

input_number:
  total_charge_power_kw:
    name: Gesamt-Ladeleistung verfügbar
    min: 0
    max: 11
    step: 0.1
    unit_of_measurement: kW
    mode: slider
    initial: 11
input_boolean:
  vnb_lock:
    name: VNB-Sperrsignal aktiv
    icon: mdi:transmission-tower-off
    initial: false


template:
  - sensor:
      - &charging_state_template
        name: WB1 Charging State
        unique_id: wb1_charging_state
        icon: mdi:ev-station
        state: >-
          {% set raw = states('sensor.wb1_charging_state_code') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(-1) %}
            {% set state_map = {
              1: 'Initialisierung',
              2: 'A1 – Kein Fahrzeug angeschlossen, Wallbox gesperrt',
              3: 'A2 – Kein Fahrzeug angeschlossen, Wallbox freigegeben',
              4: 'B1 – Fahrzeug angeschlossen ohne Ladeanforderung, Wallbox gesperrt',
              5: 'B2 – Fahrzeug angeschlossen ohne Ladeanforderung, Wallbox freigegeben',
              6: 'C1 – Ladeanforderung aktiv, Wallbox gesperrt',
              7: 'C2 – Ladeanforderung aktiv, Wallbox freigegeben',
              8: 'Derating aktiv',
              9: 'Fehlerzustand',
              10: 'Wallbox gesperrt oder nicht bereit',
              11: 'Unbekannter Fehlerzustand'
            } %}
            {{ state_map.get(code, 'Unbekannt') }}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb1_charging_state_code') }}"
          iec_state: >-
            {% set code = states('sensor.wb1_charging_state_code') | int(-1) %}
            {% set iec_map = {
              2: 'A1',
              3: 'A2',
              4: 'B1',
              5: 'B2',
              6: 'C1',
              7: 'C2',
              8: 'Derating',
              9: 'E',
              10: 'F',
              11: '---'
            } %}
            {{ iec_map.get(code, '---') }}
          car_state: >-
            {% set code = states('sensor.wb1_charging_state_code') | int(-1) %}
            {% set car_map = {
              2: 'Kein Fahrzeug angeschlossen',
              3: 'Kein Fahrzeug angeschlossen',
              4: 'Fahrzeug angeschlossen, keine Ladeanforderung',
              5: 'Fahrzeug angeschlossen, keine Ladeanforderung',
              6: 'Fahrzeug angeschlossen, Ladeanforderung aktiv',
              7: 'Fahrzeug angeschlossen, lädt',
              8: 'Derating aktiv',
              9: 'Fehlerzustand',
              10: 'Wallbox gesperrt oder nicht bereit',
              11: 'Fehlerzustand'
            } %}
            {{ car_map.get(code, 'Unbekannt') }}
          wallbox_state: >-
            {% set code = states('sensor.wb1_charging_state_code') | int(-1) %}
            {% set wb_map = {
              2: 'Wallbox sperrt',
              3: 'Wallbox freigegeben',
              4: 'Wallbox sperrt',
              5: 'Wallbox freigegeben',
              6: 'Wallbox sperrt',
              7: 'Wallbox freigegeben',
              8: 'Wallbox reduziert Leistung (Derating)',
              9: 'Wallbox Fehler',
              10: 'Wallbox gesperrt oder nicht bereit',
              11: 'Wallbox Fehler'
            } %}
            {{ wb_map.get(code, 'Unbekannt') }}
      - <<: *charging_state_template
        name: WB2 Charging State
        unique_id: wb2_charging_state
        state: >-
          {% set raw = states('sensor.wb2_charging_state_code') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(-1) %}
            {% set state_map = {
              1: 'Initialisierung',
              2: 'A1 – Kein Fahrzeug angeschlossen, Wallbox gesperrt',
              3: 'A2 – Kein Fahrzeug angeschlossen, Wallbox freigegeben',
              4: 'B1 – Fahrzeug angeschlossen ohne Ladeanforderung, Wallbox gesperrt',
              5: 'B2 – Fahrzeug angeschlossen ohne Ladeanforderung, Wallbox freigegeben',
              6: 'C1 – Ladeanforderung aktiv, Wallbox gesperrt',
              7: 'C2 – Ladeanforderung aktiv, Wallbox freigegeben',
              8: 'Derating aktiv',
              9: 'Fehlerzustand',
              10: 'Wallbox gesperrt oder nicht bereit',
              11: 'Unbekannter Fehlerzustand'
            } %}
            {{ state_map.get(code, 'Unbekannt') }}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb2_charging_state_code') }}"
          iec_state: >-
            {% set code = states('sensor.wb2_charging_state_code') | int(-1) %}
            {% set iec_map = {
              2: 'A1',
              3: 'A2',
              4: 'B1',
              5: 'B2',
              6: 'C1',
              7: 'C2',
              8: 'Derating',
              9: 'E',
              10: 'F',
              11: '---'
            } %}
            {{ iec_map.get(code, '---') }}
          car_state: >-
            {% set code = states('sensor.wb2_charging_state_code') | int(-1) %}
            {% set car_map = {
              2: 'Kein Fahrzeug angeschlossen',
              3: 'Kein Fahrzeug angeschlossen',
              4: 'Fahrzeug angeschlossen, keine Ladeanforderung',
              5: 'Fahrzeug angeschlossen, keine Ladeanforderung',
              6: 'Fahrzeug angeschlossen, Ladeanforderung aktiv',
              7: 'Fahrzeug angeschlossen, lädt',
              8: 'Derating aktiv',
              9: 'Fehlerzustand',
              10: 'Wallbox gesperrt oder nicht bereit',
              11: 'Fehlerzustand'
            } %}
            {{ car_map.get(code, 'Unbekannt') }}
          wallbox_state: >-
            {% set code = states('sensor.wb2_charging_state_code') | int(-1) %}
            {% set wb_map = {
              2: 'Wallbox sperrt',
              3: 'Wallbox freigegeben',
              4: 'Wallbox sperrt',
              5: 'Wallbox freigegeben',
              6: 'Wallbox sperrt',
              7: 'Wallbox freigegeben',
              8: 'Wallbox reduziert Leistung (Derating)',
              9: 'Wallbox Fehler',
              10: 'Wallbox gesperrt oder nicht bereit',
              11: 'Wallbox Fehler'
            } %}
            {{ wb_map.get(code, 'Unbekannt') }}

      - &external_unlock_template
        name: WB1 Externe Sperre
        unique_id: wb1_external_unlock_state_text
        icon: >-
          {% set code = states('sensor.wb1_external_unlock_state') | int(0) %}
          {% if code == 1 %}
            mdi:lock-open-variant
          {% else %}
            mdi:lock
          {% endif %}
        state: >-
          {% set raw = states('sensor.wb1_external_unlock_state') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(0) %}
            {% if code == 1 %}
              Freigegeben
            {% elif code == 0 %}
              Gesperrt
            {% else %}
              Unbekannt ({{ code }})
            {% endif %}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb1_external_unlock_state') }}"
      - <<: *external_unlock_template
        name: WB2 Externe Sperre
        unique_id: wb2_external_unlock_state_text
        icon: >-
          {% set code = states('sensor.wb2_external_unlock_state') | int(0) %}
          {% if code == 1 %}
            mdi:lock-open-variant
          {% else %}
            mdi:lock
          {% endif %}
        state: >-
          {% set raw = states('sensor.wb2_external_unlock_state') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(0) %}
            {% if code == 1 %}
              Freigegeben
            {% elif code == 0 %}
              Gesperrt
            {% else %}
              Unbekannt ({{ code }})
            {% endif %}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb2_external_unlock_state') }}"

      - &remote_unlock_template
        name: WB1 Remote Unlock
        unique_id: wb1_remote_unlock_state_text
        icon: >-
          {% set code = states('sensor.wb1_remote_unlock_state') | int(1) %}
          {% if code == 1 %}
            mdi:lock-open-variant
          {% else %}
            mdi:lock
          {% endif %}
        state: >-
          {% set raw = states('sensor.wb1_remote_unlock_state') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(1) %}
            {% if code == 1 %}
              Remote frei
            {% elif code == 0 %}
              Remote gesperrt
            {% else %}
              Unbekannter Wert ({{ code }})
            {% endif %}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb1_remote_unlock_state') }}"
      - <<: *remote_unlock_template
        name: WB2 Remote Unlock
        unique_id: wb2_remote_unlock_state_text
        icon: >-
          {% set code = states('sensor.wb2_remote_unlock_state') | int(1) %}
          {% if code == 1 %}
            mdi:lock-open-variant
          {% else %}
            mdi:lock
          {% endif %}
        state: >-
          {% set raw = states('sensor.wb2_remote_unlock_state') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(1) %}
            {% if code == 1 %}
              Remote frei
            {% elif code == 0 %}
              Remote gesperrt
            {% else %}
              Unbekannter Wert ({{ code }})
            {% endif %}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb2_remote_unlock_state') }}"

      - &standby_state_template
        name: WB1 Standby Zustand
        unique_id: wb1_standby_state_text
        icon: >-
          {% set code = states('sensor.wb1_standby_state') | int(-1) %}
          {% if code == 4 %}
            mdi:power-plug
          {% elif code == 0 %}
            mdi:power-plug-off
          {% else %}
            mdi:help-circle
          {% endif %}
        state: >-
          {% set raw = states('sensor.wb1_standby_state') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(-1) %}
            {% if code == 4 %}
              Standby deaktiviert
            {% elif code == 0 %}
              Standby aktiviert
            {% else %}
              Unbekannter Wert ({{ code }})
            {% endif %}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb1_standby_state') }}"
      - <<: *standby_state_template
        name: WB2 Standby Zustand
        unique_id: wb2_standby_state_text
        icon: >-
          {% set code = states('sensor.wb2_standby_state') | int(-1) %}
          {% if code == 4 %}
            mdi:power-plug
          {% elif code == 0 %}
            mdi:power-plug-off
          {% else %}
            mdi:help-circle
          {% endif %}
        state: >-
          {% set raw = states('sensor.wb2_standby_state') %}
          {% if raw in ['unknown', 'unavailable', 'none'] %}
            Unbekannt
          {% else %}
            {% set code = raw | int(-1) %}
            {% if code == 4 %}
              Standby deaktiviert
            {% elif code == 0 %}
              Standby aktiviert
            {% else %}
              Unbekannter Wert ({{ code }})
            {% endif %}
          {% endif %}
        attributes:
          raw_state: "{{ states('sensor.wb2_standby_state') }}"

script:
  wb1_remote_unlock_toggle:
    alias: "WB1 Remote Unlock umschalten"
    sequence:
      - variables:
          prefix: "wb1"
          unit: 1
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: "{{ unit | int }}"
          address: 259
          value: >-
            {% if states('sensor.' ~ prefix ~ '_remote_unlock_state') | int(1) == 1 %}
              0
            {% else %}
              1
            {% endif %}
  wb2_remote_unlock_toggle:
    alias: "WB2 Remote Unlock umschalten"
    sequence:
      - variables:
          prefix: "wb2"
          unit: 2
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: "{{ unit | int }}"
          address: 259
          value: >-
            {% if states('sensor.' ~ prefix ~ '_remote_unlock_state') | int(1) == 1 %}
              0
            {% else %}
              1
            {% endif %}

  set_modbus_logging_debug:
    alias: "Logger: Modbus Debug"
    sequence:
      - service: logger.set_level
        data:
          homeassistant.components.modbus: debug
          pymodbus: debug
          pymodbus.client: debug
          pymodbus.client.base: debug
          pymodbus.transaction: debug
          pymodbus.framer.rtu_framer: debug

  set_modbus_logging_normal:
    alias: "Logger: Modbus Normal"
    sequence:
      - service: logger.set_level
        data:
          homeassistant.components.modbus: info
          pymodbus: info
          pymodbus.client: info
          pymodbus.client.base: info
          pymodbus.transaction: info
          pymodbus.framer.rtu_framer: info

automation:
  - alias: wb1_disable_standby_on_start
    description: "Deaktiviert Standby bei Systemstart"
    trigger:
      - platform: homeassistant
        event: start
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 1
          address: 258
          value: 4
    mode: single
  - alias: wb2_disable_standby_on_start
    description: "Deaktiviert Standby bei Systemstart"
    trigger:
      - platform: homeassistant
        event: start
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 2
          address: 258
          value: 4
    mode: single

  - alias: wb1_disable_standby_periodic
    description: "Hält Standby von WB1 dauerhaft deaktiviert"
    trigger:
      - platform: time_pattern
        minutes: "/5"
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 1
          address: 258
          value: 4
    mode: single
  - alias: wb2_disable_standby_periodic
    description: "Hält Standby von WB2 dauerhaft deaktiviert"
    trigger:
      - platform: time_pattern
        minutes: "/5"
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 2
          address: 258
          value: 4
    mode: single

  - alias: wb1_set_currents_on_start
    description: "Setzt Maximal- und Failsafe-Strom von WB1 beim Start auf 16 A"
    trigger:
      - platform: homeassistant
        event: start
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 1
          address: 261
          value: 160
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 1
          address: 262
          value: 160
      - service: python_script.heidelberg_power_manager
    mode: single

  - alias: wb2_set_currents_on_start
    description: "Setzt Maximal- und Failsafe-Strom von WB2 beim Start auf 16 A"
    trigger:
      - platform: homeassistant
        event: start
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 2
          address: 261
          value: 160
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 2
          address: 262
          value: 160
      - service: python_script.heidelberg_power_manager
    mode: single

  - alias: vnb_lock_follow_shelly_input
    description: "Spiegelt den negierten Zustand des Shelly-Eingangs auf die VNB-Sperre"
    trigger:
      - platform: state
        entity_id: binary_sensor.shellypro1_30c6f787af0c_input_0
      - platform: time_pattern
        minutes: "/1"
    action:
      - service: >-
          {% if is_state('binary_sensor.shellypro1_30c6f787af0c_input_0', 'on') %}
            input_boolean.turn_off
          {% else %}
            input_boolean.turn_on
          {% endif %}
        target:
          entity_id: input_boolean.vnb_lock
    mode: restart

  - alias: heidelberg_distribute_total_power
    description: "Berechnet und verteilt die Gesamtleistung auf beide Wallboxen"
    trigger:
      - platform: homeassistant
        event: start
      - platform: state
        entity_id:
          - input_number.total_charge_power_kw
          - input_boolean.vnb_lock
          - sensor.wb1_charging_state_code
          - sensor.wb2_charging_state_code
          - switch.wb1_remote_unlock
          - switch.wb2_remote_unlock
    action:
      - service: python_script.heidelberg_power_manager
    mode: restart

  - &watchdog_keepalive_base
    alias: wb1_watchdog_keepalive
    description: "Sendet alle 15 Sekunden den Watchdog-Wert an die Wallbox WB1"
    trigger:
      - platform: time_pattern
        seconds: "/15"
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 1
          address: 257
          value: 15000
    mode: restart
  - <<: *watchdog_keepalive_base
    alias: wb2_watchdog_keepalive
    description: "Sendet alle 15 Sekunden den Watchdog-Wert an die Wallbox WB2"
    action:
      - service: modbus.write_register
        data:
          hub: heidelberg_bus
          unit: 2
          address: 257
          value: 15000


Hier meine lovelace_heidelberg.yaml

title: Heidelberg Wallbox
views:
  - title: Heidelberg WB
    path: heidelberg-wb
    icon: mdi:ev-station
    badges:
      - entity: sensor.wb1_charging_state
      - entity: sensor.wb1_total_power
      - entity: sensor.wb2_charging_state
      - entity: sensor.wb2_total_power
    cards:
      - type: entities
        title: Gesamtsteuerung
        show_header_toggle: false
        entities:
          - entity: input_number.total_charge_power_kw
            name: verfügbare Ladeleistung (kW)
          - entity: input_boolean.vnb_lock
            name: VNB-Sperrsignal aktiv
      - type: entities
        title: WB1 Status
        show_header_toggle: false
        entities:
          - entity: sensor.wb1_charging_state
            name: Ladezustand (IEC 61851)
          - entity: sensor.wb1_external_unlock_state_text
            name: Externer Sperreingang
          - entity: sensor.wb1_remote_unlock_state_text
            name: Remote Unlock Status
          - entity: sensor.wb1_standby_state_text
            name: Standby-Zustand
          - entity: sensor.wb1_watchdog_timeout_ms_state
            name: Watchdog Timeout (Rücklesung)
          - entity: sensor.wb1_max_current_setpoint_state
            name: Maximalstrom Soll (Rücklesung)
          - entity: sensor.wb1_failsafe_current_state
            name: Failsafe-Strom (Rücklesung)
          - entity: sensor.wb1_total_power
            name: Aktuelle Ladeleistung
          - entity: sensor.wb1_energy_since_poweron
            name: Energie seit Einschalten
          - entity: sensor.wb1_energy_since_install
            name: Energie seit Installation
          - entity: sensor.wb1_temp_pcb
            name: Leiterplattentemperatur
      - type: entities
        title: WB2 Status
        show_header_toggle: false
        entities:
          - entity: sensor.wb2_charging_state
            name: Ladezustand (IEC 61851)
          - entity: sensor.wb2_externe_sperre
            name: Externer Sperreingang
          - entity: sensor.wb2_remote_unlock
            name: Remote Unlock Status
          - entity: sensor.wb2_standby_zustand
            name: Standby-Zustand
          - entity: sensor.wb2_watchdog_timeout_ms_state
            name: Watchdog Timeout (Rücklesung)
          - entity: sensor.wb2_max_current_setpoint_state
            name: Maximalstrom Soll (Rücklesung)
          - entity: sensor.wb2_failsafe_current_state
            name: Failsafe-Strom (Rücklesung)
          - entity: sensor.wb2_total_power
            name: Aktuelle Ladeleistung
          - entity: sensor.wb2_energy_since_poweron
            name: Energie seit Einschalten
          - entity: sensor.wb2_energy_since_install
            name: Energie seit Installation
          - entity: sensor.wb2_temp_pcb
            name: Leiterplattentemperatur
      - type: entities
        title: Bedienelemente WB1
        show_header_toggle: false
        entities:
          - entity: switch.wb1_remote_unlock_2
            name: Remote Unlock
      - type: entities
        title: Bedienelemente WB2
        show_header_toggle: false
        entities:
          - entity: switch.wb2_remote_unlock
            name: Remote Unlock
      - type: history-graph
        title: Zustände WB1
        hours_to_show: 24
        entities:
          - sensor.wb1_charging_state
          - sensor.wb1_external_unlock_state_text
          - sensor.wb1_remote_unlock_state_text
          - sensor.wb1_standby_state_text
      - type: history-graph
        title: Zustände WB2
        hours_to_show: 24
        entities:
          - sensor.wb2_charging_state
          - sensor.wb2_externe_sperre
          - sensor.wb2_remote_unlock
          - sensor.wb2_standby_zustand
      - type: history-graph
        title: Leistung / Stromwerte
        hours_to_show: 24
        entities:
          - sensor.wb1_total_power
          - sensor.wb1_max_current_setpoint_state
          - sensor.wb1_failsafe_current_state
          - sensor.wb2_total_power
          - sensor.wb2_max_current_setpoint_state
          - sensor.wb2_failsafe_current_state

Settings wb1: (s6 schwer zu erkennen, ist genauso wie bei wb2)

Settings WB2:

Ach ja, in der heidelberg.yaml(Zeile 670-686) gibt es eine entität vnb_lock_follow_shelly_input: Hier wird ein Input eines Shelly Pro1 Relais minütlich auf die Eintität vnb_lockkopiert. Das ist in jedem Fall an Eure Verhältnisse anzupassen…

Hallo!

Ich bekomme es ebenfalls nicht hin, habe aber die gleiche Hardware im Einsatz. Ich würde gern deine Waveshare Einstellungen übernehmen. Frage: Was ist die Destination Ip? Ist das dein Home Assistant oder ist der Wert völlig irrelevant?

Irrelevant. HA pollt den waveshare-Server.