Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Sensoterra devices."""
2 
3 from __future__ import annotations
4 
5 from datetime import UTC, datetime, timedelta
6 from enum import StrEnum, auto
7 
8 from sensoterra.probe import Probe, Sensor
9 
11  SensorDeviceClass,
12  SensorEntity,
13  SensorEntityDescription,
14  SensorStateClass,
15 )
16 from homeassistant.const import (
17  PERCENTAGE,
18  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
19  EntityCategory,
20  UnitOfTemperature,
21 )
22 from homeassistant.core import HomeAssistant, callback
23 from homeassistant.helpers.device_registry import DeviceInfo
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 from homeassistant.helpers.typing import StateType
26 from homeassistant.helpers.update_coordinator import CoordinatorEntity
27 
28 from . import SensoterraConfigEntry
29 from .const import CONFIGURATION_URL, DOMAIN, SENSOR_EXPIRATION_DAYS
30 from .coordinator import SensoterraCoordinator
31 
32 
33 class ProbeSensorType(StrEnum):
34  """Generic sensors within a Sensoterra probe."""
35 
36  MOISTURE = auto()
37  SI = auto()
38  TEMPERATURE = auto()
39  BATTERY = auto()
40  RSSI = auto()
41 
42 
43 SENSORS: dict[ProbeSensorType, SensorEntityDescription] = {
44  ProbeSensorType.MOISTURE: SensorEntityDescription(
45  key=ProbeSensorType.MOISTURE,
46  state_class=SensorStateClass.MEASUREMENT,
47  suggested_display_precision=0,
48  device_class=SensorDeviceClass.MOISTURE,
49  native_unit_of_measurement=PERCENTAGE,
50  translation_key="soil_moisture_at_cm",
51  ),
52  ProbeSensorType.SI: SensorEntityDescription(
53  key=ProbeSensorType.SI,
54  state_class=SensorStateClass.MEASUREMENT,
55  suggested_display_precision=1,
56  translation_key="si_at_cm",
57  ),
58  ProbeSensorType.TEMPERATURE: SensorEntityDescription(
59  key=ProbeSensorType.TEMPERATURE,
60  state_class=SensorStateClass.MEASUREMENT,
61  suggested_display_precision=0,
62  device_class=SensorDeviceClass.TEMPERATURE,
63  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
64  ),
65  ProbeSensorType.BATTERY: SensorEntityDescription(
66  key=ProbeSensorType.BATTERY,
67  state_class=SensorStateClass.MEASUREMENT,
68  suggested_display_precision=0,
69  device_class=SensorDeviceClass.BATTERY,
70  native_unit_of_measurement=PERCENTAGE,
71  entity_category=EntityCategory.DIAGNOSTIC,
72  ),
73  ProbeSensorType.RSSI: SensorEntityDescription(
74  key=ProbeSensorType.RSSI,
75  state_class=SensorStateClass.MEASUREMENT,
76  suggested_display_precision=0,
77  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
78  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
79  entity_category=EntityCategory.DIAGNOSTIC,
80  entity_registry_enabled_default=False,
81  ),
82 }
83 
84 
86  hass: HomeAssistant,
87  entry: SensoterraConfigEntry,
88  async_add_devices: AddEntitiesCallback,
89 ) -> None:
90  """Set up Sensoterra sensor."""
91 
92  coordinator = entry.runtime_data
93 
94  @callback
95  def _async_add_devices(probes: list[Probe]) -> None:
96  aha = coordinator.async_contexts()
97  current_sensors = set(aha)
100  coordinator,
101  probe,
102  sensor,
103  SENSORS[ProbeSensorType[sensor.type]],
104  )
105  for probe in probes
106  for sensor in probe.sensors()
107  if sensor.type is not None
108  and sensor.type.lower() in SENSORS
109  and sensor.id not in current_sensors
110  )
111 
112  coordinator.add_devices_callback = _async_add_devices
113 
114  _async_add_devices(coordinator.data)
115 
116 
117 class SensoterraEntity(CoordinatorEntity[SensoterraCoordinator], SensorEntity):
118  """Sensoterra sensor like a soil moisture or temperature sensor."""
119 
120  _attr_has_entity_name = True
121 
122  def __init__(
123  self,
124  coordinator: SensoterraCoordinator,
125  probe: Probe,
126  sensor: Sensor,
127  entity_description: SensorEntityDescription,
128  ) -> None:
129  """Initialize entity."""
130  super().__init__(coordinator, context=sensor.id)
131 
132  self._sensor_id_sensor_id = sensor.id
133  self._attr_unique_id_attr_unique_id = self._sensor_id_sensor_id
134  self._attr_translation_placeholders_attr_translation_placeholders = {
135  "depth": "?" if sensor.depth is None else str(sensor.depth)
136  }
137 
138  self.entity_descriptionentity_description = entity_description
139 
140  self._attr_device_info_attr_device_info = DeviceInfo(
141  identifiers={(DOMAIN, probe.serial)},
142  name=probe.name,
143  model=probe.sku,
144  manufacturer="Sensoterra",
145  serial_number=probe.serial,
146  suggested_area=probe.location,
147  configuration_url=CONFIGURATION_URL,
148  )
149 
150  @property
151  def sensor(self) -> Sensor | None:
152  """Return the sensor, or None if it doesn't exist."""
153  return self.coordinator.get_sensor(self._sensor_id_sensor_id)
154 
155  @property
156  def native_value(self) -> StateType:
157  """Return the value reported by the sensor."""
158  assert self.sensorsensor
159  return self.sensorsensor.value
160 
161  @property
162  def available(self) -> bool:
163  """Return True if entity is available."""
164  if not super().available or (sensor := self.sensorsensor) is None:
165  return False
166 
167  if sensor.timestamp is None:
168  return False
169 
170  # Expire sensor if no update within the last few days.
171  expiration = datetime.now(UTC) - timedelta(days=SENSOR_EXPIRATION_DAYS)
172  return sensor.timestamp >= expiration
None __init__(self, SensoterraCoordinator coordinator, Probe probe, Sensor sensor, SensorEntityDescription entity_description)
Definition: sensor.py:128
def async_add_devices(address, multiple)
Definition: device.py:37
None async_setup_entry(HomeAssistant hass, SensoterraConfigEntry entry, AddEntitiesCallback async_add_devices)
Definition: sensor.py:89