Add new QNAP QSW uptime timestamp sensor (#122589)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Álvaro Fernández Rojas 2024-10-21 16:33:41 +02:00 committed by GitHub
parent ebd1baa42c
commit 4306b0caba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 157 additions and 14 deletions

View File

@ -2,7 +2,9 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, replace from dataclasses import dataclass, replace
from datetime import datetime
from typing import Final from typing import Final
from aioqsw.const import ( from aioqsw.const import (
@ -26,8 +28,11 @@ from aioqsw.const import (
QSD_TX_OCTETS, QSD_TX_OCTETS,
QSD_TX_SPEED, QSD_TX_SPEED,
QSD_UPTIME_SECONDS, QSD_UPTIME_SECONDS,
QSD_UPTIME_TIMESTAMP,
) )
from homeassistant.components.automation import automations_with_entity
from homeassistant.components.script import scripts_with_entity
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
@ -43,8 +48,10 @@ from homeassistant.const import (
UnitOfTime, UnitOfTime,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import UNDEFINED from homeassistant.helpers.typing import UNDEFINED, StateType
from homeassistant.util import dt as dt_util
from .const import ATTR_MAX, DOMAIN, QSW_COORD_DATA, RPM from .const import ATTR_MAX, DOMAIN, QSW_COORD_DATA, RPM
from .coordinator import QswDataCoordinator from .coordinator import QswDataCoordinator
@ -58,6 +65,17 @@ class QswSensorEntityDescription(SensorEntityDescription, QswEntityDescription):
attributes: dict[str, list[str]] | None = None attributes: dict[str, list[str]] | None = None
qsw_type: QswEntityType | None = None qsw_type: QswEntityType | None = None
sep_key: str = "_" sep_key: str = "_"
value_fn: Callable[[str], datetime | StateType] = lambda value: value
DEPRECATED_UPTIME_SECONDS = QswSensorEntityDescription(
translation_key="uptime",
key=QSD_SYSTEM_TIME,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfTime.SECONDS,
state_class=SensorStateClass.TOTAL_INCREASING,
subkey=QSD_UPTIME_SECONDS,
)
SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = ( SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = (
@ -140,12 +158,12 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = (
subkey=QSD_TX_SPEED, subkey=QSD_TX_SPEED,
), ),
QswSensorEntityDescription( QswSensorEntityDescription(
translation_key="uptime", translation_key="uptime_timestamp",
key=QSD_SYSTEM_TIME, key=QSD_SYSTEM_TIME,
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfTime.SECONDS, subkey=QSD_UPTIME_TIMESTAMP,
state_class=SensorStateClass.TOTAL_INCREASING, value_fn=dt_util.parse_datetime,
subkey=QSD_UPTIME_SECONDS,
), ),
) )
@ -337,6 +355,46 @@ async def async_setup_entry(
) )
entities.append(QswSensor(coordinator, _desc, entry, port_id)) entities.append(QswSensor(coordinator, _desc, entry, port_id))
# Can be removed in HA 2025.5.0
entity_reg = er.async_get(hass)
reg_entities = er.async_entries_for_config_entry(entity_reg, entry.entry_id)
for entity in reg_entities:
if entity.domain == "sensor" and entity.unique_id.endswith(
("_uptime", "_uptime_seconds")
):
entity_id = entity.entity_id
if entity.disabled:
entity_reg.async_remove(entity_id)
continue
if (
DEPRECATED_UPTIME_SECONDS.key in coordinator.data
and DEPRECATED_UPTIME_SECONDS.subkey
in coordinator.data[DEPRECATED_UPTIME_SECONDS.key]
):
entities.append(
QswSensor(coordinator, DEPRECATED_UPTIME_SECONDS, entry)
)
entity_automations = automations_with_entity(hass, entity_id)
entity_scripts = scripts_with_entity(hass, entity_id)
for item in entity_automations + entity_scripts:
ir.async_create_issue(
hass,
DOMAIN,
f"uptime_seconds_deprecated_{entity_id}_{item}",
breaks_in_ha_version="2025.5.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="uptime_seconds_deprecated",
translation_placeholders={
"entity": entity_id,
"info": item,
},
)
async_add_entities(entities) async_add_entities(entities)
@ -374,5 +432,5 @@ class QswSensor(QswSensorEntity, SensorEntity):
self.entity_description.subkey, self.entity_description.subkey,
self.entity_description.qsw_type, self.entity_description.qsw_type,
) )
self._attr_native_value = value self._attr_native_value = self.entity_description.value_fn(value)
super()._async_update_attrs() super()._async_update_attrs()

View File

@ -52,7 +52,16 @@
}, },
"uptime": { "uptime": {
"name": "Uptime" "name": "Uptime"
} },
"uptime_timestamp": {
"name": "Uptime timestamp"
}
}
},
"issues": {
"uptime_seconds_deprecated": {
"title": "QNAP QSW uptime seconds sensor deprecated",
"description": "The QNAP QSW uptime seconds sensor entity is deprecated and will be removed in HA 2025.2.0.\nHome Assistant detected that entity `{entity}` is being used in `{info}`\n\nYou should remove the uptime seconds entity from `{info}` then click submit to fix this issue."
} }
} }
} }

View File

@ -1,19 +1,27 @@
"""The sensor tests for the QNAP QSW platform.""" """The sensor tests for the QNAP QSW platform."""
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from homeassistant.components.qnap_qsw.const import ATTR_MAX from homeassistant.components.qnap_qsw.const import ATTR_MAX, DOMAIN
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from .util import async_init_integration from .util import async_init_integration, init_config_entry
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_qnap_qsw_create_sensors( async def test_qnap_qsw_create_sensors(
hass: HomeAssistant, hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test creation of sensors.""" """Test creation of sensors."""
await hass.config.async_set_time_zone("UTC")
freezer.move_to("2024-07-25 12:00:00+00:00")
await async_init_integration(hass) await async_init_integration(hass)
state = hass.states.get("sensor.qsw_m408_4c_fan_1_speed") state = hass.states.get("sensor.qsw_m408_4c_fan_1_speed")
@ -45,8 +53,8 @@ async def test_qnap_qsw_create_sensors(
state = hass.states.get("sensor.qsw_m408_4c_tx_speed") state = hass.states.get("sensor.qsw_m408_4c_tx_speed")
assert state.state == "0" assert state.state == "0"
state = hass.states.get("sensor.qsw_m408_4c_uptime") state = hass.states.get("sensor.qsw_m408_4c_uptime_timestamp")
assert state.state == "91" assert state.state == "2024-07-25T11:58:29+00:00"
# LACP Ports # LACP Ports
state = hass.states.get("sensor.qsw_m408_4c_lacp_port_1_link_speed") state = hass.states.get("sensor.qsw_m408_4c_lacp_port_1_link_speed")
@ -373,3 +381,60 @@ async def test_qnap_qsw_create_sensors(
state = hass.states.get("sensor.qsw_m408_4c_port_12_tx_speed") state = hass.states.get("sensor.qsw_m408_4c_port_12_tx_speed")
assert state.state == "0" assert state.state == "0"
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_deprecated_uptime_seconds(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
issue_registry: ir.IssueRegistry,
) -> None:
"""Test deprecation warning of the Uptime seconds sensor entity."""
original_id = "sensor.qsw_m408_4c_uptime"
domain = Platform.SENSOR
config_entry = init_config_entry(hass)
entity = entity_registry.async_get_or_create(
domain=domain,
platform=DOMAIN,
unique_id=original_id,
config_entry=config_entry,
suggested_object_id=original_id,
disabled_by=None,
)
assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id)
with patch(
"homeassistant.components.qnap_qsw.sensor.automations_with_entity",
return_value=["item"],
):
await async_init_integration(hass, config_entry=config_entry)
assert issue_registry.async_get_issue(
DOMAIN, f"uptime_seconds_deprecated_{entity.entity_id}_item"
)
async def test_cleanup_deprecated_uptime_seconds(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test cleanup of the Uptime seconds sensor entity."""
original_id = "sensor.qsw_m408_4c_uptime_seconds"
domain = Platform.SENSOR
config_entry = init_config_entry(hass)
entity_registry.async_get_or_create(
domain=domain,
platform=DOMAIN,
unique_id=original_id,
config_entry=config_entry,
suggested_object_id=original_id,
disabled_by=er.RegistryEntryDisabler.USER,
)
assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id)
await async_init_integration(hass, config_entry=config_entry)

View File

@ -491,11 +491,10 @@ USERS_VERIFICATION_MOCK = {
} }
async def async_init_integration( def init_config_entry(
hass: HomeAssistant, hass: HomeAssistant,
) -> None: ) -> MockConfigEntry:
"""Set up the QNAP QSW integration in Home Assistant.""" """Set up the QNAP QSW entry in Home Assistant."""
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
data=CONFIG, data=CONFIG,
domain=DOMAIN, domain=DOMAIN,
@ -503,6 +502,18 @@ async def async_init_integration(
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
return config_entry
async def async_init_integration(
hass: HomeAssistant,
config_entry: MockConfigEntry | None = None,
) -> None:
"""Set up the QNAP QSW integration in Home Assistant."""
if config_entry is None:
config_entry = init_config_entry(hass)
with ( with (
patch( patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition", "homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition",