mirror of
https://github.com/home-assistant/core.git
synced 2025-08-04 13:15:18 +02:00
Flash ZBT-1 and Yellow firmwares from Core instead of using addons (#145019)
* Make `async_flash_firmware` a public helper
* [ZBT-1] Implement flashing for Zigbee and Thread within the config flow
* WIP: Begin fixing unit tests
* WIP: Unit tests, pass 2
* WIP: pass 3
* Fix hardware unit tests
* Have the individual hardware integrations depend on the firmware flasher
* Break out firmware filter into its own helper
* Mirror to Yellow
* Simplify
* Simplify
* Revert "Have the individual hardware integrations depend on the firmware flasher"
This reverts commit 096f4297dc
.
* Move `async_flash_silabs_firmware` into `util`
* Fix existing unit tests
* Unconditionally upgrade Zigbee firmware during installation
* Fix failing error case unit tests
* Fix remaining failing unit tests
* Increase test coverage
* 100% test coverage
* Remove old translation strings
* Add new translation strings
* Do not probe OTBR firmware when completing the flow
* More translation strings
* Probe OTBR firmware info before starting the addon
This commit is contained in:
@@ -7,6 +7,8 @@ import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from ha_silabs_firmware_client import FirmwareUpdateClient
|
||||
|
||||
from homeassistant.components.hassio import (
|
||||
AddonError,
|
||||
AddonInfo,
|
||||
@@ -22,17 +24,17 @@ from homeassistant.config_entries import (
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
|
||||
from . import silabs_multiprotocol_addon
|
||||
from .const import OTBR_DOMAIN, ZHA_DOMAIN
|
||||
from .util import (
|
||||
ApplicationType,
|
||||
FirmwareInfo,
|
||||
OwningAddon,
|
||||
OwningIntegration,
|
||||
async_flash_silabs_firmware,
|
||||
get_otbr_addon_manager,
|
||||
get_zigbee_flasher_addon_manager,
|
||||
guess_firmware_info,
|
||||
guess_hardware_owners,
|
||||
probe_silabs_firmware_info,
|
||||
@@ -61,6 +63,7 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
self.addon_install_task: asyncio.Task | None = None
|
||||
self.addon_start_task: asyncio.Task | None = None
|
||||
self.addon_uninstall_task: asyncio.Task | None = None
|
||||
self.firmware_install_task: asyncio.Task | None = None
|
||||
|
||||
def _get_translation_placeholders(self) -> dict[str, str]:
|
||||
"""Shared translation placeholders."""
|
||||
@@ -77,22 +80,6 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
|
||||
return placeholders
|
||||
|
||||
async def _async_set_addon_config(
|
||||
self, config: dict, addon_manager: AddonManager
|
||||
) -> None:
|
||||
"""Set add-on config."""
|
||||
try:
|
||||
await addon_manager.async_set_addon_options(config)
|
||||
except AddonError as err:
|
||||
_LOGGER.error(err)
|
||||
raise AbortFlow(
|
||||
"addon_set_config_failed",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": addon_manager.addon_name,
|
||||
},
|
||||
) from err
|
||||
|
||||
async def _async_get_addon_info(self, addon_manager: AddonManager) -> AddonInfo:
|
||||
"""Return add-on info."""
|
||||
try:
|
||||
@@ -150,6 +137,54 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
)
|
||||
)
|
||||
|
||||
async def _install_firmware_step(
|
||||
self,
|
||||
fw_update_url: str,
|
||||
fw_type: str,
|
||||
firmware_name: str,
|
||||
expected_installed_firmware_type: ApplicationType,
|
||||
step_id: str,
|
||||
next_step_id: str,
|
||||
) -> ConfigFlowResult:
|
||||
assert self._device is not None
|
||||
|
||||
if not self.firmware_install_task:
|
||||
session = async_get_clientsession(self.hass)
|
||||
client = FirmwareUpdateClient(fw_update_url, session)
|
||||
manifest = await client.async_update_data()
|
||||
|
||||
fw_meta = next(
|
||||
fw for fw in manifest.firmwares if fw.filename.startswith(fw_type)
|
||||
)
|
||||
|
||||
fw_data = await client.async_fetch_firmware(fw_meta)
|
||||
self.firmware_install_task = self.hass.async_create_task(
|
||||
async_flash_silabs_firmware(
|
||||
hass=self.hass,
|
||||
device=self._device,
|
||||
fw_data=fw_data,
|
||||
expected_installed_firmware_type=expected_installed_firmware_type,
|
||||
bootloader_reset_type=None,
|
||||
progress_callback=lambda offset, total: self.async_update_progress(
|
||||
offset / total
|
||||
),
|
||||
),
|
||||
f"Flash {firmware_name} firmware",
|
||||
)
|
||||
|
||||
if not self.firmware_install_task.done():
|
||||
return self.async_show_progress(
|
||||
step_id=step_id,
|
||||
progress_action="install_firmware",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"firmware_name": firmware_name,
|
||||
},
|
||||
progress_task=self.firmware_install_task,
|
||||
)
|
||||
|
||||
return self.async_show_progress_done(next_step_id=next_step_id)
|
||||
|
||||
async def async_step_pick_firmware_zigbee(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -160,68 +195,133 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
# Allow the stick to be used with ZHA without flashing
|
||||
if (
|
||||
self._probed_firmware_info is not None
|
||||
and self._probed_firmware_info.firmware_type == ApplicationType.EZSP
|
||||
):
|
||||
return await self.async_step_confirm_zigbee()
|
||||
return await self.async_step_install_zigbee_firmware()
|
||||
|
||||
if not is_hassio(self.hass):
|
||||
return self.async_abort(
|
||||
reason="not_hassio",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
async def async_step_install_zigbee_firmware(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Install Zigbee firmware."""
|
||||
raise NotImplementedError
|
||||
|
||||
# Only flash new firmware if we need to
|
||||
fw_flasher_manager = get_zigbee_flasher_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(fw_flasher_manager)
|
||||
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return await self.async_step_install_zigbee_flasher_addon()
|
||||
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
return await self.async_step_run_zigbee_flasher_addon()
|
||||
|
||||
# If the addon is already installed and running, fail
|
||||
async def async_step_addon_operation_failed(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Abort when add-on installation or start failed."""
|
||||
return self.async_abort(
|
||||
reason="addon_already_running",
|
||||
reason=self._failed_addon_reason,
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": fw_flasher_manager.addon_name,
|
||||
"addon_name": self._failed_addon_name,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_install_zigbee_flasher_addon(
|
||||
async def async_step_confirm_zigbee(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Show progress dialog for installing the Zigbee flasher addon."""
|
||||
return await self._install_addon(
|
||||
get_zigbee_flasher_addon_manager(self.hass),
|
||||
"install_zigbee_flasher_addon",
|
||||
"run_zigbee_flasher_addon",
|
||||
"""Confirm Zigbee setup."""
|
||||
assert self._device is not None
|
||||
assert self._hardware_name is not None
|
||||
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="confirm_zigbee",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
if not await self._probe_firmware_info(probe_methods=(ApplicationType.EZSP,)):
|
||||
return self.async_abort(
|
||||
reason="unsupported_firmware",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
await self.hass.config_entries.flow.async_init(
|
||||
ZHA_DOMAIN,
|
||||
context={"source": "hardware"},
|
||||
data={
|
||||
"name": self._hardware_name,
|
||||
"port": {
|
||||
"path": self._device,
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
},
|
||||
)
|
||||
|
||||
async def _install_addon(
|
||||
self,
|
||||
addon_manager: silabs_multiprotocol_addon.WaitingAddonManager,
|
||||
step_id: str,
|
||||
next_step_id: str,
|
||||
return self._async_flow_finished()
|
||||
|
||||
async def _ensure_thread_addon_setup(self) -> ConfigFlowResult | None:
|
||||
"""Ensure the OTBR addon is set up and not running."""
|
||||
|
||||
# We install the OTBR addon no matter what, since it is required to use Thread
|
||||
if not is_hassio(self.hass):
|
||||
return self.async_abort(
|
||||
reason="not_hassio_thread",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
otbr_manager = get_otbr_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(otbr_manager)
|
||||
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return await self.async_step_install_otbr_addon()
|
||||
|
||||
if addon_info.state == AddonState.RUNNING:
|
||||
# We only fail setup if we have an instance of OTBR running *and* it's
|
||||
# pointing to different hardware
|
||||
if addon_info.options["device"] != self._device:
|
||||
return self.async_abort(
|
||||
reason="otbr_addon_already_running",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": otbr_manager.addon_name,
|
||||
},
|
||||
)
|
||||
|
||||
# Otherwise, stop the addon before continuing to flash firmware
|
||||
await otbr_manager.async_stop_addon()
|
||||
|
||||
return None
|
||||
|
||||
async def async_step_pick_firmware_thread(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Show progress dialog for installing an addon."""
|
||||
"""Pick Thread firmware."""
|
||||
if not await self._probe_firmware_info():
|
||||
return self.async_abort(
|
||||
reason="unsupported_firmware",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
if result := await self._ensure_thread_addon_setup():
|
||||
return result
|
||||
|
||||
return await self.async_step_install_thread_firmware()
|
||||
|
||||
async def async_step_install_thread_firmware(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Install Thread firmware."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_step_install_otbr_addon(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Show progress dialog for installing the OTBR addon."""
|
||||
addon_manager = get_otbr_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(addon_manager)
|
||||
|
||||
_LOGGER.debug("Flasher addon state: %s", addon_info)
|
||||
_LOGGER.debug("OTBR addon info: %s", addon_info)
|
||||
|
||||
if not self.addon_install_task:
|
||||
self.addon_install_task = self.hass.async_create_task(
|
||||
addon_manager.async_install_addon_waiting(),
|
||||
"Addon install",
|
||||
"OTBR addon install",
|
||||
)
|
||||
|
||||
if not self.addon_install_task.done():
|
||||
return self.async_show_progress(
|
||||
step_id=step_id,
|
||||
step_id="install_otbr_addon",
|
||||
progress_action="install_addon",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
@@ -240,208 +340,50 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
finally:
|
||||
self.addon_install_task = None
|
||||
|
||||
return self.async_show_progress_done(next_step_id=next_step_id)
|
||||
|
||||
async def async_step_addon_operation_failed(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Abort when add-on installation or start failed."""
|
||||
return self.async_abort(
|
||||
reason=self._failed_addon_reason,
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": self._failed_addon_name,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_run_zigbee_flasher_addon(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Configure the flasher addon to point to the SkyConnect and run it."""
|
||||
fw_flasher_manager = get_zigbee_flasher_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(fw_flasher_manager)
|
||||
|
||||
assert self._device is not None
|
||||
new_addon_config = {
|
||||
**addon_info.options,
|
||||
"device": self._device,
|
||||
"baudrate": 115200,
|
||||
"bootloader_baudrate": 115200,
|
||||
"flow_control": True,
|
||||
}
|
||||
|
||||
_LOGGER.debug("Reconfiguring flasher addon with %s", new_addon_config)
|
||||
await self._async_set_addon_config(new_addon_config, fw_flasher_manager)
|
||||
|
||||
if not self.addon_start_task:
|
||||
|
||||
async def start_and_wait_until_done() -> None:
|
||||
await fw_flasher_manager.async_start_addon_waiting()
|
||||
# Now that the addon is running, wait for it to finish
|
||||
await fw_flasher_manager.async_wait_until_addon_state(
|
||||
AddonState.NOT_RUNNING
|
||||
)
|
||||
|
||||
self.addon_start_task = self.hass.async_create_task(
|
||||
start_and_wait_until_done()
|
||||
)
|
||||
|
||||
if not self.addon_start_task.done():
|
||||
return self.async_show_progress(
|
||||
step_id="run_zigbee_flasher_addon",
|
||||
progress_action="run_zigbee_flasher_addon",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": fw_flasher_manager.addon_name,
|
||||
},
|
||||
progress_task=self.addon_start_task,
|
||||
)
|
||||
|
||||
try:
|
||||
await self.addon_start_task
|
||||
except (AddonError, AbortFlow) as err:
|
||||
_LOGGER.error(err)
|
||||
self._failed_addon_name = fw_flasher_manager.addon_name
|
||||
self._failed_addon_reason = "addon_start_failed"
|
||||
return self.async_show_progress_done(next_step_id="addon_operation_failed")
|
||||
finally:
|
||||
self.addon_start_task = None
|
||||
|
||||
return self.async_show_progress_done(
|
||||
next_step_id="uninstall_zigbee_flasher_addon"
|
||||
)
|
||||
|
||||
async def async_step_uninstall_zigbee_flasher_addon(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Uninstall the flasher addon."""
|
||||
fw_flasher_manager = get_zigbee_flasher_addon_manager(self.hass)
|
||||
|
||||
if not self.addon_uninstall_task:
|
||||
_LOGGER.debug("Uninstalling flasher addon")
|
||||
self.addon_uninstall_task = self.hass.async_create_task(
|
||||
fw_flasher_manager.async_uninstall_addon_waiting()
|
||||
)
|
||||
|
||||
if not self.addon_uninstall_task.done():
|
||||
return self.async_show_progress(
|
||||
step_id="uninstall_zigbee_flasher_addon",
|
||||
progress_action="uninstall_zigbee_flasher_addon",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": fw_flasher_manager.addon_name,
|
||||
},
|
||||
progress_task=self.addon_uninstall_task,
|
||||
)
|
||||
|
||||
try:
|
||||
await self.addon_uninstall_task
|
||||
except (AddonError, AbortFlow) as err:
|
||||
_LOGGER.error(err)
|
||||
# The uninstall failing isn't critical so we can just continue
|
||||
finally:
|
||||
self.addon_uninstall_task = None
|
||||
|
||||
return self.async_show_progress_done(next_step_id="confirm_zigbee")
|
||||
|
||||
async def async_step_confirm_zigbee(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Confirm Zigbee setup."""
|
||||
assert self._device is not None
|
||||
assert self._hardware_name is not None
|
||||
|
||||
if not await self._probe_firmware_info(probe_methods=(ApplicationType.EZSP,)):
|
||||
return self.async_abort(
|
||||
reason="unsupported_firmware",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
if user_input is not None:
|
||||
await self.hass.config_entries.flow.async_init(
|
||||
ZHA_DOMAIN,
|
||||
context={"source": "hardware"},
|
||||
data={
|
||||
"name": self._hardware_name,
|
||||
"port": {
|
||||
"path": self._device,
|
||||
"baudrate": 115200,
|
||||
"flow_control": "hardware",
|
||||
},
|
||||
"radio_type": "ezsp",
|
||||
},
|
||||
)
|
||||
|
||||
return self._async_flow_finished()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm_zigbee",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
async def async_step_pick_firmware_thread(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Pick Thread firmware."""
|
||||
if not await self._probe_firmware_info():
|
||||
return self.async_abort(
|
||||
reason="unsupported_firmware",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
# We install the OTBR addon no matter what, since it is required to use Thread
|
||||
if not is_hassio(self.hass):
|
||||
return self.async_abort(
|
||||
reason="not_hassio_thread",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
otbr_manager = get_otbr_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(otbr_manager)
|
||||
|
||||
if addon_info.state == AddonState.NOT_INSTALLED:
|
||||
return await self.async_step_install_otbr_addon()
|
||||
|
||||
if addon_info.state == AddonState.NOT_RUNNING:
|
||||
return await self.async_step_start_otbr_addon()
|
||||
|
||||
# If the addon is already installed and running, fail
|
||||
return self.async_abort(
|
||||
reason="otbr_addon_already_running",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": otbr_manager.addon_name,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_install_otbr_addon(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Show progress dialog for installing the OTBR addon."""
|
||||
return await self._install_addon(
|
||||
get_otbr_addon_manager(self.hass), "install_otbr_addon", "start_otbr_addon"
|
||||
)
|
||||
return self.async_show_progress_done(next_step_id="install_thread_firmware")
|
||||
|
||||
async def async_step_start_otbr_addon(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Configure OTBR to point to the SkyConnect and run the addon."""
|
||||
otbr_manager = get_otbr_addon_manager(self.hass)
|
||||
addon_info = await self._async_get_addon_info(otbr_manager)
|
||||
|
||||
assert self._device is not None
|
||||
new_addon_config = {
|
||||
**addon_info.options,
|
||||
"device": self._device,
|
||||
"baudrate": 460800,
|
||||
"flow_control": True,
|
||||
"autoflash_firmware": True,
|
||||
}
|
||||
|
||||
_LOGGER.debug("Reconfiguring OTBR addon with %s", new_addon_config)
|
||||
await self._async_set_addon_config(new_addon_config, otbr_manager)
|
||||
|
||||
if not self.addon_start_task:
|
||||
# Before we start the addon, confirm that the correct firmware is running
|
||||
# and populate `self._probed_firmware_info` with the correct information
|
||||
if not await self._probe_firmware_info(
|
||||
probe_methods=(ApplicationType.SPINEL,)
|
||||
):
|
||||
return self.async_abort(
|
||||
reason="unsupported_firmware",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
addon_info = await self._async_get_addon_info(otbr_manager)
|
||||
|
||||
assert self._device is not None
|
||||
new_addon_config = {
|
||||
**addon_info.options,
|
||||
"device": self._device,
|
||||
"baudrate": 460800,
|
||||
"flow_control": True,
|
||||
"autoflash_firmware": False,
|
||||
}
|
||||
|
||||
_LOGGER.debug("Reconfiguring OTBR addon with %s", new_addon_config)
|
||||
|
||||
try:
|
||||
await otbr_manager.async_set_addon_options(new_addon_config)
|
||||
except AddonError as err:
|
||||
_LOGGER.error(err)
|
||||
raise AbortFlow(
|
||||
"addon_set_config_failed",
|
||||
description_placeholders={
|
||||
**self._get_translation_placeholders(),
|
||||
"addon_name": otbr_manager.addon_name,
|
||||
},
|
||||
) from err
|
||||
|
||||
self.addon_start_task = self.hass.async_create_task(
|
||||
otbr_manager.async_start_addon_waiting()
|
||||
)
|
||||
@@ -475,20 +417,14 @@ class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
|
||||
"""Confirm OTBR setup."""
|
||||
assert self._device is not None
|
||||
|
||||
if not await self._probe_firmware_info(probe_methods=(ApplicationType.SPINEL,)):
|
||||
return self.async_abort(
|
||||
reason="unsupported_firmware",
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="confirm_otbr",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
|
||||
if user_input is not None:
|
||||
# OTBR discovery is done automatically via hassio
|
||||
return self._async_flow_finished()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm_otbr",
|
||||
description_placeholders=self._get_translation_placeholders(),
|
||||
)
|
||||
# OTBR discovery is done automatically via hassio
|
||||
return self._async_flow_finished()
|
||||
|
||||
@abstractmethod
|
||||
def _async_flow_finished(self) -> ConfigFlowResult:
|
||||
|
@@ -10,22 +10,6 @@
|
||||
"pick_firmware_thread": "Thread"
|
||||
}
|
||||
},
|
||||
"install_zigbee_flasher_addon": {
|
||||
"title": "Installing flasher",
|
||||
"description": "Installing the Silicon Labs Flasher add-on."
|
||||
},
|
||||
"run_zigbee_flasher_addon": {
|
||||
"title": "Installing Zigbee firmware",
|
||||
"description": "Installing Zigbee firmware. This will take about a minute."
|
||||
},
|
||||
"uninstall_zigbee_flasher_addon": {
|
||||
"title": "Removing flasher",
|
||||
"description": "Removing the Silicon Labs Flasher add-on."
|
||||
},
|
||||
"zigbee_flasher_failed": {
|
||||
"title": "Zigbee installation failed",
|
||||
"description": "The Zigbee firmware installation process was unsuccessful. Ensure no other software is trying to communicate with the {model} and try again."
|
||||
},
|
||||
"confirm_zigbee": {
|
||||
"title": "Zigbee setup complete",
|
||||
"description": "Your {model} is now a Zigbee coordinator and will be shown as discovered by the Zigbee Home Automation integration."
|
||||
@@ -55,9 +39,7 @@
|
||||
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or add-on is currently trying to communicate with the device. If you are running Home Assistant OS in a virtual machine or in Docker, please make sure that permissions are set correctly for the device."
|
||||
},
|
||||
"progress": {
|
||||
"install_zigbee_flasher_addon": "The Silicon Labs Flasher add-on is installed, this may take a few minutes.",
|
||||
"run_zigbee_flasher_addon": "Please wait while Zigbee firmware is installed to your {model}, this will take a few minutes. Do not make any changes to your hardware or software until this finishes.",
|
||||
"uninstall_zigbee_flasher_addon": "The Silicon Labs Flasher add-on is being removed."
|
||||
"install_firmware": "Please wait while {firmware_name} firmware is installed to your {model}, this will take a few minutes. Do not make any changes to your hardware or software until this finishes."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -110,16 +92,6 @@
|
||||
"data": {
|
||||
"disable_multi_pan": "Disable multiprotocol support"
|
||||
}
|
||||
},
|
||||
"install_flasher_addon": {
|
||||
"title": "The Silicon Labs Flasher add-on installation has started"
|
||||
},
|
||||
"configure_flasher_addon": {
|
||||
"title": "The Silicon Labs Flasher add-on installation has started"
|
||||
},
|
||||
"start_flasher_addon": {
|
||||
"title": "Installing firmware",
|
||||
"description": "Zigbee firmware is now being installed. This will take a few minutes."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
@@ -2,15 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from ha_silabs_firmware_client import FirmwareManifest, FirmwareMetadata
|
||||
from universal_silabs_flasher.firmware import parse_firmware_image
|
||||
from universal_silabs_flasher.flasher import Flasher
|
||||
from yarl import URL
|
||||
|
||||
from homeassistant.components.update import (
|
||||
@@ -20,18 +17,12 @@ from homeassistant.components.update import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.restore_state import ExtraStoredData
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .coordinator import FirmwareUpdateCoordinator
|
||||
from .helpers import async_register_firmware_info_callback
|
||||
from .util import (
|
||||
ApplicationType,
|
||||
FirmwareInfo,
|
||||
guess_firmware_info,
|
||||
probe_silabs_firmware_info,
|
||||
)
|
||||
from .util import ApplicationType, FirmwareInfo, async_flash_silabs_firmware
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -249,19 +240,11 @@ class BaseFirmwareUpdateEntity(
|
||||
self._attr_update_percentage = round((offset * 100) / total_size)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@asynccontextmanager
|
||||
async def _temporarily_stop_hardware_owners(
|
||||
self, device: str
|
||||
) -> AsyncIterator[None]:
|
||||
"""Temporarily stop addons and integrations communicating with the device."""
|
||||
firmware_info = await guess_firmware_info(self.hass, device)
|
||||
_LOGGER.debug("Identified firmware info: %s", firmware_info)
|
||||
|
||||
async with AsyncExitStack() as stack:
|
||||
for owner in firmware_info.owners:
|
||||
await stack.enter_async_context(owner.temporarily_stop(self.hass))
|
||||
|
||||
yield
|
||||
# Switch to an indeterminate progress bar after installation is complete, since
|
||||
# we probe the firmware after flashing
|
||||
if offset == total_size:
|
||||
self._attr_update_percentage = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_install(
|
||||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
@@ -278,49 +261,18 @@ class BaseFirmwareUpdateEntity(
|
||||
fw_data = await self.coordinator.client.async_fetch_firmware(
|
||||
self._latest_firmware
|
||||
)
|
||||
fw_image = await self.hass.async_add_executor_job(parse_firmware_image, fw_data)
|
||||
|
||||
device = self._current_device
|
||||
try:
|
||||
firmware_info = await async_flash_silabs_firmware(
|
||||
hass=self.hass,
|
||||
device=self._current_device,
|
||||
fw_data=fw_data,
|
||||
expected_installed_firmware_type=self.entity_description.expected_firmware_type,
|
||||
bootloader_reset_type=self.bootloader_reset_type,
|
||||
progress_callback=self._update_progress,
|
||||
)
|
||||
finally:
|
||||
self._attr_in_progress = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
flasher = Flasher(
|
||||
device=device,
|
||||
probe_methods=(
|
||||
ApplicationType.GECKO_BOOTLOADER.as_flasher_application_type(),
|
||||
ApplicationType.EZSP.as_flasher_application_type(),
|
||||
ApplicationType.SPINEL.as_flasher_application_type(),
|
||||
ApplicationType.CPC.as_flasher_application_type(),
|
||||
),
|
||||
bootloader_reset=self.bootloader_reset_type,
|
||||
)
|
||||
|
||||
async with self._temporarily_stop_hardware_owners(device):
|
||||
try:
|
||||
try:
|
||||
# Enter the bootloader with indeterminate progress
|
||||
await flasher.enter_bootloader()
|
||||
|
||||
# Flash the firmware, with progress
|
||||
await flasher.flash_firmware(
|
||||
fw_image, progress_callback=self._update_progress
|
||||
)
|
||||
except Exception as err:
|
||||
raise HomeAssistantError("Failed to flash firmware") from err
|
||||
|
||||
# Probe the running application type with indeterminate progress
|
||||
self._attr_update_percentage = None
|
||||
self.async_write_ha_state()
|
||||
|
||||
firmware_info = await probe_silabs_firmware_info(
|
||||
device,
|
||||
probe_methods=(self.entity_description.expected_firmware_type,),
|
||||
)
|
||||
|
||||
if firmware_info is None:
|
||||
raise HomeAssistantError(
|
||||
"Failed to probe the firmware after flashing"
|
||||
)
|
||||
|
||||
self._firmware_info_callback(firmware_info)
|
||||
finally:
|
||||
self._attr_in_progress = False
|
||||
self.async_write_ha_state()
|
||||
self._firmware_info_callback(firmware_info)
|
||||
|
@@ -4,18 +4,20 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections import defaultdict
|
||||
from collections.abc import AsyncIterator, Iterable
|
||||
from contextlib import asynccontextmanager
|
||||
from collections.abc import AsyncIterator, Callable, Iterable
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
import logging
|
||||
|
||||
from universal_silabs_flasher.const import ApplicationType as FlasherApplicationType
|
||||
from universal_silabs_flasher.firmware import parse_firmware_image
|
||||
from universal_silabs_flasher.flasher import Flasher
|
||||
|
||||
from homeassistant.components.hassio import AddonError, AddonManager, AddonState
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.singleton import singleton
|
||||
|
||||
@@ -333,3 +335,52 @@ async def probe_silabs_firmware_type(
|
||||
return None
|
||||
|
||||
return fw_info.firmware_type
|
||||
|
||||
|
||||
async def async_flash_silabs_firmware(
|
||||
hass: HomeAssistant,
|
||||
device: str,
|
||||
fw_data: bytes,
|
||||
expected_installed_firmware_type: ApplicationType,
|
||||
bootloader_reset_type: str | None = None,
|
||||
progress_callback: Callable[[int, int], None] | None = None,
|
||||
) -> FirmwareInfo:
|
||||
"""Flash firmware to the SiLabs device."""
|
||||
firmware_info = await guess_firmware_info(hass, device)
|
||||
_LOGGER.debug("Identified firmware info: %s", firmware_info)
|
||||
|
||||
fw_image = await hass.async_add_executor_job(parse_firmware_image, fw_data)
|
||||
|
||||
flasher = Flasher(
|
||||
device=device,
|
||||
probe_methods=(
|
||||
ApplicationType.GECKO_BOOTLOADER.as_flasher_application_type(),
|
||||
ApplicationType.EZSP.as_flasher_application_type(),
|
||||
ApplicationType.SPINEL.as_flasher_application_type(),
|
||||
ApplicationType.CPC.as_flasher_application_type(),
|
||||
),
|
||||
bootloader_reset=bootloader_reset_type,
|
||||
)
|
||||
|
||||
async with AsyncExitStack() as stack:
|
||||
for owner in firmware_info.owners:
|
||||
await stack.enter_async_context(owner.temporarily_stop(hass))
|
||||
|
||||
try:
|
||||
# Enter the bootloader with indeterminate progress
|
||||
await flasher.enter_bootloader()
|
||||
|
||||
# Flash the firmware, with progress
|
||||
await flasher.flash_firmware(fw_image, progress_callback=progress_callback)
|
||||
except Exception as err:
|
||||
raise HomeAssistantError("Failed to flash firmware") from err
|
||||
|
||||
probed_firmware_info = await probe_silabs_firmware_info(
|
||||
device,
|
||||
probe_methods=(expected_installed_firmware_type,),
|
||||
)
|
||||
|
||||
if probed_firmware_info is None:
|
||||
raise HomeAssistantError("Failed to probe the firmware after flashing")
|
||||
|
||||
return probed_firmware_info
|
||||
|
@@ -32,6 +32,7 @@ from .const import (
|
||||
FIRMWARE,
|
||||
FIRMWARE_VERSION,
|
||||
MANUFACTURER,
|
||||
NABU_CASA_FIRMWARE_RELEASES_URL,
|
||||
PID,
|
||||
PRODUCT,
|
||||
SERIAL_NUMBER,
|
||||
@@ -45,19 +46,29 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class TranslationPlaceholderProtocol(Protocol):
|
||||
"""Protocol describing `BaseFirmwareInstallFlow`'s translation placeholders."""
|
||||
class FirmwareInstallFlowProtocol(Protocol):
|
||||
"""Protocol describing `BaseFirmwareInstallFlow` for a mixin."""
|
||||
|
||||
def _get_translation_placeholders(self) -> dict[str, str]:
|
||||
return {}
|
||||
|
||||
async def _install_firmware_step(
|
||||
self,
|
||||
fw_update_url: str,
|
||||
fw_type: str,
|
||||
firmware_name: str,
|
||||
expected_installed_firmware_type: ApplicationType,
|
||||
step_id: str,
|
||||
next_step_id: str,
|
||||
) -> ConfigFlowResult: ...
|
||||
|
||||
else:
|
||||
# Multiple inheritance with `Protocol` seems to break
|
||||
TranslationPlaceholderProtocol = object
|
||||
FirmwareInstallFlowProtocol = object
|
||||
|
||||
|
||||
class SkyConnectTranslationMixin(ConfigEntryBaseFlow, TranslationPlaceholderProtocol):
|
||||
"""Translation placeholder mixin for Home Assistant SkyConnect."""
|
||||
class SkyConnectFirmwareMixin(ConfigEntryBaseFlow, FirmwareInstallFlowProtocol):
|
||||
"""Mixin for Home Assistant SkyConnect firmware methods."""
|
||||
|
||||
context: ConfigFlowContext
|
||||
|
||||
@@ -72,9 +83,35 @@ class SkyConnectTranslationMixin(ConfigEntryBaseFlow, TranslationPlaceholderProt
|
||||
|
||||
return placeholders
|
||||
|
||||
async def async_step_install_zigbee_firmware(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Install Zigbee firmware."""
|
||||
return await self._install_firmware_step(
|
||||
fw_update_url=NABU_CASA_FIRMWARE_RELEASES_URL,
|
||||
fw_type="skyconnect_zigbee_ncp",
|
||||
firmware_name="Zigbee",
|
||||
expected_installed_firmware_type=ApplicationType.EZSP,
|
||||
step_id="install_zigbee_firmware",
|
||||
next_step_id="confirm_zigbee",
|
||||
)
|
||||
|
||||
async def async_step_install_thread_firmware(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Install Thread firmware."""
|
||||
return await self._install_firmware_step(
|
||||
fw_update_url=NABU_CASA_FIRMWARE_RELEASES_URL,
|
||||
fw_type="skyconnect_openthread_rcp",
|
||||
firmware_name="OpenThread",
|
||||
expected_installed_firmware_type=ApplicationType.SPINEL,
|
||||
step_id="install_thread_firmware",
|
||||
next_step_id="start_otbr_addon",
|
||||
)
|
||||
|
||||
|
||||
class HomeAssistantSkyConnectConfigFlow(
|
||||
SkyConnectTranslationMixin,
|
||||
SkyConnectFirmwareMixin,
|
||||
firmware_config_flow.BaseFirmwareConfigFlow,
|
||||
domain=DOMAIN,
|
||||
):
|
||||
@@ -207,7 +244,7 @@ class HomeAssistantSkyConnectMultiPanOptionsFlowHandler(
|
||||
|
||||
|
||||
class HomeAssistantSkyConnectOptionsFlowHandler(
|
||||
SkyConnectTranslationMixin, firmware_config_flow.BaseFirmwareOptionsFlow
|
||||
SkyConnectFirmwareMixin, firmware_config_flow.BaseFirmwareOptionsFlow
|
||||
):
|
||||
"""Zigbee and Thread options flow handlers."""
|
||||
|
||||
|
@@ -48,16 +48,6 @@
|
||||
"disable_multi_pan": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::uninstall_addon::data::disable_multi_pan%]"
|
||||
}
|
||||
},
|
||||
"install_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::install_flasher_addon::title%]"
|
||||
},
|
||||
"configure_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::configure_flasher_addon::title%]"
|
||||
},
|
||||
"start_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::start_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::start_flasher_addon::description%]"
|
||||
},
|
||||
"pick_firmware": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
|
||||
@@ -66,18 +56,6 @@
|
||||
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]"
|
||||
}
|
||||
},
|
||||
"install_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::install_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::install_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"run_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::run_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::run_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"zigbee_flasher_failed": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_flasher_failed::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_flasher_failed::description%]"
|
||||
},
|
||||
"confirm_zigbee": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_zigbee::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_zigbee::description%]"
|
||||
@@ -120,9 +98,7 @@
|
||||
"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%]",
|
||||
"start_otbr_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::start_addon%]",
|
||||
"install_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::install_zigbee_flasher_addon%]",
|
||||
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
|
||||
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
|
||||
"install_firmware": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::install_firmware%]"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
@@ -136,22 +112,6 @@
|
||||
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
|
||||
}
|
||||
},
|
||||
"install_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::install_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::install_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"run_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::run_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::run_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"uninstall_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::uninstall_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::uninstall_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"zigbee_flasher_failed": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_flasher_failed::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_flasher_failed::description%]"
|
||||
},
|
||||
"confirm_zigbee": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_zigbee::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_zigbee::description%]"
|
||||
@@ -191,9 +151,7 @@
|
||||
"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%]",
|
||||
"start_otbr_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::start_addon%]",
|
||||
"install_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::install_zigbee_flasher_addon%]",
|
||||
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
|
||||
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
|
||||
"install_firmware": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::install_firmware%]"
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any, final
|
||||
from typing import TYPE_CHECKING, Any, Protocol, final
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
@@ -31,6 +31,7 @@ from homeassistant.components.homeassistant_hardware.util import (
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_HARDWARE,
|
||||
ConfigEntry,
|
||||
ConfigEntryBaseFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
@@ -41,6 +42,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
FIRMWARE,
|
||||
FIRMWARE_VERSION,
|
||||
NABU_CASA_FIRMWARE_RELEASES_URL,
|
||||
RADIO_DEVICE,
|
||||
ZHA_DOMAIN,
|
||||
ZHA_HW_DISCOVERY_DATA,
|
||||
@@ -57,8 +59,59 @@ STEP_HW_SETTINGS_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
class HomeAssistantYellowConfigFlow(BaseFirmwareConfigFlow, domain=DOMAIN):
|
||||
class FirmwareInstallFlowProtocol(Protocol):
|
||||
"""Protocol describing `BaseFirmwareInstallFlow` for a mixin."""
|
||||
|
||||
async def _install_firmware_step(
|
||||
self,
|
||||
fw_update_url: str,
|
||||
fw_type: str,
|
||||
firmware_name: str,
|
||||
expected_installed_firmware_type: ApplicationType,
|
||||
step_id: str,
|
||||
next_step_id: str,
|
||||
) -> ConfigFlowResult: ...
|
||||
|
||||
else:
|
||||
# Multiple inheritance with `Protocol` seems to break
|
||||
FirmwareInstallFlowProtocol = object
|
||||
|
||||
|
||||
class YellowFirmwareMixin(ConfigEntryBaseFlow, FirmwareInstallFlowProtocol):
|
||||
"""Mixin for Home Assistant Yellow firmware methods."""
|
||||
|
||||
async def async_step_install_zigbee_firmware(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Install Zigbee firmware."""
|
||||
return await self._install_firmware_step(
|
||||
fw_update_url=NABU_CASA_FIRMWARE_RELEASES_URL,
|
||||
fw_type="yellow_zigbee_ncp",
|
||||
firmware_name="Zigbee",
|
||||
expected_installed_firmware_type=ApplicationType.EZSP,
|
||||
step_id="install_zigbee_firmware",
|
||||
next_step_id="confirm_zigbee",
|
||||
)
|
||||
|
||||
async def async_step_install_thread_firmware(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Install Thread firmware."""
|
||||
return await self._install_firmware_step(
|
||||
fw_update_url=NABU_CASA_FIRMWARE_RELEASES_URL,
|
||||
fw_type="yellow_openthread_rcp",
|
||||
firmware_name="OpenThread",
|
||||
expected_installed_firmware_type=ApplicationType.SPINEL,
|
||||
step_id="install_thread_firmware",
|
||||
next_step_id="start_otbr_addon",
|
||||
)
|
||||
|
||||
|
||||
class HomeAssistantYellowConfigFlow(
|
||||
YellowFirmwareMixin, BaseFirmwareConfigFlow, domain=DOMAIN
|
||||
):
|
||||
"""Handle a config flow for Home Assistant Yellow."""
|
||||
|
||||
VERSION = 1
|
||||
@@ -275,7 +328,9 @@ class HomeAssistantYellowMultiPanOptionsFlowHandler(
|
||||
|
||||
|
||||
class HomeAssistantYellowOptionsFlowHandler(
|
||||
BaseHomeAssistantYellowOptionsFlow, BaseFirmwareOptionsFlow
|
||||
YellowFirmwareMixin,
|
||||
BaseHomeAssistantYellowOptionsFlow,
|
||||
BaseFirmwareOptionsFlow,
|
||||
):
|
||||
"""Handle a firmware options flow for Home Assistant Yellow."""
|
||||
|
||||
|
@@ -71,16 +71,6 @@
|
||||
"disable_multi_pan": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::uninstall_addon::data::disable_multi_pan%]"
|
||||
}
|
||||
},
|
||||
"install_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::install_flasher_addon::title%]"
|
||||
},
|
||||
"configure_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::configure_flasher_addon::title%]"
|
||||
},
|
||||
"start_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::start_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::start_flasher_addon::description%]"
|
||||
},
|
||||
"pick_firmware": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
|
||||
@@ -89,18 +79,6 @@
|
||||
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]"
|
||||
}
|
||||
},
|
||||
"install_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::install_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::install_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"run_zigbee_flasher_addon": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::run_zigbee_flasher_addon::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::run_zigbee_flasher_addon::description%]"
|
||||
},
|
||||
"zigbee_flasher_failed": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_flasher_failed::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_flasher_failed::description%]"
|
||||
},
|
||||
"confirm_zigbee": {
|
||||
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_zigbee::title%]",
|
||||
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_zigbee::description%]"
|
||||
@@ -145,9 +123,7 @@
|
||||
"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%]",
|
||||
"start_otbr_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::start_addon%]",
|
||||
"install_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::install_zigbee_flasher_addon%]",
|
||||
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
|
||||
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
|
||||
"install_firmware": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::install_firmware%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
|
Reference in New Issue
Block a user