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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,6 +64,8 @@ DEVICE_PAIRING_STATUS = "pairing_status"
|
||||
|
||||
DOMAIN = "zha"
|
||||
|
||||
LEGACY_ZEROCONF_PORT = 6638
|
||||
|
||||
GROUP_ID = "group_id"
|
||||
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user