async def test_removed_device(opp, client, multiple_devices, integration): """Test that the device registry gets updated when a device gets removed.""" nodes = multiple_devices # Verify how many nodes are available assert len(client.driver.controller.nodes) == 2 # Make sure there are the same number of devices dev_reg = dr.async_get(opp) device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 2 # Check how many entities there are ent_reg = er.async_get(opp) entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 24 # Remove a node and reload the entry old_node = nodes.pop(13) await opp.config_entries.async_reload(integration.entry_id) await opp.async_block_till_done() # Assert that the node and all of it's entities were removed from the device and # entity registry device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id) assert len(device_entries) == 1 entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id) assert len(entity_entries) == 15 assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None
async def async_restore_block_attribute_entities(opp, config_entry, async_add_entities, wrapper, sensors, sensor_class): """Restore block attributes entities.""" entities = [] ent_reg = await entity_registry.async_get_registry(opp) entries = entity_registry.async_entries_for_config_entry( ent_reg, config_entry.entry_id) domain = sensor_class.__module__.split(".")[-1] for entry in entries: if entry.domain != domain: continue attribute = entry.unique_id.split("-")[-1] description = BlockAttributeDescription( name="", icon=entry.original_icon, unit=entry.unit_of_measurement, device_class=entry.device_class, ) entities.append( sensor_class(wrapper, None, attribute, description, entry, sensors)) if not entities: return async_add_entities(entities)
async def async_get_migration_data(opp): """Return dict with ozw side migration info.""" data = {} nodes_values = opp.data[DOMAIN][NODES_VALUES] ozw_config_entries = opp.config_entries.async_entries(DOMAIN) config_entry = ozw_config_entries[0] # ozw only has a single config entry ent_reg = await async_get_entity_registry(opp) entity_entries = async_entries_for_config_entry(ent_reg, config_entry.entry_id) unique_entries = {entry.unique_id: entry for entry in entity_entries} dev_reg = await async_get_device_registry(opp) for node_id, node_values in nodes_values.items(): for entity_values in node_values: unique_id = create_value_id(entity_values.primary) if unique_id not in unique_entries: continue node = entity_values.primary.node device_identifier = ( DOMAIN, create_device_id(node, entity_values.primary.instance), ) device_entry = dev_reg.async_get_device({device_identifier}, set()) data[unique_id] = { "node_id": node_id, "node_instance": entity_values.primary.instance, "device_id": device_entry.id, "command_class": entity_values.primary.command_class.value, "command_class_label": entity_values.primary.label, "value_index": entity_values.primary.index, "unique_id": unique_id, "entity_entry": unique_entries[unique_id], } return data
async def async_unload_entry(opp: core.OpenPeerPower, config_entry: config_entries.ConfigEntry): """Unload a config entry.""" unload_ok = await opp.config_entries.async_unload_platforms( config_entry, PLATFORMS) opp.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() # Remove zone2 and zone3 entities if needed entity_registry = await er.async_get_registry(opp) entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) unique_id = config_entry.unique_id or config_entry.entry_id zone2_id = f"{unique_id}-Zone2" zone3_id = f"{unique_id}-Zone3" for entry in entries: if entry.unique_id == zone2_id and not config_entry.options.get( CONF_ZONE2): entity_registry.async_remove(entry.entity_id) _LOGGER.debug("Removing zone2 from DenonAvr") if entry.unique_id == zone3_id and not config_entry.options.get( CONF_ZONE3): entity_registry.async_remove(entry.entity_id) _LOGGER.debug("Removing zone3 from DenonAvr") if unload_ok: opp.data[DOMAIN].pop(config_entry.entry_id) return unload_ok
async def async_setup_entry(opp, config_entry, async_add_entities): """Set up switches for UniFi component. Switches are controlling network access and switch ports with POE. """ controller = opp.data[UNIFI_DOMAIN][config_entry.entry_id] controller.entities[DOMAIN] = { BLOCK_SWITCH: set(), POE_SWITCH: set(), DPI_SWITCH: set(), } if controller.site_role != "admin": return # Store previously known POE control entities in case their POE are turned off. known_poe_clients = [] entity_registry = await opp.helpers.entity_registry.async_get_registry() for entry in async_entries_for_config_entry(entity_registry, config_entry.entry_id): if not entry.unique_id.startswith(POE_SWITCH): continue mac = entry.unique_id.replace(f"{POE_SWITCH}-", "") if mac not in controller.api.clients: continue known_poe_clients.append(mac) for mac in controller.option_block_clients: if mac not in controller.api.clients and mac in controller.api.clients_all: client = controller.api.clients_all[mac] controller.api.clients.process_raw([client.raw]) @callback def items_added( clients: set = controller.api.clients, devices: set = controller.api.devices, dpi_groups: set = controller.api.dpi_groups, ) -> None: """Update the values of the controller.""" if controller.option_block_clients: add_block_entities(controller, async_add_entities, clients) if controller.option_poe_clients: add_poe_entities(controller, async_add_entities, clients, known_poe_clients) if controller.option_dpi_restrictions: add_dpi_entities(controller, async_add_entities, dpi_groups) for signal in (controller.signal_update, controller.signal_options_update): config_entry.async_on_unload( async_dispatcher_connect(opp, signal, items_added)) items_added() known_poe_clients.clear()
async def test_add_new_binary_sensor_ignored(opp, aioclient_mock, mock_deconz_websocket): """Test that adding a new binary sensor is not allowed.""" sensor = { "name": "Presence sensor", "type": "ZHAPresence", "state": { "presence": False }, "config": { "on": True, "reachable": True }, "uniqueid": "00:00:00:00:00:00:00:00-00", } event_added_sensor = { "t": "event", "e": "added", "r": "sensors", "id": "1", "sensor": sensor, } config_entry = await setup_deconz_integration( opp, aioclient_mock, options={ CONF_MASTER_GATEWAY: True, CONF_ALLOW_NEW_DEVICES: False }, ) assert len(opp.states.async_all()) == 0 await mock_deconz_websocket(data=event_added_sensor) await opp.async_block_till_done() assert len(opp.states.async_all()) == 0 assert not opp.states.get("binary_sensor.presence_sensor") entity_registry = er.async_get(opp) assert (len( async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 0) aioclient_mock.clear_requests() data = {"groups": {}, "lights": {}, "sensors": {"1": sensor}} mock_deconz_request(aioclient_mock, config_entry.data, data) await opp.services.async_call(DECONZ_DOMAIN, SERVICE_DEVICE_REFRESH) await opp.async_block_till_done() assert len(opp.states.async_all()) == 1 assert opp.states.get("binary_sensor.presence_sensor")
def _resolve_config_entry(self, config_entry_id) -> None: """Resolve a config entry. Will only be called if config entry is an entry point. """ for device_entry in device_registry.async_entries_for_config_entry( self._device_reg, config_entry_id): self._add_or_resolve("device", device_entry.id) for entity_entry in entity_registry.async_entries_for_config_entry( self._entity_reg, config_entry_id): self._add_or_resolve("entity", entity_entry.entity_id)
async def async_setup_entry(opp: OpenPeerPower, config_entry: ConfigEntry) -> bool: """Load the saved entities.""" if config_entry.unique_id is not None: opp.config_entries.async_update_entry(config_entry, unique_id=None) ent_reg = async_get(opp) for entity in async_entries_for_config_entry(ent_reg, config_entry.entry_id): ent_reg.async_update_entity( entity.entity_id, new_unique_id=config_entry.entry_id ) opp.config_entries.async_setup_platforms(config_entry, PLATFORMS) return True
async def async_setup_entry(opp: OpenPeerPower, config_entry: ConfigEntry): """Set up Google Maps Travel Time from a config entry.""" if config_entry.unique_id is not None: opp.config_entries.async_update_entry(config_entry, unique_id=None) ent_reg = async_get(opp) for entity in async_entries_for_config_entry(ent_reg, config_entry.entry_id): ent_reg.async_update_entity( entity.entity_id, new_unique_id=config_entry.entry_id ) opp.config_entries.async_setup_platforms(config_entry, PLATFORMS) return True
async def async_setup_entry(opp, config_entry, async_add_entities): """Set up mobile app binary sensor from a config entry.""" entities = [] webhook_id = config_entry.data[CONF_WEBHOOK_ID] entity_registry = await er.async_get_registry(opp) entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id) for entry in entries: if entry.domain != ENTITY_TYPE or entry.disabled_by: continue config = { ATTR_SENSOR_ATTRIBUTES: {}, ATTR_SENSOR_DEVICE_CLASS: entry.device_class, ATTR_SENSOR_ICON: entry.original_icon, ATTR_SENSOR_NAME: entry.original_name, ATTR_SENSOR_STATE: None, ATTR_SENSOR_TYPE: entry.domain, ATTR_SENSOR_UNIQUE_ID: entry.unique_id, } entities.append( MobileAppBinarySensor(config, entry.device_id, config_entry)) async_add_entities(entities) @callback def handle_sensor_registration(data): if data[CONF_WEBHOOK_ID] != webhook_id: return data[CONF_UNIQUE_ID] = unique_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]) data[ CONF_NAME] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}" device = opp.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]] async_add_entities([MobileAppBinarySensor(data, device, config_entry)]) async_dispatcher_connect( opp, f"{DOMAIN}_{ENTITY_TYPE}_register", handle_sensor_registration, )
async def async_cleanup_registry_entries(service) -> None: """Remove extra entities that are no longer part of the integration.""" entity_registry = await er.async_get_registry(opp) config_ids = [] current_unique_ids = [] for config_entry_id in opp.data[DOMAIN]: entries_for_this_config = er.async_entries_for_config_entry( entity_registry, config_entry_id) config_ids.extend([(entity.unique_id, entity.entity_id) for entity in entries_for_this_config]) opp_isy_data = opp.data[DOMAIN][config_entry_id] uuid = opp_isy_data[ISY994_ISY].configuration["uuid"] for platform in PLATFORMS: for node in opp_isy_data[ISY994_NODES][platform]: if hasattr(node, "address"): current_unique_ids.append(f"{uuid}_{node.address}") for platform in PROGRAM_PLATFORMS: for _, node, _ in opp_isy_data[ISY994_PROGRAMS][platform]: if hasattr(node, "address"): current_unique_ids.append(f"{uuid}_{node.address}") for node in opp_isy_data[ISY994_VARIABLES]: if hasattr(node, "address"): current_unique_ids.append(f"{uuid}_{node.address}") extra_entities = [ entity_id for unique_id, entity_id in config_ids if unique_id not in current_unique_ids ] for entity_id in extra_entities: if entity_registry.async_is_registered(entity_id): entity_registry.async_remove(entity_id) _LOGGER.debug( "Cleaning up ISY994 Entities and devices: Config Entries: %s, Current Entries: %s, " "Extra Entries Removed: %s", len(config_ids), len(current_unique_ids), len(extra_entities), )
async def async_get_ozw_migration_data(opp): """Return dict with info for migration to ozw integration.""" data_to_migrate = {} zwave_config_entries = opp.config_entries.async_entries(DOMAIN) if not zwave_config_entries: _LOGGER.error("Config entry not set up") return data_to_migrate if opp.data.get(DATA_ZWAVE_CONFIG_YAML_PRESENT): _LOGGER.warning( "Remove %s from configuration.yaml " "to avoid setting up this integration on restart " "after completing migration to ozw", DOMAIN, ) config_entry = zwave_config_entries[ 0] # zwave only has a single config entry ent_reg = await async_get_entity_registry(opp) entity_entries = async_entries_for_config_entry(ent_reg, config_entry.entry_id) unique_entries = {entry.unique_id: entry for entry in entity_entries} dev_reg = await async_get_device_registry(opp) for entity_values in opp.data[DATA_ENTITY_VALUES]: node = entity_values.primary.node unique_id = compute_value_unique_id(node, entity_values.primary) if unique_id not in unique_entries: continue device_identifier, _ = node_device_id_and_name( node, entity_values.primary.instance) device_entry = dev_reg.async_get_device({device_identifier}, set()) data_to_migrate[unique_id] = { "node_id": node.node_id, "node_instance": entity_values.primary.instance, "device_id": device_entry.id, "command_class": entity_values.primary.command_class, "command_class_label": entity_values.primary.label, "value_index": entity_values.primary.index, "unique_id": unique_id, "entity_entry": unique_entries[unique_id], } return data_to_migrate
async def async_remove_obsolete_entities(opp: OpenPeerPower, entry: ConfigEntry, hap: HomematicipHAP): """Remove obsolete entities from entity registry.""" if hap.home.currentAPVersion < "2.2.12": return entity_registry = await er.async_get_registry(opp) er_entries = async_entries_for_config_entry(entity_registry, entry.entry_id) for er_entry in er_entries: if er_entry.unique_id.startswith("HomematicipAccesspointStatus"): entity_registry.async_remove(er_entry.entity_id) continue for hapid in hap.home.accessPointUpdateStates: if er_entry.unique_id == f"HomematicipBatterySensor_{hapid}": entity_registry.async_remove(er_entry.entity_id)
async def async_setup(self): """Set up a UniFi controller.""" try: self.api = await get_controller( self.opp, host=self.config_entry.data[CONF_HOST], username=self.config_entry.data[CONF_USERNAME], password=self.config_entry.data[CONF_PASSWORD], port=self.config_entry.data[CONF_PORT], site=self.config_entry.data[CONF_SITE_ID], verify_ssl=self.config_entry.data[CONF_VERIFY_SSL], async_callback=self.async_unifi_signalling_callback, ) await self.api.initialize() sites = await self.api.sites() description = await self.api.site_description() except CannotConnect as err: raise ConfigEntryNotReady from err except AuthenticationRequired as err: raise ConfigEntryAuthFailed from err for site in sites.values(): if self.site == site["name"]: self.site_id = site["_id"] self._site_name = site["desc"] break self._site_role = description[0]["site_role"] # Restore clients that are not a part of active clients list. entity_registry = await self.opp.helpers.entity_registry.async_get_registry( ) for entry in async_entries_for_config_entry( entity_registry, self.config_entry.entry_id): if entry.domain == TRACKER_DOMAIN: mac = entry.unique_id.split("-", 1)[0] elif entry.domain == SWITCH_DOMAIN and ( entry.unique_id.startswith(BLOCK_SWITCH) or entry.unique_id.startswith(POE_SWITCH)): mac = entry.unique_id.split("-", 1)[1] else: continue if mac in self.api.clients or mac not in self.api.clients_all: continue client = self.api.clients_all[mac] self.api.clients.process_raw([client.raw]) LOGGER.debug( "Restore disconnected client %s (%s)", entry.entity_id, client.mac, ) wireless_clients = self.opp.data[UNIFI_WIRELESS_CLIENTS] self.wireless_clients = wireless_clients.get_data(self.config_entry) self.update_wireless_clients() self.opp.config_entries.async_setup_platforms(self.config_entry, PLATFORMS) self.api.start_websocket() self.config_entry.add_update_listener(self.async_config_entry_updated) self._cancel_heartbeat_check = async_track_time_interval( self.opp, self._async_check_for_stale, CHECK_HEARTBEAT_INTERVAL) return True
async def test_remove_orphaned_entries_service(opp, aioclient_mock): """Test service works and also don't remove more than expected.""" data = { "lights": { "1": { "name": "Light 1 name", "state": { "reachable": True }, "type": "Light", "uniqueid": "00:00:00:00:00:00:00:01-00", } }, "sensors": { "1": { "name": "Switch 1", "type": "ZHASwitch", "state": { "buttonevent": 1000, "gesture": 1 }, "config": { "battery": 100 }, "uniqueid": "00:00:00:00:00:00:00:03-00", }, }, } with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(opp, aioclient_mock) device_registry = dr.async_get(opp) device = device_registry.async_get_or_create( config_entry_id=config_entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, "123")}, ) assert (len([ entry for entry in device_registry.devices.values() if config_entry.entry_id in entry.config_entries ]) == 5 # Host, gateway, light, switch and orphan ) entity_registry = er.async_get(opp) entity_registry.async_get_or_create( SENSOR_DOMAIN, DECONZ_DOMAIN, "12345", suggested_object_id="Orphaned sensor", config_entry=config_entry, device_id=device.id, ) assert (len( async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 3 # Light, switch battery and orphan ) await opp.services.async_call( DECONZ_DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES, service_data={CONF_BRIDGE_ID: BRIDGEID}, ) await opp.async_block_till_done() assert (len([ entry for entry in device_registry.devices.values() if config_entry.entry_id in entry.config_entries ]) == 4 # Host, gateway, light and switch ) assert (len( async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 2 # Light and switch battery )