async def async_initialise_vehicle(
     self,
     vehicle_link: KamereonVehiclesLink,
     renault_account: RenaultAccount,
     scan_interval: timedelta,
     config_entry: ConfigEntry,
     device_registry: dr.DeviceRegistry,
 ) -> None:
     """Set up proxy."""
     assert vehicle_link.vin is not None
     assert vehicle_link.vehicleDetails is not None
     # Generate vehicle proxy
     vehicle = RenaultVehicleProxy(
         hass=self._hass,
         vehicle=await renault_account.get_api_vehicle(vehicle_link.vin),
         details=vehicle_link.vehicleDetails,
         scan_interval=scan_interval,
     )
     await vehicle.async_initialise()
     device_registry.async_get_or_create(
         config_entry_id=config_entry.entry_id,
         identifiers=vehicle.device_info[ATTR_IDENTIFIERS],
         manufacturer=vehicle.device_info[ATTR_MANUFACTURER],
         name=vehicle.device_info[ATTR_NAME],
         model=vehicle.device_info[ATTR_MODEL],
         sw_version=vehicle.device_info[ATTR_SW_VERSION],
     )
     self._vehicles[vehicle_link.vin] = vehicle
Пример #2
0
def async_register_os_in_dev_reg(entry_id: str, dev_reg: DeviceRegistry,
                                 os_dict: dict[str, Any]) -> None:
    """Register OS in the device registry."""
    params = DeviceInfo(
        identifiers={(DOMAIN, "OS")},
        manufacturer="Home Assistant",
        model=SupervisorEntityModel.OS,
        sw_version=os_dict[ATTR_VERSION],
        name="Home Assistant Operating System",
        entry_type=DeviceEntryType.SERVICE,
    )
    dev_reg.async_get_or_create(config_entry_id=entry_id, **params)
Пример #3
0
def async_register_os_in_dev_reg(entry_id: str, dev_reg: DeviceRegistry,
                                 os_dict: Dict[str, Any]) -> None:
    """Register OS in the device registry."""
    params = {
        "config_entry_id": entry_id,
        "identifiers": {(DOMAIN, "OS")},
        "manufacturer": "Home Assistant",
        "model": "Home Assistant Operating System",
        "sw_version": os_dict[ATTR_VERSION],
        "name": "Home Assistant Operating System",
        "entry_type": ATTR_SERVICE,
    }
    dev_reg.async_get_or_create(**params)
Пример #4
0
def async_register_addons_in_dev_reg(entry_id: str, dev_reg: DeviceRegistry,
                                     addons: List[Dict[str, Any]]) -> None:
    """Register addons in the device registry."""
    for addon in addons:
        params = {
            "config_entry_id": entry_id,
            "identifiers": {(DOMAIN, addon[ATTR_SLUG])},
            "model": "Home Assistant Add-on",
            "sw_version": addon[ATTR_VERSION],
            "name": addon[ATTR_NAME],
            "entry_type": ATTR_SERVICE,
        }
        if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL):
            params["manufacturer"] = manufacturer
        dev_reg.async_get_or_create(**params)
Пример #5
0
def async_register_supervisor_in_dev_reg(
    entry_id: str,
    dev_reg: dr.DeviceRegistry,
    supervisor_dict: dict[str, Any],
) -> None:
    """Register OS in the device registry."""
    params = DeviceInfo(
        identifiers={(DOMAIN, "supervisor")},
        manufacturer="Home Assistant",
        model=SupervisorEntityModel.SUPERVIOSR,
        sw_version=supervisor_dict[ATTR_VERSION],
        name="Home Assistant Supervisor",
        entry_type=dr.DeviceEntryType.SERVICE,
    )
    dev_reg.async_get_or_create(config_entry_id=entry_id, **params)
