async def test_options_replace_sensor_device(hass):
    """Test we can replace a sensor device."""
    await setup.async_setup_component(hass, "persistent_notification", {})

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            "host": None,
            "port": None,
            "device": "/dev/tty123",
            "automatic_add": False,
            "devices": {
                "0a520101f00400e22d0189": {
                    "device_id": ["52", "1", "f0:04"]
                },
                "0a520105230400c3260279": {
                    "device_id": ["52", "1", "23:04"]
                },
            },
        },
        unique_id=DOMAIN,
    )
    entry.add_to_hass(hass)

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

    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_rssi_numeric")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_battery_numeric")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity_status")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_temperature")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_rssi_numeric")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_battery_numeric")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity_status")
    assert state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_temperature")
    assert state

    device_registry = dr.async_get(hass)
    device_entries = dr.async_entries_for_config_entry(device_registry,
                                                       entry.entry_id)

    old_device = next(
        (elem.id for elem in device_entries
         if next(iter(elem.identifiers))[1:] == ("52", "1", "f0:04")),
        None,
    )
    new_device = next(
        (elem.id for elem in device_entries
         if next(iter(elem.identifiers))[1:] == ("52", "1", "23:04")),
        None,
    )

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": False,
            "device": old_device,
        },
    )

    assert result["type"] == "form"
    assert result["step_id"] == "set_device_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "replace_device": new_device,
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    entity_registry = er.async_get(hass)

    entry = entity_registry.async_get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_rssi_numeric")
    assert entry
    assert entry.device_id == new_device
    entry = entity_registry.async_get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity")
    assert entry
    assert entry.device_id == new_device
    entry = entity_registry.async_get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_humidity_status")
    assert entry
    assert entry.device_id == new_device
    entry = entity_registry.async_get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_battery_numeric")
    assert entry
    assert entry.device_id == new_device
    entry = entity_registry.async_get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_f0_04_temperature")
    assert entry
    assert entry.device_id == new_device

    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_rssi_numeric")
    assert not state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_battery_numeric")
    assert not state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity")
    assert not state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_humidity_status")
    assert not state
    state = hass.states.get(
        "sensor.thgn122_123_thgn132_thgr122_228_238_268_23_04_temperature")
    assert not state
Exemple #2
0
async def test_device_registry_calls(hass):
    """Test device registry entries for hassio."""
    dev_reg = async_get(hass)
    supervisor_mock_data = {
        "addons": [
            {
                "name": "test",
                "slug": "test",
                "installed": True,
                "update_available": False,
                "version": "1.0.0",
                "version_latest": "1.0.0",
                "repository": "test",
                "url": "https://github.com/home-assistant/addons/test",
            },
            {
                "name": "test2",
                "slug": "test2",
                "installed": True,
                "update_available": False,
                "version": "1.0.0",
                "version_latest": "1.0.0",
                "url": "https://github.com",
            },
        ]
    }
    os_mock_data = {
        "board": "odroid-n2",
        "boot": "A",
        "update_available": False,
        "version": "5.12",
        "version_latest": "5.12",
    }

    with patch.dict(os.environ, MOCK_ENVIRON), patch(
            "homeassistant.components.hassio.HassIO.get_supervisor_info",
            return_value=supervisor_mock_data,
    ), patch(
            "homeassistant.components.hassio.HassIO.get_os_info",
            return_value=os_mock_data,
    ):
        config_entry = MockConfigEntry(domain=DOMAIN,
                                       data={},
                                       unique_id=DOMAIN)
        config_entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()
        assert len(dev_reg.devices) == 3

    supervisor_mock_data = {
        "addons": [
            {
                "name": "test2",
                "slug": "test2",
                "installed": True,
                "update_available": False,
                "version": "1.0.0",
                "version_latest": "1.0.0",
                "url": "https://github.com",
            },
        ]
    }

    # Test that when addon is removed, next update will remove the add-on and subsequent updates won't
    with patch(
            "homeassistant.components.hassio.HassIO.get_supervisor_info",
            return_value=supervisor_mock_data,
    ), patch(
            "homeassistant.components.hassio.HassIO.get_os_info",
            return_value=os_mock_data,
    ):
        async_fire_time_changed(hass, dt_util.now() + timedelta(hours=1))
        await hass.async_block_till_done()
        assert len(dev_reg.devices) == 2

        async_fire_time_changed(hass, dt_util.now() + timedelta(hours=2))
        await hass.async_block_till_done()
        assert len(dev_reg.devices) == 2

    supervisor_mock_data = {
        "addons": [
            {
                "name": "test2",
                "slug": "test2",
                "installed": True,
                "update_available": False,
                "version": "1.0.0",
                "version_latest": "1.0.0",
                "url": "https://github.com",
            },
            {
                "name": "test3",
                "slug": "test3",
                "installed": True,
                "update_available": False,
                "version": "1.0.0",
                "version_latest": "1.0.0",
                "url": "https://github.com",
            },
        ]
    }

    # Test that when addon is added, next update will reload the entry so we register
    # a new device
    with patch(
            "homeassistant.components.hassio.HassIO.get_supervisor_info",
            return_value=supervisor_mock_data,
    ), patch(
            "homeassistant.components.hassio.HassIO.get_os_info",
            return_value=os_mock_data,
    ):
        async_fire_time_changed(hass, dt_util.now() + timedelta(hours=3))
        await hass.async_block_till_done()
        assert len(dev_reg.devices) == 3
async def test_get_action_capabilities(
    hass: HomeAssistant,
    client: Client,
    climate_radio_thermostat_ct100_plus: Node,
    integration: ConfigEntry,
):
    """Test we get the expected action capabilities."""
    node = climate_radio_thermostat_ct100_plus
    dev_reg = device_registry.async_get(hass)
    device = device_registry.async_entries_for_config_entry(
        dev_reg, integration.entry_id
    )[0]

    # Test refresh_value
    capabilities = await device_action.async_get_action_capabilities(
        hass,
        {
            "platform": "device",
            "domain": DOMAIN,
            "device_id": device.id,
            "type": "refresh_value",
        },
    )
    assert capabilities and "extra_fields" in capabilities

    assert voluptuous_serialize.convert(
        capabilities["extra_fields"], custom_serializer=cv.custom_serializer
    ) == [{"type": "boolean", "name": "refresh_all_values", "optional": True}]

    # Test ping
    capabilities = await device_action.async_get_action_capabilities(
        hass,
        {
            "platform": "device",
            "domain": DOMAIN,
            "device_id": device.id,
            "type": "ping",
        },
    )
    assert not capabilities

    # Test set_value
    capabilities = await device_action.async_get_action_capabilities(
        hass,
        {
            "platform": "device",
            "domain": DOMAIN,
            "device_id": device.id,
            "type": "set_value",
        },
    )
    assert capabilities and "extra_fields" in capabilities

    cc_options = [(cc.value, cc.name) for cc in CommandClass]

    assert voluptuous_serialize.convert(
        capabilities["extra_fields"], custom_serializer=cv.custom_serializer
    ) == [
        {
            "name": "command_class",
            "required": True,
            "options": cc_options,
            "type": "select",
        },
        {"name": "property", "required": True, "type": "string"},
        {"name": "property_key", "optional": True, "type": "string"},
        {"name": "endpoint", "optional": True, "type": "string"},
        {"name": "value", "required": True, "type": "string"},
        {"type": "boolean", "name": "wait_for_result", "optional": True},
    ]

    # Test enumerated type param
    capabilities = await device_action.async_get_action_capabilities(
        hass,
        {
            "platform": "device",
            "domain": DOMAIN,
            "device_id": device.id,
            "type": "set_config_parameter",
            "parameter": 1,
            "bitmask": None,
            "subtype": f"{node.node_id}-112-0-1 (Temperature Reporting Threshold)",
        },
    )
    assert capabilities and "extra_fields" in capabilities

    assert voluptuous_serialize.convert(
        capabilities["extra_fields"], custom_serializer=cv.custom_serializer
    ) == [
        {
            "name": "value",
            "required": True,
            "options": [
                (0, "Disabled"),
                (1, "0.5° F"),
                (2, "1.0° F"),
                (3, "1.5° F"),
                (4, "2.0° F"),
            ],
            "type": "select",
        }
    ]

    # Test range type param
    capabilities = await device_action.async_get_action_capabilities(
        hass,
        {
            "platform": "device",
            "domain": DOMAIN,
            "device_id": device.id,
            "type": "set_config_parameter",
            "parameter": 10,
            "bitmask": None,
            "subtype": f"{node.node_id}-112-0-10 (Temperature Reporting Filter)",
        },
    )
    assert capabilities and "extra_fields" in capabilities

    assert voluptuous_serialize.convert(
        capabilities["extra_fields"], custom_serializer=cv.custom_serializer
    ) == [
        {
            "name": "value",
            "required": True,
            "type": "integer",
            "valueMin": 0,
            "valueMax": 124,
        }
    ]

    # Test undefined type param
    capabilities = await device_action.async_get_action_capabilities(
        hass,
        {
            "platform": "device",
            "domain": DOMAIN,
            "device_id": device.id,
            "type": "set_config_parameter",
            "parameter": 2,
            "bitmask": None,
            "subtype": f"{node.node_id}-112-0-2 (HVAC Settings)",
        },
    )
    assert not capabilities
