COP-Sensoren - Technische Dokumentation¶
Diese Dokumentation beschreibt die technische Implementierung der COP-Sensoren (Coefficient of Performance) in der Lambda Heat Pumps Integration.
Übersicht¶
Die COP-Sensoren berechnen automatisch die Leistungszahl (COP) einer Wärmepumpe basierend auf dem Verhältnis von thermischer Energie zu elektrischer Energie:
Formel: COP = Thermal Energy (kWh) / Electrical Energy (kWh)
Die Sensoren werden als echte Python-Entities (LambdaCOPSensor) implementiert, nicht als Template-Sensoren, um bessere Performance und direkte Kontrolle zu ermöglichen.
Architektur¶
Komponenten¶
┌─────────────────────────────────────────────────────────────┐
│ sensor.py │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LambdaCOPSensor (RestoreEntity, SensorEntity) │ │
│ │ - _calculate_cop() │ │
│ │ - _update_cop() │ │
│ │ - State-Tracking (async_track_state_change_event) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ Liest States │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LambdaEnergyConsumptionSensor (Quellsensoren) │ │
│ │ - thermal_energy_{period} │ │
│ │ - energy_{period} │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Datenfluss¶
- Sensor-Erstellung (in
async_setup_entry): - Für jede Wärmepumpe (hp1, hp2, ...)
- Für jeden Mode (heating, hot_water, cooling)
- Für jeden Period (daily, monthly, total)
- Generiere Entity-IDs für Quellsensoren
-
Erstelle
LambdaCOPSensor-Instanz -
Initialisierung (
async_added_to_hass): - RestoreState von vorherigem Wert (falls vorhanden)
- Registriere State-Change-Tracker für Quellsensoren
-
Berechne initialen COP-Wert
-
State-Tracking:
async_track_state_change_eventüberwacht beide Quellsensoren- Bei State-Änderung wird
_update_cop()aufgerufen -
COP wird neu berechnet und State aktualisiert
-
COP-Berechnung (
_calculate_cop): - Liest States von thermal_energy und energy Sensoren
- Prüft Verfügbarkeit beider Sensoren
- Berechnet COP = thermal / electrical
- Division durch Null Schutz (COP = 0.0 wenn electrical <= 0)
- Rundet auf 2 Dezimalstellen
Hinweis: Periodische COP-Sensoren (daily, monthly, yearly, hourly) bauen sich erst im Lauf der Zeit auf; bis keine Berechnung stattgefunden hat, können sie unknown oder 0 anzeigen. Total-COP nutzt eine Baseline (Deltas seit Stichtag). Zyklische COP (inkl. stündlich) nutzen eigene Zyklus-Baselines aus den Quellsensoren. Siehe FAQ – COP-Sensoren.
Warum Baseline?¶
Die Baseline wird nur benötigt, weil ein Quellsensor früher in der Integration vorhanden ist, der andere später angelegt wird. Mit diesem Release kommen die thermischen Energy-Sensoren dazu; die elektrischen Energy-Sensoren gab es bereits. Ohne Baseline wäre COP = Total_thermal / Total_electrical verfälscht: Die elektrische Total-Summe umfasst einen längeren Zeitraum als die thermische (die erst ab Einführung der thermischen Sensoren zählt). Mit Baseline speichert man die Werte beider Quellen zu einem Stichtag (z. B. beim ersten Start, an dem beide vorhanden sind) und rechnet nur die Deltas seit diesem Stichtag: COP = (Total_thermal − thermal_baseline) / (Total_electrical − electrical_baseline). So gilt die COP ausschließlich für den Zeitraum, in dem beide Quellsensoren aktiv sind.
- Total-COP: Verwendet immer die Baseline (einmalig beim ersten Start bzw. aus Restore).
- Zyklische COP (daily, monthly, yearly, hourly): Jeder hat eigene Zyklus-Baselines (Werte der Quellsensoren zu Zyklusstart). Sind Baselines gesetzt, wird immer
COP = (Quellsensor_thermal − thermal_baseline) / (Quellsensor_electrical − electrical_baseline)gerechnet. Sind beide Quellsensoren 0 oder einer nicht verfügbar, dient Total − Baseline als Fallback. Ohne Baselines: direkte Divisionperiod_thermal / period_electrical.
Implementierung¶
1. Sensor-Klasse¶
Die LambdaCOPSensor Klasse ist in sensor.py implementiert:
class LambdaCOPSensor(RestoreEntity, SensorEntity):
"""COP (Coefficient of Performance) sensor."""
def __init__(
self,
hass,
entry,
sensor_id,
name,
entity_id,
unique_id,
unit,
state_class,
device_class,
device_type,
hp_index,
mode,
period,
thermal_energy_entity_id,
electrical_energy_entity_id,
):
# ... Initialisierung ...
self._thermal_energy_entity_id = thermal_energy_entity_id
self._electrical_energy_entity_id = electrical_energy_entity_id
self._precision = 2
self._cop_value = None
2. Sensor-Registrierung¶
COP-Sensoren werden in async_setup_entry erstellt (nach thermal energy Sensoren):
# COP sensors (per HP, per mode, per period)
cop_modes = ["heating", "hot_water", "cooling"]
cop_periods = ["daily", "monthly", "total"]
for hp_idx in range(1, num_hps + 1):
for mode in cop_modes:
for period in cop_periods:
# Generiere Entity-IDs für Quell-Sensoren
thermal_entity_id = ... # z.B. sensor.eu08l_hp1_heating_thermal_energy_daily
electrical_entity_id = ... # z.B. sensor.eu08l_hp1_heating_energy_daily
# Erstelle COP-Sensor
cop_sensor = LambdaCOPSensor(
hass, entry, sensor_id, name, entity_id, unique_id,
None, # Keine Einheit (COP ist dimensionslos)
"measurement", # State class
None, # Keine device_class
"hp", hp_idx, mode, period,
thermal_entity_id, electrical_entity_id,
)
sensors.append(cop_sensor)
3. COP-Berechnung¶
Die _calculate_cop() Methode implementiert die Berechnung:
def _calculate_cop(self) -> float | None:
"""Berechne COP aus thermal_energy und electrical_energy."""
thermal_state = self.hass.states.get(self._thermal_energy_entity_id)
electrical_state = self.hass.states.get(self._electrical_energy_entity_id)
# Prüfe Verfügbarkeit
if not thermal_state or thermal_state.state in (None, "unknown", "unavailable"):
return None
if not electrical_state or electrical_state.state in (None, "unknown", "unavailable"):
return None
# Konvertiere zu float
thermal_value = float(thermal_state.state)
electrical_value = float(electrical_state.state)
# Division durch Null Schutz
if electrical_value <= 0:
return 0.0
# Berechne COP
cop = thermal_value / electrical_value
return round(cop, self._precision) # 2 Dezimalstellen
4. State-Tracking¶
State-Tracking wird in async_added_to_hass registriert:
async def async_added_to_hass(self):
await super().async_added_to_hass()
# RestoreState
last_state = await self.async_get_last_state()
await self.restore_state(last_state)
# Registriere State-Change-Tracker
track_entities = [
self._thermal_energy_entity_id,
self._electrical_energy_entity_id,
]
@callback
def _state_change_callback(event):
new_state = event.data.get("new_state")
old_state = event.data.get("old_state")
entity_id = event.data.get("entity_id")
if new_state is None:
return
if old_state is None or old_state.state != new_state.state:
self._update_cop()
self._unsub_state_changes = async_track_state_change_event(
self.hass, track_entities, _state_change_callback
)
# Initialisiere State
if self._cop_value is None:
self._update_cop()
else:
self.async_write_ha_state()
5. State-Update¶
Die _update_cop() Methode wird bei Quellsensor-Änderungen aufgerufen:
@callback
def _update_cop(self):
"""Update COP value when source sensors change."""
old_cop = self._cop_value
new_cop = self._calculate_cop()
if new_cop != old_cop:
self._cop_value = new_cop
self.async_write_ha_state()
Quellsensoren¶
Die COP-Sensoren lesen ihre Werte aus den Energy-Consumption-Sensoren (thermal_energy_, energy_). Deren Daten stammen von den konfigurierbaren Quellsensoren: In der lambda_wp_config.yaml (Abschnitt energy_consumption_sensors) können pro HP sensor_entity_id (elektrisch) und thermal_sensor_entity_id (thermisch, optional) gesetzt werden. Ohne Konfiguration werden die Lambda-Modbus-Sensoren verwendet.
Thermische Energie (Quellen der Energy-Sensoren)¶
- Entity-ID Pattern:
sensor.{prefix}_hp{idx}_{mode}_thermal_energy_{period} - Beispiel:
sensor.eu08l_hp1_heating_thermal_energy_daily - Typ:
LambdaEnergyConsumptionSensormitsensor_type="thermal" - Quelle: In Config gesetzter
thermal_sensor_entity_id, sonst Standardcompressor_thermal_energy_output_accumulated(Modbus Register 1022). Siehe Energieverbrauchssensoren – Quellsensoren.
Elektrische Energie (Quellen der Energy-Sensoren)¶
- Entity-ID Pattern:
sensor.{prefix}_hp{idx}_{mode}_energy_{period} - Beispiel:
sensor.eu08l_hp1_heating_energy_daily - Typ:
LambdaEnergyConsumptionSensormitsensor_type="electrical" - Quelle: In Config gesetzter
sensor_entity_id, sonst Standardcompressor_power_consumption_accumulated(Modbus Register 1021). Siehe Energieverbrauchssensoren – Quellsensoren.
Sensoren pro Wärmepumpe¶
Für jede Wärmepumpe werden folgende COP-Sensoren erstellt:
| Mode | Period | Sensor-ID | Entity-ID (Beispiel) |
|---|---|---|---|
| heating | daily | heating_cop_daily |
sensor.eu08l_hp1_heating_cop_daily |
| heating | monthly | heating_cop_monthly |
sensor.eu08l_hp1_heating_cop_monthly |
| heating | total | heating_cop_total |
sensor.eu08l_hp1_heating_cop_total |
| hot_water | daily | hot_water_cop_daily |
sensor.eu08l_hp1_hot_water_cop_daily |
| hot_water | monthly | hot_water_cop_monthly |
sensor.eu08l_hp1_hot_water_cop_monthly |
| hot_water | total | hot_water_cop_total |
sensor.eu08l_hp1_hot_water_cop_total |
| cooling | daily | cooling_cop_daily |
sensor.eu08l_hp1_cooling_cop_daily |
| cooling | monthly | cooling_cop_monthly |
sensor.eu08l_hp1_cooling_cop_monthly |
| cooling | total | cooling_cop_total |
sensor.eu08l_hp1_cooling_cop_total |
Total: 9 COP-Sensoren pro Wärmepumpe
State-Management¶
RestoreEntity¶
Die LambdaCOPSensor Klasse erbt von RestoreEntity, um State-Persistenz zu ermöglichen:
- Werte werden beim HA-Neustart automatisch wiederhergestellt
restore_state()wird inasync_added_to_hass()aufgerufen- Falls kein vorheriger State vorhanden ist, wird COP initial berechnet
State-Class¶
- Type:
SensorStateClass.MEASUREMENT - Zweck: Für momentane Werte (nicht kumulativ)
- Recorder: Werte werden automatisch im Home Assistant Recorder gespeichert
- Grafana/InfluxDB: Kompatibel für Visualisierung
Fehlerbehandlung¶
Division durch Null¶
Wenn electrical_energy <= 0, wird COP = 0.0 zurückgegeben:
Unavailable Quellsensoren¶
Wenn ein Quellsensor nicht verfügbar ist, wird COP = None (unavailable) zurückgegeben:
Fehlerhafte Werte¶
Bei Konvertierungsfehlern (ValueError, TypeError) wird ein Warning geloggt und None zurückgegeben.
Konsistenz Total-COP (Baseline nach Neustart)¶
Problem: Nach Neustart oder Reset der Quellsensoren können persistierte Baselines (thermal_baseline, electrical_baseline) höher sein als die aktuellen Werte der thermischen bzw. elektrischen Total-Sensoren. Dann wären die effektiven Deltas negativ (effective_thermal = current − baseline < 0), was zu falschem oder „unavailable“ COP führt.
Lösung: Beim Restore der Total-COP-Baselines aus den Attributen wird geprüft:
- Ist
thermal_baselinegrößer als der aktuelle State des thermischen Quellsensors → Baseline wird auf den aktuellen Wert gesetzt. - Ist
electrical_baselinegrößer als der aktuelle State des elektrischen Quellsensors → Baseline wird auf den aktuellen Wert gesetzt.
Damit gilt nach dem Restore stets: Baseline ≤ aktueller Quellwert; negative Deltas und daraus resultierende Fehlanzeigen werden vermieden. Die Prüfung erfolgt in LambdaCOPSensor.restore_state() (sensor.py).
Hinweis: Zyklische COP (daily, monthly, yearly, hourly) haben eigene Zyklus-Baselines; sofern gesetzt, wird immer (Quellsensor − Baseline) gerechnet, sonst direkte Division (siehe Abschnitt „Warum Baseline?“).
Performance¶
Vorteile gegenüber Template-Sensoren¶
- Kein Template-Rendering: Direkte Berechnung ohne Jinja2-Overhead
- Direkter State-Zugriff: Kein Template-Parsing nötig
- Optimiertes State-Tracking: Nur bei tatsächlichen Änderungen
- Eigene Entity-Klasse: Bessere Kontrolle über Update-Verhalten
State-Tracking-Optimierung¶
- Nur bei State-Änderungen wird COP neu berechnet
old_state.state != new_state.statePrüfung verhindert unnötige Updates- Callback-Funktion ist mit
@callbackdekoriert (nicht-blockierend)
Erweiterbarkeit¶
Weitere Zeiträume hinzufügen¶
Um weitere Zeiträume (z.B. yearly, 2h, 4h) hinzuzufügen:
-
Erweitere
cop_periodsinasync_setup_entry: -
Stelle sicher, dass Quellsensoren für diese Perioden existieren
- Keine weiteren Code-Änderungen nötig
Weitere Modi hinzufügen¶
Um weitere Modi (z.B. defrost) hinzuzufügen:
-
Erweitere
cop_modesinasync_setup_entry: -
Stelle sicher, dass Quellsensoren für diese Modi existieren
Testing¶
Die COP-Sensoren können getestet werden durch:
- Unit-Tests: Teste
_calculate_cop()mit verschiedenen Inputs - Integration-Tests: Teste Sensor-Erstellung und State-Updates
- Manuelle Tests: Überwache COP-Werte in Home Assistant UI
Beispiel-Test¶
def test_cop_calculation():
sensor = LambdaCOPSensor(...)
# Mock Quellsensoren
sensor.hass.states.get = Mock(return_value=Mock(state="10.0")) # thermal
sensor.hass.states.get = Mock(return_value=Mock(state="2.0")) # electrical
cop = sensor._calculate_cop()
assert cop == 5.0
# Division durch Null
sensor.hass.states.get = Mock(return_value=Mock(state="0.0")) # electrical
cop = sensor._calculate_cop()
assert cop == 0.0
Abhängigkeiten¶
Die COP-Sensoren sind abhängig von:
- LambdaEnergyConsumptionSensor: Quellsensoren müssen existieren
- State-Tracking:
async_track_state_change_eventaus Home Assistant - RestoreEntity: Für State-Persistenz
Code-Stellen¶
- Klasse:
custom_components/lambda_heat_pumps/sensor.py(Zeile 1413-1641) - Registrierung:
custom_components/lambda_heat_pumps/sensor.py(Zeile 688-743) - Translations:
custom_components/lambda_heat_pumps/translations/de.jsonunden.json
Verwandte Dokumentation¶
- Energieverbrauchssensoren - Technische Details zu Quellsensoren
- Sensor-Implementierung - Anwenderdokumentation