1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-10 08:15:14 +01:00

Fix ZHA config entries using a URI without a port (#171164)

This commit is contained in:
puddly
2026-05-19 03:14:48 -04:00
committed by Franck Nijhof
parent 7ebaaf129a
commit e8295e14b1
6 changed files with 70 additions and 62 deletions
+35 -2
View File
@@ -5,6 +5,7 @@ import logging
from zoneinfo import ZoneInfo
import voluptuous as vol
from yarl import URL
from zha.application.const import BAUD_RATES, RadioType
from zha.application.gateway import Gateway
from zha.application.helpers import ZHAData
@@ -32,6 +33,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import ConfigType
from . import homeassistant_hardware, repairs, websocket_api
from .config_flow import ZhaConfigFlowHandler
from .const import (
CONF_BAUDRATE,
CONF_CUSTOM_QUIRKS_PATH,
@@ -43,6 +45,7 @@ from .const import (
CONF_ZIGPY,
DATA_ZHA,
DOMAIN,
LEGACY_ZEROCONF_PORT,
)
from .helpers import (
SIGNAL_ADD_ENTITIES,
@@ -301,7 +304,18 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
_LOGGER.debug(
"Migrating from version %s.%s",
config_entry.version,
config_entry.minor_version,
)
if (config_entry.version, config_entry.minor_version) > (
ZhaConfigFlowHandler.VERSION,
ZhaConfigFlowHandler.MINOR_VERSION,
):
# This means the user has downgraded from a future version
return False
if config_entry.version == 1:
data = {
@@ -361,5 +375,24 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
version=5,
)
_LOGGER.info("Migration to version %s successful", config_entry.version)
if config_entry.version == 5 and config_entry.minor_version < 2:
data = {**config_entry.data, CONF_DEVICE: {**config_entry.data[CONF_DEVICE]}}
device_path = data[CONF_DEVICE][CONF_DEVICE_PATH]
if device_path.startswith(("socket://", "tcp://")):
url = URL(device_path)
if url.explicit_port is None:
data[CONF_DEVICE][CONF_DEVICE_PATH] = str(
url.with_port(LEGACY_ZEROCONF_PORT)
)
hass.config_entries.async_update_entry(
config_entry, data=data, version=5, minor_version=2
)
_LOGGER.info(
"Migration to version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)
return True
+8 -2
View File
@@ -47,7 +47,13 @@ from homeassistant.helpers.service_info.usb import UsbServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from homeassistant.util import dt as dt_util
from .const import CONF_BAUDRATE, CONF_FLOW_CONTROL, CONF_RADIO_TYPE, DOMAIN
from .const import (
CONF_BAUDRATE,
CONF_FLOW_CONTROL,
CONF_RADIO_TYPE,
DOMAIN,
LEGACY_ZEROCONF_PORT,
)
from .helpers import get_config_entry_unique_id, get_zha_gateway
from .radio_manager import (
DEVICE_SCHEMA,
@@ -89,7 +95,6 @@ UPLOADED_BACKUP_FILE = "uploaded_backup_file"
REPAIR_MY_URL = "https://my.home-assistant.io/redirect/repairs/"
LEGACY_ZEROCONF_PORT = 6638
LEGACY_ZEROCONF_ESPHOME_API_PORT = 6053
ZEROCONF_SERVICE_TYPE = "_zigbee-coordinator._tcp.local."
@@ -760,6 +765,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
VERSION = 5
MINOR_VERSION = 2
async def _set_unique_id_and_update_ignored_flow(
self, unique_id: str, device_path: str
+2
View File
@@ -64,6 +64,8 @@ DEVICE_PAIRING_STATUS = "pairing_status"
DOMAIN = "zha"
LEGACY_ZEROCONF_PORT = 6638
GROUP_ID = "group_id"
-25
View File
@@ -1265,19 +1265,6 @@ def async_add_entities(
entities.clear()
def _clean_serial_port_path(path: str) -> str:
"""Clean the serial port path, applying corrections where necessary."""
if path.startswith("socket://"):
path = path.strip()
# Removes extraneous brackets from IP addresses (they don't parse in CPython 3.11.4)
if re.match(r"^socket://\[\d+\.\d+\.\d+\.\d+\]:\d+$", path):
path = path.replace("[", "").replace("]", "")
return path
CONF_ZHA_OPTIONS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_DEFAULT_LIGHT_TRANSITION, default=0): vol.All(
@@ -1316,18 +1303,6 @@ def create_zha_config(hass: HomeAssistant, ha_zha_data: HAZHAData) -> ZHAData:
assert ha_zha_data.config_entry is not None
assert ha_zha_data.yaml_config is not None
# Remove brackets around IP addresses, this no longer works in CPython 3.11.4
# This will be removed in 2023.11.0
path = ha_zha_data.config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
cleaned_path = _clean_serial_port_path(path)
if path != cleaned_path:
_LOGGER.debug("Cleaned serial port path %r -> %r", path, cleaned_path)
ha_zha_data.config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] = cleaned_path
hass.config_entries.async_update_entry(
ha_zha_data.config_entry, data=ha_zha_data.config_entry.data
)
# deep copy the yaml config to avoid modifying the original and to safely
# pass it to the ZHA library
app_config = copy.deepcopy(ha_zha_data.yaml_config.get(CONF_ZIGPY, {}))
@@ -100,7 +100,7 @@
'discovery_keys': dict({
}),
'domain': 'zha',
'minor_version': 1,
'minor_version': 2,
'options': dict({
'custom_configuration': dict({
'zha_alarm_options': dict({
+24 -32
View File
@@ -4,7 +4,7 @@ import asyncio
from collections.abc import Callable
import logging
import typing
from unittest.mock import AsyncMock, Mock, patch
from unittest.mock import AsyncMock, patch
import zoneinfo
import pytest
@@ -141,52 +141,44 @@ async def test_config_depreciation(hass: HomeAssistant, zha_config) -> None:
@pytest.mark.parametrize(
("path", "cleaned_path"),
("old_path", "new_path"),
[
# No corrections
("/dev/path1", "/dev/path1"),
("/dev/path1[asd]", "/dev/path1[asd]"),
("/dev/path1 ", "/dev/path1 "),
("/dev/ttyUSB0", "/dev/ttyUSB0"),
("socket://1.2.3.4:5678", "socket://1.2.3.4:5678"),
# Brackets around URI
("socket://[1.2.3.4]:5678", "socket://1.2.3.4:5678"),
# Spaces
("socket://dev/path1 ", "socket://dev/path1"),
# Both
("socket://[1.2.3.4]:5678 ", "socket://1.2.3.4:5678"),
("socket://1.2.3.4", "socket://1.2.3.4:6638"),
("tcp://hostname", "tcp://hostname:6638"),
("tcp://hostname:1234", "tcp://hostname:1234"),
("socket://[::1]", "socket://[::1]:6638"),
],
)
@patch(
"homeassistant.components.zha.websocket_api.async_load_api", Mock(return_value=True)
)
async def test_setup_with_v3_cleaning_uri(
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
async def test_migration_v5_explicit_socket_port(
old_path: str,
new_path: str,
hass: HomeAssistant,
path: str,
cleaned_path: str,
mock_zigpy_connect: ControllerApplication,
config_entry: MockConfigEntry,
) -> None:
"""Test migration of config entry from v3, applying corrections to the port path."""
config_entry_v4 = MockConfigEntry(
domain=DOMAIN,
"""Test that socket:// and tcp:// paths get an explicit default port."""
config_entry.add_to_hass(hass)
hass.config_entries.async_update_entry(
config_entry,
data={
CONF_RADIO_TYPE: DATA_RADIO_TYPE,
**config_entry.data,
CONF_DEVICE: {
CONF_DEVICE_PATH: path,
CONF_BAUDRATE: 115200,
CONF_FLOW_CONTROL: None,
**config_entry.data[CONF_DEVICE],
CONF_DEVICE_PATH: old_path,
},
},
version=5,
minor_version=1,
)
config_entry_v4.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry_v4.entry_id)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await hass.config_entries.async_unload(config_entry_v4.entry_id)
assert config_entry_v4.data[CONF_RADIO_TYPE] == DATA_RADIO_TYPE
assert config_entry_v4.data[CONF_DEVICE][CONF_DEVICE_PATH] == cleaned_path
assert config_entry_v4.version == 5
assert config_entry.version == 5
assert config_entry.minor_version == 2
assert config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] == new_path
@pytest.mark.parametrize(