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 ( from homeassistant.config_entries import (
SOURCE_USB, SOURCE_USB,
ConfigEntry,
ConfigEntryState, ConfigEntryState,
ConfigFlow, ConfigFlow,
ConfigFlowResult, ConfigFlowResult,
@ -77,9 +78,6 @@ CONF_EMULATE_HARDWARE = "emulate_hardware"
CONF_LOG_LEVEL = "log_level" CONF_LOG_LEVEL = "log_level"
SERVER_VERSION_TIMEOUT = 10 SERVER_VERSION_TIMEOUT = 10
OPTIONS_INTENT_MIGRATE = "intent_migrate"
OPTIONS_INTENT_RECONFIGURE = "intent_reconfigure"
ADDON_LOG_LEVELS = { ADDON_LOG_LEVELS = {
"error": "Error", "error": "Error",
"warn": "Warn", "warn": "Warn",
@ -203,7 +201,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self.backup_data: bytes | None = None self.backup_data: bytes | None = None
self.backup_filepath: str | None = None self.backup_filepath: str | None = None
self.use_addon = False self.use_addon = False
self._reconfiguring = False self._reconfigure_config_entry: ConfigEntry | None = None
self._usb_discovery = False self._usb_discovery = False
async def async_step_install_addon( async def async_step_install_addon(
@ -266,8 +264,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Add-on start failed.""" """Add-on start failed."""
if self._reconfiguring: if self._reconfigure_config_entry:
return await self.async_step_start_failed_reconfigure() return await self.async_revert_addon_config(reason="addon_start_failed")
return self.async_abort(reason="addon_start_failed") return self.async_abort(reason="addon_start_failed")
async def _async_start_addon(self) -> None: async def _async_start_addon(self) -> None:
@ -305,7 +303,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on.""" """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_reconfigure(user_input)
return await self.async_step_configure_addon_user(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. Get add-on discovery info and server version info.
Set unique id and abort if already configured. 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_reconfigure(user_input)
return await self.async_step_finish_addon_setup_user(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 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.""" """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) addon_manager: AddonManager = get_addon_manager(self.hass)
try: try:
await addon_manager.async_set_addon_options(config) await addon_manager.async_set_addon_options(new_addon_config)
except AddonError as err: except AddonError as err:
_LOGGER.error(err) _LOGGER.error(err)
raise AbortFlow("addon_set_config_failed") from 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 self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Confirm if we are migrating adapters or just re-configuring.""" """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( return self.async_show_menu(
step_id="reconfigure", step_id="reconfigure",
menu_options=[ menu_options=[
OPTIONS_INTENT_RECONFIGURE, "intent_reconfigure",
OPTIONS_INTENT_MIGRATE, "intent_migrate",
], ],
) )
@ -432,7 +444,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id( await self.async_set_unique_id(
f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}" 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 dev_path = discovery_info.device
self.usb_path = dev_path self.usb_path = dev_path
if manufacturer == "Nabu Casa" and description == "ZWA-2 - Nabu Casa ZWA-2": 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: if not self._usb_discovery:
self.usb_path = user_input[CONF_USB_PATH] self.usb_path = user_input[CONF_USB_PATH]
new_addon_config = { addon_config_updates = {
**addon_config,
CONF_ADDON_DEVICE: self.usb_path, CONF_ADDON_DEVICE: self.usb_path,
CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key, CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key,
CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_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, CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
} }
if new_addon_config != addon_config: await self._async_set_addon_config(addon_config_updates)
await self._async_set_addon_config(new_addon_config)
return await self.async_step_start_addon() return await self.async_step_start_addon()
@ -730,11 +743,17 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
) )
@callback @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.""" """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.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( async def async_step_intent_reconfigure(
self, user_input: dict[str, Any] | None = None 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 self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Confirm the user wants to reset their current controller.""" """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") return self.async_abort(reason="addon_required")
if user_input is not None: if user_input is not None:
@ -834,7 +855,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle a manual configuration.""" """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: if user_input is None:
return self.async_show_form( return self.async_show_form(
step_id="manual_reconfigure", step_id="manual_reconfigure",
@ -858,14 +880,12 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
# if the controller is reconfigured in a manual step. # if the controller is reconfigured in a manual step.
self._async_update_entry( self._async_update_entry(
{ {
**config_entry.data,
**user_input, **user_input,
CONF_USE_ADDON: False, CONF_USE_ADDON: False,
CONF_INTEGRATION_CREATED_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_abort(reason="reconfigure_successful")
return self.async_show_form( return self.async_show_form(
@ -878,7 +898,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle logic when on Supervisor host.""" """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: if user_input is None:
return self.async_show_form( return self.async_show_form(
step_id="on_supervisor_reconfigure", step_id="on_supervisor_reconfigure",
@ -914,7 +935,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on.""" """Ask for config for Z-Wave JS add-on."""
config_entry = self._get_reconfigure_entry()
addon_info = await self._async_get_addon_info() addon_info = await self._async_get_addon_info()
addon_config = addon_info.options 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.lr_s2_authenticated_key = user_input[CONF_LR_S2_AUTHENTICATED_KEY]
self.usb_path = user_input[CONF_USB_PATH] self.usb_path = user_input[CONF_USB_PATH]
new_addon_config = { addon_config_updates = {
**addon_config,
CONF_ADDON_DEVICE: self.usb_path, CONF_ADDON_DEVICE: self.usb_path,
CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key, CONF_ADDON_S0_LEGACY_KEY: self.s0_legacy_key,
CONF_ADDON_S2_ACCESS_CONTROL_KEY: self.s2_access_control_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: await self._async_set_addon_config(addon_config_updates)
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)
if addon_info.state == AddonState.RUNNING and not self.restart_addon: if addon_info.state == AddonState.RUNNING and not self.restart_addon:
return await self.async_step_finish_addon_setup_reconfigure() 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. # Disconnect integration before restarting add-on.
await self.hass.config_entries.async_unload(config_entry.entry_id) await self.hass.config_entries.async_unload(config_entry.entry_id)
@ -1021,18 +1035,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Choose a serial port.""" """Choose a serial port."""
if user_input is not None: 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] self.usb_path = user_input[CONF_USB_PATH]
new_addon_config = { await self._async_set_addon_config({CONF_ADDON_DEVICE: self.usb_path})
**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)
return await self.async_step_start_addon() return await self.async_step_start_addon()
try: try:
@ -1050,12 +1054,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="choose_serial_port", data_schema=data_schema 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( async def async_step_backup_failed(
self, user_input: dict[str, Any] | None = None self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult: ) -> ConfigFlowResult:
@ -1082,7 +1080,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
Get add-on discovery info and server version info. Get add-on discovery info and server version info.
Check for same unique id and abort if not the same unique id. 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: if self.revert_reason:
self.original_addon_config = None self.original_addon_config = None
reason = self.revert_reason reason = self.revert_reason
@ -1108,9 +1107,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self._async_update_entry( 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_URL: self.ws_address,
CONF_USB_PATH: self.usb_path, CONF_USB_PATH: self.usb_path,
CONF_S0_LEGACY_KEY: self.s0_legacy_key, CONF_S0_LEGACY_KEY: self.s0_legacy_key,
@ -1126,8 +1122,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
if self.backup_data: if self.backup_data:
return await self.async_step_restore_nvm() 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") return self.async_abort(reason="reconfigure_successful")
async def async_revert_addon_config(self, reason: str) -> ConfigFlowResult: 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: if self.revert_reason or not self.original_addon_config:
self.hass.config_entries.async_schedule_reload( config_entry = self._reconfigure_config_entry
self._get_reconfigure_entry().entry_id assert config_entry is not None
) self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
return self.async_abort(reason=reason) return self.async_abort(reason=reason)
self.revert_reason = reason self.revert_reason = reason
@ -1189,11 +1183,11 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
async def _async_restore_network_backup(self) -> None: async def _async_restore_network_backup(self) -> None:
"""Restore the backup.""" """Restore the backup."""
assert self.backup_data is not None 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 # Reload the config entry to reconnect the client after the addon restart
await self.hass.config_entries.async_reload( await self.hass.config_entries.async_reload(config_entry.entry_id)
self._get_reconfigure_entry().entry_id
)
@callback @callback
def forward_progress(event: dict) -> None: def forward_progress(event: dict) -> None:
@ -1222,7 +1216,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
def _get_driver(self) -> Driver: def _get_driver(self) -> Driver:
"""Get the driver from the config entry.""" """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: if config_entry.state != ConfigEntryState.LOADED:
raise AbortFlow("Configuration entry is not loaded") raise AbortFlow("Configuration entry is not loaded")
client: Client = config_entry.runtime_data[DATA_CLIENT] client: Client = config_entry.runtime_data[DATA_CLIENT]