mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 23:27:37 +00:00
Add config_flow to QNAP (#80450)
* Create config_flow.py * Update __init__.py * Create const.py * Create strings.json * Update sensor.py * Update manifest.json * Update manifest.json * Add device name to entities * Correcting health sensor * Update manifest.json * Adding integration_type * Update sensor.py * Update __init__.py * Enums * Update sensor.py * Removed unused notify_create * Switch to SensorDeviceClass.TEMPERATURE * Update enums * Remove SENSOR_TYPES from const.py * Add SENSOR_TYPES to sensor.py * Removed dependancies * Removed import yaml * Removed entity_registry_enabled_default * Update const.py remove dups * Update manifest.json removed dups * Update __init__.py * Update const.py * Update manifest.json * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py remove unused * Update sensor.py add docstring * Update sensor.py add super * Remove FOLDER sensors * Remove VOLUME_NAME * fix cli * fix cli * Add config flow tests * Update requirements_test_all.txt * Update CODEOWNERS * Update homeassistant/components/qnap/config_flow.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/config_flow.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/config_flow.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update __init__.py Change unload to walrus Remove async_setup * Update const.py remove PLATFORMS * Update __init__.py add Platform Enum As per epenet * Update __init__.py * Update config_flow.py * Update sensor.py * Update __init__.py ruff * Update config_flow.py Ruff * Update sensor.py * Update const.py remove attrs * Update sensor.py add attrs * Revert tuple for sensor extend * Update sensor.py * Update coordinator.py * Update coordinator.py * Update sensor.py * Update coordinator.py * Update homeassistant/components/qnap/__init__.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/const.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/__init__.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update coordinator.py * Update __init__.py * Update coordinator.py * Update sensor.py * Add device_info * Update sensor.py * Update sensor.py self._attr_unique_id * Update sensor.py * Update sensor.py * Type Hints * Move tuples * Drive sensor name * Remove caps * Remove caps * Add YAML import * Update sensor.py fix ruff * Revert tuples * Update sensor.py as per review * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * Update sensor.py * assert uid is not None * 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 .coveragerc Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/config_flow.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/config_flow.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/const.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update homeassistant/components/qnap/const.py Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> * Update sensor.py * Update config_flow.py add imports * Update const.py * Update sensor.py move confs to const * Update config_flow.py add const * Update test_config_flow.py remove const name * Update test_config_flow.py remove standard result const * Update test_config_flow.py * Combine tests * Update test_config_flow.py * Update config_flow.py * Update test_config_flow.py * Update config_flow.py * Update test_config_flow.py * Update test_config_flow.py * Update sensor.py change UID as requested * Update sensor.py added check for monitor_device * fix tests * Remove rounding * Revert "Remove rounding" This reverts commit 61bf653c069d37cd7c20e3dd2f555f80b6e5d94f. --------- Co-authored-by: starkillerOG <starkiller.og@gmail.com> Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
parent
43fe30f6ee
commit
7c676c0a7d
@ -942,6 +942,8 @@ omit =
|
|||||||
homeassistant/components/pyload/sensor.py
|
homeassistant/components/pyload/sensor.py
|
||||||
homeassistant/components/qbittorrent/__init__.py
|
homeassistant/components/qbittorrent/__init__.py
|
||||||
homeassistant/components/qbittorrent/sensor.py
|
homeassistant/components/qbittorrent/sensor.py
|
||||||
|
homeassistant/components/qnap/__init__.py
|
||||||
|
homeassistant/components/qnap/coordinator.py
|
||||||
homeassistant/components/qnap/sensor.py
|
homeassistant/components/qnap/sensor.py
|
||||||
homeassistant/components/qrcode/image_processing.py
|
homeassistant/components/qrcode/image_processing.py
|
||||||
homeassistant/components/quantum_gateway/device_tracker.py
|
homeassistant/components/quantum_gateway/device_tracker.py
|
||||||
|
@ -970,6 +970,7 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/qld_bushfire/ @exxamalte
|
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||||
/tests/components/qld_bushfire/ @exxamalte
|
/tests/components/qld_bushfire/ @exxamalte
|
||||||
/homeassistant/components/qnap/ @disforw
|
/homeassistant/components/qnap/ @disforw
|
||||||
|
/tests/components/qnap/ @disforw
|
||||||
/homeassistant/components/qnap_qsw/ @Noltari
|
/homeassistant/components/qnap_qsw/ @Noltari
|
||||||
/tests/components/qnap_qsw/ @Noltari
|
/tests/components/qnap_qsw/ @Noltari
|
||||||
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
||||||
|
@ -1 +1,33 @@
|
|||||||
"""The qnap component."""
|
"""The qnap component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import QnapCoordinator
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [
|
||||||
|
Platform.SENSOR,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
|
"""Set the config entry up."""
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
coordinator = QnapCoordinator(hass, config_entry)
|
||||||
|
# Fetch initial data so we have data when entities subscribe
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
hass.data[DOMAIN][config_entry.entry_id] = coordinator
|
||||||
|
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||||
|
config_entry, PLATFORMS
|
||||||
|
):
|
||||||
|
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||||
|
return unload_ok
|
||||||
|
99
homeassistant/components/qnap/config_flow.py
Normal file
99
homeassistant/components/qnap/config_flow.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
"""Config flow to configure qnap component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from qnapstats import QNAPStats
|
||||||
|
from requests.exceptions import ConnectTimeout
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_MONITORED_CONDITIONS,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
|
)
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_DRIVES,
|
||||||
|
CONF_NICS,
|
||||||
|
CONF_VOLUMES,
|
||||||
|
DEFAULT_PORT,
|
||||||
|
DEFAULT_SSL,
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
DEFAULT_VERIFY_SSL,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
||||||
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class QnapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Qnap configuration flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_import(self, import_info: dict[str, Any]) -> FlowResult:
|
||||||
|
"""Set the config entry up from yaml."""
|
||||||
|
import_info.pop(CONF_MONITORED_CONDITIONS, None)
|
||||||
|
import_info.pop(CONF_NICS, None)
|
||||||
|
import_info.pop(CONF_DRIVES, None)
|
||||||
|
import_info.pop(CONF_VOLUMES, None)
|
||||||
|
return await self.async_step_user(import_info)
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self,
|
||||||
|
user_input: dict[str, Any] | None = None,
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
host = user_input[CONF_HOST]
|
||||||
|
protocol = "https" if user_input[CONF_SSL] else "http"
|
||||||
|
api = QNAPStats(
|
||||||
|
host=f"{protocol}://{host}",
|
||||||
|
port=user_input[CONF_PORT],
|
||||||
|
username=user_input[CONF_USERNAME],
|
||||||
|
password=user_input[CONF_PASSWORD],
|
||||||
|
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||||
|
timeout=DEFAULT_TIMEOUT,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
stats = await self.hass.async_add_executor_job(api.get_system_stats)
|
||||||
|
except ConnectTimeout:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except TypeError:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception as error: # pylint: disable=broad-except
|
||||||
|
_LOGGER.error(error)
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
unique_id = stats["system"]["serial_number"]
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
title = stats["system"]["name"]
|
||||||
|
return self.async_create_entry(title=title, data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=self.add_suggested_values_to_schema(DATA_SCHEMA, user_input),
|
||||||
|
errors=errors,
|
||||||
|
)
|
@ -1,6 +1,12 @@
|
|||||||
"""The Qnap constants."""
|
"""The Qnap constants."""
|
||||||
|
|
||||||
|
CONF_DRIVES = "drives"
|
||||||
|
CONF_NICS = "nics"
|
||||||
|
CONF_VOLUMES = "volumes"
|
||||||
|
|
||||||
DEFAULT_PORT = 8080
|
DEFAULT_PORT = 8080
|
||||||
DEFAULT_TIMEOUT = 5
|
DEFAULT_TIMEOUT = 5
|
||||||
|
DEFAULT_SSL = False
|
||||||
|
DEFAULT_VERIFY_SSL = True
|
||||||
|
|
||||||
DOMAIN = "qnap"
|
DOMAIN = "qnap"
|
||||||
|
@ -7,6 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
from qnapstats import QNAPStats
|
from qnapstats import QNAPStats
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
@ -17,7 +18,6 @@ from homeassistant.const import (
|
|||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.typing import ConfigType
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -30,18 +30,18 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
|
class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
|
||||||
"""Custom coordinator for the qnap integration."""
|
"""Custom coordinator for the qnap integration."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, config: ConfigType) -> None:
|
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||||
"""Initialize the qnap coordinator."""
|
"""Initialize the qnap coordinator."""
|
||||||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
|
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
|
||||||
|
|
||||||
protocol = "https" if config[CONF_SSL] else "http"
|
protocol = "https" if config_entry.data[CONF_SSL] else "http"
|
||||||
self._api = QNAPStats(
|
self._api = QNAPStats(
|
||||||
f"{protocol}://{config.get(CONF_HOST)}",
|
f"{protocol}://{config_entry.data.get(CONF_HOST)}",
|
||||||
config.get(CONF_PORT),
|
config_entry.data.get(CONF_PORT),
|
||||||
config.get(CONF_USERNAME),
|
config_entry.data.get(CONF_USERNAME),
|
||||||
config.get(CONF_PASSWORD),
|
config_entry.data.get(CONF_PASSWORD),
|
||||||
verify_ssl=config.get(CONF_VERIFY_SSL),
|
verify_ssl=config_entry.data.get(CONF_VERIFY_SSL),
|
||||||
timeout=config.get(CONF_TIMEOUT),
|
timeout=config_entry.data.get(CONF_TIMEOUT),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _sync_update(self) -> dict[str, dict[str, Any]]:
|
def _sync_update(self) -> dict[str, dict[str, Any]]:
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"domain": "qnap",
|
"domain": "qnap",
|
||||||
"name": "QNAP",
|
"name": "QNAP",
|
||||||
"codeowners": ["@disforw"],
|
"codeowners": ["@disforw"],
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/qnap",
|
"documentation": "https://www.home-assistant.io/integrations/qnap",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
"""Support for QNAP NAS Sensors."""
|
"""Support for QNAP NAS Sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -28,17 +31,25 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DEFAULT_PORT, DEFAULT_TIMEOUT
|
from .const import (
|
||||||
|
CONF_DRIVES,
|
||||||
|
CONF_NICS,
|
||||||
|
CONF_VOLUMES,
|
||||||
|
DEFAULT_PORT,
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
from .coordinator import QnapCoordinator
|
from .coordinator import QnapCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_DRIVE = "Drive"
|
ATTR_DRIVE = "Drive"
|
||||||
ATTR_DRIVE_SIZE = "Drive Size"
|
|
||||||
ATTR_IP = "IP Address"
|
ATTR_IP = "IP Address"
|
||||||
ATTR_MAC = "MAC Address"
|
ATTR_MAC = "MAC Address"
|
||||||
ATTR_MASK = "Mask"
|
ATTR_MASK = "Mask"
|
||||||
@ -53,13 +64,6 @@ ATTR_TYPE = "Type"
|
|||||||
ATTR_UPTIME = "Uptime"
|
ATTR_UPTIME = "Uptime"
|
||||||
ATTR_VOLUME_SIZE = "Volume Size"
|
ATTR_VOLUME_SIZE = "Volume Size"
|
||||||
|
|
||||||
CONF_DRIVES = "drives"
|
|
||||||
CONF_NICS = "nics"
|
|
||||||
CONF_VOLUMES = "volumes"
|
|
||||||
|
|
||||||
NOTIFICATION_ID = "qnap_notification"
|
|
||||||
NOTIFICATION_TITLE = "QNAP Sensor Setup"
|
|
||||||
|
|
||||||
_SYSTEM_MON_COND: tuple[SensorEntityDescription, ...] = (
|
_SYSTEM_MON_COND: tuple[SensorEntityDescription, ...] = (
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="status",
|
key="status",
|
||||||
@ -224,72 +228,87 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the QNAP NAS sensor."""
|
"""Set up the qnap sensor platform from yaml."""
|
||||||
coordinator = QnapCoordinator(hass, config)
|
|
||||||
|
async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"deprecated_yaml",
|
||||||
|
breaks_in_ha_version="2023.12.0",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
translation_key="deprecated_yaml",
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: config_entries.ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up entry."""
|
||||||
|
coordinator = QnapCoordinator(hass, config_entry)
|
||||||
await coordinator.async_refresh()
|
await coordinator.async_refresh()
|
||||||
if not coordinator.last_update_success:
|
if not coordinator.last_update_success:
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
uid = config_entry.unique_id
|
||||||
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
|
assert uid is not None
|
||||||
sensors: list[QNAPSensor] = []
|
sensors: list[QNAPSensor] = []
|
||||||
|
|
||||||
# Basic sensors
|
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
QNAPSystemSensor(coordinator, description)
|
QNAPSystemSensor(coordinator, description, uid)
|
||||||
for description in _SYSTEM_MON_COND
|
for description in _SYSTEM_MON_COND
|
||||||
if description.key in monitored_conditions
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[QNAPCPUSensor(coordinator, description, uid) for description in _CPU_MON_COND]
|
||||||
QNAPCPUSensor(coordinator, description)
|
|
||||||
for description in _CPU_MON_COND
|
|
||||||
if description.key in monitored_conditions
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
QNAPMemorySensor(coordinator, description)
|
QNAPMemorySensor(coordinator, description, uid)
|
||||||
for description in _MEMORY_MON_COND
|
for description in _MEMORY_MON_COND
|
||||||
if description.key in monitored_conditions
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Network sensors
|
# Network sensors
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
QNAPNetworkSensor(coordinator, description, nic)
|
QNAPNetworkSensor(coordinator, description, uid, nic)
|
||||||
for nic in config.get(CONF_NICS, coordinator.data["system_stats"]["nics"])
|
for nic in coordinator.data["system_stats"]["nics"]
|
||||||
for description in _NETWORK_MON_COND
|
for description in _NETWORK_MON_COND
|
||||||
if description.key in monitored_conditions
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Drive sensors
|
# Drive sensors
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
QNAPDriveSensor(coordinator, description, drive)
|
QNAPDriveSensor(coordinator, description, uid, drive)
|
||||||
for drive in config.get(CONF_DRIVES, coordinator.data["smart_drive_health"])
|
for drive in coordinator.data["smart_drive_health"]
|
||||||
for description in _DRIVE_MON_COND
|
for description in _DRIVE_MON_COND
|
||||||
if description.key in monitored_conditions
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Volume sensors
|
# Volume sensors
|
||||||
sensors.extend(
|
sensors.extend(
|
||||||
[
|
[
|
||||||
QNAPVolumeSensor(coordinator, description, volume)
|
QNAPVolumeSensor(coordinator, description, uid, volume)
|
||||||
for volume in config.get(CONF_VOLUMES, coordinator.data["volumes"])
|
for volume in coordinator.data["volumes"]
|
||||||
for description in _VOLUME_MON_COND
|
for description in _VOLUME_MON_COND
|
||||||
if description.key in monitored_conditions
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
async_add_entities(sensors)
|
||||||
add_entities(sensors)
|
|
||||||
|
|
||||||
|
|
||||||
def round_nicely(number):
|
def round_nicely(number):
|
||||||
@ -309,13 +328,24 @@ class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity):
|
|||||||
self,
|
self,
|
||||||
coordinator: QnapCoordinator,
|
coordinator: QnapCoordinator,
|
||||||
description: SensorEntityDescription,
|
description: SensorEntityDescription,
|
||||||
|
unique_id: str,
|
||||||
monitor_device: str | None = None,
|
monitor_device: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self.monitor_device = monitor_device
|
|
||||||
self.device_name = self.coordinator.data["system_stats"]["system"]["name"]
|
self.device_name = self.coordinator.data["system_stats"]["system"]["name"]
|
||||||
|
self.monitor_device = monitor_device
|
||||||
|
self._attr_unique_id = f"{unique_id}_{description.key}"
|
||||||
|
if monitor_device:
|
||||||
|
self._attr_unique_id = f"{self._attr_unique_id}_{monitor_device}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, unique_id)},
|
||||||
|
name=self.device_name,
|
||||||
|
model=self.coordinator.data["system_stats"]["system"]["model"],
|
||||||
|
sw_version=self.coordinator.data["system_stats"]["firmware"]["version"],
|
||||||
|
manufacturer="QNAP",
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -493,7 +523,5 @@ class QNAPVolumeSensor(QNAPSensor):
|
|||||||
total_gb = int(data["total_size"]) / 1024 / 1024 / 1024
|
total_gb = int(data["total_size"]) / 1024 / 1024 / 1024
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_VOLUME_SIZE: (
|
ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"
|
||||||
f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
23
homeassistant/components/qnap/strings.json
Normal file
23
homeassistant/components/qnap/strings.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Connect to the QNAP device",
|
||||||
|
"description": "This qnap sensor allows getting various statistics from your QNAP NAS.",
|
||||||
|
"data": {
|
||||||
|
"host": "Hostname",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"port": "Port",
|
||||||
|
"ssl": "Enable SSL",
|
||||||
|
"verify_ssl": "Verify SSL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Cannot connect to host",
|
||||||
|
"invalid_auth": "Bad authentication",
|
||||||
|
"unknown": "Unknown error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -355,6 +355,7 @@ FLOWS = {
|
|||||||
"pvpc_hourly_pricing",
|
"pvpc_hourly_pricing",
|
||||||
"qbittorrent",
|
"qbittorrent",
|
||||||
"qingping",
|
"qingping",
|
||||||
|
"qnap",
|
||||||
"qnap_qsw",
|
"qnap_qsw",
|
||||||
"rachio",
|
"rachio",
|
||||||
"radarr",
|
"radarr",
|
||||||
|
@ -4387,7 +4387,7 @@
|
|||||||
"integrations": {
|
"integrations": {
|
||||||
"qnap": {
|
"qnap": {
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"config_flow": false,
|
"config_flow": true,
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"name": "QNAP"
|
"name": "QNAP"
|
||||||
},
|
},
|
||||||
|
@ -1636,6 +1636,9 @@ pyzerproc==0.4.8
|
|||||||
# homeassistant.components.qingping
|
# homeassistant.components.qingping
|
||||||
qingping-ble==0.8.2
|
qingping-ble==0.8.2
|
||||||
|
|
||||||
|
# homeassistant.components.qnap
|
||||||
|
qnapstats==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.radio_browser
|
# homeassistant.components.radio_browser
|
||||||
radios==0.1.1
|
radios==0.1.1
|
||||||
|
|
||||||
|
1
tests/components/qnap/__init__.py
Normal file
1
tests/components/qnap/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the QNAP integration."""
|
33
tests/components/qnap/conftest.py
Normal file
33
tests/components/qnap/conftest.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""Setup the QNAP tests."""
|
||||||
|
from collections.abc import Generator
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
TEST_HOST = "1.2.3.4"
|
||||||
|
TEST_USERNAME = "admin"
|
||||||
|
TEST_PASSWORD = "password"
|
||||||
|
TEST_NAS_NAME = "Test NAS name"
|
||||||
|
TEST_SERIAL = "123456789"
|
||||||
|
|
||||||
|
TEST_SYSTEM_STATS = {"system": {"serial_number": TEST_SERIAL, "name": TEST_NAS_NAME}}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||||
|
"""Override async_setup_entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.qnap.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def qnap_connect(mock_get_source_ip: None) -> Generator[MagicMock, None, None]:
|
||||||
|
"""Mock qnap connection."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.qnap.config_flow.QNAPStats", autospec=True
|
||||||
|
) as host_mock_class:
|
||||||
|
host_mock = host_mock_class.return_value
|
||||||
|
host_mock.get_system_stats.return_value = TEST_SYSTEM_STATS
|
||||||
|
yield host_mock
|
110
tests/components/qnap/test_config_flow.py
Normal file
110
tests/components/qnap/test_config_flow.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
"""Test the QNAP config flow."""
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from requests.exceptions import ConnectTimeout
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components.qnap import const
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .conftest import TEST_HOST, TEST_PASSWORD, TEST_USERNAME
|
||||||
|
|
||||||
|
STANDARD_CONFIG = {
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures("mock_setup_entry", "qnap_connect")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow(hass: HomeAssistant, qnap_connect: MagicMock) -> None:
|
||||||
|
"""Config flow manually initialized by the user."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
qnap_connect.get_system_stats.side_effect = ConnectTimeout("Test error")
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
STANDARD_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
qnap_connect.get_system_stats.side_effect = TypeError("Test error")
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
STANDARD_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "invalid_auth"}
|
||||||
|
|
||||||
|
qnap_connect.get_system_stats.side_effect = Exception("Test error")
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
STANDARD_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is data_entry_flow.FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
qnap_connect.get_system_stats.side_effect = None
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
STANDARD_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "Test NAS name"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_USERNAME: "admin",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_SSL: const.DEFAULT_SSL,
|
||||||
|
CONF_VERIFY_SSL: const.DEFAULT_VERIFY_SSL,
|
||||||
|
CONF_PORT: const.DEFAULT_PORT,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_import(hass: HomeAssistant) -> None:
|
||||||
|
"""Test import of YAML config."""
|
||||||
|
data = STANDARD_CONFIG
|
||||||
|
data[CONF_SSL] = const.DEFAULT_SSL
|
||||||
|
data[CONF_VERIFY_SSL] = const.DEFAULT_VERIFY_SSL
|
||||||
|
data[CONF_PORT] = const.DEFAULT_PORT
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
const.DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "Test NAS name"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_USERNAME: "admin",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_SSL: const.DEFAULT_SSL,
|
||||||
|
CONF_VERIFY_SSL: const.DEFAULT_VERIFY_SSL,
|
||||||
|
CONF_PORT: const.DEFAULT_PORT,
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user