Display Z-Wave home IDs as hexadecimal (#161624)

This commit is contained in:
AlCalzone
2026-01-27 09:58:36 +01:00
committed by GitHub
parent b13c2e3018
commit 69ee3a15b6
8 changed files with 138 additions and 18 deletions

View File

@@ -71,6 +71,7 @@ from .const import (
ATTR_EVENT_TYPE,
ATTR_EVENT_TYPE_LABEL,
ATTR_HOME_ID,
ATTR_HOME_ID_HEX,
ATTR_LABEL,
ATTR_NODE_ID,
ATTR_PARAMETERS,
@@ -123,6 +124,7 @@ from .helpers import (
async_disable_server_logging_if_needed,
async_enable_server_logging_if_needed,
async_enable_statistics,
format_home_id_for_display,
get_device_id,
get_device_id_ext,
get_network_identifier_for_notification,
@@ -955,6 +957,7 @@ class NodeEvents:
ATTR_DOMAIN: DOMAIN,
ATTR_NODE_ID: notification.node.node_id,
ATTR_HOME_ID: driver.controller.home_id,
ATTR_HOME_ID_HEX: format_home_id_for_display(driver.controller.home_id),
ATTR_ENDPOINT: notification.endpoint,
ATTR_DEVICE_ID: device.id,
ATTR_COMMAND_CLASS: notification.command_class,
@@ -992,6 +995,7 @@ class NodeEvents:
ATTR_DOMAIN: DOMAIN,
ATTR_NODE_ID: notification.node.node_id,
ATTR_HOME_ID: driver.controller.home_id,
ATTR_HOME_ID_HEX: format_home_id_for_display(driver.controller.home_id),
ATTR_ENDPOINT: notification.endpoint_idx,
ATTR_DEVICE_ID: device.id,
ATTR_COMMAND_CLASS: notification.command_class,
@@ -1077,6 +1081,7 @@ class NodeEvents:
{
ATTR_NODE_ID: value.node.node_id,
ATTR_HOME_ID: driver.controller.home_id,
ATTR_HOME_ID_HEX: format_home_id_for_display(driver.controller.home_id),
ATTR_DEVICE_ID: device.id,
ATTR_ENTITY_ID: entity_id,
ATTR_COMMAND_CLASS: value.command_class,

View File

@@ -73,6 +73,7 @@ from .helpers import (
CannotConnect,
async_get_version_info,
async_wait_for_driver_ready_event,
format_home_id_for_display,
)
from .models import ZwaveJSConfigEntry
@@ -467,7 +468,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(home_id)
self._abort_if_unique_id_configured()
self.ws_address = f"ws://{discovery_info.host}:{discovery_info.port}"
self.context.update({"title_placeholders": {CONF_NAME: home_id}})
home_id_display = format_home_id_for_display(int(home_id))
self.context.update({"title_placeholders": {CONF_NAME: home_id_display}})
return await self.async_step_zeroconf_confirm()
async def async_step_zeroconf_confirm(
@@ -479,10 +481,11 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
assert self.ws_address
assert self.unique_id
home_id_display = format_home_id_for_display(int(self.unique_id))
return self.async_show_form(
step_id="zeroconf_confirm",
description_placeholders={
"home_id": self.unique_id,
"home_id": home_id_display,
CONF_URL: self.ws_address[5:],
},
)

View File

@@ -57,6 +57,7 @@ ZWAVE_JS_NOTIFICATION_EVENT = f"{DOMAIN}_notification"
ZWAVE_JS_VALUE_UPDATED_EVENT = f"{DOMAIN}_value_updated"
ATTR_NODE_ID = "node_id"
ATTR_HOME_ID = "home_id"
ATTR_HOME_ID_HEX = "home_id_hex"
ATTR_ENDPOINT = "endpoint"
ATTR_LABEL = "label"
ATTR_VALUE = "value"

View File

@@ -184,6 +184,13 @@ async def async_disable_server_logging_if_needed(
LOGGER.info("Zwave-js-server logging is enabled")
def format_home_id_for_display(home_id: int | None) -> str:
"""Format home ID as hexadecimal string for display."""
if home_id is None:
return "Unknown"
return f"0x{home_id:08x}"
def get_valueless_base_unique_id(driver: Driver, node: ZwaveNode) -> str:
"""Return the base unique ID for an entity that is not based on a value."""
return f"{driver.controller.home_id}.{node.node_id}"
@@ -555,9 +562,9 @@ def get_network_identifier_for_notification(
hass: HomeAssistant, config_entry: ZwaveJSConfigEntry, controller: Controller
) -> str:
"""Return the network identifier string for persistent notifications."""
home_id = str(controller.home_id)
home_id = format_home_id_for_display(controller.home_id)
if len(hass.config_entries.async_entries(DOMAIN)) > 1:
if str(home_id) != config_entry.title:
if home_id != config_entry.title:
return f"`{config_entry.title}`, with the home ID `{home_id}`,"
return f"with the home ID `{home_id}`"
return ""

View File

@@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from .const import DOMAIN
from .helpers import async_get_node_from_device_id
from .helpers import async_get_node_from_device_id, format_home_id_for_display
class DeviceConfigFileChangedFlow(RepairsFlow):
@@ -62,13 +62,26 @@ class MigrateUniqueIDFlow(RepairsFlow):
def __init__(self, data: dict[str, str]) -> None:
"""Initialize."""
# Format IDs for display
try:
new_unique_id_hex = format_home_id_for_display(int(data["new_unique_id"]))
except (ValueError, TypeError):
new_unique_id_hex = data["new_unique_id"]
try:
old_unique_id_hex = format_home_id_for_display(int(data["old_unique_id"]))
except (ValueError, TypeError):
old_unique_id_hex = data["old_unique_id"]
self.description_placeholders: dict[str, str] = {
"config_entry_title": data["config_entry_title"],
"controller_model": data["controller_model"],
"new_unique_id": data["new_unique_id"],
"old_unique_id": data["old_unique_id"],
"new_unique_id": new_unique_id_hex,
"old_unique_id": old_unique_id_hex,
}
self._config_entry_id: str = data["config_entry_id"]
self._new_unique_id: str = data["new_unique_id"]
async def async_step_init(
self, user_input: dict[str, str] | None = None
@@ -88,7 +101,7 @@ class MigrateUniqueIDFlow(RepairsFlow):
if config_entry is not None:
self.hass.config_entries.async_update_entry(
config_entry,
unique_id=self.description_placeholders["new_unique_id"],
unique_id=self._new_unique_id,
)
self.hass.config_entries.async_schedule_reload(config_entry.entry_id)
return self.async_create_entry(data={})

View File

@@ -12,6 +12,7 @@ from homeassistant.components.zwave_js.helpers import (
async_get_node_status_sensor_entity_id,
async_get_nodes_from_area_id,
async_get_provisioning_entry_from_device_id,
format_home_id_for_display,
get_value_state_schema,
)
from homeassistant.config_entries import ConfigEntryState
@@ -138,3 +139,18 @@ async def test_async_get_provisioning_entry_from_device_id(
):
result = await async_get_provisioning_entry_from_device_id(hass, device.id)
assert result == provisioning_entry
def test_format_home_id_for_display() -> None:
"""Test format_home_id_for_display."""
# Test with standard home ID
assert format_home_id_for_display(3245146787) == "0xc16d02a3"
# Test with zero
assert format_home_id_for_display(0) == "0x00000000"
# Test with max 32-bit value
assert format_home_id_for_display(4294967295) == "0xffffffff"
# Test with None
assert format_home_id_for_display(None) == "Unknown"

View File

@@ -2077,18 +2077,18 @@ async def test_identify_event(
assert len(notifications) == 1
assert list(notifications)[0] == msg_id
assert (
"network `Mock Title`, with the home ID `3245146787`"
"network `Mock Title`, with the home ID `0xc16d02a3`"
in notifications[msg_id]["message"]
)
async_dismiss(hass, msg_id)
# Test case where config entry title and home ID do match
hass.config_entries.async_update_entry(integration, title="3245146787")
hass.config_entries.async_update_entry(integration, title="0xc16d02a3")
client.driver.controller.receive_event(event)
notifications = async_get_persistent_notifications(hass)
assert len(notifications) == 1
assert list(notifications)[0] == msg_id
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]
assert "network with the home ID `0xc16d02a3`" in notifications[msg_id]["message"]
async def test_server_logging(
@@ -2241,13 +2241,13 @@ async def test_factory_reset_node(
assert len(notifications) == 1
assert list(notifications)[0] == msg_id
assert (
"network `Mock Title`, with the home ID `3245146787`"
"network `Mock Title`, with the home ID `0xc16d02a3`"
in notifications[msg_id]["message"]
)
async_dismiss(hass, msg_id)
# Test case where config entry title and home ID do match
hass.config_entries.async_update_entry(integration, title="3245146787")
hass.config_entries.async_update_entry(integration, title="0xc16d02a3")
add_event = Event(
type="node added",
data={
@@ -2264,7 +2264,7 @@ async def test_factory_reset_node(
notifications = async_get_persistent_notifications(hass)
assert len(notifications) == 1
assert list(notifications)[0] == msg_id
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]
assert "network with the home ID `0xc16d02a3`" in notifications[msg_id]["message"]
async def test_entity_available_when_node_dead(

View File

@@ -379,8 +379,8 @@ async def test_migrate_unique_id(
assert data["description_placeholders"] == {
"config_entry_title": "Z-Wave JS",
"controller_model": "ZW090",
"new_unique_id": "3245146787",
"old_unique_id": old_unique_id,
"new_unique_id": "0xc16d02a3",
"old_unique_id": "0x075bcd15",
}
# Apply fix
@@ -446,8 +446,8 @@ async def test_migrate_unique_id_missing_config_entry(
assert data["description_placeholders"] == {
"config_entry_title": "Z-Wave JS",
"controller_model": "ZW090",
"new_unique_id": "3245146787",
"old_unique_id": old_unique_id,
"new_unique_id": "0xc16d02a3",
"old_unique_id": "0x075bcd15",
}
# Apply fix
@@ -459,3 +459,78 @@ async def test_migrate_unique_id_missing_config_entry(
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 0
@pytest.mark.usefixtures("client")
async def test_migrate_unique_id_non_integer_ids(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the migrate unique id flow with non-integer unique IDs."""
old_unique_id = "non_numeric_id"
new_unique_id = "also_invalid"
config_entry = MockConfigEntry(
domain=DOMAIN,
title="Z-Wave JS",
data={
"url": "ws://test.org",
},
unique_id=old_unique_id,
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
# Manually create the repair issue with non-integer unique IDs
ir.async_create_issue(
hass,
DOMAIN,
f"migrate_unique_id.{config_entry.entry_id}",
data={
"config_entry_id": config_entry.entry_id,
"config_entry_title": "Z-Wave JS",
"controller_model": "ZW090",
"new_unique_id": new_unique_id,
"old_unique_id": old_unique_id,
},
is_fixable=True,
severity=ir.IssueSeverity.ERROR,
translation_key="migrate_unique_id",
)
await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
http_client = await hass_client()
# Assert the issue is present
await ws_client.send_json_auto_id({"type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
issue = msg["result"]["issues"][0]
issue_id = issue["issue_id"]
assert issue_id == f"migrate_unique_id.{config_entry.entry_id}"
data = await start_repair_fix_flow(http_client, DOMAIN, issue_id)
flow_id = data["flow_id"]
assert data["step_id"] == "confirm"
# The non-integer IDs should be displayed as-is
assert data["description_placeholders"] == {
"config_entry_title": "Z-Wave JS",
"controller_model": "ZW090",
"new_unique_id": new_unique_id,
"old_unique_id": old_unique_id,
}
# Apply fix
data = await process_repair_fix_flow(http_client, flow_id)
assert data["type"] == "create_entry"
assert config_entry.unique_id == new_unique_id
await ws_client.send_json_auto_id({"type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 0