async def test_get_conditions(
    hass: HomeAssistant,
    device_reg: device_registry.DeviceRegistry,
    entity_reg: entity_registry.EntityRegistry,
) -> None:
    """Test we get the expected conditions from a NEW_DOMAIN."""
    config_entry = MockConfigEntry(domain="test", data={})
    config_entry.add_to_hass(hass)
    device_entry = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
    )
    entity_reg.async_get_or_create(DOMAIN, "test", "5678", device_id=device_entry.id)
    expected_conditions = [
        {
            "condition": "device",
            "domain": DOMAIN,
            "type": "is_off",
            "device_id": device_entry.id,
            "entity_id": f"{DOMAIN}.test_5678",
        },
        {
            "condition": "device",
            "domain": DOMAIN,
            "type": "is_on",
            "device_id": device_entry.id,
            "entity_id": f"{DOMAIN}.test_5678",
        },
    ]
    conditions = await async_get_device_automations(hass, "condition", device_entry.id)
    assert_lists_same(conditions, expected_conditions)
Пример #7
0
def async_register_addons_in_dev_reg(
    entry_id: str, dev_reg: DeviceRegistry, addons: list[dict[str, Any]]
) -> None:
    """Register addons in the device registry."""
    for addon in addons:
        params = DeviceInfo(
            identifiers={(DOMAIN, addon[ATTR_SLUG])},
            model=SupervisorEntityModel.ADDON,
            sw_version=addon[ATTR_VERSION],
            name=addon[ATTR_NAME],
            entry_type=DeviceEntryType.SERVICE,
            configuration_url=f"homeassistant://hassio/addon/{addon[ATTR_SLUG]}",
        )
        if manufacturer := addon.get(ATTR_REPOSITORY) or addon.get(ATTR_URL):
            params[ATTR_MANUFACTURER] = manufacturer
        dev_reg.async_get_or_create(config_entry_id=entry_id, **params)
Пример #8
0
def _update_device(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    config: TasmotaDeviceConfig,
    device_registry: DeviceRegistry,
) -> None:
    """Add or update device registry."""
    _LOGGER.debug("Adding or updating tasmota device %s", config[CONF_MAC])
    device_registry.async_get_or_create(
        connections={(CONNECTION_NETWORK_MAC, config[CONF_MAC])},
        manufacturer=config[CONF_MANUFACTURER],
        model=config[CONF_MODEL],
        name=config[CONF_NAME],
        sw_version=config[CONF_SW_VERSION],
        config_entry_id=config_entry.entry_id,
    )
Пример #9
0
async def test_get_actions(
    hass: HomeAssistant,
    device_reg: device_registry.DeviceRegistry,
    entity_reg: entity_registry.EntityRegistry,
) -> None:
    """Test we get the expected actions from a button."""
    config_entry = MockConfigEntry(domain="test", data={})
    config_entry.add_to_hass(hass)
    device_entry = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(device_registry.CONNECTION_NETWORK_MAC,
                      "12:34:56:AB:CD:EF")},
    )
    entity_reg.async_get_or_create(DOMAIN,
                                   "test",
                                   "5678",
                                   device_id=device_entry.id)
    expected_actions = [{
        "domain": DOMAIN,
        "type": "press",
        "device_id": device_entry.id,
        "entity_id": "button.test_5678",
    }]
    actions = await async_get_device_automations(hass,
                                                 DeviceAutomationType.ACTION,
                                                 device_entry.id)
    assert_lists_same(actions, expected_actions)
Пример #10
0
async def test_get_trigger_capabilities(hass: HomeAssistant,
                                        device_reg: DeviceRegistry,
                                        entity_reg: EntityRegistry) -> None:
    """Test we get the expected capabilities from a update trigger."""
    config_entry = MockConfigEntry(domain="test", data={})
    config_entry.add_to_hass(hass)
    device_entry = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(device_registry.CONNECTION_NETWORK_MAC,
                      "12:34:56:AB:CD:EF")},
    )
    entity_reg.async_get_or_create(DOMAIN,
                                   "test",
                                   "5678",
                                   device_id=device_entry.id)
    expected_capabilities = {
        "extra_fields": [{
            "name": "for",
            "optional": True,
            "type": "positive_time_period_dict"
        }]
    }
    triggers = await async_get_device_automations(hass,
                                                  DeviceAutomationType.TRIGGER,
                                                  device_entry.id)
    for trigger in triggers:
        capabilities = await async_get_device_automation_capabilities(
            hass, DeviceAutomationType.TRIGGER, trigger)
        assert capabilities == expected_capabilities
Пример #11
0
async def test_get_triggers(
    hass: HomeAssistant,
    device_reg: device_registry.DeviceRegistry,
    entity_reg: EntityRegistry,
) -> None:
    """Test we get the expected triggers from a button."""
    config_entry = MockConfigEntry(domain="test", data={})
    config_entry.add_to_hass(hass)
    device_entry = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(device_registry.CONNECTION_NETWORK_MAC,
                      "12:34:56:AB:CD:EF")},
    )
    entity_reg.async_get_or_create(DOMAIN,
                                   "test",
                                   "5678",
                                   device_id=device_entry.id)
    expected_triggers = [{
        "platform": "device",
        "domain": DOMAIN,
        "type": "pressed",
        "device_id": device_entry.id,
        "entity_id": f"{DOMAIN}.test_5678",
        "metadata": {
            "secondary": False
        },
    }]
    triggers = await async_get_device_automations(hass,
                                                  DeviceAutomationType.TRIGGER,
                                                  device_entry.id)
    assert_lists_same(triggers, expected_triggers)
Пример #12
0
async def test_migration_device_online_end_to_end(hass: HomeAssistant,
                                                  device_reg: DeviceRegistry,
                                                  entity_reg: EntityRegistry):
    """Test migration from single config entry."""
    config_entry = MockConfigEntry(domain=DOMAIN,
                                   title="LEGACY",
                                   data={},
                                   unique_id=DOMAIN)
    config_entry.add_to_hass(hass)
    device = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        identifiers={(DOMAIN, SERIAL)},
        connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
        name=LABEL,
    )
    light_entity_reg = entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="light",
        unique_id=dr.format_mac(SERIAL),
        original_name=LABEL,
        device_id=device.id,
    )

    with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
        await setup.async_setup_component(hass, DOMAIN, {})
        await hass.async_block_till_done()

        migrated_entry = None
        for entry in hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == DOMAIN:
                migrated_entry = entry
                break

        assert migrated_entry is not None

        assert device.config_entries == {migrated_entry.entry_id}
        assert light_entity_reg.config_entry_id == migrated_entry.entry_id
        assert er.async_entries_for_config_entry(entity_reg,
                                                 config_entry) == []

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()
        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
        await hass.async_block_till_done()

        legacy_entry = None
        for entry in hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == DOMAIN:
                legacy_entry = entry
                break

        assert legacy_entry is None
async def test_migration_device_online_end_to_end_after_downgrade(
        hass: HomeAssistant, device_reg: DeviceRegistry,
        entity_reg: EntityRegistry):
    """Test migration from single config entry can happen again after a downgrade."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=MAC_ADDRESS)
    already_migrated_config_entry.add_to_hass(hass)
    device = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
        name=ALIAS,
    )
    light_entity_reg = entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="light",
        unique_id=MAC_ADDRESS,
        original_name=ALIAS,
        device_id=device.id,
    )
    power_sensor_entity_reg = entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="sensor",
        unique_id=f"{MAC_ADDRESS}_sensor",
        original_name=ALIAS,
        device_id=device.id,
    )

    with _patch_discovery(), _patch_single_discovery():
        await setup.async_setup_component(hass, DOMAIN, {})
        await hass.async_block_till_done()

        assert device.config_entries == {config_entry.entry_id}
        assert light_entity_reg.config_entry_id == config_entry.entry_id
        assert power_sensor_entity_reg.config_entry_id == config_entry.entry_id
        assert er.async_entries_for_config_entry(entity_reg,
                                                 config_entry) == []

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        legacy_entry = None
        for entry in hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == DOMAIN:
                legacy_entry = entry
                break

        assert legacy_entry is None
Пример #14
0
def _add_camera(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    client: MotionEyeClient,
    entry: ConfigEntry,
    camera_id: int,
    camera: dict[str, Any],
    device_identifier: tuple[str, str],
) -> None:
    """Add a motionEye camera to hass."""

    device_registry.async_get_or_create(
        config_entry_id=entry.entry_id,
        identifiers={device_identifier},
        manufacturer=MOTIONEYE_MANUFACTURER,
        model=MOTIONEYE_MANUFACTURER,
        name=camera[KEY_NAME],
    )

    async_dispatcher_send(
        hass,
        SIGNAL_CAMERA_ADD.format(entry.entry_id),
        camera,
    )
Пример #15
0
def register_node_in_dev_reg(
    hass: HomeAssistant,
    entry: ConfigEntry,
    dev_reg: device_registry.DeviceRegistry,
    client: ZwaveClient,
    node: ZwaveNode,
    remove_device_func: Callable[[device_registry.DeviceEntry], None],
) -> device_registry.DeviceEntry:
    """Register node in dev reg."""
    device_id = get_device_id(client, node)
    device_id_ext = get_device_id_ext(client, node)
    device = dev_reg.async_get_device({device_id})

    # Replace the device if it can be determined that this node is not the
    # same product as it was previously.
    if (
        device_id_ext
        and device
        and len(device.identifiers) == 2
        and device_id_ext not in device.identifiers
    ):
        remove_device_func(device)
        device = None

    if device_id_ext:
        ids = {device_id, device_id_ext}
    else:
        ids = {device_id}

    params = {
        ATTR_IDENTIFIERS: ids,
        ATTR_SW_VERSION: node.firmware_version,
        ATTR_NAME: node.name
        or node.device_config.description
        or f"Node {node.node_id}",
        ATTR_MODEL: node.device_config.label,
        ATTR_MANUFACTURER: node.device_config.manufacturer,
    }
    if node.location:
        params[ATTR_SUGGESTED_AREA] = node.location
    device = dev_reg.async_get_or_create(config_entry_id=entry.entry_id, **params)

    async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)

    return device
Пример #16
0
def register_device(
    hass: HomeAssistant,
    entry: ConfigEntry,
    dev_reg: device_registry.DeviceRegistry,
    device: Device,
) -> None:
    """Register device in device registry."""
    params = DeviceInfo(
        identifiers={(DOMAIN, device.address)},
        name=device.name,
        model=device.model,
        manufacturer="Govee",
    )

    device = dev_reg.async_get_or_create(config_entry_id=entry.entry_id,
                                         **params)

    async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
Пример #17
0
def register_node_in_dev_reg(
    hass: HomeAssistant,
    entry: ConfigEntry,
    dev_reg: device_registry.DeviceRegistry,
    client: ZwaveClient,
    node: ZwaveNode,
) -> None:
    """Register node in dev reg."""
    device = dev_reg.async_get_or_create(
        config_entry_id=entry.entry_id,
        identifiers={(DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}")},
        sw_version=node.firmware_version,
        name=node.name or node.device_config.description or f"Node {node.node_id}",
        model=node.device_config.label,
        manufacturer=node.device_config.manufacturer,
    )

    async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
Пример #18
0
def register_node_in_dev_reg(
    hass: HomeAssistant,
    entry: ConfigEntry,
    dev_reg: device_registry.DeviceRegistry,
    driver: Driver,
    node: ZwaveNode,
    remove_device_func: Callable[[device_registry.DeviceEntry], None],
) -> device_registry.DeviceEntry:
    """Register node in dev reg."""
    device_id = get_device_id(driver, node)
    device_id_ext = get_device_id_ext(driver, node)
    device = dev_reg.async_get_device({device_id})

    # Replace the device if it can be determined that this node is not the
    # same product as it was previously.
    if (
        device_id_ext
        and device
        and len(device.identifiers) == 2
        and device_id_ext not in device.identifiers
    ):
        remove_device_func(device)
        device = None

    if device_id_ext:
        ids = {device_id, device_id_ext}
    else:
        ids = {device_id}

    device = dev_reg.async_get_or_create(
        config_entry_id=entry.entry_id,
        identifiers=ids,
        sw_version=node.firmware_version,
        name=node.name or node.device_config.description or f"Node {node.node_id}",
        model=node.device_config.label,
        manufacturer=node.device_config.manufacturer,
        suggested_area=node.location if node.location else UNDEFINED,
    )

    async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)

    return device
Пример #19
0
async def test_get_triggers(hass: HomeAssistant, device_reg: DeviceRegistry,
                            entity_reg: EntityRegistry) -> None:
    """Test we get the expected triggers from a update entity."""
    config_entry = MockConfigEntry(domain="test", data={})
    config_entry.add_to_hass(hass)
    device_entry = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(device_registry.CONNECTION_NETWORK_MAC,
                      "12:34:56:AB:CD:EF")},
    )
    entity_reg.async_get_or_create(DOMAIN,
                                   "test",
                                   "5678",
                                   device_id=device_entry.id)
    expected_triggers = [
        {
            "platform": "device",
            "domain": DOMAIN,
            "type": "changed_states",
            "device_id": device_entry.id,
            "entity_id": f"{DOMAIN}.test_5678",
        },
        {
            "platform": "device",
            "domain": DOMAIN,
            "type": "turned_off",
            "device_id": device_entry.id,
            "entity_id": f"{DOMAIN}.test_5678",
        },
        {
            "platform": "device",
            "domain": DOMAIN,
            "type": "turned_on",
            "device_id": device_entry.id,
            "entity_id": f"{DOMAIN}.test_5678",
        },
    ]
    triggers = await async_get_device_automations(hass,
                                                  DeviceAutomationType.TRIGGER,
                                                  device_entry.id)
    assert triggers == expected_triggers
Пример #20
0
def register_node_in_dev_reg(
    hass: HomeAssistant,
    entry: ConfigEntry,
    dev_reg: device_registry.DeviceRegistry,
    client: ZwaveClient,
    node: ZwaveNode,
) -> None:
    """Register node in dev reg."""
    params = {
        "config_entry_id": entry.entry_id,
        "identifiers": {get_device_id(client, node)},
        "sw_version": node.firmware_version,
        "name": node.name or node.device_config.description
        or f"Node {node.node_id}",
        "model": node.device_config.label,
        "manufacturer": node.device_config.manufacturer,
    }
    if node.location:
        params["suggested_area"] = node.location
    device = dev_reg.async_get_or_create(**params)

    async_dispatcher_send(hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device)
Пример #21
0
async def test_migration_device_online_end_to_end_ignores_other_devices(
        hass: HomeAssistant, device_reg: DeviceRegistry,
        entity_reg: EntityRegistry):
    """Test migration from single config entry."""
    legacy_config_entry = MockConfigEntry(domain=DOMAIN,
                                          title="LEGACY",
                                          data={},
                                          unique_id=DOMAIN)
    legacy_config_entry.add_to_hass(hass)

    other_domain_config_entry = MockConfigEntry(domain="other_domain",
                                                data={},
                                                unique_id="other_domain")
    other_domain_config_entry.add_to_hass(hass)
    device = device_reg.async_get_or_create(
        config_entry_id=legacy_config_entry.entry_id,
        identifiers={(DOMAIN, SERIAL)},
        connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
        name=LABEL,
    )
    other_device = device_reg.async_get_or_create(
        config_entry_id=other_domain_config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, "556655665566")},
        name=LABEL,
    )
    light_entity_reg = entity_reg.async_get_or_create(
        config_entry=legacy_config_entry,
        platform=DOMAIN,
        domain="light",
        unique_id=SERIAL,
        original_name=LABEL,
        device_id=device.id,
    )
    ignored_entity_reg = entity_reg.async_get_or_create(
        config_entry=other_domain_config_entry,
        platform=DOMAIN,
        domain="sensor",
        unique_id="00:00:00:00:00:00_sensor",
        original_name=LABEL,
        device_id=device.id,
    )
    garbage_entity_reg = entity_reg.async_get_or_create(
        config_entry=legacy_config_entry,
        platform=DOMAIN,
        domain="sensor",
        unique_id="garbage",
        original_name=LABEL,
        device_id=other_device.id,
    )

    with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
        await setup.async_setup_component(hass, DOMAIN, {})
        await hass.async_block_till_done()
        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()
        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=20))
        await hass.async_block_till_done()

        new_entry = None
        legacy_entry = None
        for entry in hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == DOMAIN:
                legacy_entry = entry
            else:
                new_entry = entry

        assert new_entry is not None
        assert legacy_entry is None

        assert device.config_entries == {legacy_config_entry.entry_id}
        assert light_entity_reg.config_entry_id == legacy_config_entry.entry_id
        assert ignored_entity_reg.config_entry_id == other_domain_config_entry.entry_id
        assert garbage_entity_reg.config_entry_id == legacy_config_entry.entry_id

        assert er.async_entries_for_config_entry(entity_reg,
                                                 legacy_config_entry) == []
        assert dr.async_entries_for_config_entry(device_reg,
                                                 legacy_config_entry) == []
async def test_migration_device_online_end_to_end_ignores_other_devices(
        hass: HomeAssistant, device_reg: DeviceRegistry,
        entity_reg: EntityRegistry):
    """Test migration from single config entry."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    other_domain_config_entry = MockConfigEntry(domain="other_domain",
                                                data={},
                                                unique_id="other_domain")
    other_domain_config_entry.add_to_hass(hass)
    device = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
        name=ALIAS,
    )
    other_device = device_reg.async_get_or_create(
        config_entry_id=other_domain_config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, "556655665566")},
        name=ALIAS,
    )
    light_entity_reg = entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="light",
        unique_id=MAC_ADDRESS,
        original_name=ALIAS,
        device_id=device.id,
    )
    power_sensor_entity_reg = entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="sensor",
        unique_id=f"{MAC_ADDRESS}_sensor",
        original_name=ALIAS,
        device_id=device.id,
    )
    ignored_entity_reg = entity_reg.async_get_or_create(
        config_entry=other_domain_config_entry,
        platform=DOMAIN,
        domain="sensor",
        unique_id="00:00:00:00:00:00_sensor",
        original_name=ALIAS,
        device_id=device.id,
    )
    garbage_entity_reg = entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="sensor",
        unique_id="garbage",
        original_name=ALIAS,
        device_id=other_device.id,
    )

    with _patch_discovery(), _patch_single_discovery():
        await setup.async_setup_component(hass, DOMAIN, {})
        await hass.async_block_till_done()

        migrated_entry = None
        for entry in hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == DOMAIN:
                migrated_entry = entry
                break

        assert migrated_entry is not None

        assert device.config_entries == {migrated_entry.entry_id}
        assert light_entity_reg.config_entry_id == migrated_entry.entry_id
        assert power_sensor_entity_reg.config_entry_id == migrated_entry.entry_id
        assert ignored_entity_reg.config_entry_id == other_domain_config_entry.entry_id
        assert garbage_entity_reg.config_entry_id == config_entry.entry_id

        assert er.async_entries_for_config_entry(entity_reg,
                                                 config_entry) == []

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()

        legacy_entry = None
        for entry in hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == DOMAIN:
                legacy_entry = entry
                break

        assert legacy_entry is not None
