Z-WaveJS config flow: Change keys question (#147518)

Co-authored-by: Norbert Rittel <norbert@rittel.de>
This commit is contained in:
Petar Petrov 2025-06-27 18:27:43 +03:00 committed by Franck Nijhof
parent 41b9a7a9a3
commit 1829acd0e1
No known key found for this signature in database
GPG Key ID: AB33ADACE7101952
3 changed files with 289 additions and 56 deletions

View File

@ -40,7 +40,6 @@ from homeassistant.helpers.hassio import is_hassio
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
from homeassistant.helpers.service_info.usb import UsbServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from homeassistant.helpers.typing import VolDictType
from .addon import get_addon_manager
from .const import (
@ -90,6 +89,9 @@ ADDON_USER_INPUT_MAP = {
ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool})
MIN_MIGRATION_SDK_VERSION = AwesomeVersion("6.61")
NETWORK_TYPE_NEW = "new"
NETWORK_TYPE_EXISTING = "existing"
def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema:
"""Return a schema for the manual step."""
@ -632,6 +634,81 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for config for Z-Wave JS add-on."""
if user_input is not None:
self.usb_path = user_input[CONF_USB_PATH]
return await self.async_step_network_type()
if self._usb_discovery:
return await self.async_step_network_type()
usb_path = self.usb_path or ""
try:
ports = await async_get_usb_ports(self.hass)
except OSError as err:
_LOGGER.error("Failed to get USB ports: %s", err)
return self.async_abort(reason="usb_ports_failed")
data_schema = vol.Schema(
{
vol.Required(CONF_USB_PATH, default=usb_path): vol.In(ports),
}
)
return self.async_show_form(
step_id="configure_addon_user", data_schema=data_schema
)
async def async_step_network_type(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for network type (new or existing)."""
# For recommended installation, automatically set network type to "new"
if self._recommended_install:
user_input = {"network_type": NETWORK_TYPE_NEW}
if user_input is not None:
if user_input["network_type"] == NETWORK_TYPE_NEW:
# Set all keys to empty strings for new network
self.s0_legacy_key = ""
self.s2_access_control_key = ""
self.s2_authenticated_key = ""
self.s2_unauthenticated_key = ""
self.lr_s2_access_control_key = ""
self.lr_s2_authenticated_key = ""
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,
CONF_ADDON_S2_AUTHENTICATED_KEY: self.s2_authenticated_key,
CONF_ADDON_S2_UNAUTHENTICATED_KEY: self.s2_unauthenticated_key,
CONF_ADDON_LR_S2_ACCESS_CONTROL_KEY: self.lr_s2_access_control_key,
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
}
await self._async_set_addon_config(addon_config_updates)
return await self.async_step_start_addon()
# Network already exists, go to security keys step
return await self.async_step_configure_security_keys()
return self.async_show_form(
step_id="network_type",
data_schema=vol.Schema(
{
vol.Required("network_type", default=""): vol.In(
[NETWORK_TYPE_NEW, NETWORK_TYPE_EXISTING]
)
}
),
)
async def async_step_configure_security_keys(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask for security keys for existing Z-Wave network."""
addon_info = await self._async_get_addon_info()
addon_config = addon_info.options
@ -654,10 +731,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_ADDON_LR_S2_AUTHENTICATED_KEY, self.lr_s2_authenticated_key or ""
)
if self._recommended_install and self._usb_discovery:
# Recommended installation with USB discovery, skip asking for keys
user_input = {}
if user_input is not None:
self.s0_legacy_key = user_input.get(CONF_S0_LEGACY_KEY, s0_legacy_key)
self.s2_access_control_key = user_input.get(
@ -675,8 +748,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
self.lr_s2_authenticated_key = user_input.get(
CONF_LR_S2_AUTHENTICATED_KEY, lr_s2_authenticated_key
)
if not self._usb_discovery:
self.usb_path = user_input[CONF_USB_PATH]
addon_config_updates = {
CONF_ADDON_DEVICE: self.usb_path,
@ -689,14 +760,10 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
}
await self._async_set_addon_config(addon_config_updates)
return await self.async_step_start_addon()
usb_path = self.usb_path or addon_config.get(CONF_ADDON_DEVICE) or ""
schema: VolDictType = (
{}
if self._recommended_install
else {
data_schema = vol.Schema(
{
vol.Optional(CONF_S0_LEGACY_KEY, default=s0_legacy_key): str,
vol.Optional(
CONF_S2_ACCESS_CONTROL_KEY, default=s2_access_control_key
@ -716,22 +783,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
}
)
if not self._usb_discovery:
try:
ports = await async_get_usb_ports(self.hass)
except OSError as err:
_LOGGER.error("Failed to get USB ports: %s", err)
return self.async_abort(reason="usb_ports_failed")
schema = {
vol.Required(CONF_USB_PATH, default=usb_path): vol.In(ports),
**schema,
}
data_schema = vol.Schema(schema)
return self.async_show_form(
step_id="configure_addon_user", data_schema=data_schema
step_id="configure_security_keys", data_schema=data_schema
)
async def async_step_finish_addon_setup_user(

View File

@ -39,25 +39,37 @@
"step": {
"configure_addon_user": {
"data": {
"lr_s2_access_control_key": "Long Range S2 Access Control Key",
"lr_s2_authenticated_key": "Long Range S2 Authenticated Key",
"s0_legacy_key": "S0 Key (Legacy)",
"s2_access_control_key": "S2 Access Control Key",
"s2_authenticated_key": "S2 Authenticated Key",
"s2_unauthenticated_key": "S2 Unauthenticated Key",
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"description": "Select your Z-Wave adapter",
"title": "Enter the Z-Wave add-on configuration"
},
"network_type": {
"data": {
"network_type": "Is your network new or does it already exist?"
},
"title": "Z-Wave network"
},
"configure_security_keys": {
"data": {
"lr_s2_access_control_key": "Long Range S2 Access Control Key",
"lr_s2_authenticated_key": "Long Range S2 Authenticated Key",
"s0_legacy_key": "S0 Key (Legacy)",
"s2_access_control_key": "S2 Access Control Key",
"s2_authenticated_key": "S2 Authenticated Key",
"s2_unauthenticated_key": "S2 Unauthenticated Key"
},
"description": "Enter the security keys for your existing Z-Wave network",
"title": "Security keys"
},
"configure_addon_reconfigure": {
"data": {
"lr_s2_access_control_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::lr_s2_access_control_key%]",
"lr_s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::lr_s2_authenticated_key%]",
"s0_legacy_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s0_legacy_key%]",
"s2_access_control_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s2_access_control_key%]",
"s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s2_authenticated_key%]",
"s2_unauthenticated_key": "[%key:component::zwave_js::config::step::configure_addon_user::data::s2_unauthenticated_key%]",
"lr_s2_access_control_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::lr_s2_access_control_key%]",
"lr_s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::lr_s2_authenticated_key%]",
"s0_legacy_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s0_legacy_key%]",
"s2_access_control_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s2_access_control_key%]",
"s2_authenticated_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s2_authenticated_key%]",
"s2_unauthenticated_key": "[%key:component::zwave_js::config::step::configure_security_keys::data::s2_unauthenticated_key%]",
"usb_path": "[%key:common::config_flow::data::usb_path%]"
},
"description": "[%key:component::zwave_js::config::step::configure_addon_user::description%]",
@ -622,5 +634,13 @@
},
"name": "Set a value (advanced)"
}
},
"selector": {
"network_type": {
"options": {
"new": "It's new",
"existing": "It already exists"
}
}
}
}

View File

@ -29,12 +29,6 @@ from homeassistant.components.zwave_js.const import (
CONF_ADDON_S2_ACCESS_CONTROL_KEY,
CONF_ADDON_S2_AUTHENTICATED_KEY,
CONF_ADDON_S2_UNAUTHENTICATED_KEY,
CONF_LR_S2_ACCESS_CONTROL_KEY,
CONF_LR_S2_AUTHENTICATED_KEY,
CONF_S0_LEGACY_KEY,
CONF_S2_ACCESS_CONTROL_KEY,
CONF_S2_AUTHENTICATED_KEY,
CONF_S2_UNAUTHENTICATED_KEY,
CONF_USB_PATH,
DOMAIN,
)
@ -687,7 +681,17 @@ async def test_usb_discovery(
assert install_addon.call_args == call("core_zwave_js")
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_addon_user"
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@ -778,9 +782,18 @@ async def test_usb_discovery_addon_not_running(
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_addon_user"
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
# Make sure the discovered usb device is preferred.
data_schema = result["data_schema"]
assert data_schema is not None
assert data_schema({}) == {
@ -1126,6 +1139,25 @@ async def test_discovery_addon_not_running(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -1226,6 +1258,25 @@ async def test_discovery_addon_not_installed(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -1728,6 +1779,25 @@ async def test_addon_installed(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -1822,6 +1892,25 @@ async def test_addon_installed_start_failure(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -1911,6 +2000,25 @@ async def test_addon_installed_failures(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -1981,6 +2089,25 @@ async def test_addon_installed_set_options_failure(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -2091,6 +2218,25 @@ async def test_addon_installed_already_configured(
result["flow_id"],
{
"usb_path": "/new",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -2178,6 +2324,25 @@ async def test_addon_not_installed(
result["flow_id"],
{
"usb_path": "/test",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "network_type"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"network_type": "existing",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "configure_security_keys"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"s0_legacy_key": "new123",
"s2_access_control_key": "new456",
"s2_authenticated_key": "new789",
@ -4229,13 +4394,8 @@ async def test_intent_recommended_user(
assert result["step_id"] == "configure_addon_user"
data_schema = result["data_schema"]
assert data_schema is not None
assert data_schema.schema[CONF_USB_PATH] is not None
assert data_schema.schema.get(CONF_S0_LEGACY_KEY) is None
assert data_schema.schema.get(CONF_S2_ACCESS_CONTROL_KEY) is None
assert data_schema.schema.get(CONF_S2_AUTHENTICATED_KEY) is None
assert data_schema.schema.get(CONF_S2_UNAUTHENTICATED_KEY) is None
assert data_schema.schema.get(CONF_LR_S2_ACCESS_CONTROL_KEY) is None
assert data_schema.schema.get(CONF_LR_S2_AUTHENTICATED_KEY) is None
assert len(data_schema.schema) == 1
assert data_schema.schema.get(CONF_USB_PATH) is not None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],