예제 #1
0
async def clear_code(hass: HomeAssistant, entity_id: str,
                     code_slot: int) -> None:
    """Clear the usercode from a code slot."""
    _LOGGER.debug("Attempting to call clear_usercode...")

    if async_using_zwave_js(entity_id=entity_id,
                            ent_reg=async_get_entity_registry(hass)):
        servicedata = {
            ATTR_ENTITY_ID: entity_id,
            ATTR_CODE_SLOT: code_slot,
        }
        await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_CLEAR_LOCK_USERCODE,
                           servicedata)

    elif async_using_ozw(entity_id=entity_id,
                         ent_reg=async_get_entity_registry(hass)):
        # Call dummy slot first as a workaround
        for curr_code_slot in (999, code_slot):
            servicedata = {
                ATTR_ENTITY_ID: entity_id,
                ATTR_CODE_SLOT: curr_code_slot,
            }
            await call_service(hass, OZW_DOMAIN, CLEAR_USERCODE, servicedata)

    elif async_using_zwave(entity_id=entity_id,
                           ent_reg=async_get_entity_registry(hass)):
        node_id = get_node_id(hass, entity_id)
        if node_id is None:
            _LOGGER.error(
                "Problem retrieving node_id from entity %s",
                entity_id,
            )
            return

        servicedata = {
            ATTR_NODE_ID: node_id,
            ATTR_CODE_SLOT: code_slot,
        }

        _LOGGER.debug(
            "Setting code slot value to random PIN as workaround in case clearing code "
            "doesn't work")
        await call_service(
            hass,
            LOCK_DOMAIN,
            SET_USERCODE,
            {
                **servicedata, ATTR_USER_CODE: str(random.randint(1000, 9999))
            },
        )

        await call_service(hass, LOCK_DOMAIN, CLEAR_USERCODE, servicedata)
    else:
        raise ZWaveIntegrationNotConfiguredError
예제 #2
0
async def async_migrate_legacy_zwave(
    hass: HomeAssistant,
    zwave_config_entry: ConfigEntry,
    zwave_js_config_entry: ConfigEntry,
    migration_map: LegacyZWaveMappedData,
) -> None:
    """Perform Z-Wave to Z-Wave JS migration."""
    dev_reg = async_get_device_registry(hass)
    for zwave_js_device_id, zwave_device_id in migration_map.device_entries.items(
    ):
        zwave_device_entry = dev_reg.async_get(zwave_device_id)
        if not zwave_device_entry:
            continue
        dev_reg.async_update_device(
            zwave_js_device_id,
            area_id=zwave_device_entry.area_id,
            name_by_user=zwave_device_entry.name_by_user,
        )

    ent_reg = async_get_entity_registry(hass)
    for zwave_js_entity_id, zwave_entry in migration_map.entity_entries.items(
    ):
        zwave_entity_id = zwave_entry["entity_id"]
        if not (entity_entry := ent_reg.async_get(zwave_entity_id)):
            continue
        ent_reg.async_remove(zwave_entity_id)
        ent_reg.async_update_entity(
            zwave_js_entity_id,
            new_entity_id=entity_entry.entity_id,
            name=entity_entry.name,
            icon=entity_entry.icon,
        )
예제 #3
0
async def test_setup_partial_config_v31x(hass, mock_remote):
    """Test the setup with a v3.1.x server."""
    config = {"platform": "uvc", "nvr": "foo", "key": "secret"}
    mock_remote.return_value.server_version = (3, 1, 3)

    assert await async_setup_component(hass, "camera", {"camera": config})
    await hass.async_block_till_done()

    assert mock_remote.call_count == 1
    assert mock_remote.call_args == call("foo", 7080, "secret", ssl=False)

    camera_states = hass.states.async_all("camera")

    assert len(camera_states) == 2

    state = hass.states.get("camera.front")

    assert state
    assert state.name == "Front"

    state = hass.states.get("camera.back")

    assert state
    assert state.name == "Back"

    entity_registry = async_get_entity_registry(hass)
    entity_entry = entity_registry.async_get("camera.front")

    assert entity_entry.unique_id == "one"

    entity_entry = entity_registry.async_get("camera.back")

    assert entity_entry.unique_id == "two"
예제 #4
0
async def test_alarm_create_delete(
    hass, config_entry, config, soco, alarm_clock, alarm_clock_extended, alarm_event
):
    """Test for correct creation and deletion of alarms during runtime."""
    soco.alarmClock = alarm_clock_extended

    await setup_platform(hass, config_entry, config)

    subscription = alarm_clock_extended.subscribe.return_value
    sub_callback = subscription.callback

    sub_callback(event=alarm_event)
    await hass.async_block_till_done()

    entity_registry = async_get_entity_registry(hass)

    assert "switch.sonos_alarm_14" in entity_registry.entities
    assert "switch.sonos_alarm_15" in entity_registry.entities

    alarm_clock_extended.ListAlarms.return_value = alarm_clock.ListAlarms.return_value

    sub_callback(event=alarm_event)
    await hass.async_block_till_done()

    assert "switch.sonos_alarm_14" in entity_registry.entities
    assert "switch.sonos_alarm_15" not in entity_registry.entities