Пример #23
0
def _add_camera(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    client: MotionEyeClient,
    entry: ConfigEntry,
    camera_id: int,
    camera: dict[str, Any],
    device_identifier: tuple[str, str],
) -> None:
    """Add a motionEye camera to hass."""
    def _is_recognized_web_hook(url: str) -> bool:
        """Determine whether this integration set a web hook."""
        return f"{WEB_HOOK_SENTINEL_KEY}={WEB_HOOK_SENTINEL_VALUE}" in url

    def _set_webhook(
        url: str,
        key_url: str,
        key_method: str,
        key_enabled: str,
        camera: dict[str, Any],
    ) -> bool:
        """Set a web hook."""
        if (entry.options.get(
                CONF_WEBHOOK_SET_OVERWRITE,
                DEFAULT_WEBHOOK_SET_OVERWRITE,
        ) or not camera.get(key_url)
                or _is_recognized_web_hook(camera[key_url])) and (
                    not camera.get(key_enabled, False)
                    or camera.get(key_method) != KEY_HTTP_METHOD_GET
                    or camera.get(key_url) != url):
            camera[key_enabled] = True
            camera[key_method] = KEY_HTTP_METHOD_GET
            camera[key_url] = url
            return True
        return False

    def _build_url(base: str, keys: list[str]) -> str:
        """Build a motionEye webhook URL."""

        return (base + "?" + urlencode(
            {
                **{
                    k: KEY_WEB_HOOK_CONVERSION_SPECIFIERS[k]
                    for k in sorted(keys)
                },
                WEB_HOOK_SENTINEL_KEY: WEB_HOOK_SENTINEL_VALUE,
            },
            safe="%{}",
        ))

    device = device_registry.async_get_or_create(
        config_entry_id=entry.entry_id,
        identifiers={device_identifier},
        manufacturer=MOTIONEYE_MANUFACTURER,
        model=MOTIONEYE_MANUFACTURER,
        name=camera[KEY_NAME],
    )
    if entry.options.get(CONF_WEBHOOK_SET, DEFAULT_WEBHOOK_SET):
        url = None
        try:
            url = get_url(hass)
        except NoURLAvailableError:
            pass
        if url:
            if _set_webhook(
                    _build_url(
                        f"{url}{API_PATH_DEVICE_ROOT}{device.id}/{EVENT_MOTION_DETECTED}",
                        EVENT_MOTION_DETECTED_KEYS,
                    ),
                    KEY_WEB_HOOK_NOTIFICATIONS_URL,
                    KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD,
                    KEY_WEB_HOOK_NOTIFICATIONS_ENABLED,
                    camera,
            ) | _set_webhook(
                    _build_url(
                        f"{url}{API_PATH_DEVICE_ROOT}{device.id}/{EVENT_FILE_STORED}",
                        EVENT_FILE_STORED_KEYS,
                    ),
                    KEY_WEB_HOOK_STORAGE_URL,
                    KEY_WEB_HOOK_STORAGE_HTTP_METHOD,
                    KEY_WEB_HOOK_STORAGE_ENABLED,
                    camera,
            ):
                hass.async_create_task(
                    client.async_set_camera(camera_id, camera))

    async_dispatcher_send(
        hass,
        SIGNAL_CAMERA_ADD.format(entry.entry_id),
        camera,
    )
