Skip to content

Commit edb8007

Browse files
authored
Migrate Tuya climate (temperature) to use wrapper class (home-assistant#156977)
1 parent 956a294 commit edb8007

File tree

2 files changed

+100
-107
lines changed

2 files changed

+100
-107
lines changed

homeassistant/components/tuya/climate.py

Lines changed: 99 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6-
from typing import TYPE_CHECKING, Any
6+
from typing import Any
77

88
from tuya_sharing import CustomerDevice, Manager
99

@@ -30,7 +30,6 @@
3030
DPCodeBooleanWrapper,
3131
DPCodeEnumWrapper,
3232
DPCodeIntegerWrapper,
33-
IntegerTypeData,
3433
find_dpcode,
3534
)
3635

@@ -91,6 +90,82 @@ class TuyaClimateEntityDescription(ClimateEntityDescription):
9190
}
9291

9392

93+
def _get_temperature_wrappers(
94+
device: CustomerDevice, system_temperature_unit: UnitOfTemperature
95+
) -> tuple[DPCodeIntegerWrapper | None, DPCodeIntegerWrapper | None, UnitOfTemperature]:
96+
"""Get temperature wrappers for current and set temperatures."""
97+
current_temperature_wrapper: DPCodeIntegerWrapper | None = None
98+
set_temperature_wrapper: DPCodeIntegerWrapper | None = None
99+
100+
# Default to System Temperature Unit
101+
temperature_unit = system_temperature_unit
102+
103+
# If both temperature values for celsius and fahrenheit are present,
104+
# use whatever the device is set to, with a fallback to celsius.
105+
preferred_temperature_unit = None
106+
if all(
107+
dpcode in device.status
108+
for dpcode in (DPCode.TEMP_CURRENT, DPCode.TEMP_CURRENT_F)
109+
) or all(
110+
dpcode in device.status for dpcode in (DPCode.TEMP_SET, DPCode.TEMP_SET_F)
111+
):
112+
preferred_temperature_unit = UnitOfTemperature.CELSIUS
113+
if any(
114+
"f" in device.status[dpcode].lower()
115+
for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT)
116+
if isinstance(device.status.get(dpcode), str)
117+
):
118+
preferred_temperature_unit = UnitOfTemperature.FAHRENHEIT
119+
120+
# Figure out current temperature, use preferred unit or what is available
121+
celsius_type = find_dpcode(
122+
device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER
123+
)
124+
fahrenheit_type = find_dpcode(
125+
device,
126+
(DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F),
127+
dptype=DPType.INTEGER,
128+
)
129+
if fahrenheit_type and (
130+
preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT
131+
or (
132+
preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type
133+
)
134+
):
135+
temperature_unit = UnitOfTemperature.FAHRENHEIT
136+
current_temperature_wrapper = DPCodeIntegerWrapper(
137+
fahrenheit_type.dpcode, fahrenheit_type
138+
)
139+
elif celsius_type:
140+
temperature_unit = UnitOfTemperature.CELSIUS
141+
current_temperature_wrapper = DPCodeIntegerWrapper(
142+
celsius_type.dpcode, celsius_type
143+
)
144+
145+
# Figure out setting temperature, use preferred unit or what is available
146+
celsius_type = find_dpcode(
147+
device, DPCode.TEMP_SET, dptype=DPType.INTEGER, prefer_function=True
148+
)
149+
fahrenheit_type = find_dpcode(
150+
device, DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=True
151+
)
152+
if fahrenheit_type and (
153+
preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT
154+
or (
155+
preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type
156+
)
157+
):
158+
set_temperature_wrapper = DPCodeIntegerWrapper(
159+
fahrenheit_type.dpcode, fahrenheit_type
160+
)
161+
elif celsius_type:
162+
set_temperature_wrapper = DPCodeIntegerWrapper(
163+
celsius_type.dpcode, celsius_type
164+
)
165+
166+
return current_temperature_wrapper, set_temperature_wrapper, temperature_unit
167+
168+
94169
async def async_setup_entry(
95170
hass: HomeAssistant,
96171
entry: TuyaConfigEntry,
@@ -106,15 +181,18 @@ def async_discover_device(device_ids: list[str]) -> None:
106181
for device_id in device_ids:
107182
device = manager.device_map[device_id]
108183
if device and device.category in CLIMATE_DESCRIPTIONS:
184+
temperature_wrappers = _get_temperature_wrappers(
185+
device, hass.config.units.temperature_unit
186+
)
109187
entities.append(
110188
TuyaClimateEntity(
111189
device,
112190
manager,
113191
CLIMATE_DESCRIPTIONS[device.category],
114-
hass.config.units.temperature_unit,
115192
current_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
116193
device, DPCode.HUMIDITY_CURRENT
117194
),
195+
current_temperature_wrapper=temperature_wrappers[0],
118196
fan_mode_wrapper=DPCodeEnumWrapper.find_dpcode(
119197
device,
120198
(DPCode.FAN_SPEED_ENUM, DPCode.LEVEL, DPCode.WINDSPEED),
@@ -123,6 +201,7 @@ def async_discover_device(device_ids: list[str]) -> None:
123201
hvac_mode_wrapper=DPCodeEnumWrapper.find_dpcode(
124202
device, DPCode.MODE, prefer_function=True
125203
),
204+
set_temperature_wrapper=temperature_wrappers[1],
126205
swing_wrapper=DPCodeBooleanWrapper.find_dpcode(
127206
device, (DPCode.SWING, DPCode.SHAKE), prefer_function=True
128207
),
@@ -138,6 +217,7 @@ def async_discover_device(device_ids: list[str]) -> None:
138217
target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
139218
device, DPCode.HUMIDITY_SET, prefer_function=True
140219
),
220+
temperature_unit=temperature_wrappers[2],
141221
)
142222
)
143223
async_add_entities(entities)
@@ -152,9 +232,7 @@ def async_discover_device(device_ids: list[str]) -> None:
152232
class TuyaClimateEntity(TuyaEntity, ClimateEntity):
153233
"""Tuya Climate Device."""
154234

155-
_current_temperature: IntegerTypeData | None = None
156235
_hvac_to_tuya: dict[str, str]
157-
_set_temperature: IntegerTypeData | None = None
158236
entity_description: TuyaClimateEntityDescription
159237
_attr_name = None
160238

@@ -163,98 +241,45 @@ def __init__(
163241
device: CustomerDevice,
164242
device_manager: Manager,
165243
description: TuyaClimateEntityDescription,
166-
system_temperature_unit: UnitOfTemperature,
167244
*,
168245
current_humidity_wrapper: _RoundedIntegerWrapper | None,
246+
current_temperature_wrapper: DPCodeIntegerWrapper | None,
169247
fan_mode_wrapper: DPCodeEnumWrapper | None,
170248
hvac_mode_wrapper: DPCodeEnumWrapper | None,
249+
set_temperature_wrapper: DPCodeIntegerWrapper | None,
171250
swing_wrapper: DPCodeBooleanWrapper | None,
172251
swing_h_wrapper: DPCodeBooleanWrapper | None,
173252
swing_v_wrapper: DPCodeBooleanWrapper | None,
174253
switch_wrapper: DPCodeBooleanWrapper | None,
175254
target_humidity_wrapper: _RoundedIntegerWrapper | None,
255+
temperature_unit: UnitOfTemperature,
176256
) -> None:
177257
"""Determine which values to use."""
178258
self._attr_target_temperature_step = 1.0
179259
self.entity_description = description
180260

181261
super().__init__(device, device_manager)
182262
self._current_humidity_wrapper = current_humidity_wrapper
263+
self._current_temperature = current_temperature_wrapper
183264
self._fan_mode_wrapper = fan_mode_wrapper
184265
self._hvac_mode_wrapper = hvac_mode_wrapper
266+
self._set_temperature = set_temperature_wrapper
185267
self._swing_wrapper = swing_wrapper
186268
self._swing_h_wrapper = swing_h_wrapper
187269
self._swing_v_wrapper = swing_v_wrapper
188270
self._switch_wrapper = switch_wrapper
189271
self._target_humidity_wrapper = target_humidity_wrapper
190-
191-
# If both temperature values for celsius and fahrenheit are present,
192-
# use whatever the device is set to, with a fallback to celsius.
193-
prefered_temperature_unit = None
194-
if all(
195-
dpcode in device.status
196-
for dpcode in (DPCode.TEMP_CURRENT, DPCode.TEMP_CURRENT_F)
197-
) or all(
198-
dpcode in device.status for dpcode in (DPCode.TEMP_SET, DPCode.TEMP_SET_F)
199-
):
200-
prefered_temperature_unit = UnitOfTemperature.CELSIUS
201-
if any(
202-
"f" in device.status[dpcode].lower()
203-
for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT)
204-
if isinstance(device.status.get(dpcode), str)
205-
):
206-
prefered_temperature_unit = UnitOfTemperature.FAHRENHEIT
207-
208-
# Default to System Temperature Unit
209-
self._attr_temperature_unit = system_temperature_unit
210-
211-
# Figure out current temperature, use preferred unit or what is available
212-
celsius_type = find_dpcode(
213-
self.device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER
214-
)
215-
fahrenheit_type = find_dpcode(
216-
self.device,
217-
(DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F),
218-
dptype=DPType.INTEGER,
219-
)
220-
if fahrenheit_type and (
221-
prefered_temperature_unit == UnitOfTemperature.FAHRENHEIT
222-
or (
223-
prefered_temperature_unit == UnitOfTemperature.CELSIUS
224-
and not celsius_type
225-
)
226-
):
227-
self._attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
228-
self._current_temperature = fahrenheit_type
229-
elif celsius_type:
230-
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
231-
self._current_temperature = celsius_type
232-
233-
# Figure out setting temperature, use preferred unit or what is available
234-
celsius_type = find_dpcode(
235-
self.device, DPCode.TEMP_SET, dptype=DPType.INTEGER, prefer_function=True
236-
)
237-
fahrenheit_type = find_dpcode(
238-
self.device, DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=True
239-
)
240-
if fahrenheit_type and (
241-
prefered_temperature_unit == UnitOfTemperature.FAHRENHEIT
242-
or (
243-
prefered_temperature_unit == UnitOfTemperature.CELSIUS
244-
and not celsius_type
245-
)
246-
):
247-
self._set_temperature = fahrenheit_type
248-
elif celsius_type:
249-
self._set_temperature = celsius_type
272+
self._attr_temperature_unit = temperature_unit
250273

251274
# Get integer type data for the dpcode to set temperature, use
252275
# it to define min, max & step temperatures
253276
if self._set_temperature:
254277
self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
255-
self._attr_max_temp = self._set_temperature.max_scaled
256-
self._attr_min_temp = self._set_temperature.min_scaled
257-
self._attr_target_temperature_step = self._set_temperature.step_scaled
278+
self._attr_max_temp = self._set_temperature.type_information.max_scaled
279+
self._attr_min_temp = self._set_temperature.type_information.min_scaled
280+
self._attr_target_temperature_step = (
281+
self._set_temperature.type_information.step_scaled
282+
)
258283

259284
# Determine HVAC modes
260285
self._attr_hvac_modes: list[HVACMode] = []
@@ -366,41 +391,16 @@ async def async_set_swing_mode(self, swing_mode: str) -> None:
366391
if commands:
367392
await self._async_send_commands(commands)
368393

369-
def set_temperature(self, **kwargs: Any) -> None:
394+
async def async_set_temperature(self, **kwargs: Any) -> None:
370395
"""Set new target temperature."""
371-
if TYPE_CHECKING:
372-
# guarded by ClimateEntityFeature.TARGET_TEMPERATURE
373-
assert self._set_temperature is not None
374-
375-
self._send_command(
376-
[
377-
{
378-
"code": self._set_temperature.dpcode,
379-
"value": round(
380-
self._set_temperature.scale_value_back(kwargs[ATTR_TEMPERATURE])
381-
),
382-
}
383-
]
396+
await self._async_send_dpcode_update(
397+
self._set_temperature, kwargs[ATTR_TEMPERATURE]
384398
)
385399

386400
@property
387401
def current_temperature(self) -> float | None:
388402
"""Return the current temperature."""
389-
if self._current_temperature is None:
390-
return None
391-
392-
temperature = self.device.status.get(self._current_temperature.dpcode)
393-
if temperature is None:
394-
return None
395-
396-
if self._current_temperature.scale == 0 and self._current_temperature.step != 1:
397-
# The current temperature can have a scale of 0 or 1 and is used for
398-
# rounding, Home Assistant doesn't need to round but we will always
399-
# need to divide the value by 10^1 in case of 0 as scale.
400-
# https://developer.tuya.com/en/docs/iot/shift-temperature-scale-follow-the-setting-of-app-account-center?id=Ka9qo7so58efq#title-7-Round%20values
401-
temperature = temperature / 10
402-
403-
return self._current_temperature.scale_value(temperature)
403+
return self._read_wrapper(self._current_temperature)
404404

405405
@property
406406
def current_humidity(self) -> int | None:
@@ -410,14 +410,7 @@ def current_humidity(self) -> int | None:
410410
@property
411411
def target_temperature(self) -> float | None:
412412
"""Return the temperature currently set to be reached."""
413-
if self._set_temperature is None:
414-
return None
415-
416-
temperature = self.device.status.get(self._set_temperature.dpcode)
417-
if temperature is None:
418-
return None
419-
420-
return self._set_temperature.scale_value(temperature)
413+
return self._read_wrapper(self._set_temperature)
421414

422415
@property
423416
def target_humidity(self) -> int | None:

tests/components/tuya/snapshots/test_climate.ambr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@
419419
# name: test_platform_setup_and_discovery[climate.el_termostato_de_la_cocina-state]
420420
StateSnapshot({
421421
'attributes': ReadOnlyDict({
422-
'current_temperature': 4.5,
422+
'current_temperature': 45.0,
423423
'friendly_name': 'El termostato de la cocina',
424424
'hvac_modes': list([
425425
<HVACMode.OFF: 'off'>,

0 commit comments

Comments
 (0)