Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions homeassistant/components/zha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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
Expand Down Expand Up @@ -32,6 +33,7 @@
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,
Expand All @@ -43,6 +45,7 @@
CONF_ZIGPY,
DATA_ZHA,
DOMAIN,
LEGACY_ZEROCONF_PORT,
)
from .helpers import (
SIGNAL_ADD_ENTITIES,
Expand Down Expand Up @@ -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
Comment thread
TheJulianJES marked this conversation as resolved.

if config_entry.version == 1:
data = {
Expand Down Expand Up @@ -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)
)
Comment thread
puddly marked this conversation as resolved.

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
10 changes: 8 additions & 2 deletions homeassistant/components/zha/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@
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,
Expand Down Expand Up @@ -87,7 +93,6 @@

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."
Expand Down Expand Up @@ -758,6 +763,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
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/zha/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@

DOMAIN = "zha"

LEGACY_ZEROCONF_PORT = 6638

GROUP_ID = "group_id"


Expand Down
25 changes: 0 additions & 25 deletions homeassistant/components/zha/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1263,19 +1263,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(
Expand Down Expand Up @@ -1314,18 +1301,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, {}))
Expand Down
2 changes: 1 addition & 1 deletion tests/components/zha/snapshots/test_diagnostics.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
'discovery_keys': dict({
}),
'domain': 'zha',
'minor_version': 1,
'minor_version': 2,
'options': dict({
'custom_configuration': dict({
'zha_alarm_options': dict({
Expand Down
56 changes: 24 additions & 32 deletions tests/components/zha/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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
Expand Down Expand Up @@ -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(
Expand Down
Loading