diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 8b47363c7ba..1b9a418fb29 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -20,8 +20,7 @@ async def async_setup_entry(hass, config_entry): Load config, group, light and sensor data for server information. Start websocket for push notification of state changes from deCONZ. """ - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} + hass.data.setdefault(DOMAIN, {}) await async_update_group_unique_id(hass, config_entry) @@ -33,7 +32,7 @@ async def async_setup_entry(hass, config_entry): if not await gateway.async_setup(): return False - hass.data[DOMAIN][config_entry.unique_id] = gateway + hass.data[DOMAIN][config_entry.entry_id] = gateway await gateway.async_update_device_registry() @@ -48,7 +47,7 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload deCONZ config entry.""" - gateway = hass.data[DOMAIN].pop(config_entry.unique_id) + gateway = hass.data[DOMAIN].pop(config_entry.entry_id) if not hass.data[DOMAIN]: await async_unload_services(hass) diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 8b057ab9e51..0a7d7e0c849 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -33,8 +33,8 @@ from .errors import AuthenticationRequired, CannotConnect @callback def get_gateway_from_config_entry(hass, config_entry): - """Return gateway with a matching bridge id.""" - return hass.data[DECONZ_DOMAIN][config_entry.unique_id] + """Return gateway with a matching config entry ID.""" + return hass.data[DECONZ_DOMAIN][config_entry.entry_id] class DeconzGateway: diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index d524354ff0b..a4f4aec6a76 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -59,14 +59,29 @@ async def async_setup_services(hass): service = service_call.service service_data = service_call.data + gateway = get_master_gateway(hass) + if CONF_BRIDGE_ID in service_data: + found_gateway = False + bridge_id = normalize_bridge_id(service_data[CONF_BRIDGE_ID]) + + for possible_gateway in hass.data[DOMAIN].values(): + if possible_gateway.bridgeid == bridge_id: + gateway = possible_gateway + found_gateway = True + break + + if not found_gateway: + LOGGER.error("Could not find the gateway %s", bridge_id) + return + if service == SERVICE_CONFIGURE_DEVICE: - await async_configure_service(hass, service_data) + await async_configure_service(gateway, service_data) elif service == SERVICE_DEVICE_REFRESH: - await async_refresh_devices_service(hass, service_data) + await async_refresh_devices_service(gateway) elif service == SERVICE_REMOVE_ORPHANED_ENTRIES: - await async_remove_orphaned_entries_service(hass, service_data) + await async_remove_orphaned_entries_service(gateway) hass.services.async_register( DOMAIN, @@ -102,7 +117,7 @@ async def async_unload_services(hass): hass.services.async_remove(DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES) -async def async_configure_service(hass, data): +async def async_configure_service(gateway, data): """Set attribute of device in deCONZ. Entity is used to resolve to a device path (e.g. '/lights/1'). @@ -118,10 +133,6 @@ async def async_configure_service(hass, data): See Dresden Elektroniks REST API documentation for details: http://dresden-elektronik.github.io/deconz-rest-doc/rest/ """ - gateway = get_master_gateway(hass) - if CONF_BRIDGE_ID in data: - gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] - field = data.get(SERVICE_FIELD, "") entity_id = data.get(SERVICE_ENTITY) data = data[SERVICE_DATA] @@ -136,31 +147,21 @@ async def async_configure_service(hass, data): await gateway.api.request("put", field, json=data) -async def async_refresh_devices_service(hass, data): +async def async_refresh_devices_service(gateway): """Refresh available devices from deCONZ.""" - gateway = get_master_gateway(hass) - if CONF_BRIDGE_ID in data: - gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] - gateway.ignore_state_updates = True await gateway.api.refresh_state() gateway.ignore_state_updates = False - gateway.async_add_device_callback(NEW_GROUP, force=True) - gateway.async_add_device_callback(NEW_LIGHT, force=True) - gateway.async_add_device_callback(NEW_SCENE, force=True) - gateway.async_add_device_callback(NEW_SENSOR, force=True) + for new_device_type in [NEW_GROUP, NEW_LIGHT, NEW_SCENE, NEW_SENSOR]: + gateway.async_add_device_callback(new_device_type, force=True) -async def async_remove_orphaned_entries_service(hass, data): +async def async_remove_orphaned_entries_service(gateway): """Remove orphaned deCONZ entries from device and entity registries.""" - gateway = get_master_gateway(hass) - if CONF_BRIDGE_ID in data: - gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] - device_registry, entity_registry = await asyncio.gather( - hass.helpers.device_registry.async_get_registry(), - hass.helpers.entity_registry.async_get_registry(), + gateway.hass.helpers.device_registry.async_get_registry(), + gateway.hass.helpers.entity_registry.async_get_registry(), ) entity_entries = async_entries_for_config_entry( diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 6583372d7bd..814ec588b1e 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -61,8 +61,8 @@ async def test_setup_entry_successful(hass, aioclient_mock): config_entry = await setup_deconz_integration(hass, aioclient_mock) assert hass.data[DECONZ_DOMAIN] - assert config_entry.unique_id in hass.data[DECONZ_DOMAIN] - assert hass.data[DECONZ_DOMAIN][config_entry.unique_id].master + assert config_entry.entry_id in hass.data[DECONZ_DOMAIN] + assert hass.data[DECONZ_DOMAIN][config_entry.entry_id].master async def test_setup_entry_multiple_gateways(hass, aioclient_mock): @@ -80,8 +80,8 @@ async def test_setup_entry_multiple_gateways(hass, aioclient_mock): ) assert len(hass.data[DECONZ_DOMAIN]) == 2 - assert hass.data[DECONZ_DOMAIN][config_entry.unique_id].master - assert not hass.data[DECONZ_DOMAIN][config_entry2.unique_id].master + assert hass.data[DECONZ_DOMAIN][config_entry.entry_id].master + assert not hass.data[DECONZ_DOMAIN][config_entry2.entry_id].master async def test_unload_entry(hass, aioclient_mock): @@ -112,7 +112,7 @@ async def test_unload_entry_multiple_gateways(hass, aioclient_mock): assert await async_unload_entry(hass, config_entry) assert len(hass.data[DECONZ_DOMAIN]) == 1 - assert hass.data[DECONZ_DOMAIN][config_entry2.unique_id].master + assert hass.data[DECONZ_DOMAIN][config_entry2.entry_id].master async def test_update_group_unique_id(hass): diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 7ad9c82b08c..8a696da9eb4 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -152,8 +152,27 @@ async def test_configure_service_with_entity_and_field(hass, aioclient_mock): assert aioclient_mock.mock_calls[1][2] == {"on": True, "attr1": 10, "attr2": 20} +async def test_configure_service_with_faulty_bridgeid(hass, aioclient_mock): + """Test that service fails on a bad bridge id.""" + await setup_deconz_integration(hass, aioclient_mock) + aioclient_mock.clear_requests() + + data = { + CONF_BRIDGE_ID: "Bad bridge id", + SERVICE_FIELD: "/lights/1", + SERVICE_DATA: {"on": True}, + } + + await hass.services.async_call( + DECONZ_DOMAIN, SERVICE_CONFIGURE_DEVICE, service_data=data + ) + await hass.async_block_till_done() + + assert len(aioclient_mock.mock_calls) == 0 + + async def test_configure_service_with_faulty_field(hass, aioclient_mock): - """Test that service invokes pydeconz with the correct path and data.""" + """Test that service fails on a bad field.""" await setup_deconz_integration(hass, aioclient_mock) data = {SERVICE_FIELD: "light/2", SERVICE_DATA: {}} @@ -166,7 +185,7 @@ async def test_configure_service_with_faulty_field(hass, aioclient_mock): async def test_configure_service_with_faulty_entity(hass, aioclient_mock): - """Test that service invokes pydeconz with the correct path and data.""" + """Test that service on a non existing entity.""" await setup_deconz_integration(hass, aioclient_mock) aioclient_mock.clear_requests()