diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 0d60f3f6b52..7bc2306d418 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -398,8 +398,8 @@ class Light(ToggleEntity): return None @property - def state_attributes(self): - """Return optional state attributes.""" + def capability_attributes(self): + """Return capability attributes.""" data = {} supported_features = self.supported_features @@ -410,25 +410,35 @@ class Light(ToggleEntity): if supported_features & SUPPORT_EFFECT: data[ATTR_EFFECT_LIST] = self.effect_list - if self.is_on: - if supported_features & SUPPORT_BRIGHTNESS: - data[ATTR_BRIGHTNESS] = self.brightness + return data - if supported_features & SUPPORT_COLOR_TEMP: - data[ATTR_COLOR_TEMP] = self.color_temp + @property + def state_attributes(self): + """Return state attributes.""" + if not self.is_on: + return None - if supported_features & SUPPORT_COLOR and self.hs_color: - # pylint: disable=unsubscriptable-object,not-an-iterable - hs_color = self.hs_color - data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) - data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) - data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + data = {} + supported_features = self.supported_features - if supported_features & SUPPORT_WHITE_VALUE: - data[ATTR_WHITE_VALUE] = self.white_value + if supported_features & SUPPORT_BRIGHTNESS: + data[ATTR_BRIGHTNESS] = self.brightness - if supported_features & SUPPORT_EFFECT: - data[ATTR_EFFECT] = self.effect + if supported_features & SUPPORT_COLOR_TEMP: + data[ATTR_COLOR_TEMP] = self.color_temp + + if supported_features & SUPPORT_COLOR and self.hs_color: + # pylint: disable=unsubscriptable-object,not-an-iterable + hs_color = self.hs_color + data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3)) + data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color) + data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color) + + if supported_features & SUPPORT_WHITE_VALUE: + data[ATTR_WHITE_VALUE] = self.white_value + + if supported_features & SUPPORT_EFFECT: + data[ATTR_EFFECT] = self.effect return {key: val for key, val in data.items() if val is not None} diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 1805c6bd74b..ed656061401 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -144,6 +144,17 @@ class Entity(ABC): """Return the state of the entity.""" return STATE_UNKNOWN + @property + def capability_attributes(self) -> Optional[Dict[str, Any]]: + """Return the capability attributes. + + Attributes that explain the capabilities of an entity. + + Implemented by component base class. Convention for attribute names + is lowercase snake_case. + """ + return None + @property def state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes. @@ -302,7 +313,7 @@ class Entity(ABC): start = timer() - attr = {} + attr = self.capability_attributes or {} if not self.available: state = STATE_UNAVAILABLE else: diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 9d05920f78b..cd852f5bfc0 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -9,7 +9,7 @@ import pytest from homeassistant.helpers import entity, entity_registry from homeassistant.core import Context -from homeassistant.const import ATTR_HIDDEN, ATTR_DEVICE_CLASS +from homeassistant.const import ATTR_HIDDEN, ATTR_DEVICE_CLASS, STATE_UNAVAILABLE from homeassistant.config import DATA_CUSTOMIZE from homeassistant.helpers.entity_values import EntityValues @@ -641,3 +641,23 @@ async def test_disabled_in_entity_registry(hass): assert entry3 != entry2 assert ent.registry_entry == entry3 assert ent.enabled is False + + +async def test_capability_attrs(hass): + """Test we still include capabilities even when unavailable.""" + with patch.object( + entity.Entity, "available", PropertyMock(return_value=False) + ), patch.object( + entity.Entity, + "capability_attributes", + PropertyMock(return_value={"always": "there"}), + ): + ent = entity.Entity() + ent.hass = hass + ent.entity_id = "hello.world" + ent.async_write_ha_state() + + state = hass.states.get("hello.world") + assert state is not None + assert state.state == STATE_UNAVAILABLE + assert state.attributes["always"] == "there"