Avoid using coordinator in config flow of APCUPSD (#112121)
* Separate data class out of coordinator * Further fix the imports * Update homeassistant/components/apcupsd/coordinator.py Co-authored-by: J. Nick Koston <nick@koston.org> * Use `or` to make it a bit cleaner when trying to find the UPS model Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com> * Use or to make it a bit cleaner when trying to find the UPS model Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com> * Use plain dict instead of `OrderedDict` --------- Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
This commit is contained in:
parent
38f9285bd6
commit
2c5510df30
5 changed files with 49 additions and 53 deletions
|
@ -13,7 +13,8 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import DOMAIN, APCUPSdCoordinator
|
from .const import DOMAIN
|
||||||
|
from .coordinator import APCUPSdCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_DESCRIPTION = BinarySensorEntityDescription(
|
_DESCRIPTION = BinarySensorEntityDescription(
|
||||||
|
@ -53,7 +54,7 @@ class OnlineStatus(CoordinatorEntity[APCUPSdCoordinator], BinarySensorEntity):
|
||||||
super().__init__(coordinator, context=description.key.upper())
|
super().__init__(coordinator, context=description.key.upper())
|
||||||
|
|
||||||
# Set up unique id and device info if serial number is available.
|
# Set up unique id and device info if serial number is available.
|
||||||
if (serial_no := coordinator.ups_serial_no) is not None:
|
if (serial_no := coordinator.data.serial_no) is not None:
|
||||||
self._attr_unique_id = f"{serial_no}_{description.key}"
|
self._attr_unique_id = f"{serial_no}_{description.key}"
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_device_info = coordinator.device_info
|
self._attr_device_info = coordinator.device_info
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
"""Config flow for APCUPSd integration."""
|
"""Config flow for APCUPSd integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import aioapcaccess
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
from homeassistant.helpers import selector
|
from homeassistant.helpers import selector
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.update_coordinator import UpdateFailed
|
|
||||||
|
|
||||||
from . import DOMAIN, APCUPSdCoordinator
|
from .const import CONNECTION_TIMEOUT, DOMAIN
|
||||||
|
from .coordinator import APCUPSdData
|
||||||
|
|
||||||
_PORT_SELECTOR = vol.All(
|
_PORT_SELECTOR = vol.All(
|
||||||
selector.NumberSelector(
|
selector.NumberSelector(
|
||||||
|
@ -49,32 +51,22 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
self._async_abort_entries_match({CONF_HOST: host, CONF_PORT: port})
|
self._async_abort_entries_match({CONF_HOST: host, CONF_PORT: port})
|
||||||
|
|
||||||
# Test the connection to the host and get the current status for serial number.
|
# Test the connection to the host and get the current status for serial number.
|
||||||
coordinator = APCUPSdCoordinator(self.hass, host, port)
|
try:
|
||||||
await coordinator.async_request_refresh()
|
async with asyncio.timeout(CONNECTION_TIMEOUT):
|
||||||
|
data = APCUPSdData(await aioapcaccess.request_status(host, port))
|
||||||
if isinstance(coordinator.last_exception, (UpdateFailed, TimeoutError)):
|
except (OSError, asyncio.IncompleteReadError, TimeoutError):
|
||||||
errors = {"base": "cannot_connect"}
|
errors = {"base": "cannot_connect"}
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user", data_schema=_SCHEMA, errors=errors
|
step_id="user", data_schema=_SCHEMA, errors=errors
|
||||||
)
|
)
|
||||||
|
|
||||||
if not coordinator.data:
|
if not data:
|
||||||
return self.async_abort(reason="no_status")
|
return self.async_abort(reason="no_status")
|
||||||
|
|
||||||
# We _try_ to use the serial number of the UPS as the unique id since this field
|
# We _try_ to use the serial number of the UPS as the unique id since this field
|
||||||
# is not guaranteed to exist on all APC UPS models.
|
# is not guaranteed to exist on all APC UPS models.
|
||||||
await self.async_set_unique_id(coordinator.ups_serial_no)
|
await self.async_set_unique_id(data.serial_no)
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
title = "APC UPS"
|
title = data.name or data.model or data.serial_no or "APC UPS"
|
||||||
if coordinator.ups_name is not None:
|
return self.async_create_entry(title=title, data=user_input)
|
||||||
title = coordinator.ups_name
|
|
||||||
elif coordinator.ups_model is not None:
|
|
||||||
title = coordinator.ups_model
|
|
||||||
elif coordinator.ups_serial_no is not None:
|
|
||||||
title = coordinator.ups_serial_no
|
|
||||||
|
|
||||||
return self.async_create_entry(
|
|
||||||
title=title,
|
|
||||||
data=user_input,
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,3 +2,4 @@
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
DOMAIN: Final = "apcupsd"
|
DOMAIN: Final = "apcupsd"
|
||||||
|
CONNECTION_TIMEOUT: int = 10
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import OrderedDict
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
@ -19,14 +18,35 @@ from homeassistant.helpers.update_coordinator import (
|
||||||
UpdateFailed,
|
UpdateFailed,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CONNECTION_TIMEOUT, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
UPDATE_INTERVAL: Final = timedelta(seconds=60)
|
UPDATE_INTERVAL: Final = timedelta(seconds=60)
|
||||||
REQUEST_REFRESH_COOLDOWN: Final = 5
|
REQUEST_REFRESH_COOLDOWN: Final = 5
|
||||||
|
|
||||||
|
|
||||||
class APCUPSdCoordinator(DataUpdateCoordinator[OrderedDict[str, str]]):
|
class APCUPSdData(dict[str, str]):
|
||||||
|
"""Store data about an APCUPSd and provide a few helper methods for easier accesses."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str | None:
|
||||||
|
"""Return the name of the UPS, if available."""
|
||||||
|
return self.get("UPSNAME")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def model(self) -> str | None:
|
||||||
|
"""Return the model of the UPS, if available."""
|
||||||
|
# Different UPS models may report slightly different keys for model, here we
|
||||||
|
# try them all.
|
||||||
|
return self.get("APCMODEL") or self.get("MODEL")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serial_no(self) -> str | None:
|
||||||
|
"""Return the unique serial number of the UPS, if available."""
|
||||||
|
return self.get("SERIALNO")
|
||||||
|
|
||||||
|
|
||||||
|
class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]):
|
||||||
"""Store and coordinate the data retrieved from APCUPSd for all sensors.
|
"""Store and coordinate the data retrieved from APCUPSd for all sensors.
|
||||||
|
|
||||||
For each entity to use, acts as the single point responsible for fetching
|
For each entity to use, acts as the single point responsible for fetching
|
||||||
|
@ -52,46 +72,27 @@ class APCUPSdCoordinator(DataUpdateCoordinator[OrderedDict[str, str]]):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
|
|
||||||
@property
|
|
||||||
def ups_name(self) -> str | None:
|
|
||||||
"""Return the name of the UPS, if available."""
|
|
||||||
return self.data.get("UPSNAME")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ups_model(self) -> str | None:
|
|
||||||
"""Return the model of the UPS, if available."""
|
|
||||||
# Different UPS models may report slightly different keys for model, here we
|
|
||||||
# try them all.
|
|
||||||
for model_key in ("APCMODEL", "MODEL"):
|
|
||||||
if model_key in self.data:
|
|
||||||
return self.data[model_key]
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ups_serial_no(self) -> str | None:
|
|
||||||
"""Return the unique serial number of the UPS, if available."""
|
|
||||||
return self.data.get("SERIALNO")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return the DeviceInfo of this APC UPS, if serial number is available."""
|
"""Return the DeviceInfo of this APC UPS, if serial number is available."""
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
identifiers={(DOMAIN, self.ups_serial_no or self.config_entry.entry_id)},
|
identifiers={(DOMAIN, self.data.serial_no or self.config_entry.entry_id)},
|
||||||
model=self.ups_model,
|
model=self.data.model,
|
||||||
manufacturer="APC",
|
manufacturer="APC",
|
||||||
name=self.ups_name if self.ups_name else "APC UPS",
|
name=self.data.name or "APC UPS",
|
||||||
hw_version=self.data.get("FIRMWARE"),
|
hw_version=self.data.get("FIRMWARE"),
|
||||||
sw_version=self.data.get("VERSION"),
|
sw_version=self.data.get("VERSION"),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_update_data(self) -> OrderedDict[str, str]:
|
async def _async_update_data(self) -> APCUPSdData:
|
||||||
"""Fetch the latest status from APCUPSd.
|
"""Fetch the latest status from APCUPSd.
|
||||||
|
|
||||||
Note that the result dict uses upper case for each resource, where our
|
Note that the result dict uses upper case for each resource, where our
|
||||||
integration uses lower cases as keys internally.
|
integration uses lower cases as keys internally.
|
||||||
"""
|
"""
|
||||||
async with asyncio.timeout(10):
|
async with asyncio.timeout(CONNECTION_TIMEOUT):
|
||||||
try:
|
try:
|
||||||
return await aioapcaccess.request_status(self._host, self._port)
|
data = await aioapcaccess.request_status(self._host, self._port)
|
||||||
|
return APCUPSdData(data)
|
||||||
except (OSError, asyncio.IncompleteReadError) as error:
|
except (OSError, asyncio.IncompleteReadError) as error:
|
||||||
raise UpdateFailed(error) from error
|
raise UpdateFailed(error) from error
|
||||||
|
|
|
@ -24,7 +24,8 @@ from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import DOMAIN, APCUPSdCoordinator
|
from .const import DOMAIN
|
||||||
|
from .coordinator import APCUPSdCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -493,7 +494,7 @@ class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
|
||||||
super().__init__(coordinator=coordinator, context=description.key.upper())
|
super().__init__(coordinator=coordinator, context=description.key.upper())
|
||||||
|
|
||||||
# Set up unique id and device info if serial number is available.
|
# Set up unique id and device info if serial number is available.
|
||||||
if (serial_no := coordinator.ups_serial_no) is not None:
|
if (serial_no := coordinator.data.serial_no) is not None:
|
||||||
self._attr_unique_id = f"{serial_no}_{description.key}"
|
self._attr_unique_id = f"{serial_no}_{description.key}"
|
||||||
|
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
|
Loading…
Add table
Reference in a new issue