Improve airq handling of DeviceInfo (#90232)
* Reduce data sharing between ConfigFlow and DataUpdateCoordinator Instead of fetching device information from the device once in `ConfigFlow` and then piping it through in `ConfigEntry.data`, only use as much as needed in `ConfigFlow.async_step_user`, then fetch again in `AirQCoordinator._async_update_data` if a key is missing. Additionally, factor `AirQCoordinator` out into a sumbodule. Add a simple test for `AirQCoordinator.device_info` update. Positive side effect: `AirQCoordinator.device_info` is updated explicitly, instead of dumping the entire content of (a fully compatible) `TypedDict`, retrieved from `aioairq`. * Remove tests ill-suited to this PR `test_config_flow.test_duplicate_error` slipped through by mistake, while `test_coordinator.test_fetch_device_info_on_first_update` may need a more thoroughly suite of accompanying tests * Ignore airq/coordinator.py ...newly separated from airq/__init__.py, that's already in this list * Reorder files alphabetically
This commit is contained in:
parent
5b4663d2ca
commit
e27d3c9523
5 changed files with 68 additions and 52 deletions
|
@ -36,6 +36,7 @@ omit =
|
||||||
homeassistant/components/airnow/__init__.py
|
homeassistant/components/airnow/__init__.py
|
||||||
homeassistant/components/airnow/sensor.py
|
homeassistant/components/airnow/sensor.py
|
||||||
homeassistant/components/airq/__init__.py
|
homeassistant/components/airq/__init__.py
|
||||||
|
homeassistant/components/airq/coordinator.py
|
||||||
homeassistant/components/airq/sensor.py
|
homeassistant/components/airq/sensor.py
|
||||||
homeassistant/components/airthings/__init__.py
|
homeassistant/components/airthings/__init__.py
|
||||||
homeassistant/components/airthings/sensor.py
|
homeassistant/components/airthings/sensor.py
|
||||||
|
|
|
@ -1,58 +1,16 @@
|
||||||
"""The air-Q integration."""
|
"""The air-Q integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from aioairq import AirQ
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
from .const import DOMAIN, MANUFACTURER, TARGET_ROUTE, UPDATE_INTERVAL
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AirQCoordinator
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
class AirQCoordinator(DataUpdateCoordinator):
|
|
||||||
"""Coordinator is responsible for querying the device at a specified route."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
) -> None:
|
|
||||||
"""Initialise a custom coordinator."""
|
|
||||||
super().__init__(
|
|
||||||
hass,
|
|
||||||
_LOGGER,
|
|
||||||
name=DOMAIN,
|
|
||||||
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
|
||||||
)
|
|
||||||
session = async_get_clientsession(hass)
|
|
||||||
self.airq = AirQ(
|
|
||||||
entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], session
|
|
||||||
)
|
|
||||||
self.device_id = entry.unique_id
|
|
||||||
assert self.device_id is not None
|
|
||||||
self.device_info = DeviceInfo(
|
|
||||||
manufacturer=MANUFACTURER,
|
|
||||||
identifiers={(DOMAIN, self.device_id)},
|
|
||||||
)
|
|
||||||
self.device_info.update(entry.data["device_info"])
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict:
|
|
||||||
"""Fetch the data from the device."""
|
|
||||||
data = await self.airq.get(TARGET_ROUTE)
|
|
||||||
return self.airq.drop_uncertainties_from_data(data)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up air-Q from a config entry."""
|
"""Set up air-Q from a config entry."""
|
||||||
|
|
||||||
|
|
|
@ -74,12 +74,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
)
|
)
|
||||||
|
|
||||||
device_info = await airq.fetch_device_info()
|
device_info = await airq.fetch_device_info()
|
||||||
await self.async_set_unique_id(device_info.pop("id"))
|
await self.async_set_unique_id(device_info["id"])
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=device_info["name"],
|
title=device_info["name"], data=user_input
|
||||||
data=user_input | {"device_info": device_info},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
|
61
homeassistant/components/airq/coordinator.py
Normal file
61
homeassistant/components/airq/coordinator.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
"""The air-Q integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aioairq import AirQ
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
|
from .const import DOMAIN, MANUFACTURER, TARGET_ROUTE, UPDATE_INTERVAL
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AirQCoordinator(DataUpdateCoordinator):
|
||||||
|
"""Coordinator is responsible for querying the device at a specified route."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Initialise a custom coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name=DOMAIN,
|
||||||
|
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
||||||
|
)
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
self.airq = AirQ(
|
||||||
|
entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], session
|
||||||
|
)
|
||||||
|
self.device_id = entry.unique_id
|
||||||
|
assert self.device_id is not None
|
||||||
|
self.device_info = DeviceInfo(
|
||||||
|
manufacturer=MANUFACTURER,
|
||||||
|
identifiers={(DOMAIN, self.device_id)},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict:
|
||||||
|
"""Fetch the data from the device."""
|
||||||
|
if "name" not in self.device_info:
|
||||||
|
info = await self.airq.fetch_device_info()
|
||||||
|
self.device_info.update(
|
||||||
|
DeviceInfo(
|
||||||
|
name=info["name"],
|
||||||
|
model=info["model"],
|
||||||
|
sw_version=info["sw_version"],
|
||||||
|
hw_version=info["hw_version"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = await self.airq.get(TARGET_ROUTE)
|
||||||
|
return self.airq.drop_uncertainties_from_data(data)
|
|
@ -24,9 +24,6 @@ TEST_DEVICE_INFO = DeviceInfo(
|
||||||
sw_version="sw",
|
sw_version="sw",
|
||||||
hw_version="hw",
|
hw_version="hw",
|
||||||
)
|
)
|
||||||
TEST_DATA_OUT = TEST_USER_DATA | {
|
|
||||||
"device_info": {k: v for k, v in TEST_DEVICE_INFO.items() if k != "id"}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_form(hass: HomeAssistant) -> None:
|
async def test_form(hass: HomeAssistant) -> None:
|
||||||
|
@ -48,7 +45,7 @@ async def test_form(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result2["title"] == TEST_DEVICE_INFO["name"]
|
assert result2["title"] == TEST_DEVICE_INFO["name"]
|
||||||
assert result2["data"] == TEST_DATA_OUT
|
assert result2["data"] == TEST_USER_DATA
|
||||||
|
|
||||||
|
|
||||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue