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

View file

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

View file

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