예제 #5
0
async def generate_keymaster_locks(
        hass: HomeAssistant, config_entry: ConfigEntry
) -> Tuple[KeymasterLock, List[KeymasterLock]]:
    """Generate primary and child keymaster locks from config entry."""
    ent_reg = async_get_entity_registry(hass)
    primary_lock = KeymasterLock(
        config_entry.data[CONF_LOCK_NAME],
        config_entry.data[CONF_LOCK_ENTITY_ID],
        config_entry.data.get(CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID),
        config_entry.data.get(CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID),
        ent_reg,
        door_sensor_entity_id=config_entry.data[CONF_SENSOR_NAME],
    )
    child_locks = [
        KeymasterLock(
            lock_name,
            lock[CONF_LOCK_ENTITY_ID],
            lock.get(CONF_ALARM_LEVEL_OR_USER_CODE_ENTITY_ID),
            lock.get(CONF_ALARM_TYPE_OR_ACCESS_CONTROL_ENTITY_ID),
            ent_reg,
        )
        for lock_name, lock in config_entry.data.get(CHILD_LOCKS, {}).items()
    ]

    return primary_lock, child_locks
예제 #6
0
async def test_alarm_create_delete(hass, config_entry, config, soco,
                                   alarm_clock, alarm_clock_extended,
                                   alarm_event):
    """Test for correct creation and deletion of alarms during runtime."""
    entity_registry = async_get_entity_registry(hass)

    one_alarm = copy(alarm_clock.ListAlarms.return_value)
    two_alarms = copy(alarm_clock_extended.ListAlarms.return_value)

    await setup_platform(hass, config_entry, config)

    assert "switch.sonos_alarm_14" in entity_registry.entities
    assert "switch.sonos_alarm_15" not in entity_registry.entities

    subscription = alarm_clock.subscribe.return_value
    sub_callback = subscription.callback

    alarm_clock.ListAlarms.return_value = two_alarms

    sub_callback(event=alarm_event)
    await hass.async_block_till_done()

    assert "switch.sonos_alarm_14" in entity_registry.entities
    assert "switch.sonos_alarm_15" in entity_registry.entities

    alarm_event.increment_variable("alarm_list_version")

    alarm_clock.ListAlarms.return_value = one_alarm

    sub_callback(event=alarm_event)
    await hass.async_block_till_done()

    assert "switch.sonos_alarm_14" in entity_registry.entities
    assert "switch.sonos_alarm_15" not in entity_registry.entities
예제 #7
0
async def add_code(hass: HomeAssistant, entity_id: str, code_slot: int,
                   usercode: str) -> None:
    """Set a user code."""
    _LOGGER.debug("Attempting to call set_usercode...")

    servicedata = {
        ATTR_CODE_SLOT: code_slot,
    }

    if async_using_zwave_js(entity_id=entity_id,
                            ent_reg=async_get_entity_registry(hass)):
        servicedata[ATTR_ENTITY_ID] = entity_id
        servicedata[ATTR_USER_CODE] = usercode
        await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_SET_LOCK_USERCODE,
                           servicedata)

    elif async_using_zha(entity_id=entity_id,
                         ent_reg=async_get_entity_registry(hass)):
        servicedata[ATTR_ENTITY_ID] = entity_id
        servicedata[ATTR_ZHA_USER_CODE] = usercode
        await call_service(hass, ZHA_DOMAIN, ZHA_SERVICE_SET_LOCK_USERCODE,
                           servicedata)

    elif async_using_ozw(entity_id=entity_id,
                         ent_reg=async_get_entity_registry(hass)):
        servicedata[ATTR_ENTITY_ID] = entity_id
        servicedata[ATTR_USER_CODE] = usercode
        await call_service(hass, OZW_DOMAIN, SET_USERCODE, servicedata)

    elif async_using_zwave(entity_id=entity_id,
                           ent_reg=async_get_entity_registry(hass)):
        node_id = get_node_id(hass, entity_id)
        if node_id is None:
            _LOGGER.error(
                "Problem retrieving node_id from entity %s",
                entity_id,
            )
            return

        servicedata[ATTR_NODE_ID] = node_id
        servicedata[ATTR_USER_CODE] = usercode
        await call_service(hass, LOCK_DOMAIN, SET_USERCODE, servicedata)

    else:
        raise ZWaveIntegrationNotConfiguredError
예제 #8
0
    async def async_update(self) -> None:
        """Update sensor."""
        if not self.ent_reg:
            self.ent_reg = async_get_entity_registry(self.hass)

        if (
            not self.lock_config_entry_id
            or not self.hass.config_entries.async_get_entry(self.lock_config_entry_id)
        ):
            entity_id = self.primary_lock.lock_entity_id
            lock_ent_reg_entry = self.ent_reg.async_get(entity_id)

            if not lock_ent_reg_entry:
                if self._lock_found:
                    self._lock_found = False
                    _LOGGER.warning("Can't find your lock %s.", entity_id)
                return

            self.lock_config_entry_id = lock_ent_reg_entry.config_entry_id

            if not self._lock_found:
                _LOGGER.info("Found your lock %s", entity_id)
                self._lock_found = True

        try:
            client = self.hass.data[ZWAVE_JS_DOMAIN][self.lock_config_entry_id][
                ZWAVE_JS_DATA_CLIENT
            ]
        except KeyError:
            _LOGGER.debug("Can't access Z-Wave JS data client.")
            self._attr_is_on = False
            return

        network_ready = bool(
            client.connected and client.driver and client.driver.controller
        )

        # If network_ready and self._attr_is_on are both true or both false, we don't need
        # to do anything since there is nothing to update.
        if not network_ready ^ self.is_on:
            return

        self.async_set_is_on_property(network_ready, False)

        # If we just turned the sensor on, we need to get the latest lock
        # nodes and devices
        if self.is_on:
            await async_update_zwave_js_nodes_and_devices(
                self.hass,
                self.lock_config_entry_id,
                self.primary_lock,
                self.child_locks,
            )
