Implement data update coordinator for nextcloud (#89652)

* implement data update coordinator

* apply suggestions

* apply suggestions
pull/90106/head
Michael 2023-03-22 09:18:09 +01:00 committed by GitHub
parent 96225bb287
commit d25e394310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 72 deletions

View File

@ -13,10 +13,10 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
from .coordinator import NextcloudDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@ -40,61 +40,28 @@ CONFIG_SCHEMA = vol.Schema(
)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Nextcloud integration."""
# Fetch Nextcloud Monitor api data
conf = config[DOMAIN]
try:
ncm = NextcloudMonitor(conf[CONF_URL], conf[CONF_USERNAME], conf[CONF_PASSWORD])
ncm = await hass.async_add_executor_job(
NextcloudMonitor, conf[CONF_URL], conf[CONF_USERNAME], conf[CONF_PASSWORD]
)
except NextcloudMonitorError:
_LOGGER.error("Nextcloud setup failed - Check configuration")
return False
hass.data[DOMAIN] = get_data_points(ncm.data)
hass.data[DOMAIN]["instance"] = conf[CONF_URL]
coordinator = NextcloudDataUpdateCoordinator(
hass,
ncm,
conf,
)
hass.data[DOMAIN] = coordinator
def nextcloud_update(event_time):
"""Update data from nextcloud api."""
try:
ncm.update()
except NextcloudMonitorError:
_LOGGER.error("Nextcloud update failed")
return False
hass.data[DOMAIN] = get_data_points(ncm.data)
hass.data[DOMAIN]["instance"] = conf[CONF_URL]
# Update sensors on time interval
track_time_interval(hass, nextcloud_update, conf[CONF_SCAN_INTERVAL])
await coordinator.async_config_entry_first_refresh()
for platform in PLATFORMS:
discovery.load_platform(hass, platform, DOMAIN, {}, config)
return True
# Use recursion to create list of sensors & values based on nextcloud api data
def get_data_points(api_data, key_path="", leaf=False):
"""Use Recursion to discover data-points and values.
Get dictionary of data-points by recursing through dict returned by api until
the dictionary value does not contain another dictionary and use the
resulting path of dictionary keys and resulting value as the name/value
for the data-point.
returns: dictionary of data-point/values
"""
result = {}
for key, value in api_data.items():
if isinstance(value, dict):
if leaf:
key_path = f"{key}_"
if not leaf:
key_path += f"{key}_"
leaf = True
result.update(get_data_points(value, key_path, leaf))
else:
result[f"{DOMAIN}_{key_path}{key}"] = value
leaf = False
return result

View File

@ -7,6 +7,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DOMAIN
from .coordinator import NextcloudDataUpdateCoordinator
from .entity import NextcloudEntity
BINARY_SENSORS = (
@ -26,11 +27,16 @@ def setup_platform(
"""Set up the Nextcloud sensors."""
if discovery_info is None:
return
binary_sensors = []
for name in hass.data[DOMAIN]:
if name in BINARY_SENSORS:
binary_sensors.append(NextcloudBinarySensor(name))
add_entities(binary_sensors, True)
coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN]
add_entities(
[
NextcloudBinarySensor(coordinator, name)
for name in coordinator.data
if name in BINARY_SENSORS
],
True,
)
class NextcloudBinarySensor(NextcloudEntity, BinarySensorEntity):
@ -39,4 +45,4 @@ class NextcloudBinarySensor(NextcloudEntity, BinarySensorEntity):
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return self._state == "yes"
return self.coordinator.data.get(self.item) == "yes"

View File

@ -0,0 +1,73 @@
"""Data update coordinator for the Nextcloud integration."""
import logging
from typing import Any
from nextcloudmonitor import NextcloudMonitor, NextcloudMonitorError
from homeassistant.const import CONF_SCAN_INTERVAL, CONF_URL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
_LOGGER = logging.getLogger(__name__)
class NextcloudDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Nextcloud data update coordinator."""
def __init__(
self, hass: HomeAssistant, ncm: NextcloudMonitor, config: ConfigType
) -> None:
"""Initialize the Nextcloud coordinator."""
self.config = config
self.ncm = ncm
self.url = config[CONF_URL]
super().__init__(
hass,
_LOGGER,
name=self.url,
update_interval=config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
)
# Use recursion to create list of sensors & values based on nextcloud api data
def _get_data_points(
self, api_data: dict, key_path: str = "", leaf: bool = False
) -> dict[str, Any]:
"""Use Recursion to discover data-points and values.
Get dictionary of data-points by recursing through dict returned by api until
the dictionary value does not contain another dictionary and use the
resulting path of dictionary keys and resulting value as the name/value
for the data-point.
returns: dictionary of data-point/values
"""
result = {}
for key, value in api_data.items():
if isinstance(value, dict):
if leaf:
key_path = f"{key}_"
if not leaf:
key_path += f"{key}_"
leaf = True
result.update(self._get_data_points(value, key_path, leaf))
else:
result[f"{DOMAIN}_{key_path}{key}"] = value
leaf = False
return result
async def _async_update_data(self) -> dict[str, Any]:
"""Fetch all Nextcloud data."""
def _update_data() -> None:
try:
self.ncm.update()
except NextcloudMonitorError as ex:
raise UpdateFailed from ex
await self.hass.async_add_executor_job(_update_data)
return self._get_data_points(self.ncm.data)

View File

@ -1,26 +1,23 @@
"""Base entity for the Nextcloud integration."""
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
class NextcloudEntity(Entity):
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import NextcloudDataUpdateCoordinator
class NextcloudEntity(CoordinatorEntity[NextcloudDataUpdateCoordinator]):
"""Base Nextcloud entity."""
_attr_icon = "mdi:cloud"
def __init__(self, item: str) -> None:
"""Initialize the Nextcloud entity."""
self._attr_name = item
def __init__(self, coordinator: NextcloudDataUpdateCoordinator, item: str) -> None:
"""Initialize the Nextcloud sensor."""
super().__init__(coordinator)
self.item = item
self._state: StateType = None
self._attr_name = item
@property
def unique_id(self):
def unique_id(self) -> str:
"""Return the unique ID for this sensor."""
return f"{self.hass.data[DOMAIN]['instance']}#{self.item}"
def update(self) -> None:
"""Update the sensor."""
self._state = self.hass.data[DOMAIN][self.item]
return f"{self.coordinator.url}#{self.item}"

View File

@ -7,6 +7,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
from .const import DOMAIN
from .coordinator import NextcloudDataUpdateCoordinator
from .entity import NextcloudEntity
SENSORS = (
@ -65,11 +66,16 @@ def setup_platform(
"""Set up the Nextcloud sensors."""
if discovery_info is None:
return
sensors = []
for name in hass.data[DOMAIN]:
if name in SENSORS:
sensors.append(NextcloudSensor(name))
add_entities(sensors, True)
coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN]
add_entities(
[
NextcloudSensor(coordinator, name)
for name in coordinator.data
if name in SENSORS
],
True,
)
class NextcloudSensor(NextcloudEntity, SensorEntity):
@ -78,4 +84,4 @@ class NextcloudSensor(NextcloudEntity, SensorEntity):
@property
def native_value(self) -> StateType:
"""Return the state for this sensor."""
return self._state
return self.coordinator.data.get(self.item)