diff --git a/homeassistant/components/otbr/config_flow.py b/homeassistant/components/otbr/config_flow.py index c8ab8246c8b..67c8412102d 100644 --- a/homeassistant/components/otbr/config_flow.py +++ b/homeassistant/components/otbr/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import asyncio +from contextlib import suppress import logging from typing import cast @@ -12,11 +13,18 @@ from python_otbr_api.tlv_parser import MeshcopTLVType import voluptuous as vol import yarl -from homeassistant.components.hassio import HassioServiceInfo +from homeassistant.components.hassio import ( + HassioAPIError, + HassioServiceInfo, + async_get_addon_info, +) +from homeassistant.components.homeassistant_yellow import hardware as yellow_hardware from homeassistant.components.thread import async_get_preferred_dataset from homeassistant.config_entries import SOURCE_HASSIO, ConfigFlow from homeassistant.const import CONF_URL +from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DEFAULT_CHANNEL, DOMAIN @@ -25,6 +33,32 @@ from .util import get_allowed_channel _LOGGER = logging.getLogger(__name__) +def _is_yellow(hass: HomeAssistant) -> bool: + """Return True if Home Assistant is running on a Home Assistant Yellow.""" + try: + yellow_hardware.async_info(hass) + except HomeAssistantError: + return False + return True + + +async def _title(hass: HomeAssistant, discovery_info: HassioServiceInfo) -> str: + """Return config entry title.""" + device: str | None = None + + with suppress(HassioAPIError): + addon_info = await async_get_addon_info(hass, discovery_info.slug) + device = addon_info.get("options", {}).get("device") + + if _is_yellow(hass) and device == "/dev/TTYAMA1": + return "Home Assistant Yellow" + + if device and "SkyConnect" in device: + return "Home Assistant SkyConnect" + + return discovery_info.name + + class OTBRConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Open Thread Border Router.""" @@ -124,6 +158,6 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(discovery_info.uuid) return self.async_create_entry( - title="Open Thread Border Router", + title=await _title(self.hass, discovery_info), data=config_entry_data, ) diff --git a/tests/components/otbr/test_config_flow.py b/tests/components/otbr/test_config_flow.py index cfb47a28bcf..b6cb0df78cd 100644 --- a/tests/components/otbr/test_config_flow.py +++ b/tests/components/otbr/test_config_flow.py @@ -25,6 +25,23 @@ HASSIO_DATA = hassio.HassioServiceInfo( ) +@pytest.fixture(name="addon_info") +def addon_info_fixture(): + """Mock Supervisor add-on info.""" + with patch( + "homeassistant.components.otbr.config_flow.async_get_addon_info", + ) as addon_info: + addon_info.return_value = { + "available": True, + "hostname": None, + "options": {}, + "state": None, + "update_available": False, + "version": None, + } + yield addon_info + + async def test_user_flow( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: @@ -176,7 +193,7 @@ async def test_user_flow_connect_error(hass: HomeAssistant, error) -> None: async def test_hassio_discovery_flow( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info ) -> None: """Test the hassio discovery flow.""" url = "http://core-silabs-multiprotocol:8081" @@ -195,7 +212,7 @@ async def test_hassio_discovery_flow( } assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Open Thread Border Router" + assert result["title"] == "Silicon Labs Multiprotocol" assert result["data"] == expected_data assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 @@ -203,12 +220,101 @@ async def test_hassio_discovery_flow( config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0] assert config_entry.data == expected_data assert config_entry.options == {} - assert config_entry.title == "Open Thread Border Router" + assert config_entry.title == "Silicon Labs Multiprotocol" + assert config_entry.unique_id == HASSIO_DATA.uuid + + +async def test_hassio_discovery_flow_yellow( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info +) -> None: + """Test the hassio discovery flow.""" + url = "http://core-silabs-multiprotocol:8081" + aioclient_mock.get(f"{url}/node/dataset/active", text="aa") + + addon_info.return_value = { + "available": True, + "hostname": None, + "options": {"device": "/dev/TTYAMA1"}, + "state": None, + "update_available": False, + "version": None, + } + + with patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.otbr.config_flow.yellow_hardware.async_info" + ): + result = await hass.config_entries.flow.async_init( + otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA + ) + + expected_data = { + "url": f"http://{HASSIO_DATA.config['host']}:{HASSIO_DATA.config['port']}", + } + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Home Assistant Yellow" + assert result["data"] == expected_data + assert result["options"] == {} + assert len(mock_setup_entry.mock_calls) == 1 + + 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 Yellow" + assert config_entry.unique_id == HASSIO_DATA.uuid + + +async def test_hassio_discovery_flow_sky_connect( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info +) -> None: + """Test the hassio discovery flow.""" + url = "http://core-silabs-multiprotocol:8081" + aioclient_mock.get(f"{url}/node/dataset/active", text="aa") + + addon_info.return_value = { + "available": True, + "hostname": None, + "options": { + "device": ( + "/dev/serial/by-id/usb-Nabu_Casa_SkyConnect_v1.0_" + "9e2adbd75b8beb119fe564a0f320645d-if00-port0" + ) + }, + "state": None, + "update_available": False, + "version": None, + } + + with patch( + "homeassistant.components.otbr.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result = await hass.config_entries.flow.async_init( + otbr.DOMAIN, context={"source": "hassio"}, data=HASSIO_DATA + ) + + expected_data = { + "url": f"http://{HASSIO_DATA.config['host']}:{HASSIO_DATA.config['port']}", + } + + assert result["type"] == FlowResultType.CREATE_ENTRY + assert result["title"] == "Home Assistant SkyConnect" + assert result["data"] == expected_data + assert result["options"] == {} + assert len(mock_setup_entry.mock_calls) == 1 + + 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" assert config_entry.unique_id == HASSIO_DATA.uuid async def test_hassio_discovery_flow_router_not_setup( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info ) -> None: """Test the hassio discovery flow when the border router has no dataset. @@ -247,7 +353,7 @@ async def test_hassio_discovery_flow_router_not_setup( } assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Open Thread Border Router" + assert result["title"] == "Silicon Labs Multiprotocol" assert result["data"] == expected_data assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 @@ -255,12 +361,12 @@ async def test_hassio_discovery_flow_router_not_setup( config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0] assert config_entry.data == expected_data assert config_entry.options == {} - assert config_entry.title == "Open Thread Border Router" + assert config_entry.title == "Silicon Labs Multiprotocol" assert config_entry.unique_id == HASSIO_DATA.uuid async def test_hassio_discovery_flow_router_not_setup_has_preferred( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, addon_info ) -> None: """Test the hassio discovery flow when the border router has no dataset. @@ -296,7 +402,7 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred( } assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Open Thread Border Router" + assert result["title"] == "Silicon Labs Multiprotocol" assert result["data"] == expected_data assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 @@ -304,7 +410,7 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred( config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0] assert config_entry.data == expected_data assert config_entry.options == {} - assert config_entry.title == "Open Thread Border Router" + assert config_entry.title == "Silicon Labs Multiprotocol" assert config_entry.unique_id == HASSIO_DATA.uuid @@ -312,6 +418,7 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred_2( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, multiprotocol_addon_manager_mock, + addon_info, ) -> None: """Test the hassio discovery flow when the border router has no dataset. @@ -353,7 +460,7 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred_2( } assert result["type"] == FlowResultType.CREATE_ENTRY - assert result["title"] == "Open Thread Border Router" + assert result["title"] == "Silicon Labs Multiprotocol" assert result["data"] == expected_data assert result["options"] == {} assert len(mock_setup_entry.mock_calls) == 1 @@ -361,7 +468,7 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred_2( config_entry = hass.config_entries.async_entries(otbr.DOMAIN)[0] assert config_entry.data == expected_data assert config_entry.options == {} - assert config_entry.title == "Open Thread Border Router" + assert config_entry.title == "Silicon Labs Multiprotocol" assert config_entry.unique_id == HASSIO_DATA.uuid