Add velbus USB dicovery (#62596)

This commit is contained in:
Maikel Punie 2021-12-31 20:44:35 +01:00 committed by GitHub
parent c65a50bd2e
commit d7f0ad29df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 6 deletions

View file

@ -8,6 +8,7 @@ from velbusaio.exceptions import VelbusConnectionFailed
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import usb
from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
@ -32,6 +33,8 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the velbus config flow."""
self._errors: dict[str, str] = {}
self._device: str = ""
self._title: str = ""
def _create_device(self, name: str, prt: str) -> FlowResult:
"""Create an entry async."""
@ -50,9 +53,7 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def _prt_in_configuration_exists(self, prt: str) -> bool:
"""Return True if port exists in configuration."""
if prt in velbus_entries(self.hass):
return True
return False
return prt in velbus_entries(self.hass)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -82,3 +83,37 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
),
errors=self._errors,
)
async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult:
"""Handle USB Discovery."""
await self.async_set_unique_id(
f"{discovery_info.vid}:{discovery_info.pid}_{discovery_info.serial_number}_{discovery_info.manufacturer}_{discovery_info.description}"
)
dev_path = await self.hass.async_add_executor_job(
usb.get_serial_by_id, discovery_info.device
)
# check if this device is not already configured
if self._prt_in_configuration_exists(dev_path):
return self.async_abort(reason="already_configured")
# check if we can make a valid velbus connection
if not await self._test_connection(dev_path):
return self.async_abort(reason="cannot_connect")
# store the data for the config step
self._device = dev_path
self._title = "Velbus USB"
# call the config step
self._set_confirm_only()
return await self.async_step_discovery_confirm()
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle Discovery confirmation."""
if user_input is not None:
return self._create_device(self._title, self._device)
return self.async_show_form(
step_id="discovery_confirm",
description_placeholders={CONF_NAME: self._title},
data_schema=vol.Schema({}),
)

View file

@ -5,5 +5,24 @@
"requirements": ["velbus-aio==2021.11.7"],
"config_flow": true,
"codeowners": ["@Cereal2nd", "@brefra"],
"iot_class": "local_push"
"dependencies": ["usb"],
"iot_class": "local_push",
"usb": [
{
"vid": "10CF",
"pid": "0B1B"
},
{
"vid": "10CF",
"pid": "0516"
},
{
"vid": "10CF",
"pid": "0517"
},
{
"vid": "10CF",
"pid": "0518"
}
]
}

View file

@ -11,6 +11,26 @@ USB = [
"vid": "0572",
"pid": "1340"
},
{
"domain": "velbus",
"vid": "10CF",
"pid": "0B1B"
},
{
"domain": "velbus",
"vid": "10CF",
"pid": "0516"
},
{
"domain": "velbus",
"vid": "10CF",
"pid": "0517"
},
{
"domain": "velbus",
"vid": "10CF",
"pid": "0518"
},
{
"domain": "zha",
"vid": "10C4",

View file

@ -1,16 +1,41 @@
"""Tests for the Velbus config flow."""
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import serial.tools.list_ports
from velbusaio.exceptions import VelbusConnectionFailed
from homeassistant import data_entry_flow
from homeassistant.components import usb
from homeassistant.components.velbus import config_flow
from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.components.velbus.const import DOMAIN
from homeassistant.config_entries import SOURCE_USB
from homeassistant.const import CONF_NAME, CONF_PORT, CONF_SOURCE
from homeassistant.core import HomeAssistant
from .const import PORT_SERIAL, PORT_TCP
from tests.common import MockConfigEntry
DISCOVERY_INFO = usb.UsbServiceInfo(
device=PORT_SERIAL,
pid="10CF",
vid="0B1B",
serial_number="1234",
description="Velbus VMB1USB",
manufacturer="Velleman",
)
def com_port():
"""Mock of a serial port."""
port = serial.tools.list_ports_common.ListPortInfo(PORT_SERIAL)
port.serial_number = "1234"
port.manufacturer = "Virtual serial port"
port.device = PORT_SERIAL
port.description = "Some serial port"
return port
@pytest.fixture(autouse=True)
def override_async_setup_entry() -> AsyncMock:
@ -85,3 +110,49 @@ async def test_abort_if_already_setup(hass: HomeAssistant):
result = await flow.async_step_user({CONF_PORT: PORT_TCP, CONF_NAME: "velbus test"})
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"port": "already_configured"}
@pytest.mark.usefixtures("controller")
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
async def test_flow_usb(hass: HomeAssistant):
"""Test usb discovery flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USB},
data=DISCOVERY_INFO,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "discovery_confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
# test an already configured discovery
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_PORT: PORT_SERIAL},
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USB},
data=DISCOVERY_INFO,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("controller_connection_failed")
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
async def test_flow_usb_failed(hass: HomeAssistant):
"""Test usb discovery flow with a failed velbus test."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USB},
data=DISCOVERY_INFO,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "cannot_connect"