Skip to content

Commit 080a7dc

Browse files
authored
Allow more device types for Vodafone Station (home-assistant#153990)
1 parent 3e20c50 commit 080a7dc

File tree

12 files changed

+271
-89
lines changed

12 files changed

+271
-89
lines changed

homeassistant/components/vodafone_station/__init__.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""Vodafone Station integration."""
22

3-
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
3+
from aiohttp import ClientSession, CookieJar
4+
from aiovodafone.api import VodafoneStationCommonApi
5+
6+
from homeassistant.const import CONF_HOST, Platform
47
from homeassistant.core import HomeAssistant
58

9+
from .const import _LOGGER, CONF_DEVICE_DETAILS, DEVICE_TYPE, DEVICE_URL
610
from .coordinator import VodafoneConfigEntry, VodafoneStationRouter
711
from .utils import async_client_session
812

@@ -14,9 +18,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) ->
1418
session = await async_client_session(hass)
1519
coordinator = VodafoneStationRouter(
1620
hass,
17-
entry.data[CONF_HOST],
18-
entry.data[CONF_USERNAME],
19-
entry.data[CONF_PASSWORD],
2021
entry,
2122
session,
2223
)
@@ -30,6 +31,46 @@ async def async_setup_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) ->
3031
return True
3132

3233

34+
async def async_migrate_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) -> bool:
35+
"""Migrate old entry."""
36+
if entry.version == 1 and entry.minor_version == 1:
37+
_LOGGER.debug(
38+
"Migrating from version %s.%s", entry.version, entry.minor_version
39+
)
40+
41+
jar = CookieJar(unsafe=True, quote_cookie=False)
42+
session = ClientSession(cookie_jar=jar)
43+
44+
try:
45+
device_type, url = await VodafoneStationCommonApi.get_device_type(
46+
entry.data[CONF_HOST],
47+
session,
48+
)
49+
finally:
50+
await session.close()
51+
52+
# Save device details to config entry
53+
new_data = entry.data.copy()
54+
new_data.update(
55+
{
56+
CONF_DEVICE_DETAILS: {
57+
DEVICE_TYPE: device_type,
58+
DEVICE_URL: str(url),
59+
}
60+
},
61+
)
62+
63+
hass.config_entries.async_update_entry(
64+
entry, data=new_data, version=1, minor_version=2
65+
)
66+
67+
_LOGGER.info(
68+
"Migration to version %s.%s successful", entry.version, entry.minor_version
69+
)
70+
71+
return True
72+
73+
3374
async def async_unload_entry(hass: HomeAssistant, entry: VodafoneConfigEntry) -> bool:
3475
"""Unload a config entry."""
3576
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):

homeassistant/components/vodafone_station/config_flow.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from collections.abc import Mapping
66
from typing import Any
77

8-
from aiovodafone import VodafoneStationSercommApi, exceptions as aiovodafone_exceptions
8+
from aiovodafone import exceptions as aiovodafone_exceptions
9+
from aiovodafone.api import VodafoneStationCommonApi, init_api_class
910
import voluptuous as vol
1011

1112
from homeassistant.components.device_tracker import (
@@ -20,7 +21,15 @@
2021
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
2122
from homeassistant.core import HomeAssistant, callback
2223

23-
from .const import _LOGGER, DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN
24+
from .const import (
25+
_LOGGER,
26+
CONF_DEVICE_DETAILS,
27+
DEFAULT_HOST,
28+
DEFAULT_USERNAME,
29+
DEVICE_TYPE,
30+
DEVICE_URL,
31+
DOMAIN,
32+
)
2433
from .coordinator import VodafoneConfigEntry
2534
from .utils import async_client_session
2635

@@ -40,26 +49,37 @@ def user_form_schema(user_input: dict[str, Any] | None) -> vol.Schema:
4049
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
4150

4251

43-
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
52+
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
4453
"""Validate the user input allows us to connect."""
4554

4655
session = await async_client_session(hass)
47-
api = VodafoneStationSercommApi(
48-
data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD], session
56+
57+
device_type, url = await VodafoneStationCommonApi.get_device_type(
58+
data[CONF_HOST],
59+
session,
4960
)
5061

62+
api = init_api_class(url, device_type, data, session)
63+
5164
try:
5265
await api.login()
5366
finally:
5467
await api.logout()
5568

56-
return {"title": data[CONF_HOST]}
69+
return {
70+
"title": data[CONF_HOST],
71+
CONF_DEVICE_DETAILS: {
72+
DEVICE_TYPE: device_type,
73+
DEVICE_URL: str(url),
74+
},
75+
}
5776

5877

5978
class VodafoneStationConfigFlow(ConfigFlow, domain=DOMAIN):
6079
"""Handle a config flow for Vodafone Station."""
6180

6281
VERSION = 1
82+
MINOR_VERSION = 2
6383

6484
@staticmethod
6585
@callback
@@ -97,7 +117,10 @@ async def async_step_user(
97117
_LOGGER.exception("Unexpected exception")
98118
errors["base"] = "unknown"
99119
else:
100-
return self.async_create_entry(title=info["title"], data=user_input)
120+
return self.async_create_entry(
121+
title=info["title"],
122+
data=user_input | {CONF_DEVICE_DETAILS: info[CONF_DEVICE_DETAILS]},
123+
)
101124

102125
return self.async_show_form(
103126
step_id="user", data_schema=user_form_schema(user_input), errors=errors

homeassistant/components/vodafone_station/const.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
DOMAIN = "vodafone_station"
88
SCAN_INTERVAL = 30
99

10+
CONF_DEVICE_DETAILS = "device_details"
11+
DEVICE_URL = "device_url"
12+
DEVICE_TYPE = "device_type"
13+
1014
DEFAULT_DEVICE_NAME = "Unknown device"
1115
DEFAULT_HOST = "192.168.1.1"
1216
DEFAULT_USERNAME = "vodafone"

homeassistant/components/vodafone_station/coordinator.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,31 @@
66
from typing import Any, cast
77

88
from aiohttp import ClientSession
9-
from aiovodafone import VodafoneStationDevice, VodafoneStationSercommApi, exceptions
9+
from aiovodafone import exceptions
10+
from aiovodafone.api import VodafoneStationDevice, init_api_class
11+
from yarl import URL
1012

1113
from homeassistant.components.device_tracker import (
1214
DEFAULT_CONSIDER_HOME,
1315
DOMAIN as DEVICE_TRACKER_DOMAIN,
1416
)
1517
from homeassistant.config_entries import ConfigEntry
18+
from homeassistant.const import CONF_HOST
1619
from homeassistant.core import HomeAssistant
1720
from homeassistant.exceptions import ConfigEntryAuthFailed
1821
from homeassistant.helpers import entity_registry as er
1922
from homeassistant.helpers.device_registry import DeviceInfo
2023
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
2124
from homeassistant.util import dt as dt_util
2225

23-
from .const import _LOGGER, DOMAIN, SCAN_INTERVAL
26+
from .const import (
27+
_LOGGER,
28+
CONF_DEVICE_DETAILS,
29+
DEVICE_TYPE,
30+
DEVICE_URL,
31+
DOMAIN,
32+
SCAN_INTERVAL,
33+
)
2434
from .helpers import cleanup_device_tracker
2535

2636
CONSIDER_HOME_SECONDS = DEFAULT_CONSIDER_HOME.total_seconds()
@@ -53,24 +63,27 @@ class VodafoneStationRouter(DataUpdateCoordinator[UpdateCoordinatorDataType]):
5363
def __init__(
5464
self,
5565
hass: HomeAssistant,
56-
host: str,
57-
username: str,
58-
password: str,
5966
config_entry: VodafoneConfigEntry,
6067
session: ClientSession,
6168
) -> None:
6269
"""Initialize the scanner."""
6370

64-
self._host = host
65-
self.api = VodafoneStationSercommApi(host, username, password, session)
71+
data = config_entry.data
72+
73+
self.api = init_api_class(
74+
URL(data[CONF_DEVICE_DETAILS][DEVICE_URL]),
75+
data[CONF_DEVICE_DETAILS][DEVICE_TYPE],
76+
data,
77+
session,
78+
)
6679

6780
# Last resort as no MAC or S/N can be retrieved via API
6881
self._id = config_entry.unique_id
6982

7083
super().__init__(
7184
hass=hass,
7285
logger=_LOGGER,
73-
name=f"{DOMAIN}-{host}-coordinator",
86+
name=f"{DOMAIN}-{data[CONF_HOST]}-coordinator",
7487
update_interval=timedelta(seconds=SCAN_INTERVAL),
7588
config_entry=config_entry,
7689
)
@@ -117,7 +130,7 @@ def _calculate_update_time_and_consider_home(
117130

118131
async def _async_update_data(self) -> UpdateCoordinatorDataType:
119132
"""Update router data."""
120-
_LOGGER.debug("Polling Vodafone Station host: %s", self._host)
133+
_LOGGER.debug("Polling Vodafone Station host: %s", self.api.base_url.host)
121134

122135
try:
123136
await self.api.login()

homeassistant/components/vodafone_station/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"iot_class": "local_polling",
99
"loggers": ["aiovodafone"],
1010
"quality_scale": "platinum",
11-
"requirements": ["aiovodafone==1.2.1"]
11+
"requirements": ["aiovodafone==2.0.1"]
1212
}

requirements_all.txt

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

requirements_test_all.txt

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/components/vodafone_station/conftest.py

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@
22

33
from datetime import UTC, datetime
44

5-
from aiovodafone import VodafoneStationDevice
5+
from aiovodafone.api import VodafoneStationCommonApi, VodafoneStationDevice
66
import pytest
7+
from yarl import URL
78

8-
from homeassistant.components.vodafone_station.const import DOMAIN
9+
from homeassistant.components.vodafone_station.const import (
10+
CONF_DEVICE_DETAILS,
11+
DEVICE_TYPE,
12+
DEVICE_URL,
13+
DOMAIN,
14+
)
915
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
1016

11-
from .const import DEVICE_1_HOST, DEVICE_1_MAC, DEVICE_2_MAC
17+
from .const import (
18+
DEVICE_1_HOST,
19+
DEVICE_1_MAC,
20+
DEVICE_2_MAC,
21+
TEST_HOST,
22+
TEST_PASSWORD,
23+
TEST_TYPE,
24+
TEST_URL,
25+
TEST_USERNAME,
26+
)
1227

1328
from tests.common import (
1429
AsyncMock,
@@ -34,53 +49,71 @@ def mock_vodafone_station_router() -> Generator[AsyncMock]:
3449
"""Mock a Vodafone Station router."""
3550
with (
3651
patch(
37-
"homeassistant.components.vodafone_station.coordinator.VodafoneStationSercommApi",
52+
"homeassistant.components.vodafone_station.coordinator.init_api_class",
3853
autospec=True,
3954
) as mock_router,
4055
patch(
41-
"homeassistant.components.vodafone_station.config_flow.VodafoneStationSercommApi",
56+
"homeassistant.components.vodafone_station.config_flow.init_api_class",
4257
new=mock_router,
4358
),
59+
patch.object(
60+
VodafoneStationCommonApi,
61+
"get_device_type",
62+
new=AsyncMock(return_value=(TEST_TYPE, URL(TEST_URL))),
63+
),
4464
):
4565
router = mock_router.return_value
46-
router.get_devices_data.return_value = {
47-
DEVICE_1_MAC: VodafoneStationDevice(
48-
connected=True,
49-
connection_type="wifi",
50-
ip_address="192.168.1.10",
51-
name=DEVICE_1_HOST,
52-
mac=DEVICE_1_MAC,
53-
type="laptop",
54-
wifi="2.4G",
55-
),
56-
DEVICE_2_MAC: VodafoneStationDevice(
57-
connected=False,
58-
connection_type="lan",
59-
ip_address="192.168.1.11",
60-
name="LanDevice1",
61-
mac=DEVICE_2_MAC,
62-
type="desktop",
63-
wifi="",
64-
),
65-
}
66-
router.get_sensor_data.return_value = load_json_object_fixture(
67-
"get_sensor_data.json", DOMAIN
66+
router.login = AsyncMock(return_value=True)
67+
router.logout = AsyncMock(return_value=True)
68+
router.get_devices_data = AsyncMock(
69+
return_value={
70+
DEVICE_1_MAC: VodafoneStationDevice(
71+
connected=True,
72+
connection_type="wifi",
73+
ip_address="192.168.1.10",
74+
name=DEVICE_1_HOST,
75+
mac=DEVICE_1_MAC,
76+
type="laptop",
77+
wifi="2.4G",
78+
),
79+
DEVICE_2_MAC: VodafoneStationDevice(
80+
connected=False,
81+
connection_type="lan",
82+
ip_address="192.168.1.11",
83+
name="LanDevice1",
84+
mac=DEVICE_2_MAC,
85+
type="desktop",
86+
wifi="",
87+
),
88+
}
89+
)
90+
router.get_sensor_data = AsyncMock(
91+
return_value=load_json_object_fixture("get_sensor_data.json", DOMAIN)
6892
)
6993
router.convert_uptime.return_value = datetime(
7094
2024, 11, 19, 20, 19, 0, tzinfo=UTC
7195
)
72-
router.base_url = "https://fake_host"
96+
router.base_url = URL(TEST_URL)
97+
router.restart_connection = AsyncMock(return_value=True)
98+
router.restart_router = AsyncMock(return_value=True)
99+
73100
yield router
74101

75102

76103
@pytest.fixture
77-
def mock_config_entry() -> Generator[MockConfigEntry]:
104+
def mock_config_entry() -> MockConfigEntry:
78105
"""Mock a Vodafone Station config entry."""
79106
return MockConfigEntry(
80107
domain=DOMAIN,
81108
data={
82-
CONF_HOST: "fake_host",
83-
CONF_USERNAME: "fake_username",
84-
CONF_PASSWORD: "fake_password",
109+
CONF_HOST: TEST_HOST,
110+
CONF_USERNAME: TEST_USERNAME,
111+
CONF_PASSWORD: TEST_PASSWORD,
112+
CONF_DEVICE_DETAILS: {
113+
DEVICE_TYPE: TEST_TYPE,
114+
DEVICE_URL: TEST_URL,
115+
},
85116
},
117+
version=1,
118+
minor_version=2,
86119
)

0 commit comments

Comments
 (0)