mirror of
https://github.com/home-assistant/core.git
synced 2025-04-28 11:17:53 +00:00
Add new QNAP QSW uptime timestamp sensor (#122589)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
ebd1baa42c
commit
4306b0caba
@ -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()
|
||||||
|
@ -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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user