예제 #9
0
 def _handle_event(self, event_type: EventType, resource: CLIPResource) -> None:
     """Handle status event for this resource (or it's parent)."""
     if event_type == EventType.RESOURCE_DELETED and resource.id == self.resource.id:
         self.logger.debug("Received delete for %s", self.entity_id)
         # non-device bound entities like groups and scenes need to be removed here
         # all others will be be removed by device setup in case of device removal
         ent_reg = async_get_entity_registry(self.hass)
         ent_reg.async_remove(self.entity_id)
     else:
         self.logger.debug("Received status update for %s", self.entity_id)
         self.on_update()
         self.async_write_ha_state()
예제 #10
0
async def test_setup_full_config(hass, mock_remote, camera_info):
    """Test the setup with full configuration."""
    config = {
        "platform": "uvc",
        "nvr": "foo",
        "password": "******",
        "port": 123,
        "key": "secret",
    }

    def mock_get_camera(uuid):
        """Create a mock camera."""
        if uuid == "id3":
            camera_info["model"] = "airCam"

        return camera_info

    mock_remote.return_value.index.return_value.append({
        "uuid": "three",
        "name": "Old AirCam",
        "id": "id3"
    })
    mock_remote.return_value.get_camera.side_effect = mock_get_camera

    assert await async_setup_component(hass, "camera", {"camera": config})
    await hass.async_block_till_done()

    assert mock_remote.call_count == 1
    assert mock_remote.call_args == call("foo", 123, "secret", ssl=False)

    camera_states = hass.states.async_all("camera")

    assert len(camera_states) == 2

    state = hass.states.get("camera.front")

    assert state
    assert state.name == "Front"

    state = hass.states.get("camera.back")

    assert state
    assert state.name == "Back"

    entity_registry = async_get_entity_registry(hass)
    entity_entry = entity_registry.async_get("camera.front")

    assert entity_entry.unique_id == "id1"

    entity_entry = entity_registry.async_get("camera.back")

    assert entity_entry.unique_id == "id2"
예제 #11
0
async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities
):
    """Setup config entry."""
    # Add entities for all defined slots
    start_from = entry.data[CONF_START]
    code_slots = entry.data[CONF_SLOTS]
    async_add_entities(
        [
            CodesSensor(hass, entry, x)
            for x in range(start_from, start_from + code_slots)
        ],
        True,
    )

    async def code_slots_changed(
        ent_reg: EntityRegistry,
        platform: entity_platform.EntityPlatform,
        config_entry: ConfigEntry,
        old_slots: List[int],
        new_slots: List[int],
    ):
        """Handle code slots changed."""
        slots_to_add = list(set(new_slots) - set(old_slots))
        slots_to_remove = list(set(old_slots) - set(new_slots))
        for slot in slots_to_remove:
            sensor_name = slugify(
                f"{config_entry.data[CONF_LOCK_NAME]}_code_slot_{slot}"
            )
            entity_id = f"sensor.{sensor_name}"
            if ent_reg.async_get(entity_id):
                await platform.async_remove_entity(entity_id)
                ent_reg.async_remove(entity_id)

        async_add_entities(
            [CodesSensor(hass, entry, x) for x in slots_to_add],
            True,
        )

    async_dispatcher_connect(
        hass,
        f"{DOMAIN}_{entry.entry_id}_code_slots_changed",
        partial(
            code_slots_changed,
            async_get_entity_registry(hass),
            entity_platform.current_platform.get(),
            entry,
        ),
    )

    return True
예제 #12
0
 def _handle_event(self, event_type: EventType,
                   resource: HueResource) -> None:
     """Handle status event for this resource (or it's parent)."""
     if event_type == EventType.RESOURCE_DELETED:
         # remove any services created for zones/rooms
         # regular devices are removed automatically by the logic in device.py.
         if resource.type in [ResourceTypes.ROOM, ResourceTypes.ZONE]:
             dev_reg = async_get_device_registry(self.hass)
             if device := dev_reg.async_get_device({(DOMAIN, resource.id)}):
                 dev_reg.async_remove_device(device.id)
         if resource.type in [
                 ResourceTypes.GROUPED_LIGHT, ResourceTypes.SCENE
         ]:
             ent_reg = async_get_entity_registry(self.hass)
             ent_reg.async_remove(self.entity_id)
         return
예제 #13
0
 async def set_unique_id(self):
     """Set unique id for projector config entry."""
     _LOGGER.debug("Setting unique_id for projector")
     if self._unique_id:
         return False
     if uid := await self._projector.get_serial_number():
         self.hass.config_entries.async_update_entry(self._entry,
                                                     unique_id=uid)
         registry = async_get_entity_registry(self.hass)
         old_entity_id = registry.async_get_entity_id(
             "media_player", DOMAIN, self._entry.entry_id)
         if old_entity_id is not None:
             registry.async_update_entity(old_entity_id, new_unique_id=uid)
         self.hass.async_create_task(
             self.hass.config_entries.async_reload(self._entry.entry_id))
         return True
