Add Insteon USB discovery (#70306)

* Add Insteon USB discovery

* Update tests/components/insteon/test_config_flow.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Black

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Paulus Schoutsen 2022-04-20 10:07:35 -07:00 committed by GitHub
parent 415c8b4ab8
commit b049ffca23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 1 deletions

View file

@ -1,19 +1,24 @@
"""Test config flow for Insteon."""
from __future__ import annotations
import logging
from pyinsteon import async_connect
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import usb
from homeassistant.const import (
CONF_ADDRESS,
CONF_DEVICE,
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import (
@ -107,6 +112,9 @@ def _remove_x10(device, options):
class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Insteon config flow handler."""
_device_path: str | None = None
_device_name: str | None = None
@staticmethod
@callback
def async_get_options_flow(config_entry):
@ -177,6 +185,38 @@ class InsteonFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="cannot_connect")
return self.async_create_entry(title="", data=import_info)
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()
async def async_step_confirm_usb(self, user_input=None):
"""Confirm a discovery."""
if user_input is not None:
return await self.async_step_plm({CONF_DEVICE: self._device_path})
return self.async_show_form(
step_id="confirm_usb",
description_placeholders={CONF_NAME: self._device_name},
)
class InsteonOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle an Insteon options flow."""

View file

@ -6,5 +6,11 @@
"codeowners": ["@teharris1"],
"config_flow": true,
"iot_class": "local_push",
"loggers": ["pyinsteon", "pypubsub"]
"loggers": ["pyinsteon", "pypubsub"],
"after_dependencies": ["usb"],
"usb": [
{
"vid": "10BF"
}
]
}

View file

@ -1,5 +1,6 @@
{
"config": {
"flow_title": "{name}",
"step": {
"user": {
"description": "Select the Insteon modem type.",
@ -31,6 +32,9 @@
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
},
"confirm_usb": {
"description": "Do you want to setup {name}?"
}
},
"error": {

View file

@ -8,7 +8,11 @@
"cannot_connect": "Failed to connect",
"select_single": "Select one option."
},
"flow_title": "{name}",
"step": {
"confirm_usb": {
"description": "Do you want to setup {name}?"
},
"hubv1": {
"data": {
"host": "IP Address",

View file

@ -6,6 +6,10 @@ To update, run python3 -m script.hassfest
# fmt: off
USB = [
{
"domain": "insteon",
"vid": "10BF"
},
{
"domain": "modem_callerid",
"vid": "0572",

View file

@ -3,6 +3,7 @@
from unittest.mock import patch
from homeassistant import config_entries, data_entry_flow
from homeassistant.components import usb
from homeassistant.components.insteon.config_flow import (
HUB1,
HUB2,
@ -594,3 +595,56 @@ async def test_options_override_bad_data(hass: HomeAssistant):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"base": "input_error"}
async def test_discovery_via_usb(hass):
"""Test usb flow."""
discovery_info = usb.UsbServiceInfo(
device="/dev/ttyINSTEON",
pid="AAAA",
vid="AAAA",
serial_number="1234",
description="insteon radio",
manufacturer="test",
)
result = await hass.config_entries.flow.async_init(
"insteon", context={"source": config_entries.SOURCE_USB}, data=discovery_info
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "confirm_usb"
with patch("homeassistant.components.insteon.config_flow.async_connect"), patch(
"homeassistant.components.insteon.async_setup_entry", return_value=True
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={}
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result2["data"] == {"device": "/dev/ttyINSTEON"}
async def test_discovery_via_usb_already_setup(hass):
"""Test usb flow -- already setup."""
MockConfigEntry(
domain=DOMAIN, data={CONF_DEVICE: {CONF_DEVICE: "/dev/ttyUSB1"}}
).add_to_hass(hass)
discovery_info = usb.UsbServiceInfo(
device="/dev/ttyINSTEON",
pid="AAAA",
vid="AAAA",
serial_number="1234",
description="insteon radio",
manufacturer="test",
)
result = await hass.config_entries.flow.async_init(
"insteon", context={"source": config_entries.SOURCE_USB}, data=discovery_info
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "single_instance_allowed"