33from __future__ import annotations
44
55from dataclasses import dataclass
6- from typing import TYPE_CHECKING , Any
6+ from typing import Any
77
88from tuya_sharing import CustomerDevice , Manager
99
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+
94169async 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:
152232class 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 :
0 commit comments