Skip to content

Commit 71c665e

Browse files
authored
Fix fallback to local system unit in Tuya climate (home-assistant#156999)
1 parent 85a1afb commit 71c665e

File tree

3 files changed

+91
-85
lines changed

3 files changed

+91
-85
lines changed

homeassistant/components/tuya/climate.py

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@
2424
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2525

2626
from . import TuyaConfigEntry
27-
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType
28-
from .entity import TuyaEntity
29-
from .models import (
30-
DPCodeBooleanWrapper,
31-
DPCodeEnumWrapper,
32-
DPCodeIntegerWrapper,
33-
find_dpcode,
27+
from .const import (
28+
CELSIUS_ALIASES,
29+
FAHRENHEIT_ALIASES,
30+
TUYA_DISCOVERY_NEW,
31+
DeviceCategory,
32+
DPCode,
3433
)
34+
from .entity import TuyaEntity
35+
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
3536

3637
TUYA_HVAC_TO_HA = {
3738
"auto": HVACMode.HEAT_COOL,
@@ -90,80 +91,82 @@ class TuyaClimateEntityDescription(ClimateEntityDescription):
9091
}
9192

9293

94+
def _get_temperature_wrapper(
95+
wrappers: list[DPCodeIntegerWrapper | None], aliases: set[str]
96+
) -> DPCodeIntegerWrapper | None:
97+
"""Return first wrapper with matching unit."""
98+
return next(
99+
(
100+
wrapper
101+
for wrapper in wrappers
102+
if wrapper is not None
103+
and (unit := wrapper.type_information.unit)
104+
and unit.lower() in aliases
105+
),
106+
None,
107+
)
108+
109+
93110
def _get_temperature_wrappers(
94111
device: CustomerDevice, system_temperature_unit: UnitOfTemperature
95112
) -> tuple[DPCodeIntegerWrapper | None, DPCodeIntegerWrapper | None, UnitOfTemperature]:
96113
"""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
114+
# Get all possible temperature dpcodes
115+
temp_current = DPCodeIntegerWrapper.find_dpcode(
116+
device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP)
123117
)
124-
fahrenheit_type = find_dpcode(
125-
device,
126-
(DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F),
127-
dptype=DPType.INTEGER,
118+
temp_current_f = DPCodeIntegerWrapper.find_dpcode(
119+
device, (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F)
120+
)
121+
temp_set = DPCodeIntegerWrapper.find_dpcode(
122+
device, DPCode.TEMP_SET, prefer_function=True
123+
)
124+
temp_set_f = DPCodeIntegerWrapper.find_dpcode(
125+
device, DPCode.TEMP_SET_F, prefer_function=True
128126
)
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-
)
144127

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
128+
# Get wrappers for celsius and fahrenheit
129+
# We need to check the unit of measurement
130+
current_celsius = _get_temperature_wrapper(
131+
[temp_current, temp_current_f], CELSIUS_ALIASES
148132
)
149-
fahrenheit_type = find_dpcode(
150-
device, DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=True
133+
current_fahrenheit = _get_temperature_wrapper(
134+
[temp_current_f, temp_current], FAHRENHEIT_ALIASES
151135
)
152-
if fahrenheit_type and (
153-
preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT
154-
or (
155-
preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type
156-
)
136+
set_celsius = _get_temperature_wrapper([temp_set, temp_set_f], CELSIUS_ALIASES)
137+
set_fahrenheit = _get_temperature_wrapper(
138+
[temp_set_f, temp_set], FAHRENHEIT_ALIASES
139+
)
140+
141+
# Return early if we have the right wrappers for the system unit
142+
if system_temperature_unit == UnitOfTemperature.FAHRENHEIT:
143+
if (
144+
(current_fahrenheit and set_fahrenheit)
145+
or (current_fahrenheit and not set_celsius)
146+
or (set_fahrenheit and not current_celsius)
147+
):
148+
return current_fahrenheit, set_fahrenheit, UnitOfTemperature.FAHRENHEIT
149+
if (
150+
(current_celsius and set_celsius)
151+
or (current_celsius and not set_fahrenheit)
152+
or (set_celsius and not current_fahrenheit)
157153
):
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
154+
return current_celsius, set_celsius, UnitOfTemperature.CELSIUS
155+
156+
# If we don't have the right wrappers, return whatever is available
157+
# and assume system unit
158+
if system_temperature_unit == UnitOfTemperature.FAHRENHEIT:
159+
return (
160+
temp_current_f or temp_current,
161+
temp_set_f or temp_set,
162+
UnitOfTemperature.FAHRENHEIT,
164163
)
165164

166-
return current_temperature_wrapper, set_temperature_wrapper, temperature_unit
165+
return (
166+
temp_current or temp_current_f,
167+
temp_set or temp_set_f,
168+
UnitOfTemperature.CELSIUS,
169+
)
167170

168171

169172
async def async_setup_entry(

homeassistant/components/tuya/const.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
TUYA_RESPONSE_RESULT = "result"
5050
TUYA_RESPONSE_SUCCESS = "success"
5151

52+
CELSIUS_ALIASES = {"°c", "c", "celsius", "℃"}
53+
FAHRENHEIT_ALIASES = {"°f", "f", "fahrenheit", "℉"}
54+
5255
PLATFORMS = [
5356
Platform.ALARM_CONTROL_PANEL,
5457
Platform.BINARY_SENSOR,
@@ -1159,12 +1162,12 @@ class UnitOfMeasurement:
11591162
),
11601163
UnitOfMeasurement(
11611164
unit=UnitOfTemperature.CELSIUS,
1162-
aliases={"°c", "c", "celsius", "℃"},
1165+
aliases=CELSIUS_ALIASES,
11631166
device_classes={SensorDeviceClass.TEMPERATURE},
11641167
),
11651168
UnitOfMeasurement(
11661169
unit=UnitOfTemperature.FAHRENHEIT,
1167-
aliases={"°f", "f", "fahrenheit"},
1170+
aliases=FAHRENHEIT_ALIASES,
11681171
device_classes={SensorDeviceClass.TEMPERATURE},
11691172
),
11701173
UnitOfMeasurement(

tests/components/tuya/snapshots/test_climate.ambr

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,11 +1375,11 @@
13751375
# ---
13761376
# name: test_us_customary_system[climate.air_conditioner]
13771377
ReadOnlyDict({
1378-
'current_temperature': 72,
1379-
'max_temp': 187,
1380-
'min_temp': 61,
1378+
'current_temperature': 22,
1379+
'max_temp': 86,
1380+
'min_temp': 16,
13811381
'target_temp_step': 1.0,
1382-
'temperature': 73,
1382+
'temperature': 23,
13831383
})
13841384
# ---
13851385
# name: test_us_customary_system[climate.anbau]
@@ -1435,11 +1435,11 @@
14351435
# ---
14361436
# name: test_us_customary_system[climate.floor_thermostat_kitchen]
14371437
ReadOnlyDict({
1438-
'current_temperature': 68,
1439-
'max_temp': 95,
1440-
'min_temp': 41,
1438+
'current_temperature': -4,
1439+
'max_temp': 66,
1440+
'min_temp': 12,
14411441
'target_temp_step': 1.0,
1442-
'temperature': 36,
1442+
'temperature': 4,
14431443
})
14441444
# ---
14451445
# name: test_us_customary_system[climate.geti_solar_pv_water_heater]
@@ -1470,11 +1470,11 @@
14701470
# ---
14711471
# name: test_us_customary_system[climate.mini_split]
14721472
ReadOnlyDict({
1473-
'current_temperature': 156,
1474-
'max_temp': 194,
1475-
'min_temp': 61,
1473+
'current_temperature': 69,
1474+
'max_temp': 90,
1475+
'min_temp': 16,
14761476
'target_temp_step': 1.0,
1477-
'temperature': 151,
1477+
'temperature': 66,
14781478
})
14791479
# ---
14801480
# name: test_us_customary_system[climate.mr_pure]
@@ -1487,10 +1487,10 @@
14871487
# ---
14881488
# name: test_us_customary_system[climate.polotentsosushitel]
14891489
ReadOnlyDict({
1490-
'current_temperature': 78,
1490+
'current_temperature': 32,
14911491
'max_temp': 104,
14921492
'min_temp': 41,
1493-
'target_temp_step': 0.5,
1493+
'target_temp_step': 1.0,
14941494
'temperature': 41,
14951495
})
14961496
# ---

0 commit comments

Comments
 (0)