Use _attr_ for MQTT light (#81465)

* Schema basic

* Schema json

* Schema template

* add color_mode - follow up comments

* Fix regression

* Follow up comments 2

* Fix mypy errors

* Update homeassistant/components/mqtt/light/schema_template.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
Jan Bouwhuis 2022-11-08 10:16:05 +01:00 committed by GitHub
parent 8c0a7b9d7f
commit d66d079330
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 387 deletions

View file

@ -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()

View file

@ -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()

View file

@ -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