Beispiel #1
0
async def test_registry_cleanup(owproxy, hass):
    """Test for 1-Wire device.

    As they would be on a clean setup: all binary-sensors and switches disabled.
    """
    await async_setup_component(hass, "persistent_notification", {})
    entity_registry = mock_registry(hass)
    device_registry = mock_device_registry(hass)

    # Initialise with two components
    setup_owproxy_mock_devices(owproxy, SENSOR_DOMAIN,
                               ["10.111111111111", "28.111111111111"])
    with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]):
        await setup_onewire_patched_owserver_integration(hass)
        await hass.async_block_till_done()

    assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2
    assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 2

    # Second item has disappeared from bus, and was removed manually from the front-end
    setup_owproxy_mock_devices(owproxy, SENSOR_DOMAIN, ["10.111111111111"])
    entity_registry.async_remove("sensor.28_111111111111_temperature")
    await hass.async_block_till_done()

    assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
    assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2

    # Second item has disappeared from bus, and was removed manually from the front-end
    with patch("homeassistant.components.onewire.PLATFORMS", [SENSOR_DOMAIN]):
        await hass.config_entries.async_reload("2")
        await hass.async_block_till_done()

    assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
    assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 1
Beispiel #2
0
async def test_registry_cleanup(hass: HomeAssistant, config_entry: ConfigEntry,
                                owproxy: MagicMock):
    """Test for 1-Wire device.

    As they would be on a clean setup: all binary-sensors and switches disabled.
    """
    entity_registry = mock_registry(hass)
    device_registry = mock_device_registry(hass)

    # Initialise with two components
    setup_owproxy_mock_devices(owproxy, SENSOR_DOMAIN,
                               ["10.111111111111", "28.111111111111"])
    await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2
    assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 2

    # Second item has disappeared from bus, and was removed manually from the front-end
    setup_owproxy_mock_devices(owproxy, SENSOR_DOMAIN, ["10.111111111111"])
    entity_registry.async_remove("sensor.28_111111111111_temperature")
    await hass.async_block_till_done()

    assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
    assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 2

    # Second item has disappeared from bus, and was removed manually from the front-end
    await hass.config_entries.async_reload("2")
    await hass.async_block_till_done()

    assert len(er.async_entries_for_config_entry(entity_registry, "2")) == 1
    assert len(dr.async_entries_for_config_entry(device_registry, "2")) == 1
Beispiel #3
0
async def test_unique_id_migration(
    hass,
    mock_events_list_items,
    component_setup,
    config_entry,
    old_unique_id,
):
    """Test that old unique id format is migrated to the new format that supports multiple accounts."""
    entity_registry = er.async_get(hass)

    # Create an entity using the old unique id format
    entity_registry.async_get_or_create(
        DOMAIN,
        Platform.CALENDAR,
        unique_id=old_unique_id,
        config_entry=config_entry,
    )
    registry_entries = er.async_entries_for_config_entry(
        entity_registry, config_entry.entry_id)
    assert {entry.unique_id for entry in registry_entries} == {old_unique_id}

    mock_events_list_items([])
    assert await component_setup()

    registry_entries = er.async_entries_for_config_entry(
        entity_registry, config_entry.entry_id)
    assert {entry.unique_id
            for entry in registry_entries
            } == {f"{config_entry.unique_id}-{CALENDAR_ID}"}
Beispiel #4
0
async def test_removed_device(hass, client,
                              climate_radio_thermostat_ct100_plus,
                              lock_schlage_be469, integration):
    """Test that the device registry gets updated when a device gets removed."""
    driver = client.driver
    assert driver
    # Verify how many nodes are available
    assert len(driver.controller.nodes) == 2

    # Make sure there are the same number of devices
    dev_reg = dr.async_get(hass)
    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(hass)
    entity_entries = er.async_entries_for_config_entry(ent_reg,
                                                       integration.entry_id)
    assert len(entity_entries) == 29

    # Remove a node and reload the entry
    old_node = driver.controller.nodes.pop(13)
    await hass.config_entries.async_reload(integration.entry_id)
    await hass.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) == 17
    assert dev_reg.async_get_device({get_device_id(driver, old_node)}) is None
Beispiel #5
0
async def test_removed_device(hass, 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(hass)
    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(hass)
    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 hass.config_entries.async_reload(integration.entry_id)
    await hass.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 test_remove_orphaned_entries_service(hass):
    """Test service works and also don't remove more than expected."""
    data = deepcopy(DECONZ_WEB_REQUEST)
    data["lights"] = deepcopy(LIGHT)
    data["sensors"] = deepcopy(SWITCH)
    gateway = await setup_deconz_integration(hass, get_state_response=data)

    data = {CONF_BRIDGE_ID: BRIDGEID}

    device_registry = await hass.helpers.device_registry.async_get_registry()
    device = device_registry.async_get_or_create(
        config_entry_id=gateway.config_entry.entry_id,
        identifiers={("mac", "123")})

    assert (len([
        entry for entry in device_registry.devices.values()
        if gateway.config_entry.entry_id in entry.config_entries
    ]) == 4  # Gateway, light, switch and orphan
            )

    entity_registry = await hass.helpers.entity_registry.async_get_registry()
    entity_registry.async_get_or_create(
        SENSOR_DOMAIN,
        deconz.DOMAIN,
        "12345",
        suggested_object_id="Orphaned sensor",
        config_entry=gateway.config_entry,
        device_id=device.id,
    )

    assert (len(
        async_entries_for_config_entry(entity_registry,
                                       gateway.config_entry.entry_id)) ==
            3  # Light, switch battery and orphan
            )

    await hass.services.async_call(
        deconz.DOMAIN,
        deconz.services.SERVICE_REMOVE_ORPHANED_ENTRIES,
        service_data=data,
    )
    await hass.async_block_till_done()

    assert (len([
        entry for entry in device_registry.devices.values()
        if gateway.config_entry.entry_id in entry.config_entries
    ]) == 3  # Gateway, light and switch
            )

    assert (len(
        async_entries_for_config_entry(entity_registry,
                                       gateway.config_entry.entry_id)) ==
            2  # Light and switch battery
            )
Beispiel #7
0
async def async_remove_config_entry_device(hass: HomeAssistant,
                                           config_entry: ConfigEntry,
                                           device_entry: DeviceEntry) -> bool:
    """Remove a config entry from a device."""
    dev_id = list(device_entry.identifiers)[0][1].split("_")[-1]

    ent_reg = er.async_get(hass)
    entities = {
        ent.unique_id: ent.entity_id
        for ent in er.async_entries_for_config_entry(
            ent_reg, config_entry.entry_id) if dev_id in ent.unique_id
    }
    for entity_id in entities.values():
        ent_reg.async_remove(entity_id)

    if dev_id not in config_entry.data[CONF_DEVICES]:
        _LOGGER.info(
            "Device %s not found in config entry: finalizing device removal",
            dev_id)
        return True

    await hass.data[DOMAIN][TUYA_DEVICES][dev_id].close()

    new_data = config_entry.data.copy()
    new_data[CONF_DEVICES].pop(dev_id)
    new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000))

    hass.config_entries.async_update_entry(
        config_entry,
        data=new_data,
    )

    _LOGGER.info("Device %s removed.", dev_id)

    return True
Beispiel #8
0
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Unload a config entry."""
    unload_ok = await hass.config_entries.async_unload_platforms(
        entry, PLATFORMS)

    router = hass.data[DOMAIN][entry.entry_id][KEY_ROUTER]

    if unload_ok:
        hass.data[DOMAIN].pop(entry.entry_id)
        if not hass.data[DOMAIN]:
            hass.data.pop(DOMAIN)

    if router.mode != MODE_ROUTER:
        router_id = None
        # Remove devices that are no longer tracked
        device_registry = dr.async_get(hass)
        devices = dr.async_entries_for_config_entry(device_registry,
                                                    entry.entry_id)
        for device_entry in devices:
            if device_entry.via_device_id is None:
                router_id = device_entry.id
                continue  # do not remove the router itself
            device_registry.async_update_device(
                device_entry.id, remove_config_entry_id=entry.entry_id)
        # Remove entities that are no longer tracked
        entity_registry = er.async_get(hass)
        entries = er.async_entries_for_config_entry(entity_registry,
                                                    entry.entry_id)
        for entity_entry in entries:
            if entity_entry.device_id is not router_id:
                entity_registry.async_remove(entity_entry.entity_id)

    return unload_ok
async def async_migrate_entities_devices(
    hass: HomeAssistant, legacy_entry_id: str, new_entry: ConfigEntry
) -> None:
    """Move entities and devices to the new config entry."""
    migrated_devices = []
    device_registry = dr.async_get(hass)
    for dev_entry in dr.async_entries_for_config_entry(
        device_registry, legacy_entry_id
    ):
        for connection_type, value in dev_entry.connections:
            if (
                connection_type == dr.CONNECTION_NETWORK_MAC
                and value == new_entry.unique_id
            ):
                migrated_devices.append(dev_entry.id)
                device_registry.async_update_device(
                    dev_entry.id, add_config_entry_id=new_entry.entry_id
                )

    entity_registry = er.async_get(hass)
    for reg_entity in er.async_entries_for_config_entry(
        entity_registry, legacy_entry_id
    ):
        if reg_entity.device_id in migrated_devices:
            entity_registry.async_update_entity(
                reg_entity.entity_id, config_entry_id=new_entry.entry_id
            )
Beispiel #10
0
async def test_migrate_reboot_button_no_device(hass: HomeAssistant,
                                               ufp: MockUFPFixture,
                                               light: Light):
    """Test migrating unique ID of reboot button if UniFi Protect device ID changed."""

    light2_id, _ = generate_random_ids()

    registry = er.async_get(hass)
    registry.async_get_or_create(Platform.BUTTON,
                                 DOMAIN,
                                 light2_id,
                                 config_entry=ufp.entry)

    ufp.api.get_bootstrap = AsyncMock(return_value=ufp.api.bootstrap)
    await init_entry(hass, ufp, [light], regenerate_ids=False)

    assert ufp.entry.state == ConfigEntryState.LOADED
    assert ufp.api.update.called
    assert ufp.entry.unique_id == ufp.api.bootstrap.nvr.mac

    buttons = []
    for entity in er.async_entries_for_config_entry(registry,
                                                    ufp.entry.entry_id):
        if entity.domain == Platform.BUTTON.value:
            buttons.append(entity)
    assert len(buttons) == 3

    entity = registry.async_get(
        f"{Platform.BUTTON}.unifiprotect_{light2_id.lower()}")
    assert entity is not None
    assert entity.unique_id == light2_id
Beispiel #11
0
def async_restore_block_attribute_entities(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
    wrapper: BlockDeviceWrapper,
    sensors: Mapping[tuple[str, str], BlockEntityDescription],
    sensor_class: Callable,
    description_class: Callable[[entity_registry.RegistryEntry],
                                BlockEntityDescription],
) -> None:
    """Restore block attributes entities."""
    entities = []

    ent_reg = entity_registry.async_get(hass)
    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 = description_class(entry)

        entities.append(
            sensor_class(wrapper, None, attribute, description, entry,
                         sensors))

    if not entities:
        return

    async_add_entities(entities)
Beispiel #12
0
async def async_remove_orphaned_entries_service(gateway):
    """Remove orphaned deCONZ entries from device and entity registries."""
    device_registry, entity_registry = await asyncio.gather(
        gateway.hass.helpers.device_registry.async_get_registry(),
        gateway.hass.helpers.entity_registry.async_get_registry(),
    )

    entity_entries = async_entries_for_config_entry(
        entity_registry, gateway.config_entry.entry_id)

    entities_to_be_removed = []
    devices_to_be_removed = [
        entry.id for entry in device_registry.devices.values()
        if gateway.config_entry.entry_id in entry.config_entries
    ]

    # Don't remove the Gateway host entry
    gateway_host = device_registry.async_get_device(
        connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)},
        identifiers=set(),
    )
    if gateway_host.id in devices_to_be_removed:
        devices_to_be_removed.remove(gateway_host.id)

    # Don't remove the Gateway service entry
    gateway_service = device_registry.async_get_device(identifiers={
        (DOMAIN, gateway.api.config.bridgeid)
    },
                                                       connections=set())
    if gateway_service.id in devices_to_be_removed:
        devices_to_be_removed.remove(gateway_service.id)

    # Don't remove devices belonging to available events
    for event in gateway.events:
        if event.device_id in devices_to_be_removed:
            devices_to_be_removed.remove(event.device_id)

    for entry in entity_entries:

        # Don't remove available entities
        if entry.unique_id in gateway.entities[entry.domain]:

            # Don't remove devices with available entities
            if entry.device_id in devices_to_be_removed:
                devices_to_be_removed.remove(entry.device_id)
            continue
        # Remove entities that are not available
        entities_to_be_removed.append(entry.entity_id)

    # Remove unavailable entities
    for entity_id in entities_to_be_removed:
        entity_registry.async_remove(entity_id)

    # Remove devices that don't belong to any entity
    for device_id in devices_to_be_removed:
        if (len(
                async_entries_for_device(
                    entity_registry, device_id,
                    include_disabled_entities=True)) == 0):
            device_registry.async_remove_device(device_id)
Beispiel #13
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Huawei LTE component from config entry."""
    url = entry.data[CONF_URL]

    def get_connection() -> Connection:
        """Set up a connection."""
        if entry.options.get(CONF_UNAUTHENTICATED_MODE):
            _LOGGER.debug(
                "Connecting in unauthenticated mode, reduced feature set")
            connection = Connection(url, timeout=CONNECTION_TIMEOUT)
        else:
            _LOGGER.debug("Connecting in authenticated mode, full feature set")
            username = entry.data.get(CONF_USERNAME) or ""
            password = entry.data.get(CONF_PASSWORD) or ""
            connection = Connection(url,
                                    username=username,
                                    password=password,
                                    timeout=CONNECTION_TIMEOUT)
        return connection

    try:
        connection = await hass.async_add_executor_job(get_connection)
    except Timeout as ex:
        raise ConfigEntryNotReady from ex

    # Set up router
    router = Router(hass, entry, connection, url)

    # Do initial data update
    await hass.async_add_executor_job(router.update)

    # Check that we found required information
    router_info = router.data.get(KEY_DEVICE_INFORMATION)
    if not entry.unique_id:
        # Transitional from < 2021.8: update None config entry and entity unique ids
        if router_info and (serial_number := router_info.get("SerialNumber")):
            hass.config_entries.async_update_entry(entry,
                                                   unique_id=serial_number)
            ent_reg = entity_registry.async_get(hass)
            for entity_entry in entity_registry.async_entries_for_config_entry(
                    ent_reg, entry.entry_id):
                if not entity_entry.unique_id.startswith("None-"):
                    continue
                new_unique_id = (
                    f"{serial_number}-{entity_entry.unique_id.split('-', 1)[1]}"
                )
                ent_reg.async_update_entity(entity_entry.entity_id,
                                            new_unique_id=new_unique_id)
        else:
            await hass.async_add_executor_job(router.cleanup)
            msg = (
                "Could not resolve serial number to use as unique id for router at %s"
                ", setup failed")
            if not entry.data.get(CONF_PASSWORD):
                msg += (
                    ". Try setting up credentials for the router for one startup, "
                    "unauthenticated mode can be enabled after that in integration "
                    "settings")
            _LOGGER.error(msg, url)
            return False
async def async_get_migration_data(hass):
    """Return dict with ozw side migration info."""
    data = {}
    nodes_values = hass.data[DOMAIN][NODES_VALUES]
    ozw_config_entries = hass.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(hass)
    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(hass)

    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
Beispiel #15
0
    def async_cleanup_registry_entries(service: ServiceCall) -> None:
        """Remove extra entities that are no longer part of the integration."""
        entity_registry = er.async_get(hass)
        config_ids = []
        current_unique_ids: set[str] = set()

        for config_entry_id in hass.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
                ]
            )
            current_unique_ids |= unique_ids_for_config_entry_id(hass, config_entry_id)

        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),
        )
Beispiel #16
0
async def async_setup_entry(hass, config_entry, async_add_entities):

    controller: OmadaController = hass.data[OMADA_DOMAIN][
        config_entry.entry_id][DATA_OMADA]
    controller.entities[DOMAIN] = set()

    def get_clients_filtered():
        clients = set()

        for mac in controller.api.clients:
            client = controller.api.clients[mac]

            # Skip adding client if not connected to ssid in filter list
            if controller.option_ssid_filter and client.ssid not in controller.option_ssid_filter:
                continue

            clients.add(client.mac)

        return clients

    def get_devices():
        devices = set()

        for mac in controller.api.devices:
            device = controller.api.devices[mac]
            devices.add(device.mac)

        return devices

    @callback
    def items_added(macs: set = get_clients_filtered()):
        add_client_entities(controller, async_add_entities, macs)

    config_entry.async_on_unload(
        async_dispatcher_connect(hass, controller.signal_update, items_added))

    entity_registry = await hass.helpers.entity_registry.async_get_registry()
    initial_set = set()

    # Add connected entries
    for mac in get_devices():
        initial_set.add(mac)
    for mac in get_clients_filtered():
        initial_set.add(mac)

    # Add entries that used to exist in HA but are now disconnected.
    for entry in async_entries_for_config_entry(entity_registry,
                                                config_entry.entry_id):
        mac = entry.unique_id

        if mac in controller.api.devices:
            continue
        elif mac not in controller.api.clients:
            if mac in controller.api.known_clients:
                initial_set.add(mac)
        elif controller.option_ssid_filter and controller.api.clients[
                mac].ssid not in controller.option_ssid_filter:
            entity_registry.async_remove(entry.entity_id)

    items_added(initial_set)
Beispiel #17
0
async def async_unload_entry(
    hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
):
    """Unload a config entry."""
    unload_ok = await hass.config_entries.async_forward_entry_unload(
        config_entry, "media_player"
    )

    hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]()

    # Remove zone2 and zone3 entities if needed
    entity_registry = await er.async_get_registry(hass)
    entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
    zone2_id = f"{config_entry.unique_id}-Zone2"
    zone3_id = f"{config_entry.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:
        hass.data[DOMAIN].pop(config_entry.entry_id)

    return unload_ok
    async def async_step_init(self,
                              user_input: Dict[str,
                                               str] = None) -> Dict[str, str]:
        """Manage the options for the custom component."""
        errors: Dict[str, str] = {}

        # Grab all devices from the entity registry so we can populate the
        # dropdown list that will allow a user to configure a device.
        entity_registry = await async_get_registry(self.hass)
        devices = async_entries_for_config_entry(entity_registry,
                                                 self.config_entry.entry_id)
        stats = {
            e.unique_id: e.capabilities
            for e in devices if e.entity_id.startswith('climate.')
        }

        if user_input is not None:
            _LOGGER.debug(f"user_input: {user_input}")
            _LOGGER.debug(f"original config: {self.config}")

            # Remove any devices where hvac_modes have been unset.
            remove_devices = [
                unique_id for unique_id in stats.keys()
                if unique_id == user_input["device"]
                if len(user_input["hvac_modes"]) == 0
            ]
            for unique_id in remove_devices:
                if unique_id in self.config:
                    self.config.pop(unique_id)

            if len(user_input["hvac_modes"]) != 0:
                if not errors:
                    # Add the new device config.
                    self.config[
                        user_input["device"]] = user_input["hvac_modes"]

            _LOGGER.debug(f"updated config: {self.config}")

            if not errors:
                # If user selected the 'more' tickbox, show this form again
                # so they can configure additional devices.
                if user_input.get('more', False):
                    return await self.async_step_init()

                # Value of data will be set on the options property of the config_entry instance.
                return self.async_create_entry(
                    title="", data={CONF_HVAC_MODES: self.config})

        options_schema = vol.Schema({
            vol.Optional("device", default=list(stats.keys())):
            vol.In(stats.keys()),
            vol.Optional("hvac_modes", default=list(default_modes)):
            cv.multi_select(modes),
            vol.Optional("more"):
            cv.boolean
        })

        return self.async_show_form(step_id="init",
                                    data_schema=options_schema,
                                    errors=errors)
async def test_add_new_binary_sensor_ignored(hass):
    """Test that adding a new binary sensor is not allowed."""
    config_entry = await setup_deconz_integration(
        hass,
        options={CONF_ALLOW_NEW_DEVICES: False},
    )
    gateway = get_gateway_from_config_entry(hass, config_entry)
    assert len(hass.states.async_all()) == 0

    state_added_event = {
        "t": "event",
        "e": "added",
        "r": "sensors",
        "id": "1",
        "sensor": deepcopy(SENSORS["1"]),
    }
    gateway.api.event_handler(state_added_event)
    await hass.async_block_till_done()

    assert len(hass.states.async_all()) == 0

    entity_registry = await hass.helpers.entity_registry.async_get_registry()
    assert (
        len(async_entries_for_config_entry(entity_registry, config_entry.entry_id)) == 0
    )
Beispiel #20
0
def async_migrate_entities_devices(
    hass: HomeAssistant, legacy_entry_id: str, new_entry: ConfigEntry
) -> None:
    """Move entities and devices to the new config entry."""
    migrated_devices = []
    device_registry = dr.async_get(hass)
    for dev_entry in dr.async_entries_for_config_entry(
        device_registry, legacy_entry_id
    ):
        for domain, value in dev_entry.identifiers:
            if domain == DOMAIN and value == new_entry.unique_id:
                _LOGGER.debug(
                    "Migrating device with %s to %s",
                    dev_entry.identifiers,
                    new_entry.unique_id,
                )
                migrated_devices.append(dev_entry.id)
                device_registry.async_update_device(
                    dev_entry.id,
                    add_config_entry_id=new_entry.entry_id,
                    remove_config_entry_id=legacy_entry_id,
                )

    entity_registry = er.async_get(hass)
    for reg_entity in er.async_entries_for_config_entry(
        entity_registry, legacy_entry_id
    ):
        if reg_entity.device_id in migrated_devices:
            entity_registry.async_update_entity(
                reg_entity.entity_id, config_entry_id=new_entry.entry_id
            )
Beispiel #21
0
async def async_unload_entry(hass: HomeAssistant,
                             config_entry: ConfigEntry) -> bool:
    """Unload a config entry."""
    unload_ok = await hass.config_entries.async_unload_platforms(
        config_entry, PLATFORMS)

    hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]()

    # Remove zone2 and zone3 entities if needed
    entity_registry = er.async_get(hass)
    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:
        hass.data[DOMAIN].pop(config_entry.entry_id)

    return unload_ok
Beispiel #22
0
async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up a ONVIF binary sensor."""
    device = hass.data[DOMAIN][config_entry.unique_id]

    entities = {
        event.uid: ONVIFSensor(event.uid, device)
        for event in device.events.get_platform("sensor")
    }

    ent_reg = er.async_get(hass)
    for entry in er.async_entries_for_config_entry(ent_reg, config_entry.entry_id):
        if entry.domain == "sensor" and entry.unique_id not in entities:
            entities[entry.unique_id] = ONVIFSensor(entry.unique_id, device, entry)

    async_add_entities(entities.values())

    @callback
    def async_check_entities():
        """Check if we have added an entity for the event."""
        new_entities = []
        for event in device.events.get_platform("sensor"):
            if event.uid not in entities:
                entities[event.uid] = ONVIFSensor(event.uid, device)
                new_entities.append(entities[event.uid])
        async_add_entities(new_entities)

    device.events.async_add_listener(async_check_entities)

    return True
Beispiel #23
0
def purge_entity_registry(hass: HomeAssistant, entry_id: str,
                          imported_entry_data: ConfigType) -> None:
    """Remove orphans from entity registry which are not in entry data."""
    entity_registry = er.async_get(hass)

    # Find all entities that are referenced in the config entry.
    references_config_entry = {
        entity_entry.entity_id
        for entity_entry in er.async_entries_for_config_entry(
            entity_registry, entry_id)
    }

    # Find all entities that are referenced by the entry_data.
    references_entry_data = set()
    for entity_data in imported_entry_data[CONF_ENTITIES]:
        entity_unique_id = generate_unique_id(entry_id,
                                              entity_data[CONF_ADDRESS],
                                              entity_data[CONF_RESOURCE])
        entity_id = entity_registry.async_get_entity_id(
            entity_data[CONF_DOMAIN], DOMAIN, entity_unique_id)
        if entity_id is not None:
            references_entry_data.add(entity_id)

    orphaned_ids = references_config_entry - references_entry_data
    for orphaned_id in orphaned_ids:
        entity_registry.async_remove(orphaned_id)
Beispiel #24
0
async def async_restore_block_attribute_entities(hass, config_entry,
                                                 async_add_entities, wrapper,
                                                 sensors, sensor_class):
    """Restore block attributes entities."""
    entities = []

    ent_reg = await entity_registry.async_get_registry(hass)
    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)
Beispiel #25
0
def async_migrate_alarm_unique_ids(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    household_id: str,
    alarm_ids: list[str],
) -> None:
    """Migrate alarm switch unique_ids in the entity registry to the new format."""
    entity_registry = er.async_get(hass)
    registry_entries = er.async_entries_for_config_entry(
        entity_registry, config_entry.entry_id
    )

    alarm_entries = [
        (entry.unique_id, entry)
        for entry in registry_entries
        if entry.domain == Platform.SWITCH and entry.original_icon == "mdi:alarm"
    ]

    for old_unique_id, alarm_entry in alarm_entries:
        if ":" in old_unique_id:
            continue

        entry_alarm_id = old_unique_id.split("-")[-1]
        if entry_alarm_id in alarm_ids:
            new_unique_id = f"alarm-{household_id}:{entry_alarm_id}"
            _LOGGER.debug(
                "Migrating unique_id for %s from %s to %s",
                alarm_entry.entity_id,
                old_unique_id,
                new_unique_id,
            )
            entity_registry.async_update_entity(
                alarm_entry.entity_id, new_unique_id=new_unique_id
            )
Beispiel #26
0
async def async_remove_orphaned_entries_service(hass, data):
    """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])]

    entity_registry = await hass.helpers.entity_registry.async_get_registry()
    device_registry = await hass.helpers.device_registry.async_get_registry()

    entity_entries = async_entries_for_config_entry(
        entity_registry, gateway.config_entry.entry_id)

    entities_to_be_removed = []
    devices_to_be_removed = [
        entry.id for entry in device_registry.devices.values()
        if gateway.config_entry.entry_id in entry.config_entries
    ]

    # Don't remove the Gateway host entry
    gateway_host = device_registry.async_get_device(
        connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)},
        identifiers=set(),
    )
    if gateway_host.id in devices_to_be_removed:
        devices_to_be_removed.remove(gateway_host.id)

    # Don't remove the Gateway service entry
    gateway_service = device_registry.async_get_device(identifiers={
        (DOMAIN, gateway.api.config.bridgeid)
    },
                                                       connections=set())
    if gateway_service.id in devices_to_be_removed:
        devices_to_be_removed.remove(gateway_service.id)

    # Don't remove devices belonging to available events
    for event in gateway.events:
        if event.device_id in devices_to_be_removed:
            devices_to_be_removed.remove(event.device_id)

    for entry in entity_entries:

        # Don't remove available entities
        if entry.unique_id in gateway.entities[entry.domain]:

            # Don't remove devices with available entities
            if entry.device_id in devices_to_be_removed:
                devices_to_be_removed.remove(entry.device_id)
            continue
        # Remove entities that are not available
        entities_to_be_removed.append(entry.entity_id)

    # Remove unavailable entities
    for entity_id in entities_to_be_removed:
        entity_registry.async_remove(entity_id)

    # Remove devices that don't belong to any entity
    for device_id in devices_to_be_removed:
        if len(async_entries_for_device(entity_registry, device_id)) == 0:
            device_registry.async_remove_device(device_id)
Beispiel #27
0
async def async_cleanup_legacy_entry(
    hass: HomeAssistant,
    legacy_entry_id: str,
) -> None:
    """Cleanup the legacy entry if the migration is successful."""
    entity_registry = er.async_get(hass)
    if not er.async_entries_for_config_entry(entity_registry, legacy_entry_id):
        await hass.config_entries.async_remove(legacy_entry_id)
