1 """Support for MQTT humidifiers."""
3 from __future__
import annotations
5 from collections.abc
import Callable
9 import voluptuous
as vol
14 ATTR_CURRENT_HUMIDITY,
20 HumidifierDeviceClass,
22 HumidifierEntityFeature,
39 from .
import subscription
40 from .config
import MQTT_RW_SCHEMA
44 CONF_COMMAND_TEMPLATE,
46 CONF_CURRENT_HUMIDITY_TEMPLATE,
47 CONF_CURRENT_HUMIDITY_TOPIC,
49 CONF_STATE_VALUE_TEMPLATE,
52 from .entity
import MqttEntity, async_setup_entity_entry_helper
59 from .schemas
import MQTT_ENTITY_COMMON_SCHEMA
60 from .util
import valid_publish_topic, valid_subscribe_topic
64 CONF_AVAILABLE_MODES_LIST =
"modes"
65 CONF_DEVICE_CLASS =
"device_class"
66 CONF_MODE_COMMAND_TEMPLATE =
"mode_command_template"
67 CONF_MODE_COMMAND_TOPIC =
"mode_command_topic"
68 CONF_MODE_STATE_TOPIC =
"mode_state_topic"
69 CONF_MODE_STATE_TEMPLATE =
"mode_state_template"
70 CONF_PAYLOAD_RESET_MODE =
"payload_reset_mode"
71 CONF_PAYLOAD_RESET_HUMIDITY =
"payload_reset_humidity"
72 CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE =
"target_humidity_command_template"
73 CONF_TARGET_HUMIDITY_COMMAND_TOPIC =
"target_humidity_command_topic"
74 CONF_TARGET_HUMIDITY_MIN =
"min_humidity"
75 CONF_TARGET_HUMIDITY_MAX =
"max_humidity"
76 CONF_TARGET_HUMIDITY_STATE_TEMPLATE =
"target_humidity_state_template"
77 CONF_TARGET_HUMIDITY_STATE_TOPIC =
"target_humidity_state_topic"
79 DEFAULT_NAME =
"MQTT Humidifier"
80 DEFAULT_PAYLOAD_ON =
"ON"
81 DEFAULT_PAYLOAD_OFF =
"OFF"
82 DEFAULT_PAYLOAD_RESET =
"None"
84 MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED = frozenset(
86 humidifier.ATTR_HUMIDITY,
87 humidifier.ATTR_MAX_HUMIDITY,
88 humidifier.ATTR_MIN_HUMIDITY,
90 humidifier.ATTR_AVAILABLE_MODES,
94 _LOGGER = logging.getLogger(__name__)
98 """Validate that the mode reset payload is not one of the available modes."""
99 if config[CONF_PAYLOAD_RESET_MODE]
in config[CONF_AVAILABLE_MODES_LIST]:
100 raise vol.Invalid(
"modes must not contain payload_reset_mode")
105 """Validate humidity range.
107 Ensures that the target_humidity range configuration is valid,
110 if config[CONF_TARGET_HUMIDITY_MIN] >= config[CONF_TARGET_HUMIDITY_MAX]:
111 raise vol.Invalid(
"target_humidity_max must be > target_humidity_min")
112 if config[CONF_TARGET_HUMIDITY_MAX] > 100:
113 raise vol.Invalid(
"max_humidity must be <= 100")
118 _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
120 vol.Optional(CONF_ACTION_TEMPLATE): cv.template,
121 vol.Optional(CONF_ACTION_TOPIC): valid_subscribe_topic,
124 CONF_AVAILABLE_MODES_LIST,
"available_modes", default=[]
126 vol.Inclusive(CONF_MODE_COMMAND_TOPIC,
"available_modes"): valid_publish_topic,
127 vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
128 vol.Optional(CONF_CURRENT_HUMIDITY_TEMPLATE): cv.template,
129 vol.Optional(CONF_CURRENT_HUMIDITY_TOPIC): valid_subscribe_topic,
131 CONF_DEVICE_CLASS, default=HumidifierDeviceClass.HUMIDIFIER
133 [HumidifierDeviceClass.HUMIDIFIER, HumidifierDeviceClass.DEHUMIDIFIER,
None]
135 vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
136 vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic,
137 vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
138 vol.Optional(CONF_NAME): vol.Any(cv.string,
None),
139 vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
140 vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
141 vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
142 vol.Required(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): valid_publish_topic,
143 vol.Optional(CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE): cv.template,
145 CONF_TARGET_HUMIDITY_MAX, default=DEFAULT_MAX_HUMIDITY
146 ): cv.positive_float,
148 CONF_TARGET_HUMIDITY_MIN, default=DEFAULT_MIN_HUMIDITY
149 ): cv.positive_float,
150 vol.Optional(CONF_TARGET_HUMIDITY_STATE_TEMPLATE): cv.template,
151 vol.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): valid_subscribe_topic,
153 CONF_PAYLOAD_RESET_HUMIDITY, default=DEFAULT_PAYLOAD_RESET
155 vol.Optional(CONF_PAYLOAD_RESET_MODE, default=DEFAULT_PAYLOAD_RESET): cv.string,
157 ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)
159 PLATFORM_SCHEMA_MODERN = vol.All(
160 _PLATFORM_SCHEMA_BASE,
161 valid_humidity_range_configuration,
162 valid_mode_configuration,
165 DISCOVERY_SCHEMA = vol.All(
166 _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
167 valid_humidity_range_configuration,
168 valid_mode_configuration,
175 CONF_CURRENT_HUMIDITY_TOPIC,
176 CONF_TARGET_HUMIDITY_STATE_TOPIC,
177 CONF_TARGET_HUMIDITY_COMMAND_TOPIC,
178 CONF_MODE_STATE_TOPIC,
179 CONF_MODE_COMMAND_TOPIC,
185 config_entry: ConfigEntry,
186 async_add_entities: AddEntitiesCallback,
188 """Set up MQTT humidifier through YAML and through MQTT discovery."""
196 PLATFORM_SCHEMA_MODERN,
201 """A MQTT humidifier component."""
203 _attr_mode: str |
None =
None
204 _default_name = DEFAULT_NAME
205 _entity_id_format = humidifier.ENTITY_ID_FORMAT
206 _attributes_extra_blocked = MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED
208 _command_templates: dict[str, Callable[[PublishPayloadType], PublishPayloadType]]
209 _value_templates: dict[str, Callable[[ReceivePayloadType], ReceivePayloadType]]
211 _optimistic_target_humidity: bool
212 _optimistic_mode: bool
213 _payload: dict[str, str]
214 _topic: dict[str, Any]
218 """Return the config schema."""
219 return DISCOVERY_SCHEMA
222 """(Re)Setup the entity."""
227 self.
_topic_topic = {key: config.get(key)
for key
in TOPICS}
229 "STATE_ON": config[CONF_PAYLOAD_ON],
230 "STATE_OFF": config[CONF_PAYLOAD_OFF],
231 "HUMIDITY_RESET": config[CONF_PAYLOAD_RESET_HUMIDITY],
232 "MODE_RESET": config[CONF_PAYLOAD_RESET_MODE],
234 if CONF_MODE_COMMAND_TOPIC
in config
and CONF_AVAILABLE_MODES_LIST
in config:
240 if CONF_MODE_STATE_TOPIC
in config:
243 optimistic: bool = config[CONF_OPTIMISTIC]
247 optimistic
or self.
_topic_topic[CONF_TARGET_HUMIDITY_STATE_TOPIC]
is None
251 command_templates: dict[str, Template |
None] = {
252 CONF_STATE: config.get(CONF_COMMAND_TEMPLATE),
253 ATTR_HUMIDITY: config.get(CONF_TARGET_HUMIDITY_COMMAND_TEMPLATE),
254 ATTR_MODE: config.get(CONF_MODE_COMMAND_TEMPLATE),
258 for key, tpl
in command_templates.items()
261 value_templates: dict[str, Template |
None] = {
262 ATTR_ACTION: config.get(CONF_ACTION_TEMPLATE),
263 ATTR_CURRENT_HUMIDITY: config.get(CONF_CURRENT_HUMIDITY_TEMPLATE),
264 CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
265 ATTR_HUMIDITY: config.get(CONF_TARGET_HUMIDITY_STATE_TEMPLATE),
266 ATTR_MODE: config.get(CONF_MODE_STATE_TEMPLATE),
272 ).async_render_with_possible_json_value
273 for key, tpl
in value_templates.items()
278 """Handle new received MQTT message."""
281 _LOGGER.debug(
"Ignoring empty state from '%s'", msg.topic)
283 if payload == self.
_payload_payload[
"STATE_ON"]:
285 elif payload == self.
_payload_payload[
"STATE_OFF"]:
287 elif payload == PAYLOAD_NONE:
292 """Handle new received MQTT message."""
293 action_payload = self.
_value_templates_value_templates[ATTR_ACTION](msg.payload)
294 if not action_payload
or action_payload == PAYLOAD_NONE:
295 _LOGGER.debug(
"Ignoring empty action from '%s'", msg.topic)
301 "'%s' received on topic %s. '%s' is not a valid action",
310 """Handle new received MQTT message for the current humidity."""
312 ATTR_CURRENT_HUMIDITY
314 if rendered_current_humidity_payload == self.
_payload_payload[
"HUMIDITY_RESET"]:
317 if not rendered_current_humidity_payload:
318 _LOGGER.debug(
"Ignoring empty current humidity from '%s'", msg.topic)
321 current_humidity = round(
float(rendered_current_humidity_payload))
324 "'%s' received on topic %s. '%s' is not a valid humidity",
327 rendered_current_humidity_payload,
330 if current_humidity < 0
or current_humidity > 100:
332 "'%s' received on topic %s. '%s' is not a valid humidity",
335 rendered_current_humidity_payload,
342 """Handle new received MQTT message for the target humidity."""
343 rendered_target_humidity_payload = self.
_value_templates_value_templates[ATTR_HUMIDITY](
346 if not rendered_target_humidity_payload:
347 _LOGGER.debug(
"Ignoring empty target humidity from '%s'", msg.topic)
349 if rendered_target_humidity_payload == self.
_payload_payload[
"HUMIDITY_RESET"]:
353 target_humidity = round(
float(rendered_target_humidity_payload))
356 "'%s' received on topic %s. '%s' is not a valid target humidity",
359 rendered_target_humidity_payload,
367 "'%s' received on topic %s. '%s' is not a valid target humidity",
370 rendered_target_humidity_payload,
377 """Handle new received MQTT message for mode."""
379 if mode == self.
_payload_payload[
"MODE_RESET"]:
383 _LOGGER.debug(
"Ignoring empty mode from '%s'", msg.topic)
387 "'%s' received on topic %s. '%s' is not a valid mode",
398 """(Re)Subscribe to topics."""
404 CONF_CURRENT_HUMIDITY_TOPIC,
406 {
"_attr_current_humidity"},
409 CONF_TARGET_HUMIDITY_STATE_TOPIC,
411 {
"_attr_target_humidity"},
414 CONF_MODE_STATE_TOPIC, self.
_mode_received_mode_received, {
"_attr_mode"}
418 """(Re)Subscribe to topics."""
419 subscription.async_subscribe_topics_internal(self.
hasshasshass, self.
_sub_state_sub_state)
422 """Turn on the entity.
424 This method is a coroutine.
428 self.
_config_config[CONF_COMMAND_TOPIC], mqtt_payload
435 """Turn off the entity.
437 This method is a coroutine.
441 self.
_config_config[CONF_COMMAND_TOPIC], mqtt_payload
448 """Set the target humidity of the humidifier.
450 This method is a coroutine.
454 self.
_config_config[CONF_TARGET_HUMIDITY_COMMAND_TOPIC], mqtt_payload
461 """Set the mode of the fan.
463 This method is a coroutine.
466 _LOGGER.warning(
"'%s'is not a valid mode", mode)
471 self.
_config_config[CONF_MODE_COMMAND_TOPIC], mqtt_payload
list[str]|None available_modes(self)
None async_publish_with_config(self, str topic, PublishPayloadType payload)
bool add_subscription(self, str state_topic_config_key, Callable[[ReceiveMessage], None] msg_callback, set[str]|None tracked_attributes, bool disable_encoding=False)
None _subscribe_topics(self)
None async_turn_on(self, **Any kwargs)
None _action_received(self, ReceiveMessage msg)
None async_set_humidity(self, float humidity)
None _state_received(self, ReceiveMessage msg)
None _mode_received(self, ReceiveMessage msg)
None async_set_mode(self, str mode)
None _current_humidity_received(self, ReceiveMessage msg)
None _target_humidity_received(self, ReceiveMessage msg)
VolSchemaType config_schema()
None _prepare_subscribe_topics(self)
_optimistic_target_humidity
None async_turn_off(self, **Any kwargs)
None _setup_from_config(self, ConfigType config)
None async_write_ha_state(self)
None async_setup_entity_entry_helper(HomeAssistant hass, ConfigEntry entry, type[MqttEntity]|None entity_class, str domain, AddEntitiesCallback async_add_entities, VolSchemaType discovery_schema, VolSchemaType platform_schema_modern, dict[str, type[MqttEntity]]|None schema_class_mapping=None)
ConfigType valid_humidity_range_configuration(ConfigType config)
ConfigType valid_mode_configuration(ConfigType config)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)