Allow multiple Airzone entries with different System IDs (#135397)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Álvaro Fernández Rojas 2025-01-22 18:41:58 +01:00 committed by GitHub
parent 3bbd7daa7f
commit 4e494aa393
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 100 additions and 13 deletions

View File

@ -86,7 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
options = ConnectionOptions(
entry.data[CONF_HOST],
entry.data[CONF_PORT],
entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID),
entry.data[CONF_ID],
)
airzone = AirzoneLocalApi(aiohttp_client.async_get_clientsession(hass), options)
@ -120,3 +120,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: AirzoneConfigEntry) -> bool:
"""Migrate an old entry."""
if entry.version == 1 and entry.minor_version < 2:
# Add missing CONF_ID
system_id = entry.data.get(CONF_ID, DEFAULT_SYSTEM_ID)
new_data = entry.data.copy()
new_data[CONF_ID] = system_id
hass.config_entries.async_update_entry(
entry,
data=new_data,
minor_version=2,
)
_LOGGER.info(
"Migration to configuration version %s.%s successful",
entry.version,
entry.minor_version,
)
return True

View File

@ -44,6 +44,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
_discovered_ip: str | None = None
_discovered_mac: str | None = None
MINOR_VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -53,6 +54,9 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
if CONF_ID not in user_input:
user_input[CONF_ID] = DEFAULT_SYSTEM_ID
self._async_abort_entries_match(user_input)
airzone = AirzoneLocalApi(
@ -60,7 +64,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
ConnectionOptions(
user_input[CONF_HOST],
user_input[CONF_PORT],
user_input.get(CONF_ID, DEFAULT_SYSTEM_ID),
user_input[CONF_ID],
),
)
@ -84,6 +88,9 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
)
title = f"Airzone {user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
if user_input[CONF_ID] != DEFAULT_SYSTEM_ID:
title += f" #{user_input[CONF_ID]}"
return self.async_create_entry(title=title, data=user_input)
return self.async_show_form(

View File

@ -275,6 +275,7 @@
'config_entry': dict({
'data': dict({
'host': '192.168.1.100',
'id': 0,
'port': 3000,
}),
'disabled_by': None,
@ -282,7 +283,7 @@
}),
'domain': 'airzone',
'entry_id': '6e7a0798c1734ba81d26ced0e690eaec',
'minor_version': 1,
'minor_version': 2,
'options': dict({
}),
'pref_disable_new_entities': False,

View File

@ -28,6 +28,7 @@ from .util import (
HVAC_MOCK,
HVAC_VERSION_MOCK,
HVAC_WEBSERVER_MOCK,
USER_INPUT,
)
from tests.common import MockConfigEntry
@ -81,7 +82,7 @@ async def test_form(hass: HomeAssistant) -> None:
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], CONFIG
result["flow_id"], USER_INPUT
)
await hass.async_block_till_done()
@ -94,7 +95,7 @@ async def test_form(hass: HomeAssistant) -> None:
assert result["title"] == f"Airzone {CONFIG[CONF_HOST]}:{CONFIG[CONF_PORT]}"
assert result["data"][CONF_HOST] == CONFIG[CONF_HOST]
assert result["data"][CONF_PORT] == CONFIG[CONF_PORT]
assert CONF_ID not in result["data"]
assert result["data"][CONF_ID] == CONFIG[CONF_ID]
assert len(mock_setup_entry.mock_calls) == 1
@ -129,7 +130,7 @@ async def test_form_invalid_system_id(hass: HomeAssistant) -> None:
),
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
)
assert result["type"] is FlowResultType.FORM
@ -154,7 +155,7 @@ async def test_form_invalid_system_id(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.CREATE_ENTRY
assert (
result["title"]
== f"Airzone {CONFIG_ID1[CONF_HOST]}:{CONFIG_ID1[CONF_PORT]}"
== f"Airzone {CONFIG_ID1[CONF_HOST]}:{CONFIG_ID1[CONF_PORT]} #{CONFIG_ID1[CONF_ID]}"
)
assert result["data"][CONF_HOST] == CONFIG_ID1[CONF_HOST]
assert result["data"][CONF_PORT] == CONFIG_ID1[CONF_PORT]
@ -167,6 +168,7 @@ async def test_form_duplicated_id(hass: HomeAssistant) -> None:
"""Test setting up duplicated entry."""
config_entry = MockConfigEntry(
minor_version=2,
data=CONFIG,
domain=DOMAIN,
unique_id="airzone_unique_id",
@ -174,7 +176,7 @@ async def test_form_duplicated_id(hass: HomeAssistant) -> None:
config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
)
assert result["type"] is FlowResultType.ABORT
@ -189,7 +191,7 @@ async def test_connection_error(hass: HomeAssistant) -> None:
side_effect=AirzoneError,
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
DOMAIN, context={"source": SOURCE_USER}, data=USER_INPUT
)
assert result["errors"] == {"base": "cannot_connect"}

View File

@ -25,6 +25,7 @@ async def test_coordinator_client_connector_error(hass: HomeAssistant) -> None:
"""Test ClientConnectorError on coordinator update."""
config_entry = MockConfigEntry(
minor_version=2,
data=CONFIG,
domain=DOMAIN,
unique_id="airzone_unique_id",
@ -74,6 +75,7 @@ async def test_coordinator_new_devices(
"""Test new devices on coordinator update."""
config_entry = MockConfigEntry(
minor_version=2,
data=CONFIG,
domain=DOMAIN,
unique_id="airzone_unique_id",

View File

@ -2,14 +2,16 @@
from unittest.mock import patch
from aioairzone.const import DEFAULT_SYSTEM_ID
from aioairzone.exceptions import HotWaterNotAvailable, InvalidMethod, SystemOutOfRange
from homeassistant.components.airzone.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .util import CONFIG, HVAC_MOCK, HVAC_VERSION_MOCK, HVAC_WEBSERVER_MOCK
from .util import CONFIG, HVAC_MOCK, HVAC_VERSION_MOCK, HVAC_WEBSERVER_MOCK, USER_INPUT
from tests.common import MockConfigEntry
@ -19,7 +21,11 @@ async def test_unique_id_migrate(
) -> None:
"""Test unique id migration."""
config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG)
config_entry = MockConfigEntry(
minor_version=2,
domain=DOMAIN,
data=CONFIG,
)
config_entry.add_to_hass(hass)
with (
@ -89,6 +95,7 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
"""Test unload."""
config_entry = MockConfigEntry(
minor_version=2,
data=CONFIG,
domain=DOMAIN,
unique_id="airzone_unique_id",
@ -112,3 +119,42 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.NOT_LOADED
async def test_migrate_entry_v2(hass: HomeAssistant) -> None:
"""Test entry migration to v2."""
config_entry = MockConfigEntry(
minor_version=1,
data=USER_INPUT,
domain=DOMAIN,
)
config_entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.airzone.AirzoneLocalApi.get_dhw",
side_effect=HotWaterNotAvailable,
),
patch(
"homeassistant.components.airzone.AirzoneLocalApi.get_hvac",
return_value=HVAC_MOCK,
),
patch(
"homeassistant.components.airzone.AirzoneLocalApi.get_hvac_systems",
side_effect=SystemOutOfRange,
),
patch(
"homeassistant.components.airzone.AirzoneLocalApi.get_version",
return_value=HVAC_VERSION_MOCK,
),
patch(
"homeassistant.components.airzone.AirzoneLocalApi.get_webserver",
side_effect=InvalidMethod,
),
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.minor_version == 2
assert config_entry.data.get(CONF_ID) == DEFAULT_SYSTEM_ID

View File

@ -55,6 +55,7 @@ from aioairzone.const import (
API_WS_AZ,
API_WS_TYPE,
API_ZONE_ID,
DEFAULT_SYSTEM_ID,
)
from homeassistant.components.airzone.const import DOMAIN
@ -63,13 +64,18 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
CONFIG = {
USER_INPUT = {
CONF_HOST: "192.168.1.100",
CONF_PORT: 3000,
}
CONFIG = {
**USER_INPUT,
CONF_ID: DEFAULT_SYSTEM_ID,
}
CONFIG_ID1 = {
**CONFIG,
**USER_INPUT,
CONF_ID: 1,
}
@ -359,6 +365,7 @@ async def async_init_integration(
"""Set up the Airzone integration in Home Assistant."""
config_entry = MockConfigEntry(
minor_version=2,
data=CONFIG,
entry_id="6e7a0798c1734ba81d26ced0e690eaec",
domain=DOMAIN,