示例#1
0
def test_human_readable_device_name():
    """Test human readable device name includes the passed data."""
    name = usb.human_readable_device_name(
        "/dev/null",
        "612020FD",
        "Silicon Labs",
        "HubZ Smart Home Controller - HubZ Z-Wave Com Port",
        "10C4",
        "8A2A",
    )
    assert "/dev/null" in name
    assert "612020FD" in name
    assert "Silicon Labs" in name
    assert "HubZ Smart Home Controller - HubZ Z-Wave Com Port"[:26] in name
    assert "10C4" in name
    assert "8A2A" in name

    name = usb.human_readable_device_name(
        "/dev/null",
        "612020FD",
        "Silicon Labs",
        None,
        "10C4",
        "8A2A",
    )
    assert "/dev/null" in name
    assert "612020FD" in name
    assert "Silicon Labs" in name
    assert "10C4" in name
    assert "8A2A" in name
示例#2
0
async def test_flow_user_error(hass: HomeAssistant):
    """Test user initialized flow with unreachable device."""
    port = com_port()
    port_select = usb.human_readable_device_name(
        port.device,
        port.serial_number,
        port.manufacturer,
        port.description,
        port.vid,
        port.pid,
    )
    with patch_config_flow_modem() as modemmock:
        modemmock.side_effect = phone_modem.exceptions.SerialError
        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={CONF_SOURCE: SOURCE_USER},
            data={CONF_DEVICE: port_select})
        assert result["type"] == data_entry_flow.FlowResultType.FORM
        assert result["step_id"] == "user"
        assert result["errors"] == {"base": "cannot_connect"}

        modemmock.side_effect = None
        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            user_input={CONF_DEVICE: port_select},
        )
        assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
        assert result["data"] == {CONF_DEVICE: port.device}
示例#3
0
async def test_flow_user(hass: HomeAssistant):
    """Test user initialized flow."""
    port = com_port()
    port_select = usb.human_readable_device_name(
        port.device,
        port.serial_number,
        port.manufacturer,
        port.description,
        port.vid,
        port.pid,
    )
    with patch_config_flow_modem(), _patch_setup():
        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={CONF_SOURCE: SOURCE_USER},
            data={CONF_DEVICE: port_select},
        )
        assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
        assert result["data"] == {CONF_DEVICE: port.device}

        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={CONF_SOURCE: SOURCE_USER},
            data={CONF_DEVICE: port_select},
        )
        assert result["type"] == data_entry_flow.FlowResultType.ABORT
        assert result["reason"] == "no_devices_found"
def list_ports_as_str(
    serial_ports: list[ListPortInfo], no_usb_option: bool = True
) -> list[str]:
    """
    Represent currently available serial ports as string.

    Adds option to not use usb on top of the list,
    option to use manual path or refresh list at the end.
    """
    ports_as_string: list[str] = []

    if no_usb_option:
        ports_as_string.append(DONT_USE_USB)

    for port in serial_ports:
        ports_as_string.append(
            usb.human_readable_device_name(
                port.device,
                port.serial_number,
                port.manufacturer,
                port.description,
                f"{hex(port.vid)[2:]:0>4}".upper() if port.vid else None,
                f"{hex(port.pid)[2:]:0>4}".upper() if port.pid else None,
            )
        )
    ports_as_string.append(MANUAL_PATH)
    ports_as_string.append(REFRESH_LIST)

    return ports_as_string
示例#5
0
async def test_successful_login_with_usb(
    crownstone_setup: MockFixture,
    pyserial_comports_none_types: MockFixture,
    usb_path: MockFixture,
    hass: HomeAssistant,
):
    """Test flow with correct login and usb configuration."""
    entry_data_with_usb = create_mocked_entry_data_conf(
        email="*****@*****.**",
        password="******",
    )
    entry_options_with_usb = create_mocked_entry_options_conf(
        usb_path="/dev/serial/by-id/crownstone-usb",
        usb_sphere="sphere_id_1",
    )

    result = await start_config_flow(
        hass, get_mocked_crownstone_cloud(create_mocked_spheres(2))
    )
    # should show usb form
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "usb_config"
    assert pyserial_comports_none_types.call_count == 1

    # create a mocked port which should be in
    # the list returned from list_ports_as_str, from .helpers
    port = get_mocked_com_port_none_types()
    port_select = usb.human_readable_device_name(
        port.device,
        port.serial_number,
        port.manufacturer,
        port.description,
        port.vid,
        port.pid,
    )

    # select a port from the list
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], user_input={CONF_USB_PATH: port_select}
    )
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "usb_sphere_config"
    assert pyserial_comports_none_types.call_count == 2
    assert usb_path.call_count == 1

    # select a sphere
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], user_input={CONF_USB_SPHERE: "sphere_name_1"}
    )
    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
    assert result["data"] == entry_data_with_usb
    assert result["options"] == entry_options_with_usb
    assert crownstone_setup.call_count == 1
示例#6
0
def get_usb_ports() -> dict[str, str]:
    """Return a dict of USB ports and their friendly names."""
    ports = list_ports.comports()
    port_descriptions = {}
    for port in ports:
        usb_device = usb.usb_device_from_port(port)
        dev_path = usb.get_serial_by_id(usb_device.device)
        human_name = usb.human_readable_device_name(
            dev_path,
            usb_device.serial_number,
            usb_device.manufacturer,
            usb_device.description,
            usb_device.vid,
            usb_device.pid,
        )
        port_descriptions[dev_path] = human_name
    return port_descriptions
示例#7
0
    async def async_step_usb(self, discovery_info: dict[str,
                                                        str]) -> FlowResult:
        """Handle USB Discovery."""
        if not is_hassio(self.hass):
            return self.async_abort(reason="discovery_requires_supervisor")
        if self._async_current_entries():
            return self.async_abort(reason="already_configured")
        if self._async_in_progress():
            return self.async_abort(reason="already_in_progress")

        vid = discovery_info["vid"]
        pid = discovery_info["pid"]
        serial_number = discovery_info["serial_number"]
        device = discovery_info["device"]
        manufacturer = discovery_info["manufacturer"]
        description = discovery_info["description"]
        # The Nortek sticks are a special case since they
        # have a Z-Wave and a Zigbee radio. We need to reject
        # the Zigbee radio.
        if vid == "10C4" and pid == "8A2A" and "Z-Wave" not in description:
            return self.async_abort(reason="not_zwave_device")
        # Zooz uses this vid/pid, but so do 2652 sticks
        if vid == "10C4" and pid == "EA60" and "2652" in description:
            return self.async_abort(reason="not_zwave_device")

        addon_info = await self._async_get_addon_info()
        if addon_info.state not in (AddonState.NOT_INSTALLED,
                                    AddonState.NOT_RUNNING):
            return self.async_abort(reason="already_configured")

        await self.async_set_unique_id(
            f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}")
        self._abort_if_unique_id_configured()
        dev_path = await self.hass.async_add_executor_job(
            usb.get_serial_by_id, device)
        self.usb_path = dev_path
        self._title = usb.human_readable_device_name(
            dev_path,
            serial_number,
            manufacturer,
            description,
            vid,
            pid,
        )
        self.context["title_placeholders"] = {CONF_NAME: self._title}
        return await self.async_step_usb_confirm()
示例#8
0
    async def async_step_usb(self,
                             discovery_info: usb.UsbServiceInfo) -> FlowResult:
        """Handle USB Discovery."""
        if not is_hassio(self.hass):
            return self.async_abort(reason="discovery_requires_supervisor")
        if self._async_current_entries():
            return self.async_abort(reason="already_configured")
        if self._async_in_progress():
            return self.async_abort(reason="already_in_progress")

        vid = discovery_info.vid
        pid = discovery_info.pid
        serial_number = discovery_info.serial_number
        device = discovery_info.device
        manufacturer = discovery_info.manufacturer
        description = discovery_info.description
        # Zooz uses this vid/pid, but so do 2652 sticks
        if vid == "10C4" and pid == "EA60" and description and "2652" in description:
            return self.async_abort(reason="not_zwave_device")

        addon_info = await self._async_get_addon_info()
        if addon_info.state not in (AddonState.NOT_INSTALLED,
                                    AddonState.NOT_RUNNING):
            return self.async_abort(reason="already_configured")

        await self.async_set_unique_id(
            f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}")
        self._abort_if_unique_id_configured()
        dev_path = await self.hass.async_add_executor_job(
            usb.get_serial_by_id, device)
        self.usb_path = dev_path
        self._title = usb.human_readable_device_name(
            dev_path,
            serial_number,
            manufacturer,
            description,
            vid,
            pid,
        )
        self.context["title_placeholders"] = {
            CONF_NAME: self._title.split(" - ")[0].strip()
        }
        return await self.async_step_usb_confirm()
示例#9
0
    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle a flow initiated by the user."""
        errors: dict[str, str] | None = {}
        if self._async_in_progress():
            return self.async_abort(reason="already_in_progress")
        ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
        existing_devices = [
            entry.data[CONF_DEVICE] for entry in self._async_current_entries()
        ]
        unused_ports = [
            usb.human_readable_device_name(
                port.device,
                port.serial_number,
                port.manufacturer,
                port.description,
                port.vid,
                port.pid,
            )
            for port in ports
            if port.device not in existing_devices
        ]
        if not unused_ports:
            return self.async_abort(reason="no_devices_found")

        if user_input is not None:
            port = ports[unused_ports.index(str(user_input.get(CONF_DEVICE)))]
            dev_path = await self.hass.async_add_executor_job(
                usb.get_serial_by_id, port.device
            )
            errors = await self.validate_device_errors(
                dev_path=dev_path, unique_id=_generate_unique_id(port)
            )
            if errors is None:
                return self.async_create_entry(
                    title=user_input.get(CONF_NAME, DEFAULT_NAME),
                    data={CONF_DEVICE: dev_path},
                )
        user_input = user_input or {}
        schema = vol.Schema({vol.Required(CONF_DEVICE): vol.In(unused_ports)})
        return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
示例#10
0
    async def async_step_usb(self,
                             discovery_info: usb.UsbServiceInfo) -> FlowResult:
        """Handle USB discovery."""
        if self._async_current_entries():
            return self.async_abort(reason="single_instance_allowed")

        dev_path = await self.hass.async_add_executor_job(
            usb.get_serial_by_id, discovery_info.device)
        self._device_path = dev_path
        self._device_name = usb.human_readable_device_name(
            dev_path,
            discovery_info.serial_number,
            discovery_info.manufacturer,
            discovery_info.description,
            discovery_info.vid,
            discovery_info.pid,
        )
        self._set_confirm_only()
        self.context["title_placeholders"] = {CONF_NAME: self._device_name}
        await self.async_set_unique_id(
            config_entries.DEFAULT_DISCOVERY_UNIQUE_ID)
        return await self.async_step_confirm_usb()
class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow."""

    VERSION = 3

    def __init__(self):
        """Initialize flow instance."""
        self._device_path = None
        self._radio_type = None
        self._title = None

    async def async_step_user(self, user_input=None):
        """Handle a zha config flow start."""
        if self._async_current_entries():
            return self.async_abort(reason="single_instance_allowed")

        ports = await self.hass.async_add_executor_job(
            serial.tools.list_ports.comports)
        list_of_ports = [
            f"{p}, s/n: {p.serial_number or 'n/a'}" +
            (f" - {p.manufacturer}" if p.manufacturer else "") for p in ports
        ]

        if not list_of_ports:
            return await self.async_step_pick_radio()

        list_of_ports.append(CONF_MANUAL_PATH)

        if user_input is not None:
            user_selection = user_input[CONF_DEVICE_PATH]
            if user_selection == CONF_MANUAL_PATH:
                return await self.async_step_pick_radio()

            port = ports[list_of_ports.index(user_selection)]
            dev_path = await self.hass.async_add_executor_job(
                usb.get_serial_by_id, port.device)
            auto_detected_data = await detect_radios(dev_path)
            if auto_detected_data is not None:
                title = f"{port.description}, s/n: {port.serial_number or 'n/a'}"
                title += f" - {port.manufacturer}" if port.manufacturer else ""
                return self.async_create_entry(
                    title=title,
                    data=auto_detected_data,
                )

            # did not detect anything
            self._device_path = dev_path
            return await self.async_step_pick_radio()

        schema = vol.Schema(
            {vol.Required(CONF_DEVICE_PATH): vol.In(list_of_ports)})
        return self.async_show_form(step_id="user", data_schema=schema)

    async def async_step_pick_radio(self, user_input=None):
        """Select radio type."""

        if user_input is not None:
            self._radio_type = RadioType.get_by_description(
                user_input[CONF_RADIO_TYPE])
            return await self.async_step_port_config()

        schema = {
            vol.Required(CONF_RADIO_TYPE): vol.In(sorted(RadioType.list()))
        }
        return self.async_show_form(
            step_id="pick_radio",
            data_schema=vol.Schema(schema),
        )

    async def async_step_usb(self,
                             discovery_info: usb.UsbServiceInfo) -> FlowResult:
        """Handle usb discovery."""
        vid = discovery_info.vid
        pid = discovery_info.pid
        serial_number = discovery_info.serial_number
        device = discovery_info.device
        manufacturer = discovery_info.manufacturer
        description = discovery_info.description
        dev_path = await self.hass.async_add_executor_job(
            usb.get_serial_by_id, device)
        unique_id = f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}"
        if current_entry := await self.async_set_unique_id(unique_id):
            self._abort_if_unique_id_configured(
                updates={
                    CONF_DEVICE: {
                        **current_entry.data.get(CONF_DEVICE, {}),
                        CONF_DEVICE_PATH:
                        dev_path,
                    },
                })
        # Check if already configured
        if self._async_current_entries():
            return self.async_abort(reason="single_instance_allowed")

        # If they already have a discovery for deconz
        # we ignore the usb discovery as they probably
        # want to use it there instead
        if self.hass.config_entries.flow.async_progress_by_handler(
                DECONZ_DOMAIN):
            return self.async_abort(reason="not_zha_device")
        for entry in self.hass.config_entries.async_entries(DECONZ_DOMAIN):
            if entry.source != config_entries.SOURCE_IGNORE:
                return self.async_abort(reason="not_zha_device")

        self._device_path = dev_path
        self._title = usb.human_readable_device_name(
            dev_path,
            serial_number,
            manufacturer,
            description,
            vid,
            pid,
        )
        self._set_confirm_only()
        self.context["title_placeholders"] = {CONF_NAME: self._title}
        return await self.async_step_confirm()
