Remove lamps and groups from ha when removed from Hue (#26881)
* Remove light when removed from hue * add remove_config_entry_id * Review + bump aiohue * lint * Add tests
This commit is contained in:
parent
f6995b8d17
commit
b75639d9d1
7 changed files with 159 additions and 10 deletions
|
@ -8,6 +8,9 @@ import random
|
|||
import aiohue
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.helpers.entity_registry import async_get_registry as get_ent_reg
|
||||
from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg
|
||||
|
||||
from homeassistant.components import hue
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
|
@ -147,6 +150,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
tasks.append(
|
||||
async_update_items(
|
||||
hass,
|
||||
config_entry,
|
||||
bridge,
|
||||
async_add_entities,
|
||||
request_update,
|
||||
|
@ -160,6 +164,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
tasks.append(
|
||||
async_update_items(
|
||||
hass,
|
||||
config_entry,
|
||||
bridge,
|
||||
async_add_entities,
|
||||
request_update,
|
||||
|
@ -176,6 +181,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||
|
||||
async def async_update_items(
|
||||
hass,
|
||||
config_entry,
|
||||
bridge,
|
||||
async_add_entities,
|
||||
request_bridge_update,
|
||||
|
@ -204,9 +210,9 @@ async def async_update_items(
|
|||
_LOGGER.error("Unable to reach bridge %s (%s)", bridge.host, err)
|
||||
bridge.available = False
|
||||
|
||||
for light_id, light in current.items():
|
||||
if light_id not in progress_waiting:
|
||||
light.async_schedule_update_ha_state()
|
||||
for item_id, item in current.items():
|
||||
if item_id not in progress_waiting:
|
||||
item.async_schedule_update_ha_state()
|
||||
|
||||
return
|
||||
|
||||
|
@ -219,7 +225,8 @@ async def async_update_items(
|
|||
_LOGGER.info("Reconnected to bridge %s", bridge.host)
|
||||
bridge.available = True
|
||||
|
||||
new_lights = []
|
||||
new_items = []
|
||||
removed_items = []
|
||||
|
||||
for item_id in api:
|
||||
if item_id not in current:
|
||||
|
@ -227,12 +234,34 @@ async def async_update_items(
|
|||
api[item_id], request_bridge_update, bridge, is_group
|
||||
)
|
||||
|
||||
new_lights.append(current[item_id])
|
||||
new_items.append(current[item_id])
|
||||
elif item_id not in progress_waiting:
|
||||
current[item_id].async_schedule_update_ha_state()
|
||||
|
||||
if new_lights:
|
||||
async_add_entities(new_lights)
|
||||
for item_id in current:
|
||||
if item_id in api:
|
||||
continue
|
||||
|
||||
# Device is removed from Hue, so we remove it from Home Assistant
|
||||
entity = current[item_id]
|
||||
removed_items.append(item_id)
|
||||
await entity.async_remove()
|
||||
ent_registry = await get_ent_reg(hass)
|
||||
if entity.entity_id in ent_registry.entities:
|
||||
ent_registry.async_remove(entity.entity_id)
|
||||
dev_registry = await get_dev_reg(hass)
|
||||
device = dev_registry.async_get_device(
|
||||
identifiers={(hue.DOMAIN, entity.unique_id)}, connections=set()
|
||||
)
|
||||
dev_registry.async_update_device(
|
||||
device.id, remove_config_entry_id=config_entry.entry_id
|
||||
)
|
||||
|
||||
if new_items:
|
||||
async_add_entities(new_items)
|
||||
|
||||
for item_id in removed_items:
|
||||
del current[item_id]
|
||||
|
||||
|
||||
class HueLight(Light):
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/components/hue",
|
||||
"requirements": [
|
||||
"aiohue==1.9.1"
|
||||
"aiohue==1.9.2"
|
||||
],
|
||||
"ssdp": {
|
||||
"manufacturer": [
|
||||
|
|
|
@ -157,6 +157,7 @@ class DeviceRegistry:
|
|||
name_by_user=_UNDEF,
|
||||
new_identifiers=_UNDEF,
|
||||
via_device_id=_UNDEF,
|
||||
remove_config_entry_id=_UNDEF,
|
||||
):
|
||||
"""Update properties of a device."""
|
||||
return self._async_update_device(
|
||||
|
@ -166,6 +167,7 @@ class DeviceRegistry:
|
|||
name_by_user=name_by_user,
|
||||
new_identifiers=new_identifiers,
|
||||
via_device_id=via_device_id,
|
||||
remove_config_entry_id=remove_config_entry_id,
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -203,6 +205,10 @@ class DeviceRegistry:
|
|||
remove_config_entry_id is not _UNDEF
|
||||
and remove_config_entry_id in config_entries
|
||||
):
|
||||
if config_entries == {remove_config_entry_id}:
|
||||
self.async_remove_device(device_id)
|
||||
return
|
||||
|
||||
config_entries = config_entries - {remove_config_entry_id}
|
||||
|
||||
if config_entries is not old.config_entries:
|
||||
|
|
|
@ -152,7 +152,7 @@ aioharmony==0.1.13
|
|||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==1.9.1
|
||||
aiohue==1.9.2
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==0.7.15
|
||||
|
|
|
@ -62,7 +62,7 @@ aioesphomeapi==2.2.0
|
|||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==1.9.1
|
||||
aiohue==1.9.2
|
||||
|
||||
# homeassistant.components.notion
|
||||
aionotion==1.1.0
|
||||
|
|
|
@ -420,6 +420,62 @@ async def test_new_light_discovered(hass, mock_bridge):
|
|||
assert light.state == "off"
|
||||
|
||||
|
||||
async def test_group_removed(hass, mock_bridge):
|
||||
"""Test if 2nd update has removed group."""
|
||||
mock_bridge.allow_groups = True
|
||||
mock_bridge.mock_light_responses.append({})
|
||||
mock_bridge.mock_group_responses.append(GROUP_RESPONSE)
|
||||
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 2
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
mock_bridge.mock_light_responses.append({})
|
||||
mock_bridge.mock_group_responses.append({"1": GROUP_RESPONSE["1"]})
|
||||
|
||||
# Calling a service will trigger the updates to run
|
||||
await hass.services.async_call(
|
||||
"light", "turn_on", {"entity_id": "light.group_1"}, blocking=True
|
||||
)
|
||||
|
||||
# 2x group update, 2x light update, 1 turn on request
|
||||
assert len(mock_bridge.mock_requests) == 5
|
||||
assert len(hass.states.async_all()) == 2
|
||||
|
||||
group = hass.states.get("light.group_1")
|
||||
assert group is not None
|
||||
|
||||
removed_group = hass.states.get("light.group_2")
|
||||
assert removed_group is None
|
||||
|
||||
|
||||
async def test_light_removed(hass, mock_bridge):
|
||||
"""Test if 2nd update has removed light."""
|
||||
mock_bridge.mock_light_responses.append(LIGHT_RESPONSE)
|
||||
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
mock_bridge.mock_light_responses.clear()
|
||||
mock_bridge.mock_light_responses.append({"1": LIGHT_RESPONSE.get("1")})
|
||||
|
||||
# Calling a service will trigger the updates to run
|
||||
await hass.services.async_call(
|
||||
"light", "turn_on", {"entity_id": "light.hue_lamp_1"}, blocking=True
|
||||
)
|
||||
|
||||
# 2x light update, 1 turn on request
|
||||
assert len(mock_bridge.mock_requests) == 3
|
||||
assert len(hass.states.async_all()) == 2
|
||||
|
||||
light = hass.states.get("light.hue_lamp_1")
|
||||
assert light is not None
|
||||
|
||||
removed_light = hass.states.get("light.hue_lamp_2")
|
||||
assert removed_light is None
|
||||
|
||||
|
||||
async def test_other_group_update(hass, mock_bridge):
|
||||
"""Test changing one group that will impact the state of other light."""
|
||||
mock_bridge.allow_groups = True
|
||||
|
|
|
@ -409,6 +409,64 @@ async def test_update(registry):
|
|||
assert updated_entry.via_device_id == "98765B"
|
||||
|
||||
|
||||
async def test_update_remove_config_entries(hass, registry, update_events):
|
||||
"""Make sure we do not get duplicate entries."""
|
||||
entry = registry.async_get_or_create(
|
||||
config_entry_id="123",
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
identifiers={("bridgeid", "0123")},
|
||||
manufacturer="manufacturer",
|
||||
model="model",
|
||||
)
|
||||
entry2 = registry.async_get_or_create(
|
||||
config_entry_id="456",
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
identifiers={("bridgeid", "0123")},
|
||||
manufacturer="manufacturer",
|
||||
model="model",
|
||||
)
|
||||
entry3 = registry.async_get_or_create(
|
||||
config_entry_id="123",
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, "34:56:78:CD:EF:12")},
|
||||
identifiers={("bridgeid", "4567")},
|
||||
manufacturer="manufacturer",
|
||||
model="model",
|
||||
)
|
||||
|
||||
assert len(registry.devices) == 2
|
||||
assert entry.id == entry2.id
|
||||
assert entry.id != entry3.id
|
||||
assert entry2.config_entries == {"123", "456"}
|
||||
|
||||
updated_entry = registry.async_update_device(
|
||||
entry2.id, remove_config_entry_id="123"
|
||||
)
|
||||
removed_entry = registry.async_update_device(
|
||||
entry3.id, remove_config_entry_id="123"
|
||||
)
|
||||
|
||||
assert updated_entry.config_entries == {"456"}
|
||||
assert removed_entry is None
|
||||
|
||||
removed_entry = registry.async_get_device({("bridgeid", "4567")}, set())
|
||||
|
||||
assert removed_entry is None
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(update_events) == 5
|
||||
assert update_events[0]["action"] == "create"
|
||||
assert update_events[0]["device_id"] == entry.id
|
||||
assert update_events[1]["action"] == "update"
|
||||
assert update_events[1]["device_id"] == entry2.id
|
||||
assert update_events[2]["action"] == "create"
|
||||
assert update_events[2]["device_id"] == entry3.id
|
||||
assert update_events[3]["action"] == "update"
|
||||
assert update_events[3]["device_id"] == entry.id
|
||||
assert update_events[4]["action"] == "remove"
|
||||
assert update_events[4]["device_id"] == entry3.id
|
||||
|
||||
|
||||
async def test_loading_race_condition(hass):
|
||||
"""Test only one storage load called when concurrent loading occurred ."""
|
||||
with asynctest.patch(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue