diff --git a/homeassistant/components/qnap_qsw/__init__.py b/homeassistant/components/qnap_qsw/__init__.py index c1b96a3298e..838a567199d 100644 --- a/homeassistant/components/qnap_qsw/__init__.py +++ b/homeassistant/components/qnap_qsw/__init__.py @@ -1,64 +1,17 @@ """The QNAP QSW integration.""" from __future__ import annotations -from typing import Any - -from aioqsw.const import ( - QSD_FIRMWARE, - QSD_FIRMWARE_INFO, - QSD_MAC, - QSD_PRODUCT, - QSD_SYSTEM_BOARD, -) from aioqsw.localapi import ConnectionOptions, QnapQswApi from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import aiohttp_client -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, MANUFACTURER +from .const import DOMAIN from .coordinator import QswUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.SENSOR] - - -class QswEntity(CoordinatorEntity[QswUpdateCoordinator]): - """Define an QNAP QSW entity.""" - - def __init__( - self, - coordinator: QswUpdateCoordinator, - entry: ConfigEntry, - ) -> None: - """Initialize.""" - super().__init__(coordinator) - - self._attr_device_info = DeviceInfo( - configuration_url=entry.data[CONF_URL], - connections={ - ( - CONNECTION_NETWORK_MAC, - self.get_device_value(QSD_SYSTEM_BOARD, QSD_MAC), - ) - }, - manufacturer=MANUFACTURER, - model=self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT), - name=self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT), - sw_version=self.get_device_value(QSD_FIRMWARE_INFO, QSD_FIRMWARE), - ) - - def get_device_value(self, key: str, subkey: str) -> Any: - """Return device value by key.""" - value = None - if key in self.coordinator.data: - data = self.coordinator.data[key] - if subkey in data: - value = data[subkey] - return value +PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/qnap_qsw/binary_sensor.py b/homeassistant/components/qnap_qsw/binary_sensor.py new file mode 100644 index 00000000000..467a3314070 --- /dev/null +++ b/homeassistant/components/qnap_qsw/binary_sensor.py @@ -0,0 +1,88 @@ +"""Support for the QNAP QSW binary sensors.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Final + +from aioqsw.const import QSD_ANOMALY, QSD_FIRMWARE_CONDITION, QSD_MESSAGE + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ATTR_MESSAGE, DOMAIN +from .coordinator import QswUpdateCoordinator +from .entity import QswEntityDescription, QswSensorEntity + + +@dataclass +class QswBinarySensorEntityDescription( + BinarySensorEntityDescription, QswEntityDescription +): + """A class that describes QNAP QSW binary sensor entities.""" + + attributes: dict[str, list[str]] | None = None + + +BINARY_SENSOR_TYPES: Final[tuple[QswBinarySensorEntityDescription, ...]] = ( + QswBinarySensorEntityDescription( + attributes={ + ATTR_MESSAGE: [QSD_FIRMWARE_CONDITION, QSD_MESSAGE], + }, + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + key=QSD_FIRMWARE_CONDITION, + name="Anomaly", + subkey=QSD_ANOMALY, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Add QNAP QSW binary sensors from a config_entry.""" + coordinator: QswUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + QswBinarySensor(coordinator, description, entry) + for description in BINARY_SENSOR_TYPES + if ( + description.key in coordinator.data + and description.subkey in coordinator.data[description.key] + ) + ) + + +class QswBinarySensor(QswSensorEntity, BinarySensorEntity): + """Define a QNAP QSW binary sensor.""" + + entity_description: QswBinarySensorEntityDescription + + def __init__( + self, + coordinator: QswUpdateCoordinator, + description: QswBinarySensorEntityDescription, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator, entry) + self._attr_name = f"{self.product} {description.name}" + self._attr_unique_id = ( + f"{entry.unique_id}_{description.key}_{description.subkey}" + ) + self.entity_description = description + self._async_update_attrs() + + @callback + def _async_update_attrs(self) -> None: + """Update binary sensor attributes.""" + self._attr_is_on = self.get_device_value( + self.entity_description.key, self.entity_description.subkey + ) + super()._async_update_attrs() diff --git a/homeassistant/components/qnap_qsw/const.py b/homeassistant/components/qnap_qsw/const.py index b55a817927f..a6cacfd1c40 100644 --- a/homeassistant/components/qnap_qsw/const.py +++ b/homeassistant/components/qnap_qsw/const.py @@ -3,6 +3,7 @@ from typing import Final ATTR_MAX: Final = "max" +ATTR_MESSAGE: Final = "message" DOMAIN: Final = "qnap_qsw" MANUFACTURER: Final = "QNAP" diff --git a/homeassistant/components/qnap_qsw/entity.py b/homeassistant/components/qnap_qsw/entity.py new file mode 100644 index 00000000000..c3550610d83 --- /dev/null +++ b/homeassistant/components/qnap_qsw/entity.py @@ -0,0 +1,93 @@ +"""Entity classes for the QNAP QSW integration.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from aioqsw.const import ( + QSD_FIRMWARE, + QSD_FIRMWARE_INFO, + QSD_MAC, + QSD_PRODUCT, + QSD_SYSTEM_BOARD, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_URL +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.entity import DeviceInfo, EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import MANUFACTURER +from .coordinator import QswUpdateCoordinator + + +class QswEntity(CoordinatorEntity[QswUpdateCoordinator]): + """Define an QNAP QSW entity.""" + + def __init__( + self, + coordinator: QswUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + self.product = self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT) + self._attr_device_info = DeviceInfo( + configuration_url=entry.data[CONF_URL], + connections={ + ( + CONNECTION_NETWORK_MAC, + self.get_device_value(QSD_SYSTEM_BOARD, QSD_MAC), + ) + }, + manufacturer=MANUFACTURER, + model=self.product, + name=self.product, + sw_version=self.get_device_value(QSD_FIRMWARE_INFO, QSD_FIRMWARE), + ) + + def get_device_value(self, key: str, subkey: str) -> Any: + """Return device value by key.""" + value = None + if key in self.coordinator.data: + data = self.coordinator.data[key] + if subkey in data: + value = data[subkey] + return value + + +@dataclass +class QswEntityDescriptionMixin: + """Mixin to describe a QSW entity.""" + + subkey: str + + +class QswEntityDescription(EntityDescription, QswEntityDescriptionMixin): + """Class to describe a QSW entity.""" + + attributes: dict[str, list[str]] | None = None + + +class QswSensorEntity(QswEntity): + """Base class for QSW sensor entities.""" + + entity_description: QswEntityDescription + + @callback + def _handle_coordinator_update(self) -> None: + """Update attributes when the coordinator updates.""" + self._async_update_attrs() + super()._handle_coordinator_update() + + @callback + def _async_update_attrs(self) -> None: + """Update attributes.""" + if self.entity_description.attributes: + self._attr_extra_state_attributes = { + key: self.get_device_value(val[0], val[1]) + for key, val in self.entity_description.attributes.items() + } diff --git a/homeassistant/components/qnap_qsw/sensor.py b/homeassistant/components/qnap_qsw/sensor.py index 8453232ce6f..0de8ec4a39e 100644 --- a/homeassistant/components/qnap_qsw/sensor.py +++ b/homeassistant/components/qnap_qsw/sensor.py @@ -7,8 +7,6 @@ from typing import Final from aioqsw.const import ( QSD_FAN1_SPEED, QSD_FAN2_SPEED, - QSD_PRODUCT, - QSD_SYSTEM_BOARD, QSD_SYSTEM_SENSOR, QSD_SYSTEM_TIME, QSD_TEMP, @@ -28,17 +26,16 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import QswEntity from .const import ATTR_MAX, DOMAIN, RPM from .coordinator import QswUpdateCoordinator +from .entity import QswEntityDescription, QswSensorEntity @dataclass -class QswSensorEntityDescription(SensorEntityDescription): +class QswSensorEntityDescription(SensorEntityDescription, QswEntityDescription): """A class that describes QNAP QSW sensor entities.""" attributes: dict[str, list[str]] | None = None - subkey: str = "" SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( @@ -96,7 +93,7 @@ async def async_setup_entry( ) -class QswSensor(QswEntity, SensorEntity): +class QswSensor(QswSensorEntity, SensorEntity): """Define a QNAP QSW sensor.""" entity_description: QswSensorEntityDescription @@ -109,30 +106,17 @@ class QswSensor(QswEntity, SensorEntity): ) -> None: """Initialize.""" super().__init__(coordinator, entry) - self._attr_name = ( - f"{self.get_device_value(QSD_SYSTEM_BOARD, QSD_PRODUCT)} {description.name}" - ) + self._attr_name = f"{self.product} {description.name}" self._attr_unique_id = ( f"{entry.unique_id}_{description.key}_{description.subkey}" ) self.entity_description = description self._async_update_attrs() - @callback - def _handle_coordinator_update(self) -> None: - """Update attributes when the coordinator updates.""" - self._async_update_attrs() - super()._handle_coordinator_update() - @callback def _async_update_attrs(self) -> None: """Update sensor attributes.""" self._attr_native_value = self.get_device_value( self.entity_description.key, self.entity_description.subkey ) - - if self.entity_description.attributes: - self._attr_extra_state_attributes = { - key: self.get_device_value(val[0], val[1]) - for key, val in self.entity_description.attributes.items() - } + super()._async_update_attrs() diff --git a/tests/components/qnap_qsw/test_binary_sensor.py b/tests/components/qnap_qsw/test_binary_sensor.py new file mode 100644 index 00000000000..a36a34f02be --- /dev/null +++ b/tests/components/qnap_qsw/test_binary_sensor.py @@ -0,0 +1,17 @@ +"""The binary sensor tests for the QNAP QSW platform.""" + +from homeassistant.components.qnap_qsw.const import ATTR_MESSAGE +from homeassistant.const import STATE_OFF +from homeassistant.core import HomeAssistant + +from .util import async_init_integration + + +async def test_qnap_qsw_create_binary_sensors(hass: HomeAssistant) -> None: + """Test creation of binary sensors.""" + + await async_init_integration(hass) + + state = hass.states.get("binary_sensor.qsw_m408_4c_anomaly") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_MESSAGE) is None