Exemple #4
0
async def test_migrate_zwave(
    hass,
    zwave_integration,
    aeon_smart_switch_6,
    multisensor_6,
    integration,
    hass_ws_client,
):
    """Test the Z-Wave to Z-Wave JS migration websocket api."""
    entry = integration
    client = await hass_ws_client(hass)

    assert hass.config_entries.async_entries("zwave")

    await client.send_json({
        ID: 5,
        TYPE: "zwave_js/migrate_zwave",
        ENTRY_ID: entry.entry_id,
        "dry_run": False,
    })
    msg = await client.receive_json()
    result = msg["result"]

    migration_entity_map = {
        ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6",
        ZWAVE_LUMINANCE_ENTITY: "sensor.multisensor_6_illuminance",
        ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level",
    }

    assert result["zwave_entity_ids"] == [
        ZWAVE_SWITCH_ENTITY,
        ZWAVE_POWER_ENTITY,
        ZWAVE_SOURCE_NODE_ENTITY,
        ZWAVE_LUMINANCE_ENTITY,
        ZWAVE_BATTERY_ENTITY,
        ZWAVE_TAMPERING_ENTITY,
    ]
    expected_zwave_js_entities = [
        "switch.smart_switch_6",
        "sensor.multisensor_6_air_temperature",
        "sensor.multisensor_6_illuminance",
        "sensor.multisensor_6_humidity",
        "sensor.multisensor_6_ultraviolet",
        "binary_sensor.multisensor_6_home_security_tampering_product_cover_removed",
        "binary_sensor.multisensor_6_home_security_motion_detection",
        "sensor.multisensor_6_battery_level",
        "binary_sensor.multisensor_6_low_battery_level",
        "light.smart_switch_6",
        "sensor.smart_switch_6_electric_consumed_kwh",
        "sensor.smart_switch_6_electric_consumed_w",
        "sensor.smart_switch_6_electric_consumed_v",
        "sensor.smart_switch_6_electric_consumed_a",
    ]
    # Assert that both lists have the same items without checking order
    assert not set(
        result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities)
    assert result["migration_entity_map"] == migration_entity_map
    assert result["migrated"] is True

    dev_reg = dr.async_get(hass)
    ent_reg = er.async_get(hass)

    # check the device registry migration

    # check that the migrated entries have correct attributes
    multisensor_device_entry = dev_reg.async_get_device(identifiers={
        ("zwave_js", "3245146787-52")
    },
                                                        connections=set())
    assert multisensor_device_entry
    assert multisensor_device_entry.name_by_user == ZWAVE_MULTISENSOR_DEVICE_NAME
    assert multisensor_device_entry.area_id == ZWAVE_MULTISENSOR_DEVICE_AREA
    switch_device_entry = dev_reg.async_get_device(identifiers={
        ("zwave_js", "3245146787-102")
    },
                                                   connections=set())
    assert switch_device_entry
    assert switch_device_entry.name_by_user == ZWAVE_SWITCH_DEVICE_NAME
    assert switch_device_entry.area_id == ZWAVE_SWITCH_DEVICE_AREA

    migration_device_map = {
        ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id,
        ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id,
    }

    assert result["migration_device_map"] == migration_device_map

    # check the entity registry migration

    # this should have been migrated and no longer present under that id
    assert not ent_reg.async_is_registered(
        "sensor.multisensor_6_battery_level")
    assert not ent_reg.async_is_registered("sensor.multisensor_6_illuminance")

    # these should not have been migrated and is still in the registry
    assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY)
    source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY)
    assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID
    assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY)
    source_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY)
    assert source_entry.unique_id == ZWAVE_POWER_UNIQUE_ID
    assert ent_reg.async_is_registered(ZWAVE_TAMPERING_ENTITY)
    tampering_entry = ent_reg.async_get(ZWAVE_TAMPERING_ENTITY)
    assert tampering_entry.unique_id == ZWAVE_TAMPERING_UNIQUE_ID
    assert ent_reg.async_is_registered(
        "sensor.smart_switch_6_electric_consumed_w")

    # this is the new entity_ids of the zwave_js entities
    assert ent_reg.async_is_registered(ZWAVE_SWITCH_ENTITY)
    assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY)
    assert ent_reg.async_is_registered(ZWAVE_LUMINANCE_ENTITY)

    # check that the migrated entries have correct attributes
    switch_entry = ent_reg.async_get(ZWAVE_SWITCH_ENTITY)
    assert switch_entry
    assert switch_entry.unique_id == "3245146787.102-37-0-currentValue"
    assert switch_entry.name == ZWAVE_SWITCH_NAME
    assert switch_entry.icon == ZWAVE_SWITCH_ICON
    battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY)
    assert battery_entry
    assert battery_entry.unique_id == "3245146787.52-128-0-level"
    assert battery_entry.name == ZWAVE_BATTERY_NAME
    assert battery_entry.icon == ZWAVE_BATTERY_ICON
    luminance_entry = ent_reg.async_get(ZWAVE_LUMINANCE_ENTITY)
    assert luminance_entry
    assert luminance_entry.unique_id == "3245146787.52-49-0-Illuminance"
    assert luminance_entry.name == ZWAVE_LUMINANCE_NAME
    assert luminance_entry.icon == ZWAVE_LUMINANCE_ICON
    assert luminance_entry.unit_of_measurement == LIGHT_LUX

    # check that the zwave config entry has been removed
    assert not hass.config_entries.async_entries("zwave")

    # Check that the zwave integration fails entry setup after migration
    zwave_config_entry = MockConfigEntry(domain="zwave")
    zwave_config_entry.add_to_hass(hass)
    assert not await hass.config_entries.async_setup(
        zwave_config_entry.entry_id)
async def async_attach_trigger(
    hass: HomeAssistant,
    config: ConfigType,
    action: AutomationActionType,
    automation_info: AutomationTriggerInfo,
    *,
    platform_type: str = PLATFORM_TYPE,
) -> CALLBACK_TYPE:
    """Listen for state changes based on configuration."""
    nodes: set[Node] = set()
    if ATTR_DEVICE_ID in config:
        nodes.update({
            async_get_node_from_device_id(hass, device_id)
            for device_id in config.get(ATTR_DEVICE_ID, [])
        })
    if ATTR_ENTITY_ID in config:
        nodes.update({
            async_get_node_from_entity_id(hass, entity_id)
            for entity_id in config.get(ATTR_ENTITY_ID, [])
        })

    from_value = config[ATTR_FROM]
    to_value = config[ATTR_TO]
    command_class = config[ATTR_COMMAND_CLASS]
    property_ = config[ATTR_PROPERTY]
    endpoint = config.get(ATTR_ENDPOINT)
    property_key = config.get(ATTR_PROPERTY_KEY)
    unsubs = []
    job = HassJob(action)

    trigger_data = automation_info["trigger_data"]

    @callback
    def async_on_value_updated(value: Value, device: dr.DeviceEntry,
                               event: Event) -> None:
        """Handle value update."""
        event_value: Value = event["value"]
        if event_value != value:
            return

        # Get previous value and its state value if it exists
        prev_value_raw = event["args"]["prevValue"]
        prev_value = value.metadata.states.get(str(prev_value_raw),
                                               prev_value_raw)
        # Get current value and its state value if it exists
        curr_value_raw = event["args"]["newValue"]
        curr_value = value.metadata.states.get(str(curr_value_raw),
                                               curr_value_raw)
        # Check from and to values against previous and current values respectively
        for value_to_eval, raw_value_to_eval, match in (
            (prev_value, prev_value_raw, from_value),
            (curr_value, curr_value_raw, to_value),
        ):
            if (match != MATCH_ALL and value_to_eval != match
                    and not (isinstance(match, list) and
                             (value_to_eval in match
                              or raw_value_to_eval in match))
                    and raw_value_to_eval != match):
                return

        device_name = device.name_by_user or device.name

        payload = {
            **trigger_data,
            CONF_PLATFORM: platform_type,
            ATTR_DEVICE_ID: device.id,
            ATTR_NODE_ID: value.node.node_id,
            ATTR_COMMAND_CLASS: value.command_class,
            ATTR_COMMAND_CLASS_NAME: value.command_class_name,
            ATTR_PROPERTY: value.property_,
            ATTR_PROPERTY_NAME: value.property_name,
            ATTR_ENDPOINT: endpoint,
            ATTR_PROPERTY_KEY: value.property_key,
            ATTR_PROPERTY_KEY_NAME: value.property_key_name,
            ATTR_PREVIOUS_VALUE: prev_value,
            ATTR_PREVIOUS_VALUE_RAW: prev_value_raw,
            ATTR_CURRENT_VALUE: curr_value,
            ATTR_CURRENT_VALUE_RAW: curr_value_raw,
            "description": f"Z-Wave value {value_id} updated on {device_name}",
        }

        hass.async_run_hass_job(job, {"trigger": payload})

    dev_reg = dr.async_get(hass)
    for node in nodes:
        device_identifier = get_device_id(node.client, node)
        device = dev_reg.async_get_device({device_identifier})
        assert device
        value_id = get_value_id(node, command_class, property_, endpoint,
                                property_key)
        value = node.values[value_id]
        # We need to store the current value and device for the callback
        unsubs.append(
            node.on(
                "value updated",
                functools.partial(async_on_value_updated, value, device),
            ))

    @callback
    def async_remove() -> None:
        """Remove state listeners async."""
        for unsub in unsubs:
            unsub()
        unsubs.clear()

    return async_remove
Exemple #6
0
        )
        hw_version = None
        sw_version = None
        if router_info:
            hw_version = router_info.get("HardwareVersion")
            sw_version = router_info.get("SoftwareVersion")
            if router_info.get("DeviceName"):
                device_info[ATTR_MODEL] = router_info["DeviceName"]
        if not sw_version and router.data.get(KEY_DEVICE_BASIC_INFORMATION):
            sw_version = router.data[KEY_DEVICE_BASIC_INFORMATION].get(
                "SoftwareVersion")
        if hw_version:
            device_info[ATTR_HW_VERSION] = hw_version
        if sw_version:
            device_info[ATTR_SW_VERSION] = sw_version
        device_registry = dr.async_get(hass)
        device_registry.async_get_or_create(
            config_entry_id=entry.entry_id,
            **device_info,
        )

    # Forward config entry setup to platforms
    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    # Notify doesn't support config entry setup yet, load with discovery for now
    await discovery.async_load_platform(
        hass,
        Platform.NOTIFY,
        DOMAIN,
        {
            ATTR_UNIQUE_ID: entry.unique_id,
Exemple #7
0
async def async_setup_gateway_entry(hass: HomeAssistant,
                                    entry: ConfigEntry) -> None:
    """Set up the Xiaomi Gateway component from a config entry."""
    host = entry.data[CONF_HOST]
    token = entry.data[CONF_TOKEN]
    name = entry.title
    gateway_id = entry.unique_id

    # For backwards compat
    if entry.unique_id.endswith("-gateway"):
        hass.config_entries.async_update_entry(entry,
                                               unique_id=entry.data["mac"])

    entry.async_on_unload(entry.add_update_listener(update_listener))

    # Connect to gateway
    gateway = ConnectXiaomiGateway(hass, entry)
    try:
        await gateway.async_connect_gateway(host, token)
    except AuthException as error:
        raise ConfigEntryAuthFailed() from error
    except SetupException as error:
        raise ConfigEntryNotReady() from error
    gateway_info = gateway.gateway_info

    device_registry = dr.async_get(hass)
    device_registry.async_get_or_create(
        config_entry_id=entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, gateway_info.mac_address)},
        identifiers={(DOMAIN, gateway_id)},
        manufacturer="Xiaomi",
        name=name,
        model=gateway_info.model,
        sw_version=gateway_info.firmware_version,
        hw_version=gateway_info.hardware_version,
    )

    def update_data():
        """Fetch data from the subdevice."""
        data = {}
        for sub_device in gateway.gateway_device.devices.values():
            try:
                sub_device.update()
            except GatewayException as ex:
                _LOGGER.error("Got exception while fetching the state: %s", ex)
                data[sub_device.sid] = {ATTR_AVAILABLE: False}
            else:
                data[sub_device.sid] = {ATTR_AVAILABLE: True}
        return data

    async def async_update_data():
        """Fetch data from the subdevice using async_add_executor_job."""
        return await hass.async_add_executor_job(update_data)

    # Create update coordinator
    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=name,
        update_method=async_update_data,
        # Polling interval. Will only be polled if there are subscribers.
        update_interval=UPDATE_INTERVAL,
    )

    hass.data[DOMAIN][entry.entry_id] = {
        CONF_GATEWAY: gateway.gateway_device,
        KEY_COORDINATOR: coordinator,
    }

    for platform in GATEWAY_PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(entry, platform))
Exemple #8
0
async def async_setup_entry(  # noqa: C901
        hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Z-Wave JS from a config entry."""
    use_addon = entry.data.get(CONF_USE_ADDON)
    if use_addon:
        await async_ensure_addon_running(hass, entry)

    client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass))
    dev_reg = device_registry.async_get(hass)
    ent_reg = entity_registry.async_get(hass)
    entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {})

    entry_hass_data[DATA_CLIENT] = client
    entry_hass_data[DATA_PLATFORM_SETUP] = {}

    registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict)

    async def async_on_node_ready(node: ZwaveNode) -> None:
        """Handle node ready event."""
        LOGGER.debug("Processing node %s", node)

        platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP]

        # register (or update) node in device registry
        device = register_node_in_dev_reg(hass, entry, dev_reg, client, node)
        # We only want to create the defaultdict once, even on reinterviews
        if device.id not in registered_unique_ids:
            registered_unique_ids[device.id] = defaultdict(set)

        value_updates_disc_info: dict[str, ZwaveDiscoveryInfo] = {}

        # run discovery on all node values and create/update entities
        for disc_info in async_discover_values(node, device):
            platform = disc_info.platform

            # This migration logic was added in 2021.3 to handle a breaking change to
            # the value_id format. Some time in the future, this call (as well as the
            # helper functions) can be removed.
            async_migrate_discovered_value(
                hass,
                ent_reg,
                registered_unique_ids[device.id][platform],
                device,
                client,
                disc_info,
            )

            if platform not in platform_setup_tasks:
                platform_setup_tasks[platform] = hass.async_create_task(
                    hass.config_entries.async_forward_entry_setup(
                        entry, platform))

            await platform_setup_tasks[platform]

            LOGGER.debug("Discovered entity: %s", disc_info)
            async_dispatcher_send(hass,
                                  f"{DOMAIN}_{entry.entry_id}_add_{platform}",
                                  disc_info)

            # Capture discovery info for values we want to watch for updates
            if disc_info.assumed_state:
                value_updates_disc_info[
                    disc_info.primary_value.value_id] = disc_info

        # add listener for value updated events if necessary
        if value_updates_disc_info:
            entry.async_on_unload(
                node.on(
                    "value updated",
                    lambda event: async_on_value_updated(
                        value_updates_disc_info, event["value"]),
                ))

        # add listener for stateless node value notification events
        entry.async_on_unload(
            node.on(
                "value notification",
                lambda event: async_on_value_notification(event[
                    "value_notification"]),
            ))
        # add listener for stateless node notification events
        entry.async_on_unload(
            node.on(
                "notification",
                lambda event: async_on_notification(event["notification"]),
            ))

    async def async_on_node_added(node: ZwaveNode) -> None:
        """Handle node added event."""
        platform_setup_tasks = entry_hass_data[DATA_PLATFORM_SETUP]

        # We need to set up the sensor platform if it hasn't already been setup in
        # order to create the node status sensor
        if SENSOR_DOMAIN not in platform_setup_tasks:
            platform_setup_tasks[SENSOR_DOMAIN] = hass.async_create_task(
                hass.config_entries.async_forward_entry_setup(
                    entry, SENSOR_DOMAIN))

        # This guard ensures that concurrent runs of this function all await the
        # platform setup task
        if not platform_setup_tasks[SENSOR_DOMAIN].done():
            await platform_setup_tasks[SENSOR_DOMAIN]

        # Create a node status sensor for each device
        async_dispatcher_send(
            hass, f"{DOMAIN}_{entry.entry_id}_add_node_status_sensor", node)

        # we only want to run discovery when the node has reached ready state,
        # otherwise we'll have all kinds of missing info issues.
        if node.ready:
            await async_on_node_ready(node)
            return
        # if node is not yet ready, register one-time callback for ready state
        LOGGER.debug("Node added: %s - waiting for it to become ready",
                     node.node_id)
        node.once(
            "ready",
            lambda event: hass.async_create_task(
                async_on_node_ready(event["node"])),
        )
        # we do submit the node to device registry so user has
        # some visual feedback that something is (in the process of) being added
        register_node_in_dev_reg(hass, entry, dev_reg, client, node)

    @callback
    def async_on_node_removed(node: ZwaveNode) -> None:
        """Handle node removed event."""
        # grab device in device registry attached to this node
        dev_id = get_device_id(client, node)
        device = dev_reg.async_get_device({dev_id})
        # note: removal of entity registry entry is handled by core
        dev_reg.async_remove_device(device.id)  # type: ignore
        registered_unique_ids.pop(device.id, None)  # type: ignore

    @callback
    def async_on_value_notification(notification: ValueNotification) -> None:
        """Relay stateless value notification events from Z-Wave nodes to hass."""
        device = dev_reg.async_get_device(
            {get_device_id(client, notification.node)})
        raw_value = value = notification.value
        if notification.metadata.states:
            value = notification.metadata.states.get(str(value), value)
        hass.bus.async_fire(
            ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
            {
                ATTR_DOMAIN: DOMAIN,
                ATTR_NODE_ID: notification.node.node_id,
                ATTR_HOME_ID: client.driver.controller.home_id,
                ATTR_ENDPOINT: notification.endpoint,
                ATTR_DEVICE_ID: device.id,  # type: ignore
                ATTR_COMMAND_CLASS: notification.command_class,
                ATTR_COMMAND_CLASS_NAME: notification.command_class_name,
                ATTR_LABEL: notification.metadata.label,
                ATTR_PROPERTY: notification.property_,
                ATTR_PROPERTY_NAME: notification.property_name,
                ATTR_PROPERTY_KEY: notification.property_key,
                ATTR_PROPERTY_KEY_NAME: notification.property_key_name,
                ATTR_VALUE: value,
                ATTR_VALUE_RAW: raw_value,
            },
        )

    @callback
    def async_on_notification(
        notification: EntryControlNotification | NotificationNotification,
    ) -> None:
        """Relay stateless notification events from Z-Wave nodes to hass."""
        device = dev_reg.async_get_device(
            {get_device_id(client, notification.node)})
        event_data = {
            ATTR_DOMAIN: DOMAIN,
            ATTR_NODE_ID: notification.node.node_id,
            ATTR_HOME_ID: client.driver.controller.home_id,
            ATTR_DEVICE_ID: device.id,  # type: ignore
            ATTR_COMMAND_CLASS: notification.command_class,
        }

        if isinstance(notification, EntryControlNotification):
            event_data.update({
                ATTR_COMMAND_CLASS_NAME: "Entry Control",
                ATTR_EVENT_TYPE: notification.event_type,
                ATTR_DATA_TYPE: notification.data_type,
                ATTR_EVENT_DATA: notification.event_data,
            })
        else:
            event_data.update({
                ATTR_COMMAND_CLASS_NAME: "Notification",
                ATTR_LABEL: notification.label,
                ATTR_TYPE: notification.type_,
                ATTR_EVENT: notification.event,
                ATTR_EVENT_LABEL: notification.event_label,
                ATTR_PARAMETERS: notification.parameters,
            })

        hass.bus.async_fire(ZWAVE_JS_NOTIFICATION_EVENT, event_data)

    @callback
    def async_on_value_updated(value_updates_disc_info: dict[
        str, ZwaveDiscoveryInfo], value: Value) -> None:
        """Fire value updated event."""
        # Get the discovery info for the value that was updated. If there is
        # no discovery info for this value, we don't need to fire an event
        if value.value_id not in value_updates_disc_info:
            return
        disc_info = value_updates_disc_info[value.value_id]

        device = dev_reg.async_get_device({get_device_id(client, value.node)})

        unique_id = get_unique_id(client.driver.controller.home_id,
                                  disc_info.primary_value.value_id)
        entity_id = ent_reg.async_get_entity_id(disc_info.platform, DOMAIN,
                                                unique_id)

        raw_value = value_ = value.value
        if value.metadata.states:
            value_ = value.metadata.states.get(str(value), value_)

        hass.bus.async_fire(
            ZWAVE_JS_VALUE_UPDATED_EVENT,
            {
                ATTR_NODE_ID: value.node.node_id,
                ATTR_HOME_ID: client.driver.controller.home_id,
                ATTR_DEVICE_ID: device.id,  # type: ignore
                ATTR_ENTITY_ID: entity_id,
                ATTR_COMMAND_CLASS: value.command_class,
                ATTR_COMMAND_CLASS_NAME: value.command_class_name,
                ATTR_ENDPOINT: value.endpoint,
                ATTR_PROPERTY: value.property_,
                ATTR_PROPERTY_NAME: value.property_name,
                ATTR_PROPERTY_KEY: value.property_key,
                ATTR_PROPERTY_KEY_NAME: value.property_key_name,
                ATTR_VALUE: value_,
                ATTR_VALUE_RAW: raw_value,
            },
        )

    # connect and throw error if connection failed
    try:
        async with timeout(CONNECT_TIMEOUT):
            await client.connect()
    except InvalidServerVersion as err:
        if not entry_hass_data.get(DATA_INVALID_SERVER_VERSION_LOGGED):
            LOGGER.error("Invalid server version: %s", err)
            entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = True
        if use_addon:
            async_ensure_addon_updated(hass)
        raise ConfigEntryNotReady from err
    except (asyncio.TimeoutError, BaseZwaveJSServerError) as err:
        if not entry_hass_data.get(DATA_CONNECT_FAILED_LOGGED):
            LOGGER.error("Failed to connect: %s", err)
            entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = True
        raise ConfigEntryNotReady from err
    else:
        LOGGER.info("Connected to Zwave JS Server")
        entry_hass_data[DATA_CONNECT_FAILED_LOGGED] = False
        entry_hass_data[DATA_INVALID_SERVER_VERSION_LOGGED] = False

    services = ZWaveServices(hass, ent_reg, dev_reg)
    services.async_register()

    # Set up websocket API
    async_register_api(hass)

    async def start_platforms() -> None:
        """Start platforms and perform discovery."""
        driver_ready = asyncio.Event()

        async def handle_ha_shutdown(event: Event) -> None:
            """Handle HA shutdown."""
            await disconnect_client(hass, entry)

        listen_task = asyncio.create_task(
            client_listen(hass, entry, client, driver_ready))
        entry_hass_data[DATA_CLIENT_LISTEN_TASK] = listen_task
        entry.async_on_unload(
            hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP,
                                  handle_ha_shutdown))

        try:
            await driver_ready.wait()
        except asyncio.CancelledError:
            LOGGER.debug("Cancelling start platforms")
            return

        LOGGER.info("Connection to Zwave JS Server initialized")

        # If opt in preference hasn't been specified yet, we do nothing, otherwise
        # we apply the preference
        if opted_in := entry.data.get(CONF_DATA_COLLECTION_OPTED_IN):
            await async_enable_statistics(client)
        elif opted_in is False:
            await client.driver.async_disable_statistics()
def _async_get_ufp_instances(
        hass: HomeAssistant,
        device_id: str) -> tuple[dr.DeviceEntry, ProtectApiClient]:
    device_registry = dr.async_get(hass)
    if not (device_entry := device_registry.async_get(device_id)):
        raise HomeAssistantError(f"No device found for device id: {device_id}")
Exemple #10
0
async def assert_devices_and_entities_created(hass: HomeAssistant,
                                              expected: DeviceTestInfo):
    """Check that all expected devices and entities are loaded and enumerated as expected."""
    entity_registry = er.async_get(hass)
    device_registry = dr.async_get(hass)

    async def _do_assertions(expected: DeviceTestInfo) -> dr.DeviceEntry:
        # Note: homekit_controller currently uses a 3-tuple for device identifiers
        # The current standard is a 2-tuple (hkc was not migrated when this change was brought in)

        # There are currently really 3 cases here:
        # - We can match exactly one device by serial number. This won't work for devices like the Ryse.
        #   These have nlank or broken serial numbers.
        # - The device unique id is "00:00:00:00:00:00" - this is the pairing id. This is only set for
        #   the root (bridge) device.
        # - The device unique id is "00:00:00:00:00:00-X", where X is a HAP aid. This is only set when
        #   we have detected broken serial numbers (and serial number is not used as an identifier).

        device = device_registry.async_get_device({
            (DOMAIN, IDENTIFIER_SERIAL_NUMBER, expected.serial_number),
            (DOMAIN, IDENTIFIER_ACCESSORY_ID, expected.unique_id),
        })

        logger.debug("Comparing device %r to %r", device, expected)

        assert device
        assert device.name == expected.name
        assert device.model == expected.model
        assert device.manufacturer == expected.manufacturer
        assert device.hw_version == expected.hw_version
        assert device.sw_version == expected.sw_version

        # We might have matched the device by one identifier only
        # Lets check that the other one is correct. Otherwise the test might silently be wrong.
        serial_number_set = False
        accessory_id_set = False

        for _, key, value in device.identifiers:
            if key == IDENTIFIER_SERIAL_NUMBER:
                assert value == expected.serial_number
                serial_number_set = True

            elif key == IDENTIFIER_ACCESSORY_ID:
                assert value == expected.unique_id
                accessory_id_set = True

        # If unique_id or serial is provided it MUST actually appear in the device registry entry.
        assert (not expected.unique_id) ^ accessory_id_set
        assert (not expected.serial_number) ^ serial_number_set

        for entity_info in expected.entities:
            entity = entity_registry.async_get(entity_info.entity_id)
            logger.debug("Comparing entity %r to %r", entity, entity_info)

            assert entity
            assert entity.device_id == device.id
            assert entity.unique_id == entity_info.unique_id
            assert entity.supported_features == entity_info.supported_features
            assert entity.entity_category == entity_info.entity_category
            assert entity.unit_of_measurement == entity_info.unit_of_measurement
            assert entity.capabilities == entity_info.capabilities

            state = hass.states.get(entity_info.entity_id)
            logger.debug("Comparing state %r to %r", state, entity_info)

            assert state is not None
            assert state.state == entity_info.state
            assert state.attributes[
                "friendly_name"] == entity_info.friendly_name

        all_triggers = await async_get_device_automations(
            hass, DeviceAutomationType.TRIGGER, device.id)
        stateless_triggers = []
        for trigger in all_triggers:
            if trigger.get("entity_id"):
                continue
            stateless_triggers.append(
                DeviceTriggerInfo(type=trigger.get("type"),
                                  subtype=trigger.get("subtype")))
        assert stateless_triggers == (expected.stateless_triggers or [])

        for child in expected.devices:
            child_device = await _do_assertions(child)
            assert child_device.via_device_id == device.id
            assert child_device.id != device.id

        return device

    root_device = await _do_assertions(expected)

    # Root device must not have a via, otherwise its not the device
    assert root_device.via_device_id is None
Exemple #11
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
    session = SequentialWriterSession(entry.data["ip"], PORT)
    state = State()

    try:
        cmd = PingCommand(state).raw
        await asyncio.wait_for(session.write(cmd), timeout=5)
        state.increment_frame_number()
        _ = await session.read(64)
    except (asyncio.TimeoutError, IOError) as e:
        raise ConfigEntryNotReady from e

    async def async_update():
        try:
            _LOGGER.info("Getting number of zones")
            cmd = GetNumberOfZonesCommand(state).raw
            await session.write(cmd)
            state.increment_frame_number()
            res = await session.read(64)
            number_of_zones = GetNumberOfZonesResponse(res).number

            zones = []
            for zone_num in range(1, number_of_zones + 1):
                _LOGGER.info("Getting info about zone=%d", zone_num)
                cmd = GetZoneInfoCommand(state, zone=zone_num).raw
                await session.write(cmd)
                state.increment_frame_number()
                res = await session.read(64)
                zone_info = GetZoneInfoResponse(res)
                _LOGGER.debug("Zone=%d has type=%s, name=%s", zone_num, zone_info.type, zone_info.name)
                zones.append({"num": zone_num, "name": zone_info.name, "type": zone_info.type})
            return zones

        except (IOError, ValueError) as e:
            # IOError can hardly happen when Session is too resilient...
            # but ValueError happens quite often because of malformed (out-of-order) zone_info responses
            raise UpdateFailed(e) from e

    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=DOMAIN,
        update_method=async_update,
        # update_interval=timedelta(hours=1),  # do NOT update data at all - only after HA restart
    )
    await coordinator.async_config_entry_first_refresh()

    dr = device_registry.async_get(hass)
    dr.async_get_or_create(
        config_entry_id=entry.entry_id,
        connections={(CONNECTION_NETWORK_MAC, entry.data["mac"])},
        name=f"{MANUFACTURER} Wi-Fi relay",  # TODO localize?
        manufacturer=MANUFACTURER,
    )

    new_entities = []
    for zone_info in coordinator.data:
        zone = Zone(entry, coordinator, session, state, zone_info["num"], zone_info["type"], zone_info["name"])
        new_entities.append(zone)

    if new_entities:
        async_add_entities(new_entities)

    hass.data[DOMAIN][entry.entry_id] = {
        "session": session,
    }
async def test_options_add_and_configure_device(hass):
    """Test we can add a device."""
    await setup.async_setup_component(hass, "persistent_notification", {})

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            "host": None,
            "port": None,
            "device": "/dev/tty123",
            "automatic_add": False,
            "devices": {},
        },
        unique_id=DOMAIN,
    )
    entry.add_to_hass(hass)

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": True,
            "event_code": "0913000022670e013970",
        },
    )

    assert result["type"] == "form"
    assert result["step_id"] == "set_device_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "fire_event": False,
            "signal_repetitions": 5,
            "data_bits": 4,
            "off_delay": "abcdef",
            "command_on": "xyz",
            "command_off": "xyz",
        },
    )

    assert result["type"] == "form"
    assert result["step_id"] == "set_device_options"
    assert result["errors"]
    assert result["errors"]["off_delay"] == "invalid_input_off_delay"
    assert result["errors"]["command_on"] == "invalid_input_2262_on"
    assert result["errors"]["command_off"] == "invalid_input_2262_off"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "fire_event": False,
            "signal_repetitions": 5,
            "data_bits": 4,
            "command_on": "0xE",
            "command_off": "0x7",
            "off_delay": "9",
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    assert entry.data["automatic_add"]

    assert entry.data["devices"]["0913000022670e013970"]
    assert not entry.data["devices"]["0913000022670e013970"]["fire_event"]
    assert entry.data["devices"]["0913000022670e013970"][
        "signal_repetitions"] == 5
    assert entry.data["devices"]["0913000022670e013970"]["off_delay"] == 9

    state = hass.states.get("binary_sensor.pt2262_22670e")
    assert state
    assert state.state == "off"
    assert state.attributes.get("friendly_name") == "PT2262 22670e"

    device_registry = dr.async_get(hass)
    device_entries = dr.async_entries_for_config_entry(device_registry,
                                                       entry.entry_id)

    assert device_entries[0].id

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": False,
            "device": device_entries[0].id,
        },
    )

    assert result["type"] == "form"
    assert result["step_id"] == "set_device_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "fire_event": True,
            "signal_repetitions": 5,
            "data_bits": 4,
            "command_on": "0xE",
            "command_off": "0x7",
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    assert entry.data["devices"]["0913000022670e013970"]
    assert entry.data["devices"]["0913000022670e013970"]["fire_event"]
    assert entry.data["devices"]["0913000022670e013970"][
        "signal_repetitions"] == 5
    assert "delay_off" not in entry.data["devices"]["0913000022670e013970"]
async def test_options_remove_multiple_devices(hass):
    """Test we can add a device."""
    await setup.async_setup_component(hass, "persistent_notification", {})

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            "host": None,
            "port": None,
            "device": "/dev/tty123",
            "automatic_add": False,
            "devices": {
                "0b1100cd0213c7f230010f71": {
                    "device_id": ["11", "0", "213c7f2:48"]
                },
                "0b1100100118cdea02010f70": {
                    "device_id": ["11", "0", "118cdea:2"]
                },
                "0b1100101118cdea02010f70": {
                    "device_id": ["11", "0", "1118cdea:2"]
                },
            },
        },
        unique_id=DOMAIN,
    )
    entry.add_to_hass(hass)

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

    state = hass.states.get("binary_sensor.ac_213c7f2_48")
    assert state
    state = hass.states.get("binary_sensor.ac_118cdea_2")
    assert state
    state = hass.states.get("binary_sensor.ac_1118cdea_2")
    assert state

    device_registry = dr.async_get(hass)
    device_entries = dr.async_entries_for_config_entry(device_registry,
                                                       entry.entry_id)

    assert len(device_entries) == 3

    def match_device_id(entry):
        device_id = next(iter(entry.identifiers))[1:]
        if device_id == ("11", "0", "213c7f2:48"):
            return True
        if device_id == ("11", "0", "118cdea:2"):
            return True
        return False

    remove_devices = [
        elem.id for elem in device_entries if match_device_id(elem)
    ]

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": False,
            "remove_device": remove_devices,
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    state = hass.states.get("binary_sensor.ac_213c7f2_48")
    assert not state
    state = hass.states.get("binary_sensor.ac_118cdea_2")
    assert not state
    state = hass.states.get("binary_sensor.ac_1118cdea_2")
    assert state
async def test_options_replace_control_device(hass):
    """Test we can replace a control device."""
    await setup.async_setup_component(hass, "persistent_notification", {})

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            "host": None,
            "port": None,
            "device": "/dev/tty123",
            "automatic_add": False,
            "devices": {
                "0b1100100118cdea02010f70": {
                    "device_id": ["11", "0", "118cdea:2"],
                    "signal_repetitions": 1,
                },
                "0b1100101118cdea02010f70": {
                    "device_id": ["11", "0", "1118cdea:2"],
                    "signal_repetitions": 1,
                },
            },
        },
        unique_id=DOMAIN,
    )
    entry.add_to_hass(hass)

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

    state = hass.states.get("binary_sensor.ac_118cdea_2")
    assert state
    state = hass.states.get("sensor.ac_118cdea_2_rssi_numeric")
    assert state
    state = hass.states.get("switch.ac_118cdea_2")
    assert state
    state = hass.states.get("binary_sensor.ac_1118cdea_2")
    assert state
    state = hass.states.get("sensor.ac_1118cdea_2_rssi_numeric")
    assert state
    state = hass.states.get("switch.ac_1118cdea_2")
    assert state

    device_registry = dr.async_get(hass)
    device_entries = dr.async_entries_for_config_entry(device_registry,
                                                       entry.entry_id)

    old_device = next(
        (elem.id for elem in device_entries
         if next(iter(elem.identifiers))[1:] == ("11", "0", "118cdea:2")),
        None,
    )
    new_device = next(
        (elem.id for elem in device_entries
         if next(iter(elem.identifiers))[1:] == ("11", "0", "1118cdea:2")),
        None,
    )

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": False,
            "device": old_device,
        },
    )

    assert result["type"] == "form"
    assert result["step_id"] == "set_device_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "replace_device": new_device,
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    entity_registry = er.async_get(hass)

    entry = entity_registry.async_get("binary_sensor.ac_118cdea_2")
    assert entry
    assert entry.device_id == new_device
    entry = entity_registry.async_get("sensor.ac_118cdea_2_rssi_numeric")
    assert entry
    assert entry.device_id == new_device
    entry = entity_registry.async_get("switch.ac_118cdea_2")
    assert entry
    assert entry.device_id == new_device

    state = hass.states.get("binary_sensor.ac_1118cdea_2")
    assert not state
    state = hass.states.get("sensor.ac_1118cdea_2_rssi_numeric")
    assert not state
    state = hass.states.get("switch.ac_1118cdea_2")
    assert not state
Exemple #15
0
class ScannerEntity(BaseTrackerEntity):
    """Base class for a tracked device that is on a scanned network."""
    @property
    def ip_address(self) -> str | None:
        """Return the primary ip address of the device."""
        return None

    @property
    def mac_address(self) -> str | None:
        """Return the mac address of the device."""
        return None

    @property
    def hostname(self) -> str | None:
        """Return hostname of the device."""
        return None

    @property
    def state(self) -> str:
        """Return the state of the device."""
        if self.is_connected:
            return STATE_HOME
        return STATE_NOT_HOME

    @property
    def is_connected(self) -> bool:
        """Return true if the device is connected to the network."""
        raise NotImplementedError

    @property
    def unique_id(self) -> str | None:
        """Return unique ID of the entity."""
        return self.mac_address

    @final
    @property
    def device_info(self) -> DeviceInfo | None:
        """Device tracker entities should not create device registry entries."""
        return None

    @property
    def entity_registry_enabled_default(self) -> bool:
        """Return if entity is enabled by default."""
        # If mac_address is None, we can never find a device entry.
        return (
            # Do not disable if we won't activate our attach to device logic
            self.mac_address is None or self.device_info is not None
            # Disable if we automatically attach but there is no device
            or self.find_device_entry() is not None)

    @callback
    def add_to_platform_start(
        self,
        hass: HomeAssistant,
        platform: EntityPlatform,
        parallel_updates: asyncio.Semaphore | None,
    ) -> None:
        """Start adding an entity to a platform."""
        super().add_to_platform_start(hass, platform, parallel_updates)
        if self.mac_address and self.unique_id:
            _async_register_mac(hass, platform.platform_name, self.mac_address,
                                self.unique_id)

    @callback
    def find_device_entry(self) -> dr.DeviceEntry | None:
        """Return device entry."""
        assert self.mac_address is not None

        return dr.async_get(self.hass).async_get_device(
            set(), {(dr.CONNECTION_NETWORK_MAC, self.mac_address)})

    async def async_internal_added_to_hass(self) -> None:
        """Handle added to Home Assistant."""
        # Entities without a unique ID don't have a device
        if (not self.registry_entry or not self.platform
                or not self.platform.config_entry or not self.mac_address
                or (device_entry := self.find_device_entry()) is None
                # Entities should not have a device info. We opt them out
                # of this logic if they do.
                or self.device_info):
            if self.device_info:
                LOGGER.debug("Entity %s unexpectedly has a device info",
                             self.entity_id)
            await super().async_internal_added_to_hass()
            return

        # Attach entry to device
        if self.registry_entry.device_id != device_entry.id:
            self.registry_entry = er.async_get(self.hass).async_update_entity(
                self.entity_id, device_id=device_entry.id)

        # Attach device to config entry
        if self.platform.config_entry.entry_id not in device_entry.config_entries:
            dr.async_get(self.hass).async_update_device(
                device_entry.id,
                add_config_entry_id=self.platform.config_entry.entry_id,
            )

        # Do this last or else the entity registry update listener has been installed
        await super().async_internal_added_to_hass()
Exemple #16
0
async def test_switches(
    hass: HomeAssistant, mock_fully_kiosk: MagicMock, init_integration: MockConfigEntry
) -> None:
    """Test Fully Kiosk switches."""
    entity_registry = er.async_get(hass)
    device_registry = dr.async_get(hass)

    entity = hass.states.get("switch.amazon_fire_screensaver")
    assert entity
    assert entity.state == "off"
    entry = entity_registry.async_get("switch.amazon_fire_screensaver")
    assert entry
    assert entry.unique_id == "abcdef-123456-screensaver"
    await call_service(hass, "turn_on", "switch.amazon_fire_screensaver")
    assert len(mock_fully_kiosk.startScreensaver.mock_calls) == 1
    await call_service(hass, "turn_off", "switch.amazon_fire_screensaver")
    assert len(mock_fully_kiosk.stopScreensaver.mock_calls) == 1

    entity = hass.states.get("switch.amazon_fire_maintenance_mode")
    assert entity
    assert entity.state == "off"
    entry = entity_registry.async_get("switch.amazon_fire_maintenance_mode")
    assert entry
    assert entry.unique_id == "abcdef-123456-maintenance"
    await call_service(hass, "turn_on", "switch.amazon_fire_maintenance_mode")
    assert len(mock_fully_kiosk.enableLockedMode.mock_calls) == 1
    await call_service(hass, "turn_off", "switch.amazon_fire_maintenance_mode")
    assert len(mock_fully_kiosk.disableLockedMode.mock_calls) == 1

    entity = hass.states.get("switch.amazon_fire_kiosk_lock")
    assert entity
    assert entity.state == "on"
    entry = entity_registry.async_get("switch.amazon_fire_kiosk_lock")
    assert entry
    assert entry.unique_id == "abcdef-123456-kiosk"
    await call_service(hass, "turn_off", "switch.amazon_fire_kiosk_lock")
    assert len(mock_fully_kiosk.unlockKiosk.mock_calls) == 1
    await call_service(hass, "turn_on", "switch.amazon_fire_kiosk_lock")
    assert len(mock_fully_kiosk.lockKiosk.mock_calls) == 1

    entity = hass.states.get("switch.amazon_fire_motion_detection")
    assert entity
    assert entity.state == "off"
    entry = entity_registry.async_get("switch.amazon_fire_motion_detection")
    assert entry
    assert entry.unique_id == "abcdef-123456-motion-detection"
    await call_service(hass, "turn_on", "switch.amazon_fire_motion_detection")
    assert len(mock_fully_kiosk.enableMotionDetection.mock_calls) == 1
    await call_service(hass, "turn_off", "switch.amazon_fire_motion_detection")
    assert len(mock_fully_kiosk.disableMotionDetection.mock_calls) == 1

    entity = hass.states.get("switch.amazon_fire_screen")
    assert entity
    assert entity.state == "on"
    entry = entity_registry.async_get("switch.amazon_fire_screen")
    assert entry
    assert entry.unique_id == "abcdef-123456-screenOn"
    await call_service(hass, "turn_off", "switch.amazon_fire_screen")
    assert len(mock_fully_kiosk.screenOff.mock_calls) == 1
    await call_service(hass, "turn_on", "switch.amazon_fire_screen")
    assert len(mock_fully_kiosk.screenOn.mock_calls) == 1

    assert entry.device_id
    device_entry = device_registry.async_get(entry.device_id)
    assert device_entry
    assert device_entry.configuration_url == "http://192.168.1.234:2323"
    assert device_entry.entry_type is None
    assert device_entry.hw_version is None
    assert device_entry.identifiers == {(DOMAIN, "abcdef-123456")}
    assert device_entry.manufacturer == "amzn"
    assert device_entry.model == "KFDOWI"
    assert device_entry.name == "Amazon Fire"
    assert device_entry.sw_version == "1.42.5"
Exemple #17
0
    def find_device_entry(self) -> dr.DeviceEntry | None:
        """Return device entry."""
        assert self.mac_address is not None

        return dr.async_get(self.hass).async_get_device(
            set(), {(dr.CONNECTION_NETWORK_MAC, self.mac_address)})
Exemple #18
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up motionEye from a config entry."""
    hass.data.setdefault(DOMAIN, {})

    client = create_motioneye_client(
        entry.data[CONF_URL],
        admin_username=entry.data.get(CONF_ADMIN_USERNAME),
        admin_password=entry.data.get(CONF_ADMIN_PASSWORD),
        surveillance_username=entry.data.get(CONF_SURVEILLANCE_USERNAME),
        surveillance_password=entry.data.get(CONF_SURVEILLANCE_PASSWORD),
        session=async_get_clientsession(hass),
    )

    try:
        await client.async_client_login()
    except MotionEyeClientInvalidAuthError as exc:
        await client.async_client_close()
        raise ConfigEntryAuthFailed from exc
    except MotionEyeClientError as exc:
        await client.async_client_close()
        raise ConfigEntryNotReady from exc

    # Ensure every loaded entry has a registered webhook id.
    if CONF_WEBHOOK_ID not in entry.data:
        hass.config_entries.async_update_entry(
            entry, data={
                **entry.data, CONF_WEBHOOK_ID: async_generate_id()
            })
    webhook_register(hass, DOMAIN, "motionEye", entry.data[CONF_WEBHOOK_ID],
                     handle_webhook)

    @callback
    async def async_update_data() -> dict[str, Any] | None:
        try:
            return await client.async_get_cameras()
        except MotionEyeClientError as exc:
            raise UpdateFailed("Error communicating with API") from exc

    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=DOMAIN,
        update_method=async_update_data,
        update_interval=DEFAULT_SCAN_INTERVAL,
    )
    hass.data[DOMAIN][entry.entry_id] = {
        CONF_CLIENT: client,
        CONF_COORDINATOR: coordinator,
    }

    current_cameras: set[tuple[str, str]] = set()
    device_registry = dr.async_get(hass)

    @callback
    def _async_process_motioneye_cameras() -> None:
        """Process motionEye camera additions and removals."""
        inbound_camera: set[tuple[str, str]] = set()
        if coordinator.data is None or KEY_CAMERAS not in coordinator.data:
            return

        for camera in coordinator.data[KEY_CAMERAS]:
            if not is_acceptable_camera(camera):
                return
            camera_id = camera[KEY_ID]
            device_identifier = get_motioneye_device_identifier(
                entry.entry_id, camera_id)
            inbound_camera.add(device_identifier)

            if device_identifier in current_cameras:
                continue
            current_cameras.add(device_identifier)
            _add_camera(
                hass,
                device_registry,
                client,
                entry,
                camera_id,
                camera,
                device_identifier,
            )

        # Ensure every device associated with this config entry is still in the
        # list of motionEye cameras, otherwise remove the device (and thus
        # entities).
        for device_entry in dr.async_entries_for_config_entry(
                device_registry, entry.entry_id):
            for identifier in device_entry.identifiers:
                if identifier in inbound_camera:
                    break
            else:
                device_registry.async_remove_device(device_entry.id)

    async def setup_then_listen() -> None:
        await asyncio.gather(
            *(hass.config_entries.async_forward_entry_setup(entry, platform)
              for platform in PLATFORMS))
        entry.async_on_unload(
            coordinator.async_add_listener(_async_process_motioneye_cameras))
        await coordinator.async_refresh()
        entry.async_on_unload(entry.add_update_listener(_async_entry_updated))

    hass.async_create_task(setup_then_listen())
    return True
Exemple #19
0
    def async_create_devices(self):
        """
        Build device registry entries for all accessories paired with the bridge.

        This is done as well as by the entities for 2 reasons. First, the bridge
        might not have any entities attached to it. Secondly there are stateless
        entities like doorbells and remote controls.
        """
        device_registry = dr.async_get(self.hass)

        devices = {}

        for accessory in self.entity_map.accessories:
            info = accessory.services.first(
                service_type=ServicesTypes.ACCESSORY_INFORMATION,
            )

            serial_number = info.value(CharacteristicsTypes.SERIAL_NUMBER)

            if valid_serial_number(serial_number):
                identifiers = {(DOMAIN, IDENTIFIER_SERIAL_NUMBER, serial_number)}
            else:
                # Some accessories do not have a serial number
                identifiers = {
                    (
                        DOMAIN,
                        IDENTIFIER_ACCESSORY_ID,
                        f"{self.unique_id}_{accessory.aid}",
                    )
                }

            if accessory.aid == 1:
                # Accessory 1 is the root device (sometimes the only device, sometimes a bridge)
                # Link the root device to the pairing id for the connection.
                identifiers.add((DOMAIN, IDENTIFIER_ACCESSORY_ID, self.unique_id))

            device_info = DeviceInfo(
                identifiers=identifiers,
                name=info.value(CharacteristicsTypes.NAME),
                manufacturer=info.value(CharacteristicsTypes.MANUFACTURER, ""),
                model=info.value(CharacteristicsTypes.MODEL, ""),
                sw_version=info.value(CharacteristicsTypes.FIRMWARE_REVISION, ""),
                hw_version=info.value(CharacteristicsTypes.HARDWARE_REVISION, ""),
            )

            if accessory.aid != 1:
                # Every pairing has an accessory 1
                # It *doesn't* have a via_device, as it is the device we are connecting to
                # Every other accessory should use it as its via device.
                device_info[ATTR_VIA_DEVICE] = (
                    DOMAIN,
                    IDENTIFIER_SERIAL_NUMBER,
                    self.connection_info["serial-number"],
                )

            device = device_registry.async_get_or_create(
                config_entry_id=self.config_entry.entry_id,
                **device_info,
            )

            devices[accessory.aid] = device.id

        self.devices = devices
Exemple #20
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Somfy from a config entry."""

    _LOGGER.warning(
        "The Somfy integration is deprecated and will be removed "
        "in Home Assistant Core 2022.7; due to the Somfy Open API deprecation."
        "The Somfy Open API will shutdown June 21st 2022, migrate to the "
        "Overkiz integration to control your Somfy devices"
    )

    # Backwards compat
    if "auth_implementation" not in entry.data:
        hass.config_entries.async_update_entry(
            entry, data={**entry.data, "auth_implementation": DOMAIN}
        )

    implementation = (
        await config_entry_oauth2_flow.async_get_config_entry_implementation(
            hass, entry
        )
    )

    data = hass.data[DOMAIN]
    coordinator = SomfyDataUpdateCoordinator(
        hass,
        _LOGGER,
        name="somfy device update",
        client=api.ConfigEntrySomfyApi(hass, entry, implementation),
        update_interval=SCAN_INTERVAL,
    )
    data[COORDINATOR] = coordinator

    await coordinator.async_config_entry_first_refresh()

    if all(not bool(device.states) for device in coordinator.data.values()):
        _LOGGER.debug(
            "All devices have assumed state. Update interval has been reduced to: %s",
            SCAN_INTERVAL_ALL_ASSUMED_STATE,
        )
        coordinator.update_interval = SCAN_INTERVAL_ALL_ASSUMED_STATE

    device_registry = dr.async_get(hass)

    hubs = [
        device
        for device in coordinator.data.values()
        if Category.HUB.value in device.categories
    ]

    for hub in hubs:
        device_registry.async_get_or_create(
            config_entry_id=entry.entry_id,
            identifiers={(DOMAIN, hub.id)},
            manufacturer="Somfy",
            name=hub.name,
            model=hub.type,
        )

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #21
0
async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
) -> bool:
    """Create a gateway."""
    tradfri_data: dict[str, Any] = {}
    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = tradfri_data

    factory = await APIFactory.init(
        entry.data[CONF_HOST],
        psk_id=entry.data[CONF_IDENTITY],
        psk=entry.data[CONF_KEY],
    )
    tradfri_data[FACTORY] = factory  # Used for async_unload_entry

    async def on_hass_stop(event: Event) -> None:
        """Close connection when hass stops."""
        await factory.shutdown()

    # Setup listeners
    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop))

    api = factory.request
    gateway = Gateway()

    try:
        gateway_info = await api(gateway.get_gateway_info(),
                                 timeout=TIMEOUT_API)
        devices_commands: Command = await api(gateway.get_devices(),
                                              timeout=TIMEOUT_API)
        devices: list[Device] = await api(devices_commands,
                                          timeout=TIMEOUT_API)

    except RequestError as exc:
        await factory.shutdown()
        raise ConfigEntryNotReady from exc

    dev_reg = dr.async_get(hass)
    dev_reg.async_get_or_create(
        config_entry_id=entry.entry_id,
        connections=set(),
        identifiers={(DOMAIN, entry.data[CONF_GATEWAY_ID])},
        manufacturer="IKEA of Sweden",
        name="Gateway",
        # They just have 1 gateway model. Type is not exposed yet.
        model="E1526",
        sw_version=gateway_info.firmware_version,
    )

    remove_stale_devices(hass, entry, devices)

    # Setup the device coordinators
    coordinator_data = {
        CONF_GATEWAY_ID: gateway,
        KEY_API: api,
        COORDINATOR_LIST: [],
    }

    for device in devices:
        coordinator = TradfriDeviceDataUpdateCoordinator(hass=hass,
                                                         api=api,
                                                         device=device)
        await coordinator.async_config_entry_first_refresh()

        entry.async_on_unload(
            async_dispatcher_connect(hass, SIGNAL_GW,
                                     coordinator.set_hub_available))
        coordinator_data[COORDINATOR_LIST].append(coordinator)

    tradfri_data[COORDINATOR] = coordinator_data

    async def async_keep_alive(now: datetime) -> None:
        if hass.is_stopping:
            return

        gw_status = True
        try:
            await api(gateway.get_gateway_info())
        except RequestError:
            LOGGER.error("Keep-alive failed")
            gw_status = False

        async_dispatcher_send(hass, SIGNAL_GW, gw_status)

    entry.async_on_unload(
        async_track_time_interval(hass, async_keep_alive,
                                  timedelta(seconds=60)))

    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

    return True
Exemple #22
0
    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


async def async_setup_entry(  # noqa: C901
    hass: HomeAssistant, entry: ConfigEntry
) -> bool:
    """Set up Z-Wave JS from a config entry."""
    if use_addon := entry.data.get(CONF_USE_ADDON):
        await async_ensure_addon_running(hass, entry)

    client = ZwaveClient(entry.data[CONF_URL], async_get_clientsession(hass))
    dev_reg = device_registry.async_get(hass)
    ent_reg = entity_registry.async_get(hass)
    entry_hass_data: dict = hass.data[DOMAIN].setdefault(entry.entry_id, {})

    entry_hass_data[DATA_CLIENT] = client
    entry_hass_data[DATA_PLATFORM_SETUP] = {}

    registered_unique_ids: dict[str, dict[str, set[str]]] = defaultdict(dict)
    discovered_value_ids: dict[str, set[str]] = defaultdict(set)

    @callback
    def remove_device(device: device_registry.DeviceEntry) -> None:
        """Remove device from registry."""
        # note: removal of entity registry entry is handled by core
        dev_reg.async_remove_device(device.id)
        registered_unique_ids.pop(device.id, None)
Exemple #23
0
async def test_migrate_zwave_dry_run(
    hass,
    zwave_integration,
    aeon_smart_switch_6,
    multisensor_6,
    integration,
    hass_ws_client,
):
    """Test the zwave to zwave_js migration websocket api dry run."""
    entry = integration
    client = await hass_ws_client(hass)

    await client.send_json({
        ID: 5,
        TYPE: "zwave_js/migrate_zwave",
        ENTRY_ID: entry.entry_id
    })
    msg = await client.receive_json()
    result = msg["result"]

    migration_entity_map = {
        ZWAVE_SWITCH_ENTITY: "switch.smart_switch_6",
        ZWAVE_BATTERY_ENTITY: "sensor.multisensor_6_battery_level",
    }

    assert result["zwave_entity_ids"] == [
        ZWAVE_SWITCH_ENTITY,
        ZWAVE_POWER_ENTITY,
        ZWAVE_SOURCE_NODE_ENTITY,
        ZWAVE_BATTERY_ENTITY,
        ZWAVE_TAMPERING_ENTITY,
    ]
    expected_zwave_js_entities = [
        "switch.smart_switch_6",
        "sensor.multisensor_6_air_temperature",
        "sensor.multisensor_6_illuminance",
        "sensor.multisensor_6_humidity",
        "sensor.multisensor_6_ultraviolet",
        "binary_sensor.multisensor_6_home_security_tampering_product_cover_removed",
        "binary_sensor.multisensor_6_home_security_motion_detection",
        "sensor.multisensor_6_battery_level",
        "binary_sensor.multisensor_6_low_battery_level",
        "light.smart_switch_6",
        "sensor.smart_switch_6_electric_consumed_kwh",
        "sensor.smart_switch_6_electric_consumed_w",
        "sensor.smart_switch_6_electric_consumed_v",
        "sensor.smart_switch_6_electric_consumed_a",
    ]
    # Assert that both lists have the same items without checking order
    assert not set(
        result["zwave_js_entity_ids"]) ^ set(expected_zwave_js_entities)
    assert result["migration_entity_map"] == migration_entity_map

    dev_reg = dr.async_get(hass)

    multisensor_device_entry = dev_reg.async_get_device(identifiers={
        ("zwave_js", "3245146787-52")
    },
                                                        connections=set())
    assert multisensor_device_entry
    assert multisensor_device_entry.name_by_user is None
    assert multisensor_device_entry.area_id is None
    switch_device_entry = dev_reg.async_get_device(identifiers={
        ("zwave_js", "3245146787-102")
    },
                                                   connections=set())
    assert switch_device_entry
    assert switch_device_entry.name_by_user is None
    assert switch_device_entry.area_id is None

    migration_device_map = {
        ZWAVE_SWITCH_DEVICE_ID: switch_device_entry.id,
        ZWAVE_MULTISENSOR_DEVICE_ID: multisensor_device_entry.id,
    }

    assert result["migration_device_map"] == migration_device_map

    assert result["migrated"] is False

    ent_reg = er.async_get(hass)

    # no real migration should have been done
    assert ent_reg.async_is_registered("switch.smart_switch_6")
    assert ent_reg.async_is_registered("sensor.multisensor_6_battery_level")
    assert ent_reg.async_is_registered(
        "sensor.smart_switch_6_electric_consumed_w")

    assert ent_reg.async_is_registered(ZWAVE_SOURCE_NODE_ENTITY)
    source_entry = ent_reg.async_get(ZWAVE_SOURCE_NODE_ENTITY)
    assert source_entry
    assert source_entry.unique_id == ZWAVE_SOURCE_NODE_UNIQUE_ID

    assert ent_reg.async_is_registered(ZWAVE_BATTERY_ENTITY)
    battery_entry = ent_reg.async_get(ZWAVE_BATTERY_ENTITY)
    assert battery_entry
    assert battery_entry.unique_id == ZWAVE_BATTERY_UNIQUE_ID

    assert ent_reg.async_is_registered(ZWAVE_POWER_ENTITY)
    power_entry = ent_reg.async_get(ZWAVE_POWER_ENTITY)
    assert power_entry
    assert power_entry.unique_id == ZWAVE_POWER_UNIQUE_ID

    # check that the zwave config entry has not been removed
    assert hass.config_entries.async_entries("zwave")

    # Check that the zwave integration can be setup after dry run
    zwave_config_entry = zwave_integration
    with patch("openzwave.option.ZWaveOption"), patch(
            "openzwave.network.ZWaveNetwork"):
        assert await hass.config_entries.async_setup(
            zwave_config_entry.entry_id)
Exemple #24
0
async def test_get_actions(hass, device_ias):
    """Test we get the expected actions from a zha device."""

    ieee_address = str(device_ias[0].ieee)

    ha_device_registry = dr.async_get(hass)
    reg_device = ha_device_registry.async_get_device({(DOMAIN, ieee_address)})

    actions = await async_get_device_automations(hass,
                                                 DeviceAutomationType.ACTION,
                                                 reg_device.id)

    expected_actions = [
        {
            "domain": DOMAIN,
            "type": "squawk",
            "device_id": reg_device.id,
            "metadata": {},
        },
        {
            "domain": DOMAIN,
            "type": "warn",
            "device_id": reg_device.id,
            "metadata": {}
        },
        {
            "domain": Platform.SELECT,
            "type": "select_option",
            "device_id": reg_device.id,
            "entity_id":
            "select.fakemanufacturer_fakemodel_e769900a_ias_wd_warningmode",
            "metadata": {
                "secondary": True
            },
        },
        {
            "domain": Platform.SELECT,
            "type": "select_option",
            "device_id": reg_device.id,
            "entity_id":
            "select.fakemanufacturer_fakemodel_e769900a_ias_wd_sirenlevel",
            "metadata": {
                "secondary": True
            },
        },
        {
            "domain": Platform.SELECT,
            "type": "select_option",
            "device_id": reg_device.id,
            "entity_id":
            "select.fakemanufacturer_fakemodel_e769900a_ias_wd_strobelevel",
            "metadata": {
                "secondary": True
            },
        },
        {
            "domain": Platform.SELECT,
            "type": "select_option",
            "device_id": reg_device.id,
            "entity_id":
            "select.fakemanufacturer_fakemodel_e769900a_ias_wd_strobe",
            "metadata": {
                "secondary": True
            },
        },
    ]

    assert actions == expected_actions
Exemple #25
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Bond from a config entry."""
    host = entry.data[CONF_HOST]
    token = entry.data[CONF_ACCESS_TOKEN]
    config_entry_id = entry.entry_id

    bond = Bond(
        host=host,
        token=token,
        timeout=ClientTimeout(total=_API_TIMEOUT),
        session=async_get_clientsession(hass),
    )
    hub = BondHub(bond, host)
    try:
        await hub.setup()
    except ClientResponseError as ex:
        if ex.status == HTTPStatus.UNAUTHORIZED:
            _LOGGER.error("Bond token no longer valid: %s", ex)
            return False
        raise ConfigEntryNotReady from ex
    except (ClientError, AsyncIOTimeoutError, OSError) as error:
        raise ConfigEntryNotReady from error

    bpup_subs = BPUPSubscriptions()
    stop_bpup = await start_bpup(host, bpup_subs)

    @callback
    def _async_stop_event(*_: Any) -> None:
        stop_bpup()

    entry.async_on_unload(_async_stop_event)
    entry.async_on_unload(
        hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, _async_stop_event))
    hass.data.setdefault(DOMAIN, {})
    hass.data[DOMAIN][entry.entry_id] = {
        HUB: hub,
        BPUP_SUBS: bpup_subs,
    }

    if not entry.unique_id:
        hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id)

    assert hub.bond_id is not None
    hub_name = hub.name or hub.bond_id
    device_registry = dr.async_get(hass)
    device_registry.async_get_or_create(
        config_entry_id=config_entry_id,
        identifiers={(DOMAIN, hub.bond_id)},
        manufacturer=BRIDGE_MAKE,
        name=hub_name,
        model=hub.target,
        sw_version=hub.fw_ver,
        hw_version=hub.mcu_ver,
        suggested_area=hub.location,
        configuration_url=f"http://{host}",
    )

    _async_remove_old_device_identifiers(config_entry_id, device_registry, hub)

    hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    return True
Exemple #26
0
    async def _device_connect(self, location: str) -> None:
        """Connect to the device now that it's available."""
        _LOGGER.debug("Connecting to device at %s", location)

        async with self._device_lock:
            if self._device:
                _LOGGER.debug(
                    "Trying to connect when device already connected")
                return

            domain_data = get_domain_data(self.hass)

            # Connect to the base UPNP device
            upnp_device = await domain_data.upnp_factory.async_create_device(
                location)

            # Create/get event handler that is reachable by the device, using
            # the connection's local IP to listen only on the relevant interface
            _, event_ip = await async_get_local_ip(location, self.hass.loop)
            self._event_addr = self._event_addr._replace(host=event_ip)
            event_handler = await domain_data.async_get_event_notifier(
                self._event_addr, self.hass)

            # Create profile wrapper
            self._device = DmrDevice(upnp_device, event_handler)

            self.location = location

            # Subscribe to event notifications
            try:
                self._device.on_event = self._on_event
                await self._device.async_subscribe_services(
                    auto_resubscribe=True)
            except UpnpResponseError as err:
                # Device rejected subscription request. This is OK, variables
                # will be polled instead.
                _LOGGER.debug("Device rejected subscription: %r", err)
            except UpnpError as err:
                # Don't leave the device half-constructed
                self._device.on_event = None
                self._device = None
                await domain_data.async_release_event_notifier(self._event_addr
                                                               )
                _LOGGER.debug(
                    "Error while subscribing during device connect: %r", err)
                raise

        if (not self.registry_entry or not self.registry_entry.config_entry_id
                or self.registry_entry.device_id):
            return

        # Create linked HA DeviceEntry now the information is known.
        dev_reg = device_registry.async_get(self.hass)
        device_entry = dev_reg.async_get_or_create(
            config_entry_id=self.registry_entry.config_entry_id,
            # Connections are based on the root device's UDN, and the DMR
            # embedded device's UDN. They may be the same, if the DMR is the
            # root device.
            connections={
                (
                    device_registry.CONNECTION_UPNP,
                    self._device.profile_device.root_device.udn,
                ),
                (device_registry.CONNECTION_UPNP, self._device.udn),
            },
            identifiers={(DOMAIN, self.unique_id)},
            default_manufacturer=self._device.manufacturer,
            default_model=self._device.model_name,
            default_name=self._device.name,
        )

        # Update entity registry to link to the device
        ent_reg = entity_registry.async_get(self.hass)
        ent_reg.async_get_or_create(
            self.registry_entry.domain,
            self.registry_entry.platform,
            self.unique_id,
            device_id=device_entry.id,
        )
Exemple #27
0
async def test_sensors(
    hass: HomeAssistant,
    init_integration: MockConfigEntry,
) -> None:
    """Test the Forecast.Solar sensors."""
    entry_id = init_integration.entry_id
    entity_registry = er.async_get(hass)
    device_registry = dr.async_get(hass)

    state = hass.states.get("sensor.energy_production_today")
    entry = entity_registry.async_get("sensor.energy_production_today")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_energy_production_today"
    assert state.state == "100"
    assert (state.attributes.get(ATTR_FRIENDLY_NAME) ==
            "Estimated Energy Production - Today")
    assert state.attributes.get(ATTR_STATE_CLASS) is None
    assert state.attributes.get(
        ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY
    assert ATTR_ICON not in state.attributes

    state = hass.states.get("sensor.energy_production_tomorrow")
    entry = entity_registry.async_get("sensor.energy_production_tomorrow")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_energy_production_tomorrow"
    assert state.state == "200"
    assert (state.attributes.get(ATTR_FRIENDLY_NAME) ==
            "Estimated Energy Production - Tomorrow")
    assert state.attributes.get(ATTR_STATE_CLASS) is None
    assert state.attributes.get(
        ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY
    assert ATTR_ICON not in state.attributes

    state = hass.states.get("sensor.power_highest_peak_time_today")
    entry = entity_registry.async_get("sensor.power_highest_peak_time_today")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_power_highest_peak_time_today"
    assert state.state == "2021-06-27 13:00:00+00:00"
    assert state.attributes.get(
        ATTR_FRIENDLY_NAME) == "Highest Power Peak Time - Today"
    assert state.attributes.get(ATTR_STATE_CLASS) is None
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP
    assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
    assert ATTR_ICON not in state.attributes

    state = hass.states.get("sensor.power_highest_peak_time_tomorrow")
    entry = entity_registry.async_get(
        "sensor.power_highest_peak_time_tomorrow")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_power_highest_peak_time_tomorrow"
    assert state.state == "2021-06-27 14:00:00+00:00"
    assert (state.attributes.get(ATTR_FRIENDLY_NAME) ==
            "Highest Power Peak Time - Tomorrow")
    assert state.attributes.get(ATTR_STATE_CLASS) is None
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP
    assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
    assert ATTR_ICON not in state.attributes

    state = hass.states.get("sensor.power_production_now")
    entry = entity_registry.async_get("sensor.power_production_now")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_power_production_now"
    assert state.state == "300"
    assert (state.attributes.get(ATTR_FRIENDLY_NAME) ==
            "Estimated Power Production - Now")
    assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
    assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == POWER_WATT
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_POWER
    assert ATTR_ICON not in state.attributes

    state = hass.states.get("sensor.energy_current_hour")
    entry = entity_registry.async_get("sensor.energy_current_hour")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_energy_current_hour"
    assert state.state == "800"
    assert (state.attributes.get(ATTR_FRIENDLY_NAME) ==
            "Estimated Energy Production - This Hour")
    assert state.attributes.get(ATTR_STATE_CLASS) is None
    assert state.attributes.get(
        ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY
    assert ATTR_ICON not in state.attributes

    state = hass.states.get("sensor.energy_next_hour")
    entry = entity_registry.async_get("sensor.energy_next_hour")
    assert entry
    assert state
    assert entry.unique_id == f"{entry_id}_energy_next_hour"
    assert state.state == "900"
    assert (state.attributes.get(ATTR_FRIENDLY_NAME) ==
            "Estimated Energy Production - Next Hour")
    assert state.attributes.get(ATTR_STATE_CLASS) is None
    assert state.attributes.get(
        ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
    assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_ENERGY
    assert ATTR_ICON not in state.attributes

    assert entry.device_id
    device_entry = device_registry.async_get(entry.device_id)
    assert device_entry
    assert device_entry.identifiers == {(DOMAIN, f"{entry_id}")}
    assert device_entry.manufacturer == "Forecast.Solar"
    assert device_entry.name == "Solar Production Forecast"
    assert device_entry.entry_type == ENTRY_TYPE_SERVICE
    assert not device_entry.model
    assert not device_entry.sw_version
Exemple #28
0
async def test_remove_orphaned_entries_service(hass, aioclient_mock):
    """Test service works and also don't remove more than expected."""
    data = {
        "lights": {
            "1": {
                "name": "Light 1 name",
                "state": {
                    "reachable": True
                },
                "type": "Light",
                "uniqueid": "00:00:00:00:00:00:00:01-00",
            }
        },
        "sensors": {
            "1": {
                "name": "Switch 1",
                "type": "ZHASwitch",
                "state": {
                    "buttonevent": 1000,
                    "gesture": 1
                },
                "config": {
                    "battery": 100
                },
                "uniqueid": "00:00:00:00:00:00:00:03-00",
            },
        },
    }
    with patch.dict(DECONZ_WEB_REQUEST, data):
        config_entry = await setup_deconz_integration(hass, aioclient_mock)

    device_registry = dr.async_get(hass)
    device = device_registry.async_get_or_create(
        config_entry_id=config_entry.entry_id,
        connections={(dr.CONNECTION_NETWORK_MAC, "123")},
    )

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

    entity_registry = er.async_get(hass)
    entity_registry.async_get_or_create(
        SENSOR_DOMAIN,
        DECONZ_DOMAIN,
        "12345",
        suggested_object_id="Orphaned sensor",
        config_entry=config_entry,
        device_id=device.id,
    )

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

    await hass.services.async_call(
        DECONZ_DOMAIN,
        SERVICE_REMOVE_ORPHANED_ENTRIES,
        service_data={CONF_BRIDGE_ID: BRIDGEID},
    )
    await hass.async_block_till_done()

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

    assert (len(
        async_entries_for_config_entry(entity_registry, config_entry.entry_id))
            == 2  # Light and switch battery
            )
Exemple #29
0
        if sub_type is None:
            if info[d_type.value] is None:
                return web.Response(status=HTTPStatus.NOT_FOUND)
            data = await info[d_type.value](hass, config_entry)
            filename = f"{d_type}-{filename}"
            return await _async_get_json_file_response(
                hass, data, filename, config_entry.domain, d_type.value, d_id
            )

        # sub_type handling
        try:
            sub_type = DiagnosticsSubType(sub_type)
        except ValueError:
            return web.Response(status=HTTPStatus.BAD_REQUEST)

        dev_reg = async_get(hass)
        assert sub_id

        if (device := dev_reg.async_get(sub_id)) is None:
            return web.Response(status=HTTPStatus.NOT_FOUND)

        filename += f"-{device.name}-{device.id}"

        if info[sub_type.value] is None:
            return web.Response(status=HTTPStatus.NOT_FOUND)

        data = await info[sub_type.value](hass, config_entry, device)
        return await _async_get_json_file_response(
            hass, data, filename, config_entry.domain, d_type, d_id, sub_type, sub_id
        )
async def test_options_add_remove_device(hass):
    """Test we can add a device."""
    await setup.async_setup_component(hass, "persistent_notification", {})

    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            "host": None,
            "port": None,
            "device": "/dev/tty123",
            "automatic_add": False,
            "devices": {},
        },
        unique_id=DOMAIN,
    )
    entry.add_to_hass(hass)

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": True,
            "event_code": "0b1100cd0213c7f230010f71",
        },
    )

    assert result["type"] == "form"
    assert result["step_id"] == "set_device_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "fire_event": True,
            "signal_repetitions": 5,
            "off_delay": "4"
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    assert entry.data["automatic_add"]

    assert entry.data["devices"]["0b1100cd0213c7f230010f71"]
    assert entry.data["devices"]["0b1100cd0213c7f230010f71"]["fire_event"]
    assert entry.data["devices"]["0b1100cd0213c7f230010f71"][
        "signal_repetitions"] == 5
    assert entry.data["devices"]["0b1100cd0213c7f230010f71"]["off_delay"] == 4

    state = hass.states.get("binary_sensor.ac_213c7f2_48")
    assert state
    assert state.state == "off"
    assert state.attributes.get("friendly_name") == "AC 213c7f2:48"

    device_registry = dr.async_get(hass)
    device_entries = dr.async_entries_for_config_entry(device_registry,
                                                       entry.entry_id)

    assert device_entries[0].id

    result = await hass.config_entries.options.async_init(entry.entry_id)

    assert result["type"] == "form"
    assert result["step_id"] == "prompt_options"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={
            "automatic_add": False,
            "remove_device": [device_entries[0].id],
        },
    )

    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY

    await hass.async_block_till_done()

    assert not entry.data["automatic_add"]

    assert "0b1100cd0213c7f230010f71" not in entry.data["devices"]

    state = hass.states.get("binary_sensor.ac_213c7f2_48")
    assert not state