Fix switch_as_x entity naming (#89992)

* Fix switch_as_x entity naming

* Simplify name logic
This commit is contained in:
Erik Montnemery 2023-03-22 20:24:05 +01:00 committed by GitHub
parent 1ea3312ed4
commit 5948347b6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 148 additions and 84 deletions

View File

@ -30,18 +30,14 @@ async def async_setup_entry(
entity_id = er.async_validate_entity_id(
registry, config_entry.options[CONF_ENTITY_ID]
)
wrapped_switch = registry.async_get(entity_id)
device_id = wrapped_switch.device_id if wrapped_switch else None
entity_category = wrapped_switch.entity_category if wrapped_switch else None
async_add_entities(
[
CoverSwitch(
hass,
config_entry.title,
entity_id,
config_entry.entry_id,
device_id,
entity_category,
)
]
)

View File

@ -10,32 +10,47 @@ from homeassistant.const import (
SERVICE_TURN_ON,
STATE_ON,
STATE_UNAVAILABLE,
EntityCategory,
)
from homeassistant.core import Event, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity import Entity, ToggleEntity
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity import DeviceInfo, Entity, ToggleEntity
from homeassistant.helpers.event import async_track_state_change_event
from .const import DOMAIN as SWITCH_AS_X_DOMAIN
class BaseEntity(Entity):
"""Represents a Switch as a X."""
"""Represents a Switch as an X."""
_attr_should_poll = False
def __init__(
self,
name: str,
hass: HomeAssistant,
config_entry_title: str,
switch_entity_id: str,
unique_id: str | None,
device_id: str | None,
entity_category: EntityCategory | None,
) -> None:
"""Initialize Light Switch."""
"""Initialize Switch as an X."""
registry = er.async_get(hass)
device_registry = dr.async_get(hass)
wrapped_switch = registry.async_get(switch_entity_id)
device_id = wrapped_switch.device_id if wrapped_switch else None
entity_category = wrapped_switch.entity_category if wrapped_switch else None
has_entity_name = wrapped_switch.has_entity_name if wrapped_switch else False
name: str | None = config_entry_title
if wrapped_switch:
name = wrapped_switch.name or wrapped_switch.original_name
self._device_id = device_id
if device_id and (device := device_registry.async_get(device_id)):
self._attr_device_info = DeviceInfo(
connections=device.connections,
identifiers=device.identifiers,
)
self._attr_entity_category = entity_category
self._attr_has_entity_name = has_entity_name
self._attr_name = name
self._attr_unique_id = unique_id
self._switch_entity_id = switch_entity_id
@ -69,10 +84,9 @@ class BaseEntity(Entity):
# Call once on adding
_async_state_changed_listener()
# Add this entity to the wrapped switch's device
# Update entity options
registry = er.async_get(self.hass)
if registry.async_get(self.entity_id) is not None:
registry.async_update_entity(self.entity_id, device_id=self._device_id)
registry.async_update_entity_options(
self.entity_id,
SWITCH_AS_X_DOMAIN,

View File

@ -23,18 +23,14 @@ async def async_setup_entry(
entity_id = er.async_validate_entity_id(
registry, config_entry.options[CONF_ENTITY_ID]
)
wrapped_switch = registry.async_get(entity_id)
device_id = wrapped_switch.device_id if wrapped_switch else None
entity_category = wrapped_switch.entity_category if wrapped_switch else None
async_add_entities(
[
FanSwitch(
hass,
config_entry.title,
entity_id,
config_entry.entry_id,
device_id,
entity_category,
)
]
)

View File

@ -21,18 +21,14 @@ async def async_setup_entry(
entity_id = er.async_validate_entity_id(
registry, config_entry.options[CONF_ENTITY_ID]
)
wrapped_switch = registry.async_get(entity_id)
device_id = wrapped_switch.device_id if wrapped_switch else None
entity_category = wrapped_switch.entity_category if wrapped_switch else None
async_add_entities(
[
LightSwitch(
hass,
config_entry.title,
entity_id,
config_entry.entry_id,
device_id,
entity_category,
)
]
)

View File

@ -30,18 +30,14 @@ async def async_setup_entry(
entity_id = er.async_validate_entity_id(
registry, config_entry.options[CONF_ENTITY_ID]
)
wrapped_switch = registry.async_get(entity_id)
device_id = wrapped_switch.device_id if wrapped_switch else None
entity_category = wrapped_switch.entity_category if wrapped_switch else None
async_add_entities(
[
LockSwitch(
hass,
config_entry.title,
entity_id,
config_entry.entry_id,
device_id,
entity_category,
)
]
)

View File

@ -21,18 +21,14 @@ async def async_setup_entry(
entity_id = er.async_validate_entity_id(
registry, config_entry.options[CONF_ENTITY_ID]
)
wrapped_switch = registry.async_get(entity_id)
device_id = wrapped_switch.device_id if wrapped_switch else None
entity_category = wrapped_switch.entity_category if wrapped_switch else None
async_add_entities(
[
SirenSwitch(
hass,
config_entry.title,
entity_id,
config_entry.entry_id,
device_id,
entity_category,
)
]
)

View File

@ -45,6 +45,7 @@ async def test_default_state(hass: HomeAssistant) -> None:
async def test_service_calls(hass: HomeAssistant) -> None:
"""Test service calls to cover."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
@ -52,43 +53,43 @@ async def test_service_calls(hass: HomeAssistant) -> None:
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_TARGET_DOMAIN: Platform.COVER,
},
title="garage_door",
title="Title is ignored",
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("cover.garage_door").state == STATE_OPEN
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "cover.garage_door"},
{CONF_ENTITY_ID: "cover.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("cover.garage_door").state == STATE_CLOSED
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{CONF_ENTITY_ID: "cover.garage_door"},
{CONF_ENTITY_ID: "cover.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.garage_door").state == STATE_OPEN
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{CONF_ENTITY_ID: "cover.garage_door"},
{CONF_ENTITY_ID: "cover.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("cover.garage_door").state == STATE_CLOSED
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
SWITCH_DOMAIN,
@ -98,7 +99,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.garage_door").state == STATE_OPEN
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN
await hass.services.async_call(
SWITCH_DOMAIN,
@ -108,7 +109,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("cover.garage_door").state == STATE_CLOSED
assert hass.states.get("cover.decorative_lights").state == STATE_CLOSED
await hass.services.async_call(
SWITCH_DOMAIN,
@ -118,4 +119,4 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("cover.garage_door").state == STATE_OPEN
assert hass.states.get("cover.decorative_lights").state == STATE_OPEN

View File

@ -41,6 +41,7 @@ async def test_default_state(hass: HomeAssistant) -> None:
async def test_service_calls(hass: HomeAssistant) -> None:
"""Test service calls affecting the switch as fan entity."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
@ -48,43 +49,43 @@ async def test_service_calls(hass: HomeAssistant) -> None:
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_TARGET_DOMAIN: Platform.FAN,
},
title="wind_machine",
title="Title is ignored",
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("fan.wind_machine").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "fan.wind_machine"},
{CONF_ENTITY_ID: "fan.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("fan.wind_machine").state == STATE_OFF
assert hass.states.get("fan.decorative_lights").state == STATE_OFF
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "fan.wind_machine"},
{CONF_ENTITY_ID: "fan.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("fan.wind_machine").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "fan.wind_machine"},
{CONF_ENTITY_ID: "fan.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("fan.wind_machine").state == STATE_OFF
assert hass.states.get("fan.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
@ -94,7 +95,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("fan.wind_machine").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
@ -104,7 +105,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("fan.wind_machine").state == STATE_OFF
assert hass.states.get("fan.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
@ -114,4 +115,4 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("fan.wind_machine").state == STATE_ON
assert hass.states.get("fan.decorative_lights").state == STATE_ON

View File

@ -71,7 +71,9 @@ async def test_entity_registry_events(
) -> None:
"""Test entity registry events are tracked."""
registry = er.async_get(hass)
registry_entry = registry.async_get_or_create("switch", "test", "unique")
registry_entry = registry.async_get_or_create(
"switch", "test", "unique", original_name="ABC"
)
switch_entity_id = registry_entry.entity_id
hass.states.async_set(switch_entity_id, STATE_ON)
@ -144,6 +146,7 @@ async def test_device_registry_config_entry_1(
"unique",
config_entry=switch_config_entry,
device_id=device_entry.id,
original_name="ABC",
)
# Add another config entry to the same device
device_registry.async_update_device(
@ -202,6 +205,7 @@ async def test_device_registry_config_entry_2(
"unique",
config_entry=switch_config_entry,
device_id=device_entry.id,
original_name="ABC",
)
switch_as_x_config_entry = MockConfigEntry(
@ -272,7 +276,9 @@ async def test_config_entry_entity_id(
async def test_config_entry_uuid(hass: HomeAssistant, target_domain: Platform) -> None:
"""Test light switch setup from config entry with entity registry id."""
registry = er.async_get(hass)
registry_entry = registry.async_get_or_create("switch", "test", "unique")
registry_entry = registry.async_get_or_create(
"switch", "test", "unique", original_name="ABC"
)
config_entry = MockConfigEntry(
data={},
@ -305,7 +311,7 @@ async def test_device(hass: HomeAssistant, target_domain: Platform) -> None:
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
)
switch_entity_entry = entity_registry.async_get_or_create(
"switch", "test", "unique", device_id=device_entry.id
"switch", "test", "unique", device_id=device_entry.id, original_name="ABC"
)
switch_as_x_config_entry = MockConfigEntry(
@ -414,7 +420,9 @@ async def test_entity_category_inheritance(
"""Test the entity category is inherited from source device."""
registry = er.async_get(hass)
switch_entity_entry = registry.async_get_or_create("switch", "test", "unique")
switch_entity_entry = registry.async_get_or_create(
"switch", "test", "unique", original_name="ABC"
)
registry.async_update_entity(
switch_entity_entry.entity_id, entity_category=EntityCategory.CONFIG
)
@ -448,7 +456,9 @@ async def test_entity_options(
"""Test the source entity is stored as an entity option."""
registry = er.async_get(hass)
switch_entity_entry = registry.async_get_or_create("switch", "test", "unique")
switch_entity_entry = registry.async_get_or_create(
"switch", "test", "unique", original_name="ABC"
)
registry.async_update_entity(
switch_entity_entry.entity_id, entity_category=EntityCategory.CONFIG
)
@ -474,3 +484,57 @@ async def test_entity_options(
assert entity_entry.options == {
DOMAIN: {"entity_id": switch_entity_entry.entity_id}
}
@pytest.mark.parametrize("target_domain", PLATFORMS_TO_TEST)
async def test_entity_name(
hass: HomeAssistant,
target_domain: Platform,
) -> None:
"""Test the source entity has entity_name set to True."""
registry = er.async_get(hass)
device_registry = dr.async_get(hass)
switch_config_entry = MockConfigEntry()
device_entry = device_registry.async_get_or_create(
config_entry_id=switch_config_entry.entry_id,
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
name="Device name",
)
switch_entity_entry = registry.async_get_or_create(
"switch",
"test",
"unique",
device_id=device_entry.id,
has_entity_name=True,
)
switch_entity_entry = registry.async_update_entity(
switch_entity_entry.entity_id,
config_entry_id=switch_config_entry.entry_id,
)
# Add the config entry
switch_as_x_config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={
CONF_ENTITY_ID: switch_entity_entry.id,
CONF_TARGET_DOMAIN: target_domain,
},
title="ABC",
)
switch_as_x_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(switch_as_x_config_entry.entry_id)
await hass.async_block_till_done()
entity_entry = registry.async_get(f"{target_domain}.device_name")
assert entity_entry
assert entity_entry.device_id == switch_entity_entry.device_id
assert entity_entry.has_entity_name is True
assert entity_entry.original_name is None
assert entity_entry.options == {
DOMAIN: {"entity_id": switch_entity_entry.entity_id}
}

View File

@ -58,6 +58,7 @@ async def test_default_state(hass: HomeAssistant) -> None:
async def test_light_service_calls(hass: HomeAssistant) -> None:
"""Test service calls to light."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
@ -111,6 +112,7 @@ async def test_light_service_calls(hass: HomeAssistant) -> None:
async def test_switch_service_calls(hass: HomeAssistant) -> None:
"""Test service calls to switch."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
@ -118,7 +120,7 @@ async def test_switch_service_calls(hass: HomeAssistant) -> None:
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_TARGET_DOMAIN: Platform.LIGHT,
},
title="decorative_lights",
title="Title is ignored",
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)

View File

@ -44,6 +44,7 @@ async def test_default_state(hass: HomeAssistant) -> None:
async def test_service_calls(hass: HomeAssistant) -> None:
"""Test service calls affecting the switch as lock entity."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
@ -51,33 +52,33 @@ async def test_service_calls(hass: HomeAssistant) -> None:
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_TARGET_DOMAIN: Platform.LOCK,
},
title="candy_jar",
title="Title is ignored",
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("lock.candy_jar").state == STATE_UNLOCKED
assert hass.states.get("lock.decorative_lights").state == STATE_UNLOCKED
await hass.services.async_call(
LOCK_DOMAIN,
SERVICE_LOCK,
{CONF_ENTITY_ID: "lock.candy_jar"},
{CONF_ENTITY_ID: "lock.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.candy_jar").state == STATE_LOCKED
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED
await hass.services.async_call(
LOCK_DOMAIN,
SERVICE_UNLOCK,
{CONF_ENTITY_ID: "lock.candy_jar"},
{CONF_ENTITY_ID: "lock.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("lock.candy_jar").state == STATE_UNLOCKED
assert hass.states.get("lock.decorative_lights").state == STATE_UNLOCKED
await hass.services.async_call(
SWITCH_DOMAIN,
@ -87,7 +88,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.candy_jar").state == STATE_LOCKED
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED
await hass.services.async_call(
SWITCH_DOMAIN,
@ -97,7 +98,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("lock.candy_jar").state == STATE_UNLOCKED
assert hass.states.get("lock.decorative_lights").state == STATE_UNLOCKED
await hass.services.async_call(
SWITCH_DOMAIN,
@ -107,4 +108,4 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("lock.candy_jar").state == STATE_LOCKED
assert hass.states.get("lock.decorative_lights").state == STATE_LOCKED

View File

@ -41,6 +41,7 @@ async def test_default_state(hass: HomeAssistant) -> None:
async def test_service_calls(hass: HomeAssistant) -> None:
"""Test service calls affecting the switch as siren entity."""
await async_setup_component(hass, "switch", {"switch": [{"platform": "demo"}]})
await hass.async_block_till_done()
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
@ -48,43 +49,43 @@ async def test_service_calls(hass: HomeAssistant) -> None:
CONF_ENTITY_ID: "switch.decorative_lights",
CONF_TARGET_DOMAIN: Platform.SIREN,
},
title="noise_maker",
title="Title is ignored",
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get("siren.noise_maker").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON
await hass.services.async_call(
SIREN_DOMAIN,
SERVICE_TOGGLE,
{CONF_ENTITY_ID: "siren.noise_maker"},
{CONF_ENTITY_ID: "siren.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("siren.noise_maker").state == STATE_OFF
assert hass.states.get("siren.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SIREN_DOMAIN,
SERVICE_TURN_ON,
{CONF_ENTITY_ID: "siren.noise_maker"},
{CONF_ENTITY_ID: "siren.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("siren.noise_maker").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON
await hass.services.async_call(
SIREN_DOMAIN,
SERVICE_TURN_OFF,
{CONF_ENTITY_ID: "siren.noise_maker"},
{CONF_ENTITY_ID: "siren.decorative_lights"},
blocking=True,
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("siren.noise_maker").state == STATE_OFF
assert hass.states.get("siren.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
@ -94,7 +95,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("siren.noise_maker").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON
await hass.services.async_call(
SWITCH_DOMAIN,
@ -104,7 +105,7 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_OFF
assert hass.states.get("siren.noise_maker").state == STATE_OFF
assert hass.states.get("siren.decorative_lights").state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
@ -114,4 +115,4 @@ async def test_service_calls(hass: HomeAssistant) -> None:
)
assert hass.states.get("switch.decorative_lights").state == STATE_ON
assert hass.states.get("siren.noise_maker").state == STATE_ON
assert hass.states.get("siren.decorative_lights").state == STATE_ON