Fix Fujitsu fglair authentication error and other issues (#125439)
* Use correct app credentials when europe is checked * Rework to add china as well * Use our own package since the maintainer of the original package is not responding * Revert to using rewardone's package * Import app credentials where needed instead of __init__ * Rework region selector * Bump config entry minor and add migration * Address commentspull/126146/head
parent
ac93570476
commit
e2f1c60981
|
@ -5,14 +5,14 @@ from __future__ import annotations
|
|||
from contextlib import suppress
|
||||
|
||||
from ayla_iot_unofficial import new_ayla_api
|
||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_ID, FGLAIR_APP_SECRET
|
||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_CREDENTIALS
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import API_TIMEOUT, CONF_EUROPE
|
||||
from .const import API_TIMEOUT, CONF_EUROPE, CONF_REGION, REGION_DEFAULT, REGION_EU
|
||||
from .coordinator import FGLairCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||
|
@ -22,12 +22,13 @@ type FGLairConfigEntry = ConfigEntry[FGLairCoordinator]
|
|||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> bool:
|
||||
"""Set up Fujitsu HVAC (based on Ayla IOT) from a config entry."""
|
||||
app_id, app_secret = FGLAIR_APP_CREDENTIALS[entry.data[CONF_REGION]]
|
||||
api = new_ayla_api(
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
FGLAIR_APP_ID,
|
||||
FGLAIR_APP_SECRET,
|
||||
europe=entry.data[CONF_EUROPE],
|
||||
app_id,
|
||||
app_secret,
|
||||
europe=entry.data[CONF_REGION] == REGION_EU,
|
||||
websession=aiohttp_client.async_get_clientsession(hass),
|
||||
timeout=API_TIMEOUT,
|
||||
)
|
||||
|
@ -48,3 +49,24 @@ async def async_unload_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> b
|
|||
await entry.runtime_data.api.async_sign_out()
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
if entry.version > 1:
|
||||
return False
|
||||
|
||||
if entry.version == 1:
|
||||
new_data = {**entry.data}
|
||||
if entry.minor_version < 2:
|
||||
is_europe = new_data.get(CONF_EUROPE, False)
|
||||
if is_europe:
|
||||
new_data[CONF_REGION] = REGION_EU
|
||||
else:
|
||||
new_data[CONF_REGION] = REGION_DEFAULT
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data=new_data, minor_version=2, version=1
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
@ -5,14 +5,15 @@ import logging
|
|||
from typing import Any
|
||||
|
||||
from ayla_iot_unofficial import AylaAuthError, new_ayla_api
|
||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_ID, FGLAIR_APP_SECRET
|
||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_CREDENTIALS
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
|
||||
|
||||
from .const import API_TIMEOUT, CONF_EUROPE, DOMAIN
|
||||
from .const import API_TIMEOUT, CONF_REGION, DOMAIN, REGION_DEFAULT, REGION_EU
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -21,7 +22,12 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
|||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_EUROPE): bool,
|
||||
vol.Required(CONF_REGION, default=REGION_DEFAULT): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=[region.lower() for region in FGLAIR_APP_CREDENTIALS],
|
||||
translation_key=CONF_REGION,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
||||
|
@ -34,18 +40,20 @@ STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
|||
class FGLairConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Fujitsu HVAC (based on Ayla IOT)."""
|
||||
|
||||
MINOR_VERSION = 2
|
||||
_reauth_entry: ConfigEntry | None = None
|
||||
|
||||
async def _async_validate_credentials(
|
||||
self, user_input: dict[str, Any]
|
||||
) -> dict[str, str]:
|
||||
errors: dict[str, str] = {}
|
||||
app_id, app_secret = FGLAIR_APP_CREDENTIALS[user_input[CONF_REGION]]
|
||||
api = new_ayla_api(
|
||||
user_input[CONF_USERNAME],
|
||||
user_input[CONF_PASSWORD],
|
||||
FGLAIR_APP_ID,
|
||||
FGLAIR_APP_SECRET,
|
||||
europe=user_input[CONF_EUROPE],
|
||||
app_id,
|
||||
app_secret,
|
||||
europe=user_input[CONF_REGION] == REGION_EU,
|
||||
websession=aiohttp_client.async_get_clientsession(self.hass),
|
||||
timeout=API_TIMEOUT,
|
||||
)
|
||||
|
|
|
@ -7,4 +7,7 @@ API_REFRESH = timedelta(minutes=5)
|
|||
|
||||
DOMAIN = "fujitsu_fglair"
|
||||
|
||||
CONF_REGION = "region"
|
||||
CONF_EUROPE = "is_europe"
|
||||
REGION_EU = "EU"
|
||||
REGION_DEFAULT = "default"
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["ayla-iot-unofficial==1.3.1"]
|
||||
"requirements": ["ayla-iot-unofficial==1.4.1"]
|
||||
}
|
||||
|
|
|
@ -4,12 +4,9 @@
|
|||
"user": {
|
||||
"title": "Enter your FGLair credentials",
|
||||
"data": {
|
||||
"is_europe": "Use european servers",
|
||||
"region": "Region",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"is_europe": "Allows the user to choose whether to use european servers or not since the API uses different endoint URLs for european vs non-european users"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
|
@ -29,5 +26,14 @@
|
|||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"region": {
|
||||
"options": {
|
||||
"default": "Other",
|
||||
"eu": "Europe",
|
||||
"cn": "China"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -532,7 +532,7 @@ autarco==3.0.0
|
|||
axis==62
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.3.1
|
||||
ayla-iot-unofficial==1.4.1
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
|
|
|
@ -481,7 +481,7 @@ autarco==3.0.0
|
|||
axis==62
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.3.1
|
||||
ayla-iot-unofficial==1.4.1
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
|
|
|
@ -7,7 +7,11 @@ from ayla_iot_unofficial import AylaApi
|
|||
from ayla_iot_unofficial.fujitsu_hvac import FanSpeed, FujitsuHVAC, OpMode, SwingMode
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.fujitsu_fglair.const import CONF_EUROPE, DOMAIN
|
||||
from homeassistant.components.fujitsu_fglair.const import (
|
||||
CONF_REGION,
|
||||
DOMAIN,
|
||||
REGION_DEFAULT,
|
||||
)
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
@ -57,15 +61,19 @@ def mock_ayla_api(mock_devices: list[AsyncMock]) -> Generator[AsyncMock]:
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
def mock_config_entry(request: pytest.FixtureRequest) -> MockConfigEntry:
|
||||
"""Return a regular config entry."""
|
||||
region = REGION_DEFAULT
|
||||
if hasattr(request, "param"):
|
||||
region = request.param
|
||||
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=TEST_USERNAME,
|
||||
data={
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_EUROPE: False,
|
||||
CONF_REGION: region,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -5,7 +5,11 @@ from unittest.mock import AsyncMock
|
|||
from ayla_iot_unofficial import AylaAuthError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.fujitsu_fglair.const import CONF_EUROPE, DOMAIN
|
||||
from homeassistant.components.fujitsu_fglair.const import (
|
||||
CONF_REGION,
|
||||
DOMAIN,
|
||||
REGION_DEFAULT,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@ -28,7 +32,7 @@ async def _initial_step(hass: HomeAssistant) -> FlowResult:
|
|||
{
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_EUROPE: False,
|
||||
CONF_REGION: REGION_DEFAULT,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -45,7 +49,7 @@ async def test_full_flow(
|
|||
assert result["data"] == {
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_EUROPE: False,
|
||||
CONF_REGION: REGION_DEFAULT,
|
||||
}
|
||||
|
||||
|
||||
|
@ -94,7 +98,7 @@ async def test_form_exceptions(
|
|||
{
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_EUROPE: False,
|
||||
CONF_REGION: REGION_DEFAULT,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -103,7 +107,7 @@ async def test_form_exceptions(
|
|||
assert result["data"] == {
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_EUROPE: False,
|
||||
CONF_REGION: REGION_DEFAULT,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
"""Test the initialization of fujitsu_fglair entities."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from ayla_iot_unofficial import AylaAuthError
|
||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_CREDENTIALS
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.fujitsu_fglair.const import API_REFRESH, DOMAIN
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.components.fujitsu_fglair.const import (
|
||||
API_REFRESH,
|
||||
API_TIMEOUT,
|
||||
CONF_EUROPE,
|
||||
CONF_REGION,
|
||||
DOMAIN,
|
||||
REGION_DEFAULT,
|
||||
REGION_EU,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
STATE_UNAVAILABLE,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import aiohttp_client, entity_registry as er
|
||||
|
||||
from . import entity_id, setup_integration
|
||||
from .conftest import TEST_PASSWORD, TEST_USERNAME
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
@ -35,6 +51,63 @@ async def test_auth_failure(
|
|||
assert hass.states.get(entity_id(mock_devices[1])).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mock_config_entry", FGLAIR_APP_CREDENTIALS.keys(), indirect=True
|
||||
)
|
||||
async def test_auth_regions(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_ayla_api: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_devices: list[AsyncMock],
|
||||
) -> None:
|
||||
"""Test that we use the correct credentials if europe is selected."""
|
||||
with patch(
|
||||
"homeassistant.components.fujitsu_fglair.new_ayla_api", return_value=AsyncMock()
|
||||
) as new_ayla_api_patch:
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
new_ayla_api_patch.assert_called_once_with(
|
||||
TEST_USERNAME,
|
||||
TEST_PASSWORD,
|
||||
FGLAIR_APP_CREDENTIALS[mock_config_entry.data[CONF_REGION]][0],
|
||||
FGLAIR_APP_CREDENTIALS[mock_config_entry.data[CONF_REGION]][1],
|
||||
europe=mock_config_entry.data[CONF_REGION] == "EU",
|
||||
websession=aiohttp_client.async_get_clientsession(hass),
|
||||
timeout=API_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_europe", [True, False])
|
||||
async def test_migrate_entry_v11_v12(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_ayla_api: AsyncMock,
|
||||
is_europe: bool,
|
||||
mock_devices: list[AsyncMock],
|
||||
) -> None:
|
||||
"""Test migration from schema 1.1 to 1.2."""
|
||||
v11_config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
unique_id=TEST_USERNAME,
|
||||
data={
|
||||
CONF_USERNAME: TEST_USERNAME,
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
CONF_EUROPE: is_europe,
|
||||
},
|
||||
)
|
||||
|
||||
await setup_integration(hass, v11_config_entry)
|
||||
updated_entry = hass.config_entries.async_get_entry(v11_config_entry.entry_id)
|
||||
|
||||
assert updated_entry.state is ConfigEntryState.LOADED
|
||||
assert updated_entry.version == 1
|
||||
assert updated_entry.minor_version == 2
|
||||
if is_europe:
|
||||
assert updated_entry.data[CONF_REGION] is REGION_EU
|
||||
else:
|
||||
assert updated_entry.data[CONF_REGION] is REGION_DEFAULT
|
||||
|
||||
|
||||
async def test_device_auth_failure(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
|
|
Loading…
Reference in New Issue