예제 #14
0
async def clear_code(hass: HomeAssistant, entity_id: str,
                     code_slot: int) -> None:
    """Clear the usercode from a code slot."""
    _LOGGER.debug("Attempting to call clear_usercode...")

    if async_using_zwave_js(entity_id=entity_id,
                            ent_reg=async_get_entity_registry(hass)):
        servicedata = {
            ATTR_ENTITY_ID: entity_id,
            ATTR_CODE_SLOT: code_slot,
        }
        await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_CLEAR_LOCK_USERCODE,
                           servicedata)

    else:
        raise ZWaveIntegrationNotConfiguredError
예제 #15
0
async def test_enable_entity_disabled_device(hass, client, device_registry):
    """Test enabling entity of disabled device."""
    entity_id = "test_domain.test_platform_1234"
    config_entry = MockConfigEntry(domain="test_platform")
    config_entry.add_to_hass(hass)

    device = device_registry.async_get_or_create(
        config_entry_id="1234",
        connections={("ethernet", "12:34:56:78:90:AB:CD:EF")},
        identifiers={("bridgeid", "0123")},
        manufacturer="manufacturer",
        model="model",
        disabled_by=DeviceEntryDisabler.USER,
    )
    device_info = {
        "connections": {("ethernet", "12:34:56:78:90:AB:CD:EF")},
    }

    platform = MockEntityPlatform(hass)
    platform.config_entry = config_entry
    entity = MockEntity(unique_id="1234", device_info=device_info)
    await platform.async_add_entities([entity])

    state = hass.states.get(entity_id)
    assert state is None

    entity_reg = async_get_entity_registry(hass)
    entity_entry = entity_reg.async_get(entity_id)
    assert entity_entry.config_entry_id == config_entry.entry_id
    assert entity_entry.device_id == device.id
    assert entity_entry.disabled_by == RegistryEntryDisabler.DEVICE

    # UPDATE DISABLED_BY TO NONE
    await client.send_json(
        {
            "id": 8,
            "type": "config/entity_registry/update",
            "entity_id": entity_id,
            "disabled_by": None,
        }
    )

    msg = await client.receive_json()

    assert not msg["success"]
예제 #16
0
async def add_code(hass: HomeAssistant, entity_id: str, code_slot: int,
                   usercode: str) -> None:
    """Set a user code."""
    _LOGGER.debug("Attempting to call set_usercode...")

    servicedata = {
        ATTR_CODE_SLOT: code_slot,
        ATTR_USER_CODE: usercode,
    }

    if async_using_zwave_js(entity_id=entity_id,
                            ent_reg=async_get_entity_registry(hass)):
        servicedata[ATTR_ENTITY_ID] = entity_id
        await call_service(hass, ZWAVE_JS_DOMAIN, SERVICE_SET_LOCK_USERCODE,
                           servicedata)

    else:
        raise ZWaveIntegrationNotConfiguredError
예제 #17
0
async def async_update_zha_devices(
    hass: HomeAssistant,
    entry_id: str,
    primary_lock: KeymasterLock,
    child_locks: List[KeymasterLock],
) -> None:
    """Update ZHA devices."""
    ent_reg = async_get_entity_registry(hass)
    dev_reg = async_get_device_registry(hass)
    for lock in [primary_lock, *child_locks]:
        lock_ent_reg_entry = ent_reg.async_get(lock.lock_entity_id)
        if not lock_ent_reg_entry:
            continue
        lock_dev_reg_entry = dev_reg.async_get(lock_ent_reg_entry.device_id)
        if not lock_dev_reg_entry:
            continue

        lock.zha_lock_entity = lock_ent_reg_entry
        lock.zha_lock_device = lock_dev_reg_entry
예제 #18
0
    def add_entity_value(
        self,
        entity_id: str,
        entity_values: ZWaveDeviceEntityValues,
    ) -> None:
        """Add info for one entity and Z-Wave value."""
        ent_reg = async_get_entity_registry(self._hass)
        dev_reg = async_get_device_registry(self._hass)

        node = entity_values.primary.node
        entity_entry = ent_reg.async_get(entity_id)
        assert entity_entry
        device_identifier, _ = node_device_id_and_name(
            node, entity_values.primary.instance)
        device_entry = dev_reg.async_get_device({device_identifier}, set())
        assert device_entry

        # Normalize unit of measurement.
        if unit := entity_entry.unit_of_measurement:
            unit = unit.lower()
예제 #19
0
async def refresh_codes(hass: HomeAssistant,
                        entity_id: str,
                        instance_id: int = 1) -> None:
    """Refresh lock codes."""
    try:
        config_entry = next(
            config_entry
            for config_entry in hass.config_entries.async_entries(DOMAIN)
            if config_entry.data[CONF_LOCK_ENTITY_ID] == entity_id)
    except StopIteration:
        _LOGGER.error("Entity ID %s not set up in keymaster", entity_id)
        return

    ent_reg = async_get_entity_registry(hass)
    if async_using_zwave_js(entity_id=entity_id, ent_reg=ent_reg):
        code_slots = get_code_slots_list(config_entry.data)
        node = async_get_node_from_entity_id(hass, entity_id, ent_reg=ent_reg)
        for code_slot in code_slots:
            await get_usercode_from_node(node, code_slot)
        return
