Add options flow to enable multiprotocol support on sky connect (#82525)
This commit is contained in:
parent
16fc2972d3
commit
b7652c78ee
23 changed files with 962 additions and 95 deletions
|
@ -98,6 +98,10 @@ class BaseMultiPanFlow(FlowHandler):
|
|||
being migrated.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _hardware_name(self) -> str:
|
||||
"""Return the name of the hardware."""
|
||||
|
||||
@abstractmethod
|
||||
def _zha_name(self) -> str:
|
||||
"""Return the ZHA name."""
|
||||
|
@ -254,6 +258,7 @@ class OptionsFlowHandler(BaseMultiPanFlow, config_entries.OptionsFlow):
|
|||
data_schema=vol.Schema(
|
||||
{vol.Required(CONF_ENABLE_MULTI_PAN, default=False): bool}
|
||||
),
|
||||
description_placeholders={"hardware_name": self._hardware_name()},
|
||||
)
|
||||
if not user_input[CONF_ENABLE_MULTI_PAN]:
|
||||
return self.async_create_entry(title="", data={})
|
||||
|
@ -285,10 +290,8 @@ class OptionsFlowHandler(BaseMultiPanFlow, config_entries.OptionsFlow):
|
|||
"name": self._zha_name(),
|
||||
"port": {
|
||||
"path": get_zigbee_socket(self.hass, addon_info),
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
"radio_type": "ezsp",
|
||||
},
|
||||
"old_discovery_info": await self._async_zha_physical_discovery(),
|
||||
}
|
||||
|
|
43
homeassistant/components/homeassistant_hardware/strings.json
Normal file
43
homeassistant/components/homeassistant_hardware/strings.json
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"silabs_multiprotocol_hardware": {
|
||||
"options": {
|
||||
"step": {
|
||||
"addon_not_installed": {
|
||||
"title": "Enable multiprotocol support on the IEEE 802.15.4 radio",
|
||||
"description": "When multiprotocol support is enabled, the {hardware_name}'s IEEE 802.15.4 radio can be used for both Zigbee and Thread (used by Matter) at the same time. If the radio is already used by the ZHA Zigbee integration, ZHA will be reconfigured to use the multiprotocol firmware.\n\nNote: This is an experimental feature.",
|
||||
"data": {
|
||||
"enable_multi_pan": "Enable multiprotocol support"
|
||||
}
|
||||
},
|
||||
"addon_installed_other_device": {
|
||||
"title": "Multiprotocol support is already enabled for another device"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on installation has started"
|
||||
},
|
||||
"show_revert_guide": {
|
||||
"title": "Multiprotocol support is enabled for this device",
|
||||
"description": "If you want to change to Zigbee only firmware, please complete the following manual steps:\n\n * Remove the Silicon Labs Multiprotocol addon\n\n * Flash the Zigbee only firmware, follow the guide at https://github.com/NabuCasa/silabs-firmware/wiki/Flash-Silicon-Labs-radio-firmware-manually.\n\n * Reconfigure ZHA to migrate settings to the reflashed radio"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on is starting."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"addon_info_failed": "Failed to get Silicon Labs Multiprotocol add-on info.",
|
||||
"addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.",
|
||||
"addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.",
|
||||
"addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.",
|
||||
"not_hassio": "The hardware options can only be configured on HassOS installations.",
|
||||
"zha_migration_failed": "The ZHA migration did not succeed."
|
||||
},
|
||||
"progress": {
|
||||
"install_addon": "Please wait while the Silicon Labs Multiprotocol add-on installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the Silicon Labs Multiprotocol add-on start completes. This may take some seconds."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"silabs_multiprotocol_hardware": {
|
||||
"options": {
|
||||
"abort": {
|
||||
"addon_info_failed": "Failed to get Silicon Labs Multiprotocol add-on info.",
|
||||
"addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.",
|
||||
"addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.",
|
||||
"addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.",
|
||||
"not_hassio": "The hardware options can only be configured on HassOS installations.",
|
||||
"zha_migration_failed": "The ZHA migration did not succeed."
|
||||
},
|
||||
"error": {
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"progress": {
|
||||
"install_addon": "Please wait while the Silicon Labs Multiprotocol add-on installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the Silicon Labs Multiprotocol add-on start completes. This may take some seconds."
|
||||
},
|
||||
"step": {
|
||||
"addon_installed_other_device": {
|
||||
"title": "Multiprotocol support is already enabled for another device"
|
||||
},
|
||||
"addon_not_installed": {
|
||||
"data": {
|
||||
"enable_multi_pan": "Enable multiprotocol support"
|
||||
},
|
||||
"description": "When multiprotocol support is enabled, the {hardware_name}'s IEEE 802.15.4 radio can be used for both Zigbee and Thread (used by Matter) at the same time. If the radio is already used by the ZHA Zigbee integration, ZHA will be reconfigured to use the multiprotocol firmware.\n\nNote: This is an experimental feature.",
|
||||
"title": "Enable multiprotocol support on the IEEE 802.15.4 radio"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on installation has started"
|
||||
},
|
||||
"show_revert_guide": {
|
||||
"description": "If you want to change to Zigbee only firmware, please complete the following manual steps:\n\n * Remove the Silicon Labs Multiprotocol addon\n\n * Flash the Zigbee only firmware, follow the guide at https://github.com/NabuCasa/silabs-firmware/wiki/Flash-Silicon-Labs-radio-firmware-manually.\n\n * Reconfigure ZHA to migrate settings to the reflashed radio",
|
||||
"title": "Multiprotocol support is enabled for this device"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on is starting."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,63 @@
|
|||
"""The Home Assistant Sky Connect integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components import usb
|
||||
from homeassistant.components.hassio import (
|
||||
AddonError,
|
||||
AddonInfo,
|
||||
AddonManager,
|
||||
AddonState,
|
||||
is_hassio,
|
||||
)
|
||||
from homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon import (
|
||||
get_addon_manager,
|
||||
get_zigbee_socket,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .util import get_usb_service_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _multi_pan_addon_info(hass, entry: ConfigEntry) -> AddonInfo | None:
|
||||
"""Return AddonInfo if the multi-PAN addon is enabled for our SkyConnect."""
|
||||
if not is_hassio(hass):
|
||||
return None
|
||||
|
||||
addon_manager: AddonManager = get_addon_manager(hass)
|
||||
try:
|
||||
addon_info: AddonInfo = await addon_manager.async_get_addon_info()
|
||||
except AddonError as err:
|
||||
_LOGGER.error(err)
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
# Start the addon if it's not started
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
await addon_manager.async_start_addon()
|
||||
|
||||
if addon_info.state not in (AddonState.NOT_INSTALLED, AddonState.RUNNING):
|
||||
_LOGGER.debug(
|
||||
"Multi pan addon in state %s, delaying yellow config entry setup",
|
||||
addon_info.state,
|
||||
)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return None
|
||||
|
||||
usb_dev = entry.data["device"]
|
||||
dev_path = await hass.async_add_executor_job(usb.get_serial_by_id, usb_dev)
|
||||
|
||||
if addon_info.options["device"] != dev_path:
|
||||
return None
|
||||
|
||||
return addon_info
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
@ -24,19 +75,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
# The USB dongle is not plugged in
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
usb_info = usb.UsbServiceInfo(
|
||||
device=entry.data["device"],
|
||||
vid=entry.data["vid"],
|
||||
pid=entry.data["pid"],
|
||||
serial_number=entry.data["serial_number"],
|
||||
manufacturer=entry.data["manufacturer"],
|
||||
description=entry.data["description"],
|
||||
)
|
||||
addon_info = await _multi_pan_addon_info(hass, entry)
|
||||
|
||||
if not addon_info:
|
||||
usb_info = get_usb_service_info(entry)
|
||||
await hass.config_entries.flow.async_init(
|
||||
"zha",
|
||||
context={"source": "usb"},
|
||||
data=usb_info,
|
||||
)
|
||||
return True
|
||||
|
||||
hw_discovery_data = {
|
||||
"name": "Sky Connect Multi-PAN",
|
||||
"port": {
|
||||
"path": get_zigbee_socket(hass, addon_info),
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
await hass.config_entries.flow.async_init(
|
||||
"zha",
|
||||
context={"source": "usb"},
|
||||
data=usb_info,
|
||||
context={"source": "hardware"},
|
||||
data=hw_discovery_data,
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
"""Config flow for the Home Assistant Sky Connect integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components import usb
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.components.homeassistant_hardware import silabs_multiprotocol_addon
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .const import DOMAIN
|
||||
from .util import get_usb_service_info
|
||||
|
||||
|
||||
class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
@ -13,6 +18,14 @@ class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
|
||||
VERSION = 1
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> HomeAssistantSkyConnectOptionsFlow:
|
||||
"""Return the options flow."""
|
||||
return HomeAssistantSkyConnectOptionsFlow(config_entry)
|
||||
|
||||
async def async_step_usb(self, discovery_info: usb.UsbServiceInfo) -> FlowResult:
|
||||
"""Handle usb discovery."""
|
||||
device = discovery_info.device
|
||||
|
@ -35,3 +48,35 @@ class HomeAssistantSkyConnectConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
"description": description,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class HomeAssistantSkyConnectOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandler):
|
||||
"""Handle an option flow for Home Assistant Sky Connect."""
|
||||
|
||||
async def _async_serial_port_settings(
|
||||
self,
|
||||
) -> silabs_multiprotocol_addon.SerialPortSettings:
|
||||
"""Return the radio serial port settings."""
|
||||
usb_dev = self.config_entry.data["device"]
|
||||
dev_path = await self.hass.async_add_executor_job(usb.get_serial_by_id, usb_dev)
|
||||
return silabs_multiprotocol_addon.SerialPortSettings(
|
||||
device=dev_path,
|
||||
baudrate="115200",
|
||||
flow_control=True,
|
||||
)
|
||||
|
||||
async def _async_zha_physical_discovery(self) -> dict[str, Any]:
|
||||
"""Return ZHA discovery data when multiprotocol FW is not used.
|
||||
|
||||
Passed to ZHA do determine if the ZHA config entry is connected to the radio
|
||||
being migrated.
|
||||
"""
|
||||
return {"usb": get_usb_service_info(self.config_entry)}
|
||||
|
||||
def _zha_name(self) -> str:
|
||||
"""Return the ZHA name."""
|
||||
return "Sky Connect Multi-PAN"
|
||||
|
||||
def _hardware_name(self) -> str:
|
||||
"""Return the name of the hardware."""
|
||||
return "Home Assistant Sky Connect"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Home Assistant Sky Connect",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/homeassistant_sky_connect",
|
||||
"dependencies": ["hardware", "usb"],
|
||||
"dependencies": ["hardware", "usb", "homeassistant_hardware"],
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"integration_type": "hardware",
|
||||
"usb": [
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"addon_not_installed": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_not_installed::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_not_installed::description%]",
|
||||
"data": {
|
||||
"enable_multi_pan": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_not_installed::data::enable_multi_pan%]"
|
||||
}
|
||||
},
|
||||
"addon_installed_other_device": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_installed_other_device::title%]"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::install_addon::title%]"
|
||||
},
|
||||
"show_revert_guide": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::show_revert_guide::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::show_revert_guide::description%]"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::start_addon::title%]"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"unknown": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"addon_info_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_info_failed%]",
|
||||
"addon_install_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_install_failed%]",
|
||||
"addon_set_config_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_set_config_failed%]",
|
||||
"addon_start_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_start_failed%]",
|
||||
"not_hassio": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::not_hassio%]",
|
||||
"zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]"
|
||||
},
|
||||
"progress": {
|
||||
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
||||
"start_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::start_addon%]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"options": {
|
||||
"abort": {
|
||||
"addon_info_failed": "Failed to get Silicon Labs Multiprotocol add-on info.",
|
||||
"addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.",
|
||||
"addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.",
|
||||
"addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.",
|
||||
"not_hassio": "The hardware options can only be configured on HassOS installations.",
|
||||
"zha_migration_failed": "The ZHA migration did not succeed."
|
||||
},
|
||||
"error": {
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"progress": {
|
||||
"install_addon": "Please wait while the Silicon Labs Multiprotocol add-on installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the Silicon Labs Multiprotocol add-on start completes. This may take some seconds."
|
||||
},
|
||||
"step": {
|
||||
"addon_installed_other_device": {
|
||||
"title": "Multiprotocol support is already enabled for another device"
|
||||
},
|
||||
"addon_not_installed": {
|
||||
"data": {
|
||||
"enable_multi_pan": "Enable multiprotocol support"
|
||||
},
|
||||
"description": "When multiprotocol support is enabled, the {hardware_name}'s IEEE 802.15.4 radio can be used for both Zigbee and Thread (used by Matter) at the same time. If the radio is already used by the ZHA Zigbee integration, ZHA will be reconfigured to use the multiprotocol firmware.\n\nNote: This is an experimental feature.",
|
||||
"title": "Enable multiprotocol support on the IEEE 802.15.4 radio"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on installation has started"
|
||||
},
|
||||
"show_revert_guide": {
|
||||
"description": "If you want to change to Zigbee only firmware, please complete the following manual steps:\n\n * Remove the Silicon Labs Multiprotocol addon\n\n * Flash the Zigbee only firmware, follow the guide at https://github.com/NabuCasa/silabs-firmware/wiki/Flash-Silicon-Labs-radio-firmware-manually.\n\n * Reconfigure ZHA to migrate settings to the reflashed radio",
|
||||
"title": "Multiprotocol support is enabled for this device"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on is starting."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/homeassistant_sky_connect/util.py
Normal file
17
homeassistant/components/homeassistant_sky_connect/util.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
"""Utility functions for Home Assistant Sky Connect integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components import usb
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
|
||||
def get_usb_service_info(config_entry: ConfigEntry) -> usb.UsbServiceInfo:
|
||||
"""Return UsbServiceInfo."""
|
||||
return usb.UsbServiceInfo(
|
||||
device=config_entry.data["device"],
|
||||
vid=config_entry.data["vid"],
|
||||
pid=config_entry.data["pid"],
|
||||
serial_number=config_entry.data["serial_number"],
|
||||
manufacturer=config_entry.data["manufacturer"],
|
||||
description=config_entry.data["description"],
|
||||
)
|
|
@ -73,10 +73,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
"name": "Yellow Multi-PAN",
|
||||
"port": {
|
||||
"path": get_zigbee_socket(hass, addon_info),
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
|
||||
await hass.config_entries.flow.async_init(
|
||||
|
|
|
@ -51,8 +51,12 @@ class HomeAssistantYellowOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandl
|
|||
Passed to ZHA do determine if the ZHA config entry is connected to the radio
|
||||
being migrated.
|
||||
"""
|
||||
return ZHA_HW_DISCOVERY_DATA
|
||||
return {"hw": ZHA_HW_DISCOVERY_DATA}
|
||||
|
||||
def _zha_name(self) -> str:
|
||||
"""Return the ZHA name."""
|
||||
return "Yellow Multi-PAN"
|
||||
|
||||
def _hardware_name(self) -> str:
|
||||
"""Return the name of the hardware."""
|
||||
return "Home Assistant Yellow"
|
||||
|
|
|
@ -2,40 +2,40 @@
|
|||
"options": {
|
||||
"step": {
|
||||
"addon_not_installed": {
|
||||
"title": "Enable multiprotocol support on the IEEE 802.15.4 radio",
|
||||
"description": "When multiprotocol support is enabled, the Home Assistant Yellow's IEEE 802.15.4 radio can be used for both Zigbee and Thread (used by Matter) at the same time. If the radio is already used by the ZHA Zigbee integration, ZHA will be reconfigured to use the multiprotocol firmware.\n\nNote: This is an experimental feature.",
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_not_installed::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_not_installed::description%]",
|
||||
"data": {
|
||||
"enable_multi_pan": "Enable multiprotocol support"
|
||||
"enable_multi_pan": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_not_installed::data::enable_multi_pan%]"
|
||||
}
|
||||
},
|
||||
"addon_installed_other_device": {
|
||||
"title": "Multiprotocol support is already enabled for another device"
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::addon_installed_other_device::title%]"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on installation has started"
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::install_addon::title%]"
|
||||
},
|
||||
"show_revert_guide": {
|
||||
"title": "Multiprotocol support is enabled for this device",
|
||||
"description": "If you want to change to Zigbee only firmware, please complete the following manual steps:\n\n * Remove the Silicon Labs Multiprotocol addon\n\n * Flash the Zigbee only firmware, follow the guide at https://github.com/NabuCasa/silabs-firmware/wiki/Flash-Silicon-Labs-radio-firmware-manually.\n\n * Reconfigure ZHA to migrate settings to the reflashed radio"
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::show_revert_guide::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::show_revert_guide::description%]"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol add-on is starting."
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::start_addon::title%]"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
"unknown": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"addon_info_failed": "Failed to get Silicon Labs Multiprotocol add-on info.",
|
||||
"addon_install_failed": "Failed to install the Silicon Labs Multiprotocol add-on.",
|
||||
"addon_set_config_failed": "Failed to set Silicon Labs Multiprotocol configuration.",
|
||||
"addon_start_failed": "Failed to start the Silicon Labs Multiprotocol add-on.",
|
||||
"not_hassio": "The hardware options can only be configured on HassOS installations.",
|
||||
"zha_migration_failed": "The ZHA migration did not succeed."
|
||||
"addon_info_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_info_failed%]",
|
||||
"addon_install_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_install_failed%]",
|
||||
"addon_set_config_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_set_config_failed%]",
|
||||
"addon_start_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::addon_start_failed%]",
|
||||
"not_hassio": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::not_hassio%]",
|
||||
"zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]"
|
||||
},
|
||||
"progress": {
|
||||
"install_addon": "Please wait while the Silicon Labs Multiprotocol add-on installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the Silicon Labs Multiprotocol add-on start completes. This may take some seconds."
|
||||
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
||||
"start_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::start_addon%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"data": {
|
||||
"enable_multi_pan": "Enable multiprotocol support"
|
||||
},
|
||||
"description": "When multiprotocol support is enabled, the Home Assistant Yellow's IEEE 802.15.4 radio can be used for both Zigbee and Thread (used by Matter) at the same time. If the radio is already used by the ZHA Zigbee integration, ZHA will be reconfigured to use the multiprotocol firmware.\n\nNote: This is an experimental feature.",
|
||||
"description": "When multiprotocol support is enabled, the {hardware_name}'s IEEE 802.15.4 radio can be used for both Zigbee and Thread (used by Matter) at the same time. If the radio is already used by the ZHA Zigbee integration, ZHA will be reconfigured to use the multiprotocol firmware.\n\nNote: This is an experimental feature.",
|
||||
"title": "Enable multiprotocol support on the IEEE 802.15.4 radio"
|
||||
},
|
||||
"install_addon": {
|
||||
|
|
|
@ -15,6 +15,7 @@ from zigpy.config import CONF_DEVICE, CONF_DEVICE_PATH
|
|||
from zigpy.exceptions import NetworkNotFormed
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import usb
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .core.const import (
|
||||
|
@ -53,7 +54,12 @@ HARDWARE_DISCOVERY_SCHEMA = vol.Schema(
|
|||
HARDWARE_MIGRATION_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("new_discovery_info"): HARDWARE_DISCOVERY_SCHEMA,
|
||||
vol.Required("old_discovery_info"): HARDWARE_DISCOVERY_SCHEMA,
|
||||
vol.Required("old_discovery_info"): vol.Schema(
|
||||
{
|
||||
vol.Exclusive("hw", "discovery"): HARDWARE_DISCOVERY_SCHEMA,
|
||||
vol.Exclusive("usb", "discovery"): usb.UsbServiceInfo,
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -297,21 +303,20 @@ class ZhaMultiPANMigrationHelper:
|
|||
new_radio_type = ZhaRadioManager.parse_radio_type(
|
||||
migration_data["new_discovery_info"]["radio_type"]
|
||||
)
|
||||
old_radio_type = ZhaRadioManager.parse_radio_type(
|
||||
migration_data["old_discovery_info"]["radio_type"]
|
||||
)
|
||||
|
||||
new_device_settings = new_radio_type.controller.SCHEMA_DEVICE(
|
||||
migration_data["new_discovery_info"]["port"]
|
||||
)
|
||||
old_device_settings = old_radio_type.controller.SCHEMA_DEVICE(
|
||||
migration_data["old_discovery_info"]["port"]
|
||||
)
|
||||
|
||||
if (
|
||||
self._config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
|
||||
!= old_device_settings[CONF_DEVICE_PATH]
|
||||
):
|
||||
if "hw" in migration_data["old_discovery_info"]:
|
||||
old_device_path = migration_data["old_discovery_info"]["hw"]["port"]["path"]
|
||||
else: # usb
|
||||
device = migration_data["old_discovery_info"]["usb"].device
|
||||
old_device_path = await self._hass.async_add_executor_job(
|
||||
usb.get_serial_by_id, device
|
||||
)
|
||||
|
||||
if self._config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] != old_device_path:
|
||||
# ZHA is using another radio, do nothing
|
||||
return False
|
||||
|
||||
|
@ -322,11 +327,12 @@ class ZhaMultiPANMigrationHelper:
|
|||
pass
|
||||
|
||||
# Temporarily connect to the old radio to read its settings
|
||||
config_entry_data = self._config_entry.data
|
||||
old_radio_mgr = ZhaRadioManager()
|
||||
old_radio_mgr.hass = self._hass
|
||||
old_radio_mgr.radio_type = old_radio_type
|
||||
old_radio_mgr.device_path = old_device_settings[CONF_DEVICE_PATH]
|
||||
old_radio_mgr.device_settings = old_device_settings
|
||||
old_radio_mgr.device_path = config_entry_data[CONF_DEVICE][CONF_DEVICE_PATH]
|
||||
old_radio_mgr.device_settings = config_entry_data[CONF_DEVICE]
|
||||
old_radio_mgr.radio_type = RadioType[config_entry_data[CONF_RADIO_TYPE]]
|
||||
backup = await old_radio_mgr.async_load_network_settings(create_backup=True)
|
||||
|
||||
# Then configure the radio manager for the new radio to use the new settings
|
||||
|
|
|
@ -275,6 +275,22 @@ def gen_auth_schema(config: Config, integration: Integration) -> vol.Schema:
|
|||
)
|
||||
|
||||
|
||||
def gen_ha_hardware_schema(config: Config, integration: Integration):
|
||||
"""Generate auth schema."""
|
||||
return vol.Schema(
|
||||
{
|
||||
str: {
|
||||
vol.Optional("options"): gen_data_entry_schema(
|
||||
config=config,
|
||||
integration=integration,
|
||||
flow_title=UNDEFINED,
|
||||
require_step_title=False,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def gen_platform_strings_schema(config: Config, integration: Integration) -> vol.Schema:
|
||||
"""Generate platform strings schema like strings.sensor.json.
|
||||
|
||||
|
@ -351,6 +367,8 @@ def validate_translation_file( # noqa: C901
|
|||
)
|
||||
}
|
||||
)
|
||||
elif integration.domain == "homeassistant_hardware":
|
||||
strings_schema = gen_ha_hardware_schema(config, integration)
|
||||
else:
|
||||
strings_schema = gen_strings_schema(config, integration)
|
||||
|
||||
|
|
|
@ -61,13 +61,15 @@ class TestOptionsFlow(silabs_multiprotocol_addon.OptionsFlowHandler):
|
|||
being migrated.
|
||||
"""
|
||||
return {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
"hw": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
}
|
||||
}
|
||||
|
||||
def _zha_name(self) -> str:
|
||||
|
@ -223,8 +225,8 @@ async def test_option_flow_install_multi_pan_addon_zha(
|
|||
assert zha_config_entry.data == {
|
||||
"device": {
|
||||
"path": "socket://core-silabs-multiprotocol:9999",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
"baudrate": 57600, # ZHA default
|
||||
"flow_control": "software", # ZHA default
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
|
|
|
@ -1,14 +1,138 @@
|
|||
"""Test fixtures for the Home Assistant Sky Connect integration."""
|
||||
from unittest.mock import patch
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_usb_serial_by_id", autouse=True)
|
||||
def mock_usb_serial_by_id_fixture() -> Generator[MagicMock, None, None]:
|
||||
"""Mock usb serial by id."""
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.config_flow.usb.get_serial_by_id"
|
||||
) as mock_usb_serial_by_id:
|
||||
mock_usb_serial_by_id.side_effect = lambda x: x
|
||||
yield mock_usb_serial_by_id
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_zha():
|
||||
"""Mock the zha integration."""
|
||||
mock_connect_app = MagicMock()
|
||||
mock_connect_app.__aenter__.return_value.backups.backups = [MagicMock()]
|
||||
mock_connect_app.__aenter__.return_value.backups.create_backup.return_value = (
|
||||
MagicMock()
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager._connect_zigpy_app",
|
||||
return_value=mock_connect_app,
|
||||
), patch(
|
||||
"homeassistant.components.zha.async_setup_entry",
|
||||
return_value=True,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="addon_running")
|
||||
def mock_addon_running(addon_store_info, addon_info):
|
||||
"""Mock add-on already running."""
|
||||
addon_store_info.return_value = {
|
||||
"installed": "1.0.0",
|
||||
"state": "started",
|
||||
"version": "1.0.0",
|
||||
}
|
||||
addon_info.return_value["hostname"] = "core-silabs-multiprotocol"
|
||||
addon_info.return_value["state"] = "started"
|
||||
addon_info.return_value["version"] = "1.0.0"
|
||||
return addon_info
|
||||
|
||||
|
||||
@pytest.fixture(name="addon_installed")
|
||||
def mock_addon_installed(addon_store_info, addon_info):
|
||||
"""Mock add-on already installed but not running."""
|
||||
addon_store_info.return_value = {
|
||||
"installed": "1.0.0",
|
||||
"state": "stopped",
|
||||
"version": "1.0.0",
|
||||
}
|
||||
addon_info.return_value["hostname"] = "core-silabs-multiprotocol"
|
||||
addon_info.return_value["state"] = "stopped"
|
||||
addon_info.return_value["version"] = "1.0.0"
|
||||
return addon_info
|
||||
|
||||
|
||||
@pytest.fixture(name="addon_store_info")
|
||||
def addon_store_info_fixture():
|
||||
"""Mock Supervisor add-on store info."""
|
||||
with patch(
|
||||
"homeassistant.components.hassio.addon_manager.async_get_addon_store_info"
|
||||
) as addon_store_info:
|
||||
addon_store_info.return_value = {
|
||||
"installed": None,
|
||||
"state": None,
|
||||
"version": "1.0.0",
|
||||
}
|
||||
yield addon_store_info
|
||||
|
||||
|
||||
@pytest.fixture(name="addon_info")
|
||||
def addon_info_fixture():
|
||||
"""Mock Supervisor add-on info."""
|
||||
with patch(
|
||||
"homeassistant.components.hassio.addon_manager.async_get_addon_info",
|
||||
) as addon_info:
|
||||
addon_info.return_value = {
|
||||
"hostname": None,
|
||||
"options": {},
|
||||
"state": None,
|
||||
"update_available": False,
|
||||
"version": None,
|
||||
}
|
||||
yield addon_info
|
||||
|
||||
|
||||
@pytest.fixture(name="set_addon_options")
|
||||
def set_addon_options_fixture():
|
||||
"""Mock set add-on options."""
|
||||
with patch(
|
||||
"homeassistant.components.hassio.addon_manager.async_set_addon_options"
|
||||
) as set_options:
|
||||
yield set_options
|
||||
|
||||
|
||||
@pytest.fixture(name="install_addon_side_effect")
|
||||
def install_addon_side_effect_fixture(addon_store_info, addon_info):
|
||||
"""Return the install add-on side effect."""
|
||||
|
||||
async def install_addon(hass, slug):
|
||||
"""Mock install add-on."""
|
||||
addon_store_info.return_value = {
|
||||
"installed": "1.0.0",
|
||||
"state": "stopped",
|
||||
"version": "1.0.0",
|
||||
}
|
||||
addon_info.return_value["hostname"] = "core-silabs-multiprotocol"
|
||||
addon_info.return_value["state"] = "stopped"
|
||||
addon_info.return_value["version"] = "1.0.0"
|
||||
|
||||
return install_addon
|
||||
|
||||
|
||||
@pytest.fixture(name="install_addon")
|
||||
def mock_install_addon(install_addon_side_effect):
|
||||
"""Mock install add-on."""
|
||||
with patch(
|
||||
"homeassistant.components.hassio.addon_manager.async_install_addon",
|
||||
side_effect=install_addon_side_effect,
|
||||
) as install_addon:
|
||||
yield install_addon
|
||||
|
||||
|
||||
@pytest.fixture(name="start_addon")
|
||||
def start_addon_fixture():
|
||||
"""Mock start add-on."""
|
||||
with patch(
|
||||
"homeassistant.components.hassio.addon_manager.async_start_addon"
|
||||
) as start_addon:
|
||||
yield start_addon
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
"""Test the Home Assistant Sky Connect config flow."""
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from homeassistant.components import homeassistant_sky_connect, usb
|
||||
from homeassistant.components.homeassistant_sky_connect.const import DOMAIN
|
||||
from homeassistant.components.zha.core.const import (
|
||||
CONF_DEVICE_PATH,
|
||||
DOMAIN as ZHA_DOMAIN,
|
||||
RadioType,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, MockModule, mock_integration
|
||||
|
||||
USB_DATA = usb.UsbServiceInfo(
|
||||
device="bla_device",
|
||||
|
@ -143,3 +148,190 @@ async def test_config_flow_update_device(hass: HomeAssistant) -> None:
|
|||
assert result["reason"] == "already_configured"
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(mock_unload_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_option_flow_install_multi_pan_addon(
|
||||
hass: HomeAssistant,
|
||||
addon_store_info,
|
||||
addon_info,
|
||||
install_addon,
|
||||
set_addon_options,
|
||||
start_addon,
|
||||
) -> None:
|
||||
"""Test installing the multi pan addon."""
|
||||
mock_integration(hass, MockModule("hassio"))
|
||||
|
||||
# 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,
|
||||
},
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Sky Connect",
|
||||
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_hardware.silabs_multiprotocol_addon.is_hassio",
|
||||
side_effect=Mock(return_value=True),
|
||||
):
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "addon_not_installed"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"enable_multi_pan": True,
|
||||
},
|
||||
)
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "install_addon"
|
||||
assert result["progress_action"] == "install_addon"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS_DONE
|
||||
assert result["step_id"] == "configure_addon"
|
||||
install_addon.assert_called_once_with(hass, "core_silabs_multiprotocol")
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "start_addon"
|
||||
set_addon_options.assert_called_once_with(
|
||||
hass,
|
||||
"core_silabs_multiprotocol",
|
||||
{
|
||||
"options": {
|
||||
"autoflash_firmware": True,
|
||||
"device": "bla_device",
|
||||
"baudrate": "115200",
|
||||
"flow_control": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS_DONE
|
||||
assert result["step_id"] == "finish_addon_setup"
|
||||
start_addon.assert_called_once_with(hass, "core_silabs_multiprotocol")
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
def mock_detect_radio_type(radio_type=RadioType.ezsp, ret=True):
|
||||
"""Mock `detect_radio_type` that just sets the appropriate attributes."""
|
||||
|
||||
async def detect(self):
|
||||
self.radio_type = radio_type
|
||||
self.device_settings = radio_type.controller.SCHEMA_DEVICE(
|
||||
{CONF_DEVICE_PATH: self.device_path}
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
return detect
|
||||
|
||||
|
||||
@patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
|
||||
mock_detect_radio_type(),
|
||||
)
|
||||
async def test_option_flow_install_multi_pan_addon_zha(
|
||||
hass: HomeAssistant,
|
||||
addon_store_info,
|
||||
addon_info,
|
||||
install_addon,
|
||||
set_addon_options,
|
||||
start_addon,
|
||||
) -> None:
|
||||
"""Test installing the multi pan addon when a zha config entry exists."""
|
||||
mock_integration(hass, MockModule("hassio"))
|
||||
|
||||
# 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,
|
||||
},
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Sky Connect",
|
||||
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"},
|
||||
domain=ZHA_DOMAIN,
|
||||
options={},
|
||||
title="Yellow",
|
||||
)
|
||||
zha_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon.is_hassio",
|
||||
side_effect=Mock(return_value=True),
|
||||
):
|
||||
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "addon_not_installed"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"enable_multi_pan": True,
|
||||
},
|
||||
)
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "install_addon"
|
||||
assert result["progress_action"] == "install_addon"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS_DONE
|
||||
assert result["step_id"] == "configure_addon"
|
||||
install_addon.assert_called_once_with(hass, "core_silabs_multiprotocol")
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "start_addon"
|
||||
set_addon_options.assert_called_once_with(
|
||||
hass,
|
||||
"core_silabs_multiprotocol",
|
||||
{
|
||||
"options": {
|
||||
"autoflash_firmware": True,
|
||||
"device": "bla_device",
|
||||
"baudrate": "115200",
|
||||
"flow_control": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
# Check the ZHA config entry data is updated
|
||||
assert zha_config_entry.data == {
|
||||
"device": {
|
||||
"path": "socket://core-silabs-multiprotocol:9999",
|
||||
"baudrate": 57600, # ZHA default
|
||||
"flow_control": "software", # ZHA default
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS_DONE
|
||||
assert result["step_id"] == "finish_addon_setup"
|
||||
start_addon.assert_called_once_with(hass, "core_silabs_multiprotocol")
|
||||
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import patch
|
|||
from homeassistant.components.homeassistant_sky_connect.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, MockModule, mock_integration
|
||||
|
||||
CONFIG_ENTRY_DATA = {
|
||||
"device": "bla_device",
|
||||
|
@ -25,8 +25,12 @@ CONFIG_ENTRY_DATA_2 = {
|
|||
}
|
||||
|
||||
|
||||
async def test_hardware_info(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
async def test_hardware_info(
|
||||
hass: HomeAssistant, hass_ws_client, addon_store_info
|
||||
) -> None:
|
||||
"""Test we can get the board info."""
|
||||
mock_integration(hass, MockModule("usb"))
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data=CONFIG_ENTRY_DATA,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
"""Test the Home Assistant Sky Connect integration."""
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import zha
|
||||
from homeassistant.components.hassio.handler import HassioAPIError
|
||||
from homeassistant.components.homeassistant_sky_connect.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -46,7 +47,12 @@ def mock_zha_config_flow_setup() -> Generator[None, None, None]:
|
|||
"onboarded, num_entries, num_flows", ((False, 1, 0), (True, 0, 1))
|
||||
)
|
||||
async def test_setup_entry(
|
||||
mock_zha_config_flow_setup, hass: HomeAssistant, onboarded, num_entries, num_flows
|
||||
mock_zha_config_flow_setup,
|
||||
hass: HomeAssistant,
|
||||
addon_store_info,
|
||||
onboarded,
|
||||
num_entries,
|
||||
num_flows,
|
||||
) -> None:
|
||||
"""Test setup of a config entry, including setup of zha."""
|
||||
# Setup the config entry
|
||||
|
@ -90,7 +96,9 @@ async def test_setup_entry(
|
|||
assert len(hass.config_entries.async_entries("zha")) == num_entries
|
||||
|
||||
|
||||
async def test_setup_zha(mock_zha_config_flow_setup, hass: HomeAssistant) -> None:
|
||||
async def test_setup_zha(
|
||||
mock_zha_config_flow_setup, hass: HomeAssistant, addon_store_info
|
||||
) -> None:
|
||||
"""Test zha gets the right config."""
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
|
@ -134,6 +142,108 @@ async def test_setup_zha(mock_zha_config_flow_setup, hass: HomeAssistant) -> Non
|
|||
assert config_entry.title == CONFIG_ENTRY_DATA["description"]
|
||||
|
||||
|
||||
async def test_setup_zha_multipan(
|
||||
hass: HomeAssistant, addon_info, addon_running
|
||||
) -> None:
|
||||
"""Test zha gets the right config."""
|
||||
addon_info.return_value["options"]["device"] = CONFIG_ENTRY_DATA["device"]
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data=CONFIG_ENTRY_DATA,
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Sky Connect",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in",
|
||||
return_value=True,
|
||||
) as mock_is_plugged_in, patch(
|
||||
"homeassistant.components.onboarding.async_is_onboarded", return_value=False
|
||||
), patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.is_hassio",
|
||||
side_effect=Mock(return_value=True),
|
||||
):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_is_plugged_in.mock_calls) == 1
|
||||
|
||||
# Finish setting up ZHA
|
||||
zha_flows = hass.config_entries.flow.async_progress_by_handler("zha")
|
||||
assert len(zha_flows) == 1
|
||||
assert zha_flows[0]["step_id"] == "choose_formation_strategy"
|
||||
|
||||
await hass.config_entries.flow.async_configure(
|
||||
zha_flows[0]["flow_id"],
|
||||
user_input={"next_step_id": zha.config_flow.FORMATION_REUSE_SETTINGS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
config_entry = hass.config_entries.async_entries("zha")[0]
|
||||
assert config_entry.data == {
|
||||
"device": {
|
||||
"baudrate": 57600, # ZHA default
|
||||
"flow_control": "software", # ZHA default
|
||||
"path": "socket://core-silabs-multiprotocol:9999",
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
assert config_entry.options == {}
|
||||
assert config_entry.title == "Sky Connect Multi-PAN"
|
||||
|
||||
|
||||
async def test_setup_zha_multipan_other_device(
|
||||
mock_zha_config_flow_setup, hass: HomeAssistant, addon_info, addon_running
|
||||
) -> None:
|
||||
"""Test zha gets the right config."""
|
||||
addon_info.return_value["options"]["device"] = "/dev/not_our_sky_connect"
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data=CONFIG_ENTRY_DATA,
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Yellow",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in",
|
||||
return_value=True,
|
||||
) as mock_is_plugged_in, patch(
|
||||
"homeassistant.components.onboarding.async_is_onboarded", return_value=False
|
||||
), patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.is_hassio",
|
||||
side_effect=Mock(return_value=True),
|
||||
):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_is_plugged_in.mock_calls) == 1
|
||||
|
||||
# Finish setting up ZHA
|
||||
zha_flows = hass.config_entries.flow.async_progress_by_handler("zha")
|
||||
assert len(zha_flows) == 1
|
||||
assert zha_flows[0]["step_id"] == "choose_formation_strategy"
|
||||
|
||||
await hass.config_entries.flow.async_configure(
|
||||
zha_flows[0]["flow_id"],
|
||||
user_input={"next_step_id": zha.config_flow.FORMATION_REUSE_SETTINGS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
config_entry = hass.config_entries.async_entries("zha")[0]
|
||||
assert config_entry.data == {
|
||||
"device": {
|
||||
"baudrate": 115200,
|
||||
"flow_control": "software",
|
||||
"path": CONFIG_ENTRY_DATA["device"],
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
assert config_entry.options == {}
|
||||
assert config_entry.title == CONFIG_ENTRY_DATA["description"]
|
||||
|
||||
|
||||
async def test_setup_entry_wait_usb(hass: HomeAssistant) -> None:
|
||||
"""Test setup of a config entry when the dongle is not plugged in."""
|
||||
# Setup the config entry
|
||||
|
@ -152,3 +262,58 @@ async def test_setup_entry_wait_usb(hass: HomeAssistant) -> None:
|
|||
await hass.async_block_till_done()
|
||||
assert len(mock_is_plugged_in.mock_calls) == 1
|
||||
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_setup_entry_addon_info_fails(
|
||||
hass: HomeAssistant, addon_store_info
|
||||
) -> None:
|
||||
"""Test setup of a config entry when fetching addon info fails."""
|
||||
addon_store_info.side_effect = HassioAPIError("Boom")
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data=CONFIG_ENTRY_DATA,
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Sky Connect",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.onboarding.async_is_onboarded", return_value=False
|
||||
), patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.is_hassio",
|
||||
side_effect=Mock(return_value=True),
|
||||
):
|
||||
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_setup_entry_addon_not_running(
|
||||
hass: HomeAssistant, addon_installed, start_addon
|
||||
) -> None:
|
||||
"""Test the addon is started if it is not running."""
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data=CONFIG_ENTRY_DATA,
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Sky Connect",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.usb.async_is_plugged_in",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.onboarding.async_is_onboarded", return_value=False
|
||||
), patch(
|
||||
"homeassistant.components.homeassistant_sky_connect.is_hassio",
|
||||
side_effect=Mock(return_value=True),
|
||||
):
|
||||
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||
start_addon.assert_called_once()
|
||||
|
|
|
@ -197,8 +197,8 @@ async def test_option_flow_install_multi_pan_addon_zha(
|
|||
assert zha_config_entry.data == {
|
||||
"device": {
|
||||
"path": "socket://core-silabs-multiprotocol:9999",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
"baudrate": 57600, # ZHA default
|
||||
"flow_control": "software", # ZHA default
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
|
|
|
@ -144,8 +144,8 @@ async def test_setup_zha_multipan(
|
|||
config_entry = hass.config_entries.async_entries("zha")[0]
|
||||
assert config_entry.data == {
|
||||
"device": {
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
"baudrate": 57600, # ZHA default
|
||||
"flow_control": "software", # ZHA default
|
||||
"path": "socket://core-silabs-multiprotocol:9999",
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
|
|
|
@ -11,6 +11,7 @@ from zigpy.config import CONF_DEVICE_PATH
|
|||
import zigpy.types
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.usb import UsbServiceInfo
|
||||
from homeassistant.components.zha import radio_manager
|
||||
from homeassistant.components.zha.core.const import DOMAIN, RadioType
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -125,14 +126,68 @@ async def test_migrate_matching_port(
|
|||
"radio_type": "efr32",
|
||||
},
|
||||
"old_discovery_info": {
|
||||
"name": "Test",
|
||||
"hw": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
migration_helper = radio_manager.ZhaMultiPANMigrationHelper(hass, config_entry)
|
||||
assert await migration_helper.async_initiate_migration(migration_data)
|
||||
|
||||
# Check the ZHA config entry data is updated
|
||||
assert config_entry.data == {
|
||||
"device": {
|
||||
"path": "socket://some/virtual_port",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
}
|
||||
assert config_entry.title == "Test Updated"
|
||||
|
||||
await migration_helper.async_finish_migration()
|
||||
|
||||
|
||||
@patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
|
||||
mock_detect_radio_type(),
|
||||
)
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
async def test_migrate_matching_port_usb(
|
||||
hass: HomeAssistant,
|
||||
mock_connect_zigpy_app,
|
||||
) -> None:
|
||||
"""Test automatic migration."""
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data={"device": {"path": "/dev/ttyTEST123"}, "radio_type": "ezsp"},
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Test",
|
||||
version=3,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
migration_data = {
|
||||
"new_discovery_info": {
|
||||
"name": "Test Updated",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"path": "socket://some/virtual_port",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
},
|
||||
"old_discovery_info": {
|
||||
"usb": UsbServiceInfo("/dev/ttyTEST123", "blah", "blah", None, None, None)
|
||||
},
|
||||
}
|
||||
|
||||
migration_helper = radio_manager.ZhaMultiPANMigrationHelper(hass, config_entry)
|
||||
|
@ -178,13 +233,15 @@ async def test_migrate_matching_port_config_entry_not_loaded(
|
|||
"radio_type": "efr32",
|
||||
},
|
||||
"old_discovery_info": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
"hw": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -236,13 +293,15 @@ async def test_migrate_matching_port_retry(
|
|||
"radio_type": "efr32",
|
||||
},
|
||||
"old_discovery_info": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
"hw": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST123",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -290,13 +349,15 @@ async def test_migrate_non_matching_port(
|
|||
"radio_type": "efr32",
|
||||
},
|
||||
"old_discovery_info": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST456",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
"hw": {
|
||||
"name": "Test",
|
||||
"port": {
|
||||
"path": "/dev/ttyTEST456",
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "efr32",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue