Add unique ID support to light, cover and media player groups (#53225)
parent
51dd95ce35
commit
b4a50f5459
|
@ -36,6 +36,7 @@ from homeassistant.const import (
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
CONF_ENTITIES,
|
CONF_ENTITIES,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
STATE_CLOSING,
|
STATE_CLOSING,
|
||||||
STATE_OPEN,
|
STATE_OPEN,
|
||||||
STATE_OPENING,
|
STATE_OPENING,
|
||||||
|
@ -57,8 +58,9 @@ DEFAULT_NAME = "Cover Group"
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
||||||
vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN),
|
vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN),
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,7 +72,13 @@ async def async_setup_platform(
|
||||||
discovery_info: dict[str, Any] | None = None,
|
discovery_info: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Group Cover platform."""
|
"""Set up the Group Cover platform."""
|
||||||
async_add_entities([CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])])
|
async_add_entities(
|
||||||
|
[
|
||||||
|
CoverGroup(
|
||||||
|
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CoverGroup(GroupEntity, CoverEntity):
|
class CoverGroup(GroupEntity, CoverEntity):
|
||||||
|
@ -82,7 +90,7 @@ class CoverGroup(GroupEntity, CoverEntity):
|
||||||
_attr_current_cover_position: int | None = 100
|
_attr_current_cover_position: int | None = 100
|
||||||
_attr_assumed_state: bool = True
|
_attr_assumed_state: bool = True
|
||||||
|
|
||||||
def __init__(self, name: str, entities: list[str]) -> None:
|
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||||
"""Initialize a CoverGroup entity."""
|
"""Initialize a CoverGroup entity."""
|
||||||
self._entities = entities
|
self._entities = entities
|
||||||
self._covers: dict[str, set[str]] = {
|
self._covers: dict[str, set[str]] = {
|
||||||
|
@ -98,6 +106,7 @@ class CoverGroup(GroupEntity, CoverEntity):
|
||||||
|
|
||||||
self._attr_name = name
|
self._attr_name = name
|
||||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entities}
|
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entities}
|
||||||
|
self._attr_unique_id = unique_id
|
||||||
|
|
||||||
async def _update_supported_features_event(self, event: Event) -> None:
|
async def _update_supported_features_event(self, event: Event) -> None:
|
||||||
self.async_set_context(event.context)
|
self.async_set_context(event.context)
|
||||||
|
|
|
@ -39,6 +39,7 @@ from homeassistant.const import (
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
CONF_ENTITIES,
|
CONF_ENTITIES,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
|
@ -55,6 +56,7 @@ DEFAULT_NAME = "Light Group"
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
vol.Required(CONF_ENTITIES): cv.entities_domain(light.DOMAIN),
|
vol.Required(CONF_ENTITIES): cv.entities_domain(light.DOMAIN),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -72,7 +74,11 @@ async def async_setup_platform(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize light.group platform."""
|
"""Initialize light.group platform."""
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[LightGroup(cast(str, config.get(CONF_NAME)), config[CONF_ENTITIES])]
|
[
|
||||||
|
LightGroup(
|
||||||
|
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,13 +92,14 @@ class LightGroup(GroupEntity, light.LightEntity):
|
||||||
_attr_min_mireds = 154
|
_attr_min_mireds = 154
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, name: str, entity_ids: list[str]) -> None:
|
def __init__(self, unique_id: str | None, name: str, entity_ids: list[str]) -> None:
|
||||||
"""Initialize a light group."""
|
"""Initialize a light group."""
|
||||||
self._entity_ids = entity_ids
|
self._entity_ids = entity_ids
|
||||||
self._white_value: int | None = None
|
self._white_value: int | None = None
|
||||||
|
|
||||||
self._attr_name = name
|
self._attr_name = name
|
||||||
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
|
||||||
|
self._attr_unique_id = unique_id
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
|
|
|
@ -48,6 +48,7 @@ from homeassistant.const import (
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
CONF_ENTITIES,
|
CONF_ENTITIES,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
@ -71,8 +72,9 @@ DEFAULT_NAME = "Media Group"
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
||||||
vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN),
|
vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN),
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,17 +86,24 @@ async def async_setup_platform(
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Media Group platform."""
|
"""Set up the Media Group platform."""
|
||||||
async_add_entities([MediaGroup(config[CONF_NAME], config[CONF_ENTITIES])])
|
async_add_entities(
|
||||||
|
[
|
||||||
|
MediaGroup(
|
||||||
|
config.get(CONF_UNIQUE_ID), config[CONF_NAME], config[CONF_ENTITIES]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MediaGroup(MediaPlayerEntity):
|
class MediaGroup(MediaPlayerEntity):
|
||||||
"""Representation of a Media Group."""
|
"""Representation of a Media Group."""
|
||||||
|
|
||||||
def __init__(self, name: str, entities: list[str]) -> None:
|
def __init__(self, unique_id: str | None, name: str, entities: list[str]) -> None:
|
||||||
"""Initialize a Media Group entity."""
|
"""Initialize a Media Group entity."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state: str | None = None
|
self._state: str | None = None
|
||||||
self._supported_features: int = 0
|
self._supported_features: int = 0
|
||||||
|
self._attr_unique_id = unique_id
|
||||||
|
|
||||||
self._entities = entities
|
self._entities = entities
|
||||||
self._features: dict[str, set[str]] = {
|
self._features: dict[str, set[str]] = {
|
||||||
|
|
|
@ -17,6 +17,7 @@ from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
ATTR_SUPPORTED_FEATURES,
|
ATTR_SUPPORTED_FEATURES,
|
||||||
CONF_ENTITIES,
|
CONF_ENTITIES,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
SERVICE_CLOSE_COVER,
|
SERVICE_CLOSE_COVER,
|
||||||
SERVICE_CLOSE_COVER_TILT,
|
SERVICE_CLOSE_COVER_TILT,
|
||||||
SERVICE_OPEN_COVER,
|
SERVICE_OPEN_COVER,
|
||||||
|
@ -32,6 +33,7 @@ from homeassistant.const import (
|
||||||
STATE_OPEN,
|
STATE_OPEN,
|
||||||
STATE_OPENING,
|
STATE_OPENING,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
@ -77,6 +79,7 @@ CONFIG_ATTRIBUTES = {
|
||||||
DOMAIN: {
|
DOMAIN: {
|
||||||
"platform": "group",
|
"platform": "group",
|
||||||
CONF_ENTITIES: [DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT],
|
CONF_ENTITIES: [DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT],
|
||||||
|
CONF_UNIQUE_ID: "unique_identifier",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +223,11 @@ async def test_attributes(hass, setup_comp):
|
||||||
state = hass.states.get(COVER_GROUP)
|
state = hass.states.get(COVER_GROUP)
|
||||||
assert state.attributes[ATTR_ASSUMED_STATE] is True
|
assert state.attributes[ATTR_ASSUMED_STATE] is True
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entry = entity_registry.async_get(COVER_GROUP)
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "unique_identifier"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("config_count", [(CONFIG_TILT_ONLY, 2)])
|
@pytest.mark.parametrize("config_count", [(CONFIG_TILT_ONLY, 2)])
|
||||||
async def test_cover_that_only_supports_tilt_removed(hass, setup_comp):
|
async def test_cover_that_only_supports_tilt_removed(hass, setup_comp):
|
||||||
|
|
|
@ -44,6 +44,7 @@ from homeassistant.const import (
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,6 +59,7 @@ async def test_default_state(hass):
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": ["light.kitchen", "light.bedroom"],
|
"entities": ["light.kitchen", "light.bedroom"],
|
||||||
"name": "Bedroom Group",
|
"name": "Bedroom Group",
|
||||||
|
"unique_id": "unique_identifier",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -77,6 +79,11 @@ async def test_default_state(hass):
|
||||||
assert state.attributes.get(ATTR_EFFECT_LIST) is None
|
assert state.attributes.get(ATTR_EFFECT_LIST) is None
|
||||||
assert state.attributes.get(ATTR_EFFECT) is None
|
assert state.attributes.get(ATTR_EFFECT) is None
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entry = entity_registry.async_get("light.bedroom_group")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "unique_identifier"
|
||||||
|
|
||||||
|
|
||||||
async def test_state_reporting(hass):
|
async def test_state_reporting(hass):
|
||||||
"""Test the state reporting."""
|
"""Test the state reporting."""
|
||||||
|
@ -1064,7 +1071,7 @@ async def test_invalid_service_calls(hass):
|
||||||
"""Test invalid service call arguments get discarded."""
|
"""Test invalid service call arguments get discarded."""
|
||||||
add_entities = MagicMock()
|
add_entities = MagicMock()
|
||||||
await group.async_setup_platform(
|
await group.async_setup_platform(
|
||||||
hass, {"entities": ["light.test1", "light.test2"]}, add_entities
|
hass, {"name": "test", "entities": ["light.test1", "light.test2"]}, add_entities
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
await hass.async_start()
|
await hass.async_start()
|
||||||
|
|
|
@ -49,6 +49,7 @@ from homeassistant.const import (
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ async def test_default_state(hass):
|
||||||
"platform": DOMAIN,
|
"platform": DOMAIN,
|
||||||
"entities": ["media_player.player_1", "media_player.player_2"],
|
"entities": ["media_player.player_1", "media_player.player_2"],
|
||||||
"name": "Media group",
|
"name": "Media group",
|
||||||
|
"unique_id": "unique_identifier",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -89,6 +91,11 @@ async def test_default_state(hass):
|
||||||
"media_player.player_2",
|
"media_player.player_2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entry = entity_registry.async_get("media_player.media_group")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "unique_identifier"
|
||||||
|
|
||||||
|
|
||||||
async def test_state_reporting(hass):
|
async def test_state_reporting(hass):
|
||||||
"""Test the state reporting."""
|
"""Test the state reporting."""
|
||||||
|
|
Loading…
Reference in New Issue