diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index bf2ae33ca1d..19d059ae8cc 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -1,5 +1,6 @@ """Support for MQTT lights.""" import logging +from typing import cast import voluptuous as vol @@ -256,18 +257,6 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize MQTT light.""" - self._brightness = None - self._color_mode = None - self._color_temp = None - self._effect = None - self._hs_color = None - self._rgb_color = None - self._rgbw_color = None - self._rgbww_color = None - self._state = None - self._supported_color_modes = None - self._xy_color = None - self._topic = None self._payload = None self._command_templates = None @@ -292,6 +281,10 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" + self._attr_min_mireds = config.get(CONF_MIN_MIREDS, super().min_mireds) + self._attr_max_mireds = config.get(CONF_MAX_MIREDS, super().max_mireds) + self._attr_effect_list = config.get(CONF_EFFECT_LIST) + if CONF_STATE_VALUE_TEMPLATE not in config and CONF_VALUE_TEMPLATE in config: config[CONF_STATE_VALUE_TEMPLATE] = config[CONF_VALUE_TEMPLATE] @@ -378,39 +371,47 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): supported_color_modes = set() if topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.COLOR_TEMP) - self._color_mode = ColorMode.COLOR_TEMP + self._attr_color_mode = ColorMode.COLOR_TEMP if topic[CONF_HS_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.HS) - self._color_mode = ColorMode.HS + self._attr_color_mode = ColorMode.HS if topic[CONF_RGB_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.RGB) - self._color_mode = ColorMode.RGB + self._attr_color_mode = ColorMode.RGB if topic[CONF_RGBW_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.RGBW) - self._color_mode = ColorMode.RGBW + self._attr_color_mode = ColorMode.RGBW if topic[CONF_RGBWW_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.RGBWW) - self._color_mode = ColorMode.RGBWW + self._attr_color_mode = ColorMode.RGBWW if topic[CONF_WHITE_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.WHITE) if topic[CONF_XY_COMMAND_TOPIC] is not None: supported_color_modes.add(ColorMode.XY) - self._color_mode = ColorMode.XY + self._attr_color_mode = ColorMode.XY if len(supported_color_modes) > 1: - self._color_mode = ColorMode.UNKNOWN + self._attr_color_mode = ColorMode.UNKNOWN if not supported_color_modes: if topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None: - self._color_mode = ColorMode.BRIGHTNESS + self._attr_color_mode = ColorMode.BRIGHTNESS supported_color_modes.add(ColorMode.BRIGHTNESS) else: - self._color_mode = ColorMode.ONOFF + self._attr_color_mode = ColorMode.ONOFF supported_color_modes.add(ColorMode.ONOFF) # Validate the color_modes configuration - self._supported_color_modes = valid_supported_color_modes(supported_color_modes) + self._attr_supported_color_modes = valid_supported_color_modes( + supported_color_modes + ) - def _is_optimistic(self, attribute): + supported_features: int = 0 + supported_features |= ( + topic[CONF_EFFECT_COMMAND_TOPIC] is not None and LightEntityFeature.EFFECT + ) + self._attr_supported_features = supported_features + + def _is_optimistic(self, attribute: str) -> bool: """Return True if the attribute is optimistically updated.""" return getattr(self, f"_optimistic_{attribute}") @@ -438,11 +439,11 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): return if payload == self._payload["on"]: - self._state = True + self._attr_is_on = True elif payload == self._payload["off"]: - self._state = False + self._attr_is_on = False elif payload == PAYLOAD_NONE: - self._state = None + self._attr_is_on = None get_mqtt_data(self.hass).state_write_requests.write_state_request(self) if self._topic[CONF_STATE_TOPIC] is not None: @@ -466,7 +467,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): device_value = float(payload) percent_bright = device_value / self._config[CONF_BRIGHTNESS_SCALE] - self._brightness = percent_bright * 255 + self._attr_brightness = min(round(percent_bright * 255), 255) + get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_BRIGHTNESS_STATE_TOPIC, brightness_received) @@ -483,11 +485,11 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): return None color = tuple(int(val) for val in payload.split(",")) if self._optimistic_color_mode: - self._color_mode = color_mode + self._attr_color_mode = color_mode if self._topic[CONF_BRIGHTNESS_STATE_TOPIC] is None: rgb = convert_color(*color) percent_bright = float(color_util.color_RGB_to_hsv(*rgb)[2]) / 100.0 - self._brightness = percent_bright * 255 + self._attr_brightness = min(round(percent_bright * 255), 255) return color @callback @@ -499,7 +501,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): ) if not rgb: return - self._rgb_color = rgb + self._attr_rgb_color = rgb get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_RGB_STATE_TOPIC, rgb_received) @@ -516,7 +518,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): ) if not rgbw: return - self._rgbw_color = rgbw + self._attr_rgbw_color = rgbw get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_RGBW_STATE_TOPIC, rgbw_received) @@ -533,7 +535,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): ) if not rgbww: return - self._rgbww_color = rgbww + self._attr_rgbww_color = rgbww get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_RGBWW_STATE_TOPIC, rgbww_received) @@ -549,7 +551,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): _LOGGER.debug("Ignoring empty color mode message from '%s'", msg.topic) return - self._color_mode = payload + self._attr_color_mode = payload get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_COLOR_MODE_STATE_TOPIC, color_mode_received) @@ -566,8 +568,8 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): return if self._optimistic_color_mode: - self._color_mode = ColorMode.COLOR_TEMP - self._color_temp = int(payload) + self._attr_color_mode = ColorMode.COLOR_TEMP + self._attr_color_temp = int(payload) get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_COLOR_TEMP_STATE_TOPIC, color_temp_received) @@ -583,7 +585,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): _LOGGER.debug("Ignoring empty effect message from '%s'", msg.topic) return - self._effect = payload + self._attr_effect = payload get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_EFFECT_STATE_TOPIC, effect_received) @@ -599,10 +601,13 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): _LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic) return try: - hs_color = tuple(float(val) for val in payload.split(",", 2)) + hs_color = cast( + tuple[float, float], + tuple(float(val) for val in payload.split(",", 2)), + ) if self._optimistic_color_mode: - self._color_mode = ColorMode.HS - self._hs_color = hs_color + self._attr_color_mode = ColorMode.HS + self._attr_hs_color = hs_color get_mqtt_data(self.hass).state_write_requests.write_state_request(self) except ValueError: _LOGGER.debug("Failed to parse hs state update: '%s'", payload) @@ -620,10 +625,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): _LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic) return - xy_color = tuple(float(val) for val in payload.split(",")) + xy_color = cast( + tuple[float, float], tuple(float(val) for val in payload.split(",", 2)) + ) if self._optimistic_color_mode: - self._color_mode = ColorMode.XY - self._xy_color = xy_color + self._attr_color_mode = ColorMode.XY + self._attr_xy_color = xy_color get_mqtt_data(self.hass).state_write_requests.write_state_request(self) add_topic(CONF_XY_STATE_TOPIC, xy_received) @@ -643,10 +650,10 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): condition_attribute = attribute optimistic = self._is_optimistic(condition_attribute) if optimistic and last_state and last_state.attributes.get(attribute): - setattr(self, f"_{attribute}", last_state.attributes[attribute]) + setattr(self, f"_attr_{attribute}", last_state.attributes[attribute]) if self._topic[CONF_STATE_TOPIC] is None and self._optimistic and last_state: - self._state = last_state.state == STATE_ON + self._attr_is_on = last_state.state == STATE_ON restore_state(ATTR_BRIGHTNESS) restore_state(ATTR_RGB_COLOR) restore_state(ATTR_HS_COLOR, ATTR_RGB_COLOR) @@ -659,93 +666,11 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): restore_state(ATTR_XY_COLOR) restore_state(ATTR_HS_COLOR, ATTR_XY_COLOR) - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - if brightness := self._brightness: - brightness = min(round(brightness), 255) - return brightness - - @property - def color_mode(self): - """Return current color mode.""" - return self._color_mode - - @property - def hs_color(self): - """Return the hs color value.""" - return self._hs_color - - @property - def rgb_color(self): - """Return the rgb color value.""" - return self._rgb_color - - @property - def rgbw_color(self): - """Return the rgbw color value.""" - return self._rgbw_color - - @property - def rgbww_color(self): - """Return the rgbww color value.""" - return self._rgbww_color - - @property - def xy_color(self): - """Return the xy color value.""" - return self._xy_color - - @property - def color_temp(self): - """Return the color temperature in mired.""" - return self._color_temp - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return self._config.get(CONF_MIN_MIREDS, super().min_mireds) - - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return self._config.get(CONF_MAX_MIREDS, super().max_mireds) - - @property - def is_on(self): - """Return true if device is on.""" - return self._state - @property def assumed_state(self): """Return true if we do optimistic updates.""" return self._optimistic - @property - def effect_list(self): - """Return the list of supported effects.""" - return self._config.get(CONF_EFFECT_LIST) - - @property - def effect(self): - """Return the current effect.""" - return self._effect - - @property - def supported_color_modes(self): - """Flag supported color modes.""" - return self._supported_color_modes - - @property - def supported_features(self): - """Flag supported features.""" - supported_features = 0 - supported_features |= ( - self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None - and LightEntityFeature.EFFECT - ) - return supported_features - async def async_turn_on(self, **kwargs): # noqa: C901 """Turn the device on. @@ -772,9 +697,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): if self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None: brightness = 255 else: - brightness = kwargs.get( - ATTR_BRIGHTNESS, self._brightness if self._brightness else 255 - ) + brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) return tuple(int(channel * brightness / 255) for channel in color) def render_rgbx(color, template, color_mode): @@ -797,8 +720,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): if not self._is_optimistic(condition_attribute): return False if color_mode and self._optimistic_color_mode: - self._color_mode = color_mode - setattr(self, f"_{attribute}", value) + self._attr_color_mode = color_mode + + setattr(self, f"_attr_{attribute}", value) return True if on_command_type == "first": @@ -813,7 +737,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and ATTR_BRIGHTNESS not in kwargs and ATTR_WHITE not in kwargs ): - kwargs[ATTR_BRIGHTNESS] = self._brightness if self._brightness else 255 + kwargs[ATTR_BRIGHTNESS] = self.brightness or 255 hs_color = kwargs.get(ATTR_HS_COLOR) @@ -871,7 +795,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and ATTR_RGB_COLOR not in kwargs and self._topic[CONF_RGB_COMMAND_TOPIC] is not None ): - rgb_color = self._rgb_color if self._rgb_color is not None else (255,) * 3 + rgb_color = self.rgb_color or (255,) * 3 rgb = scale_rgbx(rgb_color, kwargs[ATTR_BRIGHTNESS]) rgb_s = render_rgbx(rgb, CONF_RGB_COMMAND_TEMPLATE, ColorMode.RGB) await publish(CONF_RGB_COMMAND_TOPIC, rgb_s) @@ -881,9 +805,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and ATTR_RGBW_COLOR not in kwargs and self._topic[CONF_RGBW_COMMAND_TOPIC] is not None ): - rgbw_color = ( - self._rgbw_color if self._rgbw_color is not None else (255,) * 4 - ) + rgbw_color = self.rgbw_color or (255,) * 4 rgbw = scale_rgbx(rgbw_color, kwargs[ATTR_BRIGHTNESS]) rgbw_s = render_rgbx(rgbw, CONF_RGBW_COMMAND_TEMPLATE, ColorMode.RGBW) await publish(CONF_RGBW_COMMAND_TOPIC, rgbw_s) @@ -893,9 +815,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): and ATTR_RGBWW_COLOR not in kwargs and self._topic[CONF_RGBWW_COMMAND_TOPIC] is not None ): - rgbww_color = ( - self._rgbww_color if self._rgbww_color is not None else (255,) * 5 - ) + rgbww_color = self.rgbww_color or (255,) * 5 rgbww = scale_rgbx(rgbww_color, kwargs[ATTR_BRIGHTNESS]) rgbww_s = render_rgbx(rgbww, CONF_RGBWW_COMMAND_TEMPLATE, ColorMode.RGBWW) await publish(CONF_RGBWW_COMMAND_TOPIC, rgbww_s) @@ -938,7 +858,7 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): if self._optimistic: # Optimistically assume that the light has changed state. - self._state = True + self._attr_is_on = True should_update = True if should_update: @@ -959,5 +879,5 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): if self._optimistic: # Optimistically assume that the light has changed state. - self._state = False + self._attr_is_on = False self.async_write_ha_state() diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index a4a76673176..b5824e5e456 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -188,22 +188,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize MQTT JSON light.""" - self._state = None - self._supported_features = 0 - self._topic = None self._optimistic = False - self._brightness = None - self._color_mode = None - self._color_temp = None - self._effect = None self._fixed_color_mode = None self._flash_times = None - self._hs = None - self._rgb = None - self._rgbw = None - self._rgbww = None - self._xy = None MqttEntity.__init__(self, hass, config, config_entry, discovery_data) @@ -214,6 +202,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" + self._attr_max_mireds = config.get(CONF_MAX_MIREDS, super().max_mireds) + self._attr_min_mireds = config.get(CONF_MIN_MIREDS, super().min_mireds) + self._attr_effect_list = config.get(CONF_EFFECT_LIST) + self._topic = { key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC) } @@ -225,10 +217,12 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): for key in (CONF_FLASH_TIME_SHORT, CONF_FLASH_TIME_LONG) } - self._supported_features = ( + self._attr_supported_features = ( LightEntityFeature.TRANSITION | LightEntityFeature.FLASH ) - self._supported_features |= config[CONF_EFFECT] and LightEntityFeature.EFFECT + self._attr_supported_features |= ( + config[CONF_EFFECT] and LightEntityFeature.EFFECT + ) if not self._config[CONF_COLOR_MODE]: color_modes = {ColorMode.ONOFF} if config[CONF_BRIGHTNESS]: @@ -237,13 +231,13 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): color_modes.add(ColorMode.COLOR_TEMP) if config[CONF_HS] or config[CONF_RGB] or config[CONF_XY]: color_modes.add(ColorMode.HS) - self._supported_color_modes = filter_supported_color_modes(color_modes) - if len(self._supported_color_modes) == 1: - self._fixed_color_mode = next(iter(self._supported_color_modes)) + self._attr_supported_color_modes = filter_supported_color_modes(color_modes) + if len(self.supported_color_modes) == 1: + self._fixed_color_mode = next(iter(self.supported_color_modes)) else: - self._supported_color_modes = self._config[CONF_SUPPORTED_COLOR_MODES] - if len(self._supported_color_modes) == 1: - self._color_mode = next(iter(self._supported_color_modes)) + self._attr_supported_color_modes = self._config[CONF_SUPPORTED_COLOR_MODES] + if len(self.supported_color_modes) == 1: + self._attr_color_mode = next(iter(self.supported_color_modes)) def _update_color(self, values): if not self._config[CONF_COLOR_MODE]: @@ -252,7 +246,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): red = int(values["color"]["r"]) green = int(values["color"]["g"]) blue = int(values["color"]["b"]) - self._hs = color_util.color_RGB_to_hs(red, green, blue) + self._attr_hs_color = color_util.color_RGB_to_hs(red, green, blue) except KeyError: pass except ValueError: @@ -264,7 +258,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): try: x_color = float(values["color"]["x"]) y_color = float(values["color"]["y"]) - self._hs = color_util.color_xy_to_hs(x_color, y_color) + self._attr_hs_color = color_util.color_xy_to_hs(x_color, y_color) except KeyError: pass except ValueError: @@ -276,7 +270,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): try: hue = float(values["color"]["h"]) saturation = float(values["color"]["s"]) - self._hs = (hue, saturation) + self._attr_hs_color = (hue, saturation) except KeyError: pass except ValueError: @@ -293,41 +287,41 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): return try: if color_mode == ColorMode.COLOR_TEMP: - self._color_temp = int(values["color_temp"]) - self._color_mode = ColorMode.COLOR_TEMP + self._attr_color_temp = int(values["color_temp"]) + self._attr_color_mode = ColorMode.COLOR_TEMP elif color_mode == ColorMode.HS: hue = float(values["color"]["h"]) saturation = float(values["color"]["s"]) - self._color_mode = ColorMode.HS - self._hs = (hue, saturation) + self._attr_color_mode = ColorMode.HS + self._attr_hs_color = (hue, saturation) elif color_mode == ColorMode.RGB: r = int(values["color"]["r"]) # pylint: disable=invalid-name g = int(values["color"]["g"]) # pylint: disable=invalid-name b = int(values["color"]["b"]) # pylint: disable=invalid-name - self._color_mode = ColorMode.RGB - self._rgb = (r, g, b) + self._attr_color_mode = ColorMode.RGB + self._attr_rgb_color = (r, g, b) elif color_mode == ColorMode.RGBW: r = int(values["color"]["r"]) # pylint: disable=invalid-name g = int(values["color"]["g"]) # pylint: disable=invalid-name b = int(values["color"]["b"]) # pylint: disable=invalid-name w = int(values["color"]["w"]) # pylint: disable=invalid-name - self._color_mode = ColorMode.RGBW - self._rgbw = (r, g, b, w) + self._attr_color_mode = ColorMode.RGBW + self._attr_rgbw_color = (r, g, b, w) elif color_mode == ColorMode.RGBWW: r = int(values["color"]["r"]) # pylint: disable=invalid-name g = int(values["color"]["g"]) # pylint: disable=invalid-name b = int(values["color"]["b"]) # pylint: disable=invalid-name c = int(values["color"]["c"]) # pylint: disable=invalid-name w = int(values["color"]["w"]) # pylint: disable=invalid-name - self._color_mode = ColorMode.RGBWW - self._rgbww = (r, g, b, c, w) + self._attr_color_mode = ColorMode.RGBWW + self._attr_rgbww_color = (r, g, b, c, w) elif color_mode == ColorMode.WHITE: - self._color_mode = ColorMode.WHITE + self._attr_color_mode = ColorMode.WHITE elif color_mode == ColorMode.XY: x = float(values["color"]["x"]) # pylint: disable=invalid-name y = float(values["color"]["y"]) # pylint: disable=invalid-name - self._color_mode = ColorMode.XY - self._xy = (x, y) + self._attr_color_mode = ColorMode.XY + self._attr_xy_color = (x, y) except (KeyError, ValueError): _LOGGER.warning( "Invalid or incomplete color value received for entity %s", @@ -344,29 +338,29 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): values = json_loads(msg.payload) if values["state"] == "ON": - self._state = True + self._attr_is_on = True elif values["state"] == "OFF": - self._state = False + self._attr_is_on = False elif values["state"] is None: - self._state = None + self._attr_is_on = None if ( not self._config[CONF_COLOR_MODE] - and color_supported(self._supported_color_modes) + and color_supported(self.supported_color_modes) and "color" in values ): # Deprecated color handling if values["color"] is None: - self._hs = None + self._attr_hs_color = None else: self._update_color(values) if self._config[CONF_COLOR_MODE] and "color_mode" in values: self._update_color(values) - if brightness_supported(self._supported_color_modes): + if brightness_supported(self.supported_color_modes): try: - self._brightness = int( + self._attr_brightness = int( values["brightness"] / float(self._config[CONF_BRIGHTNESS_SCALE]) * 255 @@ -380,15 +374,15 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): ) if ( - ColorMode.COLOR_TEMP in self._supported_color_modes + ColorMode.COLOR_TEMP in self.supported_color_modes and not self._config[CONF_COLOR_MODE] ): # Deprecated color handling try: if values["color_temp"] is None: - self._color_temp = None + self._attr_color_temp = None else: - self._color_temp = int(values["color_temp"]) + self._attr_color_temp = int(values["color_temp"]) except KeyError: pass except ValueError: @@ -397,9 +391,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): self.entity_id, ) - if self._supported_features and LightEntityFeature.EFFECT: + if self.supported_features and LightEntityFeature.EFFECT: with suppress(KeyError): - self._effect = values["effect"] + self._attr_effect = values["effect"] get_mqtt_data(self.hass).state_write_requests.write_state_request(self) @@ -423,77 +417,27 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): last_state = await self.async_get_last_state() if self._optimistic and last_state: - self._state = last_state.state == STATE_ON + self._attr_is_on = last_state.state == STATE_ON last_attributes = last_state.attributes - self._brightness = last_attributes.get(ATTR_BRIGHTNESS, self._brightness) - self._color_mode = last_attributes.get(ATTR_COLOR_MODE, self._color_mode) - self._color_temp = last_attributes.get(ATTR_COLOR_TEMP, self._color_temp) - self._effect = last_attributes.get(ATTR_EFFECT, self._effect) - self._hs = last_attributes.get(ATTR_HS_COLOR, self._hs) - self._rgb = last_attributes.get(ATTR_RGB_COLOR, self._rgb) - self._rgbw = last_attributes.get(ATTR_RGBW_COLOR, self._rgbw) - self._rgbww = last_attributes.get(ATTR_RGBWW_COLOR, self._rgbww) - self._xy = last_attributes.get(ATTR_XY_COLOR, self._xy) - - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - return self._brightness - - @property - def color_temp(self): - """Return the color temperature in mired.""" - return self._color_temp - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return self._config.get(CONF_MIN_MIREDS, super().min_mireds) - - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return self._config.get(CONF_MAX_MIREDS, super().max_mireds) - - @property - def effect(self): - """Return the current effect.""" - return self._effect - - @property - def effect_list(self): - """Return the list of supported effects.""" - return self._config.get(CONF_EFFECT_LIST) - - @property - def hs_color(self): - """Return the hs color value.""" - return self._hs - - @property - def rgb_color(self): - """Return the hs color value.""" - return self._rgb - - @property - def rgbw_color(self): - """Return the hs color value.""" - return self._rgbw - - @property - def rgbww_color(self): - """Return the hs color value.""" - return self._rgbww - - @property - def xy_color(self): - """Return the hs color value.""" - return self._xy - - @property - def is_on(self): - """Return true if device is on.""" - return self._state + self._attr_brightness = last_attributes.get( + ATTR_BRIGHTNESS, self.brightness + ) + self._attr_color_mode = last_attributes.get( + ATTR_COLOR_MODE, self.color_mode + ) + self._attr_color_temp = last_attributes.get( + ATTR_COLOR_TEMP, self.color_temp + ) + self._attr_effect = last_attributes.get(ATTR_EFFECT, self.effect) + self._attr_hs_color = last_attributes.get(ATTR_HS_COLOR, self.hs_color) + self._attr_rgb_color = last_attributes.get(ATTR_RGB_COLOR, self.rgb_color) + self._attr_rgbw_color = last_attributes.get( + ATTR_RGBW_COLOR, self.rgbw_color + ) + self._attr_rgbww_color = last_attributes.get( + ATTR_RGBWW_COLOR, self.rgbww_color + ) + self._attr_xy_color = last_attributes.get(ATTR_XY_COLOR, self.xy_color) @property def assumed_state(self): @@ -504,25 +448,15 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): def color_mode(self): """Return current color mode.""" if self._config[CONF_COLOR_MODE]: - return self._color_mode + return self._attr_color_mode if self._fixed_color_mode: # Legacy light with support for a single color mode return self._fixed_color_mode # Legacy light with support for ct + hs, prioritize hs - if self._hs is not None: + if self.hs_color is not None: return ColorMode.HS return ColorMode.COLOR_TEMP - @property - def supported_color_modes(self): - """Flag supported color modes.""" - return self._supported_color_modes - - @property - def supported_features(self): - """Flag supported features.""" - return self._supported_features - def _set_flash_and_transition(self, message, **kwargs): if ATTR_TRANSITION in kwargs: message["transition"] = kwargs[ATTR_TRANSITION] @@ -587,32 +521,32 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): message["color"]["s"] = hs_color[1] if self._optimistic: - self._color_temp = None - self._hs = kwargs[ATTR_HS_COLOR] + self._attr_color_temp = None + self._attr_hs_color = kwargs[ATTR_HS_COLOR] should_update = True if ATTR_HS_COLOR in kwargs and self._supports_color_mode(ColorMode.HS): hs_color = kwargs[ATTR_HS_COLOR] message["color"] = {"h": hs_color[0], "s": hs_color[1]} if self._optimistic: - self._color_mode = ColorMode.HS - self._hs = hs_color + self._attr_color_mode = ColorMode.HS + self._attr_hs_color = hs_color should_update = True if ATTR_RGB_COLOR in kwargs and self._supports_color_mode(ColorMode.RGB): rgb = self._scale_rgbxx(kwargs[ATTR_RGB_COLOR], kwargs) message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2]} if self._optimistic: - self._color_mode = ColorMode.RGB - self._rgb = rgb + self._attr_color_mode = ColorMode.RGB + self._attr_rgb_color = rgb should_update = True if ATTR_RGBW_COLOR in kwargs and self._supports_color_mode(ColorMode.RGBW): rgb = self._scale_rgbxx(kwargs[ATTR_RGBW_COLOR], kwargs) message["color"] = {"r": rgb[0], "g": rgb[1], "b": rgb[2], "w": rgb[3]} if self._optimistic: - self._color_mode = ColorMode.RGBW - self._rgbw = rgb + self._attr_color_mode = ColorMode.RGBW + self._attr_rgbw_color = rgb should_update = True if ATTR_RGBWW_COLOR in kwargs and self._supports_color_mode(ColorMode.RGBWW): @@ -625,16 +559,16 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): "w": rgb[4], } if self._optimistic: - self._color_mode = ColorMode.RGBWW - self._rgbww = rgb + self._attr_color_mode = ColorMode.RGBWW + self._attr_rgbww_color = rgb should_update = True if ATTR_XY_COLOR in kwargs and self._supports_color_mode(ColorMode.XY): xy = kwargs[ATTR_XY_COLOR] # pylint: disable=invalid-name message["color"] = {"x": xy[0], "y": xy[1]} if self._optimistic: - self._color_mode = ColorMode.XY - self._xy = xy + self._attr_color_mode = ColorMode.XY + self._attr_xy_color = xy should_update = True self._set_flash_and_transition(message, **kwargs) @@ -650,23 +584,23 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): message["brightness"] = device_brightness if self._optimistic: - self._brightness = kwargs[ATTR_BRIGHTNESS] + self._attr_brightness = kwargs[ATTR_BRIGHTNESS] should_update = True if ATTR_COLOR_TEMP in kwargs: message["color_temp"] = int(kwargs[ATTR_COLOR_TEMP]) if self._optimistic: - self._color_mode = ColorMode.COLOR_TEMP - self._color_temp = kwargs[ATTR_COLOR_TEMP] - self._hs = None + self._attr_color_mode = ColorMode.COLOR_TEMP + self._attr_color_temp = kwargs[ATTR_COLOR_TEMP] + self._attr_hs_color = None should_update = True if ATTR_EFFECT in kwargs: message["effect"] = kwargs[ATTR_EFFECT] if self._optimistic: - self._effect = kwargs[ATTR_EFFECT] + self._attr_effect = kwargs[ATTR_EFFECT] should_update = True if ATTR_WHITE in kwargs and self._supports_color_mode(ColorMode.WHITE): @@ -678,8 +612,8 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): message["white"] = device_white_level if self._optimistic: - self._color_mode = ColorMode.WHITE - self._brightness = kwargs[ATTR_WHITE] + self._attr_color_mode = ColorMode.WHITE + self._attr_brightness = kwargs[ATTR_WHITE] should_update = True await self.async_publish( @@ -692,7 +626,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): if self._optimistic: # Optimistically assume that the light has changed state. - self._state = True + self._attr_is_on = True should_update = True if should_update: @@ -717,5 +651,5 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): if self._optimistic: # Optimistically assume that the light has changed state. - self._state = False + self._attr_is_on = False self.async_write_ha_state() diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 33c7f1cea1b..b57e09e0c1f 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -121,18 +121,12 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): def __init__(self, hass, config, config_entry, discovery_data): """Initialize a MQTT Template light.""" - self._state = None - self._topics = None self._templates = None self._optimistic = False # features - self._brightness = None self._fixed_color_mode = None - self._color_temp = None - self._hs = None - self._effect = None MqttEntity.__init__(self, hass, config, config_entry, discovery_data) @@ -143,6 +137,10 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(Re)Setup the entity.""" + self._attr_max_mireds = config.get(CONF_MAX_MIREDS, super().max_mireds) + self._attr_min_mireds = config.get(CONF_MIN_MIREDS, super().min_mireds) + self._attr_effect_list = config.get(CONF_EFFECT_LIST) + self._topics = { key: config.get(key) for key in (CONF_STATE_TOPIC, CONF_COMMAND_TOPIC) } @@ -178,9 +176,23 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): and self._templates[CONF_BLUE_TEMPLATE] is not None ): color_modes.add(ColorMode.HS) - self._supported_color_modes = filter_supported_color_modes(color_modes) - if len(self._supported_color_modes) == 1: - self._fixed_color_mode = next(iter(self._supported_color_modes)) + self._attr_supported_color_modes = filter_supported_color_modes(color_modes) + self._fixed_color_mode = None + if len(self.supported_color_modes) == 1: + self._fixed_color_mode = next(iter(self.supported_color_modes)) + self._attr_color_mode = self._fixed_color_mode + + features = LightEntityFeature.FLASH | LightEntityFeature.TRANSITION + if config.get(CONF_EFFECT_LIST) is not None: + features = features | LightEntityFeature.EFFECT + self._attr_supported_features = features + + def _update_color_mode(self): + """Update the color_mode attribute.""" + if self._fixed_color_mode: + return + # 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): """(Re)Subscribe to topics.""" @@ -196,17 +208,17 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): CONF_STATE_TEMPLATE ].async_render_with_possible_json_value(msg.payload) if state == STATE_ON: - self._state = True + self._attr_is_on = True elif state == STATE_OFF: - self._state = False + self._attr_is_on = False elif state == PAYLOAD_NONE: - self._state = None + self._attr_is_on = None else: _LOGGER.warning("Invalid state value received") if self._templates[CONF_BRIGHTNESS_TEMPLATE] is not None: try: - self._brightness = int( + self._attr_brightness = int( self._templates[ CONF_BRIGHTNESS_TEMPLATE ].async_render_with_possible_json_value(msg.payload) @@ -219,7 +231,9 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): color_temp = self._templates[ CONF_COLOR_TEMP_TEMPLATE ].async_render_with_possible_json_value(msg.payload) - self._color_temp = int(color_temp) if color_temp != "None" else None + self._attr_color_temp = ( + int(color_temp) if color_temp != "None" else None + ) except ValueError: _LOGGER.warning("Invalid color temperature value received") @@ -239,11 +253,12 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): CONF_BLUE_TEMPLATE ].async_render_with_possible_json_value(msg.payload) if red == "None" and green == "None" and blue == "None": - self._hs = None + self._attr_hs_color = None else: - self._hs = color_util.color_RGB_to_hs( + self._attr_hs_color = color_util.color_RGB_to_hs( int(red), int(green), int(blue) ) + self._update_color_mode() except ValueError: _LOGGER.warning("Invalid color value received") @@ -253,7 +268,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): ].async_render_with_possible_json_value(msg.payload) if effect in self._config.get(CONF_EFFECT_LIST): - self._effect = effect + self._attr_effect = effect else: _LOGGER.warning("Unsupported effect value received") @@ -279,61 +294,22 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): last_state = await self.async_get_last_state() if self._optimistic and last_state: - self._state = last_state.state == STATE_ON + self._attr_is_on = last_state.state == STATE_ON if last_state.attributes.get(ATTR_BRIGHTNESS): - self._brightness = last_state.attributes.get(ATTR_BRIGHTNESS) + self._attr_brightness = last_state.attributes.get(ATTR_BRIGHTNESS) if last_state.attributes.get(ATTR_HS_COLOR): - self._hs = last_state.attributes.get(ATTR_HS_COLOR) + self._attr_hs_color = last_state.attributes.get(ATTR_HS_COLOR) + self._update_color_mode() if last_state.attributes.get(ATTR_COLOR_TEMP): - self._color_temp = last_state.attributes.get(ATTR_COLOR_TEMP) + self._attr_color_temp = last_state.attributes.get(ATTR_COLOR_TEMP) if last_state.attributes.get(ATTR_EFFECT): - self._effect = last_state.attributes.get(ATTR_EFFECT) - - @property - def brightness(self): - """Return the brightness of this light between 0..255.""" - return self._brightness - - @property - def color_temp(self): - """Return the color temperature in mired.""" - return self._color_temp - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return self._config.get(CONF_MIN_MIREDS, super().min_mireds) - - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return self._config.get(CONF_MAX_MIREDS, super().max_mireds) - - @property - def hs_color(self): - """Return the hs color value [int, int].""" - return self._hs - - @property - def is_on(self): - """Return True if entity is on.""" - return self._state + self._attr_effect = last_state.attributes.get(ATTR_EFFECT) @property def assumed_state(self): """Return True if unable to access real state of the entity.""" return self._optimistic - @property - def effect_list(self): - """Return the list of supported effects.""" - return self._config.get(CONF_EFFECT_LIST) - - @property - def effect(self): - """Return the current effect.""" - return self._effect - async def async_turn_on(self, **kwargs): """Turn the entity on. @@ -341,20 +317,21 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): """ values = {"state": True} if self._optimistic: - self._state = True + self._attr_is_on = True if ATTR_BRIGHTNESS in kwargs: values["brightness"] = int(kwargs[ATTR_BRIGHTNESS]) if self._optimistic: - self._brightness = kwargs[ATTR_BRIGHTNESS] + self._attr_brightness = kwargs[ATTR_BRIGHTNESS] if ATTR_COLOR_TEMP in kwargs: values["color_temp"] = int(kwargs[ATTR_COLOR_TEMP]) if self._optimistic: - self._color_temp = kwargs[ATTR_COLOR_TEMP] - self._hs = None + self._attr_color_temp = kwargs[ATTR_COLOR_TEMP] + self._attr_hs_color = None + self._update_color_mode() if ATTR_HS_COLOR in kwargs: hs_color = kwargs[ATTR_HS_COLOR] @@ -366,7 +343,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): else: brightness = kwargs.get( ATTR_BRIGHTNESS, - self._brightness if self._brightness is not None else 255, + self._attr_brightness if self._attr_brightness is not None else 255, ) rgb = color_util.color_hsv_to_RGB( hs_color[0], hs_color[1], brightness / 255 * 100 @@ -378,14 +355,15 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): values["sat"] = hs_color[1] if self._optimistic: - self._color_temp = None - self._hs = kwargs[ATTR_HS_COLOR] + self._attr_color_temp = None + self._attr_hs_color = kwargs[ATTR_HS_COLOR] + self._update_color_mode() if ATTR_EFFECT in kwargs: values["effect"] = kwargs.get(ATTR_EFFECT) if self._optimistic: - self._effect = kwargs[ATTR_EFFECT] + self._attr_effect = kwargs[ATTR_EFFECT] if ATTR_FLASH in kwargs: values["flash"] = kwargs.get(ATTR_FLASH) @@ -413,7 +391,7 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): """ values = {"state": False} if self._optimistic: - self._state = False + self._attr_is_on = False if ATTR_TRANSITION in kwargs: values["transition"] = kwargs[ATTR_TRANSITION] @@ -430,27 +408,3 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): if self._optimistic: self.async_write_ha_state() - - @property - def color_mode(self): - """Return current color mode.""" - if self._fixed_color_mode: - return self._fixed_color_mode - # Support for ct + hs, prioritize hs - if self._hs is not None: - return ColorMode.HS - return ColorMode.COLOR_TEMP - - @property - def supported_color_modes(self): - """Flag supported color modes.""" - return self._supported_color_modes - - @property - def supported_features(self): - """Flag supported features.""" - features = LightEntityFeature.FLASH | LightEntityFeature.TRANSITION - if self._config.get(CONF_EFFECT_LIST) is not None: - features = features | LightEntityFeature.EFFECT - - return features