Skip to content

modbus_wp_config.yaml – Entwicklereinstellungen

Die Datei modbus_wp_config.yaml ermöglicht Entwicklern, das Verhalten der Lambda Heat Pumps Integration auf Register‑Ebene anzupassen. Dies betrifft vor allem Register‑Deaktivierung, Sensor‑Namensüberschreibungen, Zähler‑Offsets, Energie‑Sensoren und Modbus‑Parameter.

Pfad: config/lambda_wp_config.yaml

YAML wird eingelesen

Die Konfiguration wird beim Start der Integration über die Funktion load_lambda_config() in utils.py geladen. Die Funktion prüft zuerst, ob die Konfiguration bereits gecacht ist, erstellt die Datei falls nötig, führt Migrationen durch und lädt dann die YAML-Datei.

Hauptfunktion:

```454:470:custom_components/lambda_heat_pumps/utils.py async def load_lambda_config(hass: HomeAssistant) -> dict: """Load complete Lambda configuration from lambda_wp_config.yaml.""" # Check if config is already cached in hass.data if "_lambda_config_cache" in hass.data: _LOGGER.debug("Using cached Lambda config") return hass.data["_lambda_config_cache"]

# First, ensure config file exists
await ensure_lambda_config(hass)

# Then, try to migrate if needed (only once per session)
if "_lambda_migration_done" not in hass.data:
    await migrate_lambda_config_sections(hass)
    hass.data["_lambda_migration_done"] = True

config_dir = hass.config.config_dir
lambda_config_path = os.path.join(config_dir, "lambda_wp_config.yaml")

**Standard-Konfiguration:**472:479:custom_components/lambda_heat_pumps/utils.py default_config = { "disabled_registers": set(), "sensors_names_override": {}, "cycling_offsets": {}, "energy_consumption_sensors": {}, "energy_consumption_offsets": {}, "modbus": {}, } ```

YAML-Laden und Parsing:

```485:495:custom_components/lambda_heat_pumps/utils.py try: content = await hass.async_add_executor_job( lambda: open(lambda_config_path, "r").read() ) config = yaml.safe_load(content)

    if not config:
        _LOGGER.warning(
            "lambda_wp_config.yaml is empty, using default configuration"
        )
        return default_config

`` Die Konfiguration wird gecacht inhass.data["_lambda_config_cache"]`, um wiederholtes Laden zu vermeiden.

disabled_registers

Deaktiviert einzelne Register, z. B. bei Firmware‑Inkompatibilitäten oder Fehlern. yaml disabled_registers: - 2004 # Beispiel: boil1_actual_circulation_temp - 100000 # beliebiges Register, das ignoriert werden soll

Technische Verarbeitung:

  1. Einlesen: Die Liste wird in ein set konvertiert:

```497:504:custom_components/lambda_heat_pumps/utils.py # Load disabled registers disabled_registers = set() if "disabled_registers" in config: try: disabled_registers = set(int(x) for x in config["disabled_registers"]) except (ValueError, TypeError) as e: _LOGGER.error("Invalid disabled_registers format: %s", e) disabled_registers = set()

2. **Verwendung:** Vor jedem Modbus-Lesevorgang wird geprüft, ob das Register deaktiviert ist:

```610:627:custom_components/lambda_heat_pumps/utils.py
def is_register_disabled(address: int, disabled_registers: set[int]) -> bool:
    """Check if a register is disabled.

    Args:
        address: The register address to check
        disabled_registers: Set of disabled register addresses

    Returns:
        bool: True if the register is disabled, False otherwise
    """
    is_disabled = address in disabled_registers
    if is_disabled:
        _LOGGER.debug(
            "Register %d is disabled (in set: %s)",
            address,
            disabled_registers,
        )
    return is_disabled

  1. Im Coordinator: Deaktivierte Register werden beim Lesen übersprungen:

```1267:1279:custom_components/lambda_heat_pumps/coordinator.py is_disabled = is_register_disabled(address, self.disabled_registers) if is_disabled: _LOGGER.debug( "Register %d is disabled (in set: %s)", address, self.disabled_registers, ) else: _LOGGER.debug( "Register %d is not disabled (checked against set: %s)", address, self.disabled_registers, )

**Wann verwenden:**
- Register verursacht Fehlermeldungen.
- Register wird von der vorhandenen Firmware nicht unterstützt.
- Reduzierung des Modbus‑Traffics.

## sensors_names_override

Überschreibt Standard‑Sensornamen für bessere Lesbarkeit oder Lokalisierung.

```yaml
sensors_names_override:
  - id: hp1_flow_temp
    override_name: "Vorlauf Wohnzimmer"
  - id: hp1_return_temp
    override_name: "Rücklauf Wohnzimmer"
  - id: hp1_operating_state
    override_name: "Betriebszustand"

