From c5852702ebfd88588e3e954747a05285a5f14470 Mon Sep 17 00:00:00 2001 From: Jens Timmerman <281523+JensTimmerman@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:51:33 +0200 Subject: [PATCH] Add all remaining guntamatic sensors (#179770) --- .../components/guntamatic/quality_scale.yaml | 6 +- homeassistant/components/guntamatic/sensor.py | 332 +++- .../components/guntamatic/strings.json | 118 +- tests/components/guntamatic/conftest.py | 27 + .../guntamatic/snapshots/test_sensor.ambr | 1593 +++++++++++++++++ tests/components/guntamatic/test_sensor.py | 192 +- 6 files changed, 2229 insertions(+), 39 deletions(-) diff --git a/homeassistant/components/guntamatic/quality_scale.yaml b/homeassistant/components/guntamatic/quality_scale.yaml index f6bd53407099..4e8e5108b543 100644 --- a/homeassistant/components/guntamatic/quality_scale.yaml +++ b/homeassistant/components/guntamatic/quality_scale.yaml @@ -59,12 +59,10 @@ rules: docs-supported-functions: todo docs-troubleshooting: todo docs-use-cases: todo - dynamic-devices: - status: exempt - comment: Single device + dynamic-devices: todo entity-category: done entity-device-class: done - entity-disabled-by-default: todo + entity-disabled-by-default: done entity-translations: done exception-translations: todo icon-translations: todo diff --git a/homeassistant/components/guntamatic/sensor.py b/homeassistant/components/guntamatic/sensor.py index 5995b3683727..6037e16366d0 100644 --- a/homeassistant/components/guntamatic/sensor.py +++ b/homeassistant/components/guntamatic/sensor.py @@ -1,5 +1,7 @@ """Support for Guntamatic sensors in Home Assistant.""" +from datetime import date, timedelta +import re from typing import override from homeassistant.components.sensor import ( @@ -9,17 +11,28 @@ from homeassistant.components.sensor import ( SensorStateClass, StateType, ) -from homeassistant.const import PERCENTAGE, UnitOfTemperature +from homeassistant.const import ( + PERCENTAGE, + EntityCategory, + UnitOfTemperature, + UnitOfTime, +) from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.device_registry import ChildDeviceInfo, DeviceInfo from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import dt as dt_util from .const import DOMAIN from .coordinator import GuntamaticConfigEntry, GuntamaticCoordinator PARALLEL_UPDATES = 0 +HEATING_CIRCUIT_REGEX = re.compile( + r"^(?:room|circuit|heating_circulation_pump|heating_circulation_program)_(\d+)" +) + GUNTAMATIC_SENSORS: list[SensorEntityDescription] = [ SensorEntityDescription( key="program", @@ -35,6 +48,12 @@ GUNTAMATIC_SENSORS: list[SensorEntityDescription] = [ "dhw_boost", ], ), + SensorEntityDescription( + key="status", + translation_key="status", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), SensorEntityDescription( key="boiler_temperature", translation_key="boiler_temperature", @@ -56,6 +75,33 @@ GUNTAMATIC_SENSORS: list[SensorEntityDescription] = [ state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), + SensorEntityDescription( + key="buffer_top_0_temperature", + translation_key="buffer_stage_top_temperature", + translation_placeholders={"tank": "1"}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="buffer_top_1_temperature", + translation_key="buffer_stage_top_temperature", + translation_placeholders={"tank": "2"}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="buffer_top_2_temperature", + translation_key="buffer_stage_top_temperature", + translation_placeholders={"tank": "3"}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ), SensorEntityDescription( key="buffer_center_temperature", translation_key="buffer_center_temperature", @@ -70,40 +116,241 @@ GUNTAMATIC_SENSORS: list[SensorEntityDescription] = [ state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), + SensorEntityDescription( + key="buffer_bottom_0_temperature", + translation_key="buffer_stage_bottom_temperature", + translation_placeholders={"tank": "1"}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="buffer_bottom_1_temperature", + translation_key="buffer_stage_bottom_temperature", + translation_placeholders={"tank": "2"}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="buffer_bottom_2_temperature", + translation_key="buffer_stage_bottom_temperature", + translation_placeholders={"tank": "3"}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ), SensorEntityDescription( key="domestic_hot_water_0_temperature", - translation_key="domestic_hot_water_0_temperature", + translation_key="domestic_hot_water_temperature", + translation_placeholders={"circuit": "1"}, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), SensorEntityDescription( - key="room_0_temperature", - translation_key="room_0_temperature", + key="domestic_hot_water_1_temperature", + translation_key="domestic_hot_water_temperature", + translation_placeholders={"circuit": "2"}, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), SensorEntityDescription( - key="room_1_temperature", - translation_key="room_1_temperature", - device_class=SensorDeviceClass.TEMPERATURE, - state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - ), - SensorEntityDescription( - key="room_2_temperature", - translation_key="room_2_temperature", + key="domestic_hot_water_2_temperature", + translation_key="domestic_hot_water_temperature", + translation_placeholders={"circuit": "3"}, device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), + *[ + SensorEntityDescription( + key=f"room_{i}_temperature", + translation_key="room_temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + ) + for i in range(9) + ], SensorEntityDescription( key="buffer_load", translation_key="buffer_load", state_class=SensorStateClass.MEASUREMENT, native_unit_of_measurement=PERCENTAGE, ), + SensorEntityDescription( + key="boiler_shunt_pump", + translation_key="boiler_shunt_pump", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + ), + *[ + SensorEntityDescription( + key=f"auxiliary_pump_{nr}", + translation_key="auxiliary_pump", + translation_placeholders={"pump": str(nr + 1)}, + device_class=SensorDeviceClass.ENUM, + options=[ + "auto", + "off", + "nonstop", + ], + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ) + for nr in range(3) + ], + SensorEntityDescription( + key="suction_fan", + translation_key="suction_fan", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SensorEntityDescription( + key="primary_air", + translation_key="primary_air", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SensorEntityDescription( + key="secondary_air", + translation_key="secondary_air", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), + SensorEntityDescription( + # This is CO2 content in a flue. It is measured in % and goes really high. + # It does not make sense to measure this as ppm as one does for air quality. + key="co2_content", + translation_key="co2_content", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ), + *[ + SensorEntityDescription( + key=f"dhw_pump_{nr}", + translation_key="dhw_pump", + translation_placeholders={"circuit": str(nr + 1)}, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ) + for nr in range(3) + ], + *[ + SensorEntityDescription( + key=f"extra_dhw_{nr}_temperature", + translation_key="extra_dhw_temperature", + translation_placeholders={"circuit": str(nr)}, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_registry_enabled_default=False, + ) + for nr in range(3) + ], + *[ + SensorEntityDescription( + key=f"extra_dhw_boost_{nr}", + translation_key="extra_dhw_boost", + translation_placeholders={"pump": str(nr + 1)}, + device_class=SensorDeviceClass.ENUM, + options=[ + "auto", + "off", + "nonstop", + ], + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + ) + for nr in range(3) + ], + *[ + SensorEntityDescription( + key=f"heating_circulation_pump_{i}", + translation_key="heating_circulation_pump", + device_class=SensorDeviceClass.ENUM, + options=[ + "auto", + "off", + "nonstop", + ], + entity_category=EntityCategory.DIAGNOSTIC, + ) + for i in range(9) + ], + *[ + SensorEntityDescription( + key=f"circuit_{i}_temp", + translation_key="circuit_temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + entity_category=EntityCategory.DIAGNOSTIC, + ) + for i in range(9) + ], + *[ + SensorEntityDescription( + key=f"heating_circulation_program_{i}", + translation_key="heating_circulation_program", + device_class=SensorDeviceClass.ENUM, + options=[ + "off", + "timer", + "heat", + "hibernate", + "hibernate_to", + ], + entity_registry_enabled_default=False, + ) + for i in range(9) + ], + SensorEntityDescription( + key="interruption_1", + translation_key="interruption", + translation_placeholders={"number": "1"}, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="interruption_2", + translation_key="interruption", + translation_placeholders={"number": "2"}, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="operating_time", + translation_key="operating_time", + device_class=SensorDeviceClass.DURATION, + state_class=SensorStateClass.TOTAL, + native_unit_of_measurement=UnitOfTime.HOURS, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), + SensorEntityDescription( + key="service_days", + translation_key="service_date", + device_class=SensorDeviceClass.DATE, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + ), ] @@ -114,13 +361,35 @@ async def async_setup_entry( ) -> None: """Set up Guntamatic sensors from config entry.""" coordinator = entry.runtime_data + serial = coordinator.data["serial"][0] - async_add_entities( - GuntamaticSensor(coordinator, description) - for description in GUNTAMATIC_SENSORS - if description.key in coordinator.data + device_registry = dr.async_get(hass) + main_device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, serial)}, + manufacturer="Guntamatic", + serial_number=serial, + sw_version=coordinator.data["version"][0], ) + entities: list[GuntamaticSensor] = [] + for description in GUNTAMATIC_SENSORS: + if description.key not in coordinator.data: + continue + if match := HEATING_CIRCUIT_REGEX.match(description.key): + circuit = int(match.group(1)) + identifiers = {(DOMAIN, f"{serial}_hc{circuit}")} + device_info: DeviceInfo | ChildDeviceInfo = ChildDeviceInfo( + identifiers=identifiers, + parent_device_id=main_device.id, + name=f"Heating circuit {circuit}", + ) + else: + device_info = DeviceInfo(identifiers={(DOMAIN, serial)}) + entities.append(GuntamaticSensor(coordinator, description, device_info)) + + async_add_entities(entities) + class GuntamaticSensor(CoordinatorEntity[GuntamaticCoordinator], SensorEntity): """Representation of a single Guntamatic sensor.""" @@ -131,6 +400,7 @@ class GuntamaticSensor(CoordinatorEntity[GuntamaticCoordinator], SensorEntity): self, coordinator: GuntamaticCoordinator, entity_description: SensorEntityDescription, + device_info: DeviceInfo | ChildDeviceInfo, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) @@ -139,16 +409,28 @@ class GuntamaticSensor(CoordinatorEntity[GuntamaticCoordinator], SensorEntity): serial = coordinator.data["serial"][0] self._attr_unique_id = f"{serial.replace('.', '_')}_{entity_description.key}" + self._attr_device_info = device_info - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, serial)}, - manufacturer="Guntamatic", - serial_number=serial, - sw_version=coordinator.data["version"][0], + @property + @override + def available(self) -> bool: + """Return whether the entity is available.""" + return ( + super().available and self.entity_description.key in self.coordinator.data ) @property @override - def native_value(self) -> StateType: + def native_value(self) -> StateType | date: """Return the current value of the sensor.""" - return self.coordinator.data[self.entity_description.key][0] + value = self.coordinator.data[self.entity_description.key][0] + if self.entity_description.device_class is SensorDeviceClass.DATE: + return (dt_util.now() + timedelta(days=float(value))).date() + if ( + self.entity_description.device_class is SensorDeviceClass.ENUM + and value not in (self.entity_description.options or []) + ): + # The library passes through unmapped values of new firmware + # languages; expose them as unknown instead of raising. + return None + return value diff --git a/homeassistant/components/guntamatic/strings.json b/homeassistant/components/guntamatic/strings.json index 45209405a4b8..eb1f8c5eec59 100644 --- a/homeassistant/components/guntamatic/strings.json +++ b/homeassistant/components/guntamatic/strings.json @@ -27,9 +27,29 @@ }, "entity": { "sensor": { + "auxiliary_pump": { + "name": "Auxiliary pump {pump}", + "state": { + "auto": "Auto", + "nonstop": "Non-stop", + "off": "Off" + } + }, + "boiler_shunt_pump": { + "name": "Boiler shunt pump" + }, "boiler_temperature": { "name": "Boiler temperature" }, + "buffer_bottom_0_temperature": { + "name": "Buffer 0 bottom temperature" + }, + "buffer_bottom_1_temperature": { + "name": "Buffer 1 bottom temperature" + }, + "buffer_bottom_2_temperature": { + "name": "Buffer 2 bottom temperature" + }, "buffer_bottom_temperature": { "name": "Buffer bottom temperature" }, @@ -39,15 +59,98 @@ "buffer_load": { "name": "Buffer load" }, + "buffer_stage_bottom_temperature": { + "name": "Buffer stage {tank} bottom temperature" + }, + "buffer_stage_top_temperature": { + "name": "Buffer stage {tank} top temperature" + }, + "buffer_top_0_temperature": { + "name": "Buffer 0 top temperature" + }, + "buffer_top_1_temperature": { + "name": "Buffer 1 top temperature" + }, + "buffer_top_2_temperature": { + "name": "Buffer 2 top temperature" + }, "buffer_top_temperature": { "name": "Buffer top temperature" }, + "burner_output": { + "name": "Burner output" + }, + "circuit_temperature": { + "name": "Flow temperature" + }, + "co2_content": { + "name": "Flue CO2 content" + }, + "dhw_pump": { + "name": "DHW pump {circuit}" + }, "domestic_hot_water_0_temperature": { "name": "Domestic hot water circuit 0 temperature" }, + "domestic_hot_water_1_temperature": { + "name": "Domestic hot water circuit 1 temperature" + }, + "domestic_hot_water_2_temperature": { + "name": "Domestic hot water circuit 2 temperature" + }, + "domestic_hot_water_temperature": { + "name": "Domestic hot water circuit {circuit} temperature" + }, + "extra_dhw_1_temperature": { + "name": "Extra DHW 1 temperature" + }, + "extra_dhw_2_temperature": { + "name": "Extra DHW 2 temperature" + }, + "extra_dhw_boost": { + "name": "Extra DHW pump {pump}", + "state": { + "auto": "Auto", + "nonstop": "Non-stop", + "off": "Off" + } + }, + "extra_dhw_temperature": { + "name": "Extra DHW circuit {circuit} temperature" + }, + "flue_gas_actuator_target": { + "name": "Flue gas actuator target" + }, + "heating_circulation_program": { + "name": "Program", + "state": { + "heat": "Heat", + "hibernate": "Setback mode", + "hibernate_to": "Away mode", + "off": "Off", + "timer": "Timer" + } + }, + "heating_circulation_pump": { + "name": "Pump", + "state": { + "auto": "Auto", + "nonstop": "Non-stop", + "off": "Off" + } + }, + "interruption": { + "name": "Interruption {number}" + }, + "operating_time": { + "name": "Operating time" + }, "outdoor_temperature": { "name": "Outdoor temperature" }, + "primary_air": { + "name": "Primary air" + }, "program": { "name": "Program", "state": { @@ -60,17 +163,20 @@ "timer": "Timer" } }, - "room_0_temperature": { - "name": "Room 0 temperature" + "room_temperature": { + "name": "Room temperature" }, - "room_1_temperature": { - "name": "Room 1 temperature" + "secondary_air": { + "name": "Secondary air" }, - "room_2_temperature": { - "name": "Room 2 temperature" + "service_date": { + "name": "Service date" }, "status": { "name": "Status" + }, + "suction_fan": { + "name": "Suction fan" } } } diff --git a/tests/components/guntamatic/conftest.py b/tests/components/guntamatic/conftest.py index 53d36e1c5b9b..e49fac0ce713 100644 --- a/tests/components/guntamatic/conftest.py +++ b/tests/components/guntamatic/conftest.py @@ -28,6 +28,33 @@ MOCK_PARSE_DATA = { "status": ["Service Ign.", ""], "serial": ["959103", ""], "version": ["32a", ""], + "heating_circulation_pump_1": ["auto", ""], + "heating_circulation_program_1": ["heat", ""], + "auxiliary_pump_0": ["nonstop", ""], + "auxiliary_pump_1": ["auto", ""], + "room_1_temperature": ["21.50", "°C"], + "circuit_1_temp": ["55.00", "°C"], + "operating_time": ["1188", "h"], + "service_days": ["2012", "d"], + "interruption_1": ["", ""], + "interruption_2": ["", ""], + "extra_dhw_0_temperature": ["53.00", "°C"], + "extra_dhw_1_temperature": ["52.10", "°C"], + "extra_dhw_2_temperature": ["51.80", "°C"], + "extra_dhw_boost_0": ["auto", ""], + "extra_dhw_boost_1": ["off", ""], + "buffer_top_temperature": ["65.40", "°C"], + "buffer_top_0_temperature": ["64.10", "°C"], + "buffer_center_temperature": ["58.20", "°C"], + "buffer_bottom_temperature": ["45.30", "°C"], + "buffer_bottom_0_temperature": ["44.80", "°C"], + "boiler_shunt_pump": ["45", "%"], + "suction_fan": ["78", "%"], + "primary_air": ["60", "%"], + "secondary_air": ["35", "%"], + "co2_content": ["12", "%"], + "dhw_pump_0": ["30", "%"], + "dhw_pump_1": ["25", "%"], } diff --git a/tests/components/guntamatic/snapshots/test_sensor.ambr b/tests/components/guntamatic/snapshots/test_sensor.ambr index 5953c89c62fe..da894fa4dd7d 100644 --- a/tests/components/guntamatic/snapshots/test_sensor.ambr +++ b/tests/components/guntamatic/snapshots/test_sensor.ambr @@ -1,4 +1,426 @@ # serializer version: 1 +# name: test_all_entities[sensor.heating_circuit_1_flow_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.heating_circuit_1_flow_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flow temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Flow temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'circuit_temperature', + 'unique_id': '959103_circuit_1_temp', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_flow_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Heating circuit 1 Flow temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.heating_circuit_1_flow_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '55.00', + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_program-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'off', + 'timer', + 'heat', + 'hibernate', + 'hibernate_to', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.heating_circuit_1_program', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Program', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Program', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'heating_circulation_program', + 'unique_id': '959103_heating_circulation_program_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_program-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Heating circuit 1 Program', + : list([ + 'off', + 'timer', + 'heat', + 'hibernate', + 'hibernate_to', + ]), + }), + 'context': , + 'entity_id': 'sensor.heating_circuit_1_program', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'heat', + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_pump-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.heating_circuit_1_pump', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Pump', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Pump', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'heating_circulation_pump', + 'unique_id': '959103_heating_circulation_pump_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_pump-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Heating circuit 1 Pump', + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'context': , + 'entity_id': 'sensor.heating_circuit_1_pump', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'auto', + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_room_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.heating_circuit_1_room_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Room temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Room temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'room_temperature', + 'unique_id': '959103_room_1_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.heating_circuit_1_room_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Heating circuit 1 Room temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.heating_circuit_1_room_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '21.50', + }) +# --- +# name: test_all_entities[sensor.mock_title_auxiliary_pump_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_auxiliary_pump_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary pump 1', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary pump 1', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'auxiliary_pump', + 'unique_id': '959103_auxiliary_pump_0', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_auxiliary_pump_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Mock Title Auxiliary pump 1', + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'context': , + 'entity_id': 'sensor.mock_title_auxiliary_pump_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'nonstop', + }) +# --- +# name: test_all_entities[sensor.mock_title_auxiliary_pump_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_auxiliary_pump_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Auxiliary pump 2', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Auxiliary pump 2', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'auxiliary_pump', + 'unique_id': '959103_auxiliary_pump_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_auxiliary_pump_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Mock Title Auxiliary pump 2', + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'context': , + 'entity_id': 'sensor.mock_title_auxiliary_pump_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'auto', + }) +# --- +# name: test_all_entities[sensor.mock_title_boiler_shunt_pump-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_boiler_shunt_pump', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Boiler shunt pump', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Boiler shunt pump', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'boiler_shunt_pump', + 'unique_id': '959103_boiler_shunt_pump', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_boiler_shunt_pump-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Boiler shunt pump', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_boiler_shunt_pump', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '45', + }) +# --- # name: test_all_entities[sensor.mock_title_boiler_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -57,6 +479,122 @@ 'state': '14.09', }) # --- +# name: test_all_entities[sensor.mock_title_buffer_bottom_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_buffer_bottom_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Buffer bottom temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Buffer bottom temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'buffer_bottom_temperature', + 'unique_id': '959103_buffer_bottom_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_bottom_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Buffer bottom temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_buffer_bottom_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '45.30', + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_center_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_buffer_center_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Buffer center temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Buffer center temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'buffer_center_temperature', + 'unique_id': '959103_buffer_center_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_center_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Buffer center temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_buffer_center_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '58.20', + }) +# --- # name: test_all_entities[sensor.mock_title_buffer_load-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -111,6 +649,798 @@ 'state': '22', }) # --- +# name: test_all_entities[sensor.mock_title_buffer_stage_1_bottom_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_buffer_stage_1_bottom_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Buffer stage 1 bottom temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Buffer stage 1 bottom temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'buffer_stage_bottom_temperature', + 'unique_id': '959103_buffer_bottom_0_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_stage_1_bottom_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Buffer stage 1 bottom temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_buffer_stage_1_bottom_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '44.80', + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_stage_1_top_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_buffer_stage_1_top_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Buffer stage 1 top temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Buffer stage 1 top temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'buffer_stage_top_temperature', + 'unique_id': '959103_buffer_top_0_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_stage_1_top_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Buffer stage 1 top temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_buffer_stage_1_top_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '64.10', + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_top_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_buffer_top_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Buffer top temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Buffer top temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'buffer_top_temperature', + 'unique_id': '959103_buffer_top_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_buffer_top_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Buffer top temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_buffer_top_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '65.40', + }) +# --- +# name: test_all_entities[sensor.mock_title_dhw_pump_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_dhw_pump_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'DHW pump 1', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'DHW pump 1', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dhw_pump', + 'unique_id': '959103_dhw_pump_0', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_dhw_pump_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title DHW pump 1', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_dhw_pump_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '30', + }) +# --- +# name: test_all_entities[sensor.mock_title_dhw_pump_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_dhw_pump_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'DHW pump 2', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'DHW pump 2', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dhw_pump', + 'unique_id': '959103_dhw_pump_1', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_dhw_pump_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title DHW pump 2', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_dhw_pump_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '25', + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_circuit_0_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_extra_dhw_circuit_0_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Extra DHW circuit 0 temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Extra DHW circuit 0 temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'extra_dhw_temperature', + 'unique_id': '959103_extra_dhw_0_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_circuit_0_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Extra DHW circuit 0 temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_extra_dhw_circuit_0_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '53.00', + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_circuit_1_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_extra_dhw_circuit_1_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Extra DHW circuit 1 temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Extra DHW circuit 1 temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'extra_dhw_temperature', + 'unique_id': '959103_extra_dhw_1_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_circuit_1_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Extra DHW circuit 1 temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_extra_dhw_circuit_1_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '52.10', + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_circuit_2_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.mock_title_extra_dhw_circuit_2_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Extra DHW circuit 2 temperature', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Extra DHW circuit 2 temperature', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'extra_dhw_temperature', + 'unique_id': '959103_extra_dhw_2_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_circuit_2_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'temperature', + : 'Mock Title Extra DHW circuit 2 temperature', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_extra_dhw_circuit_2_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '51.80', + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_pump_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_extra_dhw_pump_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Extra DHW pump 1', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Extra DHW pump 1', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'extra_dhw_boost', + 'unique_id': '959103_extra_dhw_boost_0', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_pump_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Mock Title Extra DHW pump 1', + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'context': , + 'entity_id': 'sensor.mock_title_extra_dhw_pump_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'auto', + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_pump_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_extra_dhw_pump_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Extra DHW pump 2', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Extra DHW pump 2', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'extra_dhw_boost', + 'unique_id': '959103_extra_dhw_boost_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_extra_dhw_pump_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'enum', + : 'Mock Title Extra DHW pump 2', + : list([ + 'auto', + 'off', + 'nonstop', + ]), + }), + 'context': , + 'entity_id': 'sensor.mock_title_extra_dhw_pump_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[sensor.mock_title_flue_co2_content-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_flue_co2_content', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Flue CO2 content', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Flue CO2 content', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'co2_content', + 'unique_id': '959103_co2_content', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_flue_co2_content-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Flue CO2 content', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_flue_co2_content', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '12', + }) +# --- +# name: test_all_entities[sensor.mock_title_interruption_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_interruption_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Interruption 1', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Interruption 1', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'interruption', + 'unique_id': '959103_interruption_1', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_interruption_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Interruption 1', + }), + 'context': , + 'entity_id': 'sensor.mock_title_interruption_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '', + }) +# --- +# name: test_all_entities[sensor.mock_title_interruption_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_interruption_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Interruption 2', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Interruption 2', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'interruption', + 'unique_id': '959103_interruption_2', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_interruption_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Interruption 2', + }), + 'context': , + 'entity_id': 'sensor.mock_title_interruption_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '', + }) +# --- +# name: test_all_entities[sensor.mock_title_operating_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_operating_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Operating time', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Operating time', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'operating_time', + 'unique_id': '959103_operating_time', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.mock_title_operating_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'duration', + : 'Mock Title Operating time', + : , + : , + }), + 'context': , + 'entity_id': 'sensor.mock_title_operating_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1188', + }) +# --- # name: test_all_entities[sensor.mock_title_outdoor_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -169,6 +1499,60 @@ 'state': '15.95', }) # --- +# name: test_all_entities[sensor.mock_title_primary_air-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_primary_air', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Primary air', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Primary air', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'primary_air', + 'unique_id': '959103_primary_air', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_primary_air-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Primary air', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_primary_air', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '60', + }) +# --- # name: test_all_entities[sensor.mock_title_program-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -239,3 +1623,212 @@ 'state': 'heat', }) # --- +# name: test_all_entities[sensor.mock_title_secondary_air-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_secondary_air', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Secondary air', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Secondary air', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'secondary_air', + 'unique_id': '959103_secondary_air', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_secondary_air-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Secondary air', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_secondary_air', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '35', + }) +# --- +# name: test_all_entities[sensor.mock_title_service_date-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_service_date', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Service date', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Service date', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'service_date', + 'unique_id': '959103_service_days', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_service_date-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'date', + : 'Mock Title Service date', + }), + 'context': , + 'entity_id': 'sensor.mock_title_service_date', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2035-07-06', + }) +# --- +# name: test_all_entities[sensor.mock_title_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Status', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': '959103_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.mock_title_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Status', + }), + 'context': , + 'entity_id': 'sensor.mock_title_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Service Ign.', + }) +# --- +# name: test_all_entities[sensor.mock_title_suction_fan-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.mock_title_suction_fan', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Suction fan', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Suction fan', + 'platform': 'guntamatic', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'suction_fan', + 'unique_id': '959103_suction_fan', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.mock_title_suction_fan-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Title Suction fan', + : , + : '%', + }), + 'context': , + 'entity_id': 'sensor.mock_title_suction_fan', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '78', + }) +# --- diff --git a/tests/components/guntamatic/test_sensor.py b/tests/components/guntamatic/test_sensor.py index 2876f2dfc0dd..0fe7458a60e1 100644 --- a/tests/components/guntamatic/test_sensor.py +++ b/tests/components/guntamatic/test_sensor.py @@ -1,19 +1,29 @@ """Tests for the Guntamatic sensor platform.""" +from datetime import datetime, timedelta from unittest.mock import MagicMock, patch from freezegun.api import FrozenDateTimeFactory -from guntamatic.heater import NoSerialException +from guntamatic.heater import ( + TRANSLATE_HC_PROGRAM, + TRANSLATE_PUMP_MODE, + NoSerialException, +) import pytest import requests from syrupy.assertion import SnapshotAssertion -from homeassistant.components.guntamatic.const import SCAN_INTERVAL -from homeassistant.const import STATE_UNAVAILABLE, Platform +from homeassistant.components.guntamatic.const import DOMAIN, SCAN_INTERVAL +from homeassistant.components.guntamatic.sensor import GUNTAMATIC_SENSORS +from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription +from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.translation import async_get_translations +from homeassistant.util import dt as dt_util from . import setup_integration +from .conftest import MOCK_PARSE_DATA from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform @@ -24,13 +34,35 @@ async def test_all_entities( snapshot: SnapshotAssertion, mock_config_entry: MockConfigEntry, entity_registry: er.EntityRegistry, + freezer: FrozenDateTimeFactory, ) -> None: """Test all entities.""" + # The service date sensor computes now() + a day count; freeze the clock + # so the snapshot does not depend on the day the tests run on. + freezer.move_to("2030-01-01 12:00:00+00:00") with patch( "homeassistant.components.guntamatic._PLATFORMS", [Platform.SENSOR], ): await setup_integration(hass, mock_config_entry) + + disabled_entries = [ + entity_entry + for entity_entry in er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) + if entity_entry.disabled_by is not None + ] + assert disabled_entries + assert all( + entity_entry.disabled_by == er.RegistryEntryDisabler.INTEGRATION + for entity_entry in disabled_entries + ) + for entity_entry in disabled_entries: + entity_registry.async_update_entity(entity_entry.entity_id, disabled_by=None) + + await hass.config_entries.async_reload(mock_config_entry.entry_id) + await hass.async_block_till_done() await snapshot_platform( hass, entity_registry, @@ -73,3 +105,155 @@ async def test_state_unavailable( await hass.async_block_till_done() state = hass.states.get("sensor.mock_title_boiler_temperature") assert state.state != STATE_UNAVAILABLE + + +@pytest.mark.parametrize( + ("key_prefix", "translation_table"), + [ + pytest.param("heating_circulation_program", TRANSLATE_HC_PROGRAM, id="program"), + pytest.param( + "heating_circulation_pump", + TRANSLATE_PUMP_MODE, + id="heating_circulation_pump", + ), + pytest.param("auxiliary_pump", TRANSLATE_PUMP_MODE, id="auxiliary_pump"), + pytest.param("extra_dhw_boost", TRANSLATE_PUMP_MODE, id="extra_dhw_boost"), + ], +) +def test_enum_options_match_library( + key_prefix: str, + translation_table: dict[str, str], +) -> None: + """Test enum options mirror the canonical values emitted by the library.""" + option_sets = { + tuple(description.options) + for description in GUNTAMATIC_SENSORS + if description.key.startswith(key_prefix) + and description.device_class is SensorDeviceClass.ENUM + } + assert len(option_sets) == 1, f"Inconsistent options across {key_prefix} sensors" + assert set(option_sets.pop()) == set(translation_table.values()) + + +enum_descriptions = [ + description + for description in GUNTAMATIC_SENSORS + if description.device_class is SensorDeviceClass.ENUM +] + + +@pytest.mark.parametrize( + "description", + enum_descriptions, + ids=lambda description: description.key, +) +async def test_enum_states_translated( + hass: HomeAssistant, + description: SensorEntityDescription, +) -> None: + """Test every option of an enum sensor has a state translation.""" + translations = await async_get_translations(hass, "en", "entity", [DOMAIN]) + prefix = f"component.{DOMAIN}.entity.sensor.{description.translation_key}.state." + for option in description.options: + assert f"{prefix}{option}" in translations + + +async def test_enum_sensor_unmapped_value( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_heater: MagicMock, +) -> None: + """Test an unmapped enum value is exposed as unknown instead of raising.""" + return_value = MOCK_PARSE_DATA.copy() + return_value["heating_circulation_pump_1"] = ["BROKEN", ""] + mock_heater.parse_data.return_value = return_value + + await setup_integration(hass, mock_config_entry) + await hass.async_block_till_done() + + state = hass.states.get("sensor.heating_circuit_1_pump") + assert state is not None + assert state.state == STATE_UNKNOWN + + +_SERIAL = "959103" + + +@pytest.mark.usefixtures("mock_heater") +async def test_heating_circuit_devices( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test circuit sub-devices link to the main unit and absent slots create none.""" + await setup_integration(hass, mock_config_entry) + + main = device_registry.async_get_device_by_identifier( + (DOMAIN, _SERIAL), mock_config_entry.entry_id + ) + circuit = device_registry.async_get_child_device_by_identifier( + (DOMAIN, f"{_SERIAL}_hc1"), mock_config_entry.entry_id + ) + assert main is not None + assert circuit is not None + assert circuit.parent_device_id == main.id + assert ( + device_registry.async_get_device_by_identifier( + (DOMAIN, f"{_SERIAL}_hc2"), mock_config_entry.entry_id + ) + is None + ) + + +async def test_key_disappearing_makes_entity_unavailable( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_heater: MagicMock, +) -> None: + """Test an individual sensor becoming unavailable when its key disappears.""" + await setup_integration(hass, mock_config_entry) + + entity_id = "sensor.mock_title_boiler_temperature" + assert hass.states.get(entity_id).state == "14.09" + + # Simulate the heater no longer reporting this key + reduced = {k: v for k, v in MOCK_PARSE_DATA.items() if k != "boiler_temperature"} + mock_heater.parse_data.return_value = reduced + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "unavailable" + + # Restore the key and verify the entity recovers + mock_heater.parse_data.return_value = MOCK_PARSE_DATA.copy() + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "14.09" + + +async def test_service_date_fractional_days( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_heater: MagicMock, + freezer: FrozenDateTimeFactory, + entity_registry: er.EntityRegistry, +) -> None: + """Fractional service_days roll over correctly when time is not midnight.""" + frozen = datetime(2026, 8, 26, 15, 00, tzinfo=dt_util.get_default_time_zone()) + freezer.move_to(frozen) + + data = MOCK_PARSE_DATA.copy() + data["service_days"] = ["1.5", "d"] + mock_heater.parse_data.return_value = data + + await setup_integration(hass, mock_config_entry) + entity_registry.async_update_entity( + "sensor.mock_title_service_date", disabled_by=None + ) + await hass.config_entries.async_reload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.mock_title_service_date") + assert state is not None + assert state.state == (frozen + timedelta(days=1.5)).date().isoformat()