Beispiel #28
0
async def test_migrate_reboot_button(
    hass: HomeAssistant, mock_entry: MockEntityFixture, mock_light: Light
):
    """Test migrating unique ID of reboot button."""

    light1 = mock_light.copy()
    light1._api = mock_entry.api
    light1.name = "Test Light 1"
    light1.id = "lightid1"

    light2 = mock_light.copy()
    light2._api = mock_entry.api
    light2.name = "Test Light 2"
    light2.id = "lightid2"
    mock_entry.api.bootstrap.lights = {
        light1.id: light1,
        light2.id: light2,
    }
    mock_entry.api.get_bootstrap = AsyncMock(return_value=mock_entry.api.bootstrap)

    registry = er.async_get(hass)
    registry.async_get_or_create(
        Platform.BUTTON, DOMAIN, light1.id, config_entry=mock_entry.entry
    )
    registry.async_get_or_create(
        Platform.BUTTON,
        DOMAIN,
        f"{light2.id}_reboot",
        config_entry=mock_entry.entry,
    )

    await hass.config_entries.async_setup(mock_entry.entry.entry_id)
    await hass.async_block_till_done()

    assert mock_entry.entry.state == ConfigEntryState.LOADED
    assert mock_entry.api.update.called
    assert mock_entry.entry.unique_id == mock_entry.api.bootstrap.nvr.mac

    buttons = []
    for entity in er.async_entries_for_config_entry(
        registry, mock_entry.entry.entry_id
    ):
        if entity.domain == Platform.BUTTON.value:
            buttons.append(entity)
            print(entity.entity_id)
    assert len(buttons) == 2

    assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device") is None
    assert registry.async_get(f"{Platform.BUTTON}.test_light_1_reboot_device_2") is None
    light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_lightid1")
    assert light is not None
    assert light.unique_id == f"{light1.id}_reboot"

    assert registry.async_get(f"{Platform.BUTTON}.test_light_2_reboot_device") is None
    assert registry.async_get(f"{Platform.BUTTON}.test_light_2_reboot_device_2") is None
    light = registry.async_get(f"{Platform.BUTTON}.unifiprotect_lightid2_reboot")
    assert light is not None
    assert light.unique_id == f"{light2.id}_reboot"
Beispiel #29
0
    async def async_instances_to_clients_raw(
            instances: list[dict[str, Any]]) -> None:
        """Convert instances to Hyperion clients."""
        registry = await async_get_registry(hass)
        running_instances: set[int] = set()
        stopped_instances: set[int] = set()
        existing_instances = hass.data[DOMAIN][
            config_entry.entry_id][CONF_INSTANCE_CLIENTS]
        server_id = cast(str, config_entry.unique_id)

        # In practice, an instance can be in 3 states as seen by this function:
        #
        #    * Exists, and is running: Should be present in HASS/registry.
        #    * Exists, but is not running: Cannot add it yet, but entity may have be
        #      registered from a previous time it was running.
        #    * No longer exists at all: Should not be present in HASS/registry.

        # Add instances that are missing.
        for instance in instances:
            instance_num = instance.get(hyperion_const.KEY_INSTANCE)
            if instance_num is None:
                continue
            if not instance.get(hyperion_const.KEY_RUNNING, False):
                stopped_instances.add(instance_num)
                continue
            running_instances.add(instance_num)
            if instance_num in existing_instances:
                continue
            hyperion_client = await async_create_connect_hyperion_client(
                host, port, instance=instance_num, token=token)
            if not hyperion_client:
                continue
            existing_instances[instance_num] = hyperion_client
            instance_name = instance.get(hyperion_const.KEY_FRIENDLY_NAME,
                                         DEFAULT_NAME)
            async_dispatcher_send(
                hass,
                SIGNAL_INSTANCE_ADD.format(config_entry.entry_id),
                instance_num,
                instance_name,
            )

        # Remove entities that are are not running instances on Hyperion.
        for instance_num in set(existing_instances) - running_instances:
            del existing_instances[instance_num]
            async_dispatcher_send(
                hass, SIGNAL_INSTANCE_REMOVE.format(config_entry.entry_id),
                instance_num)

        # Deregister entities that belong to removed instances.
        for entry in async_entries_for_config_entry(registry,
                                                    config_entry.entry_id):
            data = split_hyperion_unique_id(entry.unique_id)
            if not data:
                continue
            if data[0] == server_id and (data[1] not in running_instances
                                         and data[1] not in stopped_instances):
                registry.async_remove(entry.entity_id)
Beispiel #30
0
async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up switches for UniFi component.

    Switches are controlling network access and switch ports with POE.
    """
    controller = hass.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 hass.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(hass, signal, items_added))

    items_added()
    known_poe_clients.clear()