mirror of
https://github.com/home-assistant/core.git
synced 2026-04-20 08:29:39 +02:00
Deprecate useless sensors in APCUPSD integration (#151525)
This commit is contained in:
@@ -7,3 +7,26 @@ CONNECTION_TIMEOUT: int = 10
|
||||
|
||||
# Field name of last self test retrieved from apcupsd.
|
||||
LAST_S_TEST: Final = "laststest"
|
||||
|
||||
# Mapping of deprecated sensor keys (as reported by apcupsd, lower-cased) to their deprecation
|
||||
# repair issue translation keys.
|
||||
DEPRECATED_SENSORS: Final = {
|
||||
"apc": "apc_deprecated",
|
||||
"end apc": "date_deprecated",
|
||||
"date": "date_deprecated",
|
||||
"apcmodel": "available_via_device_info",
|
||||
"model": "available_via_device_info",
|
||||
"firmware": "available_via_device_info",
|
||||
"version": "available_via_device_info",
|
||||
"upsname": "available_via_device_info",
|
||||
"serialno": "available_via_device_info",
|
||||
}
|
||||
|
||||
AVAILABLE_VIA_DEVICE_ATTR: Final = {
|
||||
"apcmodel": "model",
|
||||
"model": "model",
|
||||
"firmware": "hw_version",
|
||||
"version": "sw_version",
|
||||
"upsname": "name",
|
||||
"serialno": "serial_number",
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.automation import automations_with_entity
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
@@ -22,9 +24,11 @@ from homeassistant.const import (
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
import homeassistant.helpers.issue_registry as ir
|
||||
|
||||
from .const import LAST_S_TEST
|
||||
from .const import AVAILABLE_VIA_DEVICE_ATTR, DEPRECATED_SENSORS, DOMAIN, LAST_S_TEST
|
||||
from .coordinator import APCUPSdConfigEntry, APCUPSdCoordinator
|
||||
from .entity import APCUPSdEntity
|
||||
|
||||
@@ -528,3 +532,62 @@ class APCUPSdSensor(APCUPSdEntity, SensorEntity):
|
||||
self._attr_native_value, inferred_unit = infer_unit(self.coordinator.data[key])
|
||||
if not self.native_unit_of_measurement:
|
||||
self._attr_native_unit_of_measurement = inferred_unit
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle when entity is added to Home Assistant.
|
||||
|
||||
If this is a deprecated sensor entity, create a repair issue to guide
|
||||
the user to disable it.
|
||||
"""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
reason = DEPRECATED_SENSORS.get(self.entity_description.key)
|
||||
if not reason:
|
||||
return
|
||||
|
||||
automations = automations_with_entity(self.hass, self.entity_id)
|
||||
scripts = scripts_with_entity(self.hass, self.entity_id)
|
||||
if not automations and not scripts:
|
||||
return
|
||||
|
||||
entity_registry = er.async_get(self.hass)
|
||||
items = [
|
||||
f"- [{entry.name or entry.original_name or entity_id}]"
|
||||
f"(/config/{integration}/edit/{entry.unique_id or entity_id.split('.', 1)[-1]})"
|
||||
for integration, entities in (
|
||||
("automation", automations),
|
||||
("script", scripts),
|
||||
)
|
||||
for entity_id in entities
|
||||
if (entry := entity_registry.async_get(entity_id))
|
||||
]
|
||||
placeholders = {
|
||||
"entity_name": str(self.name or self.entity_id),
|
||||
"entity_id": self.entity_id,
|
||||
"items": "\n".join(items),
|
||||
}
|
||||
if via_attr := AVAILABLE_VIA_DEVICE_ATTR.get(self.entity_description.key):
|
||||
placeholders["available_via_device_attr"] = via_attr
|
||||
if device_entry := self.device_entry:
|
||||
placeholders["device_id"] = device_entry.id
|
||||
|
||||
ir.async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"{reason}_{self.entity_id}",
|
||||
breaks_in_ha_version="2026.6.0",
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key=reason,
|
||||
translation_placeholders=placeholders,
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Handle when entity will be removed from Home Assistant."""
|
||||
await super().async_will_remove_from_hass()
|
||||
|
||||
if issue_key := DEPRECATED_SENSORS.get(self.entity_description.key):
|
||||
ir.async_delete_issue(self.hass, DOMAIN, f"{issue_key}_{self.entity_id}")
|
||||
|
||||
@@ -241,5 +241,19 @@
|
||||
"cannot_connect": {
|
||||
"message": "Cannot connect to APC UPS Daemon."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"apc_deprecated": {
|
||||
"description": "The {entity_name} sensor (`{entity_id}`) is deprecated because it exposes internal details of the APC UPS Daemon response.\n\nIt is still referenced in the following automations or scripts:\n{items}\n\nUpdate those automations or scripts to use supported APC UPS entities instead. Reload the APC UPS Daemon integration afterwards to resolve this issue.",
|
||||
"title": "{entity_name} sensor is deprecated"
|
||||
},
|
||||
"available_via_device_info": {
|
||||
"description": "The {entity_name} sensor (`{entity_id}`) is deprecated because the same value is available from the device registry via `device_attr(\"{device_id}\", \"{available_via_device_attr}\")`.\n\nIt is still referenced in the following automations or scripts:\n{items}\n\nUpdate those automations or scripts to use the `device_attr` helper instead of this sensor. Reload the APC UPS Daemon integration afterwards to resolve this issue.",
|
||||
"title": "{entity_name} sensor is deprecated"
|
||||
},
|
||||
"date_deprecated": {
|
||||
"description": "The {entity_name} sensor (`{entity_id}`) is deprecated because the timestamp is already available from other APC UPS sensors via their last updated time.\n\nIt is still referenced in the following automations or scripts:\n{items}\n\nUpdate those automations or scripts to reference any entity's `last_updated` attribute instead (for example, `states.binary_sensor.apcups_online_status.last_updated`). Reload the APC UPS Daemon integration afterwards to resolve this issue.",
|
||||
"title": "{entity_name} sensor is deprecated"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,244 @@
|
||||
# serializer version: 1
|
||||
# name: test_deprecated_sensor_issue[apc-apc_deprecated]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'apc_deprecated_sensor.myups_status_data',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'apc_deprecated',
|
||||
'translation_placeholders': dict({
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_status_data',
|
||||
'entity_name': 'Status data',
|
||||
'items': '''
|
||||
- [APC UPS automation (apc)](/config/automation/edit/apcupsd_auto_apc)
|
||||
- [APC UPS script (apc)](/config/script/edit/apcupsd_script_apc)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[apcmodel-available_via_device_info]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'available_via_device_info_sensor.myups_model',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'available_via_device_info',
|
||||
'translation_placeholders': dict({
|
||||
'available_via_device_attr': 'model',
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_model',
|
||||
'entity_name': 'Model',
|
||||
'items': '''
|
||||
- [APC UPS automation (apcmodel)](/config/automation/edit/apcupsd_auto_apcmodel)
|
||||
- [APC UPS script (apcmodel)](/config/script/edit/apcupsd_script_apcmodel)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[date-date_deprecated]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'date_deprecated_sensor.myups_status_date',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'date_deprecated',
|
||||
'translation_placeholders': dict({
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_status_date',
|
||||
'entity_name': 'Status date',
|
||||
'items': '''
|
||||
- [APC UPS automation (date)](/config/automation/edit/apcupsd_auto_date)
|
||||
- [APC UPS script (date)](/config/script/edit/apcupsd_script_date)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[end apc-date_deprecated]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'date_deprecated_sensor.myups_date_and_time',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'date_deprecated',
|
||||
'translation_placeholders': dict({
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_date_and_time',
|
||||
'entity_name': 'Date and time',
|
||||
'items': '''
|
||||
- [APC UPS automation (end apc)](/config/automation/edit/apcupsd_auto_end_apc)
|
||||
- [APC UPS script (end apc)](/config/script/edit/apcupsd_script_end_apc)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[firmware-available_via_device_info]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'available_via_device_info_sensor.myups_firmware_version',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'available_via_device_info',
|
||||
'translation_placeholders': dict({
|
||||
'available_via_device_attr': 'hw_version',
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_firmware_version',
|
||||
'entity_name': 'Firmware version',
|
||||
'items': '''
|
||||
- [APC UPS automation (firmware)](/config/automation/edit/apcupsd_auto_firmware)
|
||||
- [APC UPS script (firmware)](/config/script/edit/apcupsd_script_firmware)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[model-available_via_device_info]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'available_via_device_info_sensor.myups_model_2',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'available_via_device_info',
|
||||
'translation_placeholders': dict({
|
||||
'available_via_device_attr': 'model',
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_model_2',
|
||||
'entity_name': 'Model',
|
||||
'items': '''
|
||||
- [APC UPS automation (model)](/config/automation/edit/apcupsd_auto_model)
|
||||
- [APC UPS script (model)](/config/script/edit/apcupsd_script_model)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[serialno-available_via_device_info]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'available_via_device_info_sensor.myups_serial_number',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'available_via_device_info',
|
||||
'translation_placeholders': dict({
|
||||
'available_via_device_attr': 'serial_number',
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_serial_number',
|
||||
'entity_name': 'Serial number',
|
||||
'items': '''
|
||||
- [APC UPS automation (serialno)](/config/automation/edit/apcupsd_auto_serialno)
|
||||
- [APC UPS script (serialno)](/config/script/edit/apcupsd_script_serialno)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[upsname-available_via_device_info]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'available_via_device_info_sensor.myups_name',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'available_via_device_info',
|
||||
'translation_placeholders': dict({
|
||||
'available_via_device_attr': 'name',
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_name',
|
||||
'entity_name': 'Name',
|
||||
'items': '''
|
||||
- [APC UPS automation (upsname)](/config/automation/edit/apcupsd_auto_upsname)
|
||||
- [APC UPS script (upsname)](/config/script/edit/apcupsd_script_upsname)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_deprecated_sensor_issue[version-available_via_device_info]
|
||||
IssueRegistryItemSnapshot({
|
||||
'active': True,
|
||||
'breaks_in_ha_version': '2026.6.0',
|
||||
'created': <ANY>,
|
||||
'data': None,
|
||||
'dismissed_version': None,
|
||||
'domain': 'apcupsd',
|
||||
'is_fixable': False,
|
||||
'is_persistent': False,
|
||||
'issue_domain': None,
|
||||
'issue_id': 'available_via_device_info_sensor.myups_daemon_version',
|
||||
'learn_more_url': None,
|
||||
'severity': <IssueSeverity.WARNING: 'warning'>,
|
||||
'translation_key': 'available_via_device_info',
|
||||
'translation_placeholders': dict({
|
||||
'available_via_device_attr': 'sw_version',
|
||||
'device_id': '<ANY>',
|
||||
'entity_id': 'sensor.myups_daemon_version',
|
||||
'entity_name': 'Daemon version',
|
||||
'items': '''
|
||||
- [APC UPS automation (version)](/config/automation/edit/apcupsd_auto_version)
|
||||
- [APC UPS script (version)](/config/script/edit/apcupsd_script_version)
|
||||
''',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.myups_alarm_delay-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -6,7 +6,8 @@ from unittest.mock import AsyncMock
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.apcupsd.const import DOMAIN
|
||||
from homeassistant.components import automation, script
|
||||
from homeassistant.components.apcupsd.const import DEPRECATED_SENSORS, DOMAIN
|
||||
from homeassistant.components.apcupsd.coordinator import REQUEST_REFRESH_COOLDOWN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@@ -15,7 +16,11 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.dt import utcnow
|
||||
@@ -161,3 +166,76 @@ async def test_sensor_unknown(
|
||||
await hass.async_block_till_done()
|
||||
# The state should become unknown again.
|
||||
assert hass.states.get(last_self_test_id).state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("entity_key", "issue_key"), DEPRECATED_SENSORS.items())
|
||||
async def test_deprecated_sensor_issue(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_request_status: AsyncMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_key: str,
|
||||
issue_key: str,
|
||||
) -> None:
|
||||
"""Ensure the issue lists automations and scripts referencing a deprecated sensor."""
|
||||
issue_registry = ir.async_get(hass)
|
||||
unique_id = f"{mock_request_status.return_value['SERIALNO']}_{entity_key}"
|
||||
entity_id = entity_registry.async_get_entity_id("sensor", DOMAIN, unique_id)
|
||||
assert entity_id
|
||||
|
||||
# No issue yet.
|
||||
issue_id = f"{issue_key}_{entity_id}"
|
||||
assert issue_registry.async_get_issue(DOMAIN, issue_id) is None
|
||||
|
||||
# Add automations and scripts referencing the deprecated sensor.
|
||||
entity_slug = slugify(entity_key)
|
||||
automation_object_id = f"apcupsd_auto_{entity_slug}"
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"id": automation_object_id,
|
||||
"alias": f"APC UPS automation ({entity_key})",
|
||||
"trigger": {"platform": "state", "entity_id": entity_id},
|
||||
"action": {
|
||||
"action": "automation.turn_on",
|
||||
"target": {"entity_id": f"automation.{automation_object_id}"},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
script.DOMAIN,
|
||||
{
|
||||
script.DOMAIN: {
|
||||
f"apcupsd_script_{entity_slug}": {
|
||||
"alias": f"APC UPS script ({entity_key})",
|
||||
"sequence": [
|
||||
{
|
||||
"condition": "state",
|
||||
"entity_id": entity_id,
|
||||
"state": "on",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
issue = issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
# Redact the device ID in the placeholder for consistency.
|
||||
issue.translation_placeholders["device_id"] = "<ANY>"
|
||||
assert issue == snapshot
|
||||
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Assert the issue is no longer present.
|
||||
assert not issue_registry.async_get_issue(DOMAIN, issue_id)
|
||||
assert len(issue_registry.issues) == 0
|
||||
|
||||
Reference in New Issue
Block a user