Move brunt coordinator to separate module (#129090)

This commit is contained in:
epenet 2024-10-26 02:30:59 +02:00 committed by GitHub
parent 93e270f379
commit 3a39a5caa3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 88 deletions

View File

@ -2,79 +2,22 @@
from __future__ import annotations from __future__ import annotations
from asyncio import timeout
import logging
from aiohttp.client_exceptions import ClientResponseError, ServerDisconnectedError
from brunt import BruntClientAsync, Thing
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DATA_BAPI, DATA_COOR, DOMAIN, PLATFORMS, REGULAR_INTERVAL from .const import PLATFORMS
from .coordinator import BruntConfigEntry, BruntCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: BruntConfigEntry) -> bool:
"""Set up Brunt using config flow.""" """Set up Brunt using config flow."""
session = async_get_clientsession(hass) coordinator = BruntCoordinator(hass, entry)
bapi = BruntClientAsync(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
session=session,
)
try:
await bapi.async_login()
except ServerDisconnectedError as exc:
raise ConfigEntryNotReady("Brunt not ready to connect.") from exc
except ClientResponseError as exc:
raise ConfigEntryAuthFailed(
f"Brunt could not connect with username: {entry.data[CONF_USERNAME]}."
) from exc
async def async_update_data() -> dict[str | None, Thing]:
"""Fetch data from the Brunt endpoint for all Things.
Error 403 is the API response for any kind of authentication error (failed password or email)
Error 401 is the API response for things that are not part of the account, could happen when a device is deleted from the account.
"""
try:
async with timeout(10):
things = await bapi.async_get_things(force=True)
return {thing.serial: thing for thing in things}
except ServerDisconnectedError as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
except ClientResponseError as err:
if err.status == 403:
raise ConfigEntryAuthFailed from err
if err.status == 401:
_LOGGER.warning("Device not found, will reload Brunt integration")
await hass.config_entries.async_reload(entry.entry_id)
raise UpdateFailed from err
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="brunt",
update_method=async_update_data,
update_interval=REGULAR_INTERVAL,
)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {}) entry.runtime_data = coordinator
hass.data[DOMAIN][entry.entry_id] = {DATA_BAPI: bapi, DATA_COOR: coordinator}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: BruntConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -10,8 +10,6 @@ NOTIFICATION_ID = "brunt_notification"
NOTIFICATION_TITLE = "Brunt Cover Setup" NOTIFICATION_TITLE = "Brunt Cover Setup"
ATTRIBUTION = "Based on an unofficial Brunt SDK." ATTRIBUTION = "Based on an unofficial Brunt SDK."
PLATFORMS = [Platform.COVER] PLATFORMS = [Platform.COVER]
DATA_BAPI = "bapi"
DATA_COOR = "coordinator"
CLOSED_POSITION = 0 CLOSED_POSITION = 0
OPEN_POSITION = 100 OPEN_POSITION = 100

View File

