From a83a4fd5981f2751ffa9bbaeb970ca1407324ad8 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 5 Jan 2023 08:36:16 -0500 Subject: [PATCH 001/132] Update Native unit of measurement native_unit_of_measurement=UnitOfTemperature.CELSIUS --- custom_components/qnap/const.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 366ffba..c3577b4 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -6,13 +6,13 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, - TEMP_CELSIUS, SensorEntityDescription, ) from homeassistant.const import ( DATA_GIBIBYTES, DATA_RATE_MEBIBYTES_PER_SECOND, PERCENTAGE, + UnitOfTemperature, ) ATTR_DRIVE = "Drive" @@ -61,7 +61,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="basic", key="system_temp", name="System Temperature", - native_unit_of_measurement=TEMP_CELSIUS, + native_unit_of_measurement=UnitOfTemperature.CELSIUS device_class=DEVICE_CLASS_TEMPERATURE, icon="mdi:thermometer", entity_registry_enabled_default=True, @@ -71,7 +71,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="cpu", key="cpu_temp", name="CPU Temperature", - native_unit_of_measurement=TEMP_CELSIUS, + native_unit_of_measurement=UnitOfTemperature.CELSIUS device_class=DEVICE_CLASS_TEMPERATURE, icon="mdi:checkbox-marked-circle-outline", entity_registry_enabled_default=False, @@ -149,7 +149,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="drive", key="drive_temp", name="Temperature", - native_unit_of_measurement=TEMP_CELSIUS, + native_unit_of_measurement=UnitOfTemperature.CELSIUS icon="mdi:thermometer", entity_registry_enabled_default=False, state_class=STATE_CLASS_MEASUREMENT, From b54d25b2a56d3f19b799095b4a0f9783c80f282b Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 5 Jan 2023 08:45:30 -0500 Subject: [PATCH 002/132] Update const.py --- custom_components/qnap/const.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index c3577b4..6044067 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -61,7 +61,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="basic", key="system_temp", name="System Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS + native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=DEVICE_CLASS_TEMPERATURE, icon="mdi:thermometer", entity_registry_enabled_default=True, @@ -71,7 +71,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="cpu", key="cpu_temp", name="CPU Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS + native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=DEVICE_CLASS_TEMPERATURE, icon="mdi:checkbox-marked-circle-outline", entity_registry_enabled_default=False, @@ -149,7 +149,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="drive", key="drive_temp", name="Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS + native_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:thermometer", entity_registry_enabled_default=False, state_class=STATE_CLASS_MEASUREMENT, From 5edfab524ece74d66f12c683ed23bc314c340bd4 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 7 Jan 2023 08:28:20 -0500 Subject: [PATCH 003/132] Update const.py --- custom_components/qnap/const.py | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 6044067..06bbcba 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -4,8 +4,8 @@ from dataclasses import dataclass from homeassistant.components.sensor import ( - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + SensorDeviceClass, + SensorStateClass, SensorEntityDescription, ) from homeassistant.const import ( @@ -62,20 +62,20 @@ class QNapSensorEntityDescription(SensorEntityDescription): key="system_temp", name="System Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=DEVICE_CLASS_TEMPERATURE, + device_class=SensorDeviceClass.TEMPERATURE, icon="mdi:thermometer", entity_registry_enabled_default=True, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="cpu", key="cpu_temp", name="CPU Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=DEVICE_CLASS_TEMPERATURE, + device_class=SensorDeviceClass.TEMPERATURE, icon="mdi:checkbox-marked-circle-outline", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="cpu", @@ -84,7 +84,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=PERCENTAGE, icon="mdi:chip", entity_registry_enabled_default=True, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="memory", @@ -93,7 +93,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_GIBIBYTES, icon="mdi:memory", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="memory", @@ -102,7 +102,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_GIBIBYTES, icon="mdi:memory", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="memory", @@ -111,7 +111,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=PERCENTAGE, icon="mdi:memory", entity_registry_enabled_default=True, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="network", @@ -127,7 +127,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_RATE_MEBIBYTES_PER_SECOND, icon="mdi:upload", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="network", @@ -136,7 +136,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_RATE_MEBIBYTES_PER_SECOND, icon="mdi:download", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="drive", @@ -152,7 +152,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=UnitOfTemperature.CELSIUS, icon="mdi:thermometer", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="folder", @@ -161,7 +161,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_GIBIBYTES, icon="mdi:chart-pie", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="folder", @@ -170,7 +170,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=PERCENTAGE, icon="mdi:chart-pie", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="volume", @@ -179,7 +179,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_GIBIBYTES, icon="mdi:chart-pie", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="volume", @@ -188,7 +188,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=DATA_GIBIBYTES, icon="mdi:chart-pie", entity_registry_enabled_default=False, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( stype="volume", @@ -197,7 +197,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=PERCENTAGE, icon="mdi:chart-pie", entity_registry_enabled_default=True, - state_class=STATE_CLASS_MEASUREMENT, + state_class=SensorStateClass.MEASUREMENT, ), ) From 948e0d8d8354491b87efc5e540ccc0ddcfd42d33 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 13 Jan 2023 03:52:53 -0500 Subject: [PATCH 004/132] Update sensor.py --- custom_components/qnap/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 9e590e6..cd21208 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -1,4 +1,3 @@ - """Support for QNAP NAS Sensors.""" import logging From a44d6f1e5e5fc8076df3d2225634c7d2c69c73c1 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 9 Feb 2023 15:15:45 -0500 Subject: [PATCH 005/132] Update manifest.json --- custom_components/qnap/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/manifest.json b/custom_components/qnap/manifest.json index c17f98d..b30c13e 100644 --- a/custom_components/qnap/manifest.json +++ b/custom_components/qnap/manifest.json @@ -4,9 +4,9 @@ "config_flow": true, "version": "10.0", "dependencies": ["persistent_notification"], - "documentation": "https://www.home-assistant.io/integrations/qnap", "requirements": ["qnapstats==0.4.0"], "codeowners": ["@colinodell", "@disforw"], + "documentation": "https://www.home-assistant.io/integrations/qnap", "iot_class": "local_polling", "integration_type": "device" } From 624dd5320313c0ac66a9d13fa8f49c0049a9d754 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 9 Feb 2023 15:18:27 -0500 Subject: [PATCH 006/132] Update sensor.py --- custom_components/qnap/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index cd21208..0fd7cde 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -113,7 +113,7 @@ class QNAPSensor(CoordinatorEntity, SensorEntity): def __init__( self, coordinator, description, uid, monitor_device=None, monitor_subdevice=None - ): + ) -> None: """Initialize the sensor.""" self.coordinator = coordinator self.entity_description = description From cc9a475716d0615d677ba39094d3de6e5809df2a Mon Sep 17 00:00:00 2001 From: disforw Date: Sun, 26 Mar 2023 15:54:39 -0400 Subject: [PATCH 007/132] Update README.md --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b8cd215..e8d8627 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ -# QNAP NAP Integration +# QNAP Integration Re-write with Config-Flow -This is a complete re-write of the Core QNAP integration, adding config_flow and unique_ID. Using this custom componant for development and testing before creating a PR and adding to Core. +This is a complete re-write of the Core QNAP integration, adding config_flow and unique_ID. Using this custom componant will override the built-in QNAP integration. -## Looking for contributers, testers, and ideas... +## Installation + +Due to this repository overriding an existing core integration, it will need to be added as a custom repository. Goto HACS - Custom Integrations - three dots at the top - Custom Repositories. +Paste: https://github.com/disforw/qnap + + +NOTE: All folder and drive entities will be disabled by default. You will need to go to the entities you want and enable them manually. From 0cd09e6b3f80cd661fffb260f1580f77bcbaf856 Mon Sep 17 00:00:00 2001 From: disforw Date: Sun, 26 Mar 2023 16:48:43 -0400 Subject: [PATCH 008/132] Update const.py Use new HA constants --- custom_components/qnap/const.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 06bbcba..0d4e44d 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -9,10 +9,10 @@ SensorEntityDescription, ) from homeassistant.const import ( - DATA_GIBIBYTES, - DATA_RATE_MEBIBYTES_PER_SECOND, PERCENTAGE, UnitOfTemperature, + UnitOfInformation, + UnitOfDataRate, ) ATTR_DRIVE = "Drive" @@ -90,7 +90,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="memory", key="memory_free", name="Memory Available", - native_unit_of_measurement=DATA_GIBIBYTES, + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, icon="mdi:memory", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -99,7 +99,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="memory", key="memory_used", name="Memory Used", - native_unit_of_measurement=DATA_GIBIBYTES, + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, icon="mdi:memory", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -124,7 +124,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="network", key="network_tx", name="Network Up", - native_unit_of_measurement=DATA_RATE_MEBIBYTES_PER_SECOND, + native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, icon="mdi:upload", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -133,7 +133,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="network", key="network_rx", name="Network Down", - native_unit_of_measurement=DATA_RATE_MEBIBYTES_PER_SECOND, + native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, icon="mdi:download", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -158,7 +158,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="folder", key="folder_size_used", name="Used Space", - native_unit_of_measurement=DATA_GIBIBYTES, + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, icon="mdi:chart-pie", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -176,7 +176,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="volume", key="volume_size_used", name="Used Space", - native_unit_of_measurement=DATA_GIBIBYTES, + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, icon="mdi:chart-pie", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, @@ -185,7 +185,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): stype="volume", key="volume_size_free", name="Free Space", - native_unit_of_measurement=DATA_GIBIBYTES, + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, icon="mdi:chart-pie", entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, From 43d966b3ade08fb859344591c02ff22724bd8697 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 08:24:34 -0400 Subject: [PATCH 009/132] Removed notify_create unused --- custom_components/qnap/config_flow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/qnap/config_flow.py b/custom_components/qnap/config_flow.py index 26e6375..c8d5c29 100644 --- a/custom_components/qnap/config_flow.py +++ b/custom_components/qnap/config_flow.py @@ -6,7 +6,6 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.persistent_notification import create as notify_create from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, From ec7f5715127e2f79d5976c588279477c58a9bda8 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 09:08:02 -0400 Subject: [PATCH 010/132] Remove QNapSensorEntityDescription --- custom_components/qnap/const.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 0d4e44d..7b8e30c 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -41,14 +41,6 @@ NOTIFICATION_TITLE = "QNAP Sensor Setup" VOLUME_NAME = "volume" - -@dataclass -class QNapSensorEntityDescription(SensorEntityDescription): - """Represents an Flow Sensor.""" - - stype: str | None = None - - SENSOR_TYPES: tuple[QNapSensorEntityDescription, ...] = ( QNapSensorEntityDescription( stype="basic", From a2f62477be629e8ad0a76f81032ed2b9a7601b03 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 09:22:14 -0400 Subject: [PATCH 011/132] Add SENSOR_TYPES --- custom_components/qnap/sensor.py | 172 +++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 0fd7cde..94e1202 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -7,6 +7,19 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from dataclasses import dataclass +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorStateClass, + SensorEntityDescription, +) +from homeassistant.const import ( + PERCENTAGE, + UnitOfTemperature, + UnitOfInformation, + UnitOfDataRate, +) + from .const import ( ATTR_DRIVE, @@ -108,6 +121,165 @@ def round_nicely(number): return round(number) +@dataclass +class QNapSensorEntityDescription(SensorEntityDescription): + """Represents an Flow Sensor.""" + + stype: str | None = None + + +SENSOR_TYPES: tuple[QNapSensorEntityDescription, ...] = ( + QNapSensorEntityDescription( + stype="basic", + key="status", + name="Health", + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=True, + ), + QNapSensorEntityDescription( + stype="basic", + key="system_temp", + name="System Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + icon="mdi:thermometer", + entity_registry_enabled_default=True, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="cpu", + key="cpu_temp", + name="CPU Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="cpu", + key="cpu_usage", + name="CPU Usage", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chip", + entity_registry_enabled_default=True, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="memory", + key="memory_free", + name="Memory Available", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:memory", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="memory", + key="memory_used", + name="Memory Used", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:memory", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="memory", + key="memory_percent_used", + name="Memory Usage", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:memory", + entity_registry_enabled_default=True, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="network", + key="network_link_status", + name="Network Link", + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=True, + ), + QNapSensorEntityDescription( + stype="network", + key="network_tx", + name="Network Up", + native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, + icon="mdi:upload", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="network", + key="network_rx", + name="Network Down", + native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, + icon="mdi:download", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="drive", + key="drive_smart_status", + name="SMART Status", + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=False, + ), + QNapSensorEntityDescription( + stype="drive", + key="drive_temp", + name="Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + icon="mdi:thermometer", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="folder", + key="folder_size_used", + name="Used Space", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="folder", + key="folder_percentage_used", + name="Folder Used", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="volume", + key="volume_size_used", + name="Used Space", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="volume", + key="volume_size_free", + name="Free Space", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="volume", + key="volume_percentage_used", + name="Volume Used", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chart-pie", + entity_registry_enabled_default=True, + state_class=SensorStateClass.MEASUREMENT, + ), +) + class QNAPSensor(CoordinatorEntity, SensorEntity): """Base class for a QNAP sensor.""" From 19a1a5484e45ba2959d5a8d30cc0f66db5e6c4db Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 09:22:25 -0400 Subject: [PATCH 012/132] Remove SENSOR_TYPES --- custom_components/qnap/const.py | 166 -------------------------------- 1 file changed, 166 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 7b8e30c..f1a1cbc 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -1,20 +1,6 @@ """The Qnap constants.""" from __future__ import annotations -from dataclasses import dataclass - -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorStateClass, - SensorEntityDescription, -) -from homeassistant.const import ( - PERCENTAGE, - UnitOfTemperature, - UnitOfInformation, - UnitOfDataRate, -) - ATTR_DRIVE = "Drive" ATTR_ENABLED = "Sensor Enabled" ATTR_IP = "IP Address" @@ -41,158 +27,6 @@ NOTIFICATION_TITLE = "QNAP Sensor Setup" VOLUME_NAME = "volume" -SENSOR_TYPES: tuple[QNapSensorEntityDescription, ...] = ( - QNapSensorEntityDescription( - stype="basic", - key="status", - name="Health", - icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=True, - ), - QNapSensorEntityDescription( - stype="basic", - key="system_temp", - name="System Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - icon="mdi:thermometer", - entity_registry_enabled_default=True, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="cpu", - key="cpu_temp", - name="CPU Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="cpu", - key="cpu_usage", - name="CPU Usage", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chip", - entity_registry_enabled_default=True, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="memory", - key="memory_free", - name="Memory Available", - native_unit_of_measurement=UnitOfInformation.GIBIBYTES, - icon="mdi:memory", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="memory", - key="memory_used", - name="Memory Used", - native_unit_of_measurement=UnitOfInformation.GIBIBYTES, - icon="mdi:memory", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="memory", - key="memory_percent_used", - name="Memory Usage", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:memory", - entity_registry_enabled_default=True, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="network", - key="network_link_status", - name="Network Link", - icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=True, - ), - QNapSensorEntityDescription( - stype="network", - key="network_tx", - name="Network Up", - native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, - icon="mdi:upload", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="network", - key="network_rx", - name="Network Down", - native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, - icon="mdi:download", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="drive", - key="drive_smart_status", - name="SMART Status", - icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=False, - ), - QNapSensorEntityDescription( - stype="drive", - key="drive_temp", - name="Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - icon="mdi:thermometer", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="folder", - key="folder_size_used", - name="Used Space", - native_unit_of_measurement=UnitOfInformation.GIBIBYTES, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="folder", - key="folder_percentage_used", - name="Folder Used", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="volume", - key="volume_size_used", - name="Used Space", - native_unit_of_measurement=UnitOfInformation.GIBIBYTES, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="volume", - key="volume_size_free", - name="Free Space", - native_unit_of_measurement=UnitOfInformation.GIBIBYTES, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="volume", - key="volume_percentage_used", - name="Volume Used", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chart-pie", - entity_registry_enabled_default=True, - state_class=SensorStateClass.MEASUREMENT, - ), -) - BAS_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "basic"] CPU_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "cpu"] MEM_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "memory"] From 56b0f7832475445d0d4d25459bcfac0eb89a1626 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 09:30:55 -0400 Subject: [PATCH 013/132] Update sensor.py --- custom_components/qnap/sensor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 94e1202..1da56c6 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -1,26 +1,25 @@ """Support for QNAP NAS Sensors.""" import logging -from homeassistant.components.sensor import SensorEntity -from homeassistant.const import ATTR_NAME, DATA_GIBIBYTES from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from dataclasses import dataclass from homeassistant.components.sensor import ( + SensorEntity, SensorDeviceClass, SensorStateClass, SensorEntityDescription, ) from homeassistant.const import ( + ATTR_NAME, PERCENTAGE, UnitOfTemperature, UnitOfInformation, UnitOfDataRate, ) - from .const import ( ATTR_DRIVE, ATTR_IP, @@ -359,7 +358,7 @@ def extra_state_attributes(self): if self.coordinator.data: data = self.coordinator.data["system_stats"]["memory"] size = round_nicely(float(data["total"]) / 1024) - return {ATTR_MEMORY_SIZE: f"{size} {DATA_GIBIBYTES}"} + return {ATTR_MEMORY_SIZE: f"{size} {UnitOfInformation.GIBIBYTES}"} class QNAPSystemSensor(QNAPSensor): @@ -484,7 +483,7 @@ def extra_state_attributes(self): data = self.coordinator.data["volumes"][self.monitor_device] total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 - return {ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {DATA_GIBIBYTES}"} + return {ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"} class QNAPFolderSensor(QNAPSensor): @@ -519,6 +518,6 @@ def extra_state_attributes(self): volume_name = self.monitor_device return { - ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {DATA_GIBIBYTES}", + ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}", VOLUME_NAME: volume_name, } From c50669ba393de00f6bc9eabe45844ce08cad55d0 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 21:02:45 -0400 Subject: [PATCH 014/132] Update const.py --- custom_components/qnap/const.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index f1a1cbc..6666e75 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -26,11 +26,3 @@ NOTIFICATION_ID = "qnap_notification" NOTIFICATION_TITLE = "QNAP Sensor Setup" VOLUME_NAME = "volume" - -BAS_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "basic"] -CPU_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "cpu"] -MEM_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "memory"] -NET_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "network"] -DRI_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "drive"] -FOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "folder"] -VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] From e943d11d5e2526817a5c43eaee1589a4b282d9ea Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 21:03:15 -0400 Subject: [PATCH 015/132] Update sensor.py --- custom_components/qnap/sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 1da56c6..f2f8b7d 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -278,6 +278,13 @@ class QNapSensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, ), ) +BAS_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "basic"] +CPU_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "cpu"] +MEM_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "memory"] +NET_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "network"] +DRI_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "drive"] +FOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "folder"] +VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] class QNAPSensor(CoordinatorEntity, SensorEntity): """Base class for a QNAP sensor.""" From 5a157d95774d73917ad1d48ea29723f98d873231 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 31 May 2023 21:21:56 -0400 Subject: [PATCH 016/132] Update sensor.py --- custom_components/qnap/sensor.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index f2f8b7d..a268e4c 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -35,15 +35,8 @@ ATTR_TYPE, ATTR_UPTIME, ATTR_VOLUME_SIZE, - BAS_SENSOR, - CPU_SENSOR, DEFAULT_NAME, DOMAIN, - DRI_SENSOR, - FOL_SENSOR, - MEM_SENSOR, - NET_SENSOR, - VOL_SENSOR, VOLUME_NAME, ) From 8e26fea49ff92b6aa7152eb4f8f0d01f4855930a Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 1 Jun 2023 08:01:26 -0400 Subject: [PATCH 017/132] Update manifest.json --- custom_components/qnap/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/manifest.json b/custom_components/qnap/manifest.json index b30c13e..0a25be1 100644 --- a/custom_components/qnap/manifest.json +++ b/custom_components/qnap/manifest.json @@ -5,7 +5,7 @@ "version": "10.0", "dependencies": ["persistent_notification"], "requirements": ["qnapstats==0.4.0"], - "codeowners": ["@colinodell", "@disforw"], + "codeowners": ["@disforw"], "documentation": "https://www.home-assistant.io/integrations/qnap", "iot_class": "local_polling", "integration_type": "device" From 2592bcbe65171582968d1b6fca7c2ac976026b6e Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 1 Jun 2023 12:29:17 -0400 Subject: [PATCH 018/132] Update __init__.py --- custom_components/qnap/__init__.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index eded8c8..17c00bc 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -22,28 +22,6 @@ _LOGGER = logging.getLogger(__name__) - -async def async_setup(hass, config): - """Set up the qnap environment.""" - hass.data.setdefault(DOMAIN, {}) - - # Import configuration from sensor platform - config_platform = config_per_platform(config, "sensor") - for p_type, p_config in config_platform: - if p_type != DOMAIN: - continue - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=p_config, - ) - ) - - return True - - async def async_setup_entry(hass, config_entry): """Set the config entry up.""" host = config_entry.data[CONF_HOST] From 61991aab420cfdacb56974158b1b4adabdbd097f Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 1 Jun 2023 12:44:56 -0400 Subject: [PATCH 019/132] Update __init__.py --- custom_components/qnap/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 17c00bc..f2305cb 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -22,6 +22,12 @@ _LOGGER = logging.getLogger(__name__) +async def async_setup(hass, config): + """Set up the qnap environment.""" + hass.data.setdefault(DOMAIN, {}) + + return True + async def async_setup_entry(hass, config_entry): """Set the config entry up.""" host = config_entry.data[CONF_HOST] From 5c46a35bf000f00c8c1ec20848c829e8719e8b29 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 1 Jun 2023 23:12:15 -0400 Subject: [PATCH 020/132] Update manifest.json --- custom_components/qnap/manifest.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/qnap/manifest.json b/custom_components/qnap/manifest.json index 0a25be1..fa83b94 100644 --- a/custom_components/qnap/manifest.json +++ b/custom_components/qnap/manifest.json @@ -1,12 +1,12 @@ { "domain": "qnap", "name": "QNAP", + "codeowners": ["@disforw"], "config_flow": true, - "version": "10.0", "dependencies": ["persistent_notification"], - "requirements": ["qnapstats==0.4.0"], - "codeowners": ["@disforw"], - "documentation": "https://www.home-assistant.io/integrations/qnap", + "documentation": "https://www.home-assistant.io/integrations/qnap", "iot_class": "local_polling", - "integration_type": "device" + "integration_type": "device", + "requirements": ["qnapstats==0.4.0"], + "version": "10.0" } From 54fc65d70bfdd34d52d329e89e5bfdeb6eea3fff Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 1 Jun 2023 23:22:09 -0400 Subject: [PATCH 021/132] Update sensor.py --- custom_components/qnap/sensor.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index a268e4c..71d110f 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -126,7 +126,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): key="status", name="Health", icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=True, ), QNapSensorEntityDescription( stype="basic", @@ -135,7 +134,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=UnitOfTemperature.CELSIUS, device_class=SensorDeviceClass.TEMPERATURE, icon="mdi:thermometer", - entity_registry_enabled_default=True, state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( @@ -154,7 +152,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): name="CPU Usage", native_unit_of_measurement=PERCENTAGE, icon="mdi:chip", - entity_registry_enabled_default=True, state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( @@ -181,7 +178,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): name="Memory Usage", native_unit_of_measurement=PERCENTAGE, icon="mdi:memory", - entity_registry_enabled_default=True, state_class=SensorStateClass.MEASUREMENT, ), QNapSensorEntityDescription( @@ -189,7 +185,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): key="network_link_status", name="Network Link", icon="mdi:checkbox-marked-circle-outline", - entity_registry_enabled_default=True, ), QNapSensorEntityDescription( stype="network", @@ -267,7 +262,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): name="Volume Used", native_unit_of_measurement=PERCENTAGE, icon="mdi:chart-pie", - entity_registry_enabled_default=True, state_class=SensorStateClass.MEASUREMENT, ), ) From abcd8aa2ed543d30e87c1abaa10c917f7d885f2c Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 1 Jun 2023 23:58:57 -0400 Subject: [PATCH 022/132] Update sensor.py --- custom_components/qnap/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 71d110f..aa3d7b2 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -4,7 +4,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ConfigType from dataclasses import dataclass from homeassistant.components.sensor import ( SensorEntity, From 0a04626277b3232c0cebbe141a39d51f20b1e2f9 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 01:58:09 -0400 Subject: [PATCH 023/132] Update sensor.py --- custom_components/qnap/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index aa3d7b2..218e40f 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -1,6 +1,7 @@ """Support for QNAP NAS Sensors.""" import logging +from homeassistant import config_entries from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -44,7 +45,7 @@ async def async_setup_entry( hass: HomeAssistant, - config_entry: ConfigType, + config_entry: config_entries.ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Set up entry.""" From 8a6110c48466834a822e7284c8ae896ed84f25ad Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 17:33:39 -0400 Subject: [PATCH 024/132] Update sensor.py remove coordinator --- custom_components/qnap/sensor.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 218e40f..1a607df 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -292,10 +292,6 @@ def __init__( def unique_id(self): """Return unique_id.""" return f"{self.uid}_{self.name}" - - @property - def coordinator_context(self): - return None @property def name(self): From f70c1a15c42403cc5c16ec1ad8463103e5d75ec2 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 17:43:59 -0400 Subject: [PATCH 025/132] Update sensor.py readd coordinator_context --- custom_components/qnap/sensor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 1a607df..a766bae 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -293,6 +293,11 @@ def unique_id(self): """Return unique_id.""" return f"{self.uid}_{self.name}" + @property + def coordinator_context(self): + """helpers update_coordinator""" + return None + @property def name(self): """Return the name of the sensor, if any.""" From 52a64dd242e1b7cdf29b3233504ff7d4e17f20d7 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 18:40:05 -0400 Subject: [PATCH 026/132] Update sensor.py add super() --- custom_components/qnap/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index a766bae..327828f 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -5,7 +5,6 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType from dataclasses import dataclass from homeassistant.components.sensor import ( SensorEntity, @@ -281,6 +280,7 @@ def __init__( self, coordinator, description, uid, monitor_device=None, monitor_subdevice=None ) -> None: """Initialize the sensor.""" + super().__init__(self) self.coordinator = coordinator self.entity_description = description self.uid = uid From 544cb5d2bd751d8df3abee1964fd68ee5224e764 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 18:46:24 -0400 Subject: [PATCH 027/132] Update sensor.py remove super --- custom_components/qnap/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 327828f..c4fe2c2 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -280,7 +280,6 @@ def __init__( self, coordinator, description, uid, monitor_device=None, monitor_subdevice=None ) -> None: """Initialize the sensor.""" - super().__init__(self) self.coordinator = coordinator self.entity_description = description self.uid = uid From 6797e592620ee875ae2946edd3d4597638fefc5a Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 18:55:15 -0400 Subject: [PATCH 028/132] Create sensor.bak --- custom_components/qnap/sensor.bak | 519 ++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 custom_components/qnap/sensor.bak diff --git a/custom_components/qnap/sensor.bak b/custom_components/qnap/sensor.bak new file mode 100644 index 0000000..86dfb27 --- /dev/null +++ b/custom_components/qnap/sensor.bak @@ -0,0 +1,519 @@ +"""Support for QNAP NAS Sensors.""" +"""This backup includes the folder sensors""" +import logging + +from homeassistant import config_entries +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from dataclasses import dataclass +from homeassistant.components.sensor import ( + SensorEntity, + SensorDeviceClass, + SensorStateClass, + SensorEntityDescription, +) +from homeassistant.const import ( + ATTR_NAME, + PERCENTAGE, + UnitOfTemperature, + UnitOfInformation, + UnitOfDataRate, +) + +from .const import ( + ATTR_DRIVE, + ATTR_IP, + ATTR_MAC, + ATTR_MASK, + ATTR_MAX_SPEED, + ATTR_MEMORY_SIZE, + ATTR_MODEL, + ATTR_PACKETS_ERR, + ATTR_PACKETS_RX, + ATTR_PACKETS_TX, + ATTR_SERIAL, + ATTR_TYPE, + ATTR_UPTIME, + ATTR_VOLUME_SIZE, + DEFAULT_NAME, + DOMAIN, + VOLUME_NAME, +) + +_LOGGER = logging.getLogger(__name__) + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: config_entries.ConfigEntry, + async_add_entities: AddEntitiesCallback +) -> None: + """Set up entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + uid = config_entry.unique_id + sensors: list[QNAPSensor] = [] + + sensors.extend( + [QNAPSystemSensor(coordinator, description, uid) for description in BAS_SENSOR] + ) + + sensors.extend( + [QNAPCPUSensor(coordinator, description, uid) for description in CPU_SENSOR] + ) + + sensors.extend( + [QNAPMemorySensor(coordinator, description, uid) for description in MEM_SENSOR] + ) + + # Network sensors + sensors.extend( + [ + QNAPNetworkSensor(coordinator, description, uid, nic) + for nic in coordinator.data["system_stats"]["nics"].keys() + for description in NET_SENSOR + ] + ) + + # Drive sensors + sensors.extend( + [ + QNAPDriveSensor(coordinator, description, uid, drive) + for drive in coordinator.data["smart_drive_health"].keys() + for description in DRI_SENSOR + ] + ) + + # Volume sensors + sensors.extend( + [ + QNAPVolumeSensor(coordinator, description, uid, volume) + for volume in coordinator.data["volumes"].keys() + for description in VOL_SENSOR + ] + ) + + # Folders sensors + sensors.extend( + [ + QNAPFolderSensor(coordinator, description, uid, volume, folder["sharename"]) + for volume in coordinator.data["volumes"].keys() + for folder in coordinator.data["volumes"][volume].get("folders", []) + for description in FOL_SENSOR + ] + ) + async_add_entities(sensors) + + +def round_nicely(number): + """Round a number based on its size (so it looks nice).""" + if number < 10: + return round(number, 2) + if number < 100: + return round(number, 1) + + return round(number) + + +@dataclass +class QNapSensorEntityDescription(SensorEntityDescription): + """Represents an Flow Sensor.""" + + stype: str | None = None + + +SENSOR_TYPES: tuple[QNapSensorEntityDescription, ...] = ( + QNapSensorEntityDescription( + stype="basic", + key="status", + name="Health", + icon="mdi:checkbox-marked-circle-outline", + ), + QNapSensorEntityDescription( + stype="basic", + key="system_temp", + name="System Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + icon="mdi:thermometer", + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="cpu", + key="cpu_temp", + name="CPU Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="cpu", + key="cpu_usage", + name="CPU Usage", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chip", + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="memory", + key="memory_free", + name="Memory Available", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:memory", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="memory", + key="memory_used", + name="Memory Used", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:memory", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="memory", + key="memory_percent_used", + name="Memory Usage", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:memory", + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="network", + key="network_link_status", + name="Network Link", + icon="mdi:checkbox-marked-circle-outline", + ), + QNapSensorEntityDescription( + stype="network", + key="network_tx", + name="Network Up", + native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, + icon="mdi:upload", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="network", + key="network_rx", + name="Network Down", + native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, + icon="mdi:download", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="drive", + key="drive_smart_status", + name="SMART Status", + icon="mdi:checkbox-marked-circle-outline", + entity_registry_enabled_default=False, + ), + QNapSensorEntityDescription( + stype="drive", + key="drive_temp", + name="Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + icon="mdi:thermometer", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="folder", + key="folder_size_used", + name="Used Space", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="folder", + key="folder_percentage_used", + name="Folder Used", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="volume", + key="volume_size_used", + name="Used Space", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="volume", + key="volume_size_free", + name="Free Space", + native_unit_of_measurement=UnitOfInformation.GIBIBYTES, + icon="mdi:chart-pie", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + ), + QNapSensorEntityDescription( + stype="volume", + key="volume_percentage_used", + name="Volume Used", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:chart-pie", + state_class=SensorStateClass.MEASUREMENT, + ), +) +BAS_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "basic"] +CPU_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "cpu"] +MEM_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "memory"] +NET_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "network"] +DRI_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "drive"] +FOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "folder"] +VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] + +class QNAPSensor(CoordinatorEntity, SensorEntity): + """Base class for a QNAP sensor.""" + + def __init__( + self, coordinator, description, uid, monitor_device=None, monitor_subdevice=None + ) -> None: + """Initialize the sensor.""" + self.coordinator = coordinator + self.entity_description = description + self.uid = uid + self.device_name = self.coordinator.data["system_stats"]["system"]["name"] + self.monitor_device = monitor_device + self.monitor_subdevice = monitor_subdevice + + @property + def unique_id(self): + """Return unique_id.""" + return f"{self.uid}_{self.name}" + + @property + def coordinator_context(self): + """helpers update_coordinator""" + return None + + @property + def name(self): + """Return the name of the sensor, if any.""" + if self.monitor_device is not None: + return f"{self.device_name} {self.monitor_device} - {self.entity_description.name}" + return f"{self.device_name} {self.entity_description.name}" + + @property + def device_info(self): + """Return device information.""" + return { + "identifiers": {(DOMAIN, self.uid)}, + "name": self.device_name, + "model": self.coordinator.data["system_stats"]["system"]["model"], + "sw_version": self.coordinator.data["system_stats"]["firmware"]["version"], + "manufacturer": DEFAULT_NAME, + } + + +class QNAPCPUSensor(QNAPSensor): + """A QNAP sensor that monitors CPU stats.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + if self.entity_description.key == "cpu_temp": + return self.coordinator.data["system_stats"]["cpu"]["temp_c"] + if self.entity_description.key == "cpu_usage": + return self.coordinator.data["system_stats"]["cpu"]["usage_percent"] + + +class QNAPMemorySensor(QNAPSensor): + """A QNAP sensor that monitors memory stats.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + free = float(self.coordinator.data["system_stats"]["memory"]["free"]) / 1024 + if self.entity_description.key == "memory_free": + return round_nicely(free) + + total = float(self.coordinator.data["system_stats"]["memory"]["total"]) / 1024 + + used = total - free + if self.entity_description.key == "memory_used": + return round_nicely(used) + + if self.entity_description.key == "memory_percent_used": + return round(used / total * 100) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + 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}"} + + +class QNAPSystemSensor(QNAPSensor): + """A QNAP sensor that monitors overall system health.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + if self.entity_description.key == "status": + return self.coordinator.data["system_health"] + + if self.entity_description.key == "system_temp": + return int(self.coordinator.data["system_stats"]["system"]["temp_c"]) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + 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"]) + + return { + ATTR_NAME: data["system"]["name"], + ATTR_MODEL: data["system"]["model"], + ATTR_SERIAL: data["system"]["serial_number"], + ATTR_UPTIME: f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m", + } + + +class QNAPNetworkSensor(QNAPSensor): + """A QNAP sensor that monitors network stats.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + if self.entity_description.key == "network_link_status": + nic = self.coordinator.data["system_stats"]["nics"][self.monitor_device] + return nic["link_status"] + + data = self.coordinator.data["bandwidth"][self.monitor_device] + if self.entity_description.key == "network_tx": + return round_nicely(data["tx"] / 1024 / 1024) + + if self.entity_description.key == "network_rx": + return round_nicely(data["rx"] / 1024 / 1024) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + if self.coordinator.data: + data = self.coordinator.data["system_stats"]["nics"][self.monitor_device] + return { + ATTR_IP: data["ip"], + ATTR_MASK: data["mask"], + ATTR_MAC: data["mac"], + ATTR_MAX_SPEED: data["max_speed"], + ATTR_PACKETS_TX: data["tx_packets"], + ATTR_PACKETS_RX: data["rx_packets"], + ATTR_PACKETS_ERR: data["err_packets"], + } + + +class QNAPDriveSensor(QNAPSensor): + """A QNAP sensor that monitors HDD/SSD drive stats.""" + + @property + def name(self): + """Return the name of the sensor, if any.""" + return f"{self.device_name} Drive {self.monitor_device} - {self.entity_description.name}" + + @property + def native_value(self): + """Return the state of the sensor.""" + data = self.coordinator.data["smart_drive_health"][self.monitor_device] + + if self.entity_description.key == "drive_smart_status": + return data["health"] + + if self.entity_description.key == "drive_temp": + return int(data["temp_c"]) if data["temp_c"] is not None else 0 + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + if self.coordinator.data: + data = self.coordinator.data["smart_drive_health"][self.monitor_device] + return { + ATTR_DRIVE: data["drive_number"], + ATTR_MODEL: data["model"], + ATTR_SERIAL: data["serial"], + ATTR_TYPE: data["type"], + } + + +class QNAPVolumeSensor(QNAPSensor): + """A QNAP sensor that monitors storage volume stats.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + 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": + return round_nicely(free_gb) + + total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 + + used_gb = total_gb - free_gb + if self.entity_description.key == "volume_size_used": + return round_nicely(used_gb) + + if self.entity_description.key == "volume_percentage_used": + return round(used_gb / total_gb * 100) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + if self.coordinator.data: + data = self.coordinator.data["volumes"][self.monitor_device] + total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 + + return {ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"} + + +class QNAPFolderSensor(QNAPSensor): + """A QNAP sensor that monitors storage folder stats.""" + + @property + def name(self): + """Return the name of the sensor, if any.""" + return f"{self.device_name} Folder {self.monitor_subdevice} - {self.entity_description.name}" + + @property + def native_value(self): + """Return the state of the sensor.""" + for folder in self.coordinator.data["volumes"][self.monitor_device]["folders"]: + if folder["sharename"] == self.monitor_subdevice: + vol = self.coordinator.data["volumes"][self.monitor_device] + used_gb = int(folder["used_size"]) / 1024 / 1024 / 1024 + total_gb = int(vol["total_size"]) / 1024 / 1024 / 1024 + + if self.entity_description.key == "folder_size_used": + return round_nicely(used_gb) + + if self.entity_description.key == "folder_percentage_used": + return round(used_gb / total_gb * 100) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + if self.coordinator.data: + data = self.coordinator.data["volumes"][self.monitor_device] + total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 + volume_name = self.monitor_device + + return { + ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}", + VOLUME_NAME: volume_name, + } From 7cd933e37bf101f3a930d01ad44d5f16acb99c49 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 18:57:20 -0400 Subject: [PATCH 029/132] Removed folder sensors --- custom_components/qnap/sensor.py | 68 +------------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index c4fe2c2..9607fde 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -90,16 +90,6 @@ async def async_setup_entry( for description in VOL_SENSOR ] ) - - # Folders sensors - sensors.extend( - [ - QNAPFolderSensor(coordinator, description, uid, volume, folder["sharename"]) - for volume in coordinator.data["volumes"].keys() - for folder in coordinator.data["volumes"][volume].get("folders", []) - for description in FOL_SENSOR - ] - ) async_add_entities(sensors) @@ -124,7 +114,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): QNapSensorEntityDescription( stype="basic", key="status", - name="Health", + name="Status", icon="mdi:checkbox-marked-circle-outline", ), QNapSensorEntityDescription( @@ -220,24 +210,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="folder", - key="folder_size_used", - name="Used Space", - native_unit_of_measurement=UnitOfInformation.GIBIBYTES, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), - QNapSensorEntityDescription( - stype="folder", - key="folder_percentage_used", - name="Folder Used", - native_unit_of_measurement=PERCENTAGE, - icon="mdi:chart-pie", - entity_registry_enabled_default=False, - state_class=SensorStateClass.MEASUREMENT, - ), QNapSensorEntityDescription( stype="volume", key="volume_size_used", @@ -270,7 +242,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): MEM_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "memory"] NET_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "network"] DRI_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "drive"] -FOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "folder"] VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] class QNAPSensor(CoordinatorEntity, SensorEntity): @@ -479,40 +450,3 @@ def extra_state_attributes(self): total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 return {ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"} - - -class QNAPFolderSensor(QNAPSensor): - """A QNAP sensor that monitors storage folder stats.""" - - @property - def name(self): - """Return the name of the sensor, if any.""" - return f"{self.device_name} Folder {self.monitor_subdevice} - {self.entity_description.name}" - - @property - def native_value(self): - """Return the state of the sensor.""" - for folder in self.coordinator.data["volumes"][self.monitor_device]["folders"]: - if folder["sharename"] == self.monitor_subdevice: - vol = self.coordinator.data["volumes"][self.monitor_device] - used_gb = int(folder["used_size"]) / 1024 / 1024 / 1024 - total_gb = int(vol["total_size"]) / 1024 / 1024 / 1024 - - if self.entity_description.key == "folder_size_used": - return round_nicely(used_gb) - - if self.entity_description.key == "folder_percentage_used": - return round(used_gb / total_gb * 100) - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - if self.coordinator.data: - data = self.coordinator.data["volumes"][self.monitor_device] - total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 - volume_name = self.monitor_device - - return { - ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}", - VOLUME_NAME: volume_name, - } From bab321872894504daf01697d09d42cfe1e048a7d Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 20:18:28 -0400 Subject: [PATCH 030/132] Create hassfest.yaml --- .github/workflows/hassfest.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/hassfest.yaml diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml new file mode 100644 index 0000000..07d1dda --- /dev/null +++ b/.github/workflows/hassfest.yaml @@ -0,0 +1,14 @@ +name: Validate with hassfest + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + validate: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v3" + - uses: home-assistant/actions/hassfest@master From 938b739ddc62b44fabbe990485721600665a4774 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 20:22:22 -0400 Subject: [PATCH 031/132] Update manifest.json sorting --- custom_components/qnap/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/manifest.json b/custom_components/qnap/manifest.json index fa83b94..298facd 100644 --- a/custom_components/qnap/manifest.json +++ b/custom_components/qnap/manifest.json @@ -5,8 +5,8 @@ "config_flow": true, "dependencies": ["persistent_notification"], "documentation": "https://www.home-assistant.io/integrations/qnap", - "iot_class": "local_polling", "integration_type": "device", + "iot_class": "local_polling", "requirements": ["qnapstats==0.4.0"], "version": "10.0" } From 00e12ae14de9084ee7287c7d0d47e706e9790c1c Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 20:24:47 -0400 Subject: [PATCH 032/132] Create code-quality.yml --- .github/workflows/code-quality.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/code-quality.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..000e0df --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,17 @@ +name: Checks +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + name: Checks + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install --upgrade pip + - run: pip install "black<23" pylint==v3.0.0a3 mypy==v0.902 + - run: black --diff --check $(git ls-files '*.py') + - run: pylint --disable=all --enable=unused-import $(git ls-files '*.py') + - run: mypy --strict $(git ls-files '*.py') From 2ca759ca221c0157ec3e3913e70a7c4e4c9ea8c7 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 21:17:04 -0400 Subject: [PATCH 033/132] Update sensor.py --- custom_components/qnap/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 9607fde..05b23ef 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -37,7 +37,7 @@ ATTR_VOLUME_SIZE, DEFAULT_NAME, DOMAIN, - VOLUME_NAME, + ) _LOGGER = logging.getLogger(__name__) @@ -76,7 +76,7 @@ async def async_setup_entry( # Drive sensors sensors.extend( [ - QNAPDriveSensor(coordinator, description, uid, drive) + QNAPDriveSensor(coordinator, description, uid, drive for drive in coordinator.data["smart_drive_health"].keys() for description in DRI_SENSOR ] From ab7b89f0467b641cfa48406143494be8c8bfc191 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 21:24:11 -0400 Subject: [PATCH 034/132] Update and rename code-quality.yml to code-quality.yxx --- .github/workflows/{code-quality.yml => code-quality.yxx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{code-quality.yml => code-quality.yxx} (90%) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yxx similarity index 90% rename from .github/workflows/code-quality.yml rename to .github/workflows/code-quality.yxx index 000e0df..c009fbf 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yxx @@ -14,4 +14,4 @@ jobs: - run: pip install "black<23" pylint==v3.0.0a3 mypy==v0.902 - run: black --diff --check $(git ls-files '*.py') - run: pylint --disable=all --enable=unused-import $(git ls-files '*.py') - - run: mypy --strict $(git ls-files '*.py') + From 05004ead3ae403f91175393e680bc93340f431fe Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 21:24:38 -0400 Subject: [PATCH 035/132] Delete code-quality.yxx --- .github/workflows/code-quality.yxx | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/workflows/code-quality.yxx diff --git a/.github/workflows/code-quality.yxx b/.github/workflows/code-quality.yxx deleted file mode 100644 index c009fbf..0000000 --- a/.github/workflows/code-quality.yxx +++ /dev/null @@ -1,17 +0,0 @@ -name: Checks -on: [pull_request] - -jobs: - build: - runs-on: ubuntu-latest - name: Checks - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install --upgrade pip - - run: pip install "black<23" pylint==v3.0.0a3 mypy==v0.902 - - run: black --diff --check $(git ls-files '*.py') - - run: pylint --disable=all --enable=unused-import $(git ls-files '*.py') - From 2cd2b4a4f02b9ade06f63dd8e855977d5dca2624 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 2 Jun 2023 22:17:32 -0400 Subject: [PATCH 036/132] Update manifest.json --- custom_components/qnap/manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/qnap/manifest.json b/custom_components/qnap/manifest.json index 298facd..25be849 100644 --- a/custom_components/qnap/manifest.json +++ b/custom_components/qnap/manifest.json @@ -3,7 +3,6 @@ "name": "QNAP", "codeowners": ["@disforw"], "config_flow": true, - "dependencies": ["persistent_notification"], "documentation": "https://www.home-assistant.io/integrations/qnap", "integration_type": "device", "iot_class": "local_polling", From 32178521dace6888648c9997abd13ae287331aa1 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 07:50:38 -0400 Subject: [PATCH 037/132] Update __init__.py --- custom_components/qnap/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index f2305cb..8e478b4 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -4,7 +4,6 @@ from qnapstats import QNAPStats -from homeassistant import config_entries from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -13,7 +12,6 @@ CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.helpers import config_per_platform from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN, PLATFORMS @@ -22,12 +20,14 @@ _LOGGER = logging.getLogger(__name__) + async def async_setup(hass, config): """Set up the qnap environment.""" hass.data.setdefault(DOMAIN, {}) return True + async def async_setup_entry(hass, config_entry): """Set the config entry up.""" host = config_entry.data[CONF_HOST] @@ -44,8 +44,12 @@ async def async_setup_entry(hass, config_entry): async def async_update_data(): datas = {} datas["system_stats"] = await hass.async_add_executor_job(api.get_system_stats) - datas["system_health"] = await hass.async_add_executor_job(api.get_system_health) - datas["smart_drive_health"] = await hass.async_add_executor_job(api.get_smart_disk_health) + datas["system_health"] = await hass.async_add_executor_job( + api.get_system_health + ) + datas["smart_drive_health"] = await hass.async_add_executor_job( + api.get_smart_disk_health + ) datas["volumes"] = await hass.async_add_executor_job(api.get_volumes) datas["bandwidth"] = await hass.async_add_executor_job(api.get_bandwidth) return datas From 1cf119c4b407cde9bf317d7492d6c5d242398b00 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 07:59:03 -0400 Subject: [PATCH 038/132] Update sensor.py --- custom_components/qnap/sensor.py | 36 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 05b23ef..9df6d72 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -1,24 +1,24 @@ """Support for QNAP NAS Sensors.""" +from dataclasses import dataclass import logging from homeassistant import config_entries -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from dataclasses import dataclass from homeassistant.components.sensor import ( - SensorEntity, SensorDeviceClass, - SensorStateClass, + SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.const import ( ATTR_NAME, PERCENTAGE, - UnitOfTemperature, - UnitOfInformation, UnitOfDataRate, + UnitOfInformation, + UnitOfTemperature, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import ( ATTR_DRIVE, @@ -42,10 +42,11 @@ _LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, config_entry: config_entries.ConfigEntry, - async_add_entities: AddEntitiesCallback + async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] @@ -68,7 +69,7 @@ async def async_setup_entry( sensors.extend( [ QNAPNetworkSensor(coordinator, description, uid, nic) - for nic in coordinator.data["system_stats"]["nics"].keys() + for nic in coordinator.data["system_stats"]["nics"] for description in NET_SENSOR ] ) @@ -77,7 +78,7 @@ async def async_setup_entry( sensors.extend( [ QNAPDriveSensor(coordinator, description, uid, drive - for drive in coordinator.data["smart_drive_health"].keys() + for drive in coordinator.data["smart_drive_health"] for description in DRI_SENSOR ] ) @@ -86,7 +87,7 @@ async def async_setup_entry( sensors.extend( [ QNAPVolumeSensor(coordinator, description, uid, volume) - for volume in coordinator.data["volumes"].keys() + for volume in coordinator.data["volumes"] for description in VOL_SENSOR ] ) @@ -243,7 +244,8 @@ class QNapSensorEntityDescription(SensorEntityDescription): NET_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "network"] DRI_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "drive"] VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] - + + class QNAPSensor(CoordinatorEntity, SensorEntity): """Base class for a QNAP sensor.""" @@ -265,9 +267,9 @@ def unique_id(self): @property def coordinator_context(self): - """helpers update_coordinator""" + """helpers update_coordinator.""" return None - + @property def name(self): """Return the name of the sensor, if any.""" @@ -449,4 +451,6 @@ def extra_state_attributes(self): data = self.coordinator.data["volumes"][self.monitor_device] total_gb = int(data["total_size"]) / 1024 / 1024 / 1024 - return {ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}"} + return { + ATTR_VOLUME_SIZE: f"{round_nicely(total_gb)} {UnitOfInformation.GIBIBYTES}" + } From c11f4b36391e61b94beb09cb7865238b1c0419bd Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 08:06:01 -0400 Subject: [PATCH 039/132] Update config_flow.py --- custom_components/qnap/config_flow.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/custom_components/qnap/config_flow.py b/custom_components/qnap/config_flow.py index c8d5c29..c59a14b 100644 --- a/custom_components/qnap/config_flow.py +++ b/custom_components/qnap/config_flow.py @@ -16,15 +16,21 @@ ) from homeassistant.helpers import config_validation as cv -from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN +from .const import ( + 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=False): cv.boolean, - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + 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, } ) @@ -50,14 +56,17 @@ async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} if user_input is not None: + user_input.setdefault(CONF_SSL, DEFAULT_SSL) + user_input.setdefault(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL) + user_input.setdefault(CONF_PORT, DEFAULT_PORT) host = user_input[CONF_HOST] - protocol = "https" if user_input.get(CONF_SSL, False) else "http" + protocol = "https" if user_input[CONF_SSL] else "http" api = QNAPStats( host=f"{protocol}://{host}", - port=user_input.get(CONF_PORT, DEFAULT_PORT), + port=user_input[CONF_PORT], username=user_input[CONF_USERNAME], password=user_input[CONF_PASSWORD], - verify_ssl=user_input.get(CONF_VERIFY_SSL, True), + verify_ssl=user_input[CONF_VERIFY_SSL], timeout=DEFAULT_TIMEOUT, ) try: From e4c7ec8e26741dab7b51fd83f7e0e5a4cc67d2c4 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 08:06:54 -0400 Subject: [PATCH 040/132] Update const.py --- custom_components/qnap/const.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 6666e75..dbe5ce6 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -21,8 +21,10 @@ DEFAULT_NAME = "QNAP" DEFAULT_PORT = 8080 DEFAULT_TIMEOUT = 5 -DOMAIN = "qnap" +DEFAULT_SSL = False +DEFAULT_VERIFY_SSL = True +DOMAIN = "qnap" NOTIFICATION_ID = "qnap_notification" NOTIFICATION_TITLE = "QNAP Sensor Setup" VOLUME_NAME = "volume" From c7858a7b3006d5ebee513e57a68d56302e116f48 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 08:16:45 -0400 Subject: [PATCH 041/132] Delete hassfest.yaml --- .github/workflows/hassfest.yaml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/workflows/hassfest.yaml diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml deleted file mode 100644 index 07d1dda..0000000 --- a/.github/workflows/hassfest.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: Validate with hassfest - -on: - push: - pull_request: - schedule: - - cron: "0 0 * * *" - -jobs: - validate: - runs-on: "ubuntu-latest" - steps: - - uses: "actions/checkout@v3" - - uses: home-assistant/actions/hassfest@master From f57987ddf13a34a082f4fa2be4a2176f9edc514e Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 09:02:29 -0400 Subject: [PATCH 042/132] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e8d8627..eb9b80c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # QNAP Integration Re-write with Config-Flow This is a complete re-write of the Core QNAP integration, adding config_flow and unique_ID. Using this custom componant will override the built-in QNAP integration. +This integration has been submitted and accepted as a PR to Home Assistant Core. I will leave this custom_integration open, active, and in sync with the core version so I can continue to develop on it. Here are some upcomming features (that are not submitted to core). +* Folder entites +* Firmware update entity +* auto-discovery of device ## Installation From b46a25aaf3a5eb1489bdee1bd46f9e9cdff8e43a Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 09:03:10 -0400 Subject: [PATCH 043/132] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb9b80c..045eb7b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # QNAP Integration Re-write with Config-Flow This is a complete re-write of the Core QNAP integration, adding config_flow and unique_ID. Using this custom componant will override the built-in QNAP integration. -This integration has been submitted and accepted as a PR to Home Assistant Core. I will leave this custom_integration open, active, and in sync with the core version so I can continue to develop on it. Here are some upcomming features (that are not submitted to core). +This integration has been submitted and accepted as a PR to Home Assistant Core. I will leave this custom_integration open, active, and in sync with the core version so I can continue to develop on it. Here are some upcoming features (that are not submitted to core). * Folder entites * Firmware update entity * auto-discovery of device From 141829e674b9cc903c98c827d4d21d4d189bee50 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 3 Jun 2023 15:24:58 -0400 Subject: [PATCH 044/132] Update sensor.py --- custom_components/qnap/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 9df6d72..334166b 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -77,7 +77,7 @@ async def async_setup_entry( # Drive sensors sensors.extend( [ - QNAPDriveSensor(coordinator, description, uid, drive + QNAPDriveSensor(coordinator, description, uid, drive) for drive in coordinator.data["smart_drive_health"] for description in DRI_SENSOR ] From 55ed34586cf77bf2a43472621b7846aa92dc410f Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 5 Jun 2023 21:17:32 -0400 Subject: [PATCH 045/132] Remove import As per epenet --- custom_components/qnap/config_flow.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/custom_components/qnap/config_flow.py b/custom_components/qnap/config_flow.py index c59a14b..ae11e09 100644 --- a/custom_components/qnap/config_flow.py +++ b/custom_components/qnap/config_flow.py @@ -43,13 +43,8 @@ class QnapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 - def __init__(self): - """Initialize.""" - self.is_imported = False - async def async_step_import(self, import_info): """Set the config entry up from yaml.""" - self.is_imported = True return await self.async_step_user(import_info) async def async_step_user(self, user_input=None): @@ -83,11 +78,6 @@ async def async_step_user(self, user_input=None): await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured() title = stats["system"]["name"].capitalize() - if self.is_imported: - _LOGGER.warning( - "The import of the QNAP configuration was successful. \ - Please remove the platform from the YAML configuration file" - ) return self.async_create_entry(title=title, data=user_input) return self.async_show_form( From d08cf607f69bd237e8e34584b9be7c0c02c32268 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 5 Jun 2023 21:57:22 -0400 Subject: [PATCH 046/132] Change unload As per epenet --- custom_components/qnap/__init__.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 8e478b4..24b59e7 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -21,15 +21,9 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass, config): - """Set up the qnap environment.""" - hass.data.setdefault(DOMAIN, {}) - - return True - - async def async_setup_entry(hass, config_entry): """Set the config entry up.""" + hass.data.setdefault(DOMAIN, {}) host = config_entry.data[CONF_HOST] protocol = "https" if config_entry.data.get(CONF_SSL) else "http" api = QNAPStats( @@ -77,9 +71,8 @@ async def async_update_data(): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms( + if unload_ok := await hass.config_entries.async_unload_platforms( config_entry, PLATFORMS - ) - if unload_ok: + ): hass.data[DOMAIN].pop(config_entry.entry_id) return unload_ok From a531d29d08293fb6cfe351fd9ba0cdebcbb75781 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 5 Jun 2023 22:15:19 -0400 Subject: [PATCH 047/132] Add Platform Enum --- custom_components/qnap/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 24b59e7..dcac1a0 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -11,15 +11,20 @@ CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, + Platform, ) from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN, PLATFORMS +from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN UPDATE_INTERVAL = timedelta(minutes=1) _LOGGER = logging.getLogger(__name__) +PLATFORMS: list[Platform] = [ + Platform.SENSOR, +] + async def async_setup_entry(hass, config_entry): """Set the config entry up.""" From 8a4eb8577d595b386500e6771141d6be68a55aaa Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 5 Jun 2023 22:15:41 -0400 Subject: [PATCH 048/132] Remove Platforms --- custom_components/qnap/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index dbe5ce6..6cbd62e 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -17,7 +17,6 @@ ATTR_UPTIME = "Uptime" ATTR_VOLUME_SIZE = "Volume Size" -PLATFORMS = ["sensor"] DEFAULT_NAME = "QNAP" DEFAULT_PORT = 8080 DEFAULT_TIMEOUT = 5 From 39866b3a6595b04822cfd8358c02bfe70a6bf9b6 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 07:58:59 -0400 Subject: [PATCH 049/132] Update __init__.py remove coordinator --- custom_components/qnap/__init__.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index dcac1a0..f1aa6dc 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -16,6 +16,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN +from .coordinator import QnapCoordinator UPDATE_INTERVAL = timedelta(minutes=1) @@ -39,27 +40,7 @@ async def async_setup_entry(hass, config_entry): verify_ssl=config_entry.data.get(CONF_VERIFY_SSL), timeout=DEFAULT_TIMEOUT, ) - - async def async_update_data(): - datas = {} - datas["system_stats"] = await hass.async_add_executor_job(api.get_system_stats) - datas["system_health"] = await hass.async_add_executor_job( - api.get_system_health - ) - datas["smart_drive_health"] = await hass.async_add_executor_job( - api.get_smart_disk_health - ) - datas["volumes"] = await hass.async_add_executor_job(api.get_volumes) - datas["bandwidth"] = await hass.async_add_executor_job(api.get_bandwidth) - return datas - - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name="sensor", - update_method=async_update_data, - update_interval=UPDATE_INTERVAL, - ) + coordinator = QnapCoordinator(hass, api) # Fetch initial data so we have data when entities subscribe await coordinator.async_config_entry_first_refresh() From 1a5e8124b9f40373a37109c7166024b3ddee9c2a Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 08:00:25 -0400 Subject: [PATCH 050/132] Create coordinator.py --- custom_components/qnap/coordinator.py | 49 +++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 custom_components/qnap/coordinator.py diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py new file mode 100644 index 0000000..fa81d4f --- /dev/null +++ b/custom_components/qnap/coordinator.py @@ -0,0 +1,49 @@ +"""Data coordinator for the dwd_weather_warnings integration.""" + +from __future__ import annotations + +from dwdwfsapi import DwdWeatherWarningsAPI + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER + + +class DwdWeatherWarningsCoordinator(DataUpdateCoordinator[None]): + """Custom coordinator for the dwd_weather_warnings integration.""" + + def __init__(self, hass: HomeAssistant, api: DwdWeatherWarningsAPI) -> None: + """Initialize the dwd_weather_warnings coordinator.""" + super().__init__( + hass, LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL + ) + + self.api = api + + async def _async_update_data(self) -> None: + """Get the latest data from the DWD Weather Warnings API.""" + await self.hass.async_add_executor_job(self.api.update) +----- + +async def async_update_data(): + datas = {} + datas["system_stats"] = await hass.async_add_executor_job(api.get_system_stats) + datas["system_health"] = await hass.async_add_executor_job( + api.get_system_health + ) + datas["smart_drive_health"] = await hass.async_add_executor_job( + api.get_smart_disk_health + ) + datas["volumes"] = await hass.async_add_executor_job(api.get_volumes) + datas["bandwidth"] = await hass.async_add_executor_job(api.get_bandwidth) + return datas + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name="sensor", + update_method=async_update_data, + update_interval=UPDATE_INTERVAL, + ) + From 02d2a3a6a5a5a7846494823ba3cabefb8803617f Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 08:10:43 -0400 Subject: [PATCH 051/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 45 ++++++++++----------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index fa81d4f..b509018 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -1,49 +1,38 @@ -"""Data coordinator for the dwd_weather_warnings integration.""" +"""Data coordinator for the qnap integration.""" from __future__ import annotations - -from dwdwfsapi import DwdWeatherWarningsAPI +from qnapstats import QNAPStats from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER +from .const import DOMAIN +UPDATE_INTERVAL = timedelta(minutes=1) + +_LOGGER = logging.getLogger(__name__) -class DwdWeatherWarningsCoordinator(DataUpdateCoordinator[None]): - """Custom coordinator for the dwd_weather_warnings integration.""" +class QnapCoordinator(DataUpdateCoordinator[None]): + """Custom coordinator for the qnap integration.""" - def __init__(self, hass: HomeAssistant, api: DwdWeatherWarningsAPI) -> None: - """Initialize the dwd_weather_warnings coordinator.""" + def __init__(self, hass: HomeAssistant, api: QNAPStats) -> None: + """Initialize the qnap coordinator.""" super().__init__( - hass, LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL + hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL ) self.api = api async def _async_update_data(self) -> None: - """Get the latest data from the DWD Weather Warnings API.""" - await self.hass.async_add_executor_job(self.api.update) ------ - -async def async_update_data(): + """Get the latest data from the Qnap API.""" datas = {} - datas["system_stats"] = await hass.async_add_executor_job(api.get_system_stats) + datas["system_stats"] = await hass.async_add_executor_job(self.api.get_system_stats) datas["system_health"] = await hass.async_add_executor_job( - api.get_system_health + self.api.get_system_health ) datas["smart_drive_health"] = await hass.async_add_executor_job( - api.get_smart_disk_health + self.api.get_smart_disk_health ) - datas["volumes"] = await hass.async_add_executor_job(api.get_volumes) - datas["bandwidth"] = await hass.async_add_executor_job(api.get_bandwidth) + datas["volumes"] = await hass.async_add_executor_job(self.api.get_volumes) + datas["bandwidth"] = await hass.async_add_executor_job(self.api.get_bandwidth) return datas - - coordinator = DataUpdateCoordinator( - hass, - _LOGGER, - name="sensor", - update_method=async_update_data, - update_interval=UPDATE_INTERVAL, - ) - From 808ac326bf567f6f1682d05d3bb516e63cf1c791 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 08:14:39 -0400 Subject: [PATCH 052/132] Update __init__.py --- custom_components/qnap/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index f1aa6dc..0c253b7 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -13,13 +13,10 @@ CONF_VERIFY_SSL, Platform, ) -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN from .coordinator import QnapCoordinator -UPDATE_INTERVAL = timedelta(minutes=1) - _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [ From b040046bd59148d40d98274e97e1efb2e5d3f812 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 08:17:50 -0400 Subject: [PATCH 053/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index b509018..cc2982e 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -18,7 +18,7 @@ class QnapCoordinator(DataUpdateCoordinator[None]): def __init__(self, hass: HomeAssistant, api: QNAPStats) -> None: """Initialize the qnap coordinator.""" super().__init__( - hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL + hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL ) self.api = api From ce705560fa489dbde77f63a0071ce16712c8deaf Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 08:51:19 -0400 Subject: [PATCH 054/132] Remove unused imports --- custom_components/qnap/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 0c253b7..b9a1f71 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -1,6 +1,4 @@ """The qnap component.""" -from datetime import timedelta -import logging from qnapstats import QNAPStats @@ -17,8 +15,6 @@ from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN from .coordinator import QnapCoordinator -_LOGGER = logging.getLogger(__name__) - PLATFORMS: list[Platform] = [ Platform.SENSOR, ] From 58ff533e2950eff81139390b26c02ead8e50b0fa Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 10 Jun 2023 08:53:24 -0400 Subject: [PATCH 055/132] Fix hass --- custom_components/qnap/coordinator.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index cc2982e..b30a376 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -1,6 +1,8 @@ """Data coordinator for the qnap integration.""" - from __future__ import annotations + +import logging +from datetime import timedelta from qnapstats import QNAPStats from homeassistant.core import HomeAssistant @@ -26,13 +28,13 @@ def __init__(self, hass: HomeAssistant, api: QNAPStats) -> None: async def _async_update_data(self) -> None: """Get the latest data from the Qnap API.""" datas = {} - datas["system_stats"] = await hass.async_add_executor_job(self.api.get_system_stats) - datas["system_health"] = await hass.async_add_executor_job( + datas["system_stats"] = await self.hass.async_add_executor_job(self.api.get_system_stats) + datas["system_health"] = await self.hass.async_add_executor_job( self.api.get_system_health ) - datas["smart_drive_health"] = await hass.async_add_executor_job( + datas["smart_drive_health"] = await self.hass.async_add_executor_job( self.api.get_smart_disk_health ) - datas["volumes"] = await hass.async_add_executor_job(self.api.get_volumes) - datas["bandwidth"] = await hass.async_add_executor_job(self.api.get_bandwidth) + datas["volumes"] = await self.hass.async_add_executor_job(self.api.get_volumes) + datas["bandwidth"] = await self.hass.async_add_executor_job(self.api.get_bandwidth) return datas From 4c41aa9e3ba6b2fbd6c5b4bbc894e82e156ccb5b Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 07:51:15 -0400 Subject: [PATCH 056/132] Fix coord as per review All changes reflect review by epenet --- custom_components/qnap/sensor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 334166b..356b7fa 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -39,6 +39,7 @@ DOMAIN, ) +from .coordinator import QnapCoordinator _LOGGER = logging.getLogger(__name__) @@ -246,14 +247,14 @@ class QNapSensorEntityDescription(SensorEntityDescription): VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] -class QNAPSensor(CoordinatorEntity, SensorEntity): +class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): """Base class for a QNAP sensor.""" def __init__( - self, coordinator, description, uid, monitor_device=None, monitor_subdevice=None + self, coordinator: QnapCoordinator, description, uid, monitor_device=None, monitor_subdevice=None ) -> None: """Initialize the sensor.""" - self.coordinator = coordinator + super().__init__(coordinator) self.entity_description = description self.uid = uid self.device_name = self.coordinator.data["system_stats"]["system"]["name"] @@ -274,7 +275,7 @@ def coordinator_context(self): def name(self): """Return the name of the sensor, if any.""" if self.monitor_device is not None: - return f"{self.device_name} {self.monitor_device} - {self.entity_description.name}" + return f"{self.device_name} {self.entity_description.name} ({self.monitor_device})" return f"{self.device_name} {self.entity_description.name}" @property From 9e55061b298b36e47440c9963513352ce05546d7 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 07:52:00 -0400 Subject: [PATCH 057/132] Fix coordinator.py as per review All coord changes requested by epenet --- custom_components/qnap/coordinator.py | 28 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index b30a376..4adc9ed 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -14,27 +14,35 @@ _LOGGER = logging.getLogger(__name__) -class QnapCoordinator(DataUpdateCoordinator[None]): +class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]): """Custom coordinator for the qnap integration.""" - def __init__(self, hass: HomeAssistant, api: QNAPStats) -> None: + def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: """Initialize the qnap coordinator.""" super().__init__( hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL ) - self.api = api + 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), + ) - async def _async_update_data(self) -> None: + async def _async_update_data(self) -> dict[str, dict[str, Any]]: """Get the latest data from the Qnap API.""" - datas = {} - datas["system_stats"] = await self.hass.async_add_executor_job(self.api.get_system_stats) + datas: dict[str, dict[str, Any]] = {} + datas["system_stats"] = await self.hass.async_add_executor_job(self._api.get_system_stats) datas["system_health"] = await self.hass.async_add_executor_job( - self.api.get_system_health + self._api.get_system_health ) datas["smart_drive_health"] = await self.hass.async_add_executor_job( - self.api.get_smart_disk_health + self._api.get_smart_disk_health ) - datas["volumes"] = await self.hass.async_add_executor_job(self.api.get_volumes) - datas["bandwidth"] = await self.hass.async_add_executor_job(self.api.get_bandwidth) + datas["volumes"] = await self.hass.async_add_executor_job(self._api.get_volumes) + datas["bandwidth"] = await self.hass.async_add_executor_job(self._api.get_bandwidth) return datas From c833bd2fbae8154751581e8576c004926b9b44d0 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 08:15:23 -0400 Subject: [PATCH 058/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index 4adc9ed..31ea0b5 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -7,6 +7,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from typing import Any, ConfigType from .const import DOMAIN UPDATE_INTERVAL = timedelta(minutes=1) From a80bf086a71c2ce33cf2938e8bc167d5369006cf Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 08:17:56 -0400 Subject: [PATCH 059/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index 31ea0b5..0162690 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from typing import Any, ConfigType +from typing import Any from .const import DOMAIN UPDATE_INTERVAL = timedelta(minutes=1) From 29f1fb18d05abbcdaadcaa691857eee9f7628c07 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 08:21:27 -0400 Subject: [PATCH 060/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index 0162690..e277a46 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -4,10 +4,12 @@ import logging from datetime import timedelta from qnapstats import QNAPStats +from typing import Any from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from typing import Any +from homeassistant.helpers.typing import ConfigType + from .const import DOMAIN UPDATE_INTERVAL = timedelta(minutes=1) From 818f7c47299419367929ea218dffe3a2b41bb73d Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 22:06:05 -0400 Subject: [PATCH 061/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index e277a46..c14bca1 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -1,17 +1,27 @@ """Data coordinator for the qnap integration.""" from __future__ import annotations -import logging from datetime import timedelta -from qnapstats import QNAPStats +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.update_coordinator import DataUpdateCoordinator 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__) @@ -22,9 +32,7 @@ class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]): def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: """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" self._api = QNAPStats( @@ -39,7 +47,9 @@ def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: async def _async_update_data(self) -> dict[str, dict[str, Any]]: """Get the latest data from the Qnap API.""" datas: dict[str, dict[str, Any]] = {} - datas["system_stats"] = await self.hass.async_add_executor_job(self._api.get_system_stats) + datas["system_stats"] = await self.hass.async_add_executor_job( + self._api.get_system_stats + ) datas["system_health"] = await self.hass.async_add_executor_job( self._api.get_system_health ) @@ -47,5 +57,7 @@ async def _async_update_data(self) -> dict[str, dict[str, Any]]: self._api.get_smart_disk_health ) datas["volumes"] = await self.hass.async_add_executor_job(self._api.get_volumes) - datas["bandwidth"] = await self.hass.async_add_executor_job(self._api.get_bandwidth) + datas["bandwidth"] = await self.hass.async_add_executor_job( + self._api.get_bandwidth + ) return datas From bc4b6a8fbfa7132f1f80511e27587c25fdc0eaa0 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 22:13:06 -0400 Subject: [PATCH 062/132] Update sensor.py --- custom_components/qnap/sensor.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 356b7fa..0ade731 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -19,6 +19,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.exceptions import PlatformNotReady from .const import ( ATTR_DRIVE, @@ -50,7 +51,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" - coordinator = hass.data[DOMAIN][config_entry.entry_id] + coordinator = QnapCoordinator(hass, config_entry) + if not coordinator: + raise PlatformNotReady uid = config_entry.unique_id sensors: list[QNAPSensor] = [] @@ -251,7 +254,12 @@ class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): """Base class for a QNAP sensor.""" def __init__( - self, coordinator: QnapCoordinator, description, uid, monitor_device=None, monitor_subdevice=None + self, + coordinator: QnapCoordinator, + description, + uid, + monitor_device=None, + monitor_subdevice=None, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) From 07b2f9d0ec927f026d1bccdb03012dfe1948ee1d Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 23:08:14 -0400 Subject: [PATCH 063/132] Update __init__.py --- custom_components/qnap/__init__.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index b9a1f71..cc7d5b2 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -23,18 +23,9 @@ async def async_setup_entry(hass, config_entry): """Set the config entry up.""" hass.data.setdefault(DOMAIN, {}) - host = config_entry.data[CONF_HOST] - protocol = "https" if config_entry.data.get(CONF_SSL) else "http" - api = QNAPStats( - host=f"{protocol}://{host}", - port=config_entry.data.get(CONF_PORT, DEFAULT_PORT), - username=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - verify_ssl=config_entry.data.get(CONF_VERIFY_SSL), - timeout=DEFAULT_TIMEOUT, - ) - coordinator = QnapCoordinator(hass, api) - + if not (coordinator := QnapCoordinator(hass, config_entry): + raise PlatformNotReady + # Fetch initial data so we have data when entities subscribe await coordinator.async_config_entry_first_refresh() From 6fd8ec0bbaeb3cc6012ae78d351bffad8947e145 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 23:09:11 -0400 Subject: [PATCH 064/132] Update __init__.py --- custom_components/qnap/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index cc7d5b2..496c15e 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -14,6 +14,7 @@ from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN from .coordinator import QnapCoordinator +from homeassistant.exceptions import PlatformNotReady PLATFORMS: list[Platform] = [ Platform.SENSOR, From 04cacaf7db1a9754b26b84a071fed3f6d5d1786a Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 23:24:03 -0400 Subject: [PATCH 065/132] Update sensor.py --- custom_components/qnap/sensor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 0ade731..9a6c0cf 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -51,9 +51,8 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" - coordinator = QnapCoordinator(hass, config_entry) - if not coordinator: - raise PlatformNotReady + if not (coordinator := QnapCoordinator(hass, config_entry)): + raise PlatformNotReady uid = config_entry.unique_id sensors: list[QNAPSensor] = [] From 411df6bd18b3d87c918561d95ea4923a6d1a49c7 Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 23:24:18 -0400 Subject: [PATCH 066/132] Update __init__.py --- custom_components/qnap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 496c15e..23b1ca0 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -24,7 +24,7 @@ async def async_setup_entry(hass, config_entry): """Set the config entry up.""" hass.data.setdefault(DOMAIN, {}) - if not (coordinator := QnapCoordinator(hass, config_entry): + if not (coordinator := QnapCoordinator(hass, config_entry)): raise PlatformNotReady # Fetch initial data so we have data when entities subscribe From 4b2a5ca73b60b772cf7efd5bf949234b96b9cf9d Mon Sep 17 00:00:00 2001 From: disforw Date: Mon, 12 Jun 2023 23:39:25 -0400 Subject: [PATCH 067/132] Update __init__.py --- custom_components/qnap/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 23b1ca0..f5bbd11 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -1,4 +1,7 @@ """The qnap component.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry from qnapstats import QNAPStats @@ -32,10 +35,7 @@ async def async_setup_entry(hass, config_entry): hass.data[DOMAIN][config_entry.entry_id] = coordinator - for component in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, component) - ) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True From 6ef34009ae18aba567c21104bcf8c2bb48d24a66 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 08:46:47 -0400 Subject: [PATCH 068/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index c14bca1..bcf0820 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -44,20 +44,16 @@ def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: 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.""" - datas: dict[str, dict[str, Any]] = {} - datas["system_stats"] = await self.hass.async_add_executor_job( - self._api.get_system_stats - ) - datas["system_health"] = await self.hass.async_add_executor_job( - self._api.get_system_health - ) - datas["smart_drive_health"] = await self.hass.async_add_executor_job( - self._api.get_smart_disk_health - ) - datas["volumes"] = await self.hass.async_add_executor_job(self._api.get_volumes) - datas["bandwidth"] = await self.hass.async_add_executor_job( - self._api.get_bandwidth - ) - return datas + return await self.hass.async_add_executor_job(self._sync_update) From ac1982c19f449f92a501f8a39032178a943839c2 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 11:13:34 -0400 Subject: [PATCH 069/132] Update __init__.py --- custom_components/qnap/__init__.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index f5bbd11..35c4bdb 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -2,9 +2,6 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry - -from qnapstats import QNAPStats - from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -17,26 +14,22 @@ from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN from .coordinator import QnapCoordinator -from homeassistant.exceptions import PlatformNotReady +from homeassistant.core import HomeAssistant PLATFORMS: list[Platform] = [ Platform.SENSOR, ] -async def async_setup_entry(hass, config_entry): +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Set the config entry up.""" hass.data.setdefault(DOMAIN, {}) - if not (coordinator := QnapCoordinator(hass, config_entry)): - raise PlatformNotReady - - # Fetch initial data so we have data when entities subscribe - await coordinator.async_config_entry_first_refresh() - + if not (coordinator := hass.data[DOMAIN].get(entry.entry_id)): + coordinator = QnapCoordinator(hass, config) + # 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(entry, PLATFORMS) - return True From 8c88b57da58b99aab5b1e009cceb094df1599151 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 18:00:35 -0400 Subject: [PATCH 070/132] Update config_flow.py --- custom_components/qnap/config_flow.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/custom_components/qnap/config_flow.py b/custom_components/qnap/config_flow.py index ae11e09..3ff995f 100644 --- a/custom_components/qnap/config_flow.py +++ b/custom_components/qnap/config_flow.py @@ -1,5 +1,8 @@ """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 @@ -15,6 +18,8 @@ CONF_VERIFY_SSL, ) from homeassistant.helpers import config_validation as cv +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowResult from .const import ( DEFAULT_PORT, @@ -47,7 +52,7 @@ async def async_step_import(self, import_info): """Set the config entry up from yaml.""" return await self.async_step_user(import_info) - async def async_step_user(self, user_input=None): + 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: From 63973416df64ab58fac987b778f10db67ef5ae73 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 18:09:25 -0400 Subject: [PATCH 071/132] Update sensor.py --- custom_components/qnap/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 9a6c0cf..7f43681 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -1,4 +1,6 @@ """Support for QNAP NAS Sensors.""" +from __future__ import annotations + from dataclasses import dataclass import logging From 5c15ec56e70917ecbdb1c27d837841bf9e159733 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 18:26:36 -0400 Subject: [PATCH 072/132] Update __init__.py --- custom_components/qnap/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 35c4bdb..ad4b0f2 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -15,6 +15,7 @@ from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN from .coordinator import QnapCoordinator from homeassistant.core import HomeAssistant +from homeassistant.exceptions import PlatformNotReady PLATFORMS: list[Platform] = [ Platform.SENSOR, @@ -24,10 +25,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Set the config entry up.""" hass.data.setdefault(DOMAIN, {}) - if not (coordinator := hass.data[DOMAIN].get(entry.entry_id)): - coordinator = QnapCoordinator(hass, config) - # Fetch initial data so we have data when entities subscribe - await coordinator.async_config_entry_first_refresh() + coordinator = QnapCoordinator(hass, config_entry) + # Fetch initial data so we have data when entities subscribe + await coordinator.async_config_entry_first_refresh() + if not coordinator.last_update_success: + raise PlatformNotReady hass.data[DOMAIN][config_entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True From 401e38747e6f7e6eef17660cd3401f5a7395feca Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 19:11:32 -0400 Subject: [PATCH 073/132] Update __init__.py --- custom_components/qnap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index ad4b0f2..6743584 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -31,7 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b if not coordinator.last_update_success: raise PlatformNotReady hass.data[DOMAIN][config_entry.entry_id] = coordinator - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) return True From 85f680904d97f098de0b20122fa4cd25bd16fdc6 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 19:12:29 -0400 Subject: [PATCH 074/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index bcf0820..5dfdcb0 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -34,14 +34,14 @@ 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" + protocol = "https" if config.data[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), + f"{protocol}://{config.data.get(CONF_HOST)}", + config.data.get(CONF_PORT), + config.data.get(CONF_USERNAME), + config.data.get(CONF_PASSWORD), + verify_ssl=config.data.get(CONF_VERIFY_SSL), + timeout=config.data.get(CONF_TIMEOUT), ) def _sync_update(self) -> dict[str, dict[str, Any]]: From c90478f465f3de6c6576ba6259a493718df54fed Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 19:37:51 -0400 Subject: [PATCH 075/132] Update sensor.py --- custom_components/qnap/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 7f43681..f5b18e0 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -53,7 +53,9 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up entry.""" - if not (coordinator := QnapCoordinator(hass, config_entry)): + coordinator = QnapCoordinator(hass, config_entry) + await coordinator.async_refresh() + if not coordinator.last_update_success: raise PlatformNotReady uid = config_entry.unique_id sensors: list[QNAPSensor] = [] From 1c74f3076921a944373fa17730ad15d8a646cf34 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 19:46:00 -0400 Subject: [PATCH 076/132] Update sensor.py --- custom_components/qnap/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index f5b18e0..879d3f1 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -265,7 +265,7 @@ def __init__( monitor_subdevice=None, ) -> None: """Initialize the sensor.""" - super().__init__(coordinator) + self.coordinator = coordinator self.entity_description = description self.uid = uid self.device_name = self.coordinator.data["system_stats"]["system"]["name"] From 9f15b4122f431ee0d583f40acc4e6729a654dec8 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:27:18 -0400 Subject: [PATCH 077/132] Create hassfest.yaml --- .github/workflows/hassfest.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/hassfest.yaml diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml new file mode 100644 index 0000000..07d1dda --- /dev/null +++ b/.github/workflows/hassfest.yaml @@ -0,0 +1,14 @@ +name: Validate with hassfest + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + validate: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v3" + - uses: home-assistant/actions/hassfest@master From b0edd4b3f4fce5e90ba301f982c619bd5f512099 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:31:08 -0400 Subject: [PATCH 078/132] Create code-quality.yml --- .github/workflows/code-quality.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/code-quality.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..000e0df --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,17 @@ +name: Checks +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + name: Checks + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install --upgrade pip + - run: pip install "black<23" pylint==v3.0.0a3 mypy==v0.902 + - run: black --diff --check $(git ls-files '*.py') + - run: pylint --disable=all --enable=unused-import $(git ls-files '*.py') + - run: mypy --strict $(git ls-files '*.py') From ee61c8e5c912b24c045f02cf232e84a1e92465bc Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:36:01 -0400 Subject: [PATCH 079/132] Update code-quality.yml --- .github/workflows/code-quality.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 000e0df..1187b5f 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -1,5 +1,10 @@ -name: Checks -on: [pull_request] +name: Code Checks + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" jobs: build: From c6d9cf9c4f85abc28efda42a1586e5f4828edefa Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:39:07 -0400 Subject: [PATCH 080/132] Create ruff.yaml --- .github/workflows/ruff.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/workflows/ruff.yaml diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml new file mode 100644 index 0000000..757cae6 --- /dev/null +++ b/.github/workflows/ruff.yaml @@ -0,0 +1,8 @@ +name: Ruff +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 From 608350ee3ec91b5384f499c775e144a7412b0360 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:42:32 -0400 Subject: [PATCH 081/132] Delete code-quality.yml --- .github/workflows/code-quality.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/code-quality.yml diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml deleted file mode 100644 index 1187b5f..0000000 --- a/.github/workflows/code-quality.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Code Checks - -on: - push: - pull_request: - schedule: - - cron: "0 0 * * *" - -jobs: - build: - runs-on: ubuntu-latest - name: Checks - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install --upgrade pip - - run: pip install "black<23" pylint==v3.0.0a3 mypy==v0.902 - - run: black --diff --check $(git ls-files '*.py') - - run: pylint --disable=all --enable=unused-import $(git ls-files '*.py') - - run: mypy --strict $(git ls-files '*.py') From f4a4a56bf21d0dcf7fb17756ec36a238f2ec77e8 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:42:40 -0400 Subject: [PATCH 082/132] Delete hassfest.yaml --- .github/workflows/hassfest.yaml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .github/workflows/hassfest.yaml diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml deleted file mode 100644 index 07d1dda..0000000 --- a/.github/workflows/hassfest.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: Validate with hassfest - -on: - push: - pull_request: - schedule: - - cron: "0 0 * * *" - -jobs: - validate: - runs-on: "ubuntu-latest" - steps: - - uses: "actions/checkout@v3" - - uses: home-assistant/actions/hassfest@master From ef448ea6ab6e4c5ea1571a8d635aa842d9b8b7cf Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:42:59 -0400 Subject: [PATCH 083/132] Create Ci.yaml --- .github/workflows/Ci.yaml | 1032 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1032 insertions(+) create mode 100644 .github/workflows/Ci.yaml diff --git a/.github/workflows/Ci.yaml b/.github/workflows/Ci.yaml new file mode 100644 index 0000000..a60c680 --- /dev/null +++ b/.github/workflows/Ci.yaml @@ -0,0 +1,1032 @@ +name: CI +run-name: "${{ github.event_name == 'workflow_dispatch' && format('CI: {0}', github.ref_name) || '' }}" + +# yamllint disable-line rule:truthy +on: + push: + branches: + - dev + - rc + - master + pull_request: ~ + workflow_dispatch: + inputs: + full: + description: "Full run (regardless of changes)" + default: false + type: boolean + lint-only: + description: "Skip pytest" + default: false + type: boolean + pylint-only: + description: "Only run pylint" + default: false + type: boolean + mypy-only: + description: "Only run mypy" + default: false + type: boolean + +env: + CACHE_VERSION: 5 + PIP_CACHE_VERSION: 4 + MYPY_CACHE_VERSION: 4 + HA_SHORT_VERSION: 2023.7 + DEFAULT_PYTHON: "3.10" + ALL_PYTHON_VERSIONS: "['3.10', '3.11']" + # 10.3 is the oldest supported version + # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) + # 10.6 is the current long-term-support + # - 10.6.10 is the version currently shipped with the Add-on (as of 31 Jan 2023) + # 10.10 is the latest short-term-support + # - 10.10.3 is the latest (as of 6 Feb 2023) + # mysql 8.0.32 does not always behave the same as MariaDB + # and some queries that work on MariaDB do not work on MySQL + MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3','mysql:8.0.32']" + # 12 is the oldest supported version + # - 12.14 is the latest (as of 9 Feb 2023) + # 15 is the latest version + # - 15.2 is the latest (as of 9 Feb 2023) + POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']" + PRE_COMMIT_CACHE: ~/.cache/pre-commit + PIP_CACHE: /tmp/pip-cache + SQLALCHEMY_WARN_20: 1 + PYTHONASYNCIODEBUG: 1 + HASS_CI: 1 + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + info: + name: Collect information & changes data + outputs: + # In case of issues with the partial run, use the following line instead: + # test_full_suite: 'true' + core: ${{ steps.core.outputs.changes }} + integrations_glob: ${{ steps.info.outputs.integrations_glob }} + integrations: ${{ steps.integrations.outputs.changes }} + pre-commit_cache_key: ${{ steps.generate_pre-commit_cache_key.outputs.key }} + python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }} + requirements: ${{ steps.core.outputs.requirements }} + mariadb_groups: ${{ steps.info.outputs.mariadb_groups }} + postgresql_groups: ${{ steps.info.outputs.postgresql_groups }} + python_versions: ${{ steps.info.outputs.python_versions }} + test_full_suite: ${{ steps.info.outputs.test_full_suite }} + test_group_count: ${{ steps.info.outputs.test_group_count }} + test_groups: ${{ steps.info.outputs.test_groups }} + tests_glob: ${{ steps.info.outputs.tests_glob }} + tests: ${{ steps.info.outputs.tests }} + runs-on: ubuntu-22.04 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Generate partial Python venv restore key + id: generate_python_cache_key + run: >- + echo "key=venv-${{ env.CACHE_VERSION }}-${{ + hashFiles('requirements_test.txt') }}-${{ + hashFiles('requirements_all.txt') }}-${{ + hashFiles('homeassistant/package_constraints.txt') }}" >> $GITHUB_OUTPUT + - name: Generate partial pre-commit restore key + id: generate_pre-commit_cache_key + run: >- + echo "key=pre-commit-${{ env.CACHE_VERSION }}-${{ + hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT + - name: Filter for core changes + uses: dorny/paths-filter@v2.11.1 + id: core + with: + filters: .core_files.yaml + - name: Create a list of integrations to filter for changes + run: | + integrations=$(ls -Ad ./homeassistant/components/[!_]* | xargs -n 1 basename) + touch .integration_paths.yaml + for integration in $integrations; do + echo "${integration}: [homeassistant/components/${integration}/**, tests/components/${integration}/**]" \ + >> .integration_paths.yaml; + done + echo "Result:" + cat .integration_paths.yaml + - name: Filter for integration changes + uses: dorny/paths-filter@v2.11.1 + id: integrations + with: + filters: .integration_paths.yaml + - name: Collect additional information + id: info + run: | + # Defaults + integrations_glob="" + mariadb_groups=${MARIADB_VERSIONS} + postgresql_groups=${POSTGRESQL_VERSIONS} + test_full_suite="true" + test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" + test_group_count=10 + tests="[]" + tests_glob="" + + if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]]; + then + # Create a file glob for the integrations + integrations_glob=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '. | join(",")') + [[ "${integrations_glob}" == *","* ]] && integrations_glob="{${integrations_glob}}" + + # Create list of testable integrations + possible_integrations=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '.[]') + tests=$( + for integration in ${possible_integrations}; + do + if [[ -d "tests/components/${integration}" ]]; then + echo -n "\"${integration}\","; + fi; + done + ) + + [[ ! -z "${tests}" ]] && tests="${tests::-1}" + tests="[${tests}]" + test_groups="${tests}" + # Test group count should be 1, we don't split partial tests + test_group_count=1 + + # Create a file glob for the integrations tests + tests_glob=$(echo "${tests}" | jq -cSr '. | join(",")') + [[ "${tests_glob}" == *","* ]] && tests_glob="{${tests_glob}}" + + mariadb_groups="[]" + postgresql_groups="[]" + test_full_suite="false" + fi + + # We need to run the full suite on certain branches. + # Or, in case core files are touched, for the full suite as well. + if [[ "${{ github.ref }}" == "refs/heads/dev" ]] \ + || [[ "${{ github.ref }}" == "refs/heads/master" ]] \ + || [[ "${{ github.ref }}" == "refs/heads/rc" ]] \ + || [[ "${{ steps.core.outputs.any }}" == "true" ]] \ + || [[ "${{ github.event.inputs.full }}" == "true" ]] \ + || [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]]; + then + mariadb_groups=${MARIADB_VERSIONS} + postgresql_groups=${POSTGRESQL_VERSIONS} + test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" + test_group_count=10 + test_full_suite="true" + fi + + # Output & sent to GitHub Actions + echo "mariadb_groups: ${mariadb_groups}" + echo "mariadb_groups=${mariadb_groups}" >> $GITHUB_OUTPUT + echo "postgresql_groups: ${postgresql_groups}" + echo "postgresql_groups=${postgresql_groups}" >> $GITHUB_OUTPUT + echo "python_versions: ${ALL_PYTHON_VERSIONS}" + echo "python_versions=${ALL_PYTHON_VERSIONS}" >> $GITHUB_OUTPUT + echo "test_full_suite: ${test_full_suite}" + echo "test_full_suite=${test_full_suite}" >> $GITHUB_OUTPUT + echo "integrations_glob: ${integrations_glob}" + echo "integrations_glob=${integrations_glob}" >> $GITHUB_OUTPUT + echo "test_group_count: ${test_group_count}" + echo "test_group_count=${test_group_count}" >> $GITHUB_OUTPUT + echo "test_groups: ${test_groups}" + echo "test_groups=${test_groups}" >> $GITHUB_OUTPUT + echo "tests: ${tests}" + echo "tests=${tests}" >> $GITHUB_OUTPUT + echo "tests_glob: ${tests_glob}" + echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT + + pre-commit: + name: Prepare pre-commit base + runs-on: ubuntu-22.04 + if: | + github.event.inputs.pylint-only != 'true' + && github.event.inputs.mypy-only != 'true' + needs: + - info + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache@v3.3.1 + with: + path: venv + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python -m venv venv + . venv/bin/activate + python --version + pip install "$(cat requirements_test.txt | grep pre-commit)" + - name: Restore pre-commit environment from cache + id: cache-precommit + uses: actions/cache@v3.3.1 + with: + path: ${{ env.PRE_COMMIT_CACHE }} + lookup-only: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Install pre-commit dependencies + if: steps.cache-precommit.outputs.cache-hit != 'true' + run: | + . venv/bin/activate + pre-commit install-hooks + + lint-black: + name: Check black + runs-on: ubuntu-22.04 + needs: + - info + - pre-commit + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + uses: actions/setup-python@v4.6.1 + id: python + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Restore pre-commit environment from cache + id: cache-precommit + uses: actions/cache/restore@v3.3.1 + with: + path: ${{ env.PRE_COMMIT_CACHE }} + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Run black (fully) + if: needs.info.outputs.test_full_suite == 'true' + run: | + . venv/bin/activate + pre-commit run --hook-stage manual black --all-files --show-diff-on-failure + - name: Run black (partially) + if: needs.info.outputs.test_full_suite == 'false' + shell: bash + run: | + . venv/bin/activate + shopt -s globstar + pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure + + lint-ruff: + name: Check ruff + runs-on: ubuntu-22.04 + needs: + - info + - pre-commit + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + uses: actions/setup-python@v4.6.1 + id: python + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Restore pre-commit environment from cache + id: cache-precommit + uses: actions/cache/restore@v3.3.1 + with: + path: ${{ env.PRE_COMMIT_CACHE }} + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Register ruff problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/ruff.json" + - name: Run ruff (fully) + if: needs.info.outputs.test_full_suite == 'true' + run: | + . venv/bin/activate + pre-commit run --hook-stage manual ruff --all-files --show-diff-on-failure + - name: Run ruff (partially) + if: needs.info.outputs.test_full_suite == 'false' + shell: bash + run: | + . venv/bin/activate + shopt -s globstar + pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure + + lint-other: + name: Check other linters + runs-on: ubuntu-22.04 + needs: + - info + - pre-commit + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + uses: actions/setup-python@v4.6.1 + id: python + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ + needs.info.outputs.pre-commit_cache_key }} + - name: Restore pre-commit environment from cache + id: cache-precommit + uses: actions/cache/restore@v3.3.1 + with: + path: ${{ env.PRE_COMMIT_CACHE }} + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.pre-commit_cache_key }} + + - name: Register yamllint problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/yamllint.json" + - name: Run yamllint + run: | + . venv/bin/activate + pre-commit run --hook-stage manual yamllint --all-files --show-diff-on-failure + + - name: Register check-json problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/check-json.json" + - name: Run check-json + run: | + . venv/bin/activate + pre-commit run --hook-stage manual check-json --all-files + + - name: Run prettier (fully) + if: needs.info.outputs.test_full_suite == 'true' + run: | + . venv/bin/activate + pre-commit run --hook-stage manual prettier --all-files + + - name: Run prettier (partially) + if: needs.info.outputs.test_full_suite == 'false' + shell: bash + run: | + . venv/bin/activate + shopt -s globstar + pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} + + - name: Register check executables problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json" + - name: Run executables check + run: | + . venv/bin/activate + pre-commit run --hook-stage manual check-executables-have-shebangs --all-files + + - name: Register codespell problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/codespell.json" + - name: Run codespell + run: | + . venv/bin/activate + pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files + + - name: Register hadolint problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/hadolint.json" + - name: Check Dockerfile + uses: docker://hadolint/hadolint:v1.18.2 + with: + args: hadolint Dockerfile + - name: Check Dockerfile.dev + uses: docker://hadolint/hadolint:v1.18.2 + with: + args: hadolint Dockerfile.dev + + base: + name: Prepare dependencies + runs-on: ubuntu-22.04 + needs: info + timeout-minutes: 60 + strategy: + matrix: + python-version: ${{ fromJSON(needs.info.outputs.python_versions) }} + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.python-version }} + check-latest: true + - name: Generate partial pip restore key + id: generate-pip-key + run: >- + echo "key=pip-${{ env.PIP_CACHE_VERSION }}-${{ + env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache@v3.3.1 + with: + path: venv + lookup-only: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Restore pip wheel cache + if: steps.cache-venv.outputs.cache-hit != 'true' + uses: actions/cache@v3.3.1 + with: + path: ${{ env.PIP_CACHE }} + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + steps.generate-pip-key.outputs.key }} + restore-keys: | + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}- + - name: Install additional OS dependencies + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get -y install \ + bluez \ + ffmpeg \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavformat-dev \ + libavutil-dev \ + libswresample-dev \ + libswscale-dev \ + libudev-dev + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python -m venv venv + . venv/bin/activate + python --version + pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<23.2" setuptools wheel + pip install --cache-dir=$PIP_CACHE -r requirements_all.txt + pip install --cache-dir=$PIP_CACHE -r requirements_test.txt + pip install -e . + + hassfest: + name: Check hassfest + runs-on: ubuntu-22.04 + if: | + github.event.inputs.pylint-only != 'true' + && github.event.inputs.mypy-only != 'true' + needs: + - info + - base + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Run hassfest + run: | + . venv/bin/activate + python -m script.hassfest --requirements --action validate + + gen-requirements-all: + name: Check all requirements + runs-on: ubuntu-22.04 + if: | + github.event.inputs.pylint-only != 'true' + && github.event.inputs.mypy-only != 'true' + needs: + - info + - base + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Run gen_requirements_all.py + run: | + . venv/bin/activate + python -m script.gen_requirements_all validate + + pylint: + name: Check pylint + runs-on: ubuntu-22.04 + timeout-minutes: 20 + if: | + github.event.inputs.mypy-only != 'true' + || github.event.inputs.pylint-only == 'true' + needs: + - info + - base + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Register pylint problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/pylint.json" + - name: Run pylint (fully) + if: needs.info.outputs.test_full_suite == 'true' + run: | + . venv/bin/activate + python --version + pylint --ignore-missing-annotations=y homeassistant + - name: Run pylint (partially) + if: needs.info.outputs.test_full_suite == 'false' + shell: bash + run: | + . venv/bin/activate + python --version + pylint --ignore-missing-annotations=y homeassistant/components/${{ needs.info.outputs.integrations_glob }} + + mypy: + name: Check mypy + runs-on: ubuntu-22.04 + if: | + github.event.inputs.pylint-only != 'true' + || github.event.inputs.mypy-only == 'true' + needs: + - info + - base + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ env.DEFAULT_PYTHON }} + check-latest: true + - name: Generate partial mypy restore key + id: generate-mypy-key + run: | + mypy_version=$(cat requirements_test.txt | grep mypy | cut -d '=' -f 3) + echo "version=$mypy_version" >> $GITHUB_OUTPUT + echo "key=mypy-${{ env.MYPY_CACHE_VERSION }}-$mypy_version-${{ + env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT + - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Restore mypy cache + uses: actions/cache@v3.3.1 + with: + path: .mypy_cache + key: >- + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + steps.generate-mypy-key.outputs.key }} + restore-keys: | + ${{ runner.os }}-${{ steps.python.outputs.python-version }}-mypy-${{ + env.MYPY_CACHE_VERSION }}-${{ steps.generate-mypy-key.outputs.version }}-${{ + env.HA_SHORT_VERSION }}- + - name: Register mypy problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/mypy.json" + - name: Run mypy (fully) + if: needs.info.outputs.test_full_suite == 'true' + run: | + . venv/bin/activate + python --version + mypy homeassistant pylint + - name: Run mypy (partially) + if: needs.info.outputs.test_full_suite == 'false' + shell: bash + run: | + . venv/bin/activate + python --version + mypy homeassistant/components/${{ needs.info.outputs.integrations_glob }} + + pytest: + runs-on: ubuntu-22.04 + if: | + (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') + && github.event.inputs.lint-only != 'true' + && github.event.inputs.pylint-only != 'true' + && github.event.inputs.mypy-only != 'true' + && (needs.info.outputs.test_full_suite == 'true' || needs.info.outputs.tests_glob) + needs: + - info + - base + - gen-requirements-all + - hassfest + - lint-other + - lint-ruff + - mypy + strategy: + fail-fast: false + matrix: + group: ${{ fromJson(needs.info.outputs.test_groups) }} + python-version: ${{ fromJson(needs.info.outputs.python_versions) }} + name: >- + Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) + steps: + - name: Install additional OS dependencies + run: | + sudo apt-get update + sudo apt-get -y install \ + bluez \ + ffmpeg + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.python-version }} + check-latest: true + - name: Restore full Python ${{ matrix.python-version }} virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Register Python problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/python.json" + - name: Install Pytest Annotation plugin + run: | + . venv/bin/activate + # Ideally this should be part of our dependencies + # However this plugin is fairly new and doesn't run correctly + # on a non-GitHub environment. + pip install pytest-github-actions-annotate-failures==0.1.3 + - name: Register pytest slow test problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" + - name: Compile English translations + run: | + . venv/bin/activate + python3 -m script.translations develop --all + - name: Run pytest (fully) + if: needs.info.outputs.test_full_suite == 'true' + timeout-minutes: 60 + run: | + . venv/bin/activate + python --version + python3 -X dev -m pytest \ + -qq \ + --timeout=9 \ + --durations=10 \ + -n auto \ + --dist=loadfile \ + --test-group-count ${{ needs.info.outputs.test_group_count }} \ + --test-group=${{ matrix.group }} \ + --cov="homeassistant" \ + --cov-report=xml \ + -o console_output_style=count \ + -p no:sugar \ + tests + - name: Run pytest (partially) + if: needs.info.outputs.test_full_suite == 'false' + timeout-minutes: 10 + shell: bash + run: | + . venv/bin/activate + python --version + + if [[ ! -f "tests/components/${{ matrix.group }}/__init__.py" ]]; then + echo "::error:: missing file tests/components/${{ matrix.group }}/__init__.py" + exit 1 + fi + + python3 -X dev -m pytest \ + -qq \ + --timeout=9 \ + -n auto \ + --cov="homeassistant.components.${{ matrix.group }}" \ + --cov-report=xml \ + --cov-report=term-missing \ + -o console_output_style=count \ + --durations=0 \ + --durations-min=1 \ + -p no:sugar \ + tests/components/${{ matrix.group }} + - name: Upload coverage artifact + uses: actions/upload-artifact@v3.1.2 + with: + name: coverage-${{ matrix.python-version }}-${{ matrix.group }} + path: coverage.xml + - name: Check dirty + run: | + ./script/check_dirty + + pytest-mariadb: + runs-on: ubuntu-22.04 + services: + mariadb: + image: ${{ matrix.mariadb-group }} + ports: + - 3306:3306 + env: + MYSQL_ROOT_PASSWORD: password + options: --health-cmd="mysqladmin ping -uroot -ppassword" --health-interval=5s --health-timeout=2s --health-retries=3 + if: | + (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') + && github.event.inputs.lint-only != 'true' + && github.event.inputs.pylint-only != 'true' + && github.event.inputs.mypy-only != 'true' + && needs.info.outputs.mariadb_groups != '[]' + needs: + - info + - base + - gen-requirements-all + - hassfest + - lint-other + - lint-ruff + - mypy + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJson(needs.info.outputs.python_versions) }} + mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }} + name: >- + Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }} + steps: + - name: Install additional OS dependencies + run: | + sudo apt-get update + sudo apt-get -y install \ + bluez \ + ffmpeg \ + libmariadb-dev-compat + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.python-version }} + check-latest: true + - name: Restore full Python ${{ matrix.python-version }} virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Register Python problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/python.json" + - name: Install Pytest Annotation plugin + run: | + . venv/bin/activate + # Ideally this should be part of our dependencies + # However this plugin is fairly new and doesn't run correctly + # on a non-GitHub environment. + pip install pytest-github-actions-annotate-failures==0.1.3 + - name: Register pytest slow test problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" + - name: Install SQL Python libraries + run: | + . venv/bin/activate + pip install mysqlclient sqlalchemy_utils + - name: Compile English translations + run: | + . venv/bin/activate + python3 -m script.translations develop --all + - name: Run pytest (partially) + timeout-minutes: 20 + shell: bash + run: | + . venv/bin/activate + python --version + + python3 -X dev -m pytest \ + -qq \ + --timeout=20 \ + -n 1 \ + --cov="homeassistant.components.recorder" \ + --cov-report=xml \ + --cov-report=term-missing \ + -o console_output_style=count \ + --durations=10 \ + -p no:sugar \ + --dburl=mysql://root:password@127.0.0.1/homeassistant-test \ + tests/components/history \ + tests/components/logbook \ + tests/components/recorder \ + tests/components/sensor + - name: Upload coverage artifact + uses: actions/upload-artifact@v3.1.2 + with: + name: coverage-${{ matrix.python-version }}-mariadb + path: coverage.xml + - name: Check dirty + run: | + ./script/check_dirty + + pytest-postgres: + runs-on: ubuntu-22.04 + services: + postgres: + image: ${{ matrix.postgresql-group }} + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: password + options: --health-cmd="pg_isready -hlocalhost -Upostgres" --health-interval=5s --health-timeout=2s --health-retries=3 + if: | + (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') + && github.event.inputs.lint-only != 'true' + && github.event.inputs.pylint-only != 'true' + && github.event.inputs.mypy-only != 'true' + && needs.info.outputs.postgresql_groups != '[]' + needs: + - info + - base + - gen-requirements-all + - hassfest + - lint-other + - lint-ruff + - mypy + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJson(needs.info.outputs.python_versions) }} + postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }} + name: >- + Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }} + steps: + - name: Install additional OS dependencies + run: | + sudo apt-get update + sudo apt-get -y install \ + bluez \ + ffmpeg \ + postgresql-server-dev-14 + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v4.6.1 + with: + python-version: ${{ matrix.python-version }} + check-latest: true + - name: Restore full Python ${{ matrix.python-version }} virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + fail-on-cache-miss: true + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ + needs.info.outputs.python_cache_key }} + - name: Register Python problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/python.json" + - name: Install Pytest Annotation plugin + run: | + . venv/bin/activate + # Ideally this should be part of our dependencies + # However this plugin is fairly new and doesn't run correctly + # on a non-GitHub environment. + pip install pytest-github-actions-annotate-failures==0.1.3 + - name: Register pytest slow test problem matcher + run: | + echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" + - name: Install SQL Python libraries + run: | + . venv/bin/activate + pip install psycopg2 sqlalchemy_utils + - name: Compile English translations + run: | + . venv/bin/activate + python3 -m script.translations develop --all + - name: Run pytest (partially) + timeout-minutes: 20 + shell: bash + run: | + . venv/bin/activate + python --version + + python3 -X dev -m pytest \ + -qq \ + --timeout=9 \ + -n 1 \ + --cov="homeassistant.components.recorder" \ + --cov-report=xml \ + --cov-report=term-missing \ + -o console_output_style=count \ + --durations=0 \ + --durations-min=10 \ + -p no:sugar \ + --dburl=postgresql://postgres:password@127.0.0.1/homeassistant-test \ + tests/components/history \ + tests/components/logbook \ + tests/components/recorder \ + tests/components/sensor + - name: Upload coverage artifact + uses: actions/upload-artifact@v3.1.0 + with: + name: coverage-${{ matrix.python-version }}-postgresql + path: coverage.xml + - name: Check dirty + run: | + ./script/check_dirty + + coverage: + name: Upload test coverage to Codecov + runs-on: ubuntu-22.04 + needs: + - info + - pytest + timeout-minutes: 10 + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3.5.3 + - name: Download all coverage artifacts + uses: actions/download-artifact@v3 + - name: Upload coverage to Codecov (full coverage) + if: needs.info.outputs.test_full_suite == 'true' + uses: Wandalen/wretry.action@v1.0.36 + with: + action: codecov/codecov-action@v3.1.3 + with: | + fail_ci_if_error: true + flags: full-suite + attempt_limit: 5 + attempt_delay: 30000 + - name: Upload coverage to Codecov (partial coverage) + if: needs.info.outputs.test_full_suite == 'false' + uses: Wandalen/wretry.action@v1.0.36 + with: + action: codecov/codecov-action@v3.1.3 + with: | + fail_ci_if_error: true + attempt_limit: 5 + attempt_delay: 30000 From 6970a563c68aadbccf530fc092db4e641cab5af8 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:45:14 -0400 Subject: [PATCH 084/132] Delete Ci.yaml --- .github/workflows/Ci.yaml | 1032 ------------------------------------- 1 file changed, 1032 deletions(-) delete mode 100644 .github/workflows/Ci.yaml diff --git a/.github/workflows/Ci.yaml b/.github/workflows/Ci.yaml deleted file mode 100644 index a60c680..0000000 --- a/.github/workflows/Ci.yaml +++ /dev/null @@ -1,1032 +0,0 @@ -name: CI -run-name: "${{ github.event_name == 'workflow_dispatch' && format('CI: {0}', github.ref_name) || '' }}" - -# yamllint disable-line rule:truthy -on: - push: - branches: - - dev - - rc - - master - pull_request: ~ - workflow_dispatch: - inputs: - full: - description: "Full run (regardless of changes)" - default: false - type: boolean - lint-only: - description: "Skip pytest" - default: false - type: boolean - pylint-only: - description: "Only run pylint" - default: false - type: boolean - mypy-only: - description: "Only run mypy" - default: false - type: boolean - -env: - CACHE_VERSION: 5 - PIP_CACHE_VERSION: 4 - MYPY_CACHE_VERSION: 4 - HA_SHORT_VERSION: 2023.7 - DEFAULT_PYTHON: "3.10" - ALL_PYTHON_VERSIONS: "['3.10', '3.11']" - # 10.3 is the oldest supported version - # - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022) - # 10.6 is the current long-term-support - # - 10.6.10 is the version currently shipped with the Add-on (as of 31 Jan 2023) - # 10.10 is the latest short-term-support - # - 10.10.3 is the latest (as of 6 Feb 2023) - # mysql 8.0.32 does not always behave the same as MariaDB - # and some queries that work on MariaDB do not work on MySQL - MARIADB_VERSIONS: "['mariadb:10.3.32','mariadb:10.6.10','mariadb:10.10.3','mysql:8.0.32']" - # 12 is the oldest supported version - # - 12.14 is the latest (as of 9 Feb 2023) - # 15 is the latest version - # - 15.2 is the latest (as of 9 Feb 2023) - POSTGRESQL_VERSIONS: "['postgres:12.14','postgres:15.2']" - PRE_COMMIT_CACHE: ~/.cache/pre-commit - PIP_CACHE: /tmp/pip-cache - SQLALCHEMY_WARN_20: 1 - PYTHONASYNCIODEBUG: 1 - HASS_CI: 1 - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - info: - name: Collect information & changes data - outputs: - # In case of issues with the partial run, use the following line instead: - # test_full_suite: 'true' - core: ${{ steps.core.outputs.changes }} - integrations_glob: ${{ steps.info.outputs.integrations_glob }} - integrations: ${{ steps.integrations.outputs.changes }} - pre-commit_cache_key: ${{ steps.generate_pre-commit_cache_key.outputs.key }} - python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }} - requirements: ${{ steps.core.outputs.requirements }} - mariadb_groups: ${{ steps.info.outputs.mariadb_groups }} - postgresql_groups: ${{ steps.info.outputs.postgresql_groups }} - python_versions: ${{ steps.info.outputs.python_versions }} - test_full_suite: ${{ steps.info.outputs.test_full_suite }} - test_group_count: ${{ steps.info.outputs.test_group_count }} - test_groups: ${{ steps.info.outputs.test_groups }} - tests_glob: ${{ steps.info.outputs.tests_glob }} - tests: ${{ steps.info.outputs.tests }} - runs-on: ubuntu-22.04 - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Generate partial Python venv restore key - id: generate_python_cache_key - run: >- - echo "key=venv-${{ env.CACHE_VERSION }}-${{ - hashFiles('requirements_test.txt') }}-${{ - hashFiles('requirements_all.txt') }}-${{ - hashFiles('homeassistant/package_constraints.txt') }}" >> $GITHUB_OUTPUT - - name: Generate partial pre-commit restore key - id: generate_pre-commit_cache_key - run: >- - echo "key=pre-commit-${{ env.CACHE_VERSION }}-${{ - hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT - - name: Filter for core changes - uses: dorny/paths-filter@v2.11.1 - id: core - with: - filters: .core_files.yaml - - name: Create a list of integrations to filter for changes - run: | - integrations=$(ls -Ad ./homeassistant/components/[!_]* | xargs -n 1 basename) - touch .integration_paths.yaml - for integration in $integrations; do - echo "${integration}: [homeassistant/components/${integration}/**, tests/components/${integration}/**]" \ - >> .integration_paths.yaml; - done - echo "Result:" - cat .integration_paths.yaml - - name: Filter for integration changes - uses: dorny/paths-filter@v2.11.1 - id: integrations - with: - filters: .integration_paths.yaml - - name: Collect additional information - id: info - run: | - # Defaults - integrations_glob="" - mariadb_groups=${MARIADB_VERSIONS} - postgresql_groups=${POSTGRESQL_VERSIONS} - test_full_suite="true" - test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" - test_group_count=10 - tests="[]" - tests_glob="" - - if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]]; - then - # Create a file glob for the integrations - integrations_glob=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '. | join(",")') - [[ "${integrations_glob}" == *","* ]] && integrations_glob="{${integrations_glob}}" - - # Create list of testable integrations - possible_integrations=$(echo '${{ steps.integrations.outputs.changes }}' | jq -cSr '.[]') - tests=$( - for integration in ${possible_integrations}; - do - if [[ -d "tests/components/${integration}" ]]; then - echo -n "\"${integration}\","; - fi; - done - ) - - [[ ! -z "${tests}" ]] && tests="${tests::-1}" - tests="[${tests}]" - test_groups="${tests}" - # Test group count should be 1, we don't split partial tests - test_group_count=1 - - # Create a file glob for the integrations tests - tests_glob=$(echo "${tests}" | jq -cSr '. | join(",")') - [[ "${tests_glob}" == *","* ]] && tests_glob="{${tests_glob}}" - - mariadb_groups="[]" - postgresql_groups="[]" - test_full_suite="false" - fi - - # We need to run the full suite on certain branches. - # Or, in case core files are touched, for the full suite as well. - if [[ "${{ github.ref }}" == "refs/heads/dev" ]] \ - || [[ "${{ github.ref }}" == "refs/heads/master" ]] \ - || [[ "${{ github.ref }}" == "refs/heads/rc" ]] \ - || [[ "${{ steps.core.outputs.any }}" == "true" ]] \ - || [[ "${{ github.event.inputs.full }}" == "true" ]] \ - || [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]]; - then - mariadb_groups=${MARIADB_VERSIONS} - postgresql_groups=${POSTGRESQL_VERSIONS} - test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" - test_group_count=10 - test_full_suite="true" - fi - - # Output & sent to GitHub Actions - echo "mariadb_groups: ${mariadb_groups}" - echo "mariadb_groups=${mariadb_groups}" >> $GITHUB_OUTPUT - echo "postgresql_groups: ${postgresql_groups}" - echo "postgresql_groups=${postgresql_groups}" >> $GITHUB_OUTPUT - echo "python_versions: ${ALL_PYTHON_VERSIONS}" - echo "python_versions=${ALL_PYTHON_VERSIONS}" >> $GITHUB_OUTPUT - echo "test_full_suite: ${test_full_suite}" - echo "test_full_suite=${test_full_suite}" >> $GITHUB_OUTPUT - echo "integrations_glob: ${integrations_glob}" - echo "integrations_glob=${integrations_glob}" >> $GITHUB_OUTPUT - echo "test_group_count: ${test_group_count}" - echo "test_group_count=${test_group_count}" >> $GITHUB_OUTPUT - echo "test_groups: ${test_groups}" - echo "test_groups=${test_groups}" >> $GITHUB_OUTPUT - echo "tests: ${tests}" - echo "tests=${tests}" >> $GITHUB_OUTPUT - echo "tests_glob: ${tests_glob}" - echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT - - pre-commit: - name: Prepare pre-commit base - runs-on: ubuntu-22.04 - if: | - github.event.inputs.pylint-only != 'true' - && github.event.inputs.mypy-only != 'true' - needs: - - info - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore base Python virtual environment - id: cache-venv - uses: actions/cache@v3.3.1 - with: - path: venv - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Create Python virtual environment - if: steps.cache-venv.outputs.cache-hit != 'true' - run: | - python -m venv venv - . venv/bin/activate - python --version - pip install "$(cat requirements_test.txt | grep pre-commit)" - - name: Restore pre-commit environment from cache - id: cache-precommit - uses: actions/cache@v3.3.1 - with: - path: ${{ env.PRE_COMMIT_CACHE }} - lookup-only: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Install pre-commit dependencies - if: steps.cache-precommit.outputs.cache-hit != 'true' - run: | - . venv/bin/activate - pre-commit install-hooks - - lint-black: - name: Check black - runs-on: ubuntu-22.04 - needs: - - info - - pre-commit - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v4.6.1 - id: python - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore base Python virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Restore pre-commit environment from cache - id: cache-precommit - uses: actions/cache/restore@v3.3.1 - with: - path: ${{ env.PRE_COMMIT_CACHE }} - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Run black (fully) - if: needs.info.outputs.test_full_suite == 'true' - run: | - . venv/bin/activate - pre-commit run --hook-stage manual black --all-files --show-diff-on-failure - - name: Run black (partially) - if: needs.info.outputs.test_full_suite == 'false' - shell: bash - run: | - . venv/bin/activate - shopt -s globstar - pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure - - lint-ruff: - name: Check ruff - runs-on: ubuntu-22.04 - needs: - - info - - pre-commit - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v4.6.1 - id: python - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore base Python virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Restore pre-commit environment from cache - id: cache-precommit - uses: actions/cache/restore@v3.3.1 - with: - path: ${{ env.PRE_COMMIT_CACHE }} - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Register ruff problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/ruff.json" - - name: Run ruff (fully) - if: needs.info.outputs.test_full_suite == 'true' - run: | - . venv/bin/activate - pre-commit run --hook-stage manual ruff --all-files --show-diff-on-failure - - name: Run ruff (partially) - if: needs.info.outputs.test_full_suite == 'false' - shell: bash - run: | - . venv/bin/activate - shopt -s globstar - pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} --show-diff-on-failure - - lint-other: - name: Check other linters - runs-on: ubuntu-22.04 - needs: - - info - - pre-commit - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v4.6.1 - id: python - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore base Python virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ - needs.info.outputs.pre-commit_cache_key }} - - name: Restore pre-commit environment from cache - id: cache-precommit - uses: actions/cache/restore@v3.3.1 - with: - path: ${{ env.PRE_COMMIT_CACHE }} - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.pre-commit_cache_key }} - - - name: Register yamllint problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/yamllint.json" - - name: Run yamllint - run: | - . venv/bin/activate - pre-commit run --hook-stage manual yamllint --all-files --show-diff-on-failure - - - name: Register check-json problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/check-json.json" - - name: Run check-json - run: | - . venv/bin/activate - pre-commit run --hook-stage manual check-json --all-files - - - name: Run prettier (fully) - if: needs.info.outputs.test_full_suite == 'true' - run: | - . venv/bin/activate - pre-commit run --hook-stage manual prettier --all-files - - - name: Run prettier (partially) - if: needs.info.outputs.test_full_suite == 'false' - shell: bash - run: | - . venv/bin/activate - shopt -s globstar - pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*} - - - name: Register check executables problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/check-executables-have-shebangs.json" - - name: Run executables check - run: | - . venv/bin/activate - pre-commit run --hook-stage manual check-executables-have-shebangs --all-files - - - name: Register codespell problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/codespell.json" - - name: Run codespell - run: | - . venv/bin/activate - pre-commit run --show-diff-on-failure --hook-stage manual codespell --all-files - - - name: Register hadolint problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/hadolint.json" - - name: Check Dockerfile - uses: docker://hadolint/hadolint:v1.18.2 - with: - args: hadolint Dockerfile - - name: Check Dockerfile.dev - uses: docker://hadolint/hadolint:v1.18.2 - with: - args: hadolint Dockerfile.dev - - base: - name: Prepare dependencies - runs-on: ubuntu-22.04 - needs: info - timeout-minutes: 60 - strategy: - matrix: - python-version: ${{ fromJSON(needs.info.outputs.python_versions) }} - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ matrix.python-version }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ matrix.python-version }} - check-latest: true - - name: Generate partial pip restore key - id: generate-pip-key - run: >- - echo "key=pip-${{ env.PIP_CACHE_VERSION }}-${{ - env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - - name: Restore base Python virtual environment - id: cache-venv - uses: actions/cache@v3.3.1 - with: - path: venv - lookup-only: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Restore pip wheel cache - if: steps.cache-venv.outputs.cache-hit != 'true' - uses: actions/cache@v3.3.1 - with: - path: ${{ env.PIP_CACHE }} - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - steps.generate-pip-key.outputs.key }} - restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}- - - name: Install additional OS dependencies - if: steps.cache-venv.outputs.cache-hit != 'true' - run: | - sudo apt-get update - sudo apt-get -y install \ - bluez \ - ffmpeg \ - libavcodec-dev \ - libavdevice-dev \ - libavfilter-dev \ - libavformat-dev \ - libavutil-dev \ - libswresample-dev \ - libswscale-dev \ - libudev-dev - - name: Create Python virtual environment - if: steps.cache-venv.outputs.cache-hit != 'true' - run: | - python -m venv venv - . venv/bin/activate - python --version - pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<23.2" setuptools wheel - pip install --cache-dir=$PIP_CACHE -r requirements_all.txt - pip install --cache-dir=$PIP_CACHE -r requirements_test.txt - pip install -e . - - hassfest: - name: Check hassfest - runs-on: ubuntu-22.04 - if: | - github.event.inputs.pylint-only != 'true' - && github.event.inputs.mypy-only != 'true' - needs: - - info - - base - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Run hassfest - run: | - . venv/bin/activate - python -m script.hassfest --requirements --action validate - - gen-requirements-all: - name: Check all requirements - runs-on: ubuntu-22.04 - if: | - github.event.inputs.pylint-only != 'true' - && github.event.inputs.mypy-only != 'true' - needs: - - info - - base - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore base Python virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Run gen_requirements_all.py - run: | - . venv/bin/activate - python -m script.gen_requirements_all validate - - pylint: - name: Check pylint - runs-on: ubuntu-22.04 - timeout-minutes: 20 - if: | - github.event.inputs.mypy-only != 'true' - || github.event.inputs.pylint-only == 'true' - needs: - - info - - base - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Register pylint problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/pylint.json" - - name: Run pylint (fully) - if: needs.info.outputs.test_full_suite == 'true' - run: | - . venv/bin/activate - python --version - pylint --ignore-missing-annotations=y homeassistant - - name: Run pylint (partially) - if: needs.info.outputs.test_full_suite == 'false' - shell: bash - run: | - . venv/bin/activate - python --version - pylint --ignore-missing-annotations=y homeassistant/components/${{ needs.info.outputs.integrations_glob }} - - mypy: - name: Check mypy - runs-on: ubuntu-22.04 - if: | - github.event.inputs.pylint-only != 'true' - || github.event.inputs.mypy-only == 'true' - needs: - - info - - base - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ env.DEFAULT_PYTHON }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ env.DEFAULT_PYTHON }} - check-latest: true - - name: Generate partial mypy restore key - id: generate-mypy-key - run: | - mypy_version=$(cat requirements_test.txt | grep mypy | cut -d '=' -f 3) - echo "version=$mypy_version" >> $GITHUB_OUTPUT - echo "key=mypy-${{ env.MYPY_CACHE_VERSION }}-$mypy_version-${{ - env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT - - name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Restore mypy cache - uses: actions/cache@v3.3.1 - with: - path: .mypy_cache - key: >- - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - steps.generate-mypy-key.outputs.key }} - restore-keys: | - ${{ runner.os }}-${{ steps.python.outputs.python-version }}-mypy-${{ - env.MYPY_CACHE_VERSION }}-${{ steps.generate-mypy-key.outputs.version }}-${{ - env.HA_SHORT_VERSION }}- - - name: Register mypy problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/mypy.json" - - name: Run mypy (fully) - if: needs.info.outputs.test_full_suite == 'true' - run: | - . venv/bin/activate - python --version - mypy homeassistant pylint - - name: Run mypy (partially) - if: needs.info.outputs.test_full_suite == 'false' - shell: bash - run: | - . venv/bin/activate - python --version - mypy homeassistant/components/${{ needs.info.outputs.integrations_glob }} - - pytest: - runs-on: ubuntu-22.04 - if: | - (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') - && github.event.inputs.lint-only != 'true' - && github.event.inputs.pylint-only != 'true' - && github.event.inputs.mypy-only != 'true' - && (needs.info.outputs.test_full_suite == 'true' || needs.info.outputs.tests_glob) - needs: - - info - - base - - gen-requirements-all - - hassfest - - lint-other - - lint-ruff - - mypy - strategy: - fail-fast: false - matrix: - group: ${{ fromJson(needs.info.outputs.test_groups) }} - python-version: ${{ fromJson(needs.info.outputs.python_versions) }} - name: >- - Run tests Python ${{ matrix.python-version }} (${{ matrix.group }}) - steps: - - name: Install additional OS dependencies - run: | - sudo apt-get update - sudo apt-get -y install \ - bluez \ - ffmpeg - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ matrix.python-version }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ matrix.python-version }} - check-latest: true - - name: Restore full Python ${{ matrix.python-version }} virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Register Python problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Install Pytest Annotation plugin - run: | - . venv/bin/activate - # Ideally this should be part of our dependencies - # However this plugin is fairly new and doesn't run correctly - # on a non-GitHub environment. - pip install pytest-github-actions-annotate-failures==0.1.3 - - name: Register pytest slow test problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - - name: Compile English translations - run: | - . venv/bin/activate - python3 -m script.translations develop --all - - name: Run pytest (fully) - if: needs.info.outputs.test_full_suite == 'true' - timeout-minutes: 60 - run: | - . venv/bin/activate - python --version - python3 -X dev -m pytest \ - -qq \ - --timeout=9 \ - --durations=10 \ - -n auto \ - --dist=loadfile \ - --test-group-count ${{ needs.info.outputs.test_group_count }} \ - --test-group=${{ matrix.group }} \ - --cov="homeassistant" \ - --cov-report=xml \ - -o console_output_style=count \ - -p no:sugar \ - tests - - name: Run pytest (partially) - if: needs.info.outputs.test_full_suite == 'false' - timeout-minutes: 10 - shell: bash - run: | - . venv/bin/activate - python --version - - if [[ ! -f "tests/components/${{ matrix.group }}/__init__.py" ]]; then - echo "::error:: missing file tests/components/${{ matrix.group }}/__init__.py" - exit 1 - fi - - python3 -X dev -m pytest \ - -qq \ - --timeout=9 \ - -n auto \ - --cov="homeassistant.components.${{ matrix.group }}" \ - --cov-report=xml \ - --cov-report=term-missing \ - -o console_output_style=count \ - --durations=0 \ - --durations-min=1 \ - -p no:sugar \ - tests/components/${{ matrix.group }} - - name: Upload coverage artifact - uses: actions/upload-artifact@v3.1.2 - with: - name: coverage-${{ matrix.python-version }}-${{ matrix.group }} - path: coverage.xml - - name: Check dirty - run: | - ./script/check_dirty - - pytest-mariadb: - runs-on: ubuntu-22.04 - services: - mariadb: - image: ${{ matrix.mariadb-group }} - ports: - - 3306:3306 - env: - MYSQL_ROOT_PASSWORD: password - options: --health-cmd="mysqladmin ping -uroot -ppassword" --health-interval=5s --health-timeout=2s --health-retries=3 - if: | - (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') - && github.event.inputs.lint-only != 'true' - && github.event.inputs.pylint-only != 'true' - && github.event.inputs.mypy-only != 'true' - && needs.info.outputs.mariadb_groups != '[]' - needs: - - info - - base - - gen-requirements-all - - hassfest - - lint-other - - lint-ruff - - mypy - strategy: - fail-fast: false - matrix: - python-version: ${{ fromJson(needs.info.outputs.python_versions) }} - mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }} - name: >- - Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }} - steps: - - name: Install additional OS dependencies - run: | - sudo apt-get update - sudo apt-get -y install \ - bluez \ - ffmpeg \ - libmariadb-dev-compat - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ matrix.python-version }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ matrix.python-version }} - check-latest: true - - name: Restore full Python ${{ matrix.python-version }} virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Register Python problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Install Pytest Annotation plugin - run: | - . venv/bin/activate - # Ideally this should be part of our dependencies - # However this plugin is fairly new and doesn't run correctly - # on a non-GitHub environment. - pip install pytest-github-actions-annotate-failures==0.1.3 - - name: Register pytest slow test problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - - name: Install SQL Python libraries - run: | - . venv/bin/activate - pip install mysqlclient sqlalchemy_utils - - name: Compile English translations - run: | - . venv/bin/activate - python3 -m script.translations develop --all - - name: Run pytest (partially) - timeout-minutes: 20 - shell: bash - run: | - . venv/bin/activate - python --version - - python3 -X dev -m pytest \ - -qq \ - --timeout=20 \ - -n 1 \ - --cov="homeassistant.components.recorder" \ - --cov-report=xml \ - --cov-report=term-missing \ - -o console_output_style=count \ - --durations=10 \ - -p no:sugar \ - --dburl=mysql://root:password@127.0.0.1/homeassistant-test \ - tests/components/history \ - tests/components/logbook \ - tests/components/recorder \ - tests/components/sensor - - name: Upload coverage artifact - uses: actions/upload-artifact@v3.1.2 - with: - name: coverage-${{ matrix.python-version }}-mariadb - path: coverage.xml - - name: Check dirty - run: | - ./script/check_dirty - - pytest-postgres: - runs-on: ubuntu-22.04 - services: - postgres: - image: ${{ matrix.postgresql-group }} - ports: - - 5432:5432 - env: - POSTGRES_PASSWORD: password - options: --health-cmd="pg_isready -hlocalhost -Upostgres" --health-interval=5s --health-timeout=2s --health-retries=3 - if: | - (github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core') - && github.event.inputs.lint-only != 'true' - && github.event.inputs.pylint-only != 'true' - && github.event.inputs.mypy-only != 'true' - && needs.info.outputs.postgresql_groups != '[]' - needs: - - info - - base - - gen-requirements-all - - hassfest - - lint-other - - lint-ruff - - mypy - strategy: - fail-fast: false - matrix: - python-version: ${{ fromJson(needs.info.outputs.python_versions) }} - postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }} - name: >- - Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }} - steps: - - name: Install additional OS dependencies - run: | - sudo apt-get update - sudo apt-get -y install \ - bluez \ - ffmpeg \ - postgresql-server-dev-14 - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Set up Python ${{ matrix.python-version }} - id: python - uses: actions/setup-python@v4.6.1 - with: - python-version: ${{ matrix.python-version }} - check-latest: true - - name: Restore full Python ${{ matrix.python-version }} virtual environment - id: cache-venv - uses: actions/cache/restore@v3.3.1 - with: - path: venv - fail-on-cache-miss: true - key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{ - needs.info.outputs.python_cache_key }} - - name: Register Python problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Install Pytest Annotation plugin - run: | - . venv/bin/activate - # Ideally this should be part of our dependencies - # However this plugin is fairly new and doesn't run correctly - # on a non-GitHub environment. - pip install pytest-github-actions-annotate-failures==0.1.3 - - name: Register pytest slow test problem matcher - run: | - echo "::add-matcher::.github/workflows/matchers/pytest-slow.json" - - name: Install SQL Python libraries - run: | - . venv/bin/activate - pip install psycopg2 sqlalchemy_utils - - name: Compile English translations - run: | - . venv/bin/activate - python3 -m script.translations develop --all - - name: Run pytest (partially) - timeout-minutes: 20 - shell: bash - run: | - . venv/bin/activate - python --version - - python3 -X dev -m pytest \ - -qq \ - --timeout=9 \ - -n 1 \ - --cov="homeassistant.components.recorder" \ - --cov-report=xml \ - --cov-report=term-missing \ - -o console_output_style=count \ - --durations=0 \ - --durations-min=10 \ - -p no:sugar \ - --dburl=postgresql://postgres:password@127.0.0.1/homeassistant-test \ - tests/components/history \ - tests/components/logbook \ - tests/components/recorder \ - tests/components/sensor - - name: Upload coverage artifact - uses: actions/upload-artifact@v3.1.0 - with: - name: coverage-${{ matrix.python-version }}-postgresql - path: coverage.xml - - name: Check dirty - run: | - ./script/check_dirty - - coverage: - name: Upload test coverage to Codecov - runs-on: ubuntu-22.04 - needs: - - info - - pytest - timeout-minutes: 10 - steps: - - name: Check out code from GitHub - uses: actions/checkout@v3.5.3 - - name: Download all coverage artifacts - uses: actions/download-artifact@v3 - - name: Upload coverage to Codecov (full coverage) - if: needs.info.outputs.test_full_suite == 'true' - uses: Wandalen/wretry.action@v1.0.36 - with: - action: codecov/codecov-action@v3.1.3 - with: | - fail_ci_if_error: true - flags: full-suite - attempt_limit: 5 - attempt_delay: 30000 - - name: Upload coverage to Codecov (partial coverage) - if: needs.info.outputs.test_full_suite == 'false' - uses: Wandalen/wretry.action@v1.0.36 - with: - action: codecov/codecov-action@v3.1.3 - with: | - fail_ci_if_error: true - attempt_limit: 5 - attempt_delay: 30000 From 1ee02f2a378eaed028548672400bce2a1accb416 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:48:55 -0400 Subject: [PATCH 085/132] Update ruff.yaml --- .github/workflows/ruff.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 757cae6..257c18b 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -6,3 +6,10 @@ jobs: steps: - uses: actions/checkout@v3 - uses: chartboost/ruff-action@v1 + - name: github-action-auto-format + uses: cloudposse/github-action-auto-format@v0.12.0 + - name: Pylint action + uses: gabriel-milan/action-pylint@v1 + - name: Python Black + uses: cytopia/docker-black@0.11 + From 1db89258d76cdb78abc4a64a261a6e399474696f Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:51:09 -0400 Subject: [PATCH 086/132] Update __init__.py --- custom_components/qnap/__init__.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 6743584..6704723 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -2,17 +2,9 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_PASSWORD, - CONF_PORT, - CONF_SSL, - CONF_USERNAME, - CONF_VERIFY_SSL, - Platform, -) +from homeassistant.const import Platform -from .const import DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN +from .const import DOMAIN from .coordinator import QnapCoordinator from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady From 4c02d3838a5df5e64079ef3aab52f0ef17bc5de5 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:52:20 -0400 Subject: [PATCH 087/132] Update config_flow.py --- custom_components/qnap/config_flow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/config_flow.py b/custom_components/qnap/config_flow.py index 3ff995f..e62623b 100644 --- a/custom_components/qnap/config_flow.py +++ b/custom_components/qnap/config_flow.py @@ -18,7 +18,6 @@ CONF_VERIFY_SSL, ) from homeassistant.helpers import config_validation as cv -from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult from .const import ( @@ -52,7 +51,10 @@ async def async_step_import(self, import_info): """Set the config entry up from yaml.""" return await self.async_step_user(import_info) - async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult: + 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: From fc94ff48ec03062d487892b4d7baeb3530453d72 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:55:26 -0400 Subject: [PATCH 088/132] Update ruff.yaml --- .github/workflows/ruff.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 257c18b..8ed9587 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -4,12 +4,13 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: chartboost/ruff-action@v1 - name: github-action-auto-format uses: cloudposse/github-action-auto-format@v0.12.0 - name: Pylint action uses: gabriel-milan/action-pylint@v1 - name: Python Black uses: cytopia/docker-black@0.11 + - uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 + From 0372fd202d081703cfb873dcb055336ab0818896 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:57:07 -0400 Subject: [PATCH 089/132] Update ruff.yaml --- .github/workflows/ruff.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 8ed9587..06e9f9a 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -4,8 +4,6 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - name: github-action-auto-format - uses: cloudposse/github-action-auto-format@v0.12.0 - name: Pylint action uses: gabriel-milan/action-pylint@v1 - name: Python Black From ded3e9614d6200ec61ea7ca027573498bd4e6ac7 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:58:11 -0400 Subject: [PATCH 090/132] Create black.yaml --- .github/workflows/black.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/workflows/black.yaml diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml new file mode 100644 index 0000000..15fadcb --- /dev/null +++ b/.github/workflows/black.yaml @@ -0,0 +1,8 @@ +name: Black +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - name: Python Black + uses: cytopia/docker-black@0.11 From 956688678bd32a0f2e0c88d8c673c0095f833486 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:59:05 -0400 Subject: [PATCH 091/132] Create pylint.yaml --- .github/workflows/pylint.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/workflows/pylint.yaml diff --git a/.github/workflows/pylint.yaml b/.github/workflows/pylint.yaml new file mode 100644 index 0000000..8129a6c --- /dev/null +++ b/.github/workflows/pylint.yaml @@ -0,0 +1,8 @@ +name: Pylint +on: [ push, pull_request ] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - name: Pylint action + uses: gabriel-milan/action-pylint@v1 From 2c453cd4b7d391510571cbc613301736b1b0b48a Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 20:59:22 -0400 Subject: [PATCH 092/132] Update ruff.yaml --- .github/workflows/ruff.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 06e9f9a..bbaef24 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -4,10 +4,6 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - name: Pylint action - uses: gabriel-milan/action-pylint@v1 - - name: Python Black - uses: cytopia/docker-black@0.11 - uses: actions/checkout@v3 - uses: chartboost/ruff-action@v1 From fd3c0b34b7e427f014d1aef44f24c5f4aca8c553 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 21:18:53 -0400 Subject: [PATCH 093/132] Update __init__.py --- custom_components/qnap/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 6704723..e1e90c3 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -3,11 +3,11 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import PlatformNotReady from .const import DOMAIN from .coordinator import QnapCoordinator -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady PLATFORMS: list[Platform] = [ Platform.SENSOR, From 64017f8f04cfe00f48678543f35988ca6706473b Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 21:19:43 -0400 Subject: [PATCH 094/132] Update config_flow.py --- custom_components/qnap/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/config_flow.py b/custom_components/qnap/config_flow.py index e62623b..6103327 100644 --- a/custom_components/qnap/config_flow.py +++ b/custom_components/qnap/config_flow.py @@ -17,8 +17,8 @@ CONF_USERNAME, CONF_VERIFY_SSL, ) -from homeassistant.helpers import config_validation as cv from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import config_validation as cv from .const import ( DEFAULT_PORT, From 6a55fae7565e43d450d644981fb14a9da9c7bf06 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 13 Jun 2023 21:21:19 -0400 Subject: [PATCH 095/132] Update sensor.py --- custom_components/qnap/sensor.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 879d3f1..0ec9e3c 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -19,9 +19,9 @@ UnitOfTemperature, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.exceptions import PlatformNotReady from .const import ( ATTR_DRIVE, @@ -40,7 +40,6 @@ ATTR_VOLUME_SIZE, DEFAULT_NAME, DOMAIN, - ) from .coordinator import QnapCoordinator @@ -257,7 +256,7 @@ class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): """Base class for a QNAP sensor.""" def __init__( - self, + self, coordinator: QnapCoordinator, description, uid, @@ -276,10 +275,10 @@ def __init__( def unique_id(self): """Return unique_id.""" return f"{self.uid}_{self.name}" - + @property def coordinator_context(self): - """helpers update_coordinator.""" + """Helpers update_coordinator.""" return None @property From e191a05a2e744a9ca9e7a8f3139476da4a859bd6 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 14 Jun 2023 00:02:08 -0400 Subject: [PATCH 096/132] Update sensor.py --- custom_components/qnap/sensor.py | 35 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 0ec9e3c..8a43fff 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -23,28 +23,27 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ( - ATTR_DRIVE, - ATTR_IP, - ATTR_MAC, - ATTR_MASK, - ATTR_MAX_SPEED, - ATTR_MEMORY_SIZE, - ATTR_MODEL, - ATTR_PACKETS_ERR, - ATTR_PACKETS_RX, - ATTR_PACKETS_TX, - ATTR_SERIAL, - ATTR_TYPE, - ATTR_UPTIME, - ATTR_VOLUME_SIZE, - DEFAULT_NAME, - DOMAIN, -) +from .const import DEFAULT_NAME, DOMAIN from .coordinator import QnapCoordinator _LOGGER = logging.getLogger(__name__) +ATTR_DRIVE = "Drive" +ATTR_ENABLED = "Sensor Enabled" +ATTR_IP = "IP Address" +ATTR_MAC = "MAC Address" +ATTR_MASK = "Mask" +ATTR_MAX_SPEED = "Max Speed" +ATTR_MEMORY_SIZE = "Memory Size" +ATTR_MODEL = "Model" +ATTR_PACKETS_TX = "Packets (TX)" +ATTR_PACKETS_RX = "Packets (RX)" +ATTR_PACKETS_ERR = "Packets (Err)" +ATTR_SERIAL = "Serial #" +ATTR_TYPE = "Type" +ATTR_UPTIME = "Uptime" +ATTR_VOLUME_SIZE = "Volume Size" + async def async_setup_entry( hass: HomeAssistant, From f32f9ff2928d3984770340192837e7c72c487919 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 14 Jun 2023 00:02:26 -0400 Subject: [PATCH 097/132] Update const.py --- custom_components/qnap/const.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 6cbd62e..420580b 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -1,22 +1,6 @@ """The Qnap constants.""" from __future__ import annotations -ATTR_DRIVE = "Drive" -ATTR_ENABLED = "Sensor Enabled" -ATTR_IP = "IP Address" -ATTR_MAC = "MAC Address" -ATTR_MASK = "Mask" -ATTR_MAX_SPEED = "Max Speed" -ATTR_MEMORY_SIZE = "Memory Size" -ATTR_MODEL = "Model" -ATTR_PACKETS_TX = "Packets (TX)" -ATTR_PACKETS_RX = "Packets (RX)" -ATTR_PACKETS_ERR = "Packets (Err)" -ATTR_SERIAL = "Serial #" -ATTR_TYPE = "Type" -ATTR_UPTIME = "Uptime" -ATTR_VOLUME_SIZE = "Volume Size" - DEFAULT_NAME = "QNAP" DEFAULT_PORT = 8080 DEFAULT_TIMEOUT = 5 From c5e6558d6e9cd5158b80ff4cb024aeb4fd033cb7 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 14 Jun 2023 16:41:23 -0400 Subject: [PATCH 098/132] Update sensor.py --- custom_components/qnap/sensor.py | 82 +++++++++++++------------------- 1 file changed, 32 insertions(+), 50 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 8a43fff..9035e91 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -59,15 +59,15 @@ async def async_setup_entry( sensors: list[QNAPSensor] = [] sensors.extend( - [QNAPSystemSensor(coordinator, description, uid) for description in BAS_SENSOR] + [QNAPSystemSensor(coordinator, description, uid) for description in _SYSTEM_SENSORS] ) sensors.extend( - [QNAPCPUSensor(coordinator, description, uid) for description in CPU_SENSOR] + [QNAPCPUSensor(coordinator, description, uid) for description in _CPU_SENSORS] ) sensors.extend( - [QNAPMemorySensor(coordinator, description, uid) for description in MEM_SENSOR] + [QNAPMemorySensor(coordinator, description, uid) for description in _MEMORY_SENSORS] ) # Network sensors @@ -75,7 +75,7 @@ async def async_setup_entry( [ QNAPNetworkSensor(coordinator, description, uid, nic) for nic in coordinator.data["system_stats"]["nics"] - for description in NET_SENSOR + for description in _NETWORK_SENSORS ] ) @@ -84,7 +84,7 @@ async def async_setup_entry( [ QNAPDriveSensor(coordinator, description, uid, drive) for drive in coordinator.data["smart_drive_health"] - for description in DRI_SENSOR + for description in _DRIVE_SENSORS ] ) @@ -93,7 +93,7 @@ async def async_setup_entry( [ QNAPVolumeSensor(coordinator, description, uid, volume) for volume in coordinator.data["volumes"] - for description in VOL_SENSOR + for description in _VOLUME_SENSORS ] ) async_add_entities(sensors) @@ -109,22 +109,13 @@ def round_nicely(number): return round(number) -@dataclass -class QNapSensorEntityDescription(SensorEntityDescription): - """Represents an Flow Sensor.""" - - stype: str | None = None - - -SENSOR_TYPES: tuple[QNapSensorEntityDescription, ...] = ( - QNapSensorEntityDescription( - stype="basic", +_SYSTEM_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="status", name="Status", icon="mdi:checkbox-marked-circle-outline", ), - QNapSensorEntityDescription( - stype="basic", + SensorEntityDescription( key="system_temp", name="System Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, @@ -132,8 +123,9 @@ class QNapSensorEntityDescription(SensorEntityDescription): icon="mdi:thermometer", state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="cpu", +) +_CPU_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="cpu_temp", name="CPU Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, @@ -142,16 +134,16 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="cpu", + SensorEntityDescription( key="cpu_usage", name="CPU Usage", native_unit_of_measurement=PERCENTAGE, icon="mdi:chip", state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="memory", +) +_MEMORY_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="memory_free", name="Memory Available", native_unit_of_measurement=UnitOfInformation.GIBIBYTES, @@ -159,8 +151,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="memory", + SensorEntityDescription( key="memory_used", name="Memory Used", native_unit_of_measurement=UnitOfInformation.GIBIBYTES, @@ -168,22 +159,21 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="memory", + SensorEntityDescription( key="memory_percent_used", name="Memory Usage", native_unit_of_measurement=PERCENTAGE, icon="mdi:memory", state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="network", +) +_NETWORK_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="network_link_status", name="Network Link", icon="mdi:checkbox-marked-circle-outline", ), - QNapSensorEntityDescription( - stype="network", + SensorEntityDescription( key="network_tx", name="Network Up", native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, @@ -191,8 +181,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="network", + SensorEntityDescription( key="network_rx", name="Network Down", native_unit_of_measurement=UnitOfDataRate.MEBIBYTES_PER_SECOND, @@ -200,15 +189,15 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="drive", +) +_DRIVE_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="drive_smart_status", name="SMART Status", icon="mdi:checkbox-marked-circle-outline", entity_registry_enabled_default=False, ), - QNapSensorEntityDescription( - stype="drive", + SensorEntityDescription( key="drive_temp", name="Temperature", native_unit_of_measurement=UnitOfTemperature.CELSIUS, @@ -216,8 +205,9 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="volume", +) +_VOLUME_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( key="volume_size_used", name="Used Space", native_unit_of_measurement=UnitOfInformation.GIBIBYTES, @@ -225,8 +215,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="volume", + SensorEntityDescription( key="volume_size_free", name="Free Space", native_unit_of_measurement=UnitOfInformation.GIBIBYTES, @@ -234,8 +223,7 @@ class QNapSensorEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, state_class=SensorStateClass.MEASUREMENT, ), - QNapSensorEntityDescription( - stype="volume", + SensorEntityDescription( key="volume_percentage_used", name="Volume Used", native_unit_of_measurement=PERCENTAGE, @@ -243,12 +231,6 @@ class QNapSensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, ), ) -BAS_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "basic"] -CPU_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "cpu"] -MEM_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "memory"] -NET_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "network"] -DRI_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "drive"] -VOL_SENSOR = [desc for desc in SENSOR_TYPES if desc.stype == "volume"] class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): From 1c1c7f615fa68555ce91e0a8cdfec2d219be9848 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 14 Jun 2023 16:55:17 -0400 Subject: [PATCH 099/132] Update sensor.py --- custom_components/qnap/sensor.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 9035e91..e433f97 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -1,7 +1,6 @@ """Support for QNAP NAS Sensors.""" from __future__ import annotations -from dataclasses import dataclass import logging from homeassistant import config_entries @@ -59,7 +58,7 @@ async def async_setup_entry( sensors: list[QNAPSensor] = [] sensors.extend( - [QNAPSystemSensor(coordinator, description, uid) for description in _SYSTEM_SENSORS] + [QNAPSystemSensor(coordinator, description, uid) for description in _SYS_SENSORS] ) sensors.extend( @@ -67,7 +66,7 @@ async def async_setup_entry( ) sensors.extend( - [QNAPMemorySensor(coordinator, description, uid) for description in _MEMORY_SENSORS] + [QNAPMemorySensor(coordinator, description, uid) for description in _MEM_SENSORS] ) # Network sensors @@ -75,7 +74,7 @@ async def async_setup_entry( [ QNAPNetworkSensor(coordinator, description, uid, nic) for nic in coordinator.data["system_stats"]["nics"] - for description in _NETWORK_SENSORS + for description in _NET_SENSORS ] ) @@ -84,7 +83,7 @@ async def async_setup_entry( [ QNAPDriveSensor(coordinator, description, uid, drive) for drive in coordinator.data["smart_drive_health"] - for description in _DRIVE_SENSORS + for description in _DRI_SENSORS ] ) @@ -93,7 +92,7 @@ async def async_setup_entry( [ QNAPVolumeSensor(coordinator, description, uid, volume) for volume in coordinator.data["volumes"] - for description in _VOLUME_SENSORS + for description in _VOL_SENSORS ] ) async_add_entities(sensors) @@ -109,7 +108,7 @@ def round_nicely(number): return round(number) -_SYSTEM_SENSORS: tuple[SensorEntityDescription, ...] = ( +_SYS_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="status", name="Status", @@ -142,7 +141,7 @@ def round_nicely(number): state_class=SensorStateClass.MEASUREMENT, ), ) -_MEMORY_SENSORS: tuple[SensorEntityDescription, ...] = ( +_MEM_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="memory_free", name="Memory Available", @@ -167,7 +166,7 @@ def round_nicely(number): state_class=SensorStateClass.MEASUREMENT, ), ) -_NETWORK_SENSORS: tuple[SensorEntityDescription, ...] = ( +_NET_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="network_link_status", name="Network Link", @@ -190,7 +189,7 @@ def round_nicely(number): state_class=SensorStateClass.MEASUREMENT, ), ) -_DRIVE_SENSORS: tuple[SensorEntityDescription, ...] = ( +_DRI_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="drive_smart_status", name="SMART Status", @@ -206,7 +205,7 @@ def round_nicely(number): state_class=SensorStateClass.MEASUREMENT, ), ) -_VOLUME_SENSORS: tuple[SensorEntityDescription, ...] = ( +_VOL_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="volume_size_used", name="Used Space", From 0b757a5c43a00ae9ce174720ae4e83e646f1d3dd Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 14 Jun 2023 18:16:45 -0400 Subject: [PATCH 100/132] Update sensor.py --- custom_components/qnap/sensor.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index e433f97..425567c 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -58,7 +58,10 @@ async def async_setup_entry( sensors: list[QNAPSensor] = [] sensors.extend( - [QNAPSystemSensor(coordinator, description, uid) for description in _SYS_SENSORS] + [ + QNAPSystemSensor(coordinator, description, uid) + for description in _SYS_SENSORS + ] ) sensors.extend( @@ -66,7 +69,10 @@ async def async_setup_entry( ) sensors.extend( - [QNAPMemorySensor(coordinator, description, uid) for description in _MEM_SENSORS] + [ + QNAPMemorySensor(coordinator, description, uid) + for description in _MEM_SENSORS + ] ) # Network sensors From 50ded89342bc2ca59f6209e727aea0a4095091eb Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 14 Jun 2023 22:48:47 -0400 Subject: [PATCH 101/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index 5dfdcb0..c30d2a2 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) -class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]): +class QnapCoordinator(DataUpdateCoordinator[None]): """Custom coordinator for the qnap integration.""" def __init__(self, hass: HomeAssistant, config: ConfigType) -> None: From 4cea1ff46fc5f2feb9aa22d022501d322a0681f2 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 07:54:01 -0400 Subject: [PATCH 102/132] Update __init__.py --- custom_components/qnap/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index e1e90c3..2437e51 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -20,8 +20,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b coordinator = QnapCoordinator(hass, config_entry) # Fetch initial data so we have data when entities subscribe await coordinator.async_config_entry_first_refresh() - if not coordinator.last_update_success: - raise PlatformNotReady hass.data[DOMAIN][config_entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) return True From 9d21a48176d99a16fcde7279ca5354c96e439138 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 07:54:41 -0400 Subject: [PATCH 103/132] Update const.py --- custom_components/qnap/const.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/qnap/const.py b/custom_components/qnap/const.py index 420580b..babba8e 100644 --- a/custom_components/qnap/const.py +++ b/custom_components/qnap/const.py @@ -1,5 +1,4 @@ """The Qnap constants.""" -from __future__ import annotations DEFAULT_NAME = "QNAP" DEFAULT_PORT = 8080 From 4fe103e13520f0387130083adc00c39c0fdabb48 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 07:58:54 -0400 Subject: [PATCH 104/132] Update __init__.py --- custom_components/qnap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 2437e51..7d7cf26 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -25,7 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b return True -async def async_unload_entry(hass, config_entry): + 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 From 887cf9449f1a6d7c35b557cc4bd25a11482da242 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 08:03:56 -0400 Subject: [PATCH 105/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index c30d2a2..8c0311b 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -27,21 +27,21 @@ _LOGGER = logging.getLogger(__name__) -class QnapCoordinator(DataUpdateCoordinator[None]): +class QnapCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]): """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.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) - protocol = "https" if config.data[CONF_SSL] else "http" + protocol = "https" if config_entry.data[CONF_SSL] else "http" self._api = QNAPStats( - f"{protocol}://{config.data.get(CONF_HOST)}", - config.data.get(CONF_PORT), - config.data.get(CONF_USERNAME), - config.data.get(CONF_PASSWORD), - verify_ssl=config.data.get(CONF_VERIFY_SSL), - timeout=config.data.get(CONF_TIMEOUT), + f"{protocol}://{config_entry.data.get(CONF_HOST)}", + config_entry.data.get(CONF_PORT), + config_entry.data.get(CONF_USERNAME), + config_entry.data.get(CONF_PASSWORD), + verify_ssl=config_entry.data.get(CONF_VERIFY_SSL), + timeout=config_entry.data.get(CONF_TIMEOUT), ) def _sync_update(self) -> dict[str, dict[str, Any]]: From f7d2e0dfad15b4970463537f1c2abe1e219fe497 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 08:06:45 -0400 Subject: [PATCH 106/132] Update coordinator.py --- custom_components/qnap/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/coordinator.py b/custom_components/qnap/coordinator.py index 8c0311b..8738ed6 100644 --- a/custom_components/qnap/coordinator.py +++ b/custom_components/qnap/coordinator.py @@ -16,8 +16,8 @@ CONF_USERNAME, CONF_VERIFY_SSL, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN From 3a9579b000b618328423d3d7b80124a7350f2945 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 08:40:26 -0400 Subject: [PATCH 107/132] Update sensor.py --- custom_components/qnap/sensor.py | 58 ++++++++++++++++---------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 425567c..8c47bec 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -326,35 +326,6 @@ def extra_state_attributes(self): return {ATTR_MEMORY_SIZE: f"{size} {UnitOfInformation.GIBIBYTES}"} -class QNAPSystemSensor(QNAPSensor): - """A QNAP sensor that monitors overall system health.""" - - @property - def native_value(self): - """Return the state of the sensor.""" - if self.entity_description.key == "status": - return self.coordinator.data["system_health"] - - if self.entity_description.key == "system_temp": - return int(self.coordinator.data["system_stats"]["system"]["temp_c"]) - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - 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"]) - - return { - ATTR_NAME: data["system"]["name"], - ATTR_MODEL: data["system"]["model"], - ATTR_SERIAL: data["system"]["serial_number"], - ATTR_UPTIME: f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m", - } - - class QNAPNetworkSensor(QNAPSensor): """A QNAP sensor that monitors network stats.""" @@ -388,6 +359,35 @@ def extra_state_attributes(self): } +class QNAPSystemSensor(QNAPSensor): + """A QNAP sensor that monitors overall system health.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + if self.entity_description.key == "status": + return self.coordinator.data["system_health"] + + if self.entity_description.key == "system_temp": + return int(self.coordinator.data["system_stats"]["system"]["temp_c"]) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + 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"]) + + return { + ATTR_NAME: data["system"]["name"], + ATTR_MODEL: data["system"]["model"], + ATTR_SERIAL: data["system"]["serial_number"], + ATTR_UPTIME: f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m", + } + + class QNAPDriveSensor(QNAPSensor): """A QNAP sensor that monitors HDD/SSD drive stats.""" From 6da23c8bf7ab49f4516fe28ccbb8d67ac2b6e60e Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 08:43:22 -0400 Subject: [PATCH 108/132] Update sensor.py --- custom_components/qnap/sensor.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 8c47bec..af8932f 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -262,11 +262,6 @@ def unique_id(self): """Return unique_id.""" return f"{self.uid}_{self.name}" - @property - def coordinator_context(self): - """Helpers update_coordinator.""" - return None - @property def name(self): """Return the name of the sensor, if any.""" From c557f127b995e119eef189bece4411259eacf109 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 08:58:52 -0400 Subject: [PATCH 109/132] Update sensor.py --- custom_components/qnap/sensor.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index af8932f..8c5cbc9 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -256,6 +256,13 @@ def __init__( self.device_name = self.coordinator.data["system_stats"]["system"]["name"] self.monitor_device = monitor_device self.monitor_subdevice = monitor_subdevice + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.uid)}, + name=self.device_name, + model: self.coordinator.data["system_stats"]["system"]["model"], + sw_version: self.coordinator.data["system_stats"]["firmware"]["version"], + manufacturer: DEFAULT_NAME, + ) @property def unique_id(self): @@ -275,9 +282,7 @@ def device_info(self): return { "identifiers": {(DOMAIN, self.uid)}, "name": self.device_name, - "model": self.coordinator.data["system_stats"]["system"]["model"], - "sw_version": self.coordinator.data["system_stats"]["firmware"]["version"], - "manufacturer": DEFAULT_NAME, + } From 5e7bd87e0fb183aaf3e289b76d971d4dc32b4f33 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 09:02:59 -0400 Subject: [PATCH 110/132] Update sensor.py --- custom_components/qnap/sensor.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 8c5cbc9..12419fe 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -276,15 +276,6 @@ def name(self): return f"{self.device_name} {self.entity_description.name} ({self.monitor_device})" return f"{self.device_name} {self.entity_description.name}" - @property - def device_info(self): - """Return device information.""" - return { - "identifiers": {(DOMAIN, self.uid)}, - "name": self.device_name, - - } - class QNAPCPUSensor(QNAPSensor): """A QNAP sensor that monitors CPU stats.""" From fba7fde4bc0684921300ed96f873899abbf4eb8b Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 09:27:55 -0400 Subject: [PATCH 111/132] Update __init__.py --- custom_components/qnap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/qnap/__init__.py b/custom_components/qnap/__init__.py index 7d7cf26..14dab8c 100644 --- a/custom_components/qnap/__init__.py +++ b/custom_components/qnap/__init__.py @@ -25,7 +25,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b return True - async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: +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 From 1ac0ed5de7e6399441eb782c1f585f35e3db5088 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 09:32:45 -0400 Subject: [PATCH 112/132] Update sensor.py --- custom_components/qnap/sensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 12419fe..38a1618 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -19,6 +19,7 @@ ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -259,9 +260,9 @@ def __init__( self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self.uid)}, name=self.device_name, - model: self.coordinator.data["system_stats"]["system"]["model"], - sw_version: self.coordinator.data["system_stats"]["firmware"]["version"], - manufacturer: DEFAULT_NAME, + model=self.coordinator.data["system_stats"]["system"]["model"], + sw_version=self.coordinator.data["system_stats"]["firmware"]["version"], + manufacturer=DEFAULT_NAME, ) @property From 62aa766c80a505621fafe04a393fb05d35bc95c0 Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 09:42:15 -0400 Subject: [PATCH 113/132] Update sensor.py --- custom_components/qnap/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 38a1618..82d1991 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -257,6 +257,7 @@ def __init__( self.device_name = self.coordinator.data["system_stats"]["system"]["name"] self.monitor_device = monitor_device self.monitor_subdevice = monitor_subdevice + self.coordinator_context=None self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self.uid)}, name=self.device_name, From 23920430fd0fb1586a1534f03310c9e060807c4e Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 15:57:05 -0400 Subject: [PATCH 114/132] Update sensor.py --- custom_components/qnap/sensor.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 82d1991..68f49eb 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -251,6 +251,7 @@ def __init__( monitor_subdevice=None, ) -> None: """Initialize the sensor.""" + super().__init__(coordinator) self.coordinator = coordinator self.entity_description = description self.uid = uid @@ -258,6 +259,7 @@ def __init__( self.monitor_device = monitor_device self.monitor_subdevice = monitor_subdevice self.coordinator_context=None + self._attr_unique_id = f"{self.uid}_{self.device_name}_{self.name}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, self.uid)}, name=self.device_name, @@ -266,11 +268,6 @@ def __init__( manufacturer=DEFAULT_NAME, ) - @property - def unique_id(self): - """Return unique_id.""" - return f"{self.uid}_{self.name}" - @property def name(self): """Return the name of the sensor, if any.""" From e8a2c3651b3ae1fd30c2471615af5a97b714124c Mon Sep 17 00:00:00 2001 From: disforw Date: Thu, 15 Jun 2023 19:59:22 -0400 Subject: [PATCH 115/132] Update sensor.py --- custom_components/qnap/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 68f49eb..3dad6a9 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -247,8 +247,8 @@ def __init__( coordinator: QnapCoordinator, description, uid, - monitor_device=None, - monitor_subdevice=None, + monitor_device: str = None, + monitor_subdevice: str = None, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) From 6c5cb480382dd9a5a90b75200e555ea147c93f52 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 16 Jun 2023 08:14:33 -0400 Subject: [PATCH 116/132] Type hints --- custom_components/qnap/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index 3dad6a9..eea4087 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -247,8 +247,8 @@ def __init__( coordinator: QnapCoordinator, description, uid, - monitor_device: str = None, - monitor_subdevice: str = None, + monitor_device: str | None = None, + monitor_subdevice: str | None = None, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) From a834cf5e60fda9067b1d062fb64fc816217a39f5 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 16 Jun 2023 08:16:31 -0400 Subject: [PATCH 117/132] Move sensor description --- custom_components/qnap/sensor.py | 140 +++++++++++++++---------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/custom_components/qnap/sensor.py b/custom_components/qnap/sensor.py index eea4087..1bbd029 100644 --- a/custom_components/qnap/sensor.py +++ b/custom_components/qnap/sensor.py @@ -45,76 +45,6 @@ ATTR_VOLUME_SIZE = "Volume Size" -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() - if not coordinator.last_update_success: - raise PlatformNotReady - uid = config_entry.unique_id - sensors: list[QNAPSensor] = [] - - sensors.extend( - [ - QNAPSystemSensor(coordinator, description, uid) - for description in _SYS_SENSORS - ] - ) - - sensors.extend( - [QNAPCPUSensor(coordinator, description, uid) for description in _CPU_SENSORS] - ) - - sensors.extend( - [ - QNAPMemorySensor(coordinator, description, uid) - for description in _MEM_SENSORS - ] - ) - - # Network sensors - sensors.extend( - [ - QNAPNetworkSensor(coordinator, description, uid, nic) - for nic in coordinator.data["system_stats"]["nics"] - for description in _NET_SENSORS - ] - ) - - # Drive sensors - sensors.extend( - [ - QNAPDriveSensor(coordinator, description, uid, drive) - for drive in coordinator.data["smart_drive_health"] - for description in _DRI_SENSORS - ] - ) - - # Volume sensors - sensors.extend( - [ - QNAPVolumeSensor(coordinator, description, uid, volume) - for volume in coordinator.data["volumes"] - for description in _VOL_SENSORS - ] - ) - async_add_entities(sensors) - - -def round_nicely(number): - """Round a number based on its size (so it looks nice).""" - if number < 10: - return round(number, 2) - if number < 100: - return round(number, 1) - - return round(number) - - _SYS_SENSORS: tuple[SensorEntityDescription, ...] = ( SensorEntityDescription( key="status", @@ -239,6 +169,76 @@ def round_nicely(number): ) +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() + if not coordinator.last_update_success: + raise PlatformNotReady + uid = config_entry.unique_id + sensors: list[QNAPSensor] = [] + + sensors.extend( + [ + QNAPSystemSensor(coordinator, description, uid) + for description in _SYS_SENSORS + ] + ) + + sensors.extend( + [QNAPCPUSensor(coordinator, description, uid) for description in _CPU_SENSORS] + ) + + sensors.extend( + [ + QNAPMemorySensor(coordinator, description, uid) + for description in _MEM_SENSORS + ] + ) + + # Network sensors + sensors.extend( + [ + QNAPNetworkSensor(coordinator, description, uid, nic) + for nic in coordinator.data["system_stats"]["nics"] + for description in _NET_SENSORS + ] + ) + + # Drive sensors + sensors.extend( + [ + QNAPDriveSensor(coordinator, description, uid, drive) + for drive in coordinator.data["smart_drive_health"] + for description in _DRI_SENSORS + ] + ) + + # Volume sensors + sensors.extend( + [ + QNAPVolumeSensor(coordinator, description, uid, volume) + for volume in coordinator.data["volumes"] + for description in _VOL_SENSORS + ] + ) + async_add_entities(sensors) + + +def round_nicely(number): + """Round a number based on its size (so it looks nice).""" + if number < 10: + return round(number, 2) + if number < 100: + return round(number, 1) + + return round(number) + + class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): """Base class for a QNAP sensor.""" From 2914ec25ee6ff4f5f8af403406a3cbf6ff60c770 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 16 Jun 2023 18:34:17 -0400 Subject: [PATCH 118/132] Delete black.yaml --- .github/workflows/black.yaml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .github/workflows/black.yaml diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml deleted file mode 100644 index 15fadcb..0000000 --- a/.github/workflows/black.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: Black -on: [ push, pull_request ] -jobs: - ruff: - runs-on: ubuntu-latest - steps: - - name: Python Black - uses: cytopia/docker-black@0.11 From e0a64bba0a32bf25050327da236e69ccc5c2f26f Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 16 Jun 2023 18:34:23 -0400 Subject: [PATCH 119/132] Delete pylint.yaml --- .github/workflows/pylint.yaml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .github/workflows/pylint.yaml diff --git a/.github/workflows/pylint.yaml b/.github/workflows/pylint.yaml deleted file mode 100644 index 8129a6c..0000000 --- a/.github/workflows/pylint.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: Pylint -on: [ push, pull_request ] -jobs: - ruff: - runs-on: ubuntu-latest - steps: - - name: Pylint action - uses: gabriel-milan/action-pylint@v1 From 4ecac16a0032cbea7e5550416f4e4602172a7a78 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 16 Jun 2023 18:34:30 -0400 Subject: [PATCH 120/132] Delete ruff.yaml --- .github/workflows/ruff.yaml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .github/workflows/ruff.yaml diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml deleted file mode 100644 index bbaef24..0000000 --- a/.github/workflows/ruff.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: Ruff -on: [ push, pull_request ] -jobs: - ruff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: chartboost/ruff-action@v1 - - From f6ddc58e24aa4c9c505b174266bcb6bfda360b6f Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 23 Jun 2023 21:44:24 -0400 Subject: [PATCH 121/132] Create hassfest.yml --- .github/workflows/hassfest.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/hassfest.yml diff --git a/.github/workflows/hassfest.yml b/.github/workflows/hassfest.yml new file mode 100644 index 0000000..13fda23 --- /dev/null +++ b/.github/workflows/hassfest.yml @@ -0,0 +1,23 @@ +name: Validate with hassfest + +on: + + push: + + pull_request: + + schedule: + + - cron: '0 0 * * *' + +jobs: + + validate: + + runs-on: "ubuntu-latest" + + steps: + + - uses: "actions/checkout@v3" + + - uses: "home-assistant/actions/hassfest@master" From 63dd566ccc8651679f78bbf4aefbc6c7847c7f1b Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 23 Jun 2023 21:46:51 -0400 Subject: [PATCH 122/132] Create hacs.yml --- .github/workflows/hacs.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/hacs.yml diff --git a/.github/workflows/hacs.yml b/.github/workflows/hacs.yml new file mode 100644 index 0000000..290a73b --- /dev/null +++ b/.github/workflows/hacs.yml @@ -0,0 +1,29 @@ +name: HACS Action + +on: + + push: + + pull_request: + + schedule: + + - cron: "0 0 * * *" + +jobs: + + hacs: + + name: HACS Action + + runs-on: "ubuntu-latest" + + steps: + + - name: HACS Action + + uses: "hacs/action@main" + + with: + + category: "integration" From 8b929eba57c3151de1a0400598dd733959a0b5ab Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 23 Jun 2023 23:22:22 -0400 Subject: [PATCH 123/132] Update hacs.json --- hacs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/hacs.json b/hacs.json index 73993c7..8c5600c 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,5 @@ { "name": "Qnap", - "domains": ["sensor"], "homeassistant": "2021.5", "render_readme": true, "iot_class": "Local Push" From de664738b771c065ef544ef1e6f8e2b1bd1ff44b Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 23 Jun 2023 23:31:24 -0400 Subject: [PATCH 124/132] Update manifest.json --- custom_components/qnap/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/qnap/manifest.json b/custom_components/qnap/manifest.json index 25be849..24fabc2 100644 --- a/custom_components/qnap/manifest.json +++ b/custom_components/qnap/manifest.json @@ -6,6 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/qnap", "integration_type": "device", "iot_class": "local_polling", + "issue_tracker": "https://github.com/disforw/inverse_switch/issues", "requirements": ["qnapstats==0.4.0"], "version": "10.0" } From 600e6be1dfeccad78ff176fe788f3e429d448e46 Mon Sep 17 00:00:00 2001 From: disforw Date: Fri, 23 Jun 2023 23:34:41 -0400 Subject: [PATCH 125/132] Update hacs.json --- hacs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/hacs.json b/hacs.json index 8c5600c..21c444f 100644 --- a/hacs.json +++ b/hacs.json @@ -2,5 +2,4 @@ "name": "Qnap", "homeassistant": "2021.5", "render_readme": true, - "iot_class": "Local Push" } From 8c0392cbb4e14028c884d17f58c8c3ea037a6b70 Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 24 Jun 2023 19:35:05 -0400 Subject: [PATCH 126/132] Create tests --- tests | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests diff --git a/tests b/tests new file mode 100644 index 0000000..359aad0 --- /dev/null +++ b/tests @@ -0,0 +1,115 @@ +"""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_success(hass: HomeAssistant) -> None: + """Successful 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"] == {} + + 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_errors(hass: HomeAssistant, qnap_connect: MagicMock) -> None: + """Flow manually initialized by the user after some errors.""" + 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"} + + +async def test_config_flow_import(hass: HomeAssistant) -> None: + """Test import of YAML config.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=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, + } From 9c12581f68b8698e0ba2267849b7a25815439c6f Mon Sep 17 00:00:00 2001 From: disforw Date: Sat, 24 Jun 2023 19:35:56 -0400 Subject: [PATCH 127/132] Rename tests to testss/test_config_flow.py --- tests => testss/test_config_flow.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests => testss/test_config_flow.py (100%) diff --git a/tests b/testss/test_config_flow.py similarity index 100% rename from tests rename to testss/test_config_flow.py From c982ec8a6dbb87f52bb761b8128dc7cab1a2c080 Mon Sep 17 00:00:00 2001 From: disforw Date: Sun, 25 Jun 2023 12:35:50 -0400 Subject: [PATCH 128/132] Delete hacs.yml --- .github/workflows/hacs.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/hacs.yml diff --git a/.github/workflows/hacs.yml b/.github/workflows/hacs.yml deleted file mode 100644 index 290a73b..0000000 --- a/.github/workflows/hacs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: HACS Action - -on: - - push: - - pull_request: - - schedule: - - - cron: "0 0 * * *" - -jobs: - - hacs: - - name: HACS Action - - runs-on: "ubuntu-latest" - - steps: - - - name: HACS Action - - uses: "hacs/action@main" - - with: - - category: "integration" From c0799fbff8ca940ba4a8ba6a5a59d4c3c8f85c79 Mon Sep 17 00:00:00 2001 From: disforw Date: Tue, 27 Jun 2023 07:46:06 -0400 Subject: [PATCH 129/132] Delete test_config_flow.py --- testss/test_config_flow.py | 115 ------------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 testss/test_config_flow.py diff --git a/testss/test_config_flow.py b/testss/test_config_flow.py deleted file mode 100644 index 359aad0..0000000 --- a/testss/test_config_flow.py +++ /dev/null @@ -1,115 +0,0 @@ -"""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_success(hass: HomeAssistant) -> None: - """Successful 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"] == {} - - 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_errors(hass: HomeAssistant, qnap_connect: MagicMock) -> None: - """Flow manually initialized by the user after some errors.""" - 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"} - - -async def test_config_flow_import(hass: HomeAssistant) -> None: - """Test import of YAML config.""" - result = await hass.config_entries.flow.async_init( - const.DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=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, - } From 0c56eb5186354a36db944801291e74aa5b384873 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 28 Jun 2023 17:06:06 -0400 Subject: [PATCH 130/132] Create time.py --- custom_components/qnap/time.py | 184 +++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 custom_components/qnap/time.py diff --git a/custom_components/qnap/time.py b/custom_components/qnap/time.py new file mode 100644 index 0000000..14bfaa6 --- /dev/null +++ b/custom_components/qnap/time.py @@ -0,0 +1,184 @@ +"""Support for QNAP NAS Sensors.""" +from __future__ import annotations + +import logging + +from homeassistant import config_entries +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import ( + ATTR_NAME, + PERCENTAGE, + UnitOfDataRate, + UnitOfInformation, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DEFAULT_NAME, DOMAIN +from .coordinator import QnapCoordinator + +_LOGGER = logging.getLogger(__name__) + + +_SYS_SENSORS: tuple[SensorEntityDescription, ...] = ( + SensorEntityDescription( + key="status", + name="Status", + icon="mdi:checkbox-marked-circle-outline", + ), + SensorEntityDescription( + key="system_temp", + name="System Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + icon="mdi:thermometer", + state_class=SensorStateClass.MEASUREMENT, + ), +) + + +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() + if not coordinator.last_update_success: + raise PlatformNotReady + uid = config_entry.unique_id + sensors: list[QNAPSensor] = [] + + sensors.extend( + [ + QNAPSystemSensor(coordinator, description, uid) + for description in _SYS_SENSORS + ] + ) + + sensors.extend( + [QNAPCPUSensor(coordinator, description, uid) for description in _CPU_SENSORS] + ) + + sensors.extend( + [ + QNAPMemorySensor(coordinator, description, uid) + for description in _MEM_SENSORS + ] + ) + + # Network sensors + sensors.extend( + [ + QNAPNetworkSensor(coordinator, description, uid, nic) + for nic in coordinator.data["system_stats"]["nics"] + for description in _NET_SENSORS + ] + ) + + # Drive sensors + sensors.extend( + [ + QNAPDriveSensor(coordinator, description, uid, drive) + for drive in coordinator.data["smart_drive_health"] + for description in _DRI_SENSORS + ] + ) + + # Volume sensors + sensors.extend( + [ + QNAPVolumeSensor(coordinator, description, uid, volume) + for volume in coordinator.data["volumes"] + for description in _VOL_SENSORS + ] + ) + async_add_entities(sensors) + + +def round_nicely(number): + """Round a number based on its size (so it looks nice).""" + if number < 10: + return round(number, 2) + if number < 100: + return round(number, 1) + + return round(number) + + +class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): + """Base class for a QNAP sensor.""" + + def __init__( + self, + coordinator: QnapCoordinator, + description, + uid, + monitor_device: str | None = None, + monitor_subdevice: str | None = None, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + self.coordinator = coordinator + self.entity_description = description + self.uid = uid + self.device_name = self.coordinator.data["system_stats"]["system"]["name"] + self.monitor_device = monitor_device + self.monitor_subdevice = monitor_subdevice + self.coordinator_context=None + self._attr_unique_id = f"{self.uid}_{self.device_name}_{self.name}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.uid)}, + name=self.device_name, + model=self.coordinator.data["system_stats"]["system"]["model"], + sw_version=self.coordinator.data["system_stats"]["firmware"]["version"], + manufacturer=DEFAULT_NAME, + ) + + @property + def name(self): + """Return the name of the sensor, if any.""" + if self.monitor_device is not None: + return f"{self.device_name} {self.entity_description.name} ({self.monitor_device})" + return f"{self.device_name} {self.entity_description.name}" + + +class QNAPSystemTime(QNAPSensor): + """A QNAP sensor that monitors overall system health.""" + + @property + def native_value(self): + """Return the state of the sensor.""" + if self.entity_description.key == "status": + return self.coordinator.data["system_health"] + + if self.entity_description.key == "system_temp": + return int(self.coordinator.data["system_stats"]["system"]["temp_c"]) + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + 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"]) + + return { + ATTR_NAME: data["system"]["name"], + ATTR_MODEL: data["system"]["model"], + ATTR_SERIAL: data["system"]["serial_number"], + ATTR_UPTIME: f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m", + } + + From 3167c0f7b95018eaf8138b2da7835ee020992bd8 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 28 Jun 2023 17:35:07 -0400 Subject: [PATCH 131/132] Update time.py --- custom_components/qnap/time.py | 63 +--------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/custom_components/qnap/time.py b/custom_components/qnap/time.py index 14bfaa6..f7b848d 100644 --- a/custom_components/qnap/time.py +++ b/custom_components/qnap/time.py @@ -65,56 +65,9 @@ async def async_setup_entry( for description in _SYS_SENSORS ] ) - - sensors.extend( - [QNAPCPUSensor(coordinator, description, uid) for description in _CPU_SENSORS] - ) - - sensors.extend( - [ - QNAPMemorySensor(coordinator, description, uid) - for description in _MEM_SENSORS - ] - ) - - # Network sensors - sensors.extend( - [ - QNAPNetworkSensor(coordinator, description, uid, nic) - for nic in coordinator.data["system_stats"]["nics"] - for description in _NET_SENSORS - ] - ) - - # Drive sensors - sensors.extend( - [ - QNAPDriveSensor(coordinator, description, uid, drive) - for drive in coordinator.data["smart_drive_health"] - for description in _DRI_SENSORS - ] - ) - - # Volume sensors - sensors.extend( - [ - QNAPVolumeSensor(coordinator, description, uid, volume) - for volume in coordinator.data["volumes"] - for description in _VOL_SENSORS - ] - ) async_add_entities(sensors) -def round_nicely(number): - """Round a number based on its size (so it looks nice).""" - if number < 10: - return round(number, 2) - if number < 100: - return round(number, 1) - - return round(number) - class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): """Base class for a QNAP sensor.""" @@ -159,26 +112,12 @@ class QNAPSystemTime(QNAPSensor): @property def native_value(self): """Return the state of the sensor.""" - if self.entity_description.key == "status": - return self.coordinator.data["system_health"] - - if self.entity_description.key == "system_temp": - return int(self.coordinator.data["system_stats"]["system"]["temp_c"]) - - @property - def extra_state_attributes(self): - """Return the state attributes.""" 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"]) - return { - ATTR_NAME: data["system"]["name"], - ATTR_MODEL: data["system"]["model"], - ATTR_SERIAL: data["system"]["serial_number"], - ATTR_UPTIME: f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m", - } + return f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m" From 35a19797e01cfefd2723bc9cc3841336e81dde26 Mon Sep 17 00:00:00 2001 From: disforw Date: Wed, 28 Jun 2023 18:25:09 -0400 Subject: [PATCH 132/132] Delete time.py --- custom_components/qnap/time.py | 123 --------------------------------- 1 file changed, 123 deletions(-) delete mode 100644 custom_components/qnap/time.py diff --git a/custom_components/qnap/time.py b/custom_components/qnap/time.py deleted file mode 100644 index f7b848d..0000000 --- a/custom_components/qnap/time.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Support for QNAP NAS Sensors.""" -from __future__ import annotations - -import logging - -from homeassistant import config_entries -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.const import ( - ATTR_NAME, - PERCENTAGE, - UnitOfDataRate, - UnitOfInformation, - UnitOfTemperature, -) -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import DEFAULT_NAME, DOMAIN -from .coordinator import QnapCoordinator - -_LOGGER = logging.getLogger(__name__) - - -_SYS_SENSORS: tuple[SensorEntityDescription, ...] = ( - SensorEntityDescription( - key="status", - name="Status", - icon="mdi:checkbox-marked-circle-outline", - ), - SensorEntityDescription( - key="system_temp", - name="System Temperature", - native_unit_of_measurement=UnitOfTemperature.CELSIUS, - device_class=SensorDeviceClass.TEMPERATURE, - icon="mdi:thermometer", - state_class=SensorStateClass.MEASUREMENT, - ), -) - - -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() - if not coordinator.last_update_success: - raise PlatformNotReady - uid = config_entry.unique_id - sensors: list[QNAPSensor] = [] - - sensors.extend( - [ - QNAPSystemSensor(coordinator, description, uid) - for description in _SYS_SENSORS - ] - ) - async_add_entities(sensors) - - - -class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity): - """Base class for a QNAP sensor.""" - - def __init__( - self, - coordinator: QnapCoordinator, - description, - uid, - monitor_device: str | None = None, - monitor_subdevice: str | None = None, - ) -> None: - """Initialize the sensor.""" - super().__init__(coordinator) - self.coordinator = coordinator - self.entity_description = description - self.uid = uid - self.device_name = self.coordinator.data["system_stats"]["system"]["name"] - self.monitor_device = monitor_device - self.monitor_subdevice = monitor_subdevice - self.coordinator_context=None - self._attr_unique_id = f"{self.uid}_{self.device_name}_{self.name}" - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, self.uid)}, - name=self.device_name, - model=self.coordinator.data["system_stats"]["system"]["model"], - sw_version=self.coordinator.data["system_stats"]["firmware"]["version"], - manufacturer=DEFAULT_NAME, - ) - - @property - def name(self): - """Return the name of the sensor, if any.""" - if self.monitor_device is not None: - return f"{self.device_name} {self.entity_description.name} ({self.monitor_device})" - return f"{self.device_name} {self.entity_description.name}" - - -class QNAPSystemTime(QNAPSensor): - """A QNAP sensor that monitors overall system health.""" - - @property - def native_value(self): - """Return the state of the sensor.""" - 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"]) - - return f"{days:0>2d}d {hours:0>2d}h {minutes:0>2d}m" - -