Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create bound callback_message_received method for handling mqtt callbacks #117951

Merged
merged 13 commits into from
May 24, 2024
158 changes: 78 additions & 80 deletions homeassistant/components/mqtt/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from datetime import datetime, timedelta
from functools import partial
import logging
from typing import Any

Expand Down Expand Up @@ -37,13 +38,7 @@
from . import subscription
from .config import MQTT_RO_SCHEMA
from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, PAYLOAD_NONE
from .debug_info import log_messages
from .mixins import (
MqttAvailability,
MqttEntity,
async_setup_entity_entry_helper,
write_state_on_attr_change,
)
from .mixins import MqttAvailability, MqttEntity, async_setup_entity_entry_helper
from .models import MqttValueTemplate, ReceiveMessage
from .schemas import MQTT_ENTITY_COMMON_SCHEMA

Expand Down Expand Up @@ -162,92 +157,95 @@ def _setup_from_config(self, config: ConfigType) -> None:
entity=self,
).async_render_with_possible_json_value

def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
@callback
def _off_delay_listener(self, now: datetime) -> None:
"""Switch device off after a delay."""
self._delay_listener = None
self._attr_is_on = False
self.async_write_ha_state()

@callback
def off_delay_listener(now: datetime) -> None:
"""Switch device off after a delay."""
self._delay_listener = None
self._attr_is_on = False
self.async_write_ha_state()

@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_is_on", "_expired"})
def state_message_received(msg: ReceiveMessage) -> None:
"""Handle a new received MQTT state message."""
# auto-expire enabled?
if self._expire_after:
# When expire_after is set, and we receive a message, assume device is
# not expired since it has to be to receive the message
self._expired = False

# Reset old trigger
if self._expiration_trigger:
self._expiration_trigger()

# Set new trigger
self._expiration_trigger = async_call_later(
self.hass, self._expire_after, self._value_is_expired
)
def _state_message_received(self, msg: ReceiveMessage) -> None:
"""Handle a new received MQTT state message."""

payload = self._value_template(msg.payload)
if not payload.strip(): # No output from template, ignore
_LOGGER.debug(
(
"Empty template output for entity: %s with state topic: %s."
" Payload: '%s', with value template '%s'"
),
self.entity_id,
self._config[CONF_STATE_TOPIC],
msg.payload,
self._config.get(CONF_VALUE_TEMPLATE),
)
return
# auto-expire enabled?
if self._expire_after:
# When expire_after is set, and we receive a message, assume device is
# not expired since it has to be to receive the message
self._expired = False

if payload == self._config[CONF_PAYLOAD_ON]:
self._attr_is_on = True
elif payload == self._config[CONF_PAYLOAD_OFF]:
self._attr_is_on = False
elif payload == PAYLOAD_NONE:
self._attr_is_on = None
else: # Payload is not for this entity
template_info = ""
if self._config.get(CONF_VALUE_TEMPLATE) is not None:
template_info = (
f", template output: '{payload!s}', with value template"
f" '{self._config.get(CONF_VALUE_TEMPLATE)!s}'"
)
_LOGGER.info(
(
"No matching payload found for entity: %s with state topic: %s."
" Payload: '%s'%s"
),
self.entity_id,
self._config[CONF_STATE_TOPIC],
msg.payload,
template_info,
)
return
# Reset old trigger
if self._expiration_trigger:
self._expiration_trigger()

if self._delay_listener is not None:
self._delay_listener()
self._delay_listener = None
# Set new trigger
self._expiration_trigger = async_call_later(
self.hass, self._expire_after, self._value_is_expired
)

off_delay: int | None = self._config.get(CONF_OFF_DELAY)
if self._attr_is_on and off_delay is not None:
self._delay_listener = evt.async_call_later(
self.hass, off_delay, off_delay_listener
payload = self._value_template(msg.payload)
if not payload.strip(): # No output from template, ignore
_LOGGER.debug(
(
"Empty template output for entity: %s with state topic: %s."
" Payload: '%s', with value template '%s'"
),
self.entity_id,
self._config[CONF_STATE_TOPIC],
msg.payload,
self._config.get(CONF_VALUE_TEMPLATE),
)
return

if payload == self._config[CONF_PAYLOAD_ON]:
self._attr_is_on = True
elif payload == self._config[CONF_PAYLOAD_OFF]:
self._attr_is_on = False
elif payload == PAYLOAD_NONE:
self._attr_is_on = None
else: # Payload is not for this entity
template_info = ""
if self._config.get(CONF_VALUE_TEMPLATE) is not None:
template_info = (
f", template output: '{payload!s}', with value template"
f" '{self._config.get(CONF_VALUE_TEMPLATE)!s}'"
)
_LOGGER.info(
(
"No matching payload found for entity: %s with state topic: %s."
" Payload: '%s'%s"
),
self.entity_id,
self._config[CONF_STATE_TOPIC],
msg.payload,
template_info,
)
return

if self._delay_listener is not None:
self._delay_listener()
self._delay_listener = None

off_delay: int | None = self._config.get(CONF_OFF_DELAY)
if self._attr_is_on and off_delay is not None:
self._delay_listener = evt.async_call_later(
self.hass, off_delay, self._off_delay_listener
)

def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""

self._sub_state = subscription.async_prepare_subscribe_topics(
self.hass,
self._sub_state,
{
"state_topic": {
"topic": self._config[CONF_STATE_TOPIC],
"msg_callback": state_message_received,
"msg_callback": partial(
self._message_callback,
self._state_message_received,
{"_attr_is_on", "_expired"},
),
"entity_id": self.entity_id,
"qos": self._config[CONF_QOS],
"encoding": self._config[CONF_ENCODING] or None,
}
Expand Down
10 changes: 8 additions & 2 deletions homeassistant/components/mqtt/debug_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ def add_subscription(
hass: HomeAssistant,
message_callback: MessageCallbackType,
subscription: str,
entity_id: str | None = None,
) -> None:
"""Prepare debug data for subscription."""
if entity_id := getattr(message_callback, "__entity_id", None):
if not entity_id:
entity_id = getattr(message_callback, "__entity_id", None)
if entity_id:
entity_info = hass.data[DATA_MQTT].debug_info_entities.setdefault(
entity_id, {"subscriptions": {}, "discovery_data": {}, "transmitted": {}}
)
Expand All @@ -104,9 +107,12 @@ def remove_subscription(
hass: HomeAssistant,
message_callback: MessageCallbackType,
subscription: str,
entity_id: str | None = None,
) -> None:
"""Remove debug data for subscription if it exists."""
if (entity_id := getattr(message_callback, "__entity_id", None)) and entity_id in (
if not entity_id:
entity_id = getattr(message_callback, "__entity_id", None)
if entity_id and entity_id in (
debug_info_entities := hass.data[DATA_MQTT].debug_info_entities
):
debug_info_entities[entity_id]["subscriptions"][subscription]["count"] -= 1
Expand Down
Loading