Fix fallback to local system unit in Tuya climate (#156999)

This commit is contained in:
epenet
2025-11-22 10:24:03 +01:00
committed by GitHub
parent 85a1afb174
commit 71c665ed49
3 changed files with 92 additions and 86 deletions

View File

@@ -24,14 +24,15 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType
from .entity import TuyaEntity
from .models import (
DPCodeBooleanWrapper,
DPCodeEnumWrapper,
DPCodeIntegerWrapper,
find_dpcode,
from .const import (
CELSIUS_ALIASES,
FAHRENHEIT_ALIASES,
TUYA_DISCOVERY_NEW,
DeviceCategory,
DPCode,
)
from .entity import TuyaEntity
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
TUYA_HVAC_TO_HA = {
"auto": HVACMode.HEAT_COOL,
@@ -90,80 +91,82 @@ CLIMATE_DESCRIPTIONS: dict[DeviceCategory, TuyaClimateEntityDescription] = {
}
def _get_temperature_wrapper(
wrappers: list[DPCodeIntegerWrapper | None], aliases: set[str]
) -> DPCodeIntegerWrapper | None:
"""Return first wrapper with matching unit."""
return next(
(
wrapper
for wrapper in wrappers
if wrapper is not None
and (unit := wrapper.type_information.unit)
and unit.lower() in aliases
),
None,
)
def _get_temperature_wrappers(
device: CustomerDevice, system_temperature_unit: UnitOfTemperature
) -> tuple[DPCodeIntegerWrapper | None, DPCodeIntegerWrapper | None, UnitOfTemperature]:
"""Get temperature wrappers for current and set temperatures."""
current_temperature_wrapper: DPCodeIntegerWrapper | None = None
set_temperature_wrapper: DPCodeIntegerWrapper | None = None
# Get all possible temperature dpcodes
temp_current = DPCodeIntegerWrapper.find_dpcode(
device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP)
)
temp_current_f = DPCodeIntegerWrapper.find_dpcode(
device, (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F)
)
temp_set = DPCodeIntegerWrapper.find_dpcode(
device, DPCode.TEMP_SET, prefer_function=True
)
temp_set_f = DPCodeIntegerWrapper.find_dpcode(
device, DPCode.TEMP_SET_F, prefer_function=True
)
# Default to System Temperature Unit
temperature_unit = system_temperature_unit
# Get wrappers for celsius and fahrenheit
# We need to check the unit of measurement
current_celsius = _get_temperature_wrapper(
[temp_current, temp_current_f], CELSIUS_ALIASES
)
current_fahrenheit = _get_temperature_wrapper(
[temp_current_f, temp_current], FAHRENHEIT_ALIASES
)
set_celsius = _get_temperature_wrapper([temp_set, temp_set_f], CELSIUS_ALIASES)
set_fahrenheit = _get_temperature_wrapper(
[temp_set_f, temp_set], FAHRENHEIT_ALIASES
)
# If both temperature values for celsius and fahrenheit are present,
# use whatever the device is set to, with a fallback to celsius.
preferred_temperature_unit = None
if all(
dpcode in device.status
for dpcode in (DPCode.TEMP_CURRENT, DPCode.TEMP_CURRENT_F)
) or all(
dpcode in device.status for dpcode in (DPCode.TEMP_SET, DPCode.TEMP_SET_F)
):
preferred_temperature_unit = UnitOfTemperature.CELSIUS
if any(
"f" in device.status[dpcode].lower()
for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT)
if isinstance(device.status.get(dpcode), str)
# Return early if we have the right wrappers for the system unit
if system_temperature_unit == UnitOfTemperature.FAHRENHEIT:
if (
(current_fahrenheit and set_fahrenheit)
or (current_fahrenheit and not set_celsius)
or (set_fahrenheit and not current_celsius)
):
preferred_temperature_unit = UnitOfTemperature.FAHRENHEIT
# Figure out current temperature, use preferred unit or what is available
celsius_type = find_dpcode(
device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER
)
fahrenheit_type = find_dpcode(
device,
(DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F),
dptype=DPType.INTEGER,
)
if fahrenheit_type and (
preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT
or (
preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type
)
return current_fahrenheit, set_fahrenheit, UnitOfTemperature.FAHRENHEIT
if (
(current_celsius and set_celsius)
or (current_celsius and not set_fahrenheit)
or (set_celsius and not current_fahrenheit)
):
temperature_unit = UnitOfTemperature.FAHRENHEIT
current_temperature_wrapper = DPCodeIntegerWrapper(
fahrenheit_type.dpcode, fahrenheit_type
)
elif celsius_type:
temperature_unit = UnitOfTemperature.CELSIUS
current_temperature_wrapper = DPCodeIntegerWrapper(
celsius_type.dpcode, celsius_type
return current_celsius, set_celsius, UnitOfTemperature.CELSIUS
# If we don't have the right wrappers, return whatever is available
# and assume system unit
if system_temperature_unit == UnitOfTemperature.FAHRENHEIT:
return (
temp_current_f or temp_current,
temp_set_f or temp_set,
UnitOfTemperature.FAHRENHEIT,
)
# Figure out setting temperature, use preferred unit or what is available
celsius_type = find_dpcode(
device, DPCode.TEMP_SET, dptype=DPType.INTEGER, prefer_function=True
return (
temp_current or temp_current_f,
temp_set or temp_set_f,
UnitOfTemperature.CELSIUS,
)
fahrenheit_type = find_dpcode(
device, DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=True
)
if fahrenheit_type and (
preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT
or (
preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type
)
):
set_temperature_wrapper = DPCodeIntegerWrapper(
fahrenheit_type.dpcode, fahrenheit_type
)
elif celsius_type:
set_temperature_wrapper = DPCodeIntegerWrapper(
celsius_type.dpcode, celsius_type
)
return current_temperature_wrapper, set_temperature_wrapper, temperature_unit
async def async_setup_entry(

View File

@@ -49,6 +49,9 @@ TUYA_RESPONSE_QR_CODE = "qrcode"
TUYA_RESPONSE_RESULT = "result"
TUYA_RESPONSE_SUCCESS = "success"
CELSIUS_ALIASES = {"°c", "c", "celsius", ""}
FAHRENHEIT_ALIASES = {"°f", "f", "fahrenheit", ""}
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
@@ -1159,12 +1162,12 @@ UNITS = (
),
UnitOfMeasurement(
unit=UnitOfTemperature.CELSIUS,
aliases={"°c", "c", "celsius", ""},
aliases=CELSIUS_ALIASES,
device_classes={SensorDeviceClass.TEMPERATURE},
),
UnitOfMeasurement(
unit=UnitOfTemperature.FAHRENHEIT,
aliases={"°f", "f", "fahrenheit"},
aliases=FAHRENHEIT_ALIASES,
device_classes={SensorDeviceClass.TEMPERATURE},
),
UnitOfMeasurement(

View File

@@ -1375,11 +1375,11 @@
# ---
# name: test_us_customary_system[climate.air_conditioner]
ReadOnlyDict({
'current_temperature': 72,
'max_temp': 187,
'min_temp': 61,
'current_temperature': 22,
'max_temp': 86,
'min_temp': 16,
'target_temp_step': 1.0,
'temperature': 73,
'temperature': 23,
})
# ---
# name: test_us_customary_system[climate.anbau]
@@ -1435,11 +1435,11 @@
# ---
# name: test_us_customary_system[climate.floor_thermostat_kitchen]
ReadOnlyDict({
'current_temperature': 68,
'max_temp': 95,
'min_temp': 41,
'current_temperature': -4,
'max_temp': 66,
'min_temp': 12,
'target_temp_step': 1.0,
'temperature': 36,
'temperature': 4,
})
# ---
# name: test_us_customary_system[climate.geti_solar_pv_water_heater]
@@ -1470,11 +1470,11 @@
# ---
# name: test_us_customary_system[climate.mini_split]
ReadOnlyDict({
'current_temperature': 156,
'max_temp': 194,
'min_temp': 61,
'current_temperature': 69,
'max_temp': 90,
'min_temp': 16,
'target_temp_step': 1.0,
'temperature': 151,
'temperature': 66,
})
# ---
# name: test_us_customary_system[climate.mr_pure]
@@ -1487,10 +1487,10 @@
# ---
# name: test_us_customary_system[climate.polotentsosushitel]
ReadOnlyDict({
'current_temperature': 78,
'current_temperature': 32,
'max_temp': 104,
'min_temp': 41,
'target_temp_step': 0.5,
'target_temp_step': 1.0,
'temperature': 41,
})
# ---