예제 #20
0
    def add_entity_value(
        self,
        config_entry: ConfigEntry,
        entity_id: str,
        discovery_info: ZwaveDiscoveryInfo,
    ) -> None:
        """Add info for one entity and Z-Wave JS value."""
        ent_reg = async_get_entity_registry(self._hass)
        dev_reg = async_get_device_registry(self._hass)

        node = discovery_info.node
        primary_value = discovery_info.primary_value
        entity_entry = ent_reg.async_get(entity_id)
        assert entity_entry
        device_identifier = get_device_id(node.client, node)
        device_entry = dev_reg.async_get_device({device_identifier}, set())
        assert device_entry

        # Normalize unit of measurement.
        if unit := entity_entry.unit_of_measurement:
            unit = unit.lower()
예제 #21
0
async def refresh_codes(hass: HomeAssistant,
                        entity_id: str,
                        instance_id: int = 1) -> None:
    """Refresh lock codes."""
    try:
        config_entry = next(
            config_entry
            for config_entry in hass.config_entries.async_entries(DOMAIN)
            if config_entry.data[CONF_LOCK_ENTITY_ID] == entity_id)
    except StopIteration:
        _LOGGER.error("Entity ID %s not set up in keymaster", entity_id)
        return

    ent_reg = async_get_entity_registry(hass)
    if async_using_zwave_js(entity_id=entity_id, ent_reg=ent_reg):
        code_slots = get_code_slots_list(config_entry.data)
        node = async_get_node_from_entity_id(hass, entity_id, ent_reg=ent_reg)
        for code_slot in code_slots:
            await get_usercode_from_node(node, code_slot)
        return

    # OZW Button press (experimental)
    if async_using_ozw(entity_id=entity_id, ent_reg=ent_reg):
        node_id = get_node_id(hass, entity_id)
        if node_id is None:
            _LOGGER.error(
                "Problem retrieving node_id from entity %s",
                entity_id,
            )
            return

        manager = hass.data[OZW_DOMAIN][MANAGER]
        lock_values = manager.get_instance(instance_id).get_node(
            node_id).values()
        for value in lock_values:
            if value.command_class == CommandClass.USER_CODE and value.index == 255:
                _LOGGER.debug("DEBUG: Index found valueIDKey: %s",
                              int(value.value_id_key))
                value.send_value(True)
                value.send_value(False)
예제 #22
0
async def refresh_codes(hass: HomeAssistant,
                        entity_id: str,
                        instance_id: int = 1) -> None:
    """Refresh lock codes."""
    node_id = get_node_id(hass, entity_id)
    if node_id is None:
        _LOGGER.error(
            "Problem retrieving node_id from entity %s",
            entity_id,
        )
        return

    # OZW Button press (experimental)
    if async_using_ozw(entity_id=entity_id,
                       ent_reg=async_get_entity_registry(hass)):
        manager = hass.data[OZW_DOMAIN][MANAGER]
        lock_values = manager.get_instance(instance_id).get_node(
            node_id).values()
        for value in lock_values:
            if value.command_class == CommandClass.USER_CODE and value.index == 255:
                _LOGGER.debug("DEBUG: Index found valueIDKey: %s",
                              int(value.value_id_key))
                value.send_value(True)
                value.send_value(False)
예제 #23
0
async def async_update_zwave_js_nodes_and_devices(
    hass: HomeAssistant,
    entry_id: str,
    primary_lock: KeymasterLock,
    child_locks: List[KeymasterLock],
) -> None:
    """Update Z-Wave JS nodes and devices."""
    client = hass.data[ZWAVE_JS_DOMAIN][entry_id][ZWAVE_JS_DATA_CLIENT]
    ent_reg = async_get_entity_registry(hass)
    dev_reg = async_get_device_registry(hass)
    for lock in [primary_lock, *child_locks]:
        lock_ent_reg_entry = ent_reg.async_get(lock.lock_entity_id)
        if not lock_ent_reg_entry:
            continue
        lock_dev_reg_entry = dev_reg.async_get(lock_ent_reg_entry.device_id)
        if not lock_dev_reg_entry:
            continue
        node_id: int = 0
        for identifier in lock_dev_reg_entry.identifiers:
            if identifier[0] == ZWAVE_JS_DOMAIN:
                node_id = int(identifier[1].split("-")[1])

        lock.zwave_js_lock_node = client.driver.controller.nodes[node_id]
        lock.zwave_js_lock_device = lock_dev_reg_entry
