Skip to content

Commit

Permalink
Refactor mqtt callbacks for climate and water_heater (home-assistant#…
Browse files Browse the repository at this point in the history
…118040)

* Refactor mqtt callbacks for climate and water_heater

* Reduce callbacks
  • Loading branch information
jbouwh authored May 24, 2024
1 parent cf73a47 commit 7522bbf
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 201 deletions.
319 changes: 146 additions & 173 deletions homeassistant/components/mqtt/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from abc import ABC, abstractmethod
from collections.abc import Callable
from functools import partial
import logging
from typing import Any

Expand Down Expand Up @@ -79,12 +80,7 @@
DEFAULT_OPTIMISTIC,
PAYLOAD_NONE,
)
from .debug_info import log_messages
from .mixins import (
MqttEntity,
async_setup_entity_entry_helper,
write_state_on_attr_change,
)
from .mixins import MqttEntity, async_setup_entity_entry_helper
from .models import (
MqttCommandTemplate,
MqttValueTemplate,
Expand Down Expand Up @@ -418,13 +414,19 @@ def add_subscription(
topics: dict[str, dict[str, Any]],
topic: str,
msg_callback: Callable[[ReceiveMessage], None],
tracked_attributes: set[str],
) -> None:
"""Add a subscription."""
qos: int = self._config[CONF_QOS]
if topic in self._topic and self._topic[topic] is not None:
topics[topic] = {
"topic": self._topic[topic],
"msg_callback": msg_callback,
"msg_callback": partial(
self._message_callback,
msg_callback,
tracked_attributes,
),
"entity_id": self.entity_id,
"qos": qos,
"encoding": self._config[CONF_ENCODING] or None,
}
Expand All @@ -438,7 +440,7 @@ def render_template(

@callback
def handle_climate_attribute_received(
self, msg: ReceiveMessage, template_name: str, attr: str
self, template_name: str, attr: str, msg: ReceiveMessage
) -> None:
"""Handle climate attributes coming via MQTT."""
payload = self.render_template(msg, template_name)
Expand All @@ -456,62 +458,51 @@ def handle_climate_attribute_received(
except ValueError:
_LOGGER.error("Could not parse %s from %s", template_name, payload)

@callback
def prepare_subscribe_topics(
self,
topics: dict[str, dict[str, Any]],
) -> None:
"""(Re)Subscribe to topics."""

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_current_temperature"})
def handle_current_temperature_received(msg: ReceiveMessage) -> None:
"""Handle current temperature coming via MQTT."""
self.handle_climate_attribute_received(
msg, CONF_CURRENT_TEMP_TEMPLATE, "_attr_current_temperature"
)

self.add_subscription(
topics, CONF_CURRENT_TEMP_TOPIC, handle_current_temperature_received
topics,
CONF_CURRENT_TEMP_TOPIC,
partial(
self.handle_climate_attribute_received,
CONF_CURRENT_TEMP_TEMPLATE,
"_attr_current_temperature",
),
{"_attr_current_temperature"},
)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_target_temperature"})
def handle_target_temperature_received(msg: ReceiveMessage) -> None:
"""Handle target temperature coming via MQTT."""
self.handle_climate_attribute_received(
msg, CONF_TEMP_STATE_TEMPLATE, "_attr_target_temperature"
)

self.add_subscription(
topics, CONF_TEMP_STATE_TOPIC, handle_target_temperature_received
topics,
CONF_TEMP_STATE_TOPIC,
partial(
self.handle_climate_attribute_received,
CONF_TEMP_STATE_TEMPLATE,
"_attr_target_temperature",
),
{"_attr_target_temperature"},
)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_target_temperature_low"})
def handle_temperature_low_received(msg: ReceiveMessage) -> None:
"""Handle target temperature low coming via MQTT."""
self.handle_climate_attribute_received(
msg, CONF_TEMP_LOW_STATE_TEMPLATE, "_attr_target_temperature_low"
)

self.add_subscription(
topics, CONF_TEMP_LOW_STATE_TOPIC, handle_temperature_low_received
topics,
CONF_TEMP_LOW_STATE_TOPIC,
partial(
self.handle_climate_attribute_received,
CONF_TEMP_LOW_STATE_TEMPLATE,
"_attr_target_temperature_low",
),
{"_attr_target_temperature_low"},
)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_target_temperature_high"})
def handle_temperature_high_received(msg: ReceiveMessage) -> None:
"""Handle target temperature high coming via MQTT."""
self.handle_climate_attribute_received(
msg, CONF_TEMP_HIGH_STATE_TEMPLATE, "_attr_target_temperature_high"
)

self.add_subscription(
topics, CONF_TEMP_HIGH_STATE_TOPIC, handle_temperature_high_received
topics,
CONF_TEMP_HIGH_STATE_TOPIC,
partial(
self.handle_climate_attribute_received,
CONF_TEMP_HIGH_STATE_TEMPLATE,
"_attr_target_temperature_high",
),
{"_attr_target_temperature_high"},
)

self._sub_state = subscription.async_prepare_subscribe_topics(
Expand Down Expand Up @@ -714,146 +705,128 @@ def _setup_from_config(self, config: ConfigType) -> None:

self._attr_supported_features = support

def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
topics: dict[str, dict[str, Any]] = {}

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_hvac_action"})
def handle_action_received(msg: ReceiveMessage) -> None:
"""Handle receiving action via MQTT."""
payload = self.render_template(msg, CONF_ACTION_TEMPLATE)
if not payload or payload == PAYLOAD_NONE:
_LOGGER.debug(
"Invalid %s action: %s, ignoring",
[e.value for e in HVACAction],
payload,
)
return
try:
self._attr_hvac_action = HVACAction(str(payload))
except ValueError:
_LOGGER.warning(
"Invalid %s action: %s",
[e.value for e in HVACAction],
payload,
)
return

self.add_subscription(topics, CONF_ACTION_TOPIC, handle_action_received)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_current_humidity"})
def handle_current_humidity_received(msg: ReceiveMessage) -> None:
"""Handle current humidity coming via MQTT."""
self.handle_climate_attribute_received(
msg, CONF_CURRENT_HUMIDITY_TEMPLATE, "_attr_current_humidity"
@callback
def _handle_action_received(self, msg: ReceiveMessage) -> None:
"""Handle receiving action via MQTT."""
payload = self.render_template(msg, CONF_ACTION_TEMPLATE)
if not payload or payload == PAYLOAD_NONE:
_LOGGER.debug(
"Invalid %s action: %s, ignoring",
[e.value for e in HVACAction],
payload,
)
return
try:
self._attr_hvac_action = HVACAction(str(payload))
except ValueError:
_LOGGER.warning(
"Invalid %s action: %s",
[e.value for e in HVACAction],
payload,
)
return

self.add_subscription(
topics, CONF_CURRENT_HUMIDITY_TOPIC, handle_current_humidity_received
)
@callback
def _handle_mode_received(
self, template_name: str, attr: str, mode_list: str, msg: ReceiveMessage
) -> None:
"""Handle receiving listed mode via MQTT."""
payload = self.render_template(msg, template_name)

@callback
@write_state_on_attr_change(self, {"_attr_target_humidity"})
@log_messages(self.hass, self.entity_id)
def handle_target_humidity_received(msg: ReceiveMessage) -> None:
"""Handle target humidity coming via MQTT."""
if payload not in self._config[mode_list]:
_LOGGER.error("Invalid %s mode: %s", mode_list, payload)
else:
setattr(self, attr, payload)

self.handle_climate_attribute_received(
msg, CONF_HUMIDITY_STATE_TEMPLATE, "_attr_target_humidity"
@callback
def _handle_preset_mode_received(self, msg: ReceiveMessage) -> None:
"""Handle receiving preset mode via MQTT."""
preset_mode = self.render_template(msg, CONF_PRESET_MODE_VALUE_TEMPLATE)
if preset_mode in [PRESET_NONE, PAYLOAD_NONE]:
self._attr_preset_mode = PRESET_NONE
return
if not preset_mode:
_LOGGER.debug("Ignoring empty preset_mode from '%s'", msg.topic)
return
if not self._attr_preset_modes or preset_mode not in self._attr_preset_modes:
_LOGGER.warning(
"'%s' received on topic %s. '%s' is not a valid preset mode",
msg.payload,
msg.topic,
preset_mode,
)
else:
self._attr_preset_mode = str(preset_mode)

@callback
def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
topics: dict[str, dict[str, Any]] = {}

self.add_subscription(
topics, CONF_HUMIDITY_STATE_TOPIC, handle_target_humidity_received
topics,
CONF_ACTION_TOPIC,
self._handle_action_received,
{"_attr_hvac_action"},
)

@callback
def handle_mode_received(
msg: ReceiveMessage, template_name: str, attr: str, mode_list: str
) -> None:
"""Handle receiving listed mode via MQTT."""
payload = self.render_template(msg, template_name)

if payload not in self._config[mode_list]:
_LOGGER.error("Invalid %s mode: %s", mode_list, payload)
else:
setattr(self, attr, payload)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_hvac_mode"})
def handle_current_mode_received(msg: ReceiveMessage) -> None:
"""Handle receiving mode via MQTT."""
handle_mode_received(
msg, CONF_MODE_STATE_TEMPLATE, "_attr_hvac_mode", CONF_MODE_LIST
)

self.add_subscription(
topics, CONF_MODE_STATE_TOPIC, handle_current_mode_received
topics,
CONF_CURRENT_HUMIDITY_TOPIC,
partial(
self.handle_climate_attribute_received,
CONF_CURRENT_HUMIDITY_TEMPLATE,
"_attr_current_humidity",
),
{"_attr_current_humidity"},
)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_fan_mode"})
def handle_fan_mode_received(msg: ReceiveMessage) -> None:
"""Handle receiving fan mode via MQTT."""
handle_mode_received(
msg,
self.add_subscription(
topics,
CONF_HUMIDITY_STATE_TOPIC,
partial(
self.handle_climate_attribute_received,
CONF_HUMIDITY_STATE_TEMPLATE,
"_attr_target_humidity",
),
{"_attr_target_humidity"},
)
self.add_subscription(
topics,
CONF_MODE_STATE_TOPIC,
partial(
self._handle_mode_received,
CONF_MODE_STATE_TEMPLATE,
"_attr_hvac_mode",
CONF_MODE_LIST,
),
{"_attr_hvac_mode"},
)
self.add_subscription(
topics,
CONF_FAN_MODE_STATE_TOPIC,
partial(
self._handle_mode_received,
CONF_FAN_MODE_STATE_TEMPLATE,
"_attr_fan_mode",
CONF_FAN_MODE_LIST,
)

self.add_subscription(
topics, CONF_FAN_MODE_STATE_TOPIC, handle_fan_mode_received
),
{"_attr_fan_mode"},
)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_swing_mode"})
def handle_swing_mode_received(msg: ReceiveMessage) -> None:
"""Handle receiving swing mode via MQTT."""
handle_mode_received(
msg,
self.add_subscription(
topics,
CONF_SWING_MODE_STATE_TOPIC,
partial(
self._handle_mode_received,
CONF_SWING_MODE_STATE_TEMPLATE,
"_attr_swing_mode",
CONF_SWING_MODE_LIST,
)

self.add_subscription(
topics, CONF_SWING_MODE_STATE_TOPIC, handle_swing_mode_received
),
{"_attr_swing_mode"},
)

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_preset_mode"})
def handle_preset_mode_received(msg: ReceiveMessage) -> None:
"""Handle receiving preset mode via MQTT."""
preset_mode = self.render_template(msg, CONF_PRESET_MODE_VALUE_TEMPLATE)
if preset_mode in [PRESET_NONE, PAYLOAD_NONE]:
self._attr_preset_mode = PRESET_NONE
return
if not preset_mode:
_LOGGER.debug("Ignoring empty preset_mode from '%s'", msg.topic)
return
if (
not self._attr_preset_modes
or preset_mode not in self._attr_preset_modes
):
_LOGGER.warning(
"'%s' received on topic %s. '%s' is not a valid preset mode",
msg.payload,
msg.topic,
preset_mode,
)
else:
self._attr_preset_mode = str(preset_mode)

self.add_subscription(
topics, CONF_PRESET_MODE_STATE_TOPIC, handle_preset_mode_received
topics,
CONF_PRESET_MODE_STATE_TOPIC,
self._handle_preset_mode_received,
{"_attr_preset_mode"},
)

self.prepare_subscribe_topics(topics)
Expand Down
Loading

0 comments on commit 7522bbf

Please sign in to comment.