@ -0,0 +1,80 @@
"""The brunt component."""
from __future__ import annotations
from asyncio import timeout
import logging
from aiohttp.client_exceptions import ClientResponseError, ServerDisconnectedError
from brunt import BruntClientAsync, Thing
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import REGULAR_INTERVAL
_LOGGER = logging.getLogger(__name__)
type BruntConfigEntry = ConfigEntry[BruntCoordinator]
class BruntCoordinator(DataUpdateCoordinator[dict[str | None, Thing]]):
"""Config entry data."""
bapi: BruntClientAsync
config_entry: BruntConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: BruntConfigEntry,
) -> None:
"""Initialize the Brunt coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name="brunt",
update_interval=REGULAR_INTERVAL,
)
async def _async_setup(self) -> None:
session = async_get_clientsession(self.hass)
self.bapi = BruntClientAsync(
username=self.config_entry.data[CONF_USERNAME],
password=self.config_entry.data[CONF_PASSWORD],
session=session,
)
try:
await self.bapi.async_login()
except ServerDisconnectedError as exc:
raise ConfigEntryNotReady("Brunt not ready to connect.") from exc
except ClientResponseError as exc:
raise ConfigEntryAuthFailed(
f"Brunt could not connect with username: {self.config_entry.data[CONF_USERNAME]}."
) from exc
async def _async_update_data(self) -> dict[str | None, Thing]:
"""Fetch data from the Brunt endpoint for all Things.
Error 403 is the API response for any kind of authentication error (failed password or email)
Error 401 is the API response for things that are not part of the account, could happen when a device is deleted from the account.
"""
try:
async with timeout(10):
things = await self.bapi.async_get_things(force=True)
return {thing.serial: thing for thing in things}
except ServerDisconnectedError as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
except ClientResponseError as err:
if err.status == 403:
raise ConfigEntryAuthFailed from err
if err.status == 401:
_LOGGER.warning("Device not found, will reload Brunt integration")
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
raise UpdateFailed from err

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from typing import Any from typing import Any
from aiohttp.client_exceptions import ClientResponseError from aiohttp.client_exceptions import ClientResponseError
from brunt import BruntClientAsync, Thing from brunt import Thing
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,
@ -13,49 +13,39 @@ from homeassistant.components.cover import (
CoverEntity, CoverEntity,
CoverEntityFeature, CoverEntityFeature,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import CoordinatorEntity
CoordinatorEntity,
DataUpdateCoordinator,
)
from .const import ( from .const import (
ATTR_REQUEST_POSITION, ATTR_REQUEST_POSITION,
ATTRIBUTION, ATTRIBUTION,
CLOSED_POSITION, CLOSED_POSITION,
DATA_BAPI,
DATA_COOR,
DOMAIN, DOMAIN,
FAST_INTERVAL, FAST_INTERVAL,
OPEN_POSITION, OPEN_POSITION,
REGULAR_INTERVAL, REGULAR_INTERVAL,
) )
from .coordinator import BruntConfigEntry, BruntCoordinator
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: BruntConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the brunt platform.""" """Set up the brunt platform."""
bapi: BruntClientAsync = hass.data[DOMAIN][entry.entry_id][DATA_BAPI] coordinator = entry.runtime_data
coordinator: DataUpdateCoordinator[dict[str | None, Thing]] = hass.data[DOMAIN][
entry.entry_id
][DATA_COOR]
async_add_entities( async_add_entities(
BruntDevice(coordinator, serial, thing, bapi, entry.entry_id) BruntDevice(coordinator, serial, thing, entry.entry_id)
for serial, thing in coordinator.data.items() for serial, thing in coordinator.data.items()
) )
class BruntDevice( class BruntDevice(CoordinatorEntity[BruntCoordinator], CoverEntity):
CoordinatorEntity[DataUpdateCoordinator[dict[str | None, Thing]]], CoverEntity
):
"""Representation of a Brunt cover device. """Representation of a Brunt cover device.
Contains the common logic for all Brunt devices. Contains the common logic for all Brunt devices.
@ -73,16 +63,14 @@ class BruntDevice(
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator[dict[str | None, Thing]], coordinator: BruntCoordinator,
serial: str | None, serial: str | None,
thing: Thing, thing: Thing,
bapi: BruntClientAsync,
entry_id: str, entry_id: str,
) -> None: ) -> None:
"""Init the Brunt device.""" """Init the Brunt device."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_unique_id = serial self._attr_unique_id = serial
self._bapi = bapi
self._thing = thing self._thing = thing
self._entry_id = entry_id self._entry_id = entry_id
@ -167,7 +155,7 @@ class BruntDevice(
async def _async_update_cover(self, position: int) -> None: async def _async_update_cover(self, position: int) -> None:
"""Set the cover to the new position and wait for the update to be reflected.""" """Set the cover to the new position and wait for the update to be reflected."""
try: try:
await self._bapi.async_change_request_position( await self.coordinator.bapi.async_change_request_position(
position, thing_uri=self._thing.thing_uri position, thing_uri=self._thing.thing_uri
) )
except ClientResponseError as exc: except ClientResponseError as exc:
@ -182,7 +170,7 @@ class BruntDevice(
"""Update the update interval after each refresh.""" """Update the update interval after each refresh."""
if ( if (
self.request_cover_position self.request_cover_position
== self._bapi.last_requested_positions[self._thing.thing_uri] == self.coordinator.bapi.last_requested_positions[self._thing.thing_uri]
and self.move_state == 0 and self.move_state == 0
): ):
self.coordinator.update_interval = REGULAR_INTERVAL self.coordinator.update_interval = REGULAR_INTERVAL