diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index ecfc96f1d67..d92f44e873a 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -8,6 +8,9 @@ from typing import TYPE_CHECKING, Any from tuya_sharing import CustomerDevice, Manager from homeassistant.components.climate import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_TEMPERATURE, SWING_BOTH, SWING_HORIZONTAL, SWING_OFF, @@ -128,6 +131,8 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): _hvac_to_tuya: dict[str, str] _set_humidity: IntegerTypeData | None = None _set_temperature: IntegerTypeData | None = None + _set_temperature_lower: IntegerTypeData | None = None + _set_temperature_upper: IntegerTypeData | None = None entity_description: TuyaClimateEntityDescription _attr_name = None @@ -210,6 +215,18 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): self._attr_min_temp = self._set_temperature.min_scaled self._attr_target_temperature_step = self._set_temperature.step_scaled + # Check for range + if ( + lower_type := self.find_dpcode(DPCode.LOWER_TEMP, dptype=DPType.INTEGER) + ) and ( + upper_type := self.find_dpcode(DPCode.UPPER_TEMP, dptype=DPType.INTEGER) + ): + self._attr_supported_features |= ( + ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + ) + self._set_temperature_lower = lower_type + self._set_temperature_upper = upper_type + # Determine HVAC modes self._attr_hvac_modes: list[HVACMode] = [] self._hvac_to_tuya = {} @@ -355,20 +372,52 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): def set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - if TYPE_CHECKING: - # guarded by ClimateEntityFeature.TARGET_TEMPERATURE - assert self._set_temperature is not None - - self._send_command( - [ + commands = [] + if ATTR_TEMPERATURE in kwargs: + if TYPE_CHECKING: + # guarded by ClimateEntityFeature.TARGET_TEMPERATURE + assert self._set_temperature is not None + commands.append( { "code": self._set_temperature.dpcode, "value": round( - self._set_temperature.scale_value_back(kwargs["temperature"]) + self._set_temperature.scale_value_back(kwargs[ATTR_TEMPERATURE]) ), } - ] - ) + ) + + if ATTR_TARGET_TEMP_LOW in kwargs: + if TYPE_CHECKING: + # guarded by ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + assert self._set_temperature_lower is not None + commands.append( + { + "code": self._set_temperature_lower.dpcode, + "value": round( + self._set_temperature_lower.scale_value_back( + kwargs[ATTR_TARGET_TEMP_LOW] + ) + ), + } + ) + + if ATTR_TARGET_TEMP_HIGH in kwargs: + if TYPE_CHECKING: + # guarded by ClimateEntityFeature.TARGET_TEMPERATURE_RANGE + assert self._set_temperature_upper is not None + commands.append( + { + "code": self._set_temperature_upper.dpcode, + "value": round( + self._set_temperature_upper.scale_value_back( + kwargs[ATTR_TARGET_TEMP_HIGH] + ) + ), + } + ) + + if commands: + self._send_command(commands) @property def current_temperature(self) -> float | None: @@ -413,6 +462,30 @@ class TuyaClimateEntity(TuyaEntity, ClimateEntity): return self._set_temperature.scale_value(temperature) + @property + def target_temperature_low(self) -> float | None: + """Return the lowbound target temperature we try to reach.""" + if self._set_temperature_lower is None: + return None + + temperature = self.device.status.get(self._set_temperature_lower.dpcode) + if temperature is None: + return None + + return self._set_temperature_lower.scale_value(temperature) + + @property + def target_temperature_high(self) -> float | None: + """Return the highbound target temperature we try to reach.""" + if self._set_temperature_upper is None: + return None + + temperature = self.device.status.get(self._set_temperature_upper.dpcode) + if temperature is None: + return None + + return self._set_temperature_upper.scale_value(temperature) + @property def target_humidity(self) -> int | None: """Return the humidity currently set to be reached.""" diff --git a/homeassistant/components/tuya/number.py b/homeassistant/components/tuya/number.py index 4590b0451ec..7fadaa0489b 100644 --- a/homeassistant/components/tuya/number.py +++ b/homeassistant/components/tuya/number.py @@ -398,18 +398,6 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { translation_key="temp_correction", entity_category=EntityCategory.CONFIG, ), - NumberEntityDescription( - key=DPCode.LOWER_TEMP, - translation_key="lower_temperature", - device_class=NumberDeviceClass.TEMPERATURE, - entity_category=EntityCategory.CONFIG, - ), - NumberEntityDescription( - key=DPCode.UPPER_TEMP, - translation_key="upper_temperature", - device_class=NumberDeviceClass.TEMPERATURE, - entity_category=EntityCategory.CONFIG, - ), ), # Tank Level Sensor # Note: Undocumented diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 4adba067f28..fa15e34694c 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -231,12 +231,6 @@ }, "maximum_liquid_depth": { "name": "Maximum liquid depth" - }, - "lower_temperature": { - "name": "Lower temperature" - }, - "upper_temperature": { - "name": "Upper temperature" } }, "select": { diff --git a/tests/components/tuya/snapshots/test_climate.ambr b/tests/components/tuya/snapshots/test_climate.ambr index 445fb3f8cc6..76b3bf5d307 100644 --- a/tests/components/tuya/snapshots/test_climate.ambr +++ b/tests/components/tuya/snapshots/test_climate.ambr @@ -111,7 +111,7 @@ 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, - 'supported_features': , + 'supported_features': , 'translation_key': None, 'unique_id': 'tuya.zgiyrxflahjowpcckw', 'unit_of_measurement': None, @@ -128,7 +128,9 @@ ]), 'max_temp': 35, 'min_temp': 7, - 'supported_features': , + 'supported_features': , + 'target_temp_high': 58.5, + 'target_temp_low': 60.0, 'target_temp_step': 1.0, }), 'context': , @@ -408,7 +410,7 @@ 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, - 'supported_features': , + 'supported_features': , 'translation_key': None, 'unique_id': 'tuya.sb3zdertrw50bgogkw', 'unit_of_measurement': None, @@ -426,7 +428,9 @@ ]), 'max_temp': 90.0, 'min_temp': 5.0, - 'supported_features': , + 'supported_features': , + 'target_temp_high': 30.0, + 'target_temp_low': 5.0, 'target_temp_step': 1.0, 'temperature': 12.0, }), @@ -616,7 +620,7 @@ 'platform': 'tuya', 'previous_unique_id': None, 'suggested_object_id': None, - 'supported_features': , + 'supported_features': , 'translation_key': None, 'unique_id': 'tuya.j6mn1t4ut5end6ifkw', 'unit_of_measurement': None, @@ -633,7 +637,9 @@ ]), 'max_temp': 35.0, 'min_temp': 5.0, - 'supported_features': , + 'supported_features': , + 'target_temp_high': 35.0, + 'target_temp_low': 5.0, 'target_temp_step': 0.5, 'temperature': 22.0, }), diff --git a/tests/components/tuya/snapshots/test_number.ambr b/tests/components/tuya/snapshots/test_number.ambr index 472b4e7c8d2..1aa8c3dcca9 100644 --- a/tests/components/tuya/snapshots/test_number.ambr +++ b/tests/components/tuya/snapshots/test_number.ambr @@ -58,65 +58,6 @@ 'state': '1.0', }) # --- -# name: test_platform_setup_and_discovery[number.boiler_temperature_controller_lower_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 120.0, - 'min': -40.0, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.boiler_temperature_controller_lower_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Lower temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'lower_temperature', - 'unique_id': 'tuya.zgiyrxflahjowpcckwlower_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.boiler_temperature_controller_lower_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Boiler Temperature Controller Lower temperature', - 'max': 120.0, - 'min': -40.0, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.boiler_temperature_controller_lower_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '60.0', - }) -# --- # name: test_platform_setup_and_discovery[number.boiler_temperature_controller_temperature_correction-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -175,65 +116,6 @@ 'state': '-0.8', }) # --- -# name: test_platform_setup_and_discovery[number.boiler_temperature_controller_upper_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 120.0, - 'min': -40.0, - 'mode': , - 'step': 0.1, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.boiler_temperature_controller_upper_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Upper temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'upper_temperature', - 'unique_id': 'tuya.zgiyrxflahjowpcckwupper_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.boiler_temperature_controller_upper_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Boiler Temperature Controller Upper temperature', - 'max': 120.0, - 'min': -40.0, - 'mode': , - 'step': 0.1, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.boiler_temperature_controller_upper_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '58.5', - }) -# --- # name: test_platform_setup_and_discovery[number.c9_volume-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1349,65 +1231,6 @@ 'state': '-2.0', }) # --- -# name: test_platform_setup_and_discovery[number.kabinet_upper_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 95.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.kabinet_upper_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Upper temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'upper_temperature', - 'unique_id': 'tuya.dn7cjik6kwupper_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.kabinet_upper_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Кабінет Upper temperature', - 'max': 95.0, - 'min': 35.0, - 'mode': , - 'step': 0.5, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.kabinet_upper_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '45.0', - }) -# --- # name: test_platform_setup_and_discovery[number.multifunction_alarm_alarm_delay-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1877,65 +1700,6 @@ 'state': '10.0', }) # --- -# name: test_platform_setup_and_discovery[number.smart_thermostats_lower_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 20.0, - 'min': 5.0, - 'mode': , - 'step': 1.0, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.smart_thermostats_lower_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Lower temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'lower_temperature', - 'unique_id': 'tuya.sb3zdertrw50bgogkwlower_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.smart_thermostats_lower_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'smart thermostats Lower temperature', - 'max': 20.0, - 'min': 5.0, - 'mode': , - 'step': 1.0, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.smart_thermostats_lower_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '5.0', - }) -# --- # name: test_platform_setup_and_discovery[number.smart_thermostats_temperature_correction-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1994,65 +1758,6 @@ 'state': '-2.0', }) # --- -# name: test_platform_setup_and_discovery[number.smart_thermostats_upper_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 90.0, - 'min': 30.0, - 'mode': , - 'step': 1.0, - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'number', - 'entity_category': , - 'entity_id': 'number.smart_thermostats_upper_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Upper temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'upper_temperature', - 'unique_id': 'tuya.sb3zdertrw50bgogkwupper_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.smart_thermostats_upper_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'smart thermostats Upper temperature', - 'max': 90.0, - 'min': 30.0, - 'mode': , - 'step': 1.0, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.smart_thermostats_upper_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '30.0', - }) -# --- # name: test_platform_setup_and_discovery[number.sous_vide_cook_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2227,65 +1932,6 @@ 'state': '95.0', }) # --- -# name: test_platform_setup_and_discovery[number.wifi_smart_gas_boiler_thermostat_lower_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 14.0, - 'min': 5.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.wifi_smart_gas_boiler_thermostat_lower_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Lower temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'lower_temperature', - 'unique_id': 'tuya.j6mn1t4ut5end6ifkwlower_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.wifi_smart_gas_boiler_thermostat_lower_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'WiFi Smart Gas Boiler Thermostat Lower temperature', - 'max': 14.0, - 'min': 5.0, - 'mode': , - 'step': 0.5, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.wifi_smart_gas_boiler_thermostat_lower_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '5.0', - }) -# --- # name: test_platform_setup_and_discovery[number.wifi_smart_gas_boiler_thermostat_temperature_correction-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2344,62 +1990,3 @@ 'state': '-1.5', }) # --- -# name: test_platform_setup_and_discovery[number.wifi_smart_gas_boiler_thermostat_upper_temperature-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'max': 35.0, - 'min': 15.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.wifi_smart_gas_boiler_thermostat_upper_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Upper temperature', - 'platform': 'tuya', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'upper_temperature', - 'unique_id': 'tuya.j6mn1t4ut5end6ifkwupper_temp', - 'unit_of_measurement': , - }) -# --- -# name: test_platform_setup_and_discovery[number.wifi_smart_gas_boiler_thermostat_upper_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'WiFi Smart Gas Boiler Thermostat Upper temperature', - 'max': 35.0, - 'min': 15.0, - 'mode': , - 'step': 0.5, - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'number.wifi_smart_gas_boiler_thermostat_upper_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '35.0', - }) -# --- diff --git a/tests/components/tuya/test_climate.py b/tests/components/tuya/test_climate.py index a0da9359ea3..69c16d91e85 100644 --- a/tests/components/tuya/test_climate.py +++ b/tests/components/tuya/test_climate.py @@ -2,6 +2,7 @@ from __future__ import annotations +from typing import Any from unittest.mock import patch import pytest @@ -11,6 +12,8 @@ from tuya_sharing import CustomerDevice from homeassistant.components.climate import ( ATTR_FAN_MODE, ATTR_HUMIDITY, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, ATTR_TEMPERATURE, DOMAIN as CLIMATE_DOMAIN, SERVICE_SET_FAN_MODE, @@ -44,17 +47,50 @@ async def test_platform_setup_and_discovery( @pytest.mark.parametrize( - "mock_device_code", - ["kt_5wnlzekkstwcdsvm"], + ("mock_device_code", "entity_id", "extra_kwargs", "commands"), + [ + ( + "kt_5wnlzekkstwcdsvm", + "climate.air_conditioner", + {ATTR_TEMPERATURE: 22.7}, + [{"code": "temp_set", "value": 22}], + ), + ( + "wk_ccpwojhalfxryigz", + "climate.boiler_temperature_controller", + {ATTR_TARGET_TEMP_HIGH: 22.7, ATTR_TARGET_TEMP_LOW: 20.2}, + [ + {"code": "lower_temp", "value": 202}, + {"code": "upper_temp", "value": 227}, + ], + ), + ( + "wk_gogb05wrtredz3bs", + "climate.smart_thermostats", + {ATTR_TEMPERATURE: 22.7}, + [{"code": "temp_set", "value": 22}], + ), + ( + "wk_gogb05wrtredz3bs", + "climate.smart_thermostats", + {ATTR_TARGET_TEMP_HIGH: 22.7, ATTR_TARGET_TEMP_LOW: 20.2}, + [ + {"code": "lower_temp", "value": 20}, + {"code": "upper_temp", "value": 22}, + ], + ), + ], ) async def test_set_temperature( hass: HomeAssistant, mock_manager: ManagerCompat, mock_config_entry: MockConfigEntry, mock_device: CustomerDevice, + entity_id: str, + extra_kwargs: dict[str, Any], + commands: list[dict[str, Any]], ) -> None: """Test set temperature service.""" - entity_id = "climate.air_conditioner" await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) state = hass.states.get(entity_id) @@ -62,15 +98,10 @@ async def test_set_temperature( await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, - { - ATTR_ENTITY_ID: entity_id, - ATTR_TEMPERATURE: 22.7, - }, + {ATTR_ENTITY_ID: entity_id, **extra_kwargs}, blocking=True, ) - mock_manager.send_commands.assert_called_once_with( - mock_device.id, [{"code": "temp_set", "value": 22}] - ) + mock_manager.send_commands.assert_called_once_with(mock_device.id, commands) @pytest.mark.parametrize(