From 97de9c9f69d35b00ad67088f4679757e5f0aeb63 Mon Sep 17 00:00:00 2001 From: hahn-th <15319212+hahn-th@users.noreply.github.com> Date: Fri, 5 Jul 2024 09:45:20 +0200 Subject: [PATCH] Revert Homematic IP Cloud unique ID changes (#121231) --- .../homematicip_cloud/generic_entity.py | 11 +- .../components/homematicip_cloud/sensor.py | 288 +++++++++++------- .../fixtures/homematicip_cloud.json | 135 ++++++++ .../homematicip_cloud/test_device.py | 2 +- .../homematicip_cloud/test_sensor.py | 36 +++ 5 files changed, 351 insertions(+), 121 deletions(-) diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index 5cd48515ad7..163f3eec75e 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -216,14 +216,13 @@ class HomematicipGenericEntity(Entity): @property def unique_id(self) -> str: """Return a unique ID.""" - suffix = "" - if self._post is not None: - suffix = f"_{self._post}" - + unique_id = f"{self.__class__.__name__}_{self._device.id}" if self._is_multi_channel: - return f"{self.__class__.__name__}_Channel{self._channel}_{self._device.id}{suffix}" + unique_id = ( + f"{self.__class__.__name__}_Channel{self._channel}_{self._device.id}" + ) - return f"{self.__class__.__name__}_{self._device.id}{suffix}" + return unique_id @property def icon(self) -> str | None: diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 6bf128a1663..1f76c6cce1f 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Callable -from dataclasses import dataclass from typing import Any from homematicip.aio.device import ( @@ -36,7 +35,6 @@ from homematicip.base.functionalChannels import FunctionalChannel from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, - SensorEntityDescription, SensorStateClass, ) from homeassistant.config_entries import ConfigEntry @@ -163,19 +161,28 @@ async def async_setup_entry( for ch in get_channels_from_device( device, FunctionalChannelType.ENERGY_SENSORS_INTERFACE_CHANNEL ): - if ch.connectedEnergySensorType not in SENSORS_ESI: - continue + if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_IEC: + if ch.currentPowerConsumption is not None: + entities.append(HmipEsiIecPowerConsumption(hap, device)) + if ch.energyCounterOneType != ESI_TYPE_UNKNOWN: + entities.append(HmipEsiIecEnergyCounterHighTariff(hap, device)) + if ch.energyCounterTwoType != ESI_TYPE_UNKNOWN: + entities.append(HmipEsiIecEnergyCounterLowTariff(hap, device)) + if ch.energyCounterThreeType != ESI_TYPE_UNKNOWN: + entities.append( + HmipEsiIecEnergyCounterInputSingleTariff(hap, device) + ) - new_entities = [ - HmipEsiSensorEntity(hap, device, ch.index, description) - for description in SENSORS_ESI[ch.connectedEnergySensorType] - ] + if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_GAS: + if ch.currentGasFlow is not None: + entities.append(HmipEsiGasCurrentGasFlow(hap, device)) + if ch.gasVolume is not None: + entities.append(HmipEsiGasGasVolume(hap, device)) - entities.extend( - entity - for entity in new_entities - if entity.entity_description.exists_fn(ch) - ) + if ch.connectedEnergySensorType == ESI_CONNECTED_SENSOR_TYPE_LED: + if ch.currentPowerConsumption is not None: + entities.append(HmipEsiLedCurrentPowerConsumption(hap, device)) + entities.append(HmipEsiLedEnergyCounterHighTariff(hap, device)) async_add_entities(entities) @@ -434,132 +441,185 @@ class HomematicpTemperatureExternalSensorDelta(HomematicipGenericEntity, SensorE return self._device.temperatureExternalDelta -@dataclass(kw_only=True, frozen=True) -class HmipEsiSensorEntityDescription(SensorEntityDescription): - """SensorEntityDescription for HmIP Sensors.""" - - value_fn: Callable[[AsyncEnergySensorsInterface], StateType] - exists_fn: Callable[[FunctionalChannel], bool] - type_fn: Callable[[AsyncEnergySensorsInterface], str] - - -SENSORS_ESI = { - ESI_CONNECTED_SENSOR_TYPE_IEC: [ - HmipEsiSensorEntityDescription( - key=ESI_TYPE_CURRENT_POWER_CONSUMPTION, - native_unit_of_measurement=UnitOfPower.WATT, - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda device: device.functional_channel.currentPowerConsumption, - exists_fn=lambda channel: channel.currentPowerConsumption is not None, - type_fn=lambda device: "CurrentPowerConsumption", - ), - HmipEsiSensorEntityDescription( - key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF, - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.functional_channel.energyCounterOne, - exists_fn=lambda channel: channel.energyCounterOneType != ESI_TYPE_UNKNOWN, - type_fn=lambda device: device.functional_channel.energyCounterOneType, - ), - HmipEsiSensorEntityDescription( - key=ESI_TYPE_ENERGY_COUNTER_USAGE_LOW_TARIFF, - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.functional_channel.energyCounterTwo, - exists_fn=lambda channel: channel.energyCounterTwoType != ESI_TYPE_UNKNOWN, - type_fn=lambda device: device.functional_channel.energyCounterTwoType, - ), - HmipEsiSensorEntityDescription( - key=ESI_TYPE_ENERGY_COUNTER_INPUT_SINGLE_TARIFF, - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.functional_channel.energyCounterThree, - exists_fn=lambda channel: channel.energyCounterThreeType - != ESI_TYPE_UNKNOWN, - type_fn=lambda device: device.functional_channel.energyCounterThreeType, - ), - ], - ESI_CONNECTED_SENSOR_TYPE_LED: [ - HmipEsiSensorEntityDescription( - key=ESI_TYPE_CURRENT_POWER_CONSUMPTION, - native_unit_of_measurement=UnitOfPower.WATT, - device_class=SensorDeviceClass.POWER, - state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda device: device.functional_channel.currentPowerConsumption, - exists_fn=lambda channel: channel.currentPowerConsumption is not None, - type_fn=lambda device: "CurrentPowerConsumption", - ), - HmipEsiSensorEntityDescription( - key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF, - native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, - device_class=SensorDeviceClass.ENERGY, - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.functional_channel.energyCounterOne, - exists_fn=lambda channel: channel.energyCounterOne is not None, - type_fn=lambda device: ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF, - ), - ], - ESI_CONNECTED_SENSOR_TYPE_GAS: [ - HmipEsiSensorEntityDescription( - key=ESI_TYPE_CURRENT_GAS_FLOW, - native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR, - device_class=SensorDeviceClass.VOLUME_FLOW_RATE, - state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda device: device.functional_channel.currentGasFlow, - exists_fn=lambda channel: channel.currentGasFlow is not None, - type_fn=lambda device: "CurrentGasFlow", - ), - HmipEsiSensorEntityDescription( - key=ESI_TYPE_CURRENT_GAS_VOLUME, - native_unit_of_measurement=UnitOfVolume.CUBIC_METERS, - device_class=SensorDeviceClass.GAS, - state_class=SensorStateClass.TOTAL_INCREASING, - value_fn=lambda device: device.functional_channel.gasVolume, - exists_fn=lambda channel: channel.gasVolume is not None, - type_fn=lambda device: "GasVolume", - ), - ], -} - - class HmipEsiSensorEntity(HomematicipGenericEntity, SensorEntity): """EntityDescription for HmIP-ESI Sensors.""" - entity_description: HmipEsiSensorEntityDescription - def __init__( self, hap: HomematicipHAP, device: HomematicipGenericEntity, - channel_index: int, - entity_description: HmipEsiSensorEntityDescription, + key: str, + value_fn: Callable[[FunctionalChannel], StateType], + type_fn: Callable[[FunctionalChannel], str], ) -> None: """Initialize Sensor Entity.""" super().__init__( hap=hap, device=device, - channel=channel_index, - post=entity_description.key, + channel=1, + post=key, is_multi_channel=False, ) - self.entity_description = entity_description + + self._value_fn = value_fn + self._type_fn = type_fn @property def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the esi sensor.""" state_attr = super().extra_state_attributes - state_attr[ATTR_ESI_TYPE] = self.entity_description.type_fn(self) + state_attr[ATTR_ESI_TYPE] = self._type_fn(self.functional_channel) return state_attr @property def native_value(self) -> str | None: """Return the state of the sensor.""" - return str(self.entity_description.value_fn(self)) + return str(self._value_fn(self.functional_channel)) + + +class HmipEsiIecPowerConsumption(HmipEsiSensorEntity): + """Representation of the Hmip-ESI IEC currentPowerConsumption sensor.""" + + _attr_device_class = SensorDeviceClass.POWER + _attr_native_unit_of_measurement = UnitOfPower.WATT + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key="CurrentPowerConsumption", + value_fn=lambda channel: channel.currentPowerConsumption, + type_fn=lambda channel: "CurrentPowerConsumption", + ) + + +class HmipEsiIecEnergyCounterHighTariff(HmipEsiSensorEntity): + """Representation of the Hmip-ESI IEC energyCounterOne sensor.""" + + _attr_device_class = SensorDeviceClass.ENERGY + _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR + _attr_state_class = SensorStateClass.TOTAL_INCREASING + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF, + value_fn=lambda channel: channel.energyCounterOne, + type_fn=lambda channel: channel.energyCounterOneType, + ) + + +class HmipEsiIecEnergyCounterLowTariff(HmipEsiSensorEntity): + """Representation of the Hmip-ESI IEC energyCounterTwo sensor.""" + + _attr_device_class = SensorDeviceClass.ENERGY + _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR + _attr_state_class = SensorStateClass.TOTAL_INCREASING + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key=ESI_TYPE_ENERGY_COUNTER_USAGE_LOW_TARIFF, + value_fn=lambda channel: channel.energyCounterTwo, + type_fn=lambda channel: channel.energyCounterTwoType, + ) + + +class HmipEsiIecEnergyCounterInputSingleTariff(HmipEsiSensorEntity): + """Representation of the Hmip-ESI IEC energyCounterThree sensor.""" + + _attr_device_class = SensorDeviceClass.ENERGY + _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR + _attr_state_class = SensorStateClass.TOTAL_INCREASING + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key=ESI_TYPE_ENERGY_COUNTER_INPUT_SINGLE_TARIFF, + value_fn=lambda channel: channel.energyCounterThree, + type_fn=lambda channel: channel.energyCounterThreeType, + ) + + +class HmipEsiGasCurrentGasFlow(HmipEsiSensorEntity): + """Representation of the Hmip-ESI Gas currentGasFlow sensor.""" + + _attr_device_class = SensorDeviceClass.VOLUME_FLOW_RATE + _attr_native_unit_of_measurement = UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key="CurrentGasFlow", + value_fn=lambda channel: channel.currentGasFlow, + type_fn=lambda channel: "CurrentGasFlow", + ) + + +class HmipEsiGasGasVolume(HmipEsiSensorEntity): + """Representation of the Hmip-ESI Gas gasVolume sensor.""" + + _attr_device_class = SensorDeviceClass.GAS + _attr_native_unit_of_measurement = UnitOfVolume.CUBIC_METERS + _attr_state_class = SensorStateClass.TOTAL_INCREASING + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key="GasVolume", + value_fn=lambda channel: channel.gasVolume, + type_fn=lambda channel: "GasVolume", + ) + + +class HmipEsiLedCurrentPowerConsumption(HmipEsiSensorEntity): + """Representation of the Hmip-ESI LED currentPowerConsumption sensor.""" + + _attr_device_class = SensorDeviceClass.POWER + _attr_native_unit_of_measurement = UnitOfPower.WATT + _attr_state_class = SensorStateClass.MEASUREMENT + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key="CurrentPowerConsumption", + value_fn=lambda channel: channel.currentPowerConsumption, + type_fn=lambda channel: "CurrentPowerConsumption", + ) + + +class HmipEsiLedEnergyCounterHighTariff(HmipEsiSensorEntity): + """Representation of the Hmip-ESI LED energyCounterOne sensor.""" + + _attr_device_class = SensorDeviceClass.ENERGY + _attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR + _attr_state_class = SensorStateClass.TOTAL_INCREASING + + def __init__(self, hap: HomematicipHAP, device) -> None: + """Initialize the device.""" + super().__init__( + hap, + device, + key=ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF, + value_fn=lambda channel: channel.energyCounterOne, + type_fn=lambda channel: ESI_TYPE_ENERGY_COUNTER_USAGE_HIGH_TARIFF, + ) class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity, SensorEntity): diff --git a/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json b/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json index eba2c803b1f..e67ffd78467 100644 --- a/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json +++ b/tests/components/homematicip_cloud/fixtures/homematicip_cloud.json @@ -7757,6 +7757,141 @@ "serializedGlobalTradeItemNumber": "3014F711000000000ESIIEC2", "type": "ENERGY_SENSORS_INTERFACE", "updateState": "UP_TO_DATE" + }, + "3014F7110000000000ESIIE3": { + "availableFirmwareVersion": "0.0.0", + "connectionType": "HMIP_RF", + "deviceArchetype": "HMIP", + "firmwareVersion": "1.0.6", + "firmwareVersionInteger": 65542, + "functionalChannels": { + "0": { + "busConfigMismatch": null, + "coProFaulty": false, + "coProRestartNeeded": false, + "coProUpdateFailure": false, + "configPending": false, + "controlsMountingOrientation": null, + "daliBusState": null, + "defaultLinkedGroup": [], + "deviceCommunicationError": null, + "deviceDriveError": null, + "deviceDriveModeError": null, + "deviceId": "3014F7110000000000ESIIE3", + "deviceOperationMode": null, + "deviceOverheated": false, + "deviceOverloaded": false, + "devicePowerFailureDetected": false, + "deviceUndervoltage": false, + "displayContrast": null, + "dutyCycle": false, + "functionalChannelType": "DEVICE_BASE", + "groupIndex": 0, + "groups": ["00000000-0000-0000-0000-000000000031"], + "index": 0, + "label": "", + "lockJammed": null, + "lowBat": false, + "mountingOrientation": null, + "multicastRoutingEnabled": false, + "particulateMatterSensorCommunicationError": null, + "particulateMatterSensorError": null, + "powerShortCircuit": null, + "profilePeriodLimitReached": null, + "routerModuleEnabled": false, + "routerModuleSupported": false, + "rssiDeviceValue": -94, + "rssiPeerValue": null, + "sensorCommunicationError": false, + "sensorError": true, + "shortCircuitDataLine": null, + "supportedOptionalFeatures": { + "IFeatureBusConfigMismatch": false, + "IFeatureDeviceCoProError": false, + "IFeatureDeviceCoProRestart": false, + "IFeatureDeviceCoProUpdate": false, + "IFeatureDeviceCommunicationError": false, + "IFeatureDeviceDaliBusError": false, + "IFeatureDeviceDriveError": false, + "IFeatureDeviceDriveModeError": false, + "IFeatureDeviceIdentify": false, + "IFeatureDeviceOverheated": false, + "IFeatureDeviceOverloaded": false, + "IFeatureDeviceParticulateMatterSensorCommunicationError": false, + "IFeatureDeviceParticulateMatterSensorError": false, + "IFeatureDevicePowerFailure": false, + "IFeatureDeviceSensorCommunicationError": true, + "IFeatureDeviceSensorError": true, + "IFeatureDeviceTemperatureHumiditySensorCommunicationError": false, + "IFeatureDeviceTemperatureHumiditySensorError": false, + "IFeatureDeviceTemperatureOutOfRange": false, + "IFeatureDeviceUndervoltage": false, + "IFeatureMulticastRouter": false, + "IFeaturePowerShortCircuit": false, + "IFeatureProfilePeriodLimit": false, + "IFeatureRssiValue": true, + "IFeatureShortCircuitDataLine": false, + "IOptionalFeatureDefaultLinkedGroup": false, + "IOptionalFeatureDeviceErrorLockJammed": false, + "IOptionalFeatureDeviceOperationMode": false, + "IOptionalFeatureDisplayContrast": false, + "IOptionalFeatureDutyCycle": true, + "IOptionalFeatureLowBat": true, + "IOptionalFeatureMountingOrientation": false + }, + "temperatureHumiditySensorCommunicationError": null, + "temperatureHumiditySensorError": null, + "temperatureOutOfRange": false, + "unreach": false + }, + "1": { + "channelRole": "ENERGY_SENSOR", + "connectedEnergySensorType": "ES_LED", + "currentGasFlow": null, + "currentPowerConsumption": 189.15, + "deviceId": "3014F7110000000000ESIIE3", + "energyCounterOne": 23825.748, + "energyCounterOneType": "UNKNOWN", + "energyCounterThree": null, + "energyCounterThreeType": "UNKNOWN", + "energyCounterTwo": null, + "energyCounterTwoType": "UNKNOWN", + "functionalChannelType": "ENERGY_SENSORS_INTERFACE_CHANNEL", + "gasVolume": null, + "gasVolumePerImpulse": 0.01, + "groupIndex": 1, + "groups": ["00000000-0000-0000-0000-000000000057"], + "impulsesPerKWH": 1000, + "index": 1, + "label": "", + "supportedOptionalFeatures": { + "IOptionalFeatureCounterOffset": true, + "IOptionalFeatureCurrentGasFlow": false, + "IOptionalFeatureCurrentPowerConsumption": true, + "IOptionalFeatureEnergyCounterOne": true, + "IOptionalFeatureEnergyCounterThree": false, + "IOptionalFeatureEnergyCounterTwo": false, + "IOptionalFeatureGasVolume": false, + "IOptionalFeatureGasVolumePerImpulse": false, + "IOptionalFeatureImpulsesPerKWH": true + } + } + }, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "3014F7110000000000ESIIE3", + "label": "esi_led", + "lastStatusUpdate": 1702420986697, + "liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED", + "manuallyUpdateForced": false, + "manufacturerCode": 1, + "measuredAttributes": {}, + "modelId": 509, + "modelType": "HmIP-ESI", + "oem": "eQ-3", + "permanentlyReachable": false, + "serializedGlobalTradeItemNumber": "3014F7110000000000ESIIE3", + "type": "ENERGY_SENSORS_INTERFACE", + "updateState": "UP_TO_DATE" } }, "groups": { diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index 348171b3187..074a30e94b2 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -26,7 +26,7 @@ async def test_hmip_load_all_supported_devices( test_devices=None, test_groups=None ) - assert len(mock_hap.hmip_device_by_entity_id) == 290 + assert len(mock_hap.hmip_device_by_entity_id) == 293 async def test_hmip_remove_device( diff --git a/tests/components/homematicip_cloud/test_sensor.py b/tests/components/homematicip_cloud/test_sensor.py index 6951b750b2f..2b62c46fd72 100644 --- a/tests/components/homematicip_cloud/test_sensor.py +++ b/tests/components/homematicip_cloud/test_sensor.py @@ -634,3 +634,39 @@ async def test_hmip_esi_gas_gas_volume( ) assert ha_state.state == "1019.26" + + +async def test_hmip_esi_led_current_power_consumption( + hass: HomeAssistant, default_mock_hap_factory +) -> None: + """Test ESI-IEC currentPowerConsumption Sensor.""" + entity_id = "sensor.esi_led_currentPowerConsumption" + entity_name = "esi_led CurrentPowerConsumption" + device_model = "HmIP-ESI" + mock_hap = await default_mock_hap_factory.async_get_mock_hap( + test_devices=["esi_led"] + ) + + ha_state, hmip_device = get_and_check_entity_basics( + hass, mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == "189.15" + + +async def test_hmip_esi_led_energy_counter_usage_high_tariff( + hass: HomeAssistant, default_mock_hap_factory +) -> None: + """Test ESI-IEC ENERGY_COUNTER_USAGE_HIGH_TARIFF.""" + entity_id = "sensor.esi_led_energy_counter_usage_high_tariff" + entity_name = "esi_led ENERGY_COUNTER_USAGE_HIGH_TARIFF" + device_model = "HmIP-ESI" + mock_hap = await default_mock_hap_factory.async_get_mock_hap( + test_devices=["esi_led"] + ) + + ha_state, hmip_device = get_and_check_entity_basics( + hass, mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == "23825.748"