Exemple #1
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)
    # If a device already exists but it doesn't match the new node, it means the node
    # was replaced with a different device and the device needs to be removeed so the
    # new device can be created. Otherwise if the device exists and the node is the same,
    # the node was replaced with the same device model and we can reuse the device.
    if (device := dev_reg.async_get_device({
            device_id
    })) and (device.model != node.device_config.label
             or device.manufacturer != node.device_config.manufacturer):
        remove_device_func(device)
Exemple #2
0
async def _remove_device(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    mac: str,
    tasmota_mqtt: TasmotaMQTTClient,
    device_registry: DeviceRegistry,
) -> None:
    """Remove device from device registry."""
    device = device_registry.async_get_device(set(),
                                              {(CONNECTION_NETWORK_MAC, mac)})

    if device is None or config_entry.entry_id not in device.config_entries:
        return

    _LOGGER.debug("Removing tasmota from device %s", mac)
    device_registry.async_update_device(
        device.id, remove_config_entry_id=config_entry.entry_id)
    await clear_discovery_topic(mac, config_entry.data[CONF_DISCOVERY_PREFIX],
                                tasmota_mqtt)
Exemple #3
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
Exemple #4
0
def check_device_registry(
    device_registry: DeviceRegistry, expected_devices: list[MappingProxyType]
) -> None:
    """Ensure that the expected_devices are correctly registered."""
    for expected_device in expected_devices:
        registry_entry = device_registry.async_get_device(
            expected_device[ATTR_IDENTIFIERS]
        )
        assert registry_entry is not None
        assert registry_entry.identifiers == expected_device[ATTR_IDENTIFIERS]
        assert registry_entry.manufacturer == expected_device[ATTR_MANUFACTURER]
        assert registry_entry.name == expected_device[ATTR_NAME]
        assert registry_entry.model == expected_device[ATTR_MODEL]
        if expected_via_device := expected_device.get(ATTR_VIA_DEVICE):
            assert registry_entry.via_device_id is not None
            parent_entry = device_registry.async_get_device({expected_via_device})
            assert parent_entry is not None
            assert registry_entry.via_device_id == parent_entry.id
        else:
            assert registry_entry.via_device_id is None
Exemple #5
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
Exemple #6
0
def check_device_registry(
    device_registry: DeviceRegistry, expected_device: dict[str, Any]
) -> None:
    """Ensure that the expected_device is correctly registered."""
    assert len(device_registry.devices) == 1
    registry_entry = device_registry.async_get_device(expected_device[ATTR_IDENTIFIERS])
    assert registry_entry is not None
    assert registry_entry.identifiers == expected_device[ATTR_IDENTIFIERS]
    assert registry_entry.manufacturer == expected_device[ATTR_MANUFACTURER]
    assert registry_entry.name == expected_device[ATTR_NAME]
    assert registry_entry.model == expected_device[ATTR_MODEL]
    assert registry_entry.sw_version == expected_device[ATTR_SW_VERSION]
Exemple #7
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
Exemple #9
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,
    )
Exemple #10
0
async def test_get_actions(hass, device_reg: DeviceRegistry, device, expected):
    """Test we get the expected actions from a rfxtrx."""
    await setup_entry(hass, {device.code: {"signal_repetitions": 1}})

    device_entry = device_reg.async_get_device(device.device_identifiers, set())
    assert device_entry

    actions = await async_get_device_automations(hass, "action", device_entry.id)
    actions = [action for action in actions if action["domain"] == DOMAIN]

    expected_actions = [
        {"domain": DOMAIN, "device_id": device_entry.id, **action_type}
        for action_type in expected
    ]

    assert_lists_same(actions, expected_actions)
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)
Exemple #12
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)
async def test_invalid_trigger(hass, device_reg: DeviceRegistry):
    """Test for invalid actions."""
    event = EVENT_LIGHTING_1
    notification_calls = async_mock_service(hass, "persistent_notification",
                                            "create")

    await setup_entry(
        hass, {event.code: {
            "fire_event": True,
            "signal_repetitions": 1
        }})

    device_identifers: Any = event.device_identifiers
    device_entry = device_reg.async_get_device(device_identifers, set())
    assert device_entry

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        "platform": "device",
                        "domain": DOMAIN,
                        "device_id": device_entry.id,
                        "type": event.type,
                        "subtype": "invalid",
                    },
                    "action": {
                        "service": "test.automation",
                        "data_template": {
                            "some": ("{{trigger.platform}}")
                        },
                    },
                },
            ]
        },
    )
    await hass.async_block_till_done()

    assert len(notification_calls) == 1
    assert ("The following integrations and platforms could not be set up"
            in notification_calls[0].data["message"])
async def test_firing_event(hass, device_reg: DeviceRegistry, rfxtrx, event):
    """Test for turn_on and turn_off triggers firing."""

    await setup_entry(
        hass, {event.code: {
            "fire_event": True,
            "signal_repetitions": 1
        }})

    device_entry = device_reg.async_get_device(event.device_identifiers, set())
    assert device_entry

    calls = async_mock_service(hass, "test", "automation")

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        "platform": "device",
                        "domain": DOMAIN,
                        "device_id": device_entry.id,
                        "type": event.type,
                        "subtype": event.subtype,
                    },
                    "action": {
                        "service": "test.automation",
                        "data_template": {
                            "some": ("{{trigger.platform}}")
                        },
                    },
                },
            ]
        },
    )
    await hass.async_block_till_done()

    await rfxtrx.signal(event.code)

    assert len(calls) == 1
    assert calls[0].data["some"] == "device"
Exemple #15
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
Exemple #16
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)
Exemple #17
0
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, DeviceAutomationType.CONDITION, device_entry.id)
    assert_lists_same(conditions, expected_conditions)
Exemple #18
0
async def test_invalid_action(hass, device_reg: DeviceRegistry):
    """Test for invalid actions."""
    device = DEVICE_LIGHTING_1

    await setup_entry(hass, {device.code: {"signal_repetitions": 1}})

    device_identifers: Any = device.device_identifiers
    device_entry = device_reg.async_get_device(device_identifers, set())
    assert device_entry

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        "platform": "event",
                        "event_type": "test_event",
                    },
                    "action": {
                        "domain": DOMAIN,
                        "device_id": device_entry.id,
                        "type": "send_command",
                        "subtype": "invalid",
                    },
                },
            ]
        },
    )
    await hass.async_block_till_done()

    assert len(
        notifications := hass.states.async_all("persistent_notification")) == 1
    assert ("The following integrations and platforms could not be set up"
            in notifications[0].attributes["message"])
Exemple #19
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 select."""
    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": "current_option_changed",
            "device_id": device_entry.id,
            "entity_id": f"{DOMAIN}.test_5678",
        }
    ]
    triggers = await async_get_device_automations(hass, "trigger", device_entry.id)
    assert_lists_same(triggers, expected_triggers)
Exemple #20
0
 def _get_device_long_name(device_registry: DeviceRegistry,
                           current_device: str) -> str:
     device = device_registry.async_get_device({(DOMAIN, current_device)})
     if device and device.name_by_user:
         return f"{device.name_by_user} ({current_device})"
     return current_device
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
Exemple #22
0
    async def devices_serialize(self, entity_reg: EntityRegistry,
                                dev_reg: DeviceRegistry):
        """Serialize entity for a devices response.

        https://yandex.ru/dev/dialogs/alice/doc/smart-home/reference/get-devices-docpage/
        """
        state = self.state

        # When a state is unavailable, the attributes that describe
        # capabilities will be stripped. For example, a light entity will miss
        # the min/max mireds. Therefore they will be excluded from a sync.
        if state.state == STATE_UNAVAILABLE:
            return None

        entity_config = self.config.entity_config.get(state.entity_id, {})
        name = (entity_config.get(CONF_NAME) or state.name).strip()
        domain = state.domain
        device_class = state.attributes.get(ATTR_DEVICE_CLASS)

        # If an empty string
        if not name:
            return None

        capabilities = self.capabilities()
        properties = self.properties()

        # Found no supported capabilities for this entity
        if not capabilities and not properties:
            return None

        device_type = get_yandex_type(domain, device_class)

        entry = entity_reg.async_get(state.entity_id)
        device = dev_reg.async_get(getattr(entry, 'device_id', ""))

        manufacturer = state.entity_id + ' | ' + getattr(
            device, "manufacturer", "Yandex Smart Home")
        model = getattr(device, "model", "")

        device_info = {'manufacturer': manufacturer, 'model': model}

        device = {
            'id': state.entity_id,
            'name': name,
            'type': device_type,
            'capabilities': [],
            'properties': [],
            'device_info': device_info,
        }

        for cpb in capabilities:
            description = cpb.description()
            if description not in device['capabilities']:
                device['capabilities'].append(description)

        for ppt in properties:
            description = ppt.description()
            if description not in device['properties']:
                device['properties'].append(description)

        override_type = entity_config.get(CONF_TYPE)
        if override_type:
            device['type'] = override_type

        room = entity_config.get(CONF_ROOM)
        if room:
            device['room'] = room
            return device

        dev_reg, ent_reg, area_reg = await gather(
            self.hass.helpers.device_registry.async_get_registry(),
            self.hass.helpers.entity_registry.async_get_registry(),
            self.hass.helpers.area_registry.async_get_registry(),
        )

        entity_entry = ent_reg.async_get(state.entity_id)
        if not (entity_entry and entity_entry.device_id):
            return device

        device_entry = dev_reg.devices.get(entity_entry.device_id)
        if not (device_entry and device_entry.area_id):
            return device

        area_entry = area_reg.areas.get(device_entry.area_id)
        if area_entry and area_entry.name:
            device['room'] = area_entry.name

        return device
Exemple #23
0
def async_remove_addons_from_dev_reg(dev_reg: DeviceRegistry, addons: set[str]) -> None:
    """Remove addons from the device registry."""
    for addon_slug in addons:
        if dev := dev_reg.async_get_device({(DOMAIN, addon_slug)}):
            dev_reg.async_remove_device(dev.id)
Exemple #24
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) == []
Exemple #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_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,
    )
Exemple #26
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
Exemple #27
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,
    )