Make proper Z-Wave reconfigure flow (#143549)

* Make proper Z-Wave reconfigure flow

* Improve backup_failed string
This commit is contained in:
Martin Hjelmare 2025-04-25 13:19:03 +02:00 committed by GitHub
parent ff2c901930
commit 7c584ece23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 338 additions and 358 deletions

View File

@ -2,7 +2,6 @@
from __future__ import annotations
from abc import ABC, abstractmethod
import asyncio
from datetime import datetime
import logging
@ -26,12 +25,9 @@ from homeassistant.components.hassio import (
)
from homeassistant.config_entries import (
SOURCE_USB,
ConfigEntry,
ConfigEntryBaseFlow,
ConfigEntryState,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_NAME, CONF_URL
from homeassistant.core import HomeAssistant, callback
@ -177,8 +173,12 @@ async def async_get_usb_ports(hass: HomeAssistant) -> dict[str, str]:
return await hass.async_add_executor_job(get_usb_ports)
class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC):
"""Represent the base config flow for Z-Wave JS."""
class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Z-Wave JS."""
VERSION = 1
_title: str
def __init__(self) -> None:
"""Set up flow instance."""
@ -196,6 +196,15 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC):
self.install_task: asyncio.Task | None = None
self.start_task: asyncio.Task | None = None
self.version_info: VersionInfo | None = None
self.original_addon_config: dict[str, Any] | None = None
self.revert_reason: str | None = None
self.backup_task: asyncio.Task | None = None
self.restore_backup_task: asyncio.Task | None = None
self.backup_data: bytes | None = None
self.backup_filepath: str | None = None
self.use_addon = False
self._reconfiguring = False
self._usb_discovery = False
async def async_step_install_addon(
self, user_input: dict[str, Any] | None = None
@ -257,6 +266,8 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Add-on start failed."""
if self._reconfiguring:
return await self.async_step_start_failed_reconfigure()
return self.async_abort(reason="addon_start_failed")
async def _async_start_addon(self) -> None:
@ -290,13 +301,14 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC):
else:
raise CannotConnect("Failed to start Z-Wave JS add-on: timeout")
@abstractmethod
async def async_step_configure_addon(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on."""
if self._reconfiguring:
return await self.async_step_configure_addon_reconfigure(user_input)
return await self.async_step_configure_addon_user(user_input)
@abstractmethod
async def async_step_finish_addon_setup(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@ -305,6 +317,9 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC):
Get add-on discovery info and server version info.
Set unique id and abort if already configured.
"""
if self._reconfiguring:
return await self.async_step_finish_addon_setup_reconfigure(user_input)
return await self.async_step_finish_addon_setup_user(user_input)
async def _async_get_addon_info(self) -> AddonInfo:
"""Return and cache Z-Wave JS add-on info."""
@ -342,28 +357,6 @@ class BaseZwaveJSFlow(ConfigEntryBaseFlow, ABC):
return discovery_info_config
class ZWaveJSConfigFlow(BaseZwaveJSFlow, ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Z-Wave JS."""
VERSION = 1
_title: str
def __init__(self) -> None:
"""Set up flow instance."""
super().__init__()
self.use_addon = False
self._usb_discovery = False
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Return the options flow."""
return OptionsFlowHandler()
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@ -373,6 +366,19 @@ class ZWaveJSConfigFlow(BaseZwaveJSFlow, ConfigFlow, domain=DOMAIN):
return await self.async_step_manual()
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm if we are migrating adapters or just re-configuring."""
self._reconfiguring = True
return self.async_show_menu(
step_id="reconfigure",
menu_options=[
OPTIONS_INTENT_RECONFIGURE,
OPTIONS_INTENT_MIGRATE,
],
)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
@ -568,14 +574,14 @@ class ZWaveJSConfigFlow(BaseZwaveJSFlow, ConfigFlow, domain=DOMAIN):
self.lr_s2_authenticated_key = addon_config.get(
CONF_ADDON_LR_S2_AUTHENTICATED_KEY, ""
)
return await self.async_step_finish_addon_setup()
return await self.async_step_finish_addon_setup_user()
if addon_info.state == AddonState.NOT_RUNNING:
return await self.async_step_configure_addon()
return await self.async_step_configure_addon_user()
return await self.async_step_install_addon()
async def async_step_configure_addon(
async def async_step_configure_addon_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on."""
@ -661,7 +667,7 @@ class ZWaveJSConfigFlow(BaseZwaveJSFlow, ConfigFlow, domain=DOMAIN):
return self.async_show_form(step_id="configure_addon", data_schema=data_schema)
async def async_step_finish_addon_setup(
async def async_step_finish_addon_setup_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Prepare info needed to complete the config entry.
@ -723,35 +729,11 @@ class ZWaveJSConfigFlow(BaseZwaveJSFlow, ConfigFlow, domain=DOMAIN):
},
)
class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
"""Handle an options flow for Z-Wave JS."""
def __init__(self) -> None:
"""Set up the options flow."""
super().__init__()
self.original_addon_config: dict[str, Any] | None = None
self.revert_reason: str | None = None
self.backup_task: asyncio.Task | None = None
self.restore_backup_task: asyncio.Task | None = None
self.backup_data: bytes | None = None
self.backup_filepath: str | None = None
@callback
def _async_update_entry(self, data: dict[str, Any]) -> None:
"""Update the config entry with new data."""
self.hass.config_entries.async_update_entry(self.config_entry, data=data)
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm if we are migrating adapters or just re-configuring."""
return self.async_show_menu(
step_id="init",
menu_options=[
OPTIONS_INTENT_RECONFIGURE,
OPTIONS_INTENT_MIGRATE,
],
self.hass.config_entries.async_update_entry(
self._get_reconfigure_entry(), data=data
)
async def async_step_intent_reconfigure(
@ -759,15 +741,15 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
) -> ConfigFlowResult:
"""Manage the options."""
if is_hassio(self.hass):
return await self.async_step_on_supervisor()
return await self.async_step_on_supervisor_reconfigure()
return await self.async_step_manual()
return await self.async_step_manual_reconfigure()
async def async_step_intent_migrate(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm the user wants to reset their current controller."""
if not self.config_entry.data.get(CONF_USE_ADDON):
if not self._get_reconfigure_entry().data.get(CONF_USE_ADDON):
return self.async_abort(reason="addon_required")
if user_input is not None:
@ -837,7 +819,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
# reset the old controller
try:
await self._get_driver().async_hard_reset()
except FailedCommand as err:
except (AbortFlow, FailedCommand) as err:
_LOGGER.error("Failed to reset controller: %s", err)
return self.async_abort(reason="reset_failed")
@ -848,16 +830,15 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
},
)
async def async_step_manual(
async def async_step_manual_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a manual configuration."""
config_entry = self._get_reconfigure_entry()
if user_input is None:
return self.async_show_form(
step_id="manual",
data_schema=get_manual_schema(
{CONF_URL: self.config_entry.data[CONF_URL]}
),
step_id="manual_reconfigure",
data_schema=get_manual_schema({CONF_URL: config_entry.data[CONF_URL]}),
)
errors = {}
@ -870,43 +851,46 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
if self.config_entry.unique_id != str(version_info.home_id):
if config_entry.unique_id != str(version_info.home_id):
return self.async_abort(reason="different_device")
# Make sure we disable any add-on handling
# if the controller is reconfigured in a manual step.
self._async_update_entry(
{
**self.config_entry.data,
**config_entry.data,
**user_input,
CONF_USE_ADDON: False,
CONF_INTEGRATION_CREATED_ADDON: False,
}
)
self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id)
return self.async_create_entry(title=TITLE, data={})
self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
return self.async_abort(reason="reconfigure_successful")
return self.async_show_form(
step_id="manual", data_schema=get_manual_schema(user_input), errors=errors
step_id="manual_reconfigure",
data_schema=get_manual_schema(user_input),
errors=errors,
)
async def async_step_on_supervisor(
async def async_step_on_supervisor_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle logic when on Supervisor host."""
config_entry = self._get_reconfigure_entry()
if user_input is None:
return self.async_show_form(
step_id="on_supervisor",
step_id="on_supervisor_reconfigure",
data_schema=get_on_supervisor_schema(
{CONF_USE_ADDON: self.config_entry.data.get(CONF_USE_ADDON, True)}
{CONF_USE_ADDON: config_entry.data.get(CONF_USE_ADDON, True)}
),
)
if not user_input[CONF_USE_ADDON]:
if self.config_entry.data.get(CONF_USE_ADDON):
if config_entry.data.get(CONF_USE_ADDON):
# Unload the config entry before stopping the add-on.
await self.hass.config_entries.async_unload(self.config_entry.entry_id)
await self.hass.config_entries.async_unload(config_entry.entry_id)
addon_manager = get_addon_manager(self.hass)
_LOGGER.debug("Stopping Z-Wave JS add-on")
try:
@ -914,22 +898,23 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
except AddonError as err:
_LOGGER.error(err)
self.hass.config_entries.async_schedule_reload(
self.config_entry.entry_id
config_entry.entry_id
)
raise AbortFlow("addon_stop_failed") from err
return await self.async_step_manual()
return await self.async_step_manual_reconfigure()
addon_info = await self._async_get_addon_info()
if addon_info.state == AddonState.NOT_INSTALLED:
return await self.async_step_install_addon()
return await self.async_step_configure_addon()
return await self.async_step_configure_addon_reconfigure()
async def async_step_configure_addon(
async def async_step_configure_addon_reconfigure(
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
@ -967,11 +952,11 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
await self._async_set_addon_config(new_addon_config)
if addon_info.state == AddonState.RUNNING and not self.restart_addon:
return await self.async_step_finish_addon_setup()
return await self.async_step_finish_addon_setup_reconfigure()
if self.config_entry.data.get(CONF_USE_ADDON):
if config_entry.data.get(CONF_USE_ADDON):
# Disconnect integration before restarting add-on.
await self.hass.config_entries.async_unload(self.config_entry.entry_id)
await self.hass.config_entries.async_unload(config_entry.entry_id)
return await self.async_step_start_addon()
@ -1065,7 +1050,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
step_id="choose_serial_port", data_schema=data_schema
)
async def async_step_start_failed(
async def async_step_start_failed_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Add-on start failed."""
@ -1087,9 +1072,9 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Migration done."""
return self.async_create_entry(title=TITLE, data={})
return self.async_abort(reason="migration_successful")
async def async_step_finish_addon_setup(
async def async_step_finish_addon_setup_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Prepare info needed to complete the config entry update.
@ -1097,6 +1082,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
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()
if self.revert_reason:
self.original_addon_config = None
reason = self.revert_reason
@ -1115,14 +1101,14 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
except CannotConnect:
return await self.async_revert_addon_config(reason="cannot_connect")
if self.backup_data is None and self.config_entry.unique_id != str(
if self.backup_data is None and config_entry.unique_id != str(
self.version_info.home_id
):
return await self.async_revert_addon_config(reason="different_device")
self._async_update_entry(
{
**self.config_entry.data,
**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,
@ -1141,8 +1127,8 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
return await self.async_step_restore_nvm()
# Always reload entry since we may have disconnected the client.
self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id)
return self.async_create_entry(title=TITLE, data={})
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:
"""Abort the options flow.
@ -1157,7 +1143,9 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
)
if self.revert_reason or not self.original_addon_config:
self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id)
self.hass.config_entries.async_schedule_reload(
self._get_reconfigure_entry().entry_id
)
return self.async_abort(reason=reason)
self.revert_reason = reason
@ -1167,7 +1155,7 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
if addon_key in ADDON_USER_INPUT_MAP
}
_LOGGER.debug("Reverting add-on options, reason: %s", reason)
return await self.async_step_configure_addon(addon_config_input)
return await self.async_step_configure_addon_reconfigure(addon_config_input)
async def _async_backup_network(self) -> None:
"""Backup the current network."""
@ -1203,7 +1191,9 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
assert self.backup_data is not None
# Reload the config entry to reconnect the client after the addon restart
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
await self.hass.config_entries.async_reload(
self._get_reconfigure_entry().entry_id
)
@callback
def forward_progress(event: dict) -> None:
@ -1231,9 +1221,11 @@ class OptionsFlowHandler(BaseZwaveJSFlow, OptionsFlow):
unsub()
def _get_driver(self) -> Driver:
if self.config_entry.state != ConfigEntryState.LOADED:
"""Get the driver from the config entry."""
config_entry = self._get_reconfigure_entry()
if config_entry.state != ConfigEntryState.LOADED:
raise AbortFlow("Configuration entry is not loaded")
client: Client = self.config_entry.runtime_data[DATA_CLIENT]
client: Client = config_entry.runtime_data[DATA_CLIENT]
assert client.driver is not None
return client.driver

View File

@ -4,17 +4,22 @@
"addon_get_discovery_info_failed": "Failed to get Z-Wave add-on discovery info.",
"addon_info_failed": "Failed to get Z-Wave add-on info.",
"addon_install_failed": "Failed to install the Z-Wave add-on.",
"addon_required": "The Z-Wave migration flow requires the integration to be configured using the Z-Wave Supervisor add-on. You can still use the Backup and Restore buttons to migrate your network manually.",
"addon_set_config_failed": "Failed to set Z-Wave configuration.",
"addon_start_failed": "Failed to start the Z-Wave add-on.",
"addon_stop_failed": "Failed to stop the Z-Wave add-on.",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"backup_failed": "Failed to back up network.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"different_device": "The connected USB device is not the same as previously configured for this config entry. Please instead create a new config entry for the new device.",
"discovery_requires_supervisor": "Discovery requires the supervisor.",
"migration_successful": "Migration successful.",
"not_zwave_device": "Discovered device is not a Z-Wave device.",
"not_zwave_js_addon": "Discovered add-on is not the official Z-Wave add-on.",
"backup_failed": "Failed to backup network.",
"restore_failed": "Failed to restore network.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"reset_failed": "Failed to reset controller.",
"restore_failed": "Failed to restore network.",
"usb_ports_failed": "Failed to get USB devices."
},
"error": {
@ -42,6 +47,19 @@
"description": "The add-on will generate security keys if those fields are left empty.",
"title": "Enter the Z-Wave add-on configuration"
},
"configure_addon_reconfigure": {
"data": {
"emulate_hardware": "Emulate Hardware",
"log_level": "Log level",
"s0_legacy_key": "[%key:component::zwave_js::config::step::configure_addon::data::s0_legacy_key%]",
"s2_access_control_key": "[%key:component::zwave_js::config::step::configure_addon::data::s2_access_control_key%]",
"s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_addon::data::s2_authenticated_key%]",
"s2_unauthenticated_key": "[%key:component::zwave_js::config::step::configure_addon::data::s2_unauthenticated_key%]",
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"description": "[%key:component::zwave_js::config::step::configure_addon::description%]",
"title": "[%key:component::zwave_js::config::step::configure_addon::title%]"
},
"hassio_confirm": {
"title": "Set up Z-Wave integration with the Z-Wave add-on"
},
@ -53,6 +71,11 @@
"url": "[%key:common::config_flow::data::url%]"
}
},
"manual_reconfigure": {
"data": {
"url": "[%key:common::config_flow::data::url%]"
}
},
"on_supervisor": {
"data": {
"use_addon": "Use the Z-Wave Supervisor add-on"
@ -60,6 +83,13 @@
"description": "Do you want to use the Z-Wave Supervisor add-on?",
"title": "Select connection method"
},
"on_supervisor_reconfigure": {
"data": {
"use_addon": "[%key:component::zwave_js::config::step::on_supervisor::data::use_addon%]"
},
"description": "[%key:component::zwave_js::config::step::on_supervisor::description%]",
"title": "[%key:component::zwave_js::config::step::on_supervisor::title%]"
},
"start_addon": {
"title": "The Z-Wave add-on is starting."
},
@ -69,6 +99,28 @@
"zeroconf_confirm": {
"description": "Do you want to add the Z-Wave Server with home ID {home_id} found at {url} to Home Assistant?",
"title": "Discovered Z-Wave Server"
},
"reconfigure": {
"title": "Migrate or re-configure",
"description": "Are you migrating to a new controller or re-configuring the current controller?",
"menu_options": {
"intent_migrate": "Migrate to a new controller",
"intent_reconfigure": "Re-configure the current controller"
}
},
"intent_migrate": {
"title": "[%key:component::zwave_js::config::step::reconfigure::menu_options::intent_migrate%]",
"description": "Before setting up your new controller, your old controller needs to be reset. A backup will be performed first.\n\nDo you wish to continue?"
},
"instruct_unplug": {
"title": "Unplug your old controller",
"description": "Backup saved to \"{file_path}\"\n\nYour old controller has been reset. If the hardware is no longer needed, you can now unplug it.\n\nPlease make sure your new controller is plugged in before continuing."
},
"choose_serial_port": {
"data": {
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"title": "Select your Z-Wave device"
}
}
},
@ -213,90 +265,6 @@
"title": "Newer version of Z-Wave Server needed"
}
},
"options": {
"abort": {
"addon_get_discovery_info_failed": "[%key:component::zwave_js::config::abort::addon_get_discovery_info_failed%]",
"addon_info_failed": "[%key:component::zwave_js::config::abort::addon_info_failed%]",
"addon_install_failed": "[%key:component::zwave_js::config::abort::addon_install_failed%]",
"addon_set_config_failed": "[%key:component::zwave_js::config::abort::addon_set_config_failed%]",
"addon_start_failed": "[%key:component::zwave_js::config::abort::addon_start_failed%]",
"addon_stop_failed": "Failed to stop the Z-Wave add-on.",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"different_device": "The connected USB device is not the same as previously configured for this config entry. Please instead create a new config entry for the new device.",
"addon_required": "The Z-Wave migration flow requires the integration to be configured using the Z-Wave Supervisor add-on. You can still use the Backup and Restore buttons to migrate your network manually.",
"backup_failed": "[%key:component::zwave_js::config::abort::backup_failed%]",
"restore_failed": "[%key:component::zwave_js::config::abort::restore_failed%]",
"reset_failed": "[%key:component::zwave_js::config::abort::reset_failed%]",
"usb_ports_failed": "[%key:component::zwave_js::config::abort::usb_ports_failed%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_ws_url": "[%key:component::zwave_js::config::error::invalid_ws_url%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"progress": {
"install_addon": "[%key:component::zwave_js::config::progress::install_addon%]",
"start_addon": "[%key:component::zwave_js::config::progress::start_addon%]",
"backup_nvm": "[%key:component::zwave_js::config::progress::backup_nvm%]",
"restore_nvm": "[%key:component::zwave_js::config::progress::restore_nvm%]"
},
"step": {
"init": {
"title": "Migrate or re-configure",
"description": "Are you migrating to a new controller or re-configuring the current controller?",
"menu_options": {
"intent_migrate": "Migrate to a new controller",
"intent_reconfigure": "Re-configure the current controller"
}
},
"intent_migrate": {
"title": "[%key:component::zwave_js::options::step::init::menu_options::intent_migrate%]",
"description": "Before setting up your new controller, your old controller needs to be reset. A backup will be performed first.\n\nDo you wish to continue?"
},
"instruct_unplug": {
"title": "Unplug your old controller",
"description": "Backup saved to \"{file_path}\"\n\nYour old controller has been reset. If the hardware is no longer needed, you can now unplug it.\n\nPlease make sure your new controller is plugged in before continuing."
},
"configure_addon": {
"data": {
"emulate_hardware": "Emulate Hardware",
"log_level": "Log level",
"s0_legacy_key": "[%key:component::zwave_js::config::step::configure_addon::data::s0_legacy_key%]",
"s2_access_control_key": "[%key:component::zwave_js::config::step::configure_addon::data::s2_access_control_key%]",
"s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_addon::data::s2_authenticated_key%]",
"s2_unauthenticated_key": "[%key:component::zwave_js::config::step::configure_addon::data::s2_unauthenticated_key%]",
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"description": "[%key:component::zwave_js::config::step::configure_addon::description%]",
"title": "[%key:component::zwave_js::config::step::configure_addon::title%]"
},
"choose_serial_port": {
"data": {
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"title": "Select your Z-Wave device"
},
"install_addon": {
"title": "[%key:component::zwave_js::config::step::install_addon::title%]"
},
"manual": {
"data": {
"url": "[%key:common::config_flow::data::url%]"
}
},
"on_supervisor": {
"data": {
"use_addon": "[%key:component::zwave_js::config::step::on_supervisor::data::use_addon%]"
},
"description": "[%key:component::zwave_js::config::step::on_supervisor::description%]",
"title": "[%key:component::zwave_js::config::step::on_supervisor::title%]"
},
"start_addon": {
"title": "[%key:component::zwave_js::config::step::start_addon::title%]"
}
}
},
"services": {
"bulk_set_partial_config_parameters": {
"description": "Allows for bulk setting partial parameters. Useful when multiple partial parameters have to be set at the same time.",

File diff suppressed because it is too large Load Diff