mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Fix mqtt climate initial temperature conversion and precision (#93965)
* Fix mqtt climate initial temperature conversion * Avoid changing hass temperature_unit * Update comment
This commit is contained in:
parent
5a80eddbd7
commit
437de7c2a3
@ -15,9 +15,7 @@ from homeassistant.components.climate import (
|
|||||||
ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_LOW,
|
||||||
DEFAULT_MAX_HUMIDITY,
|
DEFAULT_MAX_HUMIDITY,
|
||||||
DEFAULT_MAX_TEMP,
|
|
||||||
DEFAULT_MIN_HUMIDITY,
|
DEFAULT_MIN_HUMIDITY,
|
||||||
DEFAULT_MIN_TEMP,
|
|
||||||
FAN_AUTO,
|
FAN_AUTO,
|
||||||
FAN_HIGH,
|
FAN_HIGH,
|
||||||
FAN_LOW,
|
FAN_LOW,
|
||||||
@ -42,12 +40,14 @@ from homeassistant.const import (
|
|||||||
PRECISION_HALVES,
|
PRECISION_HALVES,
|
||||||
PRECISION_TENTHS,
|
PRECISION_TENTHS,
|
||||||
PRECISION_WHOLE,
|
PRECISION_WHOLE,
|
||||||
|
UnitOfTemperature,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.template import Template
|
from homeassistant.helpers.template import Template
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
from homeassistant.util.unit_conversion import TemperatureConverter
|
||||||
|
|
||||||
from . import subscription
|
from . import subscription
|
||||||
from .config import DEFAULT_RETAIN, MQTT_BASE_SCHEMA
|
from .config import DEFAULT_RETAIN, MQTT_BASE_SCHEMA
|
||||||
@ -150,6 +150,8 @@ CONF_TEMP_MAX = "max_temp"
|
|||||||
CONF_TEMP_MIN = "min_temp"
|
CONF_TEMP_MIN = "min_temp"
|
||||||
CONF_TEMP_STEP = "temp_step"
|
CONF_TEMP_STEP = "temp_step"
|
||||||
|
|
||||||
|
DEFAULT_INITIAL_TEMPERATURE = 21.0
|
||||||
|
|
||||||
MQTT_CLIMATE_ATTRIBUTES_BLOCKED = frozenset(
|
MQTT_CLIMATE_ATTRIBUTES_BLOCKED = frozenset(
|
||||||
{
|
{
|
||||||
climate.ATTR_AUX_HEAT,
|
climate.ATTR_AUX_HEAT,
|
||||||
@ -338,9 +340,9 @@ _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
|
|||||||
): cv.ensure_list,
|
): cv.ensure_list,
|
||||||
vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
|
vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): valid_subscribe_topic,
|
vol.Optional(CONF_SWING_MODE_STATE_TOPIC): valid_subscribe_topic,
|
||||||
vol.Optional(CONF_TEMP_INITIAL, default=21): cv.positive_int,
|
vol.Optional(CONF_TEMP_INITIAL): cv.positive_int,
|
||||||
vol.Optional(CONF_TEMP_MIN, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
|
vol.Optional(CONF_TEMP_MIN): vol.Coerce(float),
|
||||||
vol.Optional(CONF_TEMP_MAX, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
|
vol.Optional(CONF_TEMP_MAX): vol.Coerce(float),
|
||||||
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float),
|
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float),
|
||||||
vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template,
|
vol.Optional(CONF_TEMP_COMMAND_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_TEMP_COMMAND_TOPIC): valid_publish_topic,
|
vol.Optional(CONF_TEMP_COMMAND_TOPIC): valid_publish_topic,
|
||||||
@ -443,6 +445,9 @@ class MqttTemperatureControlEntity(MqttEntity, ABC):
|
|||||||
climate and water_heater platforms.
|
climate and water_heater platforms.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_attr_target_temperature_low: float | None
|
||||||
|
_attr_target_temperature_high: float | None
|
||||||
|
|
||||||
_optimistic: bool
|
_optimistic: bool
|
||||||
_topic: dict[str, Any]
|
_topic: dict[str, Any]
|
||||||
|
|
||||||
@ -637,7 +642,7 @@ class MqttTemperatureControlEntity(MqttEntity, ABC):
|
|||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
|
||||||
class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): # type: ignore[misc]
|
class MqttClimate(MqttTemperatureControlEntity, ClimateEntity):
|
||||||
"""Representation of an MQTT climate device."""
|
"""Representation of an MQTT climate device."""
|
||||||
|
|
||||||
_entity_id_format = climate.ENTITY_ID_FORMAT
|
_entity_id_format = climate.ENTITY_ID_FORMAT
|
||||||
@ -668,28 +673,41 @@ class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): # type: ignore[
|
|||||||
def _setup_from_config(self, config: ConfigType) -> None:
|
def _setup_from_config(self, config: ConfigType) -> None:
|
||||||
"""(Re)Setup the entity."""
|
"""(Re)Setup the entity."""
|
||||||
self._attr_hvac_modes = config[CONF_MODE_LIST]
|
self._attr_hvac_modes = config[CONF_MODE_LIST]
|
||||||
self._attr_min_temp = config[CONF_TEMP_MIN]
|
# Make sure the min an max temp is converted to the correct when not set
|
||||||
self._attr_max_temp = config[CONF_TEMP_MAX]
|
|
||||||
self._attr_min_humidity = config[CONF_HUMIDITY_MIN]
|
|
||||||
self._attr_max_humidity = config[CONF_HUMIDITY_MAX]
|
|
||||||
self._attr_precision = config.get(CONF_PRECISION, super().precision)
|
|
||||||
self._attr_fan_modes = config[CONF_FAN_MODE_LIST]
|
|
||||||
self._attr_swing_modes = config[CONF_SWING_MODE_LIST]
|
|
||||||
self._attr_target_temperature_step = config[CONF_TEMP_STEP]
|
|
||||||
self._attr_temperature_unit = config.get(
|
self._attr_temperature_unit = config.get(
|
||||||
CONF_TEMPERATURE_UNIT, self.hass.config.units.temperature_unit
|
CONF_TEMPERATURE_UNIT, self.hass.config.units.temperature_unit
|
||||||
)
|
)
|
||||||
|
if (min_temp := config.get(CONF_TEMP_MIN)) is not None:
|
||||||
|
self._attr_min_temp = min_temp
|
||||||
|
if (max_temp := config.get(CONF_TEMP_MAX)) is not None:
|
||||||
|
self._attr_max_temp = max_temp
|
||||||
|
self._attr_min_humidity = config[CONF_HUMIDITY_MIN]
|
||||||
|
self._attr_max_humidity = config[CONF_HUMIDITY_MAX]
|
||||||
|
if (precision := config.get(CONF_PRECISION)) is not None:
|
||||||
|
self._attr_precision = precision
|
||||||
|
self._attr_fan_modes = config[CONF_FAN_MODE_LIST]
|
||||||
|
self._attr_swing_modes = config[CONF_SWING_MODE_LIST]
|
||||||
|
self._attr_target_temperature_step = config[CONF_TEMP_STEP]
|
||||||
|
|
||||||
self._topic = {key: config.get(key) for key in TOPIC_KEYS}
|
self._topic = {key: config.get(key) for key in TOPIC_KEYS}
|
||||||
|
|
||||||
self._optimistic = config[CONF_OPTIMISTIC]
|
self._optimistic = config[CONF_OPTIMISTIC]
|
||||||
|
|
||||||
|
# Set init temp, if it is missing convert the default to the temperature units
|
||||||
|
init_temp: float = config.get(
|
||||||
|
CONF_TEMP_INITIAL,
|
||||||
|
TemperatureConverter.convert(
|
||||||
|
DEFAULT_INITIAL_TEMPERATURE,
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
self.temperature_unit,
|
||||||
|
),
|
||||||
|
)
|
||||||
if self._topic[CONF_TEMP_STATE_TOPIC] is None or self._optimistic:
|
if self._topic[CONF_TEMP_STATE_TOPIC] is None or self._optimistic:
|
||||||
self._attr_target_temperature = config[CONF_TEMP_INITIAL]
|
self._attr_target_temperature = init_temp
|
||||||
if self._topic[CONF_TEMP_LOW_STATE_TOPIC] is None or self._optimistic:
|
if self._topic[CONF_TEMP_LOW_STATE_TOPIC] is None or self._optimistic:
|
||||||
self._attr_target_temperature_low = config[CONF_TEMP_INITIAL]
|
self._attr_target_temperature_low = init_temp
|
||||||
if self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is None or self._optimistic:
|
if self._topic[CONF_TEMP_HIGH_STATE_TOPIC] is None or self._optimistic:
|
||||||
self._attr_target_temperature_high = config[CONF_TEMP_INITIAL]
|
self._attr_target_temperature_high = init_temp
|
||||||
|
|
||||||
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None or self._optimistic:
|
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None or self._optimistic:
|
||||||
self._attr_fan_mode = FAN_LOW
|
self._attr_fan_mode = FAN_LOW
|
||||||
|
@ -27,8 +27,11 @@ from homeassistant.components.climate import (
|
|||||||
HVACAction,
|
HVACAction,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.components.mqtt.climate import MQTT_CLIMATE_ATTRIBUTES_BLOCKED
|
from homeassistant.components.mqtt.climate import (
|
||||||
from homeassistant.const import ATTR_TEMPERATURE, Platform
|
DEFAULT_INITIAL_TEMPERATURE,
|
||||||
|
MQTT_CLIMATE_ATTRIBUTES_BLOCKED,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, Platform, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from .test_common import (
|
from .test_common import (
|
||||||
@ -1690,12 +1693,97 @@ async def test_temperature_unit(
|
|||||||
"""Test that setting temperature unit converts temperature values."""
|
"""Test that setting temperature unit converts temperature values."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert state.attributes.get("temperature") == DEFAULT_INITIAL_TEMPERATURE
|
||||||
|
assert state.attributes.get("min_temp") == DEFAULT_MIN_TEMP
|
||||||
|
assert state.attributes.get("max_temp") == DEFAULT_MAX_TEMP
|
||||||
|
|
||||||
async_fire_mqtt_message(hass, "current_temperature", "77")
|
async_fire_mqtt_message(hass, "current_temperature", "77")
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_CLIMATE)
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
assert state.attributes.get("current_temperature") == 25
|
assert state.attributes.get("current_temperature") == 25
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("hass_config", "temperature_unit", "initial", "min", "max", "current"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
help_custom_config(
|
||||||
|
climate.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"temperature_unit": "F",
|
||||||
|
"current_temperature_topic": "current_temperature",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
UnitOfTemperature.CELSIUS,
|
||||||
|
DEFAULT_INITIAL_TEMPERATURE,
|
||||||
|
DEFAULT_MIN_TEMP,
|
||||||
|
DEFAULT_MAX_TEMP,
|
||||||
|
25,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
help_custom_config(
|
||||||
|
climate.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"temperature_unit": "F",
|
||||||
|
"current_temperature_topic": "current_temperature",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
UnitOfTemperature.KELVIN,
|
||||||
|
294,
|
||||||
|
280,
|
||||||
|
308,
|
||||||
|
298,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
help_custom_config(
|
||||||
|
climate.DOMAIN,
|
||||||
|
DEFAULT_CONFIG,
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"temperature_unit": "F",
|
||||||
|
"current_temperature_topic": "current_temperature",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
UnitOfTemperature.FAHRENHEIT,
|
||||||
|
70,
|
||||||
|
45,
|
||||||
|
95,
|
||||||
|
77,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_alt_temperature_unit(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
temperature_unit: UnitOfTemperature,
|
||||||
|
initial: float,
|
||||||
|
min: float,
|
||||||
|
max: float,
|
||||||
|
current: float,
|
||||||
|
) -> None:
|
||||||
|
"""Test deriving the systems temperature unit."""
|
||||||
|
with patch.object(hass.config.units, "temperature_unit", temperature_unit):
|
||||||
|
await mqtt_mock_entry()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert state.attributes.get("temperature") == initial
|
||||||
|
assert state.attributes.get("min_temp") == min
|
||||||
|
assert state.attributes.get("max_temp") == max
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "current_temperature", "77")
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_CLIMATE)
|
||||||
|
assert state.attributes.get("current_temperature") == current
|
||||||
|
|
||||||
|
|
||||||
async def test_setting_attribute_via_mqtt_json_message(
|
async def test_setting_attribute_via_mqtt_json_message(
|
||||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user