From a85bb98743de41e863463b8fce525d31052c8f00 Mon Sep 17 00:00:00 2001 From: balazs92117 Date: Tue, 11 Feb 2025 17:25:57 +0100 Subject: [PATCH] Dsmr eon hungary (#138162) Add EON hungary --- homeassistant/components/dsmr/config_flow.py | 2 +- homeassistant/components/dsmr/const.py | 2 +- homeassistant/components/dsmr/sensor.py | 160 ++++++++++++++++++- homeassistant/components/dsmr/strings.json | 63 ++++++++ tests/components/dsmr/conftest.py | 6 + tests/components/dsmr/test_config_flow.py | 10 ++ tests/components/dsmr/test_sensor.py | 70 ++++++++ 7 files changed, 303 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index 7d6a641b006..6f15f99517b 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -59,7 +59,7 @@ class DSMRConnection: self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER if dsmr_version == "5B": self._equipment_identifier = obis_ref.BELGIUM_EQUIPMENT_IDENTIFIER - if dsmr_version == "5L": + if dsmr_version in ("5L", "5EONHU"): self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER if dsmr_version == "Q3D": self._equipment_identifier = obis_ref.Q3D_EQUIPMENT_IDENTIFIER diff --git a/homeassistant/components/dsmr/const.py b/homeassistant/components/dsmr/const.py index 4c6cb31ca4d..2682b4df1cc 100644 --- a/homeassistant/components/dsmr/const.py +++ b/homeassistant/components/dsmr/const.py @@ -28,7 +28,7 @@ DEVICE_NAME_GAS = "Gas Meter" DEVICE_NAME_WATER = "Water Meter" DEVICE_NAME_HEAT = "Heat Meter" -DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"} +DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D", "5EONHU"} DSMR_PROTOCOL = "dsmr_protocol" RFXTRX_DSMR_PROTOCOL = "rfxtrx_dsmr_protocol" diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 245a28c62db..ba528271824 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -115,7 +115,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_active_tariff", translation_key="electricity_active_tariff", obis_reference="ELECTRICITY_ACTIVE_TARIFF", - dsmr_versions={"2.2", "4", "5", "5B", "5L"}, + dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"}, device_class=SensorDeviceClass.ENUM, options=["low", "normal"], ), @@ -123,7 +123,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_used_tariff_1", translation_key="electricity_used_tariff_1", obis_reference="ELECTRICITY_USED_TARIFF_1", - dsmr_versions={"2.2", "4", "5", "5B", "5L"}, + dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"}, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -131,7 +131,25 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_used_tariff_2", translation_key="electricity_used_tariff_2", obis_reference="ELECTRICITY_USED_TARIFF_2", - dsmr_versions={"2.2", "4", "5", "5B", "5L"}, + dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"}, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + DSMRSensorEntityDescription( + key="electricity_used_tariff_3", + translation_key="electricity_used_tariff_3", + obis_reference="ELECTRICITY_USED_TARIFF_3", + dsmr_versions={"5EONHU"}, + force_update=True, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + DSMRSensorEntityDescription( + key="electricity_used_tariff_4", + translation_key="electricity_used_tariff_4", + obis_reference="ELECTRICITY_USED_TARIFF_4", + dsmr_versions={"5EONHU"}, + force_update=True, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -139,7 +157,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_delivered_tariff_1", translation_key="electricity_delivered_tariff_1", obis_reference="ELECTRICITY_DELIVERED_TARIFF_1", - dsmr_versions={"2.2", "4", "5", "5B", "5L"}, + dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"}, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -147,7 +165,25 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_delivered_tariff_2", translation_key="electricity_delivered_tariff_2", obis_reference="ELECTRICITY_DELIVERED_TARIFF_2", - dsmr_versions={"2.2", "4", "5", "5B", "5L"}, + dsmr_versions={"2.2", "4", "5", "5B", "5L", "5EONHU"}, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + DSMRSensorEntityDescription( + key="electricity_delivered_tariff_3", + translation_key="electricity_delivered_tariff_3", + obis_reference="ELECTRICITY_DELIVERED_TARIFF_3", + dsmr_versions={"5EONHU"}, + force_update=True, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + DSMRSensorEntityDescription( + key="electricity_delivered_tariff_4", + translation_key="electricity_delivered_tariff_4", + obis_reference="ELECTRICITY_DELIVERED_TARIFF_4", + dsmr_versions={"5EONHU"}, + force_update=True, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -341,7 +377,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_imported_total", translation_key="electricity_imported_total", obis_reference="ELECTRICITY_IMPORTED_TOTAL", - dsmr_versions={"5L", "5S", "Q3D"}, + dsmr_versions={"5L", "5S", "Q3D", "5EONHU"}, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -349,7 +385,7 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( key="electricity_exported_total", translation_key="electricity_exported_total", obis_reference="ELECTRICITY_EXPORTED_TOTAL", - dsmr_versions={"5L", "5S", "Q3D"}, + dsmr_versions={"5L", "5S", "Q3D", "5EONHU"}, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, ), @@ -387,6 +423,113 @@ SENSORS: tuple[DSMRSensorEntityDescription, ...] = ( device_class=SensorDeviceClass.GAS, state_class=SensorStateClass.TOTAL_INCREASING, ), + DSMRSensorEntityDescription( + key="actual_threshold_electricity", + translation_key="actual_threshold_electricity", + obis_reference="ACTUAL_TRESHOLD_ELECTRICITY", # Misspelled in external tool + dsmr_versions={"5EONHU"}, + device_class=SensorDeviceClass.POWER, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="eon_hu_electricity_combined", + translation_key="electricity_combined", + obis_reference="EON_HU_ELECTRICITY_COMBINED", + dsmr_versions={"5EONHU"}, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, + ), + DSMRSensorEntityDescription( + key="eon_hu_instantaneous_power_factor_total", + translation_key="instantaneous_power_factor_total", + obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_TOTAL", + dsmr_versions={"5EONHU"}, + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.POWER_FACTOR, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="eon_hu_instantaneous_power_factor_l1", + translation_key="instantaneous_power_factor_l1", + obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_L1", + dsmr_versions={"5EONHU"}, + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.POWER_FACTOR, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="eon_hu_instantaneous_power_factor_l2", + translation_key="instantaneous_power_factor_l2", + obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_L2", + dsmr_versions={"5EONHU"}, + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.POWER_FACTOR, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="eon_hu_instantaneous_power_factor_l3", + translation_key="instantaneous_power_factor_l3", + obis_reference="EON_HU_INSTANTANEOUS_POWER_FACTOR_L3", + dsmr_versions={"5EONHU"}, + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.POWER_FACTOR, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="eon_hu_frequency", + translation_key="frequency", + obis_reference="EON_HU_FREQUENCY", + dsmr_versions={"5EONHU"}, + entity_registry_enabled_default=False, + device_class=SensorDeviceClass.FREQUENCY, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="fuse_threshold_l1", + translation_key="fuse_threshold_l1", + obis_reference="FUSE_THRESHOLD_L1", + dsmr_versions={"5EONHU"}, + device_class=SensorDeviceClass.CURRENT, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="fuse_threshold_l2", + translation_key="fuse_threshold_l2", + obis_reference="FUSE_THRESHOLD_L2", + dsmr_versions={"5EONHU"}, + device_class=SensorDeviceClass.CURRENT, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="fuse_threshold_l3", + translation_key="fuse_threshold_l3", + obis_reference="FUSE_THRESHOLD_L3", + dsmr_versions={"5EONHU"}, + device_class=SensorDeviceClass.CURRENT, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), + DSMRSensorEntityDescription( + key="text_message", + translation_key="text_message", + obis_reference="TEXT_MESSAGE", + dsmr_versions={"5EONHU"}, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + entity_category=EntityCategory.DIAGNOSTIC, + ), ) SENSORS_MBUS_DEVICE_TYPE: dict[int, tuple[DSMRSensorEntityDescription, ...]] = { @@ -844,8 +987,9 @@ class DSMREntity(SensorEntity): def translate_tariff(value: str, dsmr_version: str) -> str | None: """Convert 2/1 to normal/low depending on DSMR version.""" # DSMR V5B: Note: In Belgium values are swapped: + # DSMR V5EONHU: Note: In EON HUngary values are swapped: # Rate code 2 is used for low rate and rate code 1 is used for normal rate. - if dsmr_version == "5B": + if dsmr_version in ("5B", "5EONHU"): if value == "0001": value = "0002" elif value == "0002": diff --git a/homeassistant/components/dsmr/strings.json b/homeassistant/components/dsmr/strings.json index 055c0c41264..871dd382f2b 100644 --- a/homeassistant/components/dsmr/strings.json +++ b/homeassistant/components/dsmr/strings.json @@ -61,6 +61,12 @@ "electricity_delivered_tariff_2": { "name": "Energy production (tarif 2)" }, + "electricity_delivered_tariff_3": { + "name": "Energy production (tarif 3)" + }, + "electricity_delivered_tariff_4": { + "name": "Energy production (tarif 4)" + }, "electricity_exported_total": { "name": "Energy production (total)" }, @@ -73,6 +79,12 @@ "electricity_used_tariff_2": { "name": "Energy consumption (tarif 2)" }, + "electricity_used_tariff_3": { + "name": "Energy consumption (tarif 3)" + }, + "electricity_used_tariff_4": { + "name": "Energy consumption (tarif 4)" + }, "gas_meter_reading": { "name": "Gas consumption" }, @@ -150,6 +162,57 @@ }, "water_meter_reading": { "name": "Water consumption" + }, + "actual_threshold_electricity": { + "name": "Actual threshold electricity" + }, + "electricity_reactive_imported_total": { + "name": "Imported reactive electricity (total)" + }, + "electricity_reactive_exported_total": { + "name": "Exported reactive electricity (total)" + }, + "electricity_reactive_total_q1": { + "name": "Reactive electricity (Q1)" + }, + "electricity_reactive_total_q2": { + "name": "Reactive electricity (Q2)" + }, + "electricity_reactive_total_q3": { + "name": "Reactive electricity (Q3)" + }, + "electricity_reactive_total_q4": { + "name": "Reactive electricity (Q4)" + }, + "electricity_combined": { + "name": "Energy combined" + }, + "instantaneous_power_factor_total": { + "name": "Instantaneous power factor (total)" + }, + "instantaneous_power_factor_l1": { + "name": "Instantaneous power factor L1" + }, + "instantaneous_power_factor_l2": { + "name": "Instantaneous power factor L2" + }, + "instantaneous_power_factor_l3": { + "name": "Instantaneous power factor L3" + }, + "frequency": { + "name": "Frequency" + }, + "fuse_threshold_l1": { + "name": "Fuse threshold on L1" + }, + "fuse_threshold_l2": { + "name": "Fuse threshold on L2" + }, + "fuse_threshold_l3": { + "name": "Fuse threshold on L3" + }, + "text_message": { + "name": "Text message" } } }, diff --git a/tests/components/dsmr/conftest.py b/tests/components/dsmr/conftest.py index ccb7920e141..769e98f6db4 100644 --- a/tests/components/dsmr/conftest.py +++ b/tests/components/dsmr/conftest.py @@ -113,6 +113,12 @@ def dsmr_connection_send_validate_fixture() -> Generator[ EQUIPMENT_IDENTIFIER_GAS, [{"value": "123456789", "unit": ""}] ), } + if args[1] == "5EONHU": + protocol.telegram = { + LUXEMBOURG_EQUIPMENT_IDENTIFIER: CosemObject( + LUXEMBOURG_EQUIPMENT_IDENTIFIER, [{"value": "12345678", "unit": ""}] + ), + } if args[1] == "5S": protocol.telegram = { P1_MESSAGE_TIMESTAMP: CosemObject( diff --git a/tests/components/dsmr/test_config_flow.py b/tests/components/dsmr/test_config_flow.py index 91adf38eacf..961c9831f44 100644 --- a/tests/components/dsmr/test_config_flow.py +++ b/tests/components/dsmr/test_config_flow.py @@ -163,6 +163,16 @@ async def test_setup_network_rfxtrx( "serial_id_gas": "123456789", }, ), + ( + "5EONHU", + { + "port": "/dev/ttyUSB1234", + "dsmr_version": "5EONHU", + "protocol": "dsmr_protocol", + "serial_id": "12345678", + "serial_id_gas": None, + }, + ), ( "5S", { diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index fbe14b38aa3..5657c5999ce 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -512,6 +512,76 @@ async def test_luxembourg_meter( ) +async def test_eonhu_meter( + hass: HomeAssistant, dsmr_connection_fixture: tuple[MagicMock, MagicMock, MagicMock] +) -> None: + """Test if v5 meter is correctly parsed.""" + (connection_factory, transport, protocol) = dsmr_connection_fixture + + entry_data = { + "port": "/dev/ttyUSB0", + "dsmr_version": "5EONHU", + "serial_id": "1234", + } + entry_options = { + "time_between_update": 0, + } + + telegram = Telegram() + telegram.add( + ELECTRICITY_IMPORTED_TOTAL, + CosemObject( + (0, 0), + [{"value": Decimal("123.456"), "unit": UnitOfEnergy.KILO_WATT_HOUR}], + ), + "ELECTRICITY_IMPORTED_TOTAL", + ) + telegram.add( + ELECTRICITY_EXPORTED_TOTAL, + CosemObject( + (0, 0), + [{"value": Decimal("654.321"), "unit": UnitOfEnergy.KILO_WATT_HOUR}], + ), + "ELECTRICITY_EXPORTED_TOTAL", + ) + + mock_entry = MockConfigEntry( + domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options + ) + + mock_entry.add_to_hass(hass) + + await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + telegram_callback = connection_factory.call_args_list[0][0][2] + + # simulate a telegram pushed from the smartmeter and parsed by dsmr_parser + telegram_callback(telegram) + + # after receiving telegram entities need to have the chance to be created + await hass.async_block_till_done() + + active_tariff = hass.states.get("sensor.electricity_meter_energy_consumption_total") + assert active_tariff.state == "123.456" + assert active_tariff.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY + assert ( + active_tariff.attributes.get(ATTR_STATE_CLASS) + == SensorStateClass.TOTAL_INCREASING + ) + assert ( + active_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == UnitOfEnergy.KILO_WATT_HOUR + ) + + active_tariff = hass.states.get("sensor.electricity_meter_energy_production_total") + assert active_tariff.state == "654.321" + assert ( + active_tariff.attributes.get("unit_of_measurement") + == UnitOfEnergy.KILO_WATT_HOUR + ) + + async def test_belgian_meter( hass: HomeAssistant, dsmr_connection_fixture: tuple[MagicMock, MagicMock, MagicMock] ) -> None: