From beb08e08bc95417df5c5cdc0d24514f12eaf0ca2 Mon Sep 17 00:00:00 2001 From: RaHehl <7577984+RaHehl@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:52:01 +0000 Subject: [PATCH 1/2] feat(atw): add extended properties for Ecodan (PUD-SHWM120YAA) - Add system/zone temperatures, compressor, component status, daily energy - Fix return_temperature_boiler reading wrong API field - Fix PROPERTY_ZONE_*_COOL_FLOW string values with backwards compat - Deprecate Zone.flow/return_temperature --- src/pymelcloud/atw_device.py | 190 ++++++++++++++++++++++-- tests/samples/atw_2zone_get.json | 3 +- tests/samples/atw_2zone_listdevice.json | 8 +- tests/test_atw_properties.py | 157 +++++++++++++++++++- 4 files changed, 340 insertions(+), 18 deletions(-) diff --git a/src/pymelcloud/atw_device.py b/src/pymelcloud/atw_device.py index f4a2fd0..91817b4 100644 --- a/src/pymelcloud/atw_device.py +++ b/src/pymelcloud/atw_device.py @@ -1,4 +1,6 @@ """Air-To-Water (DeviceType=1) device definition.""" + +import warnings from typing import Any, Callable, Dict, List, Optional from pymelcloud.device import EFFECTIVE_FLAGS, Device @@ -9,11 +11,15 @@ PROPERTY_ZONE_2_TARGET_TEMPERATURE = "zone_2_target_temperature" PROPERTY_ZONE_1_TARGET_HEAT_FLOW_TEMPERATURE = "zone_1_target_heat_flow_temperature" PROPERTY_ZONE_2_TARGET_HEAT_FLOW_TEMPERATURE = "zone_2_target_heat_flow_temperature" -PROPERTY_ZONE_1_TARGET_COOL_FLOW_TEMPERATURE = "zone_1_target_heat_cool_temperature" -PROPERTY_ZONE_2_TARGET_COOL_FLOW_TEMPERATURE = "zone_2_target_heat_cool_temperature" +PROPERTY_ZONE_1_TARGET_COOL_FLOW_TEMPERATURE = "zone_1_target_cool_flow_temperature" +PROPERTY_ZONE_2_TARGET_COOL_FLOW_TEMPERATURE = "zone_2_target_cool_flow_temperature" PROPERTY_ZONE_1_OPERATION_MODE = "zone_1_operation_mode" PROPERTY_ZONE_2_OPERATION_MODE = "zone_2_operation_mode" +# Deprecated aliases for backwards compatibility +PROPERTY_ZONE_1_TARGET_HEAT_COOL_TEMPERATURE = "zone_1_target_heat_cool_temperature" +PROPERTY_ZONE_2_TARGET_HEAT_COOL_TEMPERATURE = "zone_2_target_heat_cool_temperature" + OPERATION_MODE_AUTO = "auto" OPERATION_MODE_FORCE_HOT_WATER = "force_hot_water" @@ -155,22 +161,60 @@ async def set_target_temperature(self, target_temperature): await self._device.set({prop: target_temperature}) @property - def flow_temperature(self) -> float: + def flow_temperature(self) -> Optional[float]: """Return current flow temperature. - This value is not available in the standard state poll response. The poll - update frequency can be a little bit lower that expected. + .. deprecated:: + Use `device.flow_temperature` or `zone.zone_flow_temperature` instead. """ - return self._device_conf()["Device"]["FlowTemperature"] + warnings.warn( + "Zone.flow_temperature is deprecated, use device.flow_temperature " + "or zone.zone_flow_temperature instead", + DeprecationWarning, + stacklevel=2, + ) + result: Optional[float] = ( + self._device_conf().get("Device", {}).get("FlowTemperature") + ) + return result @property - def return_temperature(self) -> float: - """Return current return flow temperature. + def return_temperature(self) -> Optional[float]: + """Return current return temperature. - This value is not available in the standard state poll response. The poll - update frequency can be a little bit lower that expected. + .. deprecated:: + Use `device.return_temperature` or `zone.zone_return_temperature` instead. """ - return self._device_conf()["Device"]["ReturnTemperature"] + warnings.warn( + "Zone.return_temperature is deprecated, use device.return_temperature " + "or zone.zone_return_temperature instead", + DeprecationWarning, + stacklevel=2, + ) + result: Optional[float] = ( + self._device_conf().get("Device", {}).get("ReturnTemperature") + ) + return result + + @property + def zone_flow_temperature(self) -> Optional[float]: + """Return current zone-specific flow temperature.""" + result: Optional[float] = ( + self._device_conf() + .get("Device", {}) + .get(f"FlowTemperatureZone{self.zone_index}") + ) + return result + + @property + def zone_return_temperature(self) -> Optional[float]: + """Return current zone-specific return temperature.""" + result: Optional[float] = ( + self._device_conf() + .get("Device", {}) + .get(f"ReturnTemperatureZone{self.zone_index}") + ) + return result @property def target_flow_temperature(self) -> Optional[float]: @@ -309,13 +353,19 @@ def apply_write(self, state: Dict[str, Any], key: str, value: Any): elif key == PROPERTY_ZONE_1_TARGET_HEAT_FLOW_TEMPERATURE: state["SetHeatFlowTemperatureZone1"] = self.round_temperature(value) flags |= 0x1000000000000 - elif key == PROPERTY_ZONE_1_TARGET_COOL_FLOW_TEMPERATURE: + elif key in ( + PROPERTY_ZONE_1_TARGET_COOL_FLOW_TEMPERATURE, + "zone_1_target_heat_cool_temperature", + ): state["SetCoolFlowTemperatureZone1"] = self.round_temperature(value) flags |= 0x1000000000000 elif key == PROPERTY_ZONE_2_TARGET_HEAT_FLOW_TEMPERATURE: state["SetHeatFlowTemperatureZone2"] = self.round_temperature(value) flags |= 0x1000000000000 - elif key == PROPERTY_ZONE_2_TARGET_COOL_FLOW_TEMPERATURE: + elif key in ( + PROPERTY_ZONE_2_TARGET_COOL_FLOW_TEMPERATURE, + "zone_2_target_heat_cool_temperature", + ): state["SetCoolFlowTemperatureZone2"] = self.round_temperature(value) flags |= 0x1000000000000 elif key == PROPERTY_ZONE_1_OPERATION_MODE: @@ -366,6 +416,16 @@ def outside_temperature(self) -> Optional[float]: """ return self.get_state_prop("OutdoorTemperature") + @property + def flow_temperature(self) -> Optional[float]: + """Return current flow temperature of the entire system.""" + return self.get_device_prop("FlowTemperature") + + @property + def return_temperature(self) -> Optional[float]: + """Return current return temperature of the entire system.""" + return self.get_device_prop("ReturnTemperature") + @property def flow_temperature_boiler(self) -> Optional[float]: """Return flow temperature of the boiler.""" @@ -373,14 +433,114 @@ def flow_temperature_boiler(self) -> Optional[float]: @property def return_temperature_boiler(self) -> Optional[float]: - """Return flow temperature of the boiler.""" - return self.get_device_prop("FlowTemperatureBoiler") + """Return return temperature of the boiler.""" + return self.get_device_prop("ReturnTemperatureBoiler") @property def mixing_tank_temperature(self) -> Optional[float]: """Return mixing tank temperature.""" return self.get_device_prop("MixingTankWaterTemperature") + @property + def condensing_temperature(self) -> Optional[float]: + """Return condensing temperature.""" + return self.get_device_prop("CondensingTemperature") + + @property + def heat_pump_frequency(self) -> Optional[int]: + """Return current heat pump compressor frequency in Hz.""" + return self.get_device_prop("HeatPumpFrequency") + + @property + def demand_percentage(self) -> Optional[int]: + """Return demand percentage (0-100).""" + return self.get_state_prop("DemandPercentage") + + @property + def boiler_status(self) -> Optional[bool]: + """Return boiler status.""" + return self.get_device_prop("BoilerStatus") + + @property + def booster_heater1_status(self) -> Optional[bool]: + """Return booster heater 1 status.""" + return self.get_device_prop("BoosterHeater1Status") + + @property + def booster_heater2_status(self) -> Optional[bool]: + """Return booster heater 2 status.""" + return self.get_device_prop("BoosterHeater2Status") + + @property + def booster_heater2plus_status(self) -> Optional[bool]: + """Return booster heater 2+ status.""" + return self.get_device_prop("BoosterHeater2PlusStatus") + + @property + def immersion_heater_status(self) -> Optional[bool]: + """Return immersion heater status.""" + return self.get_device_prop("ImmersionHeaterStatus") + + @property + def water_pump1_status(self) -> Optional[bool]: + """Return water pump 1 status.""" + return self.get_device_prop("WaterPump1Status") + + @property + def water_pump2_status(self) -> Optional[bool]: + """Return water pump 2 status.""" + return self.get_device_prop("WaterPump2Status") + + @property + def water_pump3_status(self) -> Optional[bool]: + """Return water pump 3 status.""" + return self.get_device_prop("WaterPump3Status") + + @property + def water_pump4_status(self) -> Optional[bool]: + """Return water pump 4 status.""" + return self.get_device_prop("WaterPump4Status") + + @property + def valve_3way_status(self) -> Optional[bool]: + """Return 3-way valve status.""" + return self.get_device_prop("ValveStatus3Way") + + @property + def valve_2way_status(self) -> Optional[bool]: + """Return 2-way valve status.""" + return self.get_device_prop("ValveStatus2Way") + + @property + def daily_heating_energy_consumed(self) -> Optional[float]: + """Return today's heating energy consumed in kWh.""" + return self.get_device_prop("DailyHeatingEnergyConsumed") + + @property + def daily_cooling_energy_consumed(self) -> Optional[float]: + """Return today's cooling energy consumed in kWh.""" + return self.get_device_prop("DailyCoolingEnergyConsumed") + + @property + def daily_hot_water_energy_consumed(self) -> Optional[float]: + """Return today's hot water energy consumed in kWh.""" + return self.get_device_prop("DailyHotWaterEnergyConsumed") + + @property + def daily_heating_energy_produced(self) -> Optional[float]: + """Return today's heating energy produced in kWh.""" + return self.get_device_prop("DailyHeatingEnergyProduced") + + @property + def daily_cooling_energy_produced(self) -> Optional[float]: + """Return today's cooling energy produced in kWh.""" + return self.get_device_prop("DailyCoolingEnergyProduced") + + @property + def daily_hot_water_energy_produced(self) -> Optional[float]: + """Return today's hot water energy produced in kWh.""" + return self.get_device_prop("DailyHotWaterEnergyProduced") + @property def zones(self) -> Optional[List[Zone]]: """Return zones controlled by this device. diff --git a/tests/samples/atw_2zone_get.json b/tests/samples/atw_2zone_get.json index c4f8ee3..a197eb3 100644 --- a/tests/samples/atw_2zone_get.json +++ b/tests/samples/atw_2zone_get.json @@ -1,6 +1,7 @@ { "EffectiveFlags": 0, "LocalIPAddress": null, + "DemandPercentage": 75, "SetTemperatureZone1": 19.5, "SetTemperatureZone2": 18, "RoomTemperatureZone1": 20.5, @@ -40,4 +41,4 @@ "Offline": false, "Scene": null, "SceneOwner": null -} \ No newline at end of file +} diff --git a/tests/samples/atw_2zone_listdevice.json b/tests/samples/atw_2zone_listdevice.json index 473f791..9b61c06 100644 --- a/tests/samples/atw_2zone_listdevice.json +++ b/tests/samples/atw_2zone_listdevice.json @@ -159,6 +159,12 @@ "Zone2Master": false, "DailyEnergyConsumedDate": "2020-01-01T00:00:00", "DailyEnergyProducedDate": "2020-01-01T00:00:00", + "DailyHeatingEnergyConsumed": 5.2, + "DailyCoolingEnergyConsumed": 0.0, + "DailyHotWaterEnergyConsumed": 3.1, + "DailyHeatingEnergyProduced": 18.5, + "DailyCoolingEnergyProduced": 0.0, + "DailyHotWaterEnergyProduced": 11.0, "CurrentEnergyConsumed": 1, "CurrentEnergyProduced": 5, "CurrentEnergyMode": null, @@ -284,4 +290,4 @@ "CanSetFlowTemperature": true, "CanSetTemperatureIncrementOverride": true } -} \ No newline at end of file +} diff --git a/tests/test_atw_properties.py b/tests/test_atw_properties.py index 6ae3a0a..9a674d6 100644 --- a/tests/test_atw_properties.py +++ b/tests/test_atw_properties.py @@ -1,7 +1,7 @@ """Ecodan tests.""" + import pytest -import src.pymelcloud from src.pymelcloud import DEVICE_TYPE_ATW from src.pymelcloud.atw_device import ( OPERATION_MODE_AUTO, @@ -19,8 +19,14 @@ ZONE_STATUS_UNKNOWN, AtwDevice, ) + from .util import build_device +pytestmark = [ + pytest.mark.filterwarnings("ignore:Zone.flow_temperature:DeprecationWarning"), + pytest.mark.filterwarnings("ignore:Zone.return_temperature:DeprecationWarning"), +] + def _build_device(device_conf_name: str, device_state_name: str) -> AtwDevice: device_conf, client = build_device(device_conf_name, device_state_name) @@ -45,15 +51,38 @@ async def test_1zone(): assert device.target_tank_temperature is None assert device.target_tank_temperature_min == 40 assert device.target_tank_temperature_max == 60 + assert device.flow_temperature == 57.5 + assert device.return_temperature == 53.0 assert device.flow_temperature_boiler == 25 assert device.return_temperature_boiler == 25 assert device.mixing_tank_temperature == 0 + assert device.condensing_temperature == 0 + assert device.heat_pump_frequency == 50 + assert device.boiler_status is False + assert device.booster_heater1_status is False + assert device.booster_heater2_status is False + assert device.booster_heater2plus_status is False + assert device.immersion_heater_status is False + assert device.water_pump1_status is True + assert device.water_pump2_status is True + assert device.water_pump3_status is False + assert device.water_pump4_status is False + assert device.valve_3way_status is False + assert device.valve_2way_status is True + assert device.demand_percentage is None + assert device.daily_heating_energy_consumed is None + assert device.daily_cooling_energy_consumed is None + assert device.daily_hot_water_energy_consumed is None + assert device.daily_heating_energy_produced is None + assert device.daily_cooling_energy_produced is None + assert device.daily_hot_water_energy_produced is None assert device.holiday_mode is None assert device.wifi_signal == -73 assert device.has_error is False assert device.error_code is None zones = device.zones + assert zones is not None assert len(zones) == 1 assert zones[0].name == "Zone 1" @@ -62,6 +91,8 @@ async def test_1zone(): assert zones[0].target_temperature is None assert zones[0].flow_temperature == 57.5 assert zones[0].return_temperature == 53.0 + assert zones[0].zone_flow_temperature == 25.0 + assert zones[0].zone_return_temperature == 25.0 assert zones[0].target_flow_temperature is None assert zones[0].operation_mode is None assert zones[0].operation_modes == [ @@ -77,9 +108,15 @@ async def test_1zone(): assert device.status == STATUS_HEAT_ZONES assert device.tank_temperature == 52.0 assert device.target_tank_temperature == 50.0 + assert device.flow_temperature == 57.5 + assert device.return_temperature == 53.0 assert device.flow_temperature_boiler == 25 assert device.return_temperature_boiler == 25 assert device.mixing_tank_temperature == 0 + assert device.condensing_temperature == 0 + assert device.heat_pump_frequency == 50 + assert device.boiler_status is False + assert device.water_pump1_status is True assert device.holiday_mode is False assert device.wifi_signal == -73 assert device.has_error is False @@ -87,6 +124,10 @@ async def test_1zone(): assert zones[0].room_temperature == 27.0 assert zones[0].target_temperature == 30 + assert zones[0].flow_temperature == 57.5 + assert zones[0].return_temperature == 53.0 + assert zones[0].zone_flow_temperature == 25.0 + assert zones[0].zone_return_temperature == 25.0 assert zones[0].target_flow_temperature == 60.0 assert zones[0].operation_mode == ZONE_OPERATION_MODE_HEAT_FLOW assert zones[0].operation_modes == [ @@ -115,15 +156,38 @@ async def test_2zone(): assert device.target_tank_temperature is None assert device.target_tank_temperature_min == 40 assert device.target_tank_temperature_max == 60 + assert device.flow_temperature == 36.0 + assert device.return_temperature == 30.0 assert device.flow_temperature_boiler == 25 assert device.return_temperature_boiler == 25 assert device.mixing_tank_temperature == 0 + assert device.condensing_temperature == 0 + assert device.heat_pump_frequency == 30 + assert device.boiler_status is False + assert device.booster_heater1_status is False + assert device.booster_heater2_status is False + assert device.booster_heater2plus_status is False + assert device.immersion_heater_status is False + assert device.water_pump1_status is True + assert device.water_pump2_status is True + assert device.water_pump3_status is False + assert device.water_pump4_status is False + assert device.valve_3way_status is False + assert device.valve_2way_status is False + assert device.demand_percentage is None + assert device.daily_heating_energy_consumed == 5.2 + assert device.daily_cooling_energy_consumed == 0.0 + assert device.daily_hot_water_energy_consumed == 3.1 + assert device.daily_heating_energy_produced == 18.5 + assert device.daily_cooling_energy_produced == 0.0 + assert device.daily_hot_water_energy_produced == 11.0 assert device.holiday_mode is None assert device.wifi_signal == -37 assert device.has_error is False assert device.error_code is None zones = device.zones + assert zones is not None assert len(zones) == 2 assert zones[0].name == "Downstairs" @@ -132,6 +196,8 @@ async def test_2zone(): assert zones[0].target_temperature is None assert zones[0].flow_temperature == 36.0 assert zones[0].return_temperature == 30.0 + assert zones[0].zone_flow_temperature == 25.0 + assert zones[0].zone_return_temperature == 25.0 assert zones[0].target_flow_temperature is None assert zones[0].operation_mode is None assert zones[0].operation_modes == [ @@ -147,6 +213,8 @@ async def test_2zone(): assert zones[1].target_temperature is None assert zones[1].flow_temperature == 36.0 assert zones[1].return_temperature == 30.0 + assert zones[1].zone_flow_temperature == 25.0 + assert zones[1].zone_return_temperature == 25.0 assert zones[1].target_flow_temperature is None assert zones[1].operation_mode is None assert zones[1].operation_modes == [ @@ -162,9 +230,17 @@ async def test_2zone(): assert device.status == STATUS_HEAT_ZONES assert device.tank_temperature == 49.5 assert device.target_tank_temperature == 50.0 + assert device.flow_temperature == 36.0 + assert device.return_temperature == 30.0 assert device.flow_temperature_boiler == 25 assert device.return_temperature_boiler == 25 assert device.mixing_tank_temperature == 0 + assert device.demand_percentage == 75 + assert device.boiler_status is False + assert device.water_pump1_status is True + assert device.valve_3way_status is False + assert device.daily_heating_energy_consumed == 5.2 + assert device.daily_hot_water_energy_produced == 11.0 assert device.holiday_mode is False assert device.wifi_signal == -37 assert device.has_error is False @@ -172,6 +248,10 @@ async def test_2zone(): assert zones[0].room_temperature == 20.5 assert zones[0].target_temperature == 19.5 + assert zones[0].flow_temperature == 36.0 + assert zones[0].return_temperature == 30.0 + assert zones[0].zone_flow_temperature == 25.0 + assert zones[0].zone_return_temperature == 25.0 assert zones[0].target_flow_temperature == 25.0 assert zones[0].operation_mode == ZONE_OPERATION_MODE_HEAT_THERMOSTAT assert zones[0].operation_modes == [ @@ -183,6 +263,10 @@ async def test_2zone(): assert zones[1].room_temperature == 19.5 assert zones[1].target_temperature == 18 + assert zones[1].flow_temperature == 36.0 + assert zones[1].return_temperature == 30.0 + assert zones[1].zone_flow_temperature == 25.0 + assert zones[1].zone_return_temperature == 25.0 assert zones[1].target_flow_temperature == 25.0 assert zones[1].operation_mode == ZONE_OPERATION_MODE_HEAT_THERMOSTAT assert zones[1].operation_modes == [ @@ -213,15 +297,38 @@ async def test_2zone_cancool(): assert device.target_tank_temperature is None assert device.target_tank_temperature_min == 40 assert device.target_tank_temperature_max == 60 + assert device.flow_temperature == 50.5 + assert device.return_temperature == 50.5 assert device.flow_temperature_boiler == 25 assert device.return_temperature_boiler == 25 assert device.mixing_tank_temperature is None + assert device.condensing_temperature is None + assert device.heat_pump_frequency == 0 + assert device.boiler_status is False + assert device.booster_heater1_status is False + assert device.booster_heater2_status is False + assert device.booster_heater2plus_status is False + assert device.immersion_heater_status is False + assert device.water_pump1_status is True + assert device.water_pump2_status is False + assert device.water_pump3_status is False + assert device.water_pump4_status is True + assert device.valve_3way_status is True + assert device.valve_2way_status is False + assert device.demand_percentage is None + assert device.daily_heating_energy_consumed is None + assert device.daily_cooling_energy_consumed is None + assert device.daily_hot_water_energy_consumed is None + assert device.daily_heating_energy_produced is None + assert device.daily_cooling_energy_produced is None + assert device.daily_hot_water_energy_produced is None assert device.holiday_mode is None assert device.wifi_signal == -82 assert device.has_error is False assert device.error_code is None zones = device.zones + assert zones is not None assert len(zones) == 2 assert zones[0].name == "Zone 1" @@ -230,6 +337,8 @@ async def test_2zone_cancool(): assert zones[0].target_temperature is None assert zones[0].flow_temperature == 50.5 assert zones[0].return_temperature == 50.5 + assert zones[0].zone_flow_temperature == 22.0 + assert zones[0].zone_return_temperature == 21.0 assert zones[0].target_flow_temperature is None assert zones[0].operation_mode is None assert zones[0].operation_modes == [ @@ -247,6 +356,8 @@ async def test_2zone_cancool(): assert zones[1].target_temperature is None assert zones[1].flow_temperature == 50.5 assert zones[1].return_temperature == 50.5 + assert zones[1].zone_flow_temperature == 21.0 + assert zones[1].zone_return_temperature == 21.0 assert zones[1].target_flow_temperature is None assert zones[1].operation_mode is None assert zones[1].operation_modes == [ @@ -264,9 +375,16 @@ async def test_2zone_cancool(): assert device.status == STATUS_HEAT_WATER assert device.tank_temperature == 47.5 assert device.target_tank_temperature == 52.0 + assert device.flow_temperature == 50.5 + assert device.return_temperature == 50.5 assert device.flow_temperature_boiler == 25 assert device.return_temperature_boiler == 25 assert device.mixing_tank_temperature is None + assert device.condensing_temperature is None + assert device.heat_pump_frequency == 0 + assert device.boiler_status is False + assert device.water_pump1_status is True + assert device.valve_3way_status is True assert device.holiday_mode is False assert device.wifi_signal == -82 assert device.has_error is False @@ -274,6 +392,10 @@ async def test_2zone_cancool(): assert zones[0].room_temperature == 21.5 assert zones[0].target_temperature == 20.5 + assert zones[0].flow_temperature == 50.5 + assert zones[0].return_temperature == 50.5 + assert zones[0].zone_flow_temperature == 22.0 + assert zones[0].zone_return_temperature == 21.0 assert zones[0].target_flow_temperature == 5.0 assert zones[0].operation_mode == ZONE_OPERATION_MODE_CURVE assert zones[0].operation_modes == [ @@ -287,6 +409,10 @@ async def test_2zone_cancool(): assert zones[1].room_temperature == 21.0 assert zones[1].target_temperature == 21.0 + assert zones[1].flow_temperature == 50.5 + assert zones[1].return_temperature == 50.5 + assert zones[1].zone_flow_temperature == 21.0 + assert zones[1].zone_return_temperature == 21.0 assert zones[1].target_flow_temperature == 5.0 assert zones[1].operation_mode == ZONE_OPERATION_MODE_CURVE assert zones[1].operation_modes == [ @@ -297,3 +423,32 @@ async def test_2zone_cancool(): ZONE_OPERATION_MODE_COOL_FLOW, ] assert zones[1].status == ZONE_STATUS_IDLE + + +@pytest.mark.asyncio +async def test_apply_write_old_cool_flow_property_strings(): + device = _build_device( + "atw_2zone_cancool_listdevice.json", "atw_2zone_cancool_get.json" + ) + await device.update() + + state = {"EffectiveFlags": 0} + device.apply_write(state, "zone_1_target_heat_cool_temperature", 20.0) + assert state["SetCoolFlowTemperatureZone1"] == 20.0 + + state = {"EffectiveFlags": 0} + device.apply_write(state, "zone_2_target_heat_cool_temperature", 21.0) + assert state["SetCoolFlowTemperatureZone2"] == 21.0 + + +@pytest.mark.asyncio +async def test_zone_flow_return_temperature_deprecation(): + device = _build_device("atw_1zone_listdevice.json", "atw_1zone_get.json") + zones = device.zones + assert zones is not None + + with pytest.deprecated_call(): + _ = zones[0].flow_temperature + + with pytest.deprecated_call(): + _ = zones[0].return_temperature From 52bbbe57e875b5a390400c90bb08dbe8cabec963 Mon Sep 17 00:00:00 2001 From: RaHehl <7577984+RaHehl@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:05:24 +0000 Subject: [PATCH 2/2] fix: address PR review comments - Use PROPERTY_ZONE_*_HEAT_COOL_TEMPERATURE constants in apply_write - Fix docstring grammar for return_temperature_boiler - Extend test to cover both new and deprecated property strings --- src/pymelcloud/atw_device.py | 6 +++--- tests/test_atw_properties.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/pymelcloud/atw_device.py b/src/pymelcloud/atw_device.py index 91817b4..424feb3 100644 --- a/src/pymelcloud/atw_device.py +++ b/src/pymelcloud/atw_device.py @@ -355,7 +355,7 @@ def apply_write(self, state: Dict[str, Any], key: str, value: Any): flags |= 0x1000000000000 elif key in ( PROPERTY_ZONE_1_TARGET_COOL_FLOW_TEMPERATURE, - "zone_1_target_heat_cool_temperature", + PROPERTY_ZONE_1_TARGET_HEAT_COOL_TEMPERATURE, ): state["SetCoolFlowTemperatureZone1"] = self.round_temperature(value) flags |= 0x1000000000000 @@ -364,7 +364,7 @@ def apply_write(self, state: Dict[str, Any], key: str, value: Any): flags |= 0x1000000000000 elif key in ( PROPERTY_ZONE_2_TARGET_COOL_FLOW_TEMPERATURE, - "zone_2_target_heat_cool_temperature", + PROPERTY_ZONE_2_TARGET_HEAT_COOL_TEMPERATURE, ): state["SetCoolFlowTemperatureZone2"] = self.round_temperature(value) flags |= 0x1000000000000 @@ -433,7 +433,7 @@ def flow_temperature_boiler(self) -> Optional[float]: @property def return_temperature_boiler(self) -> Optional[float]: - """Return return temperature of the boiler.""" + """Return the boiler return temperature.""" return self.get_device_prop("ReturnTemperatureBoiler") @property diff --git a/tests/test_atw_properties.py b/tests/test_atw_properties.py index 9a674d6..8e66dd2 100644 --- a/tests/test_atw_properties.py +++ b/tests/test_atw_properties.py @@ -426,12 +426,22 @@ async def test_2zone_cancool(): @pytest.mark.asyncio -async def test_apply_write_old_cool_flow_property_strings(): +async def test_apply_write_cool_flow_property_strings(): device = _build_device( "atw_2zone_cancool_listdevice.json", "atw_2zone_cancool_get.json" ) await device.update() + # New property strings + state = {"EffectiveFlags": 0} + device.apply_write(state, "zone_1_target_cool_flow_temperature", 20.0) + assert state["SetCoolFlowTemperatureZone1"] == 20.0 + + state = {"EffectiveFlags": 0} + device.apply_write(state, "zone_2_target_cool_flow_temperature", 21.0) + assert state["SetCoolFlowTemperatureZone2"] == 21.0 + + # Deprecated property strings (backwards compat) state = {"EffectiveFlags": 0} device.apply_write(state, "zone_1_target_heat_cool_temperature", 20.0) assert state["SetCoolFlowTemperatureZone1"] == 20.0