Clean up Z-Wave config flow (#143670)

This commit is contained in:
Martin Hjelmare 2025-04-25 16:43:19 +02:00 committed by GitHub
parent 4adf5ce826
commit 5b1e32f51d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -25,6 +25,7 @@ from homeassistant.components.hassio import (
)
from homeassistant.config_entries import (
SOURCE_USB,
ConfigEntry,
ConfigEntryState,
ConfigFlow,
ConfigFlowResult,
@ -77,9 +78,6 @@ CONF_EMULATE_HARDWARE = "emulate_hardware"
CONF_LOG_LEVEL = "log_level"
SERVER_VERSION_TIMEOUT = 10
OPTIONS_INTENT_MIGRATE = "intent_migrate"
OPTIONS_INTENT_RECONFIGURE = "intent_reconfigure"
ADDON_LOG_LEVELS = {
"error": "Error",
"warn": "Warn",
@ -203,7 +201,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self.backup_data: bytes | None = None
self.backup_filepath: str | None = None
self.use_addon = False
self._reconfiguring = False
self._reconfigure_config_entry: ConfigEntry | None = None
self._usb_discovery = False
async def async_step_install_addon(
@ -266,8 +264,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Add-on start failed."""
if self._reconfiguring:
return await self.async_step_start_failed_reconfigure()
if self._reconfigure_config_entry:
return await self.async_revert_addon_config(reason="addon_start_failed")
return self.async_abort(reason="addon_start_failed")
async def _async_start_addon(self) -> None:
@ -305,7 +303,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on."""
if self._reconfiguring:
if self._reconfigure_config_entry:
return await self.async_step_configure_addon_reconfigure(user_input)
return await self.async_step_configure_addon_user(user_input)
@ -317,7 +315,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
Get add-on discovery info and server version info.
Set unique id and abort if already configured.
"""
if self._reconfiguring:
if self._reconfigure_config_entry:
return await self.async_step_finish_addon_setup_reconfigure(user_input)
return await self.async_step_finish_addon_setup_user(user_input)
@ -332,11 +330,25 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
return addon_info
async def _async_set_addon_config(self, config: dict) -> None:
async def _async_set_addon_config(self, config_updates: dict) -> None:
"""Set Z-Wave JS add-on config."""
addon_info = await self._async_get_addon_info()
addon_config = addon_info.options
new_addon_config = addon_config | config_updates
if new_addon_config == addon_config:
return
if addon_info.state == AddonState.RUNNING:
self.restart_addon = True
# Copy the add-on config to keep the objects separate.
self.original_addon_config = dict(addon_config)
# Remove legacy network_key
new_addon_config.pop(CONF_ADDON_NETWORK_KEY, None)
addon_manager: AddonManager = get_addon_manager(self.hass)
try:
await addon_manager.async_set_addon_options(config)
await addon_manager.async_set_addon_options(new_addon_config)
except AddonError as err:
_LOGGER.error(err)
raise AbortFlow("addon_set_config_failed") from err
@ -370,12 +382,12 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm if we are migrating adapters or just re-configuring."""
self._reconfiguring = True
self._reconfigure_config_entry = self._get_reconfigure_entry()
return self.async_show_menu(
step_id="reconfigure",
menu_options=[
OPTIONS_INTENT_RECONFIGURE,
OPTIONS_INTENT_MIGRATE,
"intent_reconfigure",
"intent_migrate",
],
)
@ -432,7 +444,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(
f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}"
)
self._abort_if_unique_id_configured()
# We don't need to check if the unique_id is already configured
# since we will update the unique_id before finishing the flow.
# The unique_id set above is just a temporary value to avoid
# duplicate discovery flows.
dev_path = discovery_info.device
self.usb_path = dev_path
if manufacturer == "Nabu Casa" and description == "ZWA-2 - Nabu Casa ZWA-2":
@ -598,8 +613,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
if not self._usb_discovery:
self.usb_path = user_input[CONF_USB_PATH]
new_addon_config = {
**addon_config,
addon_config_updates = {
CONF_ADDON_DEVICE: self.usb_path,
CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key,
CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_key,
@ -609,8 +623,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
}
if new_addon_config != addon_config:
await self._async_set_addon_config(new_addon_config)
await self._async_set_addon_config(addon_config_updates)
return await self.async_step_start_addon()
@ -730,11 +743,17 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
)
@callback
def _async_update_entry(self, data: dict[str, Any]) -> None:
def _async_update_entry(
self, updates: dict[str, Any], *, schedule_reload: bool = True
) -> None:
"""Update the config entry with new data."""
config_entry = self._reconfigure_config_entry
assert config_entry is not None
self.hass.config_entries.async_update_entry(
self._get_reconfigure_entry(), data=data
config_entry, data=config_entry.data | updates
)
if schedule_reload:
self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
async def async_step_intent_reconfigure(
self, user_input: dict[str, Any] | None = None
@ -749,7 +768,9 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm the user wants to reset their current controller."""
if not self._get_reconfigure_entry().data.get(CONF_USE_ADDON):
config_entry = self._reconfigure_config_entry
assert config_entry is not None
if not self._usb_discovery and not config_entry.data.get(CONF_USE_ADDON):
return self.async_abort(reason="addon_required")
if user_input is not None:
@ -834,7 +855,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a manual configuration."""
config_entry = self._get_reconfigure_entry()
config_entry = self._reconfigure_config_entry
assert config_entry is not None
if user_input is None:
return self.async_show_form(
step_id="manual_reconfigure",
@ -858,14 +880,12 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
# if the controller is reconfigured in a manual step.
self._async_update_entry(
{
**config_entry.data,
**user_input,
CONF_USE_ADDON: False,
CONF_INTEGRATION_CREATED_ADDON: False,
}
)
self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
return self.async_abort(reason="reconfigure_successful")
return self.async_show_form(
@ -878,7 +898,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle logic when on Supervisor host."""
config_entry = self._get_reconfigure_entry()
config_entry = self._reconfigure_config_entry
assert config_entry is not None
if user_input is None:
return self.async_show_form(
step_id="on_supervisor_reconfigure",
@ -914,7 +935,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on."""
config_entry = self._get_reconfigure_entry()
addon_info = await self._async_get_addon_info()
addon_config = addon_info.options
@ -927,8 +947,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self.lr_s2_authenticated_key = user_input[CONF_LR_S2_AUTHENTICATED_KEY]
self.usb_path = user_input[CONF_USB_PATH]
new_addon_config = {
**addon_config,
addon_config_updates = {
CONF_ADDON_DEVICE: self.usb_path,
CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key,
CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_key,
@ -942,19 +961,14 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
),
}
if new_addon_config != addon_config:
if addon_info.state == AddonState.RUNNING:
self.restart_addon = True
# Copy the add-on config to keep the objects separate.
self.original_addon_config = dict(addon_config)
# Remove legacy network_key
new_addon_config.pop(CONF_ADDON_NETWORK_KEY, None)
await self._async_set_addon_config(new_addon_config)
await self._async_set_addon_config(addon_config_updates)
if addon_info.state == AddonState.RUNNING and not self.restart_addon:
return await self.async_step_finish_addon_setup_reconfigure()
if config_entry.data.get(CONF_USE_ADDON):
if (
config_entry := self._reconfigure_config_entry
) and config_entry.data.get(CONF_USE_ADDON):
# Disconnect integration before restarting add-on.
await self.hass.config_entries.async_unload(config_entry.entry_id)
@ -1021,18 +1035,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Choose a serial port."""
if user_input is not None:
addon_info = await self._async_get_addon_info()
addon_config = addon_info.options
self.usb_path = user_input[CONF_USB_PATH]
new_addon_config = {
**addon_config,
CONF_ADDON_DEVICE: self.usb_path,
}
if addon_info.state == AddonState.RUNNING:
self.restart_addon = True
# Copy the add-on config to keep the objects separate.
self.original_addon_config = dict(addon_config)
await self._async_set_addon_config(new_addon_config)
await self._async_set_addon_config({CONF_ADDON_DEVICE: self.usb_path})
return await self.async_step_start_addon()
try:
@ -1050,12 +1054,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="choose_serial_port", data_schema=data_schema
)
async def async_step_start_failed_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Add-on start failed."""
return await self.async_revert_addon_config(reason="addon_start_failed")
async def async_step_backup_failed(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@ -1082,7 +1080,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
Get add-on discovery info and server version info.
Check for same unique id and abort if not the same unique id.
"""
config_entry = self._get_reconfigure_entry()
config_entry = self._reconfigure_config_entry
assert config_entry is not None
if self.revert_reason:
self.original_addon_config = None
reason = self.revert_reason
@ -1108,9 +1107,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self._async_update_entry(
{
**config_entry.data,
# this will only be different in a migration flow
"unique_id": str(self.version_info.home_id),
CONF_URL: self.ws_address,
CONF_USB_PATH: self.usb_path,
CONF_S0_LEGACY_KEY: self.s0_legacy_key,
@ -1126,8 +1122,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
if self.backup_data:
return await self.async_step_restore_nvm()
# Always reload entry since we may have disconnected the client.
self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
return self.async_abort(reason="reconfigure_successful")
async def async_revert_addon_config(self, reason: str) -> ConfigFlowResult:
@ -1143,9 +1137,9 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
)
if self.revert_reason or not self.original_addon_config:
self.hass.config_entries.async_schedule_reload(
self._get_reconfigure_entry().entry_id
)
config_entry = self._reconfigure_config_entry
assert config_entry is not None
self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
return self.async_abort(reason=reason)
self.revert_reason = reason
@ -1189,11 +1183,11 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
async def _async_restore_network_backup(self) -> None:
"""Restore the backup."""
assert self.backup_data is not None
config_entry = self._reconfigure_config_entry
assert config_entry is not None
# Reload the config entry to reconnect the client after the addon restart
await self.hass.config_entries.async_reload(
self._get_reconfigure_entry().entry_id
)
await self.hass.config_entries.async_reload(config_entry.entry_id)
@callback
def forward_progress(event: dict) -> None:
@ -1222,7 +1216,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
def _get_driver(self) -> Driver:
"""Get the driver from the config entry."""
config_entry = self._get_reconfigure_entry()
config_entry = self._reconfigure_config_entry
assert config_entry is not None
if config_entry.state != ConfigEntryState.LOADED:
raise AbortFlow("Configuration entry is not loaded")
client: Client = config_entry.runtime_data[DATA_CLIENT]