async def test_options_flow_setup_usb(serial_mock: MagicMock,
                                      hass: HomeAssistant):
    """Test options flow init."""
    configured_entry_data = create_mocked_entry_data_conf(
        email="*****@*****.**",
        password="******",
    )
    configured_entry_options = create_mocked_entry_options_conf(
        usb_path=None,
        usb_sphere=None,
    )

    # create mocked entry
    entry = MockConfigEntry(
        domain=DOMAIN,
        data=configured_entry_data,
        options=configured_entry_options,
        unique_id="account_id",
    )
    entry.add_to_hass(hass)

    result = await start_options_flow(
        hass, entry.entry_id,
        get_mocked_crownstone_cloud(create_mocked_spheres(2)))

    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "init"

    schema = result["data_schema"].schema
    for schema_key in schema:
        if schema_key == CONF_USE_USB_OPTION:
            assert not schema_key.default()

    # USB is not set up, so this should not be in the options
    assert CONF_USB_SPHERE_OPTION not in schema

    result = await hass.config_entries.options.async_configure(
        result["flow_id"], user_input={CONF_USE_USB_OPTION: True})
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "usb_config_option"

    # create a mocked port
    port = get_mocked_com_port()
    port_select = usb.human_readable_device_name(
        port.device,
        port.serial_number,
        port.manufacturer,
        port.description,
        f"{hex(port.vid)[2:]:0>4}".upper(),
        f"{hex(port.pid)[2:]:0>4}".upper(),
    )

    # select a port from the list
    result = await hass.config_entries.options.async_configure(
        result["flow_id"], user_input={CONF_USB_PATH: port_select})
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["step_id"] == "usb_sphere_config_option"
    assert serial_mock.call_count == 1

    # select a sphere
    result = await hass.config_entries.options.async_configure(
        result["flow_id"], user_input={CONF_USB_SPHERE: "sphere_name_1"})
    assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
    assert result["data"] == create_mocked_entry_options_conf(
        usb_path="/dev/serial/by-id/crownstone-usb", usb_sphere="sphere_id_1")