Coverage for custom_components/autoarm/sensor.py: 100%
31 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-26 20:57 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-26 20:57 +0000
1"""Sensors reporting AutoArm's decisions and failures."""
3import datetime as dt
4from collections.abc import Callable
5from dataclasses import dataclass
6from typing import Any
8from homeassistant.components.sensor import (
9 SensorDeviceClass,
10 SensorEntity,
11 SensorEntityDescription,
12 SensorStateClass,
13)
14from homeassistant.const import EntityCategory, Platform
15from homeassistant.core import HomeAssistant
16from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
17from homeassistant.helpers.typing import StateType
19from .autoarming import AlarmArmer, AutoArmConfigEntry
20from .const import ChangeSource
21from .entity import AutoArmEntity
23PARALLEL_UPDATES = 0
26@dataclass(frozen=True, kw_only=True)
27class AutoArmSensorEntityDescription(SensorEntityDescription):
28 value_fn: Callable[[AlarmArmer], StateType | dt.datetime]
29 attributes_fn: Callable[[AlarmArmer], dict[str, Any] | None]
32def _status_value(key: str) -> Callable[[AlarmArmer], Any]:
33 return lambda armer: armer.status[key].value if key in armer.status else None
36def _status_attributes(key: str) -> Callable[[AlarmArmer], dict[str, Any] | None]:
37 return lambda armer: armer.status[key].attributes if key in armer.status else None
40SENSORS: tuple[AutoArmSensorEntityDescription, ...] = (
41 AutoArmSensorEntityDescription(
42 key="failures",
43 translation_key="failures",
44 entity_category=EntityCategory.DIAGNOSTIC,
45 state_class=SensorStateClass.TOTAL_INCREASING,
46 value_fn=lambda armer: armer.app_health_tracker.failures,
47 attributes_fn=lambda armer: {"initialization_errors": armer.app_health_tracker.initialization_errors},
48 ),
49 AutoArmSensorEntityDescription(
50 key="last_calculation",
51 translation_key="last_calculation",
52 device_class=SensorDeviceClass.TIMESTAMP,
53 value_fn=_status_value("last_calculation"),
54 attributes_fn=_status_attributes("last_calculation"),
55 ),
56 AutoArmSensorEntityDescription(
57 key="last_intervention",
58 translation_key="last_intervention",
59 device_class=SensorDeviceClass.ENUM,
60 options=[str(source) for source in ChangeSource],
61 value_fn=_status_value("last_intervention"),
62 attributes_fn=_status_attributes("last_intervention"),
63 ),
64 AutoArmSensorEntityDescription(
65 key="last_calendar_event",
66 translation_key="last_calendar_event",
67 value_fn=_status_value("last_calendar_event"),
68 attributes_fn=_status_attributes("last_calendar_event"),
69 ),
70)
73async def async_setup_entry(
74 hass: HomeAssistant, entry: AutoArmConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback
75) -> None:
76 async_add_entities(AutoArmSensor(entry.runtime_data, entry, description) for description in SENSORS)
79class AutoArmSensor(AutoArmEntity, SensorEntity):
80 entity_description: AutoArmSensorEntityDescription # ty: ignore[invalid-mutable-override]
82 def __init__(self, armer: AlarmArmer, entry: AutoArmConfigEntry, description: AutoArmSensorEntityDescription) -> None:
83 super().__init__(armer, entry, Platform.SENSOR, description)
85 @property
86 def native_value(self) -> StateType | dt.datetime: # type: ignore
87 return self.entity_description.value_fn(self.armer)
89 @property
90 def extra_state_attributes(self) -> dict[str, Any] | None: # type: ignore
91 return self.entity_description.attributes_fn(self.armer)