Add MQTT humidifier platform integration (#52828)
* New mqtt humidifier platform * Add humidifier platform * Leave out humidity step * Use humidity in constant for payload reset * change TARGET_HUMIDITY_RESET payload name * _attr_max_humidity not assigned correctly * _target_humidity_range has a zero base * align CONF_TARGET_HUMIDITY_MIN and MAX with model * shorter topics for humidity_range * Converts float to int from template * new humidifier abbreviations * Add common module to support tests * Add tests * Addtional testing * Always require target_humidity_command_topic * Typo * use available_modes to align entity model * use avail_modes not modes to avoid conflict * typo target_humidity_value_template * Allign modes and templates with climate platform * mode_state_template * target_humidity_state_template * Typo in platform name * Remove humidity_range feature and common lib * Update homeassistant/components/mqtt/humidifier.py Use vol.In, not regex Co-authored-by: Erik Montnemery <erik@montnemery.com> * black * Update homeassistant/components/mqtt/humidifier.py Co-authored-by: Erik Montnemery <erik@montnemery.com> * Use round to convert float to target humidity Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
8ce4d647c3
commit
c9eab10134
5 changed files with 1520 additions and 1 deletions
|
@ -126,6 +126,7 @@ PLATFORMS = [
|
|||
"climate",
|
||||
"cover",
|
||||
"fan",
|
||||
"humidifier",
|
||||
"light",
|
||||
"lock",
|
||||
"number",
|
||||
|
|
|
@ -74,6 +74,10 @@ ABBREVIATIONS = {
|
|||
"hs_val_tpl": "hs_value_template",
|
||||
"ic": "icon",
|
||||
"init": "initial",
|
||||
"hum_cmd_t": "target_humidity_command_topic",
|
||||
"hum_cmd_tpl": "target_humidity_command_template",
|
||||
"hum_stat_t": "target_humidity_state_topic",
|
||||
"hum_state_tpl": "target_humidity_state_template",
|
||||
"json_attr": "json_attributes",
|
||||
"json_attr_t": "json_attributes_topic",
|
||||
"json_attr_tpl": "json_attributes_template",
|
||||
|
@ -81,14 +85,17 @@ ABBREVIATIONS = {
|
|||
"lrst_val_tpl": "last_reset_value_template",
|
||||
"max": "max",
|
||||
"min": "min",
|
||||
"max_hum": "max_humidity",
|
||||
"min_hum": "min_humidity",
|
||||
"max_mirs": "max_mireds",
|
||||
"min_mirs": "min_mireds",
|
||||
"max_temp": "max_temp",
|
||||
"min_temp": "min_temp",
|
||||
"mode_cmd_tpl": "mode_command_template",
|
||||
"mode_cmd_t": "mode_command_topic",
|
||||
"mode_stat_tpl": "mode_state_template",
|
||||
"mode_stat_t": "mode_state_topic",
|
||||
"mode_stat_tpl": "mode_state_template",
|
||||
"modes": "modes",
|
||||
"name": "name",
|
||||
"off_dly": "off_delay",
|
||||
"on_cmd_type": "on_command_type",
|
||||
|
@ -126,6 +133,8 @@ ABBREVIATIONS = {
|
|||
"pl_osc_off": "payload_oscillation_off",
|
||||
"pl_osc_on": "payload_oscillation_on",
|
||||
"pl_paus": "payload_pause",
|
||||
"pl_rst_hum": "payload_reset_humidity",
|
||||
"pl_rst_mode": "payload_reset_mode",
|
||||
"pl_rst_pct": "payload_reset_percentage",
|
||||
"pl_rst_pr_mode": "payload_reset_preset_mode",
|
||||
"pl_stop": "payload_stop",
|
||||
|
|
|
@ -41,6 +41,7 @@ SUPPORTED_COMPONENTS = [
|
|||
"device_automation",
|
||||
"device_tracker",
|
||||
"fan",
|
||||
"humidifier",
|
||||
"light",
|
||||
"lock",
|
||||
"number",
|
||||
|
|
456
homeassistant/components/mqtt/humidifier.py
Normal file
456
homeassistant/components/mqtt/humidifier.py
Normal file
|
@ -0,0 +1,456 @@
|
|||
"""Support for MQTT humidifiers."""
|
||||
import functools
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import humidifier
|
||||
from homeassistant.components.humidifier import (
|
||||
ATTR_HUMIDITY,
|
||||
ATTR_MODE,
|
||||
DEFAULT_MAX_HUMIDITY,
|
||||
DEFAULT_MIN_HUMIDITY,
|
||||
DEVICE_CLASS_DEHUMIDIFIER,
|
||||
DEVICE_CLASS_HUMIDIFIER,
|
||||
SUPPORT_MODES,
|
||||
HumidifierEntity,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_PAYLOAD_OFF,
|
||||
CONF_PAYLOAD_ON,
|
||||
CONF_STATE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.reload import async_setup_reload_service
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import (
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_QOS,
|
||||
CONF_RETAIN,
|
||||
CONF_STATE_TOPIC,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
subscription,
|
||||
)
|
||||
from .. import mqtt
|
||||
from .debug_info import log_messages
|
||||
from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper
|
||||
|
||||
CONF_AVAILABLE_MODES_LIST = "modes"
|
||||
CONF_COMMAND_TEMPLATE = "command_template"
|
||||
CONF_DEVICE_CLASS = "device_class"
|
||||
CONF_MODE_COMMAND_TEMPLATE = "mode_command_template"
|
||||
CONF_MODE_COMMAND_TOPIC = "mode_command_topic"
|
||||
CONF_MODE_STATE_TOPIC = "mode_state_topic"
|
||||
CONF_MODE_STATE_TEMPLATE = "mode_state_template"
|
||||
CONF_PAYLOAD_RESET_MODE = "payload_reset_mode"
|
||||
CONF_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity"
|
||||
CONF_STATE_VALUE_TEMPLATE = "state_value_template"
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template"
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic"
|
||||
CONF_TARGET_HUMIDITY_MIN = "min_humidity"
|
||||
CONF_TARGET_HUMIDITY_MAX = "max_humidity"
|
||||
CONF_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template"
|
||||
CONF_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic"
|
||||
|
||||
DEFAULT_NAME = "MQTT Humidifier"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
DEFAULT_PAYLOAD_ON = "ON"
|
||||
DEFAULT_PAYLOAD_OFF = "OFF"
|
||||
DEFAULT_PAYLOAD_RESET = "None"
|
||||
|
||||
MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED = frozenset(
|
||||
{
|
||||
humidifier.ATTR_HUMIDITY,
|
||||
humidifier.ATTR_MAX_HUMIDITY,
|
||||
humidifier.ATTR_MIN_HUMIDITY,
|
||||
humidifier.ATTR_MODE,
|
||||
humidifier.ATTR_AVAILABLE_MODES,
|
||||
}
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def valid_mode_configuration(config):
|
||||
"""Validate that the mode reset payload is not one of the available modes."""
|
||||
if config.get(CONF_PAYLOAD_RESET_MODE) in config.get(CONF_AVAILABLE_MODES_LIST):
|
||||
raise ValueError("modes must not contain payload_reset_mode")
|
||||
return config
|
||||
|
||||
|
||||
def valid_humidity_range_configuration(config):
|
||||
"""Validate that the target_humidity range configuration is valid, throws if it isn't."""
|
||||
if config.get(CONF_TARGET_HUMIDITY_MIN) >= config.get(CONF_TARGET_HUMIDITY_MAX):
|
||||
raise ValueError("target_humidity_max must be > target_humidity_min")
|
||||
if config.get(CONF_TARGET_HUMIDITY_MAX) > 100:
|
||||
raise ValueError("max_humidity must be <= 100")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
# CONF_AVAIALABLE_MODES_LIST and CONF_MODE_COMMAND_TOPIC must be used together
|
||||
vol.Inclusive(
|
||||
CONF_AVAILABLE_MODES_LIST, "available_modes", default=[]
|
||||
): cv.ensure_list,
|
||||
vol.Inclusive(
|
||||
CONF_MODE_COMMAND_TOPIC, "available_modes"
|
||||
): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_DEVICE_CLASS, default=DEVICE_CLASS_HUMIDIFIER): vol.In(
|
||||
[DEVICE_CLASS_HUMIDIFIER, DEVICE_CLASS_DEHUMIDIFIER]
|
||||
),
|
||||
vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
||||
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
|
||||
vol.Required(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Optional(CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE): cv.template,
|
||||
vol.Optional(
|
||||
CONF_TARGET_HUMIDITY_MAX, default=DEFAULT_MAX_HUMIDITY
|
||||
): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_TARGET_HUMIDITY_MIN, default=DEFAULT_MIN_HUMIDITY
|
||||
): cv.positive_int,
|
||||
vol.Optional(CONF_TARGET_HUMIDITY_STATE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(
|
||||
CONF_PAYLOAD_RESET_HUMIDITY, default=DEFAULT_PAYLOAD_RESET
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_PAYLOAD_RESET_MODE, default=DEFAULT_PAYLOAD_RESET
|
||||
): cv.string,
|
||||
}
|
||||
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema),
|
||||
valid_humidity_range_configuration,
|
||||
valid_mode_configuration,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant, config: ConfigType, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up MQTT humidifier through configuration.yaml."""
|
||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
await _async_setup_entity(hass, async_add_entities, config)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up MQTT humidifier dynamically through MQTT discovery."""
|
||||
setup = functools.partial(
|
||||
_async_setup_entity, hass, async_add_entities, config_entry=config_entry
|
||||
)
|
||||
await async_setup_entry_helper(hass, humidifier.DOMAIN, setup, PLATFORM_SCHEMA)
|
||||
|
||||
|
||||
async def _async_setup_entity(
|
||||
hass, async_add_entities, config, config_entry=None, discovery_data=None
|
||||
):
|
||||
"""Set up the MQTT humidifier."""
|
||||
async_add_entities([MqttHumidifier(hass, config, config_entry, discovery_data)])
|
||||
|
||||
|
||||
class MqttHumidifier(MqttEntity, HumidifierEntity):
|
||||
"""A MQTT humidifier component."""
|
||||
|
||||
_attributes_extra_blocked = MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED
|
||||
|
||||
def __init__(self, hass, config, config_entry, discovery_data):
|
||||
"""Initialize the MQTT humidifier."""
|
||||
self._state = False
|
||||
self._target_humidity = None
|
||||
self._mode = None
|
||||
self._supported_features = 0
|
||||
|
||||
self._topic = None
|
||||
self._payload = None
|
||||
self._value_templates = None
|
||||
self._command_templates = None
|
||||
self._optimistic = None
|
||||
self._optimistic_target_humidity = None
|
||||
self._optimistic_mode = None
|
||||
|
||||
MqttEntity.__init__(self, hass, config, config_entry, discovery_data)
|
||||
|
||||
@staticmethod
|
||||
def config_schema():
|
||||
"""Return the config schema."""
|
||||
return PLATFORM_SCHEMA
|
||||
|
||||
def _setup_from_config(self, config):
|
||||
"""(Re)Setup the entity."""
|
||||
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._attr_min_humidity = config.get(CONF_TARGET_HUMIDITY_MIN)
|
||||
self._attr_max_humidity = config.get(CONF_TARGET_HUMIDITY_MAX)
|
||||
|
||||
self._topic = {
|
||||
key: config.get(key)
|
||||
for key in (
|
||||
CONF_STATE_TOPIC,
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_TARGET_HUMIDITY_STATE_TOPIC,
|
||||
CONF_TARGET_HUMIDITY_COMMAND_TOPIC,
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_MODE_COMMAND_TOPIC,
|
||||
)
|
||||
}
|
||||
self._value_templates = {
|
||||
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
|
||||
ATTR_HUMIDITY: config.get(CONF_TARGET_HUMIDITY_STATE_TEMPLATE),
|
||||
ATTR_MODE: config.get(CONF_MODE_STATE_TEMPLATE),
|
||||
}
|
||||
self._command_templates = {
|
||||
CONF_STATE: config.get(CONF_COMMAND_TEMPLATE),
|
||||
ATTR_HUMIDITY: config.get(CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE),
|
||||
ATTR_MODE: config.get(CONF_MODE_COMMAND_TEMPLATE),
|
||||
}
|
||||
self._payload = {
|
||||
"STATE_ON": config[CONF_PAYLOAD_ON],
|
||||
"STATE_OFF": config[CONF_PAYLOAD_OFF],
|
||||
"HUMIDITY_RESET": config[CONF_PAYLOAD_RESET_HUMIDITY],
|
||||
"MODE_RESET": config[CONF_PAYLOAD_RESET_MODE],
|
||||
}
|
||||
if CONF_MODE_COMMAND_TOPIC in config and CONF_AVAILABLE_MODES_LIST in config:
|
||||
self._available_modes = config[CONF_AVAILABLE_MODES_LIST]
|
||||
else:
|
||||
self._available_modes = []
|
||||
if self._available_modes:
|
||||
self._attr_supported_features = SUPPORT_MODES
|
||||
else:
|
||||
self._attr_supported_features = 0
|
||||
|
||||
optimistic = config[CONF_OPTIMISTIC]
|
||||
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
|
||||
self._optimistic_target_humidity = (
|
||||
optimistic or self._topic[CONF_TARGET_HUMIDITY_STATE_TOPIC] is None
|
||||
)
|
||||
self._optimistic_mode = optimistic or self._topic[CONF_MODE_STATE_TOPIC] is None
|
||||
|
||||
for tpl_dict in [self._command_templates, self._value_templates]:
|
||||
for key, tpl in tpl_dict.items():
|
||||
if tpl is None:
|
||||
tpl_dict[key] = lambda value: value
|
||||
else:
|
||||
tpl.hass = self.hass
|
||||
tpl_dict[key] = tpl.async_render_with_possible_json_value
|
||||
|
||||
async def _subscribe_topics(self):
|
||||
"""(Re)Subscribe to topics."""
|
||||
topics = {}
|
||||
|
||||
@callback
|
||||
@log_messages(self.hass, self.entity_id)
|
||||
def state_received(msg):
|
||||
"""Handle new received MQTT message."""
|
||||
payload = self._value_templates[CONF_STATE](msg.payload)
|
||||
if not payload:
|
||||
_LOGGER.debug("Ignoring empty state from '%s'", msg.topic)
|
||||
return
|
||||
if payload == self._payload["STATE_ON"]:
|
||||
self._state = True
|
||||
elif payload == self._payload["STATE_OFF"]:
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
if self._topic[CONF_STATE_TOPIC] is not None:
|
||||
topics[CONF_STATE_TOPIC] = {
|
||||
"topic": self._topic[CONF_STATE_TOPIC],
|
||||
"msg_callback": state_received,
|
||||
"qos": self._config[CONF_QOS],
|
||||
}
|
||||
|
||||
@callback
|
||||
@log_messages(self.hass, self.entity_id)
|
||||
def target_humidity_received(msg):
|
||||
"""Handle new received MQTT message for the target humidity."""
|
||||
rendered_target_humidity_payload = self._value_templates[ATTR_HUMIDITY](
|
||||
msg.payload
|
||||
)
|
||||
if not rendered_target_humidity_payload:
|
||||
_LOGGER.debug("Ignoring empty target humidity from '%s'", msg.topic)
|
||||
return
|
||||
if rendered_target_humidity_payload == self._payload["HUMIDITY_RESET"]:
|
||||
self._target_humidity = None
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
try:
|
||||
target_humidity = round(float(rendered_target_humidity_payload))
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s. '%s' is not a valid target humidity",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
rendered_target_humidity_payload,
|
||||
)
|
||||
return
|
||||
if (
|
||||
target_humidity < self._attr_min_humidity
|
||||
or target_humidity > self._attr_max_humidity
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s. '%s' is not a valid target humidity",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
rendered_target_humidity_payload,
|
||||
)
|
||||
return
|
||||
self._target_humidity = target_humidity
|
||||
self.async_write_ha_state()
|
||||
|
||||
if self._topic[CONF_TARGET_HUMIDITY_STATE_TOPIC] is not None:
|
||||
topics[CONF_TARGET_HUMIDITY_STATE_TOPIC] = {
|
||||
"topic": self._topic[CONF_TARGET_HUMIDITY_STATE_TOPIC],
|
||||
"msg_callback": target_humidity_received,
|
||||
"qos": self._config[CONF_QOS],
|
||||
}
|
||||
self._target_humidity = None
|
||||
|
||||
@callback
|
||||
@log_messages(self.hass, self.entity_id)
|
||||
def mode_received(msg):
|
||||
"""Handle new received MQTT message for mode."""
|
||||
mode = self._value_templates[ATTR_MODE](msg.payload)
|
||||
if mode == self._payload["MODE_RESET"]:
|
||||
self._mode = None
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
if not mode:
|
||||
_LOGGER.debug("Ignoring empty mode from '%s'", msg.topic)
|
||||
return
|
||||
if mode not in self.available_modes:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s. '%s' is not a valid mode",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
mode,
|
||||
)
|
||||
return
|
||||
|
||||
self._mode = mode
|
||||
self.async_write_ha_state()
|
||||
|
||||
if self._topic[CONF_MODE_STATE_TOPIC] is not None:
|
||||
topics[CONF_MODE_STATE_TOPIC] = {
|
||||
"topic": self._topic[CONF_MODE_STATE_TOPIC],
|
||||
"msg_callback": mode_received,
|
||||
"qos": self._config[CONF_QOS],
|
||||
}
|
||||
self._mode = None
|
||||
|
||||
self._sub_state = await subscription.async_subscribe_topics(
|
||||
self.hass, self._sub_state, topics
|
||||
)
|
||||
|
||||
@property
|
||||
def assumed_state(self):
|
||||
"""Return true if we do optimistic updates."""
|
||||
return self._optimistic
|
||||
|
||||
@property
|
||||
def available_modes(self) -> list:
|
||||
"""Get the list of available modes."""
|
||||
return self._available_modes
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if device is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def target_humidity(self):
|
||||
"""Return the current target humidity."""
|
||||
return self._target_humidity
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
"""Return the current mode."""
|
||||
return self._mode
|
||||
|
||||
async def async_turn_on(
|
||||
self,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Turn on the entity.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
mqtt_payload = self._command_templates[CONF_STATE](self._payload["STATE_ON"])
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._topic[CONF_COMMAND_TOPIC],
|
||||
mqtt_payload,
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
if self._optimistic:
|
||||
self._state = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn off the entity.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
mqtt_payload = self._command_templates[CONF_STATE](self._payload["STATE_OFF"])
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._topic[CONF_COMMAND_TOPIC],
|
||||
mqtt_payload,
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
if self._optimistic:
|
||||
self._state = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_humidity(self, humidity: int) -> None:
|
||||
"""Set the target humidity of the humidifier.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
mqtt_payload = self._command_templates[ATTR_HUMIDITY](humidity)
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._topic[CONF_TARGET_HUMIDITY_COMMAND_TOPIC],
|
||||
mqtt_payload,
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
|
||||
if self._optimistic_target_humidity:
|
||||
self._target_humidity = humidity
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_set_mode(self, mode: str) -> None:
|
||||
"""Set the mode of the fan.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
if mode not in self.available_modes:
|
||||
_LOGGER.warning("'%s'is not a valid mode", mode)
|
||||
return
|
||||
|
||||
mqtt_payload = self._command_templates[ATTR_MODE](mode)
|
||||
|
||||
mqtt.async_publish(
|
||||
self.hass,
|
||||
self._topic[CONF_MODE_COMMAND_TOPIC],
|
||||
mqtt_payload,
|
||||
self._config[CONF_QOS],
|
||||
self._config[CONF_RETAIN],
|
||||
)
|
||||
|
||||
if self._optimistic_mode:
|
||||
self._mode = mode
|
||||
self.async_write_ha_state()
|
1052
tests/components/mqtt/test_humidifier.py
Normal file
1052
tests/components/mqtt/test_humidifier.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue