Create bound callback_message_received method for handling mqtt callbacks (#117951)

* Create bound callback_message_received method for handling mqtt callbacks

* refactor a bit

* fix ruff

* reduce overhead

* cleanup

* cleanup

* Revert changes alarm_control_panel

* Add sensor and binary sensor

* use same pattern for MqttAttributes/MqttAvailability

* remove useless function since we did not need to add to it

* code cleanup

* collapse

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Jan Bouwhuis 2024-05-24 11:18:25 +02:00 committed by GitHub
parent d4df86da06
commit 9333965b23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 210 additions and 152 deletions

View file

@ -3,6 +3,7 @@
from __future__ import annotations
from datetime import datetime, timedelta
from functools import partial
import logging
from typing import Any
@ -37,13 +38,7 @@ from homeassistant.util import dt as dt_util
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
@ -162,92 +157,95 @@ class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity):
entity=self,
).async_render_with_possible_json_value
@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()
def _state_message_received(self, 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
)
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."""
@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
)
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, off_delay_listener
)
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,
}