Support the Home Assistant Connect ZBT-1 (#114213)

This commit is contained in:
puddly
2024-03-27 18:20:48 +01:00
committed by GitHub
parent 63e28f958d
commit fc34453caa
13 changed files with 268 additions and 115 deletions
@@ -19,13 +19,22 @@ from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, MockModule, mock_integration
USB_DATA = usb.UsbServiceInfo(
device="bla_device",
vid="bla_vid",
pid="bla_pid",
serial_number="bla_serial_number",
manufacturer="bla_manufacturer",
description="bla_description",
USB_DATA_SKY = usb.UsbServiceInfo(
device="/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
vid="10C4",
pid="EA60",
serial_number="9e2adbd75b8beb119fe564a0f320645d",
manufacturer="Nabu Casa",
description="SkyConnect v1.0",
)
USB_DATA_ZBT1 = usb.UsbServiceInfo(
device="/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
vid="10C4",
pid="EA60",
serial_number="9e2adbd75b8beb119fe564a0f320645d",
manufacturer="Nabu Casa",
description="Home Assistant Connect ZBT-1",
)
@@ -38,27 +47,36 @@ def config_flow_handler(hass: HomeAssistant) -> Generator[None, None, None]:
yield
async def test_config_flow(hass: HomeAssistant) -> None:
"""Test the config flow."""
@pytest.mark.parametrize(
("usb_data", "title"),
[
(USB_DATA_SKY, "Home Assistant SkyConnect"),
(USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"),
],
)
async def test_config_flow(
usb_data: usb.UsbServiceInfo, title: str, hass: HomeAssistant
) -> None:
"""Test the config flow for SkyConnect."""
with patch(
"homeassistant.components.homeassistant_sky_connect.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "usb"}, data=USB_DATA
DOMAIN, context={"source": "usb"}, data=usb_data
)
expected_data = {
"device": USB_DATA.device,
"vid": USB_DATA.vid,
"pid": USB_DATA.pid,
"serial_number": USB_DATA.serial_number,
"manufacturer": USB_DATA.manufacturer,
"description": USB_DATA.description,
"device": usb_data.device,
"vid": usb_data.vid,
"pid": usb_data.pid,
"serial_number": usb_data.serial_number,
"manufacturer": usb_data.manufacturer,
"description": usb_data.description,
}
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Home Assistant SkyConnect"
assert result["title"] == title
assert result["data"] == expected_data
assert result["options"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@@ -66,51 +84,35 @@ async def test_config_flow(hass: HomeAssistant) -> None:
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry.data == expected_data
assert config_entry.options == {}
assert config_entry.title == "Home Assistant SkyConnect"
assert config_entry.title == title
assert (
config_entry.unique_id
== f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}"
== f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}"
)
async def test_config_flow_unique_id(hass: HomeAssistant) -> None:
"""Test only a single entry is allowed for a dongle."""
# Setup an existing config entry
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={},
title="Home Assistant SkyConnect",
unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}",
)
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.homeassistant_sky_connect.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "usb"}, data=USB_DATA
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
mock_setup_entry.assert_not_called()
async def test_config_flow_multiple_entries(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("usb_data", "title"),
[
(USB_DATA_SKY, "Home Assistant SkyConnect"),
(USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"),
],
)
async def test_config_flow_multiple_entries(
usb_data: usb.UsbServiceInfo, title: str, hass: HomeAssistant
) -> None:
"""Test multiple entries are allowed."""
# Setup an existing config entry
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={},
title="Home Assistant SkyConnect",
unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}",
title=title,
unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}",
)
config_entry.add_to_hass(hass)
usb_data = copy.copy(USB_DATA)
usb_data = copy.copy(usb_data)
usb_data.serial_number = "bla_serial_number_2"
with patch(
@@ -124,19 +126,28 @@ async def test_config_flow_multiple_entries(hass: HomeAssistant) -> None:
assert result["type"] == FlowResultType.CREATE_ENTRY
async def test_config_flow_update_device(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("usb_data", "title"),
[
(USB_DATA_SKY, "Home Assistant SkyConnect"),
(USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"),
],
)
async def test_config_flow_update_device(
usb_data: usb.UsbServiceInfo, title: str, hass: HomeAssistant
) -> None:
"""Test updating device path."""
# Setup an existing config entry
config_entry = MockConfigEntry(
data={},
domain=DOMAIN,
options={},
title="Home Assistant SkyConnect",
unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}",
title=title,
unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}",
)
config_entry.add_to_hass(hass)
usb_data = copy.copy(USB_DATA)
usb_data = copy.copy(usb_data)
usb_data.device = "bla_device_2"
with patch(
@@ -167,7 +178,16 @@ async def test_config_flow_update_device(hass: HomeAssistant) -> None:
assert len(mock_unload_entry.mock_calls) == 1
@pytest.mark.parametrize(
("usb_data", "title"),
[
(USB_DATA_SKY, "Home Assistant SkyConnect"),
(USB_DATA_ZBT1, "Home Assistant ZBT-1"),
],
)
async def test_option_flow_install_multi_pan_addon(
usb_data: usb.UsbServiceInfo,
title: str,
hass: HomeAssistant,
addon_store_info,
addon_info,
@@ -182,17 +202,17 @@ async def test_option_flow_install_multi_pan_addon(
# Setup the config entry
config_entry = MockConfigEntry(
data={
"device": USB_DATA.device,
"vid": USB_DATA.vid,
"pid": USB_DATA.pid,
"serial_number": USB_DATA.serial_number,
"manufacturer": USB_DATA.manufacturer,
"description": USB_DATA.description,
"device": usb_data.device,
"vid": usb_data.vid,
"pid": usb_data.pid,
"serial_number": usb_data.serial_number,
"manufacturer": usb_data.manufacturer,
"description": usb_data.description,
},
domain=DOMAIN,
options={},
title="Home Assistant SkyConnect",
unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}",
title=title,
unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}",
)
config_entry.add_to_hass(hass)
@@ -226,7 +246,7 @@ async def test_option_flow_install_multi_pan_addon(
{
"options": {
"autoflash_firmware": True,
"device": "bla_device",
"device": usb_data.device,
"baudrate": "115200",
"flow_control": True,
}
@@ -254,11 +274,20 @@ def mock_detect_radio_type(radio_type=RadioType.ezsp, ret=True):
return detect
@pytest.mark.parametrize(
("usb_data", "title"),
[
(USB_DATA_SKY, "Home Assistant SkyConnect"),
(USB_DATA_ZBT1, "Home Assistant Connect ZBT-1"),
],
)
@patch(
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
mock_detect_radio_type(),
)
async def test_option_flow_install_multi_pan_addon_zha(
usb_data: usb.UsbServiceInfo,
title: str,
hass: HomeAssistant,
addon_store_info,
addon_info,
@@ -273,22 +302,22 @@ async def test_option_flow_install_multi_pan_addon_zha(
# Setup the config entry
config_entry = MockConfigEntry(
data={
"device": USB_DATA.device,
"vid": USB_DATA.vid,
"pid": USB_DATA.pid,
"serial_number": USB_DATA.serial_number,
"manufacturer": USB_DATA.manufacturer,
"description": USB_DATA.description,
"device": usb_data.device,
"vid": usb_data.vid,
"pid": usb_data.pid,
"serial_number": usb_data.serial_number,
"manufacturer": usb_data.manufacturer,
"description": usb_data.description,
},
domain=DOMAIN,
options={},
title="Home Assistant SkyConnect",
unique_id=f"{USB_DATA.vid}:{USB_DATA.pid}_{USB_DATA.serial_number}_{USB_DATA.manufacturer}_{USB_DATA.description}",
title=title,
unique_id=f"{usb_data.vid}:{usb_data.pid}_{usb_data.serial_number}_{usb_data.manufacturer}_{usb_data.description}",
)
config_entry.add_to_hass(hass)
zha_config_entry = MockConfigEntry(
data={"device": {"path": "bla_device"}, "radio_type": "ezsp"},
data={"device": {"path": usb_data.device}, "radio_type": "ezsp"},
domain=ZHA_DOMAIN,
options={},
title="Yellow",
@@ -325,7 +354,7 @@ async def test_option_flow_install_multi_pan_addon_zha(
{
"options": {
"autoflash_firmware": True,
"device": "bla_device",
"device": usb_data.device,
"baudrate": "115200",
"flow_control": True,
}
@@ -0,0 +1,27 @@
"""Test the Home Assistant SkyConnect constants."""
import pytest
from homeassistant.components.homeassistant_sky_connect.const import HardwareVariant
@pytest.mark.parametrize(
("usb_product_name", "expected_variant"),
[
("SkyConnect v1.0", HardwareVariant.SKYCONNECT),
("Home Assistant Connect ZBT-1", HardwareVariant.CONNECT_ZBT1),
],
)
def test_hardware_variant(
usb_product_name: str, expected_variant: HardwareVariant
) -> None:
"""Test hardware variant parsing."""
assert HardwareVariant.from_usb_product_name(usb_product_name) == expected_variant
def test_hardware_variant_invalid():
"""Test hardware variant parsing with an invalid product."""
with pytest.raises(
ValueError, match=r"^Unknown SkyConnect product name: Some other product$"
):
HardwareVariant.from_usb_product_name("Some other product")
@@ -10,21 +10,21 @@ from tests.common import MockConfigEntry
from tests.typing import WebSocketGenerator
CONFIG_ENTRY_DATA = {
"device": "bla_device",
"vid": "bla_vid",
"pid": "bla_pid",
"serial_number": "bla_serial_number",
"manufacturer": "bla_manufacturer",
"description": "bla_description",
"device": "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
"vid": "10C4",
"pid": "EA60",
"serial_number": "9e2adbd75b8beb119fe564a0f320645d",
"manufacturer": "Nabu Casa",
"description": "SkyConnect v1.0",
}
CONFIG_ENTRY_DATA_2 = {
"device": "bla_device_2",
"vid": "bla_vid_2",
"pid": "bla_pid_2",
"serial_number": "bla_serial_number_2",
"manufacturer": "bla_manufacturer_2",
"description": "bla_description_2",
"device": "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
"vid": "10C4",
"pid": "EA60",
"serial_number": "9e2adbd75b8beb119fe564a0f320645d",
"manufacturer": "Nabu Casa",
"description": "Home Assistant Connect ZBT-1",
}
@@ -48,7 +48,7 @@ async def test_hardware_info(
data=CONFIG_ENTRY_DATA_2,
domain=DOMAIN,
options={},
title="Home Assistant SkyConnect",
title="Home Assistant Connect ZBT-1",
unique_id="unique_2",
)
config_entry_2.add_to_hass(hass)
@@ -72,11 +72,11 @@ async def test_hardware_info(
"board": None,
"config_entries": [config_entry.entry_id],
"dongle": {
"vid": "bla_vid",
"pid": "bla_pid",
"serial_number": "bla_serial_number",
"manufacturer": "bla_manufacturer",
"description": "bla_description",
"vid": "10C4",
"pid": "EA60",
"serial_number": "9e2adbd75b8beb119fe564a0f320645d",
"manufacturer": "Nabu Casa",
"description": "SkyConnect v1.0",
},
"name": "Home Assistant SkyConnect",
"url": "https://skyconnect.home-assistant.io/documentation/",
@@ -85,13 +85,13 @@ async def test_hardware_info(
"board": None,
"config_entries": [config_entry_2.entry_id],
"dongle": {
"vid": "bla_vid_2",
"pid": "bla_pid_2",
"serial_number": "bla_serial_number_2",
"manufacturer": "bla_manufacturer_2",
"description": "bla_description_2",
"vid": "10C4",
"pid": "EA60",
"serial_number": "9e2adbd75b8beb119fe564a0f320645d",
"manufacturer": "Nabu Casa",
"description": "Home Assistant Connect ZBT-1",
},
"name": "Home Assistant SkyConnect",
"name": "Home Assistant Connect ZBT-1",
"url": "https://skyconnect.home-assistant.io/documentation/",
},
]
+21 -11
View File
@@ -280,8 +280,25 @@ async def test_hassio_discovery_flow_yellow(
assert config_entry.unique_id == HASSIO_DATA.uuid
@pytest.mark.parametrize(
("device", "title"),
[
(
"/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
"Home Assistant SkyConnect (Silicon Labs Multiprotocol)",
),
(
"/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0",
"Home Assistant Connect ZBT-1 (Silicon Labs Multiprotocol)",
),
],
)
async def test_hassio_discovery_flow_sky_connect(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info
device: str,
title: str,
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
addon_info,
) -> None:
"""Test the hassio discovery flow."""
url = "http://core-silabs-multiprotocol:8081"
@@ -290,12 +307,7 @@ async def test_hassio_discovery_flow_sky_connect(
addon_info.return_value = {
"available": True,
"hostname": None,
"options": {
"device": (
"/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_"
"9e2adbd75b8beb119fe564a0f320645d-if00-port0"
)
},
"options": {"device": device},
"state": None,
"update_available": False,
"version": None,
@@ -314,7 +326,7 @@ async def test_hassio_discovery_flow_sky_connect(
}
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Home Assistant SkyConnect (Silicon Labs Multiprotocol)"
assert result["title"] == title
assert result["data"] == expected_data
assert result["options"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@@ -322,9 +334,7 @@ async def test_hassio_discovery_flow_sky_connect(
config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0]
assert config_entry.data == expected_data
assert config_entry.options == {}
assert (
config_entry.title == "Home Assistant SkyConnect (Silicon Labs Multiprotocol)"
)
assert config_entry.title == title
assert config_entry.unique_id == HASSIO_DATA.uuid
+17
View File
@@ -38,6 +38,7 @@ from tests.common import MockConfigEntry
from tests.typing import ClientSessionGenerator
SKYCONNECT_DEVICE = "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_9e2adbd75b8beb119fe564a0f320645d-if00-port0"
CONNECT_ZBT1_DEVICE = "/dev/serial/by-id/usb-Nabu_Casa_Home_Assistant_Connect_ZBT-1_9e2adbd75b8beb119fe564a0f320645d-if00-port0"
def set_flasher_app_type(app_type: ApplicationType) -> Callable[[Flasher], None]:
@@ -66,6 +67,22 @@ def test_detect_radio_hardware(hass: HomeAssistant) -> None:
)
skyconnect_config_entry.add_to_hass(hass)
connect_zbt1_config_entry = MockConfigEntry(
data={
"device": CONNECT_ZBT1_DEVICE,
"vid": "10C4",
"pid": "EA60",
"serial_number": "3c0ed67c628beb11b1cd64a0f320645d",
"manufacturer": "Nabu Casa",
"description": "Home Assistant Connect ZBT-1",
},
domain=SKYCONNECT_DOMAIN,
options={},
title="Home Assistant Connect ZBT-1",
)
connect_zbt1_config_entry.add_to_hass(hass)
assert _detect_radio_hardware(hass, CONNECT_ZBT1_DEVICE) == HardwareType.SKYCONNECT
assert _detect_radio_hardware(hass, SKYCONNECT_DEVICE) == HardwareType.SKYCONNECT
assert (
_detect_radio_hardware(hass, SKYCONNECT_DEVICE + "_foo") == HardwareType.OTHER