mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add graceful handling for LASTSTEST sensor in APCUPSD (#113125)
* Add handling for LASTSTEST sensor * Set the state to unknown instead of unavailable * Use LASTSTEST constant and revise the logic to add it to the entity list * Use LASTSTEST constant
This commit is contained in:
parent
c8dccec956
commit
bf3a2cf393
@ -4,3 +4,6 @@ from typing import Final
|
||||
|
||||
DOMAIN: Final = "apcupsd"
|
||||
CONNECTION_TIMEOUT: int = 10
|
||||
|
||||
# Field name of last self test retrieved from apcupsd.
|
||||
LASTSTEST: Final = "laststest"
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
STATE_UNKNOWN,
|
||||
UnitOfApparentPower,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
@ -25,7 +26,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, LASTSTEST
|
||||
from .coordinator import APCUPSdCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -156,8 +157,8 @@ SENSORS: dict[str, SensorEntityDescription] = {
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"laststest": SensorEntityDescription(
|
||||
key="laststest",
|
||||
LASTSTEST: SensorEntityDescription(
|
||||
key=LASTSTEST,
|
||||
translation_key="last_self_test",
|
||||
),
|
||||
"lastxfer": SensorEntityDescription(
|
||||
@ -417,7 +418,12 @@ async def async_setup_entry(
|
||||
available_resources: set[str] = {k.lower() for k, _ in coordinator.data.items()}
|
||||
|
||||
entities = []
|
||||
for resource in available_resources:
|
||||
|
||||
# "laststest" is a special sensor that only appears when the APC UPS daemon has done a
|
||||
# periodical (or manual) self test since last daemon restart. It might not be available
|
||||
# when we set up the integration, and we do not know if it would ever be available. Here we
|
||||
# add it anyway and mark it as unknown initially.
|
||||
for resource in available_resources | {LASTSTEST}:
|
||||
if resource not in SENSORS:
|
||||
_LOGGER.warning("Invalid resource from APCUPSd: %s", resource.upper())
|
||||
continue
|
||||
@ -473,6 +479,14 @@ class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
|
||||
def _update_attrs(self) -> None:
|
||||
"""Update sensor attributes based on coordinator data."""
|
||||
key = self.entity_description.key.upper()
|
||||
# For most sensors the key will always be available for each refresh. However, some sensors
|
||||
# (e.g., "laststest") will only appear after certain event occurs (e.g., a self test is
|
||||
# performed) and may disappear again after certain event. So we mark the state as "unknown"
|
||||
# when it becomes unknown after such events.
|
||||
if key not in self.coordinator.data:
|
||||
self._attr_native_value = STATE_UNKNOWN
|
||||
return
|
||||
|
||||
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
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
PERCENTAGE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfPower,
|
||||
UnitOfTime,
|
||||
@ -25,7 +26,7 @@ from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import MOCK_STATUS, async_init_integration
|
||||
from . import MOCK_MINIMAL_STATUS, MOCK_STATUS, async_init_integration
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
@ -237,3 +238,34 @@ async def test_multiple_manual_update_entity(hass: HomeAssistant) -> None:
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_request_status.call_count == 1
|
||||
|
||||
|
||||
async def test_sensor_unknown(hass: HomeAssistant) -> None:
|
||||
"""Test if our integration can properly certain sensors as unknown when it becomes so."""
|
||||
await async_init_integration(hass, status=MOCK_MINIMAL_STATUS)
|
||||
|
||||
assert hass.states.get("sensor.mode").state == MOCK_MINIMAL_STATUS["UPSMODE"]
|
||||
# Last self test sensor should be added even if our status does not report it initially (it is
|
||||
# a sensor that appears only after a periodical or manual self test is performed).
|
||||
assert hass.states.get("sensor.last_self_test") is not None
|
||||
assert hass.states.get("sensor.last_self_test").state == STATE_UNKNOWN
|
||||
|
||||
# Simulate an event (a self test) such that "LASTSTEST" field is being reported, the state of
|
||||
# the sensor should be properly updated with the corresponding value.
|
||||
with patch("aioapcaccess.request_status") as mock_request_status:
|
||||
mock_request_status.return_value = MOCK_MINIMAL_STATUS | {
|
||||
"LASTSTEST": "1970-01-01 00:00:00 0000"
|
||||
}
|
||||
future = utcnow() + timedelta(minutes=2)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get("sensor.last_self_test").state == "1970-01-01 00:00:00 0000"
|
||||
|
||||
# Simulate another event (e.g., daemon restart) such that "LASTSTEST" is no longer reported.
|
||||
with patch("aioapcaccess.request_status") as mock_request_status:
|
||||
mock_request_status.return_value = MOCK_MINIMAL_STATUS
|
||||
future = utcnow() + timedelta(minutes=2)
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
# The state should become unknown again.
|
||||
assert hass.states.get("sensor.last_self_test").state == STATE_UNKNOWN
|
||||
|
Loading…
x
Reference in New Issue
Block a user