From 77282ed4b021f84f70d6e4c6731d7ab34426c370 Mon Sep 17 00:00:00 2001 From: fustom Date: Tue, 23 Jul 2024 12:30:06 +0200 Subject: [PATCH] Use external temp if needed in Broadlink (#118375) * Use external temp for current temp depends on the sensor state * Add SensorMode enum * Add tests for Broadlink climate * Check is the sensor included in the data * Use IntEnum as parent of SensorMode * Use SensorMode enum value for sensor test data * Parametrizing tests * Readd accidentally removed assert * Use local sensor variable Co-authored-by: Robert Resch * Refactor test_climate. Check call_counts. * Add parameter types Co-authored-by: Robert Resch * Update homeassistant/components/broadlink/climate.py --------- Co-authored-by: Robert Resch Co-authored-by: Joost Lekkerkerker --- homeassistant/components/broadlink/climate.py | 22 ++- tests/components/broadlink/test_climate.py | 180 ++++++++++++++++++ 2 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 tests/components/broadlink/test_climate.py diff --git a/homeassistant/components/broadlink/climate.py b/homeassistant/components/broadlink/climate.py index 0573c342490..dbfd982795c 100644 --- a/homeassistant/components/broadlink/climate.py +++ b/homeassistant/components/broadlink/climate.py @@ -1,5 +1,6 @@ """Support for Broadlink climate devices.""" +from enum import IntEnum from typing import Any from homeassistant.components.climate import ( @@ -19,6 +20,14 @@ from .device import BroadlinkDevice from .entity import BroadlinkEntity +class SensorMode(IntEnum): + """Thermostat sensor modes.""" + + INNER_SENSOR_CONTROL = 0 + OUTER_SENSOR_CONTROL = 1 + INNER_SENSOR_CONTROL_OUTER_LIMIT = 2 + + async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, @@ -50,6 +59,7 @@ class BroadlinkThermostat(BroadlinkEntity, ClimateEntity): super().__init__(device) self._attr_unique_id = device.unique_id self._attr_hvac_mode = None + self.sensor_mode = SensorMode.INNER_SENSOR_CONTROL async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" @@ -61,6 +71,8 @@ class BroadlinkThermostat(BroadlinkEntity, ClimateEntity): @callback def _update_state(self, data: dict[str, Any]) -> None: """Update data.""" + if (sensor := data.get("sensor")) is not None: + self.sensor_mode = SensorMode(sensor) if data.get("power"): if data.get("auto_mode"): self._attr_hvac_mode = HVACMode.AUTO @@ -74,8 +86,10 @@ class BroadlinkThermostat(BroadlinkEntity, ClimateEntity): else: self._attr_hvac_mode = HVACMode.OFF self._attr_hvac_action = HVACAction.OFF - - self._attr_current_temperature = data.get("room_temp") + if self.sensor_mode is SensorMode.OUTER_SENSOR_CONTROL: + self._attr_current_temperature = data.get("external_temp") + else: + self._attr_current_temperature = data.get("room_temp") self._attr_target_temperature = data.get("thermostat_temp") async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: @@ -85,7 +99,9 @@ class BroadlinkThermostat(BroadlinkEntity, ClimateEntity): else: await self._device.async_request(self._device.api.set_power, 1) mode = 0 if hvac_mode == HVACMode.HEAT else 1 - await self._device.async_request(self._device.api.set_mode, mode, 0) + await self._device.async_request( + self._device.api.set_mode, mode, 0, self.sensor_mode.value + ) self._attr_hvac_mode = hvac_mode self.async_write_ha_state() diff --git a/tests/components/broadlink/test_climate.py b/tests/components/broadlink/test_climate.py new file mode 100644 index 00000000000..6b39d1895b1 --- /dev/null +++ b/tests/components/broadlink/test_climate.py @@ -0,0 +1,180 @@ +"""Tests for Broadlink climate.""" + +from typing import Any + +import pytest + +from homeassistant.components.broadlink.climate import SensorMode +from homeassistant.components.broadlink.const import DOMAIN +from homeassistant.components.climate import ( + ATTR_TEMPERATURE, + DOMAIN as CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + HVACAction, + HVACMode, +) +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity_component import async_update_entity + +from . import get_device + + +@pytest.mark.parametrize( + ( + "api_return_value", + "expected_state", + "expected_current_temperature", + "expected_temperature", + "expected_hvac_action", + ), + [ + ( + { + "sensor": SensorMode.INNER_SENSOR_CONTROL.value, + "power": 1, + "auto_mode": 0, + "active": 1, + "room_temp": 22, + "thermostat_temp": 23, + "external_temp": 30, + }, + HVACMode.HEAT, + 22, + 23, + HVACAction.HEATING, + ), + ( + { + "sensor": SensorMode.OUTER_SENSOR_CONTROL.value, + "power": 1, + "auto_mode": 1, + "active": 0, + "room_temp": 22, + "thermostat_temp": 23, + "external_temp": 30, + }, + HVACMode.AUTO, + 30, + 23, + HVACAction.IDLE, + ), + ( + { + "sensor": SensorMode.INNER_SENSOR_CONTROL.value, + "power": 0, + "auto_mode": 0, + "active": 0, + "room_temp": 22, + "thermostat_temp": 23, + "external_temp": 30, + }, + HVACMode.OFF, + 22, + 23, + HVACAction.OFF, + ), + ], +) +async def test_climate( + api_return_value: dict[str, Any], + expected_state: HVACMode, + expected_current_temperature: int, + expected_temperature: int, + expected_hvac_action: HVACAction, + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test Broadlink climate.""" + + device = get_device("Guest room") + mock_setup = await device.setup_entry(hass) + + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, mock_setup.entry.unique_id)} + ) + entries = er.async_entries_for_device(entity_registry, device_entry.id) + climates = [entry for entry in entries if entry.domain == Platform.CLIMATE] + assert len(climates) == 1 + + climate = climates[0] + + mock_setup.api.get_full_status.return_value = api_return_value + + await async_update_entity(hass, climate.entity_id) + assert mock_setup.api.get_full_status.call_count == 2 + state = hass.states.get(climate.entity_id) + assert state.state == expected_state + assert state.attributes["current_temperature"] == expected_current_temperature + assert state.attributes["temperature"] == expected_temperature + assert state.attributes["hvac_action"] == expected_hvac_action + + +async def test_climate_set_temperature_turn_off_turn_on( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test Broadlink climate.""" + + device = get_device("Guest room") + mock_setup = await device.setup_entry(hass) + + device_entry = device_registry.async_get_device( + identifiers={(DOMAIN, mock_setup.entry.unique_id)} + ) + entries = er.async_entries_for_device(entity_registry, device_entry.id) + climates = [entry for entry in entries if entry.domain == Platform.CLIMATE] + assert len(climates) == 1 + + climate = climates[0] + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: climate.entity_id, + ATTR_TEMPERATURE: "24", + }, + blocking=True, + ) + state = hass.states.get(climate.entity_id) + + assert mock_setup.api.set_temp.call_count == 1 + assert mock_setup.api.set_power.call_count == 0 + assert mock_setup.api.set_mode.call_count == 0 + assert state.attributes["temperature"] == 24 + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: climate.entity_id, + }, + blocking=True, + ) + state = hass.states.get(climate.entity_id) + + assert mock_setup.api.set_temp.call_count == 1 + assert mock_setup.api.set_power.call_count == 1 + assert mock_setup.api.set_mode.call_count == 0 + assert state.state == HVACMode.OFF + + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: climate.entity_id, + }, + blocking=True, + ) + state = hass.states.get(climate.entity_id) + + assert mock_setup.api.set_temp.call_count == 1 + assert mock_setup.api.set_power.call_count == 2 + assert mock_setup.api.set_mode.call_count == 1 + assert state.state == HVACMode.HEAT