Refactor mqtt callbacks for light basic, json and template schema (#118113)

This commit is contained in:
Jan Bouwhuis 2024-05-25 23:24:38 +02:00 committed by GitHub
parent f21c0679b4
commit d4a95b3735
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 429 additions and 441 deletions

View file

@ -3,6 +3,7 @@
from __future__ import annotations
from collections.abc import Callable
from functools import partial
import logging
from typing import Any, cast
@ -53,8 +54,7 @@ from ..const import (
CONF_STATE_VALUE_TEMPLATE,
PAYLOAD_NONE,
)
from ..debug_info import log_messages
from ..mixins import MqttEntity, write_state_on_attr_change
from ..mixins import MqttEntity
from ..models import (
MessageCallbackType,
MqttCommandTemplate,
@ -378,24 +378,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
attr: bool = getattr(self, f"_optimistic_{attribute}")
return attr
def _prepare_subscribe_topics(self) -> None: # noqa: C901
"""(Re)Subscribe to topics."""
topics: dict[str, dict[str, Any]] = {}
def add_topic(topic: str, msg_callback: MessageCallbackType) -> None:
"""Add a topic."""
if self._topic[topic] is not None:
topics[topic] = {
"topic": self._topic[topic],
"msg_callback": msg_callback,
"qos": self._config[CONF_QOS],
"encoding": self._config[CONF_ENCODING] or None,
}
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_is_on"})
def state_received(msg: ReceiveMessage) -> None:
def _state_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages."""
payload = self._value_templates[CONF_STATE_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.NONE
@ -411,18 +395,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
elif payload == PAYLOAD_NONE:
self._attr_is_on = None
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],
"encoding": self._config[CONF_ENCODING] or None,
}
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_brightness"})
def brightness_received(msg: ReceiveMessage) -> None:
def _brightness_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for the brightness."""
payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
@ -439,23 +413,18 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
percent_bright = device_value / self._config[CONF_BRIGHTNESS_SCALE]
self._attr_brightness = min(round(percent_bright * 255), 255)
add_topic(CONF_BRIGHTNESS_STATE_TOPIC, brightness_received)
@callback
def _rgbx_received(
self,
msg: ReceiveMessage,
template: str,
color_mode: ColorMode,
convert_color: Callable[..., tuple[int, ...]],
) -> tuple[int, ...] | None:
"""Handle new MQTT messages for RGBW and RGBWW."""
payload = self._value_templates[template](
msg.payload, PayloadSentinel.DEFAULT
)
"""Process MQTT messages for RGBW and RGBWW."""
payload = self._value_templates[template](msg.payload, PayloadSentinel.DEFAULT)
if payload is PayloadSentinel.DEFAULT or not payload:
_LOGGER.debug(
"Ignoring empty %s message from '%s'", color_mode, msg.topic
)
_LOGGER.debug("Ignoring empty %s message from '%s'", color_mode, msg.topic)
return None
color = tuple(int(val) for val in str(payload).split(","))
if self._optimistic_color_mode:
@ -478,29 +447,19 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
return color
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(
self, {"_attr_brightness", "_attr_color_mode", "_attr_rgb_color"}
)
def rgb_received(msg: ReceiveMessage) -> None:
def _rgb_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for RGB."""
rgb = _rgbx_received(
rgb = self._rgbx_received(
msg, CONF_RGB_VALUE_TEMPLATE, ColorMode.RGB, lambda *x: x
)
if rgb is None:
return
self._attr_rgb_color = cast(tuple[int, int, int], rgb)
add_topic(CONF_RGB_STATE_TOPIC, rgb_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(
self, {"_attr_brightness", "_attr_color_mode", "_attr_rgbw_color"}
)
def rgbw_received(msg: ReceiveMessage) -> None:
def _rgbw_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for RGBW."""
rgbw = _rgbx_received(
rgbw = self._rgbx_received(
msg,
CONF_RGBW_VALUE_TEMPLATE,
ColorMode.RGBW,
@ -510,31 +469,21 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
return
self._attr_rgbw_color = cast(tuple[int, int, int, int], rgbw)
add_topic(CONF_RGBW_STATE_TOPIC, rgbw_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(
self, {"_attr_brightness", "_attr_color_mode", "_attr_rgbww_color"}
)
def rgbww_received(msg: ReceiveMessage) -> None:
def _rgbww_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for RGBWW."""
@callback
def _converter(
r: int, g: int, b: int, cw: int, ww: int
) -> tuple[int, int, int]:
min_kelvin = color_util.color_temperature_mired_to_kelvin(
self.max_mireds
)
max_kelvin = color_util.color_temperature_mired_to_kelvin(
self.min_mireds
)
min_kelvin = color_util.color_temperature_mired_to_kelvin(self.max_mireds)
max_kelvin = color_util.color_temperature_mired_to_kelvin(self.min_mireds)
return color_util.color_rgbww_to_rgb(
r, g, b, cw, ww, min_kelvin, max_kelvin
)
rgbww = _rgbx_received(
rgbww = self._rgbx_received(
msg,
CONF_RGBWW_VALUE_TEMPLATE,
ColorMode.RGBWW,
@ -544,12 +493,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
return
self._attr_rgbww_color = cast(tuple[int, int, int, int, int], rgbww)
add_topic(CONF_RGBWW_STATE_TOPIC, rgbww_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_color_mode"})
def color_mode_received(msg: ReceiveMessage) -> None:
def _color_mode_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for color mode."""
payload = self._value_templates[CONF_COLOR_MODE_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
@ -560,12 +505,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
self._attr_color_mode = ColorMode(str(payload))
add_topic(CONF_COLOR_MODE_STATE_TOPIC, color_mode_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_color_mode", "_attr_color_temp"})
def color_temp_received(msg: ReceiveMessage) -> None:
def _color_temp_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for color temperature."""
payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
@ -578,12 +519,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
self._attr_color_mode = ColorMode.COLOR_TEMP
self._attr_color_temp = int(payload)
add_topic(CONF_COLOR_TEMP_STATE_TOPIC, color_temp_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_effect"})
def effect_received(msg: ReceiveMessage) -> None:
def _effect_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for effect."""
payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
@ -594,12 +531,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
self._attr_effect = str(payload)
add_topic(CONF_EFFECT_STATE_TOPIC, effect_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_color_mode", "_attr_hs_color"})
def hs_received(msg: ReceiveMessage) -> None:
def _hs_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for hs color."""
payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
@ -615,12 +548,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
except ValueError:
_LOGGER.warning("Failed to parse hs state update: '%s'", payload)
add_topic(CONF_HS_STATE_TOPIC, hs_received)
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(self, {"_attr_color_mode", "_attr_xy_color"})
def xy_received(msg: ReceiveMessage) -> None:
def _xy_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for xy color."""
payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
@ -634,7 +563,63 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
self._attr_color_mode = ColorMode.XY
self._attr_xy_color = cast(tuple[float, float], xy_color)
add_topic(CONF_XY_STATE_TOPIC, xy_received)
def _prepare_subscribe_topics(self) -> None: # noqa: C901
"""(Re)Subscribe to topics."""
topics: dict[str, dict[str, Any]] = {}
def add_topic(
topic: str, msg_callback: MessageCallbackType, tracked_attributes: set[str]
) -> None:
"""Add a topic."""
if self._topic[topic] is not None:
topics[topic] = {
"topic": self._topic[topic],
"msg_callback": partial(
self._message_callback, msg_callback, tracked_attributes
),
"entity_id": self.entity_id,
"qos": self._config[CONF_QOS],
"encoding": self._config[CONF_ENCODING] or None,
}
add_topic(CONF_STATE_TOPIC, self._state_received, {"_attr_is_on"})
add_topic(
CONF_BRIGHTNESS_STATE_TOPIC, self._brightness_received, {"_attr_brightness"}
)
add_topic(
CONF_RGB_STATE_TOPIC,
self._rgb_received,
{"_attr_brightness", "_attr_color_mode", "_attr_rgb_color"},
)
add_topic(
CONF_RGBW_STATE_TOPIC,
self._rgbw_received,
{"_attr_brightness", "_attr_color_mode", "_attr_rgbw_color"},
)
add_topic(
CONF_RGBWW_STATE_TOPIC,
self._rgbww_received,
{"_attr_brightness", "_attr_color_mode", "_attr_rgbww_color"},
)
add_topic(
CONF_COLOR_MODE_STATE_TOPIC, self._color_mode_received, {"_attr_color_mode"}
)
add_topic(
CONF_COLOR_TEMP_STATE_TOPIC,
self._color_temp_received,
{"_attr_color_mode", "_attr_color_temp"},
)
add_topic(CONF_EFFECT_STATE_TOPIC, self._effect_received, {"_attr_effect"})
add_topic(
CONF_HS_STATE_TOPIC,
self._hs_received,
{"_attr_color_mode", "_attr_hs_color"},
)
add_topic(
CONF_XY_STATE_TOPIC,
self._xy_received,
{"_attr_color_mode", "_attr_xy_color"},
)
self._sub_state = subscription.async_prepare_subscribe_topics(
self.hass, self._sub_state, topics

View file

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from contextlib import suppress
from functools import partial
import logging
from typing import TYPE_CHECKING, Any, cast
@ -66,8 +67,7 @@ from ..const import (
CONF_STATE_TOPIC,
DOMAIN as MQTT_DOMAIN,
)
from ..debug_info import log_messages
from ..mixins import MqttEntity, write_state_on_attr_change
from ..mixins import MqttEntity
from ..models import ReceiveMessage
from ..schemas import MQTT_ENTITY_COMMON_SCHEMA
from ..util import valid_subscribe_topic
@ -414,27 +414,8 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
self.entity_id,
)
def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(
self,
{
"_attr_brightness",
"_attr_color_temp",
"_attr_effect",
"_attr_hs_color",
"_attr_is_on",
"_attr_rgb_color",
"_attr_rgbw_color",
"_attr_rgbww_color",
"_attr_xy_color",
"color_mode",
},
)
def state_received(msg: ReceiveMessage) -> None:
def _state_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages."""
values = json_loads_object(msg.payload)
@ -509,14 +490,36 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
with suppress(KeyError):
self._attr_effect = cast(str, values["effect"])
if self._topic[CONF_STATE_TOPIC] is not None:
def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
#
if self._topic[CONF_STATE_TOPIC] is None:
return
self._sub_state = subscription.async_prepare_subscribe_topics(
self.hass,
self._sub_state,
{
"state_topic": {
CONF_STATE_TOPIC: {
"topic": self._topic[CONF_STATE_TOPIC],
"msg_callback": state_received,
"msg_callback": partial(
self._message_callback,
self._state_received,
{
"_attr_brightness",
"_attr_color_temp",
"_attr_effect",
"_attr_hs_color",
"_attr_is_on",
"_attr_rgb_color",
"_attr_rgbw_color",
"_attr_rgbww_color",
"_attr_xy_color",
"color_mode",
},
),
"entity_id": self.entity_id,
"qos": self._config[CONF_QOS],
"encoding": self._config[CONF_ENCODING] or None,
}

View file

@ -3,6 +3,7 @@
from __future__ import annotations
from collections.abc import Callable
from functools import partial
import logging
from typing import Any
@ -44,8 +45,7 @@ from ..const import (
CONF_STATE_TOPIC,
PAYLOAD_NONE,
)
from ..debug_info import log_messages
from ..mixins import MqttEntity, write_state_on_attr_change
from ..mixins import MqttEntity
from ..models import (
MqttCommandTemplate,
MqttValueTemplate,
@ -188,23 +188,8 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
# Support for ct + hs, prioritize hs
self._attr_color_mode = ColorMode.HS if self.hs_color else ColorMode.COLOR_TEMP
def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
@callback
@log_messages(self.hass, self.entity_id)
@write_state_on_attr_change(
self,
{
"_attr_brightness",
"_attr_color_mode",
"_attr_color_temp",
"_attr_effect",
"_attr_hs_color",
"_attr_is_on",
},
)
def state_received(msg: ReceiveMessage) -> None:
def _state_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages."""
state = self._value_templates[CONF_STATE_TEMPLATE](msg.payload)
if state == STATE_ON:
@ -229,9 +214,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
)
except ValueError:
_LOGGER.warning(
"Invalid brightness value received from %s", msg.topic
)
_LOGGER.warning("Invalid brightness value received from %s", msg.topic)
if CONF_COLOR_TEMP_TEMPLATE in self._config:
try:
@ -272,14 +255,31 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
else:
_LOGGER.warning("Unsupported effect value received")
if self._topics[CONF_STATE_TOPIC] is not None:
def _prepare_subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
if self._topics[CONF_STATE_TOPIC] is None:
return
self._sub_state = subscription.async_prepare_subscribe_topics(
self.hass,
self._sub_state,
{
"state_topic": {
"topic": self._topics[CONF_STATE_TOPIC],
"msg_callback": state_received,
"msg_callback": partial(
self._message_callback,
self._state_received,
{
"_attr_brightness",
"_attr_color_mode",
"_attr_color_temp",
"_attr_effect",
"_attr_hs_color",
"_attr_is_on",
},
),
"entity_id": self.entity_id,
"qos": self._config[CONF_QOS],
"encoding": self._config[CONF_ENCODING] or None,
}