mirror of
https://github.com/home-assistant/core.git
synced 2026-10-06 22:38:02 -04:00
Improve handling of entity_id change in rflink (#183856)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""Support for Rflink devices."""
|
||||
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
@@ -109,6 +110,23 @@ class RflinkDevice(Entity):
|
||||
self._attr_available = availability
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _async_register_lookup(
|
||||
self, lookup: defaultdict[str, list[str]], event_id: str
|
||||
) -> None:
|
||||
"""Route events for event_id to this entity until it is removed."""
|
||||
entity_id = self.entity_id
|
||||
lookup[event_id].append(entity_id)
|
||||
|
||||
@callback
|
||||
def _async_unregister() -> None:
|
||||
entity_ids = lookup[event_id]
|
||||
entity_ids.remove(entity_id)
|
||||
if not entity_ids:
|
||||
del lookup[event_id]
|
||||
|
||||
self.async_on_remove(_async_unregister)
|
||||
|
||||
@override
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register update callback."""
|
||||
@@ -124,34 +142,24 @@ class RflinkDevice(Entity):
|
||||
].remove(tmp_entity)
|
||||
|
||||
# Register id and aliases
|
||||
self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND][self._device_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
lookup = self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND]
|
||||
group_lookup = self.hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND]
|
||||
self._async_register_lookup(lookup, self._device_id)
|
||||
if self._group:
|
||||
self.hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND][
|
||||
self._device_id
|
||||
].append(self.entity_id)
|
||||
self._async_register_lookup(group_lookup, self._device_id)
|
||||
# aliases respond to both normal and group commands (allon/alloff)
|
||||
if self._aliases:
|
||||
for _id in self._aliases:
|
||||
self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND][_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
self.hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND][_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
self._async_register_lookup(lookup, _id)
|
||||
self._async_register_lookup(group_lookup, _id)
|
||||
# group_aliases only respond to group commands (allon/alloff)
|
||||
if self._group_aliases:
|
||||
for _id in self._group_aliases:
|
||||
self.hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND][_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
self._async_register_lookup(group_lookup, _id)
|
||||
# nogroup_aliases only respond to normal commands
|
||||
if self._nogroup_aliases:
|
||||
for _id in self._nogroup_aliases:
|
||||
self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND][_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
self._async_register_lookup(lookup, _id)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_AVAILABILITY, self._availability_callback
|
||||
|
||||
@@ -389,14 +389,11 @@ class RflinkSensor(RflinkDevice, SensorEntity):
|
||||
].remove(tmp_entity)
|
||||
|
||||
# Register id and aliases
|
||||
self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_SENSOR][self._device_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
lookup = self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_SENSOR]
|
||||
self._async_register_lookup(lookup, self._device_id)
|
||||
if self._aliases:
|
||||
for _id in self._aliases:
|
||||
self.hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_SENSOR][_id].append(
|
||||
self.entity_id
|
||||
)
|
||||
self._async_register_lookup(lookup, _id)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_AVAILABILITY, self._availability_callback
|
||||
|
||||
@@ -25,6 +25,7 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .test_init import mock_rflink
|
||||
|
||||
@@ -701,3 +702,53 @@ async def test_empty_discovery_info(
|
||||
assert not hass.states.get(f"{DOMAIN}.protocol_0_0")
|
||||
assert not hass.states.get("light.protocol_0_0")
|
||||
assert "device_id not known and automatic add disabled" in caplog.text
|
||||
|
||||
|
||||
async def test_removed_sensor_unregisters_lookups(
|
||||
hass: HomeAssistant,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test a removed sensor is dropped from the event lookups."""
|
||||
config = {
|
||||
"rflink": {
|
||||
"port": "/dev/ttyABC0",
|
||||
DOMAIN: {
|
||||
"automatic_add": False,
|
||||
"devices": {
|
||||
"test_02": {
|
||||
"name": "test_02",
|
||||
"sensor_type": "humidity",
|
||||
"aliases": ["test_alias_02_0"],
|
||||
},
|
||||
# Shares the alias of the removed sensor
|
||||
"test_03": {
|
||||
"name": "test_03",
|
||||
"sensor_type": "humidity",
|
||||
"aliases": ["test_alias_02_0"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
event_callback, _, _, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch)
|
||||
lookup = hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_SENSOR]
|
||||
assert lookup["test_02"] == ["sensor.test_02"]
|
||||
|
||||
entity_registry.async_remove("sensor.test_02")
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.test_02") is None
|
||||
|
||||
assert lookup == {
|
||||
"test_03": ["sensor.test_03"],
|
||||
"test_alias_02_0": ["sensor.test_03"],
|
||||
}
|
||||
|
||||
for event_id in ("test_02", "test_alias_02_0"):
|
||||
event_callback(
|
||||
{"id": event_id, "sensor": "humidity", "value": 65, "unit": PERCENTAGE}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.test_02") is None
|
||||
assert hass.states.get("sensor.test_03").state == "65"
|
||||
|
||||
@@ -5,8 +5,15 @@ control of Rflink switch devices.
|
||||
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.rflink import (
|
||||
DATA_ENTITY_GROUP_LOOKUP,
|
||||
DATA_ENTITY_LOOKUP,
|
||||
EVENT_KEY_COMMAND,
|
||||
)
|
||||
from homeassistant.components.rflink.entity import EVENT_BUTTON_PRESSED
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@@ -16,6 +23,7 @@ from homeassistant.const import (
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import CoreState, HomeAssistant, State, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .test_init import mock_rflink
|
||||
|
||||
@@ -599,3 +607,66 @@ async def test_restore_state(
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes["assumed_state"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_config",
|
||||
[
|
||||
pytest.param({}, id="device_id"),
|
||||
pytest.param({"group": False}, id="nogroup_device_id"),
|
||||
pytest.param({"aliases": ["test_alias_0_0"]}, id="alias"),
|
||||
pytest.param({"group_aliases": ["test_group_0_0"]}, id="group_alias"),
|
||||
pytest.param({"nogroup_aliases": ["test_nogroup_0_0"]}, id="nogroup_alias"),
|
||||
],
|
||||
)
|
||||
async def test_removed_switch_unregisters_lookups(
|
||||
hass: HomeAssistant,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test a removed switch is dropped from the event lookups."""
|
||||
config = {
|
||||
"rflink": {
|
||||
"port": "/dev/ttyABC0",
|
||||
DOMAIN: {
|
||||
"devices": {
|
||||
"protocol_0_0": {"name": "test", **device_config},
|
||||
# Shares every alias id the removed switch may use
|
||||
"protocol_0_1": {
|
||||
"name": "other",
|
||||
"aliases": ["test_alias_0_0"],
|
||||
"group_aliases": ["test_group_0_0"],
|
||||
"nogroup_aliases": ["test_nogroup_0_0"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
event_callback, _, _, _ = await mock_rflink(hass, config, DOMAIN, monkeypatch)
|
||||
lookup = hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND]
|
||||
group_lookup = hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND]
|
||||
assert "switch.test" in lookup["protocol_0_0"]
|
||||
|
||||
entity_registry.async_remove("switch.test")
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("switch.test") is None
|
||||
|
||||
assert lookup == {
|
||||
"protocol_0_1": ["switch.other"],
|
||||
"test_alias_0_0": ["switch.other"],
|
||||
"test_nogroup_0_0": ["switch.other"],
|
||||
}
|
||||
assert group_lookup == {
|
||||
"protocol_0_1": ["switch.other"],
|
||||
"test_alias_0_0": ["switch.other"],
|
||||
"test_group_0_0": ["switch.other"],
|
||||
}
|
||||
|
||||
for event_id in ("protocol_0_0", "test_alias_0_0", "test_nogroup_0_0"):
|
||||
event_callback({"id": event_id, "command": "on"})
|
||||
event_callback({"id": "test_group_0_0", "command": "allon"})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.test") is None
|
||||
assert hass.states.get("switch.other").state == STATE_ON
|
||||
|
||||
Reference in New Issue
Block a user