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

1"""Sensors reporting AutoArm's decisions and failures.""" 

2 

3import datetime as dt 

4from collections.abc import Callable 

5from dataclasses import dataclass 

6from typing import Any 

7 

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 

18 

19from .autoarming import AlarmArmer, AutoArmConfigEntry 

20from .const import ChangeSource 

21from .entity import AutoArmEntity 

22 

23PARALLEL_UPDATES = 0 

24 

25 

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] 

30 

31 

32def _status_value(key: str) -> Callable[[AlarmArmer], Any]: 

33 return lambda armer: armer.status[key].value if key in armer.status else None 

34 

35 

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 

38 

39 

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) 

71 

72 

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) 

77 

78 

79class AutoArmSensor(AutoArmEntity, SensorEntity): 

80 entity_description: AutoArmSensorEntityDescription # ty: ignore[invalid-mutable-override] 

81 

82 def __init__(self, armer: AlarmArmer, entry: AutoArmConfigEntry, description: AutoArmSensorEntityDescription) -> None: 

83 super().__init__(armer, entry, Platform.SENSOR, description) 

84 

85 @property 

86 def native_value(self) -> StateType | dt.datetime: # type: ignore 

87 return self.entity_description.value_fn(self.armer) 

88 

89 @property 

90 def extra_state_attributes(self) -> dict[str, Any] | None: # type: ignore 

91 return self.entity_description.attributes_fn(self.armer)