Technische Verarbeitung:

  1. Einlesen: Die Liste wird in ein Dictionary konvertiert (idoverride_name):

```506:517:custom_components/lambda_heat_pumps/utils.py # Load sensor overrides sensors_names_override = {} if "sensors_names_override" in config: try: for override in config["sensors_names_override"]: if "id" in override and "override_name" in override: sensors_names_override[override["id"]] = override[ "override_name" ] except (TypeError, KeyError) as e: _LOGGER.error("Invalid sensors_names_override format: %s", e) sensors_names_override = {}

2. **Verwendung:** Beim Erstellen der Sensoren wird der Override-Name verwendet:

```141:152:custom_components/lambda_heat_pumps/sensor.py
                override_name = None
                if use_legacy_modbus_names and hasattr(coordinator, "sensor_overrides"):
                    override_name = coordinator.sensor_overrides.get(
                        f"{prefix}{idx}_{sensor_id}"
                    )
                if override_name:
                    name = override_name
                    sensor_id_final = f"{prefix}{idx}_{sensor_id}"
                    # Data key (original format)
                    entity_id = f"sensor.{name_prefix_lc}_{override_name}"
                    unique_id = f"{name_prefix_lc}_{override_name}"

Hinweise: - id muss einer internen Sensor‑ID entsprechen (siehe Sensorliste in der Integration). - Nur Name wird überschrieben, nicht die Entity‑ID. - Funktioniert nur, wenn use_legacy_modbus_names aktiviert ist.

cycling_offsets

Offsets für Total‑Cycling‑Zähler (z. B. nach Pumpentausch oder Reset).

cycling_offsets:
  hp1:
    heating_cycling_total: 1500
    hot_water_cycling_total: 800
    cooling_cycling_total: 200
    defrost_cycling_total: 50
    compressor_start_cycling_total: 5000
  hp2:
    heating_cycling_total: 0
    hot_water_cycling_total: 0
    cooling_cycling_total: 0
    defrost_cycling_total: 0
    compressor_start_cycling_total: 0

Technische Verarbeitung:

  1. Einlesen: Die Struktur wird validiert und ungültige Werte auf 0 gesetzt:

```519:542:custom_components/lambda_heat_pumps/utils.py # Load cycling offsets cycling_offsets = {} if "cycling_offsets" in config: try: cycling_offsets = config["cycling_offsets"] # Validate cycling offsets structure for device, offsets in cycling_offsets.items(): if not isinstance(offsets, dict): _LOGGER.warning( "Invalid cycling_offsets format for device %s", device ) continue for offset_type, value in offsets.items(): if not isinstance(value, (int, float)): _LOGGER.warning( "Invalid cycling offset value for %s.%s: %s", device, offset_type, value, ) cycling_offsets[device][offset_type] = 0 except (TypeError, KeyError) as e: _LOGGER.error("Invalid cycling_offsets format: %s", e) cycling_offsets = {}

2. **Laden im Coordinator:** Offsets werden beim Coordinator-Start geladen:

```375:383:custom_components/lambda_heat_pumps/coordinator.py
    async def _load_offsets_and_persisted(self):
        # Lade Offsets aus lambda_wp_config.yaml über das zentrale Config-System
        from .utils import load_lambda_config

        try:
            config = await load_lambda_config(self.hass)
            _LOGGER.info(f"Loaded config keys: {list(config.keys())}")
            self._cycling_offsets = config.get("cycling_offsets", {})
            self._energy_offsets = config.get("energy_consumption_offsets", {})

  1. Anwendung auf Sensoren: Der Offset wird zum Sensorwert addiert:

```880:910:custom_components/lambda_heat_pumps/sensor.py """Apply cycling offset from configuration.""" try: # Lade die Cycling-Offsets aus der Konfiguration from .utils import load_lambda_config config = await load_lambda_config(self.hass) cycling_offsets = config.get("cycling_offsets", {})

        if not cycling_offsets:
            _LOGGER.debug(f"No cycling offsets found for {self.entity_id}")
            return

        # Bestimme den Device-Key (z.B. "hp1")
        device_key = f"hp{self._hp_index}"

        if device_key not in cycling_offsets:
            _LOGGER.debug(f"No cycling offsets found for device {device_key}")
            return

        # Hole den aktuellen Offset für diesen Sensor
        current_offset = cycling_offsets[device_key].get(self._sensor_id, 0)

        # Hole den bereits angewendeten Offset aus den Attributen
        applied_offset = getattr(self, "_applied_offset", 0)

        # Berechne die Differenz zwischen aktuellem und bereits angewendetem Offset
        offset_difference = current_offset - applied_offset

        # Debug-Log für bessere Nachverfolgung
        _LOGGER.debug(
            f"Offset calculation for {self.entity_id}: current={current_offset}, applied={applied_offset}, difference={offset_difference}"

``` Hinweise: - Gilt nur für Total‑Sensoren, nicht für Daily/Monthly/Yearly. - Werte sind absolute Zähler (keine Deltas). - Der Offset wird einmalig beim Sensor-Start angewendet.