예제 #24
0
async def handle_v2_migration(hass: core.HomeAssistant,
                              entry: ConfigEntry) -> None:
    """Perform migration of devices and entities to V2 Id's."""
    host = entry.data[CONF_HOST]
    api_key = entry.data[CONF_API_KEY]
    websession = aiohttp_client.async_get_clientsession(hass)
    dev_reg = async_get_device_registry(hass)
    ent_reg = async_get_entity_registry(hass)
    LOGGER.info(
        "Start of migration of devices and entities to support API schema 2")

    # Create mapping of mac address to HA device id's.
    # Identifier in dev reg should be mac-address,
    # but in some cases it has a postfix like `-0b` or `-01`.
    dev_ids = {}
    for hass_dev in devices_for_config_entries(dev_reg, entry.entry_id):
        for domain, mac in hass_dev.identifiers:
            if domain != DOMAIN:
                continue
            normalized_mac = mac.split("-")[0]
            dev_ids[normalized_mac] = hass_dev.id

    # initialize bridge connection just for the migration
    async with HueBridgeV2(host, api_key, websession) as api:

        sensor_class_mapping = {
            SensorDeviceClass.BATTERY.value: ResourceTypes.DEVICE_POWER,
            BinarySensorDeviceClass.MOTION.value: ResourceTypes.MOTION,
            SensorDeviceClass.ILLUMINANCE.value: ResourceTypes.LIGHT_LEVEL,
            SensorDeviceClass.TEMPERATURE.value: ResourceTypes.TEMPERATURE,
        }

        # migrate entities attached to a device
        for hue_dev in api.devices:
            zigbee = api.devices.get_zigbee_connectivity(hue_dev.id)
            if not zigbee or not zigbee.mac_address:
                # not a zigbee device or invalid mac
                continue

            # get existing device by V1 identifier (mac address)
            if hue_dev.product_data.product_archetype == DeviceArchetypes.BRIDGE_V2:
                hass_dev_id = dev_ids.get(api.config.bridge_id.upper())
            else:
                hass_dev_id = dev_ids.get(zigbee.mac_address)
            if hass_dev_id is None:
                # can be safely ignored, this device does not exist in current config
                LOGGER.debug(
                    "Ignoring device %s (%s) as it does not (yet) exist in the device registry",
                    hue_dev.metadata.name,
                    hue_dev.id,
                )
                continue
            dev_reg.async_update_device(hass_dev_id,
                                        new_identifiers={(DOMAIN, hue_dev.id)})
            LOGGER.info("Migrated device %s (%s)", hue_dev.metadata.name,
                        hass_dev_id)

            # loop through all entities for device and find match
            for ent in async_entries_for_device(ent_reg, hass_dev_id, True):

                if ent.entity_id.startswith("light"):
                    # migrate light
                    # should always return one lightid here
                    new_unique_id = next(iter(hue_dev.lights), None)
                else:
                    # migrate sensors
                    matched_dev_class = sensor_class_mapping.get(
                        ent.original_device_class or "unknown")
                    new_unique_id = next(
                        (sensor.id
                         for sensor in api.devices.get_sensors(hue_dev.id)
                         if sensor.type == matched_dev_class),
                        None,
                    )

                if new_unique_id is None:
                    # this may happen if we're looking at orphaned or unsupported entity
                    LOGGER.warning(
                        "Skip migration of %s because it no longer exists on the bridge",
                        ent.entity_id,
                    )
                    continue

                try:
                    ent_reg.async_update_entity(ent.entity_id,
                                                new_unique_id=new_unique_id)
                except ValueError:
                    # assume edge case where the entity was already migrated in a previous run
                    # which got aborted somehow and we do not want
                    # to crash the entire integration init
                    LOGGER.warning(
                        "Skip migration of %s because it already exists",
                        ent.entity_id,
                    )
                else:
                    LOGGER.info(
                        "Migrated entity %s from unique id %s to %s",
                        ent.entity_id,
                        ent.unique_id,
                        new_unique_id,
                    )

        # migrate entities that are not connected to a device (groups)
        for ent in entities_for_config_entry(ent_reg, entry.entry_id):
            if ent.device_id is not None:
                continue
            if "-" in ent.unique_id:
                # handle case where unique id is v2-id of group/zone
                hue_group = api.groups.get(ent.unique_id)
            else:
                # handle case where the unique id is just the v1 id
                v1_id = f"/groups/{ent.unique_id}"
                hue_group = api.groups.room.get_by_v1_id(
                    v1_id) or api.groups.zone.get_by_v1_id(v1_id)
            if hue_group is None or hue_group.grouped_light is None:
                # this may happen if we're looking at some orphaned entity
                LOGGER.warning(
                    "Skip migration of %s because it no longer exist on the bridge",
                    ent.entity_id,
                )
                continue
            new_unique_id = hue_group.grouped_light
            LOGGER.info(
                "Migrating %s from unique id %s to %s ",
                ent.entity_id,
                ent.unique_id,
                new_unique_id,
            )
            try:
                ent_reg.async_update_entity(ent.entity_id,
                                            new_unique_id=new_unique_id)
            except ValueError:
                # assume edge case where the entity was already migrated in a previous run
                # which got aborted somehow and we do not want
                # to crash the entire integration init
                LOGGER.warning(
                    "Skip migration of %s because it already exists",
                    ent.entity_id,
                )
    LOGGER.info(
        "Migration of devices and entities to support API schema 2 finished")
