Add coordinator to QNAP (#94413)

* Create coordinator.py

* Update sensor.py

* Update sensor.py

* Update sensor.py

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Add import

* Update coordinator.py

* Update coordinator.py

* Add platformnotready

* Walrus

* Update sensor.py

* Undo walres

CI didnt like it

* Update coordinator.py black fix

* Update sensor.py fix black

* Update sensor.py fix ruff

* Update coordinator.py fix lint

* Update sensor.py fix ruff

* Update homeassistant/components/qnap/coordinator.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update sensor.py

* Update coordinator.py

* Update coordinator.py

* Update sensor.py

* Update homeassistant/components/qnap/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update sensor.py

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
pull/94615/head
disforw 2023-06-14 16:42:49 -04:00 committed by GitHub
parent b149fffa08
commit a79e37c240
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 87 deletions

View File

@ -0,0 +1,59 @@
"""Data coordinator for the qnap integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
from qnapstats import QNAPStats
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_SSL,
CONF_TIMEOUT,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
UPDATE_INTERVAL = timedelta(minutes=1)
_LOGGER = logging.getLogger(__name__)
class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Custom coordinator for the qnap integration."""
def __init__(self, hass: HomeAssistant, config: ConfigType) -> None:
"""Initialize the qnap coordinator."""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
protocol = "https" if config[CONF_SSL] else "http"
self._api = QNAPStats(
f"{protocol}://{config.get(CONF_HOST)}",
config.get(CONF_PORT),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
verify_ssl=config.get(CONF_VERIFY_SSL),
timeout=config.get(CONF_TIMEOUT),
)
def _sync_update(self) -> dict[str, dict[str, Any]]:
"""Get the latest data from the Qnap API."""
return {
"system_stats": self._api.get_system_stats(),
"system_health": self._api.get_system_health(),
"smart_drive_health": self._api.get_smart_disk_health(),
"volumes": self._api.get_volumes(),
"bandwidth": self._api.get_bandwidth(),
}
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
"""Get the latest data from the Qnap API."""
return await self.hass.async_add_executor_job(self._sync_update)

View File

@ -1,10 +1,6 @@
"""Support for QNAP NAS Sensors."""
from __future__ import annotations
from datetime import timedelta
import logging
from qnapstats import QNAPStats
import voluptuous as vol
from homeassistant.components.sensor import (
@ -33,9 +29,10 @@ from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_PORT, DEFAULT_TIMEOUT
from .coordinator import QnapCoordinator
_LOGGER = logging.getLogger(__name__)
@ -59,8 +56,6 @@ CONF_DRIVES = "drives"
CONF_NICS = "nics"
CONF_VOLUMES = "volumes"
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
NOTIFICATION_ID = "qnap_notification"
NOTIFICATION_TITLE = "QNAP Sensor Setup"
@ -201,18 +196,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
def setup_platform(
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the QNAP NAS sensor."""
api = QNAPStatsAPI(config)
api.update()
# QNAP is not available
if not api.data:
coordinator = QnapCoordinator(hass, config)
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise PlatformNotReady
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
@ -221,21 +214,21 @@ def setup_platform(
# Basic sensors
sensors.extend(
[
QNAPSystemSensor(api, description)
QNAPSystemSensor(coordinator, description)
for description in _SYSTEM_MON_COND
if description.key in monitored_conditions
]
)
sensors.extend(
[
QNAPCPUSensor(api, description)
QNAPCPUSensor(coordinator, description)
for description in _CPU_MON_COND
if description.key in monitored_conditions
]
)
sensors.extend(
[
QNAPMemorySensor(api, description)
QNAPMemorySensor(coordinator, description)
for description in _MEMORY_MON_COND
if description.key in monitored_conditions
]
@ -244,8 +237,8 @@ def setup_platform(
# Network sensors
sensors.extend(
[
QNAPNetworkSensor(api, description, nic)
for nic in config.get(CONF_NICS, api.data["system_stats"]["nics"])
QNAPNetworkSensor(coordinator, description, nic)
for nic in config.get(CONF_NICS, coordinator.data["system_stats"]["nics"])
for description in _NETWORK_MON_COND
if description.key in monitored_conditions
]
@ -254,8 +247,8 @@ def setup_platform(
# Drive sensors
sensors.extend(
[
QNAPDriveSensor(api, description, drive)
for drive in config.get(CONF_DRIVES, api.data["smart_drive_health"])
QNAPDriveSensor(coordinator, description, drive)
for drive in config.get(CONF_DRIVES, coordinator.data["smart_drive_health"])
for description in _DRIVE_MON_COND
if description.key in monitored_conditions
]
@ -264,8 +257,8 @@ def setup_platform(
# Volume sensors
sensors.extend(
[
QNAPVolumeSensor(api, description, volume)
for volume in config.get(CONF_VOLUMES, api.data["volumes"])
QNAPVolumeSensor(coordinator, description, volume)
for volume in config.get(CONF_VOLUMES, coordinator.data["volumes"])
for description in _VOLUME_MON_COND
if description.key in monitored_conditions
]
@ -284,62 +277,27 @@ def round_nicely(number):
return round(number)
class QNAPStatsAPI:
"""Class to interface with the API."""
def __init__(self, config):
"""Initialize the API wrapper."""
protocol = "https" if config[CONF_SSL] else "http"
self._api = QNAPStats(
f"{protocol}://{config.get(CONF_HOST)}",
config.get(CONF_PORT),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
verify_ssl=config.get(CONF_VERIFY_SSL),
timeout=config.get(CONF_TIMEOUT),
)
self.data = {}
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update API information and store locally."""
try:
self.data["system_stats"] = self._api.get_system_stats()
self.data["system_health"] = self._api.get_system_health()
self.data["smart_drive_health"] = self._api.get_smart_disk_health()
self.data["volumes"] = self._api.get_volumes()
self.data["bandwidth"] = self._api.get_bandwidth()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Failed to fetch QNAP stats from the NAS")
class QNAPSensor(SensorEntity):
class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity):
"""Base class for a QNAP sensor."""
def __init__(
self, api, description: SensorEntityDescription, monitor_device=None
self,
coordinator: QnapCoordinator,
description: SensorEntityDescription,
monitor_device: str | None = None,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self.monitor_device = monitor_device
self._api = api
self.device_name = self.coordinator.data["system_stats"]["system"]["name"]
@property
def name(self):
"""Return the name of the sensor, if any."""
server_name = self._api.data["system_stats"]["system"]["name"]
if self.monitor_device is not None:
return (
f"{server_name} {self.entity_description.name} ({self.monitor_device})"
)
return f"{server_name} {self.entity_description.name}"
def update(self) -> None:
"""Get the latest data for the states."""
self._api.update()
return f"{self.device_name} {self.entity_description.name} ({self.monitor_device})"
return f"{self.device_name} {self.entity_description.name}"
class QNAPCPUSensor(QNAPSensor):
@ -349,9 +307,9 @@ class QNAPCPUSensor(QNAPSensor):
def native_value(self):
"""Return the state of the sensor."""
if self.entity_description.key == "cpu_temp":
return self._api.data["system_stats"]["cpu"]["temp_c"]
return self.coordinator.data["system_stats"]["cpu"]["temp_c"]
if self.entity_description.key == "cpu_usage":
return self._api.data["system_stats"]["cpu"]["usage_percent"]
return self.coordinator.data["system_stats"]["cpu"]["usage_percent"]
class QNAPMemorySensor(QNAPSensor):
@ -360,11 +318,11 @@ class QNAPMemorySensor(QNAPSensor):
@property
def native_value(self):
"""Return the state of the sensor."""
free = float(self._api.data["system_stats"]["memory"]["free"]) / 1024
free = float(self.coordinator.data["system_stats"]["memory"]["free"]) / 1024
if self.entity_description.key == "memory_free":
return round_nicely(free)
total = float(self._api.data["system_stats"]["memory"]["total"]) / 1024
total = float(self.coordinator.data["system_stats"]["memory"]["total"]) / 1024
used = total - free
if self.entity_description.key == "memory_used":
@ -376,8 +334,8 @@ class QNAPMemorySensor(QNAPSensor):
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self._api.data:
data = self._api.data["system_stats"]["memory"]
if self.coordinator.data:
data = self.coordinator.data["system_stats"]["memory"]
size = round_nicely(float(data["total"]) / 1024)
return {ATTR_MEMORY_SIZE: f"{size} {UnitOfInformation.GIBIBYTES}"}
@ -389,10 +347,10 @@ class QNAPNetworkSensor(QNAPSensor):
def native_value(self):
"""Return the state of the sensor."""
if self.entity_description.key == "network_link_status":
nic = self._api.data["system_stats"]["nics"][self.monitor_device]
nic = self.coordinator.data["system_stats"]["nics"][self.monitor_device]
return nic["link_status"]
data = self._api.data["bandwidth"][self.monitor_device]
data = self.coordinator.data["bandwidth"][self.monitor_device]
if self.entity_description.key == "network_tx":
return round_nicely(data["tx"] / 1024 / 1024)
@ -402,8 +360,8 @@ class QNAPNetworkSensor(QNAPSensor):
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self._api.data:
data = self._api.data["system_stats"]["nics"][self.monitor_device]
if self.coordinator.data:
data = self.coordinator.data["system_stats"]["nics"][self.monitor_device]
return {
ATTR_IP: data["ip"],
ATTR_MASK: data["mask"],
@ -422,16 +380,16 @@ class QNAPSystemSensor(QNAPSensor):
def native_value(self):
"""Return the state of the sensor."""
if self.entity_description.key == "status":
return self._api.data["system_health"]
return self.coordinator.data["system_health"]
if self.entity_description.key == "system_temp":
return int(self._api.data["system_stats"]["system"]["temp_c"])
return int(self.coordinator.data["system_stats"]["system"]["temp_c"])
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self._api.data:
data = self._api.data["system_stats"]
if self.coordinator.data:
data = self.coordinator.data["system_stats"]
days = int(data["uptime"]["days"])
hours = int(data["uptime"]["hours"])
minutes = int(data["uptime"]["minutes"])
@ -450,7 +408,7 @@ class QNAPDriveSensor(QNAPSensor):
@property
def native_value(self):
"""Return the state of the sensor."""
data = self._api.data["smart_drive_health"][self.monitor_device]
data = self.coordinator.data["smart_drive_health"][self.monitor_device]
if self.entity_description.key == "drive_smart_status":
return data["health"]
@ -461,7 +419,7 @@ class QNAPDriveSensor(QNAPSensor):
@property
def name(self):
"""Return the name of the sensor, if any."""
server_name = self._api.data["system_stats"]["system"]["name"]
server_name = self.coordinator.data["system_stats"]["system"]["name"]
return (
f"{server_name} {self.entity_description.name} (Drive"
@ -471,8 +429,8 @@ class QNAPDriveSensor(QNAPSensor):
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self._api.data:
data = self._api.data["smart_drive_health"][self.monitor_device]
if self.coordinator.data:
data = self.coordinator.data["smart_drive_health"][self.monitor_device]
return {
ATTR_DRIVE: data["drive_number"],
ATTR_MODEL: data["model"],
@ -487,7 +445,7 @@ class QNAPVolumeSensor(QNAPSensor):
@property
def native_value(self):
"""Return the state of the sensor."""
data = self._api.data["volumes"][self.monitor_device]
data = self.coordinator.data["volumes"][self.monitor_device]
free_gb = int(data["free_size"]) / 1024 / 1024 / 1024
if self.entity_description.key == "volume_size_free":
@ -505,8 +463,8 @@ class QNAPVolumeSensor(QNAPSensor):
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if self._api.data:
data = self._api.data["volumes"][self.monitor_device]
if self.coordinator.data:
data = self.coordinator.data["volumes"][self.monitor_device]
total_gb = int(data["total_size"]) / 1024 / 1024 / 1024
return {