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 collections.abc import Callable
from dataclasses import dataclass, replace
from datetime import datetime
from typing import Final
from aioqsw.const import (
@ -26,8 +28,11 @@ from aioqsw.const import (
QSD_TX_OCTETS,
QSD_TX_SPEED,
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 (
SensorDeviceClass,
SensorEntity,
@ -43,8 +48,10 @@ from homeassistant.const import (
UnitOfTime,
)
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.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 .coordinator import QswDataCoordinator
@ -58,6 +65,17 @@ class QswSensorEntityDescription(SensorEntityDescription, QswEntityDescription):
attributes: dict[str, list[str]] | None = None
qsw_type: QswEntityType | None = None
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, ...]] = (
@ -140,12 +158,12 @@ SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = (
subkey=QSD_TX_SPEED,
),
QswSensorEntityDescription(
translation_key="uptime",
translation_key="uptime_timestamp",
key=QSD_SYSTEM_TIME,
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfTime.SECONDS,
state_class=SensorStateClass.TOTAL_INCREASING,
subkey=QSD_UPTIME_SECONDS,
subkey=QSD_UPTIME_TIMESTAMP,
value_fn=dt_util.parse_datetime,
),
)
@ -337,6 +355,46 @@ async def async_setup_entry(
)
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)
@ -374,5 +432,5 @@ class QswSensor(QswSensorEntity, SensorEntity):
self.entity_description.subkey,
self.entity_description.qsw_type,
)
self._attr_native_value = value
self._attr_native_value = self.entity_description.value_fn(value)
super()._async_update_attrs()

View File

@ -52,7 +52,16 @@
},
"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."""
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
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.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")
async def test_qnap_qsw_create_sensors(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
) -> None:
"""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)
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")
assert state.state == "0"
state = hass.states.get("sensor.qsw_m408_4c_uptime")
assert state.state == "91"
state = hass.states.get("sensor.qsw_m408_4c_uptime_timestamp")
assert state.state == "2024-07-25T11:58:29+00:00"
# LACP Ports
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")
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,
) -> None:
"""Set up the QNAP QSW integration in Home Assistant."""
) -> MockConfigEntry:
"""Set up the QNAP QSW entry in Home Assistant."""
config_entry = MockConfigEntry(
data=CONFIG,
domain=DOMAIN,
@ -503,6 +502,18 @@ async def async_init_integration(
)
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 (
patch(
"homeassistant.components.qnap_qsw.QnapQswApi.get_firmware_condition",