예제 #25
0
async def async_setup_entry(hass: HomeAssistant,
                            config_entry: ConfigEntry) -> bool:
    """Set up is called when Home Assistant is loading our component."""
    hass.data.setdefault(DOMAIN, {})
    _LOGGER.info(
        "Version %s is starting, if you have any issues please report"
        " them here: %s",
        VERSION,
        ISSUE_URL,
    )
    should_generate_package = config_entry.data.get(CONF_GENERATE)

    updated_config = config_entry.data.copy()

    # pop CONF_GENERATE if it is in data
    updated_config.pop(CONF_GENERATE, None)

    # If CONF_PATH is absolute, make it relative. This can be removed in the future,
    # it is only needed for entries that are being migrated from using the old absolute
    # path
    config_path = hass.config.path()
    if config_entry.data[CONF_PATH].startswith(config_path):
        num_chars_config_path = len(config_path)
        updated_config[CONF_PATH] = updated_config[CONF_PATH][
            num_chars_config_path:]
        # Remove leading slashes
        updated_config[CONF_PATH] = updated_config[CONF_PATH].lstrip(
            "/").lstrip("\\")

    if "parent" not in config_entry.data.keys():
        updated_config[CONF_PARENT] = None
    elif config_entry.data[CONF_PARENT] == "(none)":
        updated_config[CONF_PARENT] = None

    if updated_config != config_entry.data:
        hass.config_entries.async_update_entry(config_entry,
                                               data=updated_config)

    config_entry.add_update_listener(update_listener)

    primary_lock, child_locks = await generate_keymaster_locks(
        hass, config_entry)

    hass.data[DOMAIN][config_entry.entry_id] = {
        PRIMARY_LOCK: primary_lock,
        CHILD_LOCKS: child_locks,
        UNSUB_LISTENERS: [],
    }
    coordinator = LockUsercodeUpdateCoordinator(
        hass, config_entry, async_get_entity_registry(hass))
    hass.data[DOMAIN][config_entry.entry_id][COORDINATOR] = coordinator

    # Button Press
    async def _refresh_codes(service: ServiceCall) -> None:
        """Refresh lock codes."""
        _LOGGER.debug("Refresh Codes service: %s", service)
        entity_id = service.data[ATTR_ENTITY_ID]
        instance_id = 1
        await refresh_codes(hass, entity_id, instance_id)

    hass.services.async_register(
        DOMAIN,
        SERVICE_REFRESH_CODES,
        _refresh_codes,
        schema=vol.Schema({
            vol.Required(ATTR_ENTITY_ID): vol.Coerce(str),
        }),
    )

    # Add code
    async def _add_code(service: ServiceCall) -> None:
        """Set a user code."""
        _LOGGER.debug("Add Code service: %s", service)
        entity_id = service.data[ATTR_ENTITY_ID]
        code_slot = service.data[ATTR_CODE_SLOT]
        usercode = service.data[ATTR_USER_CODE]
        await add_code(hass, entity_id, code_slot, usercode)

    hass.services.async_register(
        DOMAIN,
        SERVICE_ADD_CODE,
        _add_code,
        schema=vol.Schema({
            vol.Required(ATTR_ENTITY_ID): vol.Coerce(str),
            vol.Required(ATTR_CODE_SLOT): vol.Coerce(int),
            vol.Required(ATTR_USER_CODE): vol.Coerce(str),
        }),
    )

    # Clear code
    async def _clear_code(service: ServiceCall) -> None:
        """Clear a user code."""
        _LOGGER.debug("Clear Code service: %s", service)
        entity_id = service.data[ATTR_ENTITY_ID]
        code_slot = service.data[ATTR_CODE_SLOT]
        await clear_code(hass, entity_id, code_slot)

    hass.services.async_register(
        DOMAIN,
        SERVICE_CLEAR_CODE,
        _clear_code,
        schema=vol.Schema({
            vol.Required(ATTR_ENTITY_ID): vol.Coerce(str),
            vol.Required(ATTR_CODE_SLOT): vol.Coerce(int),
        }),
    )

    # Generate package files
    def _generate_package(service: ServiceCall) -> None:
        """Generate the package files."""
        _LOGGER.debug("DEBUG: %s", service)
        name = service.data[ATTR_NAME]
        generate_package_files(hass, name)

    hass.services.async_register(
        DOMAIN,
        SERVICE_GENERATE_PACKAGE,
        _generate_package,
        schema=vol.Schema({vol.Optional(ATTR_NAME): vol.Coerce(str)}),
    )

    await async_reset_code_slot_if_pin_unknown(
        hass,
        primary_lock.lock_name,
        config_entry.data[CONF_SLOTS],
        config_entry.data[CONF_START],
    )

    for platform in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(
                config_entry, platform))

    # if the use turned on the bool generate the files
    if should_generate_package:
        servicedata = {"lockname": primary_lock.lock_name}
        await hass.services.async_call(DOMAIN,
                                       SERVICE_GENERATE_PACKAGE,
                                       servicedata,
                                       blocking=True)

    if async_using_zwave_js(lock=primary_lock):
        # Listen to Z-Wave JS events so we can fire our own events
        hass.data[DOMAIN][config_entry.entry_id][UNSUB_LISTENERS].append(
            hass.bus.async_listen(
                ZWAVE_JS_NOTIFICATION_EVENT,
                functools.partial(handle_zwave_js_event, hass, config_entry),
            ))
        await system_health_check(hass, config_entry)
        return True

    # We only get here if we are not using zwave_js

    # Check if we need to check alarm type/alarm level sensors, in which case
    # we need to listen for lock state changes
    locks_to_watch = []
    for lock in [primary_lock, *child_locks]:
        if (lock.alarm_level_or_user_code_entity_id not in (
                None,
                "sensor.fake",
        ) and lock.alarm_type_or_access_control_entity_id
                not in (None, "sensor.fake")):
            locks_to_watch.append(lock)

    if locks_to_watch:
        if hass.state == CoreState.running:
            await homeassistant_started_listener(hass, config_entry,
                                                 locks_to_watch)
        else:
            hass.bus.async_listen_once(
                EVENT_HOMEASSISTANT_STARTED,
                functools.partial(homeassistant_started_listener, hass,
                                  config_entry, locks_to_watch),
            )

    if primary_lock.parent is not None:
        await init_child_locks(
            hass,
            config_entry.data[CONF_START],
            config_entry.data[CONF_SLOTS],
            config_entry.data[CONF_LOCK_NAME],
        )

    await system_health_check(hass, config_entry)
    return True
예제 #26
0
async def handle_v2_migration(hass: core.HomeAssistant,
                              entry: ConfigEntry) -> None:
    """Perform migration of devices and entities to V2 Id's."""
    host = entry.data[CONF_HOST]
    api_key = entry.data[CONF_API_KEY]
    websession = aiohttp_client.async_get_clientsession(hass)
    dev_reg = async_get_device_registry(hass)
    ent_reg = async_get_entity_registry(hass)
    LOGGER.info(
        "Start of migration of devices and entities to support API schema 2")
    # initialize bridge connection just for the migration
    async with HueBridgeV2(host, api_key, websession) as api:

        sensor_class_mapping = {
            DEVICE_CLASS_BATTERY: ResourceTypes.DEVICE_POWER,
            DEVICE_CLASS_MOTION: ResourceTypes.MOTION,
            DEVICE_CLASS_ILLUMINANCE: ResourceTypes.LIGHT_LEVEL,
            DEVICE_CLASS_TEMPERATURE: ResourceTypes.TEMPERATURE,
        }

        # handle entities attached to device
        for hue_dev in api.devices:
            zigbee = api.devices.get_zigbee_connectivity(hue_dev.id)
            if not zigbee or not zigbee.mac_address:
                # not a zigbee device or invalid mac
                continue
            # get/update existing device by V1 identifier (mac address)
            # the device will now have both the old and the new identifier
            identifiers = {(DOMAIN, hue_dev.id), (DOMAIN, zigbee.mac_address)}
            hass_dev = dev_reg.async_get_or_create(
                config_entry_id=entry.entry_id, identifiers=identifiers)
            LOGGER.info("Migrated device %s (%s)", hass_dev.name, hass_dev.id)
            # loop through al entities for device and find match
            for ent in async_entries_for_device(ent_reg, hass_dev.id, True):
                # migrate light
                if ent.entity_id.startswith("light"):
                    # should always return one lightid here
                    new_unique_id = next(iter(hue_dev.lights))
                    if ent.unique_id == new_unique_id:
                        continue  # just in case
                    LOGGER.info(
                        "Migrating %s from unique id %s to %s",
                        ent.entity_id,
                        ent.unique_id,
                        new_unique_id,
                    )
                    ent_reg.async_update_entity(ent.entity_id,
                                                new_unique_id=new_unique_id)
                    continue
                # migrate sensors
                matched_dev_class = sensor_class_mapping.get(
                    ent.original_device_class or "unknown")
                if matched_dev_class is None:
                    # this may happen if we're looking at orphaned or unsupported entity
                    LOGGER.warning(
                        "Skip migration of %s because it no longer exists on the bridge",
                        ent.entity_id,
                    )
                    continue
                for sensor in api.devices.get_sensors(hue_dev.id):
                    if sensor.type != matched_dev_class:
                        continue
                    new_unique_id = sensor.id
                    if ent.unique_id == new_unique_id:
                        break  # just in case
                    LOGGER.info(
                        "Migrating %s from unique id %s to %s",
                        ent.entity_id,
                        ent.unique_id,
                        new_unique_id,
                    )
                    try:
                        ent_reg.async_update_entity(ent.entity_id,
                                                    new_unique_id=sensor.id)
                    except ValueError:
                        # assume edge case where the entity was already migrated in a previous run
                        # which got aborted somehow and we do not want
                        # to crash the entire integration init
                        LOGGER.warning(
                            "Skip migration of %s because it already exists",
                            ent.entity_id,
                        )
                    break

        # migrate entities that are not connected to a device (groups)
        for ent in entities_for_config_entry(ent_reg, entry.entry_id):
            if ent.device_id is not None:
                continue
            v1_id = f"/groups/{ent.unique_id}"
            hue_group = api.groups.room.get_by_v1_id(v1_id)
            if hue_group is None or hue_group.grouped_light is None:
                # try again with zone
                hue_group = api.groups.zone.get_by_v1_id(v1_id)
            if hue_group is None or hue_group.grouped_light is None:
                # this may happen if we're looking at some orphaned entity
                LOGGER.warning(
                    "Skip migration of %s because it no longer exist on the bridge",
                    ent.entity_id,
                )
                continue
            new_unique_id = hue_group.grouped_light
            LOGGER.info(
                "Migrating %s from unique id %s to %s ",
                ent.entity_id,
                ent.unique_id,
                new_unique_id,
            )
            try:
                ent_reg.async_update_entity(ent.entity_id,
                                            new_unique_id=new_unique_id)
            except ValueError:
                # assume edge case where the entity was already migrated in a previous run
                # which got aborted somehow and we do not want
                # to crash the entire integration init
                LOGGER.warning(
                    "Skip migration of %s because it already exists",
                    ent.entity_id,
                )
    LOGGER.info(
        "Migration of devices and entities to support API schema 2 finished")