energy_consumption_sensors

Definiert, welcher Sensor die Basis‑Energieverbrauchsdaten liefert (Standard: Lambda‑eigene Sensoren). Externe Zähler (z. B. Shelly) können hier eingebunden werden. yaml energy_consumption_sensors: hp1: sensor_entity_id: "sensor.shelly_lambda_gesamt_leistung" # externer Sensor hp2: sensor_entity_id: "sensor.eu08l_hp2_compressor_power_consumption_accumulated"

Technische Verarbeitung:

  1. Einlesen: Die Konfiguration wird direkt aus der YAML übernommen:

```582:582:custom_components/lambda_heat_pumps/utils.py "energy_consumption_sensors": config.get("energy_consumption_sensors", {}),

2. **Validierung:** Externe Sensoren werden validiert:

```385:389:custom_components/lambda_heat_pumps/coordinator.py
            # Lade und validiere Energy Sensor Konfigurationen
            raw_energy_sensor_configs = config.get("energy_consumption_sensors", {})

            # Validiere externe Sensoren
            from .utils import validate_external_sensors
            self._energy_sensor_configs = validate_external_sensors(self.hass, raw_energy_sensor_configs)

  1. Verwendung: Der konfigurierte Sensor wird für Energieverbrauchsberechnungen verwendet:

```1998:2002:custom_components/lambda_heat_pumps/coordinator.py sensor_config = self._energy_sensor_configs.get(hp_key, {}) _LOGGER.debug(f"DEBUG-012: Sensor config: {sensor_config}") sensor_entity_id = sensor_config.get("sensor_entity_id") _LOGGER.debug(f"DEBUG-013: Sensor entity ID: {sensor_entity_id}")

        # If no custom sensor configured, use the default power consumption sensor

`` **Hinweise:** - Sensor muss Energie in Wh oder kWh liefern; Konvertierung nach kWh erfolgt automatisch. - Je Heat Pump (hp1,hp2`, …) genau ein Sensor. - Falls kein Sensor konfiguriert, wird der Standard-Modbus-Sensor verwendet.

energy_consumption_offsets

Offsets für Total‑Energieverbrauch (kWh). Nützlich nach Pumpentausch oder Zähler‑Reset. yaml energy_consumption_offsets: hp1: heating_energy_total: 5000.0 hot_water_energy_total: 2000.0 cooling_energy_total: 500.0 defrost_energy_total: 150.0 hp2: heating_energy_total: 150.5 hot_water_energy_total: 45.25 cooling_energy_total: 12.8 defrost_energy_total: 3.1

Technische Verarbeitung:

  1. Einlesen: Die Struktur wird validiert und ungültige Werte auf 0.0 gesetzt:

```544:567:custom_components/lambda_heat_pumps/utils.py # Load energy consumption offsets energy_consumption_offsets = {} if "energy_consumption_offsets" in config: try: energy_consumption_offsets = config["energy_consumption_offsets"] # Validate energy consumption offsets structure for device, offsets in energy_consumption_offsets.items(): if not isinstance(offsets, dict): _LOGGER.warning( "Invalid energy_consumption_offsets format for device %s", device ) continue for offset_type, value in offsets.items(): if not isinstance(value, (int, float)): _LOGGER.warning( "Invalid energy consumption offset value for %s.%s: %s", device, offset_type, value, ) energy_consumption_offsets[device][offset_type] = 0.0 except (TypeError, KeyError) as e: _LOGGER.error("Invalid energy_consumption_offsets format: %s", e) energy_consumption_offsets = {}

2. **Laden im Coordinator:** Offsets werden beim Coordinator-Start geladen:

```383:383:custom_components/lambda_heat_pumps/coordinator.py
            self._energy_offsets = config.get("energy_consumption_offsets", {})

  1. Anwendung: Der Offset wird zu den Energieverbrauchswerten addiert (ähnlich wie bei cycling_offsets).

Hinweise: - Alle Werte in kWh. - Gilt nur für Total‑Sensoren (nicht Daily/Monthly/Yearly).

modbus

Konfiguration für 32‑Bit‑Register‑Reihenfolge.

modbus:
  int32_register_order: "high_first"  # oder "low_first"

Technische Verarbeitung:

  1. Einlesen: Die Modbus-Konfiguration wird direkt übernommen:

```584:584:custom_components/lambda_heat_pumps/utils.py "modbus": config.get("modbus", {}), # Include modbus configuration

2. **Laden der Register-Reihenfolge:** Beim Coordinator-Start wird die Reihenfolge geladen:

```313:362:custom_components/lambda_heat_pumps/modbus_utils.py
async def get_int32_register_order(hass) -> str:
    """
    Lädt Register-Reihenfolge-Konfiguration aus lambda_wp_config.yaml.

    Es handelt sich um die Reihenfolge der 16-Bit-Register bei 32-Bit-Werten
    (Register/Word Order), nicht um Byte-Endianness innerhalb eines Registers.

    Args:
        hass: Home Assistant Instanz

    Returns:
        str: "high_first" oder "low_first" (Standard: "high_first")

    Note:
        "high_first" = Höherwertiges Register zuerst (Register[0] << 16 | Register[1])
        "low_first" = Niedrigwertiges Register zuerst (Register[1] << 16 | Register[0])

        Rückwärtskompatibilität: "big" wird zu "high_first", "little" zu "low_first" konvertiert
    """
    try:
        from .utils import load_lambda_config
        config = await load_lambda_config(hass)
        modbus_config = config.get("modbus", {})

        # Prüfe zuerst neue Config, dann alte (für Rückwärtskompatibilität)
        register_order = modbus_config.get("int32_register_order")
        if register_order is None:
            # Rückwärtskompatibilität: Alte Config migrieren
            old_byte_order = modbus_config.get("int32_byte_order")
            if old_byte_order is not None:
                _LOGGER.info(
                    "Migration: int32_byte_order gefunden, verwende Wert für int32_register_order. "
                    "Bitte migrieren Sie Ihre Config zu modbus.int32_register_order"
                )
                register_order = old_byte_order
            else:
                register_order = "high_first"  # Standard

        # Rückwärtskompatibilität: Konvertiere alte Werte
        if register_order == "big":
            register_order = "high_first"
            _LOGGER.info(
                "Veralteter Wert 'big' verwendet. Bitte aktualisieren Sie Ihre Config auf 'high_first'"
            )
        elif register_order == "little":
            register_order = "low_first"
            _LOGGER.info(
                "Veralteter Wert 'little' verwendet. Bitte aktualisieren Sie Ihre Config auf 'low_first'"
            )

  1. Verwendung: Bei 32‑Bit‑Registern wird die Reihenfolge beim Kombinieren der Register verwendet:

```866:876:custom_components/lambda_heat_pumps/coordinator.py register_order = sensor_info.get("register_order") or sensor_info.get("byte_order") or self._int32_register_order

                                # DEBUG: Für 1020/1022
                                if addr in [1020, 1022]:
                                    _LOGGER.debug(
                                        "INT32-REGISTER-DEBUG: Batch-Verarbeitung Register %d: "
                                        "Register[%d]=%d, Register[%d]=%d, order=%s",
                                        addr, i, value, i+1, next_value, register_order
                                    )

                                value = combine_int32_registers([value, next_value], register_order)

`` **Optionen:** -high_first(Standard): höherwertiges Register zuerst. -low_first`: niedrigeres Register zuerst (für bestimmte Geräte/Firmware). Wann verwenden: - Falsche Werte bei 32‑Bit‑Sensoren (Energieverbrauch, Zähler). - Nach Firmware‑Updates mit geändertem Register‑Layout.

Vollständiges Beispiel

```yaml

Problematische Register deaktivieren

disabled_registers: - 2004 - 100000

Sensornamen überschreiben

sensors_names_override: - id: hp1_flow_temp override_name: "Wohnzimmer Temperatur" - id: hp1_return_temp override_name: "Rücklauf Temperatur"

Cycling‑Offsets

cycling_offsets: hp1: heating_cycling_total: 2500 hot_water_cycling_total: 1200 cooling_cycling_total: 300 defrost_cycling_total: 80 compressor_start_cycling_total: 5000

Energieverbrauchs‑Sensoren

energy_consumption_sensors: hp1: sensor_entity_id: "sensor.eu08l_hp1_compressor_power_consumption_accumulated" hp2: sensor_entity_id: "sensor.shelly_lambda_gesamt_leistung"

Energieverbrauchs‑Offsets (kWh)

energy_consumption_offsets: hp1: heating_energy_total: 5000.0 hot_water_energy_total: 2000.0 cooling_energy_total: 500.0 defrost_energy_total: 150.0

Modbus‑Parameter

modbus: int32_register_order: "high_first" ```

Best Practices

  • Änderungen dokumentieren und versionieren (z. B. per Git).
  • Nach Anpassungen Home Assistant neu starten.
  • Bei externen Energiesensoren sicherstellen, dass Einheit und Auflösung konsistent sind.