From 511841442f11118b678a84fe3725e98635ec0015 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 11:31:44 +0200 Subject: [PATCH 01/61] Implement water_heater platform for DHW function --- custom_components/plugwise/water_heater.py | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 custom_components/plugwise/water_heater.py diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py new file mode 100644 index 000000000..ec95fee3c --- /dev/null +++ b/custom_components/plugwise/water_heater.py @@ -0,0 +1,96 @@ +"""Plugwise water heater component for HomeAssistant.""" + +from homeassistant.components.water_heater import ( + WaterHeaterEntity, + WaterHeaterEntityFeature, +) +from homeassistant.const import ( + ATTR_NAME, + STATE_OFF, + STATE_ON, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from .const import ( + BINARY_SENSORS, + DEV_CLASS, + LOGGER, + LOWER_BOUND, + SENSORS, + TARGET_TEMP, + UPPER_BOUND, +) +from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator +from .entity import PlugwiseEntity + +MODE_HEAT = "heat" +MODE_OFF = "off" +OPERATION_MODES = [MODE_HEAT, MODE_OFF] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: PlugwiseConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up Plugwise water_heater from a config entry.""" + coordinator = entry.runtime_data + + @callback + def _add_entities() -> None: + """Add Entities.""" + if not coordinator.new_devices: + return + + for device_id in coordinator.new_devices: + device = coordinator.data[device_id] + if device[DEV_CLASS] == "heater_central" and device.get("max_dhw_temperature"): + async_add_entities([PlugwiseWaterHeaterEntity(coordinator, device_id)]) + LOGGER.debug("Add %s water_heater", device[ATTR_NAME]) + + _add_entities() + entry.async_on_unload(coordinator.async_add_listener(_add_entities)) + + +class PlugwiseWaterHeaterEntity(PlugwiseEntity, WaterHeaterEntity): + """Representation of a Plugwise water heater.""" + + _attr_name = None + _attr_operation_list = OPERATION_MODES + _attr_temperature_unit = UnitOfTemperature.CELSIUS + + def __init__( + self, + coordinator: PlugwiseDataUpdateCoordinator, + device_id: str, + ) -> None: + """Initialise the water_heater.""" + super().__init__(coordinator, device_id) + self._attr_unique_id = f"{device_id}-water_heater" + + self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) + self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) + self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE + self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE + + @property + def current_operation(self) -> str | None: + """Return current readable operation mode.""" + if (state := self.device.get(BINARY_SENSORS, {}).get("dhw_state")) is not None: + if state: + return STATE_ON + else: + return STATE_OFF + return None + + @property + def current_temperature(self) -> float | None: + """Return the current water temperature.""" + return self.device.get(SENSORS, {}).get("water_temperature") + + @property + def target_temperature(self) -> float | None: + """Return the water temperature we try to reach.""" + return self.device.get("max_dhw_temperature", {}).get(TARGET_TEMP) From 2b71396c772ee64ced46c547c16b6be832eb3e2f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 11:50:31 +0200 Subject: [PATCH 02/61] Ruffed --- custom_components/plugwise/water_heater.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index ec95fee3c..be158b257 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -4,12 +4,7 @@ WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ( - ATTR_NAME, - STATE_OFF, - STATE_ON, - UnitOfTemperature, -) +from homeassistant.const import ATTR_NAME, STATE_OFF, STATE_ON, UnitOfTemperature from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -81,8 +76,7 @@ def current_operation(self) -> str | None: if (state := self.device.get(BINARY_SENSORS, {}).get("dhw_state")) is not None: if state: return STATE_ON - else: - return STATE_OFF + return STATE_OFF return None @property From c6512214920c17a13b0df2867b0213c96ee42b95 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 12:18:15 +0200 Subject: [PATCH 03/61] Add test_water_heater.py --- .../components/plugwise/test_water_heater.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/components/plugwise/test_water_heater.py diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py new file mode 100644 index 000000000..536e4a363 --- /dev/null +++ b/tests/components/plugwise/test_water_heater.py @@ -0,0 +1,29 @@ +"""Tests for the Plugwise water_heater platform.""" + +from unittest.mock import MagicMock + +import pytest + +from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from syrupy.assertion import SnapshotAssertion + +from tests.common import MockConfigEntry, snapshot_platform + +HA_PLUGWISE_SMILE_ASYNC_UPDATE = ( + "homeassistant.components.plugwise.coordinator.Smile.async_update" +) + + +@pytest.mark.parametrize("platforms", [(WATER_HEATER_DOMAIN,)]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_adam_water_heater_snapshot( + hass: HomeAssistant, + mock_smile_adam: MagicMock, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + setup_platform: MockConfigEntry, +) -> None: + """Test Adam water_heater snapshot.""" + await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) From 8aa3b087d472e6e492a259e757196704d15d1e71 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 12:20:31 +0200 Subject: [PATCH 04/61] Improve water_heater and supported_features detection --- custom_components/plugwise/water_heater.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index be158b257..c462202fa 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -41,7 +41,7 @@ def _add_entities() -> None: for device_id in coordinator.new_devices: device = coordinator.data[device_id] - if device[DEV_CLASS] == "heater_central" and device.get("max_dhw_temperature"): + if device[DEV_CLASS] == "heater_central" and device.get(BINARY_SENSORS, {}).get("dhw_state"): async_add_entities([PlugwiseWaterHeaterEntity(coordinator, device_id)]) LOGGER.debug("Add %s water_heater", device[ATTR_NAME]) @@ -68,7 +68,8 @@ def __init__( self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE - self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE + if self.device.get("max_dhw_temperature"): + self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE @property def current_operation(self) -> str | None: From a6454093929e0aeb2036337301d3152981b9c149 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 12:26:03 +0200 Subject: [PATCH 05/61] Correct mocked_adam to fixture with water_heater --- custom_components/plugwise/const.py | 1 + tests/components/plugwise/test_water_heater.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/const.py b/custom_components/plugwise/const.py index d4e25a26f..611138431 100644 --- a/custom_components/plugwise/const.py +++ b/custom_components/plugwise/const.py @@ -165,6 +165,7 @@ Platform.SELECT, Platform.SENSOR, Platform.SWITCH, + Platform.WATER_HEATER, ] SERVICE_DELETE: Final = "delete_notification" SEVERITIES: Final[list[str]] = ["other", "info", "message", "warning", "error"] diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 536e4a363..35ec3a268 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -20,7 +20,7 @@ @pytest.mark.usefixtures("entity_registry_enabled_by_default") async def test_adam_water_heater_snapshot( hass: HomeAssistant, - mock_smile_adam: MagicMock, + mock_smile_adam_jip: MagicMock, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry, setup_platform: MockConfigEntry, From 234966a0fe8b5be4da4adc11e2a8827235196332 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 12:53:13 +0200 Subject: [PATCH 06/61] Fixes --- custom_components/plugwise/water_heater.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index c462202fa..fb0d266f0 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -39,11 +39,13 @@ def _add_entities() -> None: if not coordinator.new_devices: return + entities: list[PlugwiseWaterHeaterEntity] = [] for device_id in coordinator.new_devices: device = coordinator.data[device_id] - if device[DEV_CLASS] == "heater_central" and device.get(BINARY_SENSORS, {}).get("dhw_state"): - async_add_entities([PlugwiseWaterHeaterEntity(coordinator, device_id)]) + if device[DEV_CLASS] == "heater_central" and device.get(BINARY_SENSORS, {}).get("dhw_state") is not None: + entities.append(PlugwiseWaterHeaterEntity(coordinator, device_id)) LOGGER.debug("Add %s water_heater", device[ATTR_NAME]) + async_add_entities(entities) _add_entities() entry.async_on_unload(coordinator.async_add_listener(_add_entities)) From 720fd1cf0e5466e3c9af738ccca2f380778ef763 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:01:24 +0200 Subject: [PATCH 07/61] Save new test_water_heater snapshot --- .../plugwise/snapshots/test_water_heater.ambr | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/components/plugwise/snapshots/test_water_heater.ambr diff --git a/tests/components/plugwise/snapshots/test_water_heater.ambr b/tests/components/plugwise/snapshots/test_water_heater.ambr new file mode 100644 index 000000000..6851c43da --- /dev/null +++ b/tests/components/plugwise/snapshots/test_water_heater.ambr @@ -0,0 +1,70 @@ +# serializer version: 1 +# name: test_adam_water_heater_snapshot[platforms0][water_heater.opentherm-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'max_temp': 60.0, + 'min_temp': 40.0, + 'operation_list': list([ + 'heat', + 'off', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'water_heater', + 'entity_category': None, + 'entity_id': 'water_heater.opentherm', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'plugwise', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'e4684553153b44afbef2200885f379dc-water_heater', + 'unit_of_measurement': None, + }) +# --- +# name: test_adam_water_heater_snapshot[platforms0][water_heater.opentherm-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_temperature': 37.3, + 'friendly_name': 'OpenTherm', + 'max_temp': 60.0, + 'min_temp': 40.0, + 'operation_list': list([ + 'heat', + 'off', + ]), + 'operation_mode': 'off', + 'supported_features': , + 'target_temp_high': None, + 'target_temp_low': None, + 'temperature': 60.0, + }), + 'context': , + 'entity_id': 'water_heater.opentherm', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- From 0348d81a4845dc223c3575a84a256f77b5f7a4fd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:09:29 +0200 Subject: [PATCH 08/61] Add anna_v4_dhw fixture --- .../plugwise/fixtures/anna_v4_dhw/data.json | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/components/plugwise/fixtures/anna_v4_dhw/data.json diff --git a/tests/components/plugwise/fixtures/anna_v4_dhw/data.json b/tests/components/plugwise/fixtures/anna_v4_dhw/data.json new file mode 100644 index 000000000..677d0a622 --- /dev/null +++ b/tests/components/plugwise/fixtures/anna_v4_dhw/data.json @@ -0,0 +1,98 @@ +{ + "01b85360fdd243d0aaad4d6ac2a5ba7e": { + "active_preset": "home", + "available_schedules": [ + "Standaard", + "Thuiswerken", + "off" + ], + "climate_mode": "heat", + "control_state": "idle", + "dev_class": "thermostat", + "firmware": "2018-02-08T11:15:53+01:00", + "hardware": "6539-1301-5002", + "location": "eb5309212bf5407bb143e5bfa3b18aee", + "model": "ThermoTouch", + "name": "Anna", + "preset_modes": [ + "vacation", + "no_frost", + "away", + "asleep", + "home" + ], + "select_schedule": "off", + "sensors": { + "illuminance": 60.0, + "setpoint": 20.5, + "temperature": 20.6 + }, + "temperature_offset": { + "lower_bound": -2.0, + "resolution": 0.1, + "setpoint": 0.0, + "upper_bound": 2.0 + }, + "thermostat": { + "lower_bound": 4.0, + "resolution": 0.1, + "setpoint": 20.5, + "upper_bound": 30.0 + }, + "vendor": "Plugwise" + }, + "0466eae8520144c78afb29628384edeb": { + "binary_sensors": { + "plugwise_notification": false + }, + "dev_class": "gateway", + "firmware": "4.0.15", + "hardware": "AME Smile 2.0 board", + "location": "94c107dc6ac84ed98e9f68c0dd06bf71", + "mac_address": "012345670001", + "model": "Gateway", + "model_id": "smile_thermo", + "name": "Smile Anna", + "notifications": {}, + "sensors": { + "outdoor_temperature": 7.44 + }, + "vendor": "Plugwise" + }, + "cd0e6156b1f04d5f952349ffbe397481": { + "available": true, + "binary_sensors": { + "dhw_state": true, + "flame_state": true, + "heating_state": false + }, + "dev_class": "heater_central", + "location": "94c107dc6ac84ed98e9f68c0dd06bf71", + "max_dhw_temperature": { + "lower_bound": 30.0, + "resolution": 0.01, + "setpoint": 60.0, + "upper_bound": 60.0 + }, + "maximum_boiler_temperature": { + "lower_bound": 0.0, + "resolution": 1.0, + "setpoint": 70.0, + "upper_bound": 100.0 + }, + "model": "Generic heater", + "model_id": "2.32", + "name": "OpenTherm", + "sensors": { + "intended_boiler_temperature": 39.9, + "modulation_level": 0.0, + "return_temperature": 32.0, + "water_pressure": 2.2, + "water_temperature": 45.0 + }, + "switches": { + "dhw_cm_switch": false + }, + "vendor": "Bosch Thermotechniek B.V." + } +} From d6f1bd4d95db2ff3adc7ec3d9d909af047c6379d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:12:30 +0200 Subject: [PATCH 09/61] Add 2nd water_heater testcase --- tests/components/plugwise/test_water_heater.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 35ec3a268..7a9d3971b 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -25,5 +25,20 @@ async def test_adam_water_heater_snapshot( entity_registry: er.EntityRegistry, setup_platform: MockConfigEntry, ) -> None: - """Test Adam water_heater snapshot.""" + """Test Adam water_heater snapshot with dhw_state off.""" await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) + + +@pytest.mark.parametrize("chosen_env", ["anna_v4_dhw"], indirect=True) +@pytest.mark.parametrize("cooling_present", [False], indirect=True) +@pytest.mark.parametrize("platforms", [(WATER_HEATER_DOMAIN,)]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_anna_water_heater_snapshot( + hass: HomeAssistant, + mock_smile_anna: MagicMock, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + setup_platform: MockConfigEntry, +) -> None: + """Test Anna water_heater snapshot with dhw_state on.""" + await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) \ No newline at end of file From b10e662fd991f15501bdec91eb2f525b72fdeaae Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:16:12 +0200 Subject: [PATCH 10/61] Save updates: snapshot, ruffed --- .../plugwise/snapshots/test_water_heater.ambr | 69 +++++++++++++++++++ .../components/plugwise/test_water_heater.py | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/components/plugwise/snapshots/test_water_heater.ambr b/tests/components/plugwise/snapshots/test_water_heater.ambr index 6851c43da..d4ce0e05c 100644 --- a/tests/components/plugwise/snapshots/test_water_heater.ambr +++ b/tests/components/plugwise/snapshots/test_water_heater.ambr @@ -68,3 +68,72 @@ 'state': 'off', }) # --- +# name: test_anna_water_heater_snapshot[platforms0-False-anna_v4_dhw][water_heater.opentherm-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'max_temp': 60.0, + 'min_temp': 30.0, + 'operation_list': list([ + 'heat', + 'off', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'water_heater', + 'entity_category': None, + 'entity_id': 'water_heater.opentherm', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'plugwise', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': None, + 'unique_id': 'cd0e6156b1f04d5f952349ffbe397481-water_heater', + 'unit_of_measurement': None, + }) +# --- +# name: test_anna_water_heater_snapshot[platforms0-False-anna_v4_dhw][water_heater.opentherm-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'current_temperature': 45.0, + 'friendly_name': 'OpenTherm', + 'max_temp': 60.0, + 'min_temp': 30.0, + 'operation_list': list([ + 'heat', + 'off', + ]), + 'operation_mode': 'on', + 'supported_features': , + 'target_temp_high': None, + 'target_temp_low': None, + 'temperature': 60.0, + }), + 'context': , + 'entity_id': 'water_heater.opentherm', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 7a9d3971b..0a9f404e3 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -41,4 +41,4 @@ async def test_anna_water_heater_snapshot( setup_platform: MockConfigEntry, ) -> None: """Test Anna water_heater snapshot with dhw_state on.""" - await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) \ No newline at end of file + await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) From dae6a72d335506557ad634fccc8bebdd3847bdfc Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:20:05 +0200 Subject: [PATCH 11/61] Update related test asserts --- tests/components/plugwise/test_init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index bd5dbf89f..078cd6319 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -287,7 +287,7 @@ async def test_update_device( assert ( len(er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)) - == 53 + == 54 ) assert ( len(dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)) @@ -311,7 +311,7 @@ async def test_update_device( assert ( len(er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)) - == 60 + == 61 ) assert ( len(dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)) @@ -338,7 +338,7 @@ async def test_update_device( assert ( len(er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)) - == 53 + == 54 ) assert ( len(dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)) From 58cceac8d5ac02297190d117508a6516fdf0288c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:42:50 +0200 Subject: [PATCH 12/61] Add water_heater async_set_temperature() --- custom_components/plugwise/water_heater.py | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index fb0d266f0..a1a4b9170 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -4,7 +4,13 @@ WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ATTR_NAME, STATE_OFF, STATE_ON, UnitOfTemperature +from homeassistant.const import ( + ATTR_NAME, + ATTR_TEMPERATURE, + STATE_OFF, + STATE_ON, + UnitOfTemperature, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -13,12 +19,14 @@ DEV_CLASS, LOGGER, LOWER_BOUND, + MAX_DHW_TEMP, SENSORS, TARGET_TEMP, UPPER_BOUND, ) from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity +from .util import plugwise_command MODE_HEAT = "heat" MODE_OFF = "off" @@ -65,13 +73,17 @@ def __init__( ) -> None: """Initialise the water_heater.""" super().__init__(coordinator, device_id) + self.device_id = device_id self._attr_unique_id = f"{device_id}-water_heater" self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE + self._supports_temperature_control = False if self.device.get("max_dhw_temperature"): self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE + self._supports_temperature_control = True + @property def current_operation(self) -> str | None: @@ -91,3 +103,15 @@ def current_temperature(self) -> float | None: def target_temperature(self) -> float | None: """Return the water temperature we try to reach.""" return self.device.get("max_dhw_temperature", {}).get(TARGET_TEMP) + + @plugwise_command + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new target temperature.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if not self._supports_temperature_control or temperature is None: + return + + await self.coordinator.api.set_number(self.device_id, MAX_DHW_TEMP, temperature) + LOGGER.debug( + "Setting %s to %s was successful", MAX_DHW_TEMP, temperature + ) From d9609f3202e76fe0e92c8bdb3c6a72cb1c07d891 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 13:55:06 +0200 Subject: [PATCH 13/61] Add test case --- .../components/plugwise/test_water_heater.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 0a9f404e3..8e5dabee4 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -4,7 +4,8 @@ import pytest -from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN +from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN, SERVICE_SET_TEMPERATURE +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from syrupy.assertion import SnapshotAssertion @@ -29,6 +30,22 @@ async def test_adam_water_heater_snapshot( await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id) +async def test_adam_water_heater_setpoint_change( + hass: HomeAssistant, mock_smile_adam_jip: MagicMock, init_integration: MockConfigEntry +) -> None: + """Test Adam water_heater setpoint-change.""" + await hass.services.async_call( + WATER_HEATER_DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: "water_heater.opentherm", ATTR_TEMPERATURE: 65}, + blocking=True, + ) + assert mock_smile_adam_jip.set_number.call_count == 1 + mock_smile_adam_jip.set_number.assert_called_with( + "e4684553153b44afbef2200885f379dc", "max_dhw_temperature", 65.0, + ) + + @pytest.mark.parametrize("chosen_env", ["anna_v4_dhw"], indirect=True) @pytest.mark.parametrize("cooling_present", [False], indirect=True) @pytest.mark.parametrize("platforms", [(WATER_HEATER_DOMAIN,)]) From 845b74268cc46035f16531f067cd08d2183909b6 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:07:50 +0200 Subject: [PATCH 14/61] Add missing import --- custom_components/plugwise/water_heater.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index a1a4b9170..b302477f7 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -1,5 +1,7 @@ """Plugwise water heater component for HomeAssistant.""" +from typing import Any + from homeassistant.components.water_heater import ( WaterHeaterEntity, WaterHeaterEntityFeature, From 3a9e4b9dbcada99fdb2ba99c4e7dad820f665fa0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:09:24 +0200 Subject: [PATCH 15/61] Ruffed --- tests/components/plugwise/test_water_heater.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 8e5dabee4..9a5b580c1 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -4,7 +4,10 @@ import pytest -from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN, SERVICE_SET_TEMPERATURE +from homeassistant.components.water_heater import ( + DOMAIN as WATER_HEATER_DOMAIN, + SERVICE_SET_TEMPERATURE, +) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er From 224c54220945ef79c3654c12ff6a5dc338c6654c Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:10:35 +0200 Subject: [PATCH 16/61] Number: remove max_dhw-temperature, handled by water_heater --- custom_components/plugwise/number.py | 8 ------- tests/components/plugwise/test_number.py | 28 ------------------------ 2 files changed, 36 deletions(-) diff --git a/custom_components/plugwise/number.py b/custom_components/plugwise/number.py index cc3f79242..29122649e 100644 --- a/custom_components/plugwise/number.py +++ b/custom_components/plugwise/number.py @@ -16,7 +16,6 @@ LOGGER, LOWER_BOUND, MAX_BOILER_TEMP, - MAX_DHW_TEMP, RESOLUTION, TEMPERATURE_OFFSET, UPPER_BOUND, @@ -47,13 +46,6 @@ class PlugwiseNumberEntityDescription(NumberEntityDescription): entity_category=EntityCategory.CONFIG, native_unit_of_measurement=UnitOfTemperature.CELSIUS, ), - PlugwiseNumberEntityDescription( - key=MAX_DHW_TEMP, - translation_key=MAX_DHW_TEMP, - device_class=NumberDeviceClass.TEMPERATURE, - entity_category=EntityCategory.CONFIG, - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - ), PlugwiseNumberEntityDescription( key=TEMPERATURE_OFFSET, translation_key=TEMPERATURE_OFFSET, diff --git a/tests/components/plugwise/test_number.py b/tests/components/plugwise/test_number.py index aaf496d2a..87ea6f1f9 100644 --- a/tests/components/plugwise/test_number.py +++ b/tests/components/plugwise/test_number.py @@ -67,34 +67,6 @@ async def test_adam_temperature_offset_out_of_bounds_change( ) -@pytest.mark.parametrize("chosen_env", ["m_adam_heating"], indirect=True) -@pytest.mark.parametrize("cooling_present", [False], indirect=True) -async def test_adam_dhw_setpoint_change( - hass: HomeAssistant, - mock_smile_adam_heat_cool: MagicMock, - init_integration: MockConfigEntry, -) -> None: - """Test changing of number entities.""" - state = hass.states.get("number.opentherm_domestic_hot_water_setpoint") - assert state - assert float(state.state) == 60.0 - - await hass.services.async_call( - NUMBER_DOMAIN, - SERVICE_SET_VALUE, - { - ATTR_ENTITY_ID: "number.opentherm_domestic_hot_water_setpoint", - ATTR_VALUE: 55, - }, - blocking=True, - ) - - assert mock_smile_adam_heat_cool.set_number.call_count == 1 - mock_smile_adam_heat_cool.set_number.assert_called_with( - "056ee145a816487eaa69243c3280f8bf", "max_dhw_temperature", 55.0 - ) - - @pytest.mark.parametrize("chosen_env", ["anna_heatpump_heating"], indirect=True) @pytest.mark.parametrize("cooling_present", [True], indirect=True) @pytest.mark.parametrize("platforms", [(NUMBER_DOMAIN,)]) From 4c27cfa1abccd76e1c525b5089b163fac8830233 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:14:04 +0200 Subject: [PATCH 17/61] Revert assert update after remove double number entity --- tests/components/plugwise/test_init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index 078cd6319..bd5dbf89f 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -287,7 +287,7 @@ async def test_update_device( assert ( len(er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)) - == 54 + == 53 ) assert ( len(dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)) @@ -311,7 +311,7 @@ async def test_update_device( assert ( len(er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)) - == 61 + == 60 ) assert ( len(dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)) @@ -338,7 +338,7 @@ async def test_update_device( assert ( len(er.async_entries_for_config_entry(entity_registry, mock_config_entry.entry_id)) - == 54 + == 53 ) assert ( len(dr.async_entries_for_config_entry(device_registry, mock_config_entry.entry_id)) From e98d76a2574a181042421cb741097609c807c3f3 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:16:05 +0200 Subject: [PATCH 18/61] Save updated number snapshot --- .../plugwise/snapshots/test_number.ambr | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/tests/components/plugwise/snapshots/test_number.ambr b/tests/components/plugwise/snapshots/test_number.ambr index b033751e2..0026ef47c 100644 --- a/tests/components/plugwise/snapshots/test_number.ambr +++ b/tests/components/plugwise/snapshots/test_number.ambr @@ -609,67 +609,6 @@ 'state': '-0.5', }) # --- -# name: test_anna_number_entities[platforms0-True-anna_heatpump_heating][number.opentherm_domestic_hot_water_setpoint-entry] - EntityRegistryEntrySnapshot({ - 'aliases': list([ - None, - ]), - 'area_id': None, - 'capabilities': dict({ - 'max': 60.0, - 'min': 35.0, - 'mode': , - 'step': 0.5, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.opentherm_domestic_hot_water_setpoint', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'object_id_base': 'Domestic hot water setpoint', - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Domestic hot water setpoint', - 'platform': 'plugwise', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'max_dhw_temperature', - 'unique_id': '1cbf783bb11e4a7c8a6843dee3a86927-max_dhw_temperature', - 'unit_of_measurement': , - }) -# --- -# name: test_anna_number_entities[platforms0-True-anna_heatpump_heating][number.opentherm_domestic_hot_water_setpoint-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'OpenTherm Domestic hot water setpoint', - 'max': 60.0, - 'min': 35.0, - 'mode': , - 'step': 0.5, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.opentherm_domestic_hot_water_setpoint', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '53.0', - }) -# --- # name: test_anna_number_entities[platforms0-True-anna_heatpump_heating][number.opentherm_maximum_boiler_temperature_setpoint-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ From 7c4e06cc609df52a8cff02e0d60b36aad10ec743 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:20:31 +0200 Subject: [PATCH 19/61] hass -> _hass --- custom_components/plugwise/water_heater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index b302477f7..acdfa6fd2 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -36,7 +36,7 @@ async def async_setup_entry( - hass: HomeAssistant, + _hass: HomeAssistant, entry: PlugwiseConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: From 78f3bc1622aebdfe529775ab59cf61dc6aad6fa4 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:32:32 +0200 Subject: [PATCH 20/61] Correct current_operation modes, as suggested --- custom_components/plugwise/water_heater.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index acdfa6fd2..bb8736eb4 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -9,8 +9,6 @@ from homeassistant.const import ( ATTR_NAME, ATTR_TEMPERATURE, - STATE_OFF, - STATE_ON, UnitOfTemperature, ) from homeassistant.core import HomeAssistant, callback @@ -92,8 +90,8 @@ def current_operation(self) -> str | None: """Return current readable operation mode.""" if (state := self.device.get(BINARY_SENSORS, {}).get("dhw_state")) is not None: if state: - return STATE_ON - return STATE_OFF + return MODE_HEAT + return MODE_OFF return None @property From 00640e6dcb13ef68af17df53d420b1e9f10d3a6a Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:35:18 +0200 Subject: [PATCH 21/61] Re-ruffed --- custom_components/plugwise/water_heater.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index bb8736eb4..22f2c96aa 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -6,11 +6,7 @@ WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ( - ATTR_NAME, - ATTR_TEMPERATURE, - UnitOfTemperature, -) +from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback From f44d460cdd97d5d06c648cff8c5bcf19774c4184 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 14:42:21 +0200 Subject: [PATCH 22/61] Improve, as suggested --- custom_components/plugwise/water_heater.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 22f2c96aa..20d14a592 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -69,7 +69,6 @@ def __init__( ) -> None: """Initialise the water_heater.""" super().__init__(coordinator, device_id) - self.device_id = device_id self._attr_unique_id = f"{device_id}-water_heater" self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) @@ -107,7 +106,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None: if not self._supports_temperature_control or temperature is None: return - await self.coordinator.api.set_number(self.device_id, MAX_DHW_TEMP, temperature) + await self.coordinator.api.set_number(self._dev_id, MAX_DHW_TEMP, temperature) LOGGER.debug( "Setting %s to %s was successful", MAX_DHW_TEMP, temperature ) From 733b0baa20fb207b98963266f219d451435c27c5 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 20:21:35 +0200 Subject: [PATCH 23/61] Change modes, add set_operation_mode(), and more --- custom_components/plugwise/water_heater.py | 36 +++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 20d14a592..f315c10a5 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -6,13 +6,21 @@ WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, UnitOfTemperature +from homeassistant.const import ( + ATTR_NAME, + ATTR_TEMPERATURE, + STATE_OFF, + STATE_ON, + UnitOfTemperature, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( BINARY_SENSORS, DEV_CLASS, + DHW_CM_SWITCH, + DHW_SETPOINT, LOGGER, LOWER_BOUND, MAX_DHW_TEMP, @@ -24,9 +32,9 @@ from .entity import PlugwiseEntity from .util import plugwise_command -MODE_HEAT = "heat" -MODE_OFF = "off" -OPERATION_MODES = [MODE_HEAT, MODE_OFF] +MODE_DHW_COMFORT = "Dhw comfort" +MODE_DHW_NORMAL = "Dhw normal" +OPERATION_MODES = [MODE_DHW_COMFORT, MODE_DHW_NORMAL] async def async_setup_entry( @@ -85,8 +93,8 @@ def current_operation(self) -> str | None: """Return current readable operation mode.""" if (state := self.device.get(BINARY_SENSORS, {}).get("dhw_state")) is not None: if state: - return MODE_HEAT - return MODE_OFF + return MODE_DHW_COMFORT + return MODE_DHW_NORMAL return None @property @@ -97,7 +105,21 @@ def current_temperature(self) -> float | None: @property def target_temperature(self) -> float | None: """Return the water temperature we try to reach.""" - return self.device.get("max_dhw_temperature", {}).get(TARGET_TEMP) + return ( + self.device.get("max_dhw_temperature", {}).get(TARGET_TEMP) + or self.device.get(SENSORS, {}).get(DHW_SETPOINT) + ) + + @plugwise_command + async def async_set_operation_mode(self, mode: str) -> None: + """Set the operation mode.""" + state = STATE_ON if mode == MODE_DHW_COMFORT else STATE_OFF + await self.coordinator.api.set_switch_state( + self._dev_id, + None, + DHW_CM_SWITCH, + state, + ) @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: From 9dc664a462a4170786cb0ee45761478d7d52b656 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 20:34:37 +0200 Subject: [PATCH 24/61] Correct W0237 --- custom_components/plugwise/water_heater.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index f315c10a5..dbdd26810 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -111,9 +111,9 @@ def target_temperature(self) -> float | None: ) @plugwise_command - async def async_set_operation_mode(self, mode: str) -> None: + async def async_set_operation_mode(self, operation_mode: str) -> None: """Set the operation mode.""" - state = STATE_ON if mode == MODE_DHW_COMFORT else STATE_OFF + state = STATE_ON if operation_mode == MODE_DHW_COMFORT else STATE_OFF await self.coordinator.api.set_switch_state( self._dev_id, None, From d79c175bdc9a5e53786a0767cf25265417f502fc Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 7 Jun 2026 20:43:00 +0200 Subject: [PATCH 25/61] Save snapshot updates --- .../plugwise/snapshots/test_water_heater.ambr | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/components/plugwise/snapshots/test_water_heater.ambr b/tests/components/plugwise/snapshots/test_water_heater.ambr index d4ce0e05c..885275561 100644 --- a/tests/components/plugwise/snapshots/test_water_heater.ambr +++ b/tests/components/plugwise/snapshots/test_water_heater.ambr @@ -9,8 +9,8 @@ 'max_temp': 60.0, 'min_temp': 40.0, 'operation_list': list([ - 'heat', - 'off', + 'Dhw comfort', + 'Dhw normal', ]), }), 'config_entry_id': , @@ -51,10 +51,10 @@ 'max_temp': 60.0, 'min_temp': 40.0, 'operation_list': list([ - 'heat', - 'off', + 'Dhw comfort', + 'Dhw normal', ]), - 'operation_mode': 'off', + 'operation_mode': 'Dhw normal', 'supported_features': , 'target_temp_high': None, 'target_temp_low': None, @@ -65,7 +65,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'off', + 'state': 'Dhw normal', }) # --- # name: test_anna_water_heater_snapshot[platforms0-False-anna_v4_dhw][water_heater.opentherm-entry] @@ -78,8 +78,8 @@ 'max_temp': 60.0, 'min_temp': 30.0, 'operation_list': list([ - 'heat', - 'off', + 'Dhw comfort', + 'Dhw normal', ]), }), 'config_entry_id': , @@ -120,10 +120,10 @@ 'max_temp': 60.0, 'min_temp': 30.0, 'operation_list': list([ - 'heat', - 'off', + 'Dhw comfort', + 'Dhw normal', ]), - 'operation_mode': 'on', + 'operation_mode': 'Dhw comfort', 'supported_features': , 'target_temp_high': None, 'target_temp_low': None, @@ -134,6 +134,6 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'on', + 'state': 'Dhw comfort', }) # --- From da88bfbb3567d4380194ae73c11deda5bb872d73 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 13 Jun 2026 13:08:19 +0200 Subject: [PATCH 26/61] Tighten water_heater criterium --- custom_components/plugwise/water_heater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index dbdd26810..a2723115d 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -54,7 +54,7 @@ def _add_entities() -> None: entities: list[PlugwiseWaterHeaterEntity] = [] for device_id in coordinator.new_devices: device = coordinator.data[device_id] - if device[DEV_CLASS] == "heater_central" and device.get(BINARY_SENSORS, {}).get("dhw_state") is not None: + if device.get("max_dhw_temperature") is not None: entities.append(PlugwiseWaterHeaterEntity(coordinator, device_id)) LOGGER.debug("Add %s water_heater", device[ATTR_NAME]) async_add_entities(entities) From 324cafdbc47fd58a49c97e971d0fdbead258ee9e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 13 Jun 2026 13:21:57 +0200 Subject: [PATCH 27/61] Adapt async_set_operation_mode() to backend update --- custom_components/plugwise/water_heater.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index a2723115d..bfefbfdd6 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -113,12 +113,8 @@ def target_temperature(self) -> float | None: @plugwise_command async def async_set_operation_mode(self, operation_mode: str) -> None: """Set the operation mode.""" - state = STATE_ON if operation_mode == MODE_DHW_COMFORT else STATE_OFF - await self.coordinator.api.set_switch_state( - self._dev_id, - None, - DHW_CM_SWITCH, - state, + await self.coordinator.api.set_select( + "water_heater_mode", "", operation_mode, None ) @plugwise_command From 767a38fd3ad2089054d4d399cccf0604c0fac288 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 08:36:29 +0200 Subject: [PATCH 28/61] Call set_dwh_mode() directly --- custom_components/plugwise/water_heater.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index bfefbfdd6..c4ab4f401 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -113,9 +113,7 @@ def target_temperature(self) -> float | None: @plugwise_command async def async_set_operation_mode(self, operation_mode: str) -> None: """Set the operation mode.""" - await self.coordinator.api.set_select( - "water_heater_mode", "", operation_mode, None - ) + await self.coordinator.api.set_dhw_mode(operation_mode) @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: From b7d91b4bc44726265448b2835d1914a135738171 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 09:02:08 +0200 Subject: [PATCH 29/61] Link to updated library --- custom_components/plugwise/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index eccee39a2..72994b8b5 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -7,7 +7,7 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise"], - "requirements": ["plugwise==1.11.4"], + "requirements": ["plugwise@git+https://github.com/plugwise/python-plugwise.git/@dhw_update"], "version": "0.64.4", "zeroconf": ["_plugwise._tcp.local."] } From 46121fbc8c6f3c46cacb8748ac129ea3bbdfad7d Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 09:05:39 +0200 Subject: [PATCH 30/61] Refresh test-fixtures --- .../plugwise/fixtures/adam_plus_anna_new/data.json | 8 +++++--- .../plugwise/fixtures/anna_heatpump_heating/data.json | 8 +++++--- tests/components/plugwise/fixtures/anna_p1/data.json | 8 +++++--- tests/components/plugwise/fixtures/anna_v4/data.json | 8 +++++--- tests/components/plugwise/fixtures/anna_v4_dhw/data.json | 8 +++++--- .../components/plugwise/fixtures/m_adam_cooling/data.json | 8 +++++--- .../components/plugwise/fixtures/m_adam_heating/data.json | 8 +++++--- .../fixtures/m_adam_heating_off_schedule/data.json | 8 +++++--- tests/components/plugwise/fixtures/m_adam_jip/data.json | 8 +++++--- .../plugwise/fixtures/m_anna_heatpump_cooling/data.json | 8 +++++--- 10 files changed, 50 insertions(+), 30 deletions(-) diff --git a/tests/components/plugwise/fixtures/adam_plus_anna_new/data.json b/tests/components/plugwise/fixtures/adam_plus_anna_new/data.json index 0c1057afc..56c7ffbb0 100644 --- a/tests/components/plugwise/fixtures/adam_plus_anna_new/data.json +++ b/tests/components/plugwise/fixtures/adam_plus_anna_new/data.json @@ -7,6 +7,10 @@ "heating_state": true }, "dev_class": "heater_central", + "dhw_modes": [ + "comfort", + "off" + ], "location": "bc93488efab249e5bc54fd7e175a6f91", "maximum_boiler_temperature": { "lower_bound": 25.0, @@ -16,12 +20,10 @@ }, "model": "Generic heater", "name": "OpenTherm", + "select_dhw_mode": "off", "sensors": { "intended_boiler_temperature": 22.5, "water_temperature": 43.0 - }, - "switches": { - "dhw_cm_switch": false } }, "10016900610d4c7481df78c89606ef22": { diff --git a/tests/components/plugwise/fixtures/anna_heatpump_heating/data.json b/tests/components/plugwise/fixtures/anna_heatpump_heating/data.json index 2d90b6f12..54a045273 100644 --- a/tests/components/plugwise/fixtures/anna_heatpump_heating/data.json +++ b/tests/components/plugwise/fixtures/anna_heatpump_heating/data.json @@ -29,6 +29,11 @@ "secondary_boiler_state": false }, "dev_class": "heater_central", + "dhw_mode": "off", + "dhw_modes": [ + "comfort", + "off" + ], "location": "a57efe5f145f498c9be62a9b63626fbf", "max_dhw_temperature": { "lower_bound": 35.0, @@ -53,9 +58,6 @@ "water_pressure": 1.57, "water_temperature": 29.1 }, - "switches": { - "dhw_cm_switch": false - }, "vendor": "Techneco" }, "3cb70739631c4d17a86b8b12e8a5161b": { diff --git a/tests/components/plugwise/fixtures/anna_p1/data.json b/tests/components/plugwise/fixtures/anna_p1/data.json index 5918dc8fb..6ee72b54c 100644 --- a/tests/components/plugwise/fixtures/anna_p1/data.json +++ b/tests/components/plugwise/fixtures/anna_p1/data.json @@ -48,19 +48,21 @@ "heating_state": false }, "dev_class": "heater_central", + "dhw_modes": [ + "comfort", + "off" + ], "location": "da7be222ab3b420c927f3e49fade0304", "model": "Generic heater", "model_id": "HR24", "name": "OpenTherm", + "select_dhw_mode": "comfort", "sensors": { "intended_boiler_temperature": 0.0, "modulation_level": 0.0, "water_pressure": 6.0, "water_temperature": 35.0 }, - "switches": { - "dhw_cm_switch": true - }, "vendor": "Intergas" }, "53130847be2f436cb946b78dedb9053a": { diff --git a/tests/components/plugwise/fixtures/anna_v4/data.json b/tests/components/plugwise/fixtures/anna_v4/data.json index a64605171..563560b49 100644 --- a/tests/components/plugwise/fixtures/anna_v4/data.json +++ b/tests/components/plugwise/fixtures/anna_v4/data.json @@ -67,6 +67,11 @@ "heating_state": true }, "dev_class": "heater_central", + "dhw_mode": "off", + "dhw_modes": [ + "comfort", + "off" + ], "location": "94c107dc6ac84ed98e9f68c0dd06bf71", "max_dhw_temperature": { "lower_bound": 30.0, @@ -90,9 +95,6 @@ "water_pressure": 2.2, "water_temperature": 45.0 }, - "switches": { - "dhw_cm_switch": false - }, "vendor": "Bosch Thermotechniek B.V." } } diff --git a/tests/components/plugwise/fixtures/anna_v4_dhw/data.json b/tests/components/plugwise/fixtures/anna_v4_dhw/data.json index 677d0a622..9c1f13360 100644 --- a/tests/components/plugwise/fixtures/anna_v4_dhw/data.json +++ b/tests/components/plugwise/fixtures/anna_v4_dhw/data.json @@ -67,6 +67,11 @@ "heating_state": false }, "dev_class": "heater_central", + "dhw_mode": "off", + "dhw_modes": [ + "comfort", + "off" + ], "location": "94c107dc6ac84ed98e9f68c0dd06bf71", "max_dhw_temperature": { "lower_bound": 30.0, @@ -90,9 +95,6 @@ "water_pressure": 2.2, "water_temperature": 45.0 }, - "switches": { - "dhw_cm_switch": false - }, "vendor": "Bosch Thermotechniek B.V." } } diff --git a/tests/components/plugwise/fixtures/m_adam_cooling/data.json b/tests/components/plugwise/fixtures/m_adam_cooling/data.json index d1b3753ea..c5b321660 100644 --- a/tests/components/plugwise/fixtures/m_adam_cooling/data.json +++ b/tests/components/plugwise/fixtures/m_adam_cooling/data.json @@ -8,6 +8,10 @@ "heating_state": false }, "dev_class": "heater_central", + "dhw_modes": [ + "comfort", + "off" + ], "location": "bc93488efab249e5bc54fd7e175a6f91", "maximum_boiler_temperature": { "lower_bound": 25.0, @@ -17,12 +21,10 @@ }, "model": "Generic heater", "name": "OpenTherm", + "select_dhw_mode": "off", "sensors": { "intended_boiler_temperature": 17.5, "water_temperature": 19.0 - }, - "switches": { - "dhw_cm_switch": false } }, "14df5c4dc8cb4ba69f9d1ac0eaf7c5c6": { diff --git a/tests/components/plugwise/fixtures/m_adam_heating/data.json b/tests/components/plugwise/fixtures/m_adam_heating/data.json index 208b6df84..f29aa84c7 100644 --- a/tests/components/plugwise/fixtures/m_adam_heating/data.json +++ b/tests/components/plugwise/fixtures/m_adam_heating/data.json @@ -7,6 +7,10 @@ "heating_state": true }, "dev_class": "heater_central", + "dhw_modes": [ + "comfort", + "off" + ], "location": "bc93488efab249e5bc54fd7e175a6f91", "max_dhw_temperature": { "lower_bound": 40.0, @@ -22,12 +26,10 @@ }, "model": "Generic heater", "name": "OpenTherm", + "select_dhw_mode": "off", "sensors": { "intended_boiler_temperature": 38.1, "water_temperature": 37.0 - }, - "switches": { - "dhw_cm_switch": false } }, "14df5c4dc8cb4ba69f9d1ac0eaf7c5c6": { diff --git a/tests/components/plugwise/fixtures/m_adam_heating_off_schedule/data.json b/tests/components/plugwise/fixtures/m_adam_heating_off_schedule/data.json index 3aeb08f30..aaabc1495 100644 --- a/tests/components/plugwise/fixtures/m_adam_heating_off_schedule/data.json +++ b/tests/components/plugwise/fixtures/m_adam_heating_off_schedule/data.json @@ -7,6 +7,10 @@ "heating_state": false }, "dev_class": "heater_central", + "dhw_modes": [ + "comfort", + "off" + ], "location": "bc93488efab249e5bc54fd7e175a6f91", "max_dhw_temperature": { "lower_bound": 40.0, @@ -22,12 +26,10 @@ }, "model": "Generic heater", "name": "OpenTherm", + "select_dhw_mode": "off", "sensors": { "intended_boiler_temperature": 0.0, "water_temperature": 37.0 - }, - "switches": { - "dhw_cm_switch": false } }, "14df5c4dc8cb4ba69f9d1ac0eaf7c5c6": { diff --git a/tests/components/plugwise/fixtures/m_adam_jip/data.json b/tests/components/plugwise/fixtures/m_adam_jip/data.json index a473c7748..ff33d77c2 100644 --- a/tests/components/plugwise/fixtures/m_adam_jip/data.json +++ b/tests/components/plugwise/fixtures/m_adam_jip/data.json @@ -393,6 +393,11 @@ "heating_state": false }, "dev_class": "heater_central", + "dhw_mode": "off", + "dhw_modes": [ + "comfort", + "off" + ], "location": "9e4433a9d69f40b3aefd15e74395eaec", "max_dhw_temperature": { "lower_bound": 40.0, @@ -416,9 +421,6 @@ "water_pressure": 1.4, "water_temperature": 37.3 }, - "switches": { - "dhw_cm_switch": false - }, "vendor": "Remeha B.V." }, "f61f1a2535f54f52ad006a3d18e459ca": { diff --git a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/data.json b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/data.json index e12f137bd..fb4085270 100644 --- a/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/data.json +++ b/tests/components/plugwise/fixtures/m_anna_heatpump_cooling/data.json @@ -29,6 +29,11 @@ "secondary_boiler_state": false }, "dev_class": "heater_central", + "dhw_mode": "off", + "dhw_modes": [ + "comfort", + "off" + ], "location": "a57efe5f145f498c9be62a9b63626fbf", "max_dhw_temperature": { "lower_bound": 35.0, @@ -53,9 +58,6 @@ "water_pressure": 1.57, "water_temperature": 22.7 }, - "switches": { - "dhw_cm_switch": false - }, "vendor": "Techneco" }, "3cb70739631c4d17a86b8b12e8a5161b": { From 759e61a0c2c6b82d36ce204011d04b4cd79ebf99 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 09:10:50 +0200 Subject: [PATCH 31/61] Remove dhw_cm_switch, function moved to water_heater platform --- custom_components/plugwise/switch.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index 2edfb7842..d6c088c17 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -41,12 +41,6 @@ class PlugwiseSwitchEntityDescription(SwitchEntityDescription): # Upstream consts PLUGWISE_SWITCHES: tuple[PlugwiseSwitchEntityDescription, ...] = ( - PlugwiseSwitchEntityDescription( - key=DHW_CM_SWITCH, - translation_key=DHW_CM_SWITCH, - device_class=SwitchDeviceClass.SWITCH, - entity_category=EntityCategory.CONFIG, - ), PlugwiseSwitchEntityDescription( key=LOCK, translation_key=LOCK, From c3ad501ddef6d609450f0ff920057bd0074cff86 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 09:18:22 +0200 Subject: [PATCH 32/61] Save updated select snapshot --- .../plugwise/snapshots/test_select.ambr | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/components/plugwise/snapshots/test_select.ambr b/tests/components/plugwise/snapshots/test_select.ambr index cc1df1a73..3b87ac33b 100644 --- a/tests/components/plugwise/snapshots/test_select.ambr +++ b/tests/components/plugwise/snapshots/test_select.ambr @@ -377,6 +377,65 @@ 'state': 'active', }) # --- +# name: test_adam_2_select_entities[platforms0-True-m_adam_cooling][select.opentherm_dhw_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'comfort', + 'off', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.opentherm_dhw_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'DHW mode', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'DHW mode', + 'platform': 'plugwise', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'select_dhw_mode', + 'unique_id': '056ee145a816487eaa69243c3280f8bf-select_dhw_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_adam_2_select_entities[platforms0-True-m_adam_cooling][select.opentherm_dhw_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'OpenTherm DHW mode', + 'options': list([ + 'comfort', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.opentherm_dhw_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_adam_select_entities[platforms0][select.badkamer_thermostat_schedule-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ From 0ca4150eb277f51dda38c4178a86a373f7409df9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 09:19:12 +0200 Subject: [PATCH 33/61] Cleaning up --- custom_components/plugwise/switch.py | 1 - custom_components/plugwise/water_heater.py | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/custom_components/plugwise/switch.py b/custom_components/plugwise/switch.py index d6c088c17..2a4b25382 100644 --- a/custom_components/plugwise/switch.py +++ b/custom_components/plugwise/switch.py @@ -16,7 +16,6 @@ from .const import ( COOLING_ENA_SWITCH, - DHW_CM_SWITCH, LOCK, LOGGER, # pw-beta MEMBERS, diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index c4ab4f401..7802fe99f 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -6,20 +6,12 @@ WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ( - ATTR_NAME, - ATTR_TEMPERATURE, - STATE_OFF, - STATE_ON, - UnitOfTemperature, -) +from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( BINARY_SENSORS, - DEV_CLASS, - DHW_CM_SWITCH, DHW_SETPOINT, LOGGER, LOWER_BOUND, From 349c9642cafaad49c815bd0277afec3c2d9ac6bc Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 09:31:51 +0200 Subject: [PATCH 34/61] Simplify, remove logging, add pragma-no cover --- custom_components/plugwise/water_heater.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 7802fe99f..77ffe60d3 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -13,7 +13,6 @@ from .const import ( BINARY_SENSORS, DHW_SETPOINT, - LOGGER, LOWER_BOUND, MAX_DHW_TEMP, SENSORS, @@ -74,10 +73,7 @@ def __init__( self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE - self._supports_temperature_control = False - if self.device.get("max_dhw_temperature"): - self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE - self._supports_temperature_control = True + self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE @property @@ -87,7 +83,7 @@ def current_operation(self) -> str | None: if state: return MODE_DHW_COMFORT return MODE_DHW_NORMAL - return None + return None # pragma: no cover @property def current_temperature(self) -> float | None: @@ -111,10 +107,4 @@ async def async_set_operation_mode(self, operation_mode: str) -> None: async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) - if not self._supports_temperature_control or temperature is None: - return - await self.coordinator.api.set_number(self._dev_id, MAX_DHW_TEMP, temperature) - LOGGER.debug( - "Setting %s to %s was successful", MAX_DHW_TEMP, temperature - ) From 662dd9aa9e67a1e3e68d7cd77882b10d546162b7 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 10:02:04 +0200 Subject: [PATCH 35/61] Add set_operation_mode test --- tests/components/plugwise/test_water_heater.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 9a5b580c1..b64dadd57 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -5,7 +5,9 @@ import pytest from homeassistant.components.water_heater import ( + ATTR_OPERATION_MODE, DOMAIN as WATER_HEATER_DOMAIN, + SERVICE_SET_OPERATION_MODE, SERVICE_SET_TEMPERATURE, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE @@ -48,6 +50,17 @@ async def test_adam_water_heater_setpoint_change( "e4684553153b44afbef2200885f379dc", "max_dhw_temperature", 65.0, ) + await hass.services.async_call( + WATER_HEATER_DOMAIN, + SERVICE_SET_OPERATION_MODE, + {ATTR_ENTITY_ID: "water_heater.opentherm", ATTR_OPERATION_MODE: "off"}, + blocking=True, + ) + assert mock_smile_adam_jip.set_dhw_mode.call_count == 1 + mock_smile_adam_jip.set_dhw_mode.assert_called_with( + "e4684553153b44afbef2200885f379dc", "off", + ) + @pytest.mark.parametrize("chosen_env", ["anna_v4_dhw"], indirect=True) @pytest.mark.parametrize("cooling_present", [False], indirect=True) From 647d9ea0d3e564afbabfc7a17aa946dc702d70ed Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 10:12:51 +0200 Subject: [PATCH 36/61] Revert LOGGER removal --- custom_components/plugwise/water_heater.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 77ffe60d3..837cf269b 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -13,6 +13,7 @@ from .const import ( BINARY_SENSORS, DHW_SETPOINT, + LOGGER, LOWER_BOUND, MAX_DHW_TEMP, SENSORS, From 4e3f2fa6baa503c4b8a499ee928a4e5c42d3c975 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 10:16:23 +0200 Subject: [PATCH 37/61] Update current_operation property --- custom_components/plugwise/water_heater.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 837cf269b..5851a7074 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -80,11 +80,7 @@ def __init__( @property def current_operation(self) -> str | None: """Return current readable operation mode.""" - if (state := self.device.get(BINARY_SENSORS, {}).get("dhw_state")) is not None: - if state: - return MODE_DHW_COMFORT - return MODE_DHW_NORMAL - return None # pragma: no cover + return self.device.get("dhw_mode") @property def current_temperature(self) -> float | None: From 7d9ab52cff6d69c7c56b312538d6edb8979be787 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 10:20:16 +0200 Subject: [PATCH 38/61] Collect operation_list from device --- custom_components/plugwise/water_heater.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 5851a7074..60f4d81c4 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -24,10 +24,6 @@ from .entity import PlugwiseEntity from .util import plugwise_command -MODE_DHW_COMFORT = "Dhw comfort" -MODE_DHW_NORMAL = "Dhw normal" -OPERATION_MODES = [MODE_DHW_COMFORT, MODE_DHW_NORMAL] - async def async_setup_entry( _hass: HomeAssistant, @@ -59,7 +55,6 @@ class PlugwiseWaterHeaterEntity(PlugwiseEntity, WaterHeaterEntity): """Representation of a Plugwise water heater.""" _attr_name = None - _attr_operation_list = OPERATION_MODES _attr_temperature_unit = UnitOfTemperature.CELSIUS def __init__( @@ -73,6 +68,7 @@ def __init__( self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) + self._attr_operation_list = self.device.get("dhw_modes", {}) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE From 979f9b42a05de65864e9a158ead3b3e0a9a3a8fb Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 11:18:58 +0200 Subject: [PATCH 39/61] Fix test --- tests/components/plugwise/test_water_heater.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index b64dadd57..1d4550ac6 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -57,9 +57,7 @@ async def test_adam_water_heater_setpoint_change( blocking=True, ) assert mock_smile_adam_jip.set_dhw_mode.call_count == 1 - mock_smile_adam_jip.set_dhw_mode.assert_called_with( - "e4684553153b44afbef2200885f379dc", "off", - ) + mock_smile_adam_jip.set_dhw_mode.assert_called_with("off") @pytest.mark.parametrize("chosen_env", ["anna_v4_dhw"], indirect=True) From 12d5283c7400245941c31decbc8485792d5e821f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 11:25:43 +0200 Subject: [PATCH 40/61] Fix water_heater typing --- custom_components/plugwise/water_heater.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 60f4d81c4..2384527a8 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -6,12 +6,16 @@ WaterHeaterEntity, WaterHeaterEntityFeature, ) -from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, UnitOfTemperature +from homeassistant.const import ( + ATTR_NAME, + ATTR_TEMPERATURE, + STATE_OFF, + UnitOfTemperature, +) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( - BINARY_SENSORS, DHW_SETPOINT, LOGGER, LOWER_BOUND, @@ -68,7 +72,6 @@ def __init__( self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) - self._attr_operation_list = self.device.get("dhw_modes", {}) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE @@ -83,6 +86,13 @@ def current_temperature(self) -> float | None: """Return the current water temperature.""" return self.device.get(SENSORS, {}).get("water_temperature") + @property + def operation_list(self) -> list[str]: + """Return the list of available operation modes.""" + if (op_list := self.device.get("dhw_modes", [])): + return op_list + return [STATE_OFF] + @property def target_temperature(self) -> float | None: """Return the water temperature we try to reach.""" @@ -99,5 +109,5 @@ async def async_set_operation_mode(self, operation_mode: str) -> None: @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - temperature = kwargs.get(ATTR_TEMPERATURE) - await self.coordinator.api.set_number(self._dev_id, MAX_DHW_TEMP, temperature) + if (temperature := kwargs.get(ATTR_TEMPERATURE)) is not None: + await self.coordinator.api.set_number(self._dev_id, MAX_DHW_TEMP, float(temperature)) From 1d7ad3af28a9a2fefff3adf8dbc7f434e461e724 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 11:58:51 +0200 Subject: [PATCH 41/61] Save updated test snapshot --- .../plugwise/snapshots/test_water_heater.ambr | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/components/plugwise/snapshots/test_water_heater.ambr b/tests/components/plugwise/snapshots/test_water_heater.ambr index 885275561..5e22e905d 100644 --- a/tests/components/plugwise/snapshots/test_water_heater.ambr +++ b/tests/components/plugwise/snapshots/test_water_heater.ambr @@ -9,8 +9,8 @@ 'max_temp': 60.0, 'min_temp': 40.0, 'operation_list': list([ - 'Dhw comfort', - 'Dhw normal', + 'comfort', + 'off', ]), }), 'config_entry_id': , @@ -51,10 +51,10 @@ 'max_temp': 60.0, 'min_temp': 40.0, 'operation_list': list([ - 'Dhw comfort', - 'Dhw normal', + 'comfort', + 'off', ]), - 'operation_mode': 'Dhw normal', + 'operation_mode': 'off', 'supported_features': , 'target_temp_high': None, 'target_temp_low': None, @@ -65,7 +65,7 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'Dhw normal', + 'state': 'off', }) # --- # name: test_anna_water_heater_snapshot[platforms0-False-anna_v4_dhw][water_heater.opentherm-entry] @@ -78,8 +78,8 @@ 'max_temp': 60.0, 'min_temp': 30.0, 'operation_list': list([ - 'Dhw comfort', - 'Dhw normal', + 'comfort', + 'off', ]), }), 'config_entry_id': , @@ -120,10 +120,10 @@ 'max_temp': 60.0, 'min_temp': 30.0, 'operation_list': list([ - 'Dhw comfort', - 'Dhw normal', + 'comfort', + 'off', ]), - 'operation_mode': 'Dhw comfort', + 'operation_mode': 'off', 'supported_features': , 'target_temp_high': None, 'target_temp_low': None, @@ -134,6 +134,6 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'Dhw comfort', + 'state': 'off', }) # --- From 78110fc83c044b642e2b003ddfc3376d343d1351 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 12:04:00 +0200 Subject: [PATCH 42/61] Update strings.json, translations --- custom_components/plugwise/strings.json | 11 +++++++++-- custom_components/plugwise/translations/en.json | 11 +++++++++-- custom_components/plugwise/translations/nl.json | 11 +++++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/custom_components/plugwise/strings.json b/custom_components/plugwise/strings.json index 7616000b7..32f3d6bcf 100644 --- a/custom_components/plugwise/strings.json +++ b/custom_components/plugwise/strings.json @@ -107,8 +107,6 @@ "select_dhw_mode": { "name": "DHW mode", "state": { - "auto": "Auto", - "boost": "Boost", "comfort": "Comfort", "off": "Off" } @@ -299,6 +297,15 @@ "relay": { "name": "Relay" } + }, + "water_heater": { + "name": "DHW mode", + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } } }, "exceptions": { diff --git a/custom_components/plugwise/translations/en.json b/custom_components/plugwise/translations/en.json index 7616000b7..32f3d6bcf 100644 --- a/custom_components/plugwise/translations/en.json +++ b/custom_components/plugwise/translations/en.json @@ -107,8 +107,6 @@ "select_dhw_mode": { "name": "DHW mode", "state": { - "auto": "Auto", - "boost": "Boost", "comfort": "Comfort", "off": "Off" } @@ -299,6 +297,15 @@ "relay": { "name": "Relay" } + }, + "water_heater": { + "name": "DHW mode", + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } } }, "exceptions": { diff --git a/custom_components/plugwise/translations/nl.json b/custom_components/plugwise/translations/nl.json index d358f55f5..d71174c61 100644 --- a/custom_components/plugwise/translations/nl.json +++ b/custom_components/plugwise/translations/nl.json @@ -107,8 +107,6 @@ "select_dhw_mode": { "name": "SWW modus", "state": { - "auto": "Automatisch", - "boost": "Boost", "comfort": "Comfort", "off": "Uit" } @@ -299,6 +297,15 @@ "relay": { "name": "Schakelaar" } + }, + "water_heater": { + "name": "SWW modus", + "state": { + "auto": "Automatisch", + "boost": "Boost", + "comfort": "Comfort", + "off": "Uit" + } } }, "exceptions": { From c4a1d081138839096878493bf051c9bb1d730675 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 12:18:13 +0200 Subject: [PATCH 43/61] Prettier fixes --- custom_components/plugwise/manifest.json | 4 +++- custom_components/plugwise/strings.json | 12 ++++++------ custom_components/plugwise/translations/en.json | 12 ++++++------ custom_components/plugwise/translations/nl.json | 12 ++++++------ 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index 72994b8b5..09b0587b1 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -7,7 +7,9 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise"], - "requirements": ["plugwise@git+https://github.com/plugwise/python-plugwise.git/@dhw_update"], + "requirements": [ + "plugwise@git+https://github.com/plugwise/python-plugwise.git/@dhw_update" + ], "version": "0.64.4", "zeroconf": ["_plugwise._tcp.local."] } diff --git a/custom_components/plugwise/strings.json b/custom_components/plugwise/strings.json index 32f3d6bcf..ebed81b33 100644 --- a/custom_components/plugwise/strings.json +++ b/custom_components/plugwise/strings.json @@ -300,12 +300,12 @@ }, "water_heater": { "name": "DHW mode", - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "off": "Off" - } + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } } }, "exceptions": { diff --git a/custom_components/plugwise/translations/en.json b/custom_components/plugwise/translations/en.json index 32f3d6bcf..ebed81b33 100644 --- a/custom_components/plugwise/translations/en.json +++ b/custom_components/plugwise/translations/en.json @@ -300,12 +300,12 @@ }, "water_heater": { "name": "DHW mode", - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "off": "Off" - } + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } } }, "exceptions": { diff --git a/custom_components/plugwise/translations/nl.json b/custom_components/plugwise/translations/nl.json index d71174c61..2473a5436 100644 --- a/custom_components/plugwise/translations/nl.json +++ b/custom_components/plugwise/translations/nl.json @@ -300,12 +300,12 @@ }, "water_heater": { "name": "SWW modus", - "state": { - "auto": "Automatisch", - "boost": "Boost", - "comfort": "Comfort", - "off": "Uit" - } + "state": { + "auto": "Automatisch", + "boost": "Boost", + "comfort": "Comfort", + "off": "Uit" + } } }, "exceptions": { From 9f0d9232b7b36d0e12a283f1a3b300abaed6f45f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 12:34:37 +0200 Subject: [PATCH 44/61] Rework water_heater strings --- custom_components/plugwise/strings.json | 17 +++++++++++------ custom_components/plugwise/translations/en.json | 17 +++++++++++------ custom_components/plugwise/translations/nl.json | 17 +++++++++++------ 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/custom_components/plugwise/strings.json b/custom_components/plugwise/strings.json index ebed81b33..55dfd187c 100644 --- a/custom_components/plugwise/strings.json +++ b/custom_components/plugwise/strings.json @@ -299,12 +299,17 @@ } }, "water_heater": { - "name": "DHW mode", - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "off": "Off" + "plugwise": { + "state_attributes": { + "dhw_mode": { + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } + } + } } } }, diff --git a/custom_components/plugwise/translations/en.json b/custom_components/plugwise/translations/en.json index ebed81b33..55dfd187c 100644 --- a/custom_components/plugwise/translations/en.json +++ b/custom_components/plugwise/translations/en.json @@ -299,12 +299,17 @@ } }, "water_heater": { - "name": "DHW mode", - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "off": "Off" + "plugwise": { + "state_attributes": { + "dhw_mode": { + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } + } + } } } }, diff --git a/custom_components/plugwise/translations/nl.json b/custom_components/plugwise/translations/nl.json index 2473a5436..3e26a89dc 100644 --- a/custom_components/plugwise/translations/nl.json +++ b/custom_components/plugwise/translations/nl.json @@ -299,12 +299,17 @@ } }, "water_heater": { - "name": "SWW modus", - "state": { - "auto": "Automatisch", - "boost": "Boost", - "comfort": "Comfort", - "off": "Uit" + "plugwise": { + "state_attributes": { + "dhw_mode": { + "state": { + "auto": "Automatisch", + "boost": "Boost", + "comfort": "Comfort", + "off": "Off" + } + } + } } } }, From 1fc1338415b3a48fbc8f507b216f5266bfb3e6ff Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sun, 14 Jun 2026 13:18:52 +0200 Subject: [PATCH 45/61] Pass length of operation_list to determine set-method --- custom_components/plugwise/water_heater.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 2384527a8..942822d6b 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -104,7 +104,8 @@ def target_temperature(self) -> float | None: @plugwise_command async def async_set_operation_mode(self, operation_mode: str) -> None: """Set the operation mode.""" - await self.coordinator.api.set_dhw_mode(operation_mode) + list_type: int = len(self.operation_list) + await self.coordinator.api.set_dhw_mode(list_type, operation_mode) @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: From 3fc8e953228b2ae0a4ea38f9255a3534aa6ee7dd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Jun 2026 18:32:37 +0200 Subject: [PATCH 46/61] Fix set_dhw_mode() args --- custom_components/plugwise/water_heater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 942822d6b..e294a4952 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -105,7 +105,7 @@ def target_temperature(self) -> float | None: async def async_set_operation_mode(self, operation_mode: str) -> None: """Set the operation mode.""" list_type: int = len(self.operation_list) - await self.coordinator.api.set_dhw_mode(list_type, operation_mode) + await self.coordinator.api.set_dhw_mode("dhw_mode", self._dev_id, list_type, operation_mode) @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: From 7ac9ed96b6cb7ca2c2ca0b7c8fd4753072b451fd Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Jun 2026 19:08:12 +0200 Subject: [PATCH 47/61] Fix test assert --- tests/components/plugwise/test_water_heater.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/components/plugwise/test_water_heater.py b/tests/components/plugwise/test_water_heater.py index 1d4550ac6..11bc7af3a 100644 --- a/tests/components/plugwise/test_water_heater.py +++ b/tests/components/plugwise/test_water_heater.py @@ -57,7 +57,9 @@ async def test_adam_water_heater_setpoint_change( blocking=True, ) assert mock_smile_adam_jip.set_dhw_mode.call_count == 1 - mock_smile_adam_jip.set_dhw_mode.assert_called_with("off") + mock_smile_adam_jip.set_dhw_mode.assert_called_with( + "dhw_mode", "e4684553153b44afbef2200885f379dc", 2, "off" + ) @pytest.mark.parametrize("chosen_env", ["anna_v4_dhw"], indirect=True) From ba68dc111e535de66b623e34b4153fedddd86d37 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Jun 2026 19:23:55 +0200 Subject: [PATCH 48/61] Add pragma-no-cover --- custom_components/plugwise/water_heater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index e294a4952..c3854bcdd 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -91,7 +91,7 @@ def operation_list(self) -> list[str]: """Return the list of available operation modes.""" if (op_list := self.device.get("dhw_modes", [])): return op_list - return [STATE_OFF] + return [STATE_OFF] # pragma: no cover @property def target_temperature(self) -> float | None: From 5f59666764ae3b9e35eff82f65f47b50b2adb6d9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 16 Jun 2026 19:54:30 +0200 Subject: [PATCH 49/61] Replace strings by constants --- custom_components/plugwise/const.py | 1 + custom_components/plugwise/water_heater.py | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/custom_components/plugwise/const.py b/custom_components/plugwise/const.py index 611138431..99f66b67b 100644 --- a/custom_components/plugwise/const.py +++ b/custom_components/plugwise/const.py @@ -131,6 +131,7 @@ # Select constants AVAILABLE_SCHEDULES: Final = "available_schedules" +DHW_MODE: Final = "dhw_mode" DHW_MODES: Final = "dhw_modes" GATEWAY_MODES: Final = "gateway_modes" REGULATION_MODES: Final = "regulation_modes" diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index c3854bcdd..a0658dd16 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -16,6 +16,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import ( + DHW_MODE, + DHW_MODES, DHW_SETPOINT, LOGGER, LOWER_BOUND, @@ -46,7 +48,7 @@ def _add_entities() -> None: entities: list[PlugwiseWaterHeaterEntity] = [] for device_id in coordinator.new_devices: device = coordinator.data[device_id] - if device.get("max_dhw_temperature") is not None: + if device.get(MAX_DHW_TEMP) is not None: entities.append(PlugwiseWaterHeaterEntity(coordinator, device_id)) LOGGER.debug("Add %s water_heater", device[ATTR_NAME]) async_add_entities(entities) @@ -70,8 +72,10 @@ def __init__( super().__init__(coordinator, device_id) self._attr_unique_id = f"{device_id}-water_heater" - self._attr_max_temp = self.device.get("max_dhw_temperature", {}).get(UPPER_BOUND, 75.0) - self._attr_min_temp = self.device.get("max_dhw_temperature", {}).get(LOWER_BOUND, 40.0) + max_dhw_temp_bounds = self.device.get(MAX_DHW_TEMP, {}) + if max_dhw_temp_bounds is not None: + self._attr_max_temp = max_dhw_temp_bounds.get(UPPER_BOUND, 75.0) + self._attr_min_temp = max_dhw_temp_bounds.get(LOWER_BOUND, 40.0) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE self._attr_supported_features |= WaterHeaterEntityFeature.TARGET_TEMPERATURE @@ -79,7 +83,7 @@ def __init__( @property def current_operation(self) -> str | None: """Return current readable operation mode.""" - return self.device.get("dhw_mode") + return self.device.get(DHW_MODE) @property def current_temperature(self) -> float | None: @@ -89,7 +93,7 @@ def current_temperature(self) -> float | None: @property def operation_list(self) -> list[str]: """Return the list of available operation modes.""" - if (op_list := self.device.get("dhw_modes", [])): + if (op_list := self.device.get(DHW_MODES, [])): return op_list return [STATE_OFF] # pragma: no cover @@ -97,7 +101,7 @@ def operation_list(self) -> list[str]: def target_temperature(self) -> float | None: """Return the water temperature we try to reach.""" return ( - self.device.get("max_dhw_temperature", {}).get(TARGET_TEMP) + self.device.get(MAX_DHW_TEMP, {}).get(TARGET_TEMP) or self.device.get(SENSORS, {}).get(DHW_SETPOINT) ) @@ -105,7 +109,7 @@ def target_temperature(self) -> float | None: async def async_set_operation_mode(self, operation_mode: str) -> None: """Set the operation mode.""" list_type: int = len(self.operation_list) - await self.coordinator.api.set_dhw_mode("dhw_mode", self._dev_id, list_type, operation_mode) + await self.coordinator.api.set_dhw_mode(DHW_MODE, self._dev_id, list_type, operation_mode) @plugwise_command async def async_set_temperature(self, **kwargs: Any) -> None: From 929bbd93f59559d34c3c5bb864c7c49d66f22356 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 18 Jun 2026 20:21:34 +0200 Subject: [PATCH 50/61] Select: add extra guarding for changing to location --- custom_components/plugwise/select.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/select.py b/custom_components/plugwise/select.py index fa4b0bb49..62b6cf20e 100644 --- a/custom_components/plugwise/select.py +++ b/custom_components/plugwise/select.py @@ -131,7 +131,10 @@ def __init__( self.entity_description = entity_description self._location = device_id - if (location := self.device.get(LOCATION)) is not None: + if ( + self.entity_description.key == SELECT_SCHEDULE + and (location := self.device.get(LOCATION)) is not None + ): self._location = location @property From 15a0b5cda16dcc283b115cb3087edbea5abd5db2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Thu, 18 Jun 2026 20:25:05 +0200 Subject: [PATCH 51/61] Improve variable name, extend docstring --- custom_components/plugwise/select.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/plugwise/select.py b/custom_components/plugwise/select.py index 62b6cf20e..f97af0fe8 100644 --- a/custom_components/plugwise/select.py +++ b/custom_components/plugwise/select.py @@ -130,12 +130,12 @@ def __init__( self._attr_unique_id = f"{device_id}-{entity_description.key}" self.entity_description = entity_description - self._location = device_id + self._device_or_location = device_id if ( self.entity_description.key == SELECT_SCHEDULE and (location := self.device.get(LOCATION)) is not None ): - self._location = location + self._device_or_location = location @property def current_option(self) -> str | None: @@ -151,10 +151,11 @@ def options(self) -> list[str]: async def async_select_option(self, option: str) -> None: """Change to the selected entity option. + Appliance ID (= device_id) is required for the dhw_mode select Location ID and STATE_ON are required for the thermostat-schedule select. """ await self.coordinator.api.set_select( - self.entity_description.key, self._location, option, STATE_ON + self.entity_description.key, self._device_or_location, option, STATE_ON ) LOGGER.debug( "Set %s to %s was successful", From ca9b2516ecb75202461384a29ca51a9f7e01bca0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 19 Jun 2026 07:58:30 +0200 Subject: [PATCH 52/61] Fix device_id in test assert --- tests/components/plugwise/test_select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/plugwise/test_select.py b/tests/components/plugwise/test_select.py index d9a6ef549..162694f55 100644 --- a/tests/components/plugwise/test_select.py +++ b/tests/components/plugwise/test_select.py @@ -92,7 +92,7 @@ async def test_adam_select_regulation_mode( assert mock_smile_adam_heat_cool.set_select.call_count == 1 mock_smile_adam_heat_cool.set_select.assert_called_with( "select_regulation_mode", - "bc93488efab249e5bc54fd7e175a6f91", + "da224107914542988a88561b4452b0f6", "heating", "on", ) From e78ba42049ff13a2b4d844c0d64f48b337d2f1a0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 19 Jun 2026 17:32:53 +0200 Subject: [PATCH 53/61] SELECT_ZONE_PROFILE also requires the location id --- custom_components/plugwise/select.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/select.py b/custom_components/plugwise/select.py index f97af0fe8..8b67fe6f2 100644 --- a/custom_components/plugwise/select.py +++ b/custom_components/plugwise/select.py @@ -132,7 +132,7 @@ def __init__( self._device_or_location = device_id if ( - self.entity_description.key == SELECT_SCHEDULE + self.entity_description.key in (SELECT_SCHEDULE, SELECT_ZONE_PROFILE) and (location := self.device.get(LOCATION)) is not None ): self._device_or_location = location From 00483fd14b2ee59a25dd6c22b45e27c0437c162e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 19 Jun 2026 17:56:01 +0200 Subject: [PATCH 54/61] Add missing eco dhw_mode --- custom_components/plugwise/strings.json | 1 + custom_components/plugwise/translations/en.json | 1 + custom_components/plugwise/translations/nl.json | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/strings.json b/custom_components/plugwise/strings.json index 55dfd187c..b22f65cfc 100644 --- a/custom_components/plugwise/strings.json +++ b/custom_components/plugwise/strings.json @@ -306,6 +306,7 @@ "auto": "Auto", "boost": "Boost", "comfort": "Comfort", + "eco": "Eco", "off": "Off" } } diff --git a/custom_components/plugwise/translations/en.json b/custom_components/plugwise/translations/en.json index 55dfd187c..b22f65cfc 100644 --- a/custom_components/plugwise/translations/en.json +++ b/custom_components/plugwise/translations/en.json @@ -306,6 +306,7 @@ "auto": "Auto", "boost": "Boost", "comfort": "Comfort", + "eco": "Eco", "off": "Off" } } diff --git a/custom_components/plugwise/translations/nl.json b/custom_components/plugwise/translations/nl.json index 3e26a89dc..264efa2f3 100644 --- a/custom_components/plugwise/translations/nl.json +++ b/custom_components/plugwise/translations/nl.json @@ -303,9 +303,10 @@ "state_attributes": { "dhw_mode": { "state": { - "auto": "Automatisch", + "auto": "Auto", "boost": "Boost", "comfort": "Comfort", + "eco": "Eco", "off": "Off" } } From 604db85e3f574bc0d0546811160fc2e199212188 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Fri, 19 Jun 2026 19:11:13 +0200 Subject: [PATCH 55/61] Correct guarding as suggested --- custom_components/plugwise/water_heater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index a0658dd16..c7eb2c172 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -73,7 +73,7 @@ def __init__( self._attr_unique_id = f"{device_id}-water_heater" max_dhw_temp_bounds = self.device.get(MAX_DHW_TEMP, {}) - if max_dhw_temp_bounds is not None: + if max_dhw_temp_bounds: self._attr_max_temp = max_dhw_temp_bounds.get(UPPER_BOUND, 75.0) self._attr_min_temp = max_dhw_temp_bounds.get(LOWER_BOUND, 40.0) self._attr_supported_features = WaterHeaterEntityFeature.OPERATION_MODE From c7b7eb6de7119b75322ad80324f299578d585071 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Jun 2026 10:46:05 +0200 Subject: [PATCH 56/61] Remove strings.json --- custom_components/plugwise/strings.json | 365 ------------------------ 1 file changed, 365 deletions(-) delete mode 100644 custom_components/plugwise/strings.json diff --git a/custom_components/plugwise/strings.json b/custom_components/plugwise/strings.json deleted file mode 100644 index b22f65cfc..000000000 --- a/custom_components/plugwise/strings.json +++ /dev/null @@ -1,365 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Device is already configured", - "anna_with_adam": "Both Anna and Adam detected. Add your Adam instead of your Anna", - "not_the_same_smile": "The provided Smile-ID does not match your device", - "reconfigure_successful": "Reconfiguration was successful" - }, - "error": { - "cannot_connect": "Failed to connect", - "invalid_auth": "Invalid authentication", - "invalid_setup": "Add your Adam instead of your Anna, see the documentation", - "response_error": "Invalid XML-data or error indication received", - "unknown": "Unexpected error", - "unsupported": "Device with unsupported firmware" - }, - "step": { - "reconfigure": { - "data": { - "host": "IP address" - }, - "data_description": { - "host": "The hostname or IP address of your Smile. You can find it in your router or the Plugwise app." - }, - "description": "Update configuration for {title}." - }, - "user": { - "data": { - "host": "IP address", - "password": "Smile-ID", - "port": "Port", - "username": "Smile username" - }, - "data_description": { - "host": "The hostname or IP address of your Smile. You can find it in your router or the Plugwise app.", - "password": "The Smile-ID printed on the label on the back of your Adam, P1, Smile-T, or Stretch.", - "port": "By default your Smile uses port 80, normally you should not have to change this.", - "username": "Default is `smile`, or `stretch` for the legacy Stretch." - }, - "description": "Enter your Plugwise device data: (setup can take up to 90s)", - "title": "Set up Plugwise Adam/Smile/Stretch" - } - } - }, - "entity": { - "binary_sensor": { - "compressor_state": { - "name": "Compressor state" - }, - "cooling_enabled": { - "name": "Cooling enabled" - }, - "cooling_state": { - "name": "Cooling" - }, - "dhw_state": { - "name": "DHW state" - }, - "flame_state": { - "name": "Flame state" - }, - "heating_state": { - "name": "Heating" - }, - "low_battery": { - "name": "Battery status" - }, - "plugwise_notification": { - "name": "Plugwise notification" - }, - "secondary_boiler_state": { - "name": "Secondary boiler state" - } - }, - "button": { - "reboot": { - "name": "Reboot" - } - }, - "climate": { - "plugwise": { - "state_attributes": { - "preset_mode": { - "state": { - "asleep": "Night", - "away": "Away", - "home": "Home", - "no_frost": "Anti-frost", - "vacation": "Vacation" - } - } - } - } - }, - "number": { - "max_dhw_temperature": { - "name": "Domestic hot water setpoint" - }, - "maximum_boiler_temperature": { - "name": "Maximum boiler temperature setpoint" - }, - "temperature_offset": { - "name": "Temperature offset" - } - }, - "select": { - "select_dhw_mode": { - "name": "DHW mode", - "state": { - "comfort": "Comfort", - "off": "Off" - } - }, - "select_gateway_mode": { - "name": "Gateway mode", - "state": { - "away": "Pause", - "full": "Normal", - "vacation": "Vacation" - } - }, - "select_regulation_mode": { - "name": "Regulation mode", - "state": { - "bleeding_cold": "Bleeding cold", - "bleeding_hot": "Bleeding hot", - "cooling": "Cooling", - "heating": "Heating", - "off": "Off" - } - }, - "select_schedule": { - "name": "Thermostat schedule", - "state": { - "off": "Off" - } - }, - "select_zone_profile": { - "name": "Zone profile", - "state": { - "active": "Active", - "off": "Off", - "passive": "Passive" - } - } - }, - "sensor": { - "cooling_setpoint": { - "name": "Cooling setpoint" - }, - "dhw_temperature": { - "name": "DHW temperature" - }, - "domestic_hot_water_setpoint": { - "name": "DHW setpoint" - }, - "electricity_consumed": { - "name": "Electricity consumed" - }, - "electricity_consumed_interval": { - "name": "Electricity consumed interval" - }, - "electricity_consumed_off_peak_cumulative": { - "name": "Electricity consumed off-peak cumulative" - }, - "electricity_consumed_off_peak_interval": { - "name": "Electricity consumed off-peak interval" - }, - "electricity_consumed_off_peak_point": { - "name": "Electricity consumed off-peak point" - }, - "electricity_consumed_peak_cumulative": { - "name": "Electricity consumed peak cumulative" - }, - "electricity_consumed_peak_interval": { - "name": "Electricity consumed peak interval" - }, - "electricity_consumed_peak_point": { - "name": "Electricity consumed peak point" - }, - "electricity_consumed_point": { - "name": "Electricity consumed point" - }, - "electricity_phase_one_consumed": { - "name": "Electricity phase one consumed" - }, - "electricity_phase_one_produced": { - "name": "Electricity phase one produced" - }, - "electricity_phase_three_consumed": { - "name": "Electricity phase three consumed" - }, - "electricity_phase_three_produced": { - "name": "Electricity phase three produced" - }, - "electricity_phase_two_consumed": { - "name": "Electricity phase two consumed" - }, - "electricity_phase_two_produced": { - "name": "Electricity phase two produced" - }, - "electricity_produced": { - "name": "Electricity produced" - }, - "electricity_produced_interval": { - "name": "Electricity produced interval" - }, - "electricity_produced_off_peak_cumulative": { - "name": "Electricity produced off-peak cumulative" - }, - "electricity_produced_off_peak_interval": { - "name": "Electricity produced off-peak interval" - }, - "electricity_produced_off_peak_point": { - "name": "Electricity produced off-peak point" - }, - "electricity_produced_peak_cumulative": { - "name": "Electricity produced peak cumulative" - }, - "electricity_produced_peak_interval": { - "name": "Electricity produced peak interval" - }, - "electricity_produced_peak_point": { - "name": "Electricity produced peak point" - }, - "electricity_produced_point": { - "name": "Electricity produced point" - }, - "gas_consumed_cumulative": { - "name": "Gas consumed cumulative" - }, - "gas_consumed_interval": { - "name": "Gas consumed interval" - }, - "heating_setpoint": { - "name": "Heating setpoint" - }, - "intended_boiler_temperature": { - "name": "Intended boiler temperature" - }, - "maximum_boiler_temperature": { - "name": "Maximum boiler temperature" - }, - "modulation_level": { - "name": "Modulation level" - }, - "net_electricity_cumulative": { - "name": "Net electricity cumulative" - }, - "net_electricity_point": { - "name": "Net electricity point" - }, - "outdoor_air_temperature": { - "name": "Outdoor air temperature" - }, - "outdoor_temperature": { - "name": "Outdoor temperature" - }, - "return_temperature": { - "name": "Return temperature" - }, - "setpoint": { - "name": "Setpoint" - }, - "temperature_difference": { - "name": "Temperature difference" - }, - "valve_position": { - "name": "Valve position" - }, - "voltage_phase_one": { - "name": "Voltage phase one" - }, - "voltage_phase_three": { - "name": "Voltage phase three" - }, - "voltage_phase_two": { - "name": "Voltage phase two" - }, - "water_pressure": { - "name": "Water pressure" - }, - "water_temperature": { - "name": "Water temperature" - } - }, - "switch": { - "cooling_ena_switch": { - "name": "Cooling" - }, - "dhw_cm_switch": { - "name": "DHW comfort mode" - }, - "lock": { - "name": "Lock" - }, - "relay": { - "name": "Relay" - } - }, - "water_heater": { - "plugwise": { - "state_attributes": { - "dhw_mode": { - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "eco": "Eco", - "off": "Off" - } - } - } - } - } - }, - "exceptions": { - "authentication_failed": { - "message": "Invalid authentication" - }, - "data_incomplete_or_missing": { - "message": "Data incomplete or missing" - }, - "error_communicating_with_api": { - "message": "Error communicating with API: {error}" - }, - "failed_to_connect": { - "message": "Failed to connect" - }, - "invalid_setup": { - "message": "Add your Adam instead of your Anna, see the documentation" - }, - "response_error": { - "message": "Invalid XML-data or error indication received" - }, - "set_schedule_first": { - "message": "Failed setting HVACMode, set a schedule first" - }, - "unsupported_firmware": { - "message": "Device with unsupported firmware" - } - }, - "options": { - "step": { - "init": { - "data": { - "cooling_on": "Anna: cooling-mode is on", - "refresh_interval": "Frontend refresh-time (1.5 - 5 seconds) *) beta-only option", - "scan_interval": "Scan interval (seconds) *) beta-only option" - }, - "description": "Adjust Adam/Smile/Stretch options" - }, - "none": { - "description": "This integration does not provide any options", - "title": "No options available" - } - } - }, - "services": { - "delete_notification": { - "description": "Deletes a Plugwise notification", - "name": "Delete Plugwise notification" - } - } -} From 0053a02ad7401e3f85d52257779bc255c104d9e8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Jun 2026 11:01:22 +0200 Subject: [PATCH 57/61] Link to plugwise v1.12.0, set to v0.65.0 release-version --- custom_components/plugwise/manifest.json | 6 ++---- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/custom_components/plugwise/manifest.json b/custom_components/plugwise/manifest.json index 09b0587b1..09f91d310 100644 --- a/custom_components/plugwise/manifest.json +++ b/custom_components/plugwise/manifest.json @@ -7,9 +7,7 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["plugwise"], - "requirements": [ - "plugwise@git+https://github.com/plugwise/python-plugwise.git/@dhw_update" - ], - "version": "0.64.4", + "requirements": ["plugwise==1.12.0"], + "version": "0.65.0", "zeroconf": ["_plugwise._tcp.local."] } diff --git a/pyproject.toml b/pyproject.toml index 7bf136ae4..55a37b2af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "plugwise-beta" -version = "0.64.4" +version = "0.65.0" description = "Plugwise beta custom-component" readme = "README.md" requires-python = ">=3.14" From 266bfa091d15c925254128644311fee4ea9ac9ed Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Jun 2026 11:05:24 +0200 Subject: [PATCH 58/61] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad06f595a..97f359555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ Versions from 0.40 and up -## Ongoing +## v0.65.0 +- Replace DHW_comfort_mode switch by a select or a water_heater, bump plugwise to [v1.12.0](https://github.com/plugwise/python-plugwise/releases/tag/v1.12.0) via PR [#1085](https://github.com/plugwise/plugwise-beta/pull/1085) - Downstream Core updates via PR [#1084](https://github.com/plugwise/plugwise-beta/pull/1084) ## v0.64.4 From 66a790772c426cdb4518bc0b7cb75525826c0da8 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Jun 2026 11:22:19 +0200 Subject: [PATCH 59/61] Line up docstring with the last guarding update --- custom_components/plugwise/select.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/select.py b/custom_components/plugwise/select.py index 8b67fe6f2..5fae768e5 100644 --- a/custom_components/plugwise/select.py +++ b/custom_components/plugwise/select.py @@ -152,7 +152,8 @@ async def async_select_option(self, option: str) -> None: """Change to the selected entity option. Appliance ID (= device_id) is required for the dhw_mode select - Location ID and STATE_ON are required for the thermostat-schedule select. + Locattion ID is required for the thermostat-schedule and zone_profile selects. + STATE_ON is required for the thermostat-schedule select. """ await self.coordinator.api.set_select( self.entity_description.key, self._device_or_location, option, STATE_ON From 294c10e49740d4bbf4b7dd3273f224e5887fdbf7 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Jun 2026 11:36:50 +0200 Subject: [PATCH 60/61] Correct current_temperature determination --- custom_components/plugwise/water_heater.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index c7eb2c172..915dd2ab3 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -19,12 +19,14 @@ DHW_MODE, DHW_MODES, DHW_SETPOINT, + DHW_TEMP, LOGGER, LOWER_BOUND, MAX_DHW_TEMP, SENSORS, TARGET_TEMP, UPPER_BOUND, + WATER_TEMP, ) from .coordinator import PlugwiseConfigEntry, PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity @@ -88,7 +90,9 @@ def current_operation(self) -> str | None: @property def current_temperature(self) -> float | None: """Return the current water temperature.""" - return self.device.get(SENSORS, {}).get("water_temperature") + boiler_temperature = self.device.get(SENSORS, {}).get(WATER_TEMP) + dhw_temperature = self.device.get(SENSORS, {}).get(DHW_TEMP) + return dhw_temperature or boiler_temperature @property def operation_list(self) -> list[str]: From 865697a4d48cc85edaa5f7ce0dcdd24203c7ce29 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Sat, 20 Jun 2026 12:29:53 +0200 Subject: [PATCH 61/61] Update water_heater string formatting --- custom_components/plugwise/translations/en.json | 16 ++++++---------- custom_components/plugwise/translations/nl.json | 16 ++++++---------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/custom_components/plugwise/translations/en.json b/custom_components/plugwise/translations/en.json index b22f65cfc..0015e95e4 100644 --- a/custom_components/plugwise/translations/en.json +++ b/custom_components/plugwise/translations/en.json @@ -300,16 +300,12 @@ }, "water_heater": { "plugwise": { - "state_attributes": { - "dhw_mode": { - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "eco": "Eco", - "off": "Off" - } - } + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "eco": "Eco", + "off": "Off" } } } diff --git a/custom_components/plugwise/translations/nl.json b/custom_components/plugwise/translations/nl.json index 264efa2f3..b76c57143 100644 --- a/custom_components/plugwise/translations/nl.json +++ b/custom_components/plugwise/translations/nl.json @@ -300,16 +300,12 @@ }, "water_heater": { "plugwise": { - "state_attributes": { - "dhw_mode": { - "state": { - "auto": "Auto", - "boost": "Boost", - "comfort": "Comfort", - "eco": "Eco", - "off": "Off" - } - } + "state": { + "auto": "Auto", + "boost": "Boost", + "comfort": "Comfort", + "eco": "Eco", + "off": "Off" } } }