Пример #24
0
async def test_discovery_is_more_frequent_during_migration(
        hass: HomeAssistant, device_reg: DeviceRegistry,
        entity_reg: EntityRegistry):
    """Test that discovery is more frequent during migration."""
    config_entry = MockConfigEntry(domain=DOMAIN,
                                   title="LEGACY",
                                   data={},
                                   unique_id=DOMAIN)
    config_entry.add_to_hass(hass)
    device = device_reg.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        identifiers={(DOMAIN, SERIAL)},
        connections={(dr.CONNECTION_NETWORK_MAC, MAC_ADDRESS)},
        name=LABEL,
    )
    entity_reg.async_get_or_create(
        config_entry=config_entry,
        platform=DOMAIN,
        domain="light",
        unique_id=dr.format_mac(SERIAL),
        original_name=LABEL,
        device_id=device.id,
    )

    bulb = _mocked_bulb()
    start_calls = 0

    class MockLifxDiscovery:
        """Mock lifx discovery."""
        def __init__(self, *args, **kwargs):
            """Init discovery."""
            self.bulb = bulb
            self.lights = {}

        def start(self):
            """Mock start."""
            nonlocal start_calls
            start_calls += 1
            # Discover the bulb so we can complete migration
            # and verify we switch back to normal discovery
            # interval
            if start_calls == 4:
                self.lights = {self.bulb.mac_addr: self.bulb}

        def cleanup(self):
            """Mock cleanup."""

    with _patch_device(device=bulb), _patch_config_flow_try_connect(
            device=bulb), patch.object(discovery, "DEFAULT_TIMEOUT", 0), patch(
                "homeassistant.components.lifx.discovery.LifxDiscovery",
                MockLifxDiscovery):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert start_calls == 0

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()
        assert start_calls == 1

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
        await hass.async_block_till_done()
        assert start_calls == 3

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
        await hass.async_block_till_done()
        assert start_calls == 4

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15))
        await hass.async_block_till_done()
        assert start_calls == 5
Пример #25
0
def _add_camera(
    hass: HomeAssistant,
    device_registry: dr.DeviceRegistry,
    client: MotionEyeClient,
    entry: ConfigEntry,
    camera_id: int,
    camera: dict[str, Any],
    device_identifier: tuple[str, str],
) -> None:
    """Add a motionEye camera to hass."""
    def _is_recognized_web_hook(url: str) -> bool:
        """Determine whether this integration set a web hook."""
        return f"{WEB_HOOK_SENTINEL_KEY}={WEB_HOOK_SENTINEL_VALUE}" in url

    def _set_webhook(
        url: str,
        key_url: str,
        key_method: str,
        key_enabled: str,
        camera: dict[str, Any],
    ) -> bool:
        """Set a web hook."""
        if (entry.options.get(
                CONF_WEBHOOK_SET_OVERWRITE,
                DEFAULT_WEBHOOK_SET_OVERWRITE,
        ) or not camera.get(key_url)
                or _is_recognized_web_hook(camera[key_url])) and (
                    not camera.get(key_enabled, False)
                    or camera.get(key_method) != KEY_HTTP_METHOD_POST_JSON
                    or camera.get(key_url) != url):
            camera[key_enabled] = True
            camera[key_method] = KEY_HTTP_METHOD_POST_JSON
            camera[key_url] = url
            return True
        return False

    def _build_url(device: dr.DeviceEntry, base: str, event_type: str,
                   keys: list[str]) -> str:
        """Build a motionEye webhook URL."""

        # This URL-surgery cannot use YARL because the output must NOT be
        # url-encoded. This is because motionEye will do further string
        # manipulation/substitution on this value before ultimately fetching it,
        # and it cannot deal with URL-encoded input to that string manipulation.
        return urljoin(
            base,
            "?" + urlencode(
                {
                    **{
                        k: KEY_WEB_HOOK_CONVERSION_SPECIFIERS[k]
                        for k in sorted(keys)
                    },
                    WEB_HOOK_SENTINEL_KEY: WEB_HOOK_SENTINEL_VALUE,
                    ATTR_EVENT_TYPE: event_type,
                    ATTR_DEVICE_ID: device.id,
                },
                safe="%{}",
            ),
        )

    device = device_registry.async_get_or_create(
        config_entry_id=entry.entry_id,
        identifiers={device_identifier},
        manufacturer=MOTIONEYE_MANUFACTURER,
        model=MOTIONEYE_MANUFACTURER,
        name=camera[KEY_NAME],
    )
    if entry.options.get(CONF_WEBHOOK_SET, DEFAULT_WEBHOOK_SET):
        url = async_generate_motioneye_webhook(hass,
                                               entry.data[CONF_WEBHOOK_ID])

        if url:
            set_motion_event = _set_webhook(
                _build_url(
                    device,
                    url,
                    EVENT_MOTION_DETECTED,
                    EVENT_MOTION_DETECTED_KEYS,
                ),
                KEY_WEB_HOOK_NOTIFICATIONS_URL,
                KEY_WEB_HOOK_NOTIFICATIONS_HTTP_METHOD,
                KEY_WEB_HOOK_NOTIFICATIONS_ENABLED,
                camera,
            )

            set_storage_event = _set_webhook(
                _build_url(
                    device,
                    url,
                    EVENT_FILE_STORED,
                    EVENT_FILE_STORED_KEYS,
                ),
                KEY_WEB_HOOK_STORAGE_URL,
                KEY_WEB_HOOK_STORAGE_HTTP_METHOD,
                KEY_WEB_HOOK_STORAGE_ENABLED,
                camera,
            )
            if set_motion_event or set_storage_event:
                hass.async_create_task(
                    client.async_set_camera(camera_id, camera))

    async_dispatcher_send(
        hass,
        SIGNAL_CAMERA_ADD.format(entry.